¶Ô±ÈÐÂÎļþ |
| | |
| | | // 设å¤åç管ç - æ¬å°åæ°æ® APIï¼ä½¿ç¨ localStorage æä¹
åï¼ |
| | | |
| | | const STORAGE_KEY = 'EQUIPMENT_BRANDS'; |
| | | |
| | | function readStore() { |
| | | try { |
| | | const raw = localStorage.getItem(STORAGE_KEY); |
| | | if (raw) { |
| | | const parsed = JSON.parse(raw); |
| | | if (Array.isArray(parsed)) return parsed; |
| | | } |
| | | } catch (e) { |
| | | // ignore |
| | | } |
| | | // åå§åä¸äºç¤ºä¾æ°æ® |
| | | const initial = [ |
| | | { id: 1, name: '西é¨å', country: 'å¾·å½', description: 'å·¥ä¸èªå¨åä¸çµæ°å·¥ç¨åç', createdAt: Date.now() - 86400000 * 10 }, |
| | | { id: 2, name: 'æ½èå¾·', country: 'æ³å½', description: 'è½æºç®¡çä¸èªå¨å', createdAt: Date.now() - 86400000 * 7 }, |
| | | { id: 3, name: 'ä¸è±çµæº', country: 'æ¥æ¬', description: 'çµæ°ä¸èªå¨å设å¤', createdAt: Date.now() - 86400000 * 3 }, |
| | | ]; |
| | | localStorage.setItem(STORAGE_KEY, JSON.stringify(initial)); |
| | | return initial; |
| | | } |
| | | |
| | | function writeStore(list) { |
| | | localStorage.setItem(STORAGE_KEY, JSON.stringify(list)); |
| | | } |
| | | |
| | | function nextId(list) { |
| | | const maxId = list.reduce((max, item) => Math.max(max, Number(item.id) || 0), 0); |
| | | return maxId + 1; |
| | | } |
| | | |
| | | export function getBrandPage(params = {}) { |
| | | const { current = 1, size = 10, name } = params; |
| | | const list = readStore(); |
| | | let filtered = list; |
| | | if (name) { |
| | | const kw = String(name).trim(); |
| | | filtered = filtered.filter((b) => |
| | | (b.name && b.name.includes(kw)) || (b.country && b.country.includes(kw)) |
| | | ); |
| | | } |
| | | const start = (current - 1) * size; |
| | | const end = start + Number(size); |
| | | const records = filtered.slice(start, end); |
| | | return Promise.resolve({ |
| | | code: 200, |
| | | data: { |
| | | total: filtered.length, |
| | | records, |
| | | }, |
| | | msg: 'ok', |
| | | }); |
| | | } |
| | | |
| | | export function getBrandById(id) { |
| | | const list = readStore(); |
| | | const item = list.find((i) => String(i.id) === String(id)); |
| | | return Promise.resolve({ code: 200, data: item || null, msg: 'ok' }); |
| | | } |
| | | |
| | | export function addBrand(data) { |
| | | const list = readStore(); |
| | | const item = { ...data }; |
| | | item.id = nextId(list); |
| | | item.createdAt = Date.now(); |
| | | list.unshift(item); |
| | | writeStore(list); |
| | | return Promise.resolve({ code: 200, data: item, msg: 'æ°å¢æå' }); |
| | | } |
| | | |
| | | export function editBrand(data) { |
| | | const list = readStore(); |
| | | const index = list.findIndex((i) => String(i.id) === String(data.id)); |
| | | if (index !== -1) { |
| | | list[index] = { ...list[index], ...data }; |
| | | writeStore(list); |
| | | return Promise.resolve({ code: 200, data: list[index], msg: 'ä¿®æ¹æå' }); |
| | | } |
| | | return Promise.resolve({ code: 404, data: null, msg: 'æªæ¾å°è¯¥åç' }); |
| | | } |
| | | |
| | | export function delBrand(idOrIds) { |
| | | const list = readStore(); |
| | | const ids = Array.isArray(idOrIds) ? idOrIds.map(String) : [String(idOrIds)]; |
| | | const newList = list.filter((i) => !ids.includes(String(i.id))); |
| | | writeStore(newList); |
| | | return Promise.resolve({ code: 200, data: null, msg: 'å 餿å' }); |
| | | } |
| | | |
| | | |
| | |
| | | <template v-for="(o, key) in item.operation" :key="key"> |
| | | <el-button |
| | | v-show="o.type != 'upload'" |
| | | size="small" |
| | | v-if="o.showHide ? o.showHide(scope.row) : true" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
| | | :plain="o.plain" |
| | |
| | | (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id) |
| | | " |
| | | ref="uploadRef" |
| | | size="small" |
| | | :multiple="o.multiple ? o.multiple : false" |
| | | :limit="1" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
| | |
| | | :show-file-list="false" |
| | | > |
| | | <el-button |
| | | :size="o.size ? o.size : 'small'" |
| | | link |
| | | type="primary" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="qr-code-generator"> |
| | | <!-- äºç»´ç çæè¡¨å --> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="qr-form"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ è¯ç±»å" prop="type"> |
| | | <el-select v-model="form.type" placeholder="è¯·éæ©æ è¯ç±»å" style="width: 100%"> |
| | | <el-option label="äºç»´ç " value="qrcode"></el-option> |
| | | <el-option label="é²ä¼ªç " value="security"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
容" prop="content"> |
| | | <el-input |
| | | v-model="form.content" |
| | | placeholder="请è¾å
¥è¦ç¼ç çå
容" |
| | | :type="form.type === 'security' ? 'textarea' : 'text'" |
| | | :rows="form.type === 'security' ? 3 : 1" |
| | | ></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="尺寸" prop="size"> |
| | | <el-input-number |
| | | v-model="form.size" |
| | | :min="100" |
| | | :max="500" |
| | | :step="50" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¾¹è·" prop="margin"> |
| | | <el-input-number |
| | | v-model="form.margin" |
| | | :min="0" |
| | | :max="10" |
| | | :step="1" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="忝è²" prop="foregroundColor"> |
| | | <el-color-picker v-model="form.foregroundColor" style="width: 100%"></el-color-picker> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èæ¯è²" prop="backgroundColor"> |
| | | <el-color-picker v-model="form.backgroundColor" style="width: 100%"></el-color-picker> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="generateCode" :loading="generating"> |
| | | çæ{{ form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç ' }} |
| | | </el-button> |
| | | <el-button @click="resetForm">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <!-- çæçç æ¾ç¤ºåºå --> |
| | | <div v-if="generatedCodeUrl" class="code-display"> |
| | | <el-divider content-position="center"> |
| | | {{ form.type === 'qrcode' ? 'çæçäºç»´ç ' : 'çæçé²ä¼ªç ' }} |
| | | </el-divider> |
| | | |
| | | <div class="code-container"> |
| | | <div class="code-image"> |
| | | <img :src="generatedCodeUrl" :alt="form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '" /> |
| | | </div> |
| | | |
| | | <div class="code-info"> |
| | | <p><strong>å
容ï¼</strong>{{ form.content }}</p> |
| | | <p><strong>ç±»åï¼</strong>{{ form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç ' }}</p> |
| | | <p><strong>尺寸ï¼</strong>{{ form.size }}x{{ form.size }}px</p> |
| | | <p><strong>çææ¶é´ï¼</strong>{{ generateTime }}</p> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="code-actions"> |
| | | <el-button type="success" @click="downloadCode" icon="Download"> |
| | | ä¸è½½å¾ç |
| | | </el-button> |
| | | <el-button type="primary" @click="copyToClipboard" icon="CopyDocument"> |
| | | å¤å¶å
容 |
| | | </el-button> |
| | | <el-button @click="printCode" icon="Printer"> |
| | | æå° |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ¹éçæå¯¹è¯æ¡ --> |
| | | <el-dialog v-model="batchDialogVisible" title="æ¹éçæ" width="600px"> |
| | | <el-form :model="batchForm" label-width="120px"> |
| | | <el-form-item label="çææ°é"> |
| | | <el-input-number v-model="batchForm.quantity" :min="1" :max="100" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | <el-form-item label="åç¼"> |
| | | <el-input v-model="batchForm.prefix" placeholder="请è¾å
¥åç¼ï¼å¦ï¼PROD_"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="èµ·å§ç¼å·"> |
| | | <el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="batchDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="generateBatchCodes">å¼å§çæ</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ¹éçæç»æ --> |
| | | <div v-if="batchCodes.length > 0" class="batch-results"> |
| | | <el-divider content-position="center">æ¹éçæç»æ</el-divider> |
| | | |
| | | <div class="batch-grid"> |
| | | <div |
| | | v-for="(code, index) in batchCodes" |
| | | :key="index" |
| | | class="batch-item" |
| | | > |
| | | <img :src="code.url" :alt="code.content" /> |
| | | <p class="batch-content">{{ code.content }}</p> |
| | | <el-button size="small" @click="downloadSingleCode(code)">ä¸è½½</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="batch-actions"> |
| | | <el-button type="success" @click="downloadAllCodes">ä¸è½½å
¨é¨</el-button> |
| | | <el-button @click="clearBatchCodes">æ¸
ç©ºç»æ</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from 'vue' |
| | | import QRCode from 'qrcode' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Download, CopyDocument, Printer } from '@element-plus/icons-vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'QRCodeGenerator' |
| | | }) |
| | | |
| | | // è¡¨åæ°æ® |
| | | const form = reactive({ |
| | | type: 'qrcode', |
| | | content: '', |
| | | size: 200, |
| | | margin: 2, |
| | | foregroundColor: '#000000', |
| | | backgroundColor: '#FFFFFF' |
| | | }) |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | type: [{ required: true, message: 'è¯·éæ©æ è¯ç±»å', trigger: 'change' }], |
| | | content: [{ required: true, message: '请è¾å
¥å
容', trigger: 'blur' }] |
| | | } |
| | | |
| | | // ååºå¼æ°æ® |
| | | const formRef = ref() |
| | | const generating = ref(false) |
| | | const generatedCodeUrl = ref('') |
| | | const generateTime = ref('') |
| | | const batchDialogVisible = ref(false) |
| | | const batchForm = reactive({ |
| | | quantity: 10, |
| | | prefix: '', |
| | | startNumber: 1 |
| | | }) |
| | | const batchCodes = ref([]) |
| | | |
| | | // çæäºç»´ç æé²ä¼ªç |
| | | const generateCode = async () => { |
| | | try { |
| | | await formRef.value.validate() |
| | | |
| | | if (!form.content.trim()) { |
| | | ElMessage.warning('请è¾å
¥è¦ç¼ç çå
容') |
| | | return |
| | | } |
| | | |
| | | generating.value = true |
| | | |
| | | if (form.type === 'qrcode') { |
| | | // çæäºç»´ç |
| | | generatedCodeUrl.value = await QRCode.toDataURL(form.content, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | }, |
| | | errorCorrectionLevel: 'M' |
| | | }) |
| | | } else { |
| | | // çæé²ä¼ªç ï¼ä½¿ç¨äºç»´ç ææ¯ï¼ä½å
å®¹æ ¼å¼ä¸åï¼ |
| | | const securityContent = generateSecurityCode(form.content) |
| | | generatedCodeUrl.value = await QRCode.toDataURL(securityContent, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | }, |
| | | errorCorrectionLevel: 'H' // é²ä¼ªç ä½¿ç¨æé«çº éçº§å« |
| | | }) |
| | | } |
| | | |
| | | generateTime.value = new Date().toLocaleString() |
| | | ElMessage.success('çææåï¼') |
| | | |
| | | } catch (error) { |
| | | console.error('çæå¤±è´¥:', error) |
| | | ElMessage.error('çæå¤±è´¥ï¼' + error.message) |
| | | } finally { |
| | | generating.value = false |
| | | } |
| | | } |
| | | |
| | | // çæé²ä¼ªç å
容 |
| | | const generateSecurityCode = (content) => { |
| | | const timestamp = Date.now() |
| | | const random = Math.random().toString(36).substr(2, 8) |
| | | return `SEC_${content}_${timestamp}_${random}` |
| | | } |
| | | |
| | | // ä¸è½½çæçç |
| | | const downloadCode = () => { |
| | | if (!generatedCodeUrl.value) { |
| | | ElMessage.warning('请å
çæç ') |
| | | return |
| | | } |
| | | |
| | | const a = document.createElement('a') |
| | | a.href = generatedCodeUrl.value |
| | | a.download = `${form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}_${new Date().getTime()}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | ElMessage.success('ä¸è½½æåï¼') |
| | | } |
| | | |
| | | // å¤å¶å
容å°åªè´´æ¿ |
| | | const copyToClipboard = async () => { |
| | | try { |
| | | await navigator.clipboard.writeText(form.content) |
| | | ElMessage.success('å
容已å¤å¶å°åªè´´æ¿') |
| | | } catch (error) { |
| | | // éçº§æ¹æ¡ |
| | | const textArea = document.createElement('textarea') |
| | | textArea.value = form.content |
| | | document.body.appendChild(textArea) |
| | | textArea.select() |
| | | document.execCommand('copy') |
| | | document.body.removeChild(textArea) |
| | | ElMessage.success('å
容已å¤å¶å°åªè´´æ¿') |
| | | } |
| | | } |
| | | |
| | | // æå°ç |
| | | const printCode = () => { |
| | | if (!generatedCodeUrl.value) { |
| | | ElMessage.warning('请å
çæç ') |
| | | return |
| | | } |
| | | |
| | | const printWindow = window.open('', '_blank') |
| | | printWindow.document.write(` |
| | | <html> |
| | | <head> |
| | | <title>æå°${form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}</title> |
| | | <style> |
| | | body { text-align: center; padding: 20px; } |
| | | img { max-width: 100%; height: auto; } |
| | | .info { margin: 20px 0; } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h2>${form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}</h2> |
| | | <img src="${generatedCodeUrl.value}" alt="${form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}" /> |
| | | <div class="info"> |
| | | <p><strong>å
容ï¼</strong>${form.content}</p> |
| | | <p><strong>çææ¶é´ï¼</strong>${generateTime.value}</p> |
| | | </div> |
| | | </body> |
| | | </html> |
| | | `) |
| | | printWindow.document.close() |
| | | printWindow.print() |
| | | } |
| | | |
| | | // é置表å |
| | | const resetForm = () => { |
| | | formRef.value.resetFields() |
| | | generatedCodeUrl.value = '' |
| | | generateTime.value = '' |
| | | batchCodes.value = [] |
| | | } |
| | | |
| | | // æ¹éçæ |
| | | const generateBatchCodes = async () => { |
| | | if (!batchForm.prefix.trim()) { |
| | | ElMessage.warning('请è¾å
¥åç¼') |
| | | return |
| | | } |
| | | |
| | | batchCodes.value = [] |
| | | generating.value = true |
| | | |
| | | try { |
| | | for (let i = 0; i < batchForm.quantity; i++) { |
| | | const number = batchForm.startNumber + i |
| | | const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}` |
| | | |
| | | let codeUrl |
| | | if (form.type === 'qrcode') { |
| | | codeUrl = await QRCode.toDataURL(content, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | } |
| | | }) |
| | | } else { |
| | | const securityContent = generateSecurityCode(content) |
| | | codeUrl = await QRCode.toDataURL(securityContent, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | } |
| | | }) |
| | | } |
| | | |
| | | batchCodes.value.push({ |
| | | content, |
| | | url: codeUrl |
| | | }) |
| | | } |
| | | |
| | | ElMessage.success(`æ¹éçæå®æï¼å
±çæ ${batchForm.quantity} 个ç `) |
| | | batchDialogVisible.value = false |
| | | |
| | | } catch (error) { |
| | | console.error('æ¹éçæå¤±è´¥:', error) |
| | | ElMessage.error('æ¹éçæå¤±è´¥ï¼' + error.message) |
| | | } finally { |
| | | generating.value = false |
| | | } |
| | | } |
| | | |
| | | // ä¸è½½å个æ¹éçæçç |
| | | const downloadSingleCode = (code) => { |
| | | const a = document.createElement('a') |
| | | a.href = code.url |
| | | a.download = `${code.content}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | } |
| | | |
| | | // ä¸è½½æææ¹éçæçç |
| | | const downloadAllCodes = async () => { |
| | | if (batchCodes.value.length === 0) { |
| | | ElMessage.warning('没æå¯ä¸è½½çç ') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | // 使ç¨JSZipæå
ä¸è½½ |
| | | const JSZip = await import('jszip') |
| | | const zip = new JSZip.default() |
| | | |
| | | batchCodes.value.forEach((code, index) => { |
| | | // å°base64转æ¢ä¸ºblob |
| | | const base64Data = code.url.split(',')[1] |
| | | const byteCharacters = atob(base64Data) |
| | | const byteNumbers = new Array(byteCharacters.length) |
| | | for (let i = 0; i < byteCharacters.length; i++) { |
| | | byteNumbers[i] = byteCharacters.charCodeAt(i) |
| | | } |
| | | const byteArray = new Uint8Array(byteNumbers) |
| | | |
| | | zip.file(`${code.content}.png`, byteArray) |
| | | }) |
| | | |
| | | const content = await zip.generateAsync({ type: 'blob' }) |
| | | const a = document.createElement('a') |
| | | a.href = URL.createObjectURL(content) |
| | | a.download = `æ¹é${form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}_${new Date().getTime()}.zip` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | URL.revokeObjectURL(a.href) |
| | | |
| | | ElMessage.success('æ¹éä¸è½½å®æï¼') |
| | | } catch (error) { |
| | | console.error('æ¹éä¸è½½å¤±è´¥:', error) |
| | | ElMessage.error('æ¹éä¸è½½å¤±è´¥ï¼è¯·é个ä¸è½½') |
| | | } |
| | | } |
| | | |
| | | // æ¸
空æ¹éçæç»æ |
| | | const clearBatchCodes = () => { |
| | | batchCodes.value = [] |
| | | } |
| | | |
| | | // æ´é²æ¹æ³ç»ç¶ç»ä»¶ |
| | | defineExpose({ |
| | | generateCode, |
| | | downloadCode, |
| | | resetForm, |
| | | form |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .qr-code-generator { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .qr-form { |
| | | background: #f8f9fa; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .code-display { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .code-container { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: flex-start; |
| | | gap: 40px; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .code-image img { |
| | | border: 2px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .code-info { |
| | | text-align: left; |
| | | min-width: 200px; |
| | | } |
| | | |
| | | .code-info p { |
| | | margin: 8px 0; |
| | | color: #666; |
| | | } |
| | | |
| | | .code-actions { |
| | | text-align: center; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .code-actions .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | |
| | | .batch-results { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .batch-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
| | | gap: 20px; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .batch-item { |
| | | text-align: center; |
| | | padding: 15px; |
| | | border: 1px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | background: #fff; |
| | | } |
| | | |
| | | .batch-item img { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .batch-content { |
| | | font-size: 12px; |
| | | color: #666; |
| | | margin: 10px 0; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .batch-actions { |
| | | text-align: center; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .batch-actions .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .code-container { |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | |
| | | .batch-grid { |
| | | grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); |
| | | } |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="åçåç§°/å½å®¶"> |
| | | <el-input |
| | | v-model="filters.name" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å
³é®è¯" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="openAdd" icon="Plus"> æ°å¢ </el-button> |
| | | <el-button |
| | | type="danger" |
| | | icon="Delete" |
| | | :disabled="multipleSelection.length <= 0" |
| | | @click="handleBatchDelete" |
| | | >æ¹éå é¤</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <el-dialog v-model="visible" :title="dialogTitle" width="520px" destroy-on-close> |
| | | <el-form :model="form" ref="formRef" :rules="rules" label-width="90px"> |
| | | <el-form-item label="åçåç§°" prop="name"> |
| | | <el-input v-model="form.name" placeholder="请è¾å
¥åçåç§°" /> |
| | | </el-form-item> |
| | | <el-form-item label="æå±å½å®¶" prop="country"> |
| | | <el-input v-model="form.country" placeholder="请è¾å
¥å½å®¶/å°åº" /> |
| | | </el-form-item> |
| | | <el-form-item label="æè¿°" prop="description"> |
| | | <el-input v-model="form.description" type="textarea" :rows="3" placeholder="å¯å¡«ååçç®ä»" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="visible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, getCurrentInstance, onMounted } from 'vue' |
| | | import { ElMessageBox, ElMessage } from 'element-plus' |
| | | import { usePaginationApi } from '@/hooks/usePaginationApi' |
| | | import { getBrandPage, addBrand, editBrand, delBrand } from '@/api/equipmentManagement/brand' |
| | | |
| | | defineOptions({ name: '设å¤åç管ç' }) |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const multipleSelection = ref([]) |
| | | const formRef = ref() |
| | | const visible = ref(false) |
| | | const dialogTitle = ref('æ°å¢åç') |
| | | const form = ref({ id: undefined, name: '', country: '', description: '' }) |
| | | |
| | | const rules = { |
| | | name: [{ required: true, message: '请è¾å
¥åçåç§°', trigger: 'blur' }], |
| | | country: [{ required: true, message: '请è¾å
¥æå±å½å®¶', trigger: 'blur' }] |
| | | } |
| | | |
| | | const { |
| | | filters, |
| | | columns, |
| | | dataList, |
| | | pagination, |
| | | getTableData, |
| | | resetFilters, |
| | | onCurrentChange, |
| | | } = usePaginationApi( |
| | | getBrandPage, |
| | | { name: undefined }, |
| | | [ |
| | | { label: 'åçåç§°', align: 'center', prop: 'name' }, |
| | | { label: 'æå±å½å®¶', align: 'center', prop: 'country' }, |
| | | { label: 'æè¿°', align: 'center', prop: 'description' }, |
| | | { label: 'å建æ¶é´', align: 'center', prop: 'createdAt' }, |
| | | { |
| | | dataType: 'action', |
| | | label: 'æä½', |
| | | align: 'center', |
| | | fixed: 'right', |
| | | width: 140, |
| | | operation: [ |
| | | { |
| | | name: 'ç¼è¾', |
| | | type: 'text', |
| | | clickFun: (row) => openEdit(row), |
| | | }, |
| | | { |
| | | name: 'å é¤', |
| | | type: 'text', |
| | | clickFun: (row) => handleDelete(row.id), |
| | | } |
| | | ] |
| | | } |
| | | ] |
| | | ) |
| | | |
| | | const handleSelectionChange = (list) => { |
| | | multipleSelection.value = list |
| | | } |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page |
| | | pagination.pageSize = limit |
| | | onCurrentChange(page) |
| | | } |
| | | |
| | | function resetForm() { |
| | | form.value = { id: undefined, name: '', country: '', description: '' } |
| | | } |
| | | |
| | | function openAdd() { |
| | | resetForm() |
| | | dialogTitle.value = 'æ°å¢åç' |
| | | visible.value = true |
| | | } |
| | | |
| | | function openEdit(row) { |
| | | form.value = { id: row.id, name: row.name, country: row.country, description: row.description } |
| | | dialogTitle.value = 'ç¼è¾åç' |
| | | visible.value = true |
| | | } |
| | | |
| | | function handleSubmit() { |
| | | formRef.value.validate(async (valid) => { |
| | | if (!valid) return |
| | | const isEdit = Boolean(form.value.id) |
| | | const api = isEdit ? editBrand : addBrand |
| | | const { code, msg } = await api({ ...form.value }) |
| | | if (code === 200) { |
| | | ElMessage.success(isEdit ? 'ä¿®æ¹æå' : 'æ°å¢æå') |
| | | visible.value = false |
| | | getTableData() |
| | | } else { |
| | | ElMessage.error(msg || 'æä½å¤±è´¥') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function handleDelete(id) { |
| | | ElMessageBox.confirm('æ¤æä½å°æ°¸ä¹
å é¤è¯¥åç, æ¯å¦ç»§ç»?', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }).then(async () => { |
| | | const { code } = await delBrand(id) |
| | | if (code === 200) { |
| | | ElMessage.success('å 餿å') |
| | | getTableData() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function handleBatchDelete() { |
| | | if (multipleSelection.value.length === 0) return |
| | | ElMessageBox.confirm('å°å é¤éä¸çåçï¼æ¯å¦ç»§ç»ï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }).then(async () => { |
| | | const ids = multipleSelection.value.map((i) => i.id) |
| | | const { code } = await delBrand(ids) |
| | | if (code === 200) { |
| | | ElMessage.success('å 餿å') |
| | | getTableData() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getTableData() |
| | | }) |
| | | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .table_list { margin-top: unset; } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | </style> |
| | | |
| | | |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤åç" prop="deviceBrand"> |
| | | <el-input v-model="form.deviceBrand" placeholder="请è¾å
¥è®¾å¤åç" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºå" prop="supplierName"> |
| | | <el-input v-model="form.supplierName" placeholder="请è¾å
¥ä¾åºå" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åæ¾ä½ç½®" prop="storageLocation"> |
| | | <el-input v-model="form.storageLocation" placeholder="请è¾å
¥åæ¾ä½ç½®" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½" prop="unit"> |
| | | <el-input v-model="form.unit" placeholder="请è¾å
¥åä½" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¯ç¨ææ§" prop="enableDepreciation"> |
| | | <el-switch v-model="form.enableDepreciation" :active-value="true" :inactive-value="false" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | const { form, resetForm } = useFormData({ |
| | | deviceName: undefined, // 设å¤åç§° |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | deviceBrand: undefined, // 设å¤åç |
| | | supplierName: undefined, // ä¾åºå |
| | | storageLocation: undefined, // åæ¾ä½ç½® |
| | | enableDepreciation: false, // æ¯å¦å¯ç¨ææ§ |
| | | unit: undefined, // åä½ |
| | | number: undefined, // æ°é |
| | | taxIncludingPriceUnit: undefined, // å«ç¨åä»· |
| | |
| | | if (code == 200) { |
| | | form.deviceName = data.deviceName; |
| | | form.deviceModel = data.deviceModel; |
| | | form.deviceBrand = data.deviceBrand; |
| | | form.supplierName = data.supplierName; |
| | | form.storageLocation = data.storageLocation; |
| | | form.enableDepreciation = data.enableDepreciation; |
| | | form.unit = data.unit; |
| | | form.number = data.number; |
| | | form.taxIncludingPriceUnit = data.taxIncludingPriceUnit; |
| | |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "设å¤åç", |
| | | align: "center", |
| | | prop: "deviceBrand", |
| | | }, |
| | | { |
| | | label: "ä¾åºå", |
| | | align: "center", |
| | | prop: "supplierName", |
| | |
| | | label: "åä½", |
| | | align: "center", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "åæ¾ä½ç½®", |
| | | align: "center", |
| | | prop: "storageLocation", |
| | | }, |
| | | { |
| | | label: "æ°é", |
| | |
| | | prop: "unTaxIncludingPriceTotal", |
| | | }, |
| | | { |
| | | label: "å¯ç¨ææ§", |
| | | align: "center", |
| | | prop: "enableDepreciation", |
| | | formatData: (v) => (v ? "æ¯" : "å¦"), |
| | | }, |
| | | { |
| | | label: "å½å
¥äºº", |
| | | align: "center", |
| | | prop: "createUser", |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="éè´è®¢åå·ï¼"> |
| | | <el-input v-model="searchForm.orderNo" placeholder="请è¾å
¥è®¢åå·" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="searchForm.supplierName" placeholder="请è¾å
¥ä¾åºååç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢å°è´§</el-button> |
| | | <el-button type="success" @click="handleBatchReceive">æ¹éæ¶è´§</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="å°è´§åå·" prop="arrivalNo" width="180" /> |
| | | <el-table-column label="éè´è®¢åå·" prop="orderNo" width="180" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="å°è´§ç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å°è´§æ°é" prop="arrivalQuantity" width="100" /> |
| | | <el-table-column label="å°è´§æ¶é´" prop="arrivalTime" width="180" /> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleReceive(row)">æ¶è´§</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢å°è´§' : 'ç¼è¾å°è´§'" width="600px"> |
| | | <el-form :model="formData" label-width="120px"> |
| | | <el-form-item label="éè´è®¢åå·"> |
| | | <el-select v-model="formData.orderNo" placeholder="è¯·éæ©éè´è®¢å" style="width: 100%"> |
| | | <el-option label="PO20241201001" value="PO20241201001" /> |
| | | <el-option label="PO20241201002" value="PO20241201002" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°"> |
| | | <el-input v-model="formData.supplierName" placeholder="ä¾åºååç§°" readonly /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | orderNo: '', |
| | | supplierName: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | orderNo: '', |
| | | supplierName: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | arrivalNo: 'AR20241201001', |
| | | orderNo: 'PO20241201001', |
| | | supplierName: 'ä¾åºåA', |
| | | status: 'received', |
| | | arrivalQuantity: 250, |
| | | arrivalTime: '2024-12-01 15:30:00', |
| | | remark: 'æ£å¸¸å°è´§' |
| | | } |
| | | ] |
| | | |
| | | const tableData = ref([...mockData]) |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { pending: 'warning', received: 'success', stored: 'info' } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { pending: 'å¾
æ¶è´§', received: 'å·²æ¶è´§', stored: 'å·²å
¥åº' } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | loading.value = true |
| | | setTimeout(() => { loading.value = false }, 500) |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { orderNo: '', supplierName: '' }) |
| | | } |
| | | |
| | | const openDialog = (type, row = {}) => { |
| | | dialogType.value = type |
| | | if (type === 'edit' && row.id) { |
| | | Object.assign(formData, { orderNo: row.orderNo, supplierName: row.supplierName, remark: row.remark }) |
| | | } else { |
| | | Object.assign(formData, { orderNo: '', supplierName: '', remark: '' }) |
| | | } |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | if (dialogType.value === 'add') { |
| | | const newArrival = { |
| | | id: Date.now(), |
| | | arrivalNo: `AR${Date.now()}`, |
| | | orderNo: formData.orderNo, |
| | | supplierName: formData.supplierName, |
| | | status: 'pending', |
| | | arrivalQuantity: 0, |
| | | arrivalTime: new Date().toLocaleString(), |
| | | remark: formData.remark |
| | | } |
| | | tableData.value.unshift(newArrival) |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | |
| | | const handleReceive = (row) => { |
| | | row.status = 'received' |
| | | ElMessage.success('æ¶è´§æå') |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('ç¡®å®è¦å é¤è¿æ¡è®°å½åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = tableData.value.findIndex(item => item.id === row.id) |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleBatchReceive = () => { |
| | | ElMessage.success('æ¹éæ¶è´§æå') |
| | | } |
| | | |
| | | const handleBatchDelete = () => { |
| | | ElMessage.success('æ¹éå 餿å') |
| | | } |
| | | |
| | | const handleSelectionChange = (rows) => { |
| | | selectedRows.value = rows |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { padding: 20px; } |
| | | .search-card { margin-bottom: 20px; } |
| | | .table-card { margin-bottom: 20px; } |
| | | .table-header { margin-bottom: 20px; } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>éè´ç®¡çç³»ç»</h2> |
| | | <p>ç»ä¸ç®¡çéè´å
¨æµç¨ï¼æåéè´æçä¸è´¨é</p> |
| | | </div> |
| | | |
| | | <!-- åè½æ¨¡åå¡ç --> |
| | | <el-row :gutter="20" class="module-cards"> |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/purchaseOrder')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#409EFF"><Document /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>éè´è®¢å管ç</h3> |
| | | <p>æ°å»ºãç¼è¾ãå é¤éè´è®¢åï¼éæ©ä¾åºåï¼å¡«åååæç»</p> |
| | | <div class="card-stats"> |
| | | <span>å¾
å®¡æ ¸: {{ stats.pendingOrders }}</span> |
| | | <span>å·²å®¡æ ¸: {{ stats.approvedOrders }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/arrivalManagement')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#67C23A"><Box /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>å°è´§ç®¡ç</h3> |
| | | <p>èªå¨å
³èéè´è®¢åï¼å½å
¥å°è´§ååä¿¡æ¯ï¼æ¯ææå°æ¥ç</p> |
| | | <div class="card-stats"> |
| | | <span>å¾
æ¶è´§: {{ stats.pendingArrivals }}</span> |
| | | <span>å·²æ¶è´§: {{ stats.receivedArrivals }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/qualityInspection')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#E6A23C"><Search /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>è´¨æ£ç®¡ç</h3> |
| | | <p>å°è´§åèªå¨çæè´¨æ£åï¼å¡«ååæ ¼ä¸ä¸åæ ¼ååæ°éååå </p> |
| | | <div class="card-stats"> |
| | | <span>å¾
è´¨æ£: {{ stats.pendingInspections }}</span> |
| | | <span>已宿: {{ stats.completedInspections }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20" class="module-cards"> |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/returnManagement')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#F56C6C"><RefreshLeft /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>é货管ç</h3> |
| | | <p>çæéè´éè´§ååè´¨æ£éè´§åï¼æ¯æçéæ¥è¯¢ä¸åæ®è¯¦æ
</p> |
| | | <div class="card-stats"> |
| | | <span>å¾
å®¡æ ¸: {{ stats.pendingReturns }}</span> |
| | | <span>å·²å®¡æ ¸: {{ stats.approvedReturns }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/priceManagement')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#909399"><Money /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>ä»·æ ¼ç®¡ç</h3> |
| | | <p>æ ¹æ®åååå¸åºä»·æ ¼ååè¿è¡éè´ä»·è°æ´ï¼èªå¨æ´æ°éè´åæ®</p> |
| | | <div class="card-stats"> |
| | | <span>ææä»·æ ¼: {{ stats.activePrices }}</span> |
| | | <span>å¾
çæ: {{ stats.pendingPrices }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/procurementLedger')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#9C27B0"><List /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>éè´å°è´¦</h3> |
| | | <p>æ¥çéè´åå²è®°å½ï¼ç»è®¡åæéè´æ°æ®ï¼çæéè´æ¥è¡¨</p> |
| | | <div class="card-stats"> |
| | | <span>æ»è®¢å: {{ stats.totalOrders }}</span> |
| | | <span>æ»éé¢: Â¥{{ stats.totalAmount.toFixed(2) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- ç»è®¡æ¦è§ --> |
| | | <el-card class="stats-card" shadow="never"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>éè´ç»è®¡æ¦è§</span> |
| | | <el-button type="primary" size="small" @click="refreshStats">å·æ°æ°æ®</el-button> |
| | | </div> |
| | | </template> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ stats.totalOrders }}</div> |
| | | <div class="stat-label">éè´è®¢åæ»æ°</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ stats.totalAmount.toFixed(2) }}</div> |
| | | <div class="stat-label">éè´æ»éé¢(ä¸å
)</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ stats.avgDeliveryTime }}</div> |
| | | <div class="stat-label">å¹³å交ä»å¤©æ°</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ stats.qualityRate }}%</div> |
| | | <div class="stat-label">è´¨æ£åæ ¼ç</div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- æè¿æ´»å¨ --> |
| | | <el-card class="activity-card" shadow="never"> |
| | | <template #header> |
| | | <span>æè¿æ´»å¨</span> |
| | | </template> |
| | | |
| | | <el-timeline> |
| | | <el-timeline-item |
| | | v-for="(activity, index) in recentActivities" |
| | | :key="index" |
| | | :timestamp="activity.time" |
| | | :type="activity.type" |
| | | > |
| | | {{ activity.content }} |
| | | </el-timeline-item> |
| | | </el-timeline> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { Document, Box, Search, RefreshLeft, Money, List } from '@element-plus/icons-vue' |
| | | |
| | | const router = useRouter() |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const stats = ref({ |
| | | pendingOrders: 5, |
| | | approvedOrders: 25, |
| | | pendingArrivals: 3, |
| | | receivedArrivals: 18, |
| | | pendingInspections: 2, |
| | | completedInspections: 15, |
| | | pendingReturns: 1, |
| | | approvedReturns: 3, |
| | | activePrices: 45, |
| | | pendingPrices: 2, |
| | | totalOrders: 30, |
| | | totalAmount: 125.8, |
| | | avgDeliveryTime: 7, |
| | | qualityRate: 96.5 |
| | | }) |
| | | |
| | | // æè¿æ´»å¨ |
| | | const recentActivities = ref([ |
| | | { |
| | | time: '2024-12-01 18:30', |
| | | content: 'æ°å¢éè´è®¢å PO20241201004', |
| | | type: 'primary' |
| | | }, |
| | | { |
| | | time: '2024-12-01 17:45', |
| | | content: 'å®æè´¨æ£å QI20241201002', |
| | | type: 'success' |
| | | }, |
| | | { |
| | | time: '2024-12-01 16:20', |
| | | content: 'å°è´§å AR20241201003 å·²æ¶è´§', |
| | | type: 'success' |
| | | }, |
| | | { |
| | | time: '2024-12-01 15:15', |
| | | content: 'ä»·æ ¼è°æ´ï¼ååB ä» Â¥80 è°æ´ä¸º Â¥75', |
| | | type: 'warning' |
| | | }, |
| | | { |
| | | time: '2024-12-01 14:30', |
| | | content: 'éè´§å RT20241201003 å·²å®¡æ ¸', |
| | | type: 'info' |
| | | } |
| | | ]) |
| | | |
| | | // 导èªå°æå®é¡µé¢ |
| | | const navigateTo = (path) => { |
| | | router.push(path) |
| | | } |
| | | |
| | | // å·æ°ç»è®¡æ°æ® |
| | | const refreshStats = () => { |
| | | // 模æå·æ°æ°æ® |
| | | stats.value.pendingOrders = Math.floor(Math.random() * 10) + 1 |
| | | stats.value.totalAmount = (Math.random() * 100 + 100).toFixed(1) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | // 页é¢å è½½å®æåçåå§åé»è¾ |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | text-align: center; |
| | | margin-bottom: 30px; |
| | | padding: 20px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border-radius: 10px; |
| | | color: white; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0 0 10px 0; |
| | | font-size: 28px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .page-header p { |
| | | margin: 0; |
| | | font-size: 16px; |
| | | opacity: 0.9; |
| | | } |
| | | |
| | | .module-cards { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .module-card { |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | border: none; |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | .module-card:hover { |
| | | transform: translateY(-5px); |
| | | box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .card-icon { |
| | | margin-right: 20px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 80px; |
| | | height: 80px; |
| | | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| | | border-radius: 50%; |
| | | color: white; |
| | | } |
| | | |
| | | .card-info h3 { |
| | | margin: 0 0 10px 0; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .card-info p { |
| | | margin: 0 0 15px 0; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | .card-stats { |
| | | display: flex; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .card-stats span { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | background-color: #f5f7fa; |
| | | padding: 4px 8px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .stats-card { |
| | | margin-bottom: 20px; |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .stat-item { |
| | | text-align: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .stat-number { |
| | | font-size: 32px; |
| | | font-weight: 600; |
| | | color: #409EFF; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .activity-card { |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | .el-timeline-item { |
| | | padding-bottom: 20px; |
| | | } |
| | | |
| | | .el-timeline-item:last-child { |
| | | padding-bottom: 0; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="åååç§°ï¼"> |
| | | <el-input v-model="searchForm.productName" placeholder="请è¾å
¥åååç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="searchForm.supplierName" placeholder="请è¾å
¥ä¾åºååç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ä»·æ ¼ç¶æï¼"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©ç¶æ" clearable> |
| | | <el-option label="ææ" value="active" /> |
| | | <el-option label="å·²è¿æ" value="expired" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢ä»·æ ¼</el-button> |
| | | <el-button type="success" @click="handleBatchUpdate">æ¹éæ´æ°</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="åååç§°" prop="productName" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specification" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="åä»·æ ¼" prop="oldPrice" width="120"> |
| | | <template #default="{ row }">Â¥{{ row.oldPrice.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="æ°ä»·æ ¼" prop="newPrice" width="120"> |
| | | <template #default="{ row }">Â¥{{ row.newPrice.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="è°ä»·å¹
度" prop="priceChange" width="120"> |
| | | <template #default="{ row }"> |
| | | <span :style="{ color: row.priceChange >= 0 ? '#f56c6c' : '#67c23a' }"> |
| | | {{ row.priceChange >= 0 ? '+' : '' }}{{ row.priceChange.toFixed(2) }}% |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="çææ¶é´" prop="effectiveTime" width="180" /> |
| | | <el-table-column label="ç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleApply(row)" v-if="row.status === 'pending'">åºç¨</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢ä»·æ ¼' : 'ç¼è¾ä»·æ ¼'" width="600px"> |
| | | <el-form :model="formData" label-width="120px"> |
| | | <el-form-item label="åååç§°"> |
| | | <el-select v-model="formData.productName" placeholder="è¯·éæ©åå" style="width: 100%"> |
| | | <el-option label="ååA" value="ååA" /> |
| | | <el-option label="ååB" value="ååB" /> |
| | | <el-option label="ååC" value="ååC" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="è§æ ¼åå·"> |
| | | <el-input v-model="formData.specification" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°"> |
| | | <el-select v-model="formData.supplierName" placeholder="è¯·éæ©ä¾åºå" style="width: 100%"> |
| | | <el-option label="ä¾åºåA" value="ä¾åºåA" /> |
| | | <el-option label="ä¾åºåB" value="ä¾åºåB" /> |
| | | <el-option label="ä¾åºåC" value="ä¾åºåC" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="åä»·æ ¼"> |
| | | <el-input v-model="formData.oldPrice" placeholder="请è¾å
¥åä»·æ ¼" type="number" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ°ä»·æ ¼"> |
| | | <el-input v-model="formData.newPrice" placeholder="请è¾å
¥æ°ä»·æ ¼" type="number" /> |
| | | </el-form-item> |
| | | <el-form-item label="çææ¶é´"> |
| | | <el-date-picker v-model="formData.effectiveTime" type="datetime" placeholder="éæ©çææ¶é´" style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="è°ä»·åå "> |
| | | <el-select v-model="formData.reason" placeholder="è¯·éæ©è°ä»·åå " style="width: 100%"> |
| | | <el-option label="å¸åºä»·æ ¼åå¨" value="market" /> |
| | | <el-option label="ææ¬åå" value="cost" /> |
| | | <el-option label="ä¾åºåè°æ´" value="supplier" /> |
| | | <el-option label="å
¶ä»åå " value="other" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | productName: '', |
| | | supplierName: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | productName: '', |
| | | specification: '', |
| | | supplierName: '', |
| | | oldPrice: 0, |
| | | newPrice: 0, |
| | | effectiveTime: '', |
| | | reason: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | productName: 'ååA', |
| | | specification: 'è§æ ¼1', |
| | | supplierName: 'ä¾åºåA', |
| | | oldPrice: 50.00, |
| | | newPrice: 55.00, |
| | | priceChange: 10.00, |
| | | effectiveTime: '2024-12-01 00:00:00', |
| | | status: 'active', |
| | | reason: 'å¸åºä»·æ ¼åå¨', |
| | | remark: 'å¸åºä»·æ ¼ä¸æ¶¨' |
| | | }, |
| | | { |
| | | id: 2, |
| | | productName: 'ååB', |
| | | specification: 'è§æ ¼2', |
| | | supplierName: 'ä¾åºåB', |
| | | oldPrice: 80.00, |
| | | newPrice: 75.00, |
| | | priceChange: -6.25, |
| | | effectiveTime: '2024-12-01 00:00:00', |
| | | status: 'active', |
| | | reason: 'ææ¬åå', |
| | | remark: 'ææ¬ä¸é' |
| | | } |
| | | ] |
| | | |
| | | const tableData = ref([...mockData]) |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { active: 'success', expired: 'info', pending: 'warning' } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { active: 'ææ', expired: 'å·²è¿æ', pending: 'å¾
çæ' } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | loading.value = true |
| | | setTimeout(() => { loading.value = false }, 500) |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { productName: '', supplierName: '', status: '' }) |
| | | } |
| | | |
| | | const openDialog = (type, row = {}) => { |
| | | dialogType.value = type |
| | | if (type === 'edit' && row.id) { |
| | | Object.assign(formData, { |
| | | productName: row.productName, |
| | | specification: row.specification, |
| | | supplierName: row.supplierName, |
| | | oldPrice: row.oldPrice, |
| | | newPrice: row.newPrice, |
| | | effectiveTime: row.effectiveTime, |
| | | reason: row.reason, |
| | | remark: row.remark |
| | | }) |
| | | } else { |
| | | Object.assign(formData, { |
| | | productName: '', |
| | | specification: '', |
| | | supplierName: '', |
| | | oldPrice: 0, |
| | | newPrice: 0, |
| | | effectiveTime: '', |
| | | reason: '', |
| | | remark: '' |
| | | }) |
| | | } |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | if (dialogType.value === 'add') { |
| | | const priceChange = ((formData.newPrice - formData.oldPrice) / formData.oldPrice) * 100 |
| | | const newPrice = { |
| | | id: Date.now(), |
| | | productName: formData.productName, |
| | | specification: formData.specification, |
| | | supplierName: formData.supplierName, |
| | | oldPrice: formData.oldPrice, |
| | | newPrice: formData.newPrice, |
| | | priceChange: priceChange, |
| | | effectiveTime: formData.effectiveTime, |
| | | status: 'pending', |
| | | reason: formData.reason, |
| | | remark: formData.remark |
| | | } |
| | | tableData.value.unshift(newPrice) |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | |
| | | const handleApply = (row) => { |
| | | row.status = 'active' |
| | | ElMessage.success('ä»·æ ¼å·²åºç¨') |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('ç¡®å®è¦å é¤è¿æ¡è®°å½åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = tableData.value.findIndex(item => item.id === row.id) |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleBatchUpdate = () => { |
| | | ElMessage.success('æ¹éæ´æ°æå') |
| | | } |
| | | |
| | | const handleBatchDelete = () => { |
| | | ElMessage.success('æ¹éå 餿å') |
| | | } |
| | | |
| | | const handleSelectionChange = (rows) => { |
| | | selectedRows.value = rows |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { padding: 20px; } |
| | | .search-card { margin-bottom: 20px; } |
| | | .table-card { margin-bottom: 20px; } |
| | | .table-header { margin-bottom: 20px; } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="searchForm.supplierName" placeholder="请è¾å
¥ä¾åºååç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="订åç¶æï¼"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©ç¶æ" clearable> |
| | | <el-option label="è稿" value="draft" /> |
| | | <el-option label="å¾
å®¡æ ¸" value="pending" /> |
| | | <el-option label="å·²å®¡æ ¸" value="approved" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢è®¢å</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete" :disabled="!selectedRows.length">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="订åç¼å·" prop="orderNo" width="180" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="订åç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æ»éé¢" prop="totalAmount" width="120"> |
| | | <template #default="{ row }">Â¥{{ row.totalAmount.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="å建æ¶é´" prop="createTime" width="180" /> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" size="small" @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" size="small" @click="viewDetails(row)">æ¥ç</el-button> |
| | | <el-button type="danger" size="small" @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢éè´è®¢å' : 'ç¼è¾éè´è®¢å'" width="800px"> |
| | | <el-form :model="formData" ref="formRef" label-width="120px"> |
| | | <el-form-item label="ä¾åºååç§°" prop="supplierName"> |
| | | <el-select v-model="formData.supplierName" placeholder="è¯·éæ©ä¾åºå" style="width: 100%"> |
| | | <el-option label="ä¾åºåA" value="ä¾åºåA" /> |
| | | <el-option label="ä¾åºåB" value="ä¾åºåB" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | supplierName: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | supplierName: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | orderNo: 'PO20241201001', |
| | | supplierName: 'ä¾åºåA', |
| | | status: 'approved', |
| | | totalAmount: 12500.00, |
| | | createTime: '2024-12-01 10:30:00', |
| | | remark: '常è§éè´' |
| | | } |
| | | ] |
| | | |
| | | const tableData = ref([...mockData]) |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { draft: 'info', pending: 'warning', approved: 'success' } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { draft: 'è稿', pending: 'å¾
å®¡æ ¸', approved: 'å·²å®¡æ ¸' } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | loading.value = true |
| | | setTimeout(() => { |
| | | loading.value = false |
| | | }, 500) |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { supplierName: '', status: '' }) |
| | | } |
| | | |
| | | const openDialog = (type, row = {}) => { |
| | | dialogType.value = type |
| | | if (type === 'edit' && row.id) { |
| | | Object.assign(formData, { supplierName: row.supplierName, remark: row.remark }) |
| | | } else { |
| | | Object.assign(formData, { supplierName: '', remark: '' }) |
| | | } |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | if (dialogType.value === 'add') { |
| | | const newOrder = { |
| | | id: Date.now(), |
| | | orderNo: `PO${Date.now()}`, |
| | | supplierName: formData.supplierName, |
| | | status: 'draft', |
| | | totalAmount: 0, |
| | | createTime: new Date().toLocaleString(), |
| | | remark: formData.remark |
| | | } |
| | | tableData.value.unshift(newOrder) |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | |
| | | const viewDetails = (row) => { |
| | | ElMessage.info('æ¥ç详æ
åè½') |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('ç¡®å®è¦å é¤è¿æ¡è®°å½åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = tableData.value.findIndex(item => item.id === row.id) |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleBatchDelete = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | ElMessage.warning('è¯·éæ©è¦å é¤çè®°å½') |
| | | return |
| | | } |
| | | ElMessage.success('æ¹éå 餿å') |
| | | } |
| | | |
| | | const handleSelectionChange = (rows) => { |
| | | selectedRows.value = rows |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { padding: 20px; } |
| | | .search-card { margin-bottom: 20px; } |
| | | .table-card { margin-bottom: 20px; } |
| | | .table-header { margin-bottom: 20px; } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="è´¨æ£åå·ï¼" style="width: 300px;"> |
| | | <el-input v-model="searchForm.inspectionNo" placeholder="请è¾å
¥è´¨æ£åå·" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="è´¨æ£ç¶æï¼" style="width: 300px;"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©ç¶æ" clearable> |
| | | <el-option label="å¾
è´¨æ£" value="pending" /> |
| | | <el-option label="è´¨æ£ä¸" value="inspecting" /> |
| | | <el-option label="已宿" value="completed" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢è´¨æ£å</el-button> |
| | | <el-button type="success" @click="handleBatchComplete">æ¹é宿</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="è´¨æ£åå·" prop="inspectionNo" width="180" /> |
| | | <el-table-column label="å°è´§åå·" prop="arrivalNo" width="180" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="è´¨æ£ç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åæ ¼æ°é" prop="qualifiedQuantity" width="100" /> |
| | | <el-table-column label="ä¸åæ ¼æ°é" prop="unqualifiedQuantity" width="100" /> |
| | | <el-table-column label="è´¨æ£æ¶é´" prop="inspectionTime" width="180" /> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleComplete(row)" v-if="row.status !== 'completed'">宿</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢è´¨æ£å' : 'ç¼è¾è´¨æ£å'" width="1000px"> |
| | | <el-form :model="formData" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å°è´§åå·"> |
| | | <el-select v-model="formData.arrivalNo" placeholder="è¯·éæ©å°è´§å" style="width: 100%"> |
| | | <el-option label="AR20241201001" value="AR20241201001" /> |
| | | <el-option label="AR20241201002" value="AR20241201002" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºååç§°"> |
| | | <el-input v-model="formData.supplierName" placeholder="ä¾åºååç§°" readonly /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-form-item label="è´¨æ£åå"> |
| | | <div class="product-list" style="width: 100%;"> |
| | | <el-table :data="formData.products" border width="100%"> |
| | | <el-table-column label="åååç§°" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.productName" placeholder="请è¾å
¥åååç§°" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="è§æ ¼åå·" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.specification" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å°è´§æ°é" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input-number v-model="row.arrivalQuantity" :min="0" placeholder="æ°é" style="width: 100%;"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åæ ¼æ°é" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input-number v-model="row.qualifiedQuantity" :min="0" placeholder="æ°é" style="width: 100%;"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä¸åæ ¼æ°é" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input-number v-model="row.unqualifiedQuantity" :min="0" placeholder="æ°é" style="width: 100%;"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä¸åæ ¼åå " width="200"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.unqualifiedReason" placeholder="请è¾å
¥ä¸åæ ¼åå " /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="100"> |
| | | <template #default="{ $index }"> |
| | | <el-button type="danger" link @click="removeProduct($index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div class="add-product-btn"> |
| | | <el-button type="primary" @click="addProduct">æ·»å åå</el-button> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="è´¨æ£å"> |
| | | <el-input v-model="formData.inspector" placeholder="请è¾å
¥è´¨æ£åå§å" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | inspectionNo: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | arrivalNo: '', |
| | | supplierName: '', |
| | | products: [], |
| | | inspector: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | inspectionNo: 'QI20241201001', |
| | | arrivalNo: 'AR20241201001', |
| | | supplierName: 'ä¾åºåA', |
| | | status: 'completed', |
| | | qualifiedQuantity: 240, |
| | | unqualifiedQuantity: 10, |
| | | inspectionTime: '2024-12-01 16:30:00', |
| | | inspector: 'å¼ ä¸', |
| | | remark: 'è´¨æ£å®æ' |
| | | } |
| | | ] |
| | | |
| | | const tableData = ref([...mockData]) |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { pending: 'info', inspecting: 'warning', completed: 'success' } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { pending: 'å¾
è´¨æ£', inspecting: 'è´¨æ£ä¸', completed: '已宿' } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | loading.value = true |
| | | setTimeout(() => { loading.value = false }, 500) |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { inspectionNo: '', status: '' }) |
| | | } |
| | | |
| | | const openDialog = (type, row = {}) => { |
| | | dialogType.value = type |
| | | if (type === 'edit' && row.id) { |
| | | Object.assign(formData, { |
| | | arrivalNo: row.arrivalNo, |
| | | supplierName: row.supplierName, |
| | | inspector: row.inspector, |
| | | remark: row.remark |
| | | }) |
| | | } else { |
| | | Object.assign(formData, { |
| | | arrivalNo: '', |
| | | supplierName: '', |
| | | products: [], |
| | | inspector: '', |
| | | remark: '' |
| | | }) |
| | | } |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | if (dialogType.value === 'add') { |
| | | const newInspection = { |
| | | id: Date.now(), |
| | | inspectionNo: `QI${Date.now()}`, |
| | | arrivalNo: formData.arrivalNo, |
| | | supplierName: formData.supplierName, |
| | | status: 'pending', |
| | | qualifiedQuantity: 0, |
| | | unqualifiedQuantity: 0, |
| | | inspectionTime: new Date().toLocaleString(), |
| | | inspector: formData.inspector, |
| | | remark: formData.remark |
| | | } |
| | | tableData.value.unshift(newInspection) |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | |
| | | const handleComplete = (row) => { |
| | | row.status = 'completed' |
| | | ElMessage.success('è´¨æ£å®æ') |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('ç¡®å®è¦å é¤è¿æ¡è®°å½åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = tableData.value.findIndex(item => item.id === row.id) |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleBatchComplete = () => { |
| | | ElMessage.success('æ¹é宿æå') |
| | | } |
| | | |
| | | const handleBatchDelete = () => { |
| | | ElMessage.success('æ¹éå 餿å') |
| | | } |
| | | |
| | | const handleSelectionChange = (rows) => { |
| | | selectedRows.value = rows |
| | | } |
| | | |
| | | const addProduct = () => { |
| | | formData.products.push({ |
| | | productName: '', |
| | | specification: '', |
| | | arrivalQuantity: 0, |
| | | qualifiedQuantity: 0, |
| | | unqualifiedQuantity: 0, |
| | | unqualifiedReason: '' |
| | | }) |
| | | } |
| | | |
| | | const removeProduct = (index) => { |
| | | formData.products.splice(index, 1) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { padding: 20px; } |
| | | .search-card { margin-bottom: 20px; } |
| | | .table-card { margin-bottom: 20px; } |
| | | .table-header { margin-bottom: 20px; } |
| | | .product-list { border: 1px solid #dcdfe6; border-radius: 4px; padding: 15px; } |
| | | .product-item { margin-bottom: 15px; padding: 10px; background-color: #f5f7fa; border-radius: 4px; } |
| | | .add-product-btn { margin-top: 15px; text-align: center; } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="éè´§åå·ï¼" style="width: 300px;"> |
| | | <el-input v-model="searchForm.returnNo" placeholder="请è¾å
¥éè´§åå·" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="éè´§ç±»åï¼" style="width: 300px;"> |
| | | <el-select v-model="searchForm.returnType" placeholder="è¯·éæ©ç±»å" clearable> |
| | | <el-option label="éè´éè´§" value="purchase" /> |
| | | <el-option label="è´¨æ£éè´§" value="quality" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢éè´§å</el-button> |
| | | <el-button type="success" @click="handleBatchApprove">æ¹éå®¡æ ¸</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="éè´§åå·" prop="returnNo" width="180" /> |
| | | <el-table-column label="å
³èåå·" prop="relatedNo" width="180" /> |
| | | <el-table-column label="éè´§ç±»å" prop="returnType" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.returnType === 'purchase' ? 'danger' : 'warning'"> |
| | | {{ getReturnTypeText(row.returnType) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="éè´§ç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="éè´§éé¢" prop="returnAmount" width="120"> |
| | | <template #default="{ row }">Â¥{{ row.returnAmount.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="å建æ¶é´" prop="createTime" width="180" /> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleApprove(row)" v-if="row.status === 'pending'">å®¡æ ¸</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢éè´§å' : 'ç¼è¾éè´§å'" width="600px"> |
| | | <el-form :model="formData" label-width="120px"> |
| | | <el-form-item label="éè´§ç±»å"> |
| | | <el-select v-model="formData.returnType" placeholder="è¯·éæ©éè´§ç±»å" style="width: 100%"> |
| | | <el-option label="éè´éè´§" value="purchase" /> |
| | | <el-option label="è´¨æ£éè´§" value="quality" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="å
³èåå·"> |
| | | <el-input v-model="formData.relatedNo" placeholder="请è¾å
¥å
³èåå·" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°"> |
| | | <el-input v-model="formData.supplierName" placeholder="请è¾å
¥ä¾åºååç§°" /> |
| | | </el-form-item> |
| | | <el-form-item label="éè´§åå "> |
| | | <el-select v-model="formData.returnReason" placeholder="è¯·éæ©éè´§åå " style="width: 100%"> |
| | | <el-option label="è´¨éé®é¢" value="quality" /> |
| | | <el-option label="è§æ ¼ä¸ç¬¦" value="specification" /> |
| | | <el-option label="æ°éé误" value="quantity" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | returnNo: '', |
| | | returnType: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | returnType: '', |
| | | relatedNo: '', |
| | | supplierName: '', |
| | | returnReason: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | returnNo: 'RT20241201001', |
| | | relatedNo: 'PO20241201001', |
| | | returnType: 'purchase', |
| | | supplierName: 'ä¾åºåA', |
| | | status: 'approved', |
| | | returnAmount: 500.00, |
| | | createTime: '2024-12-01 17:30:00', |
| | | returnReason: 'è´¨éé®é¢', |
| | | remark: 'åååå¨è´¨éé®é¢' |
| | | } |
| | | ] |
| | | |
| | | const tableData = ref([...mockData]) |
| | | |
| | | const getReturnTypeText = (type) => { |
| | | const typeMap = { purchase: 'éè´éè´§', quality: 'è´¨æ£éè´§' } |
| | | return typeMap[type] || 'æªç¥' |
| | | } |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { pending: 'warning', approved: 'success', returned: 'info' } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { pending: 'å¾
å®¡æ ¸', approved: 'å·²å®¡æ ¸', returned: 'å·²éè´§' } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | loading.value = true |
| | | setTimeout(() => { loading.value = false }, 500) |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { returnNo: '', returnType: '' }) |
| | | } |
| | | |
| | | const openDialog = (type, row = {}) => { |
| | | dialogType.value = type |
| | | if (type === 'edit' && row.id) { |
| | | Object.assign(formData, { |
| | | returnType: row.returnType, |
| | | relatedNo: row.relatedNo, |
| | | supplierName: row.supplierName, |
| | | returnReason: row.returnReason, |
| | | remark: row.remark |
| | | }) |
| | | } else { |
| | | Object.assign(formData, { |
| | | returnType: '', |
| | | relatedNo: '', |
| | | supplierName: '', |
| | | returnReason: '', |
| | | remark: '' |
| | | }) |
| | | } |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | if (dialogType.value === 'add') { |
| | | const newReturn = { |
| | | id: Date.now(), |
| | | returnNo: `RT${Date.now()}`, |
| | | relatedNo: formData.relatedNo, |
| | | returnType: formData.returnType, |
| | | supplierName: formData.supplierName, |
| | | status: 'pending', |
| | | returnAmount: 0, |
| | | createTime: new Date().toLocaleString(), |
| | | returnReason: formData.returnReason, |
| | | remark: formData.remark |
| | | } |
| | | tableData.value.unshift(newReturn) |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | |
| | | const handleApprove = (row) => { |
| | | row.status = 'approved' |
| | | ElMessage.success('å®¡æ ¸éè¿') |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('ç¡®å®è¦å é¤è¿æ¡è®°å½åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = tableData.value.findIndex(item => item.id === row.id) |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleBatchApprove = () => { |
| | | ElMessage.success('æ¹éå®¡æ ¸æå') |
| | | } |
| | | |
| | | const handleBatchDelete = () => { |
| | | ElMessage.success('æ¹éå 餿å') |
| | | } |
| | | |
| | | const handleSelectionChange = (rows) => { |
| | | selectedRows.value = rows |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { padding: 20px; } |
| | | .search-card { margin-bottom: 20px; } |
| | | .table-card { margin-bottom: 20px; } |
| | | .table-header { margin-bottom: 20px; } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.productName" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.identifierType" placeholder="è¯·éæ©æ è¯ç±»å" clearable> |
| | | <el-option label="äºç»´ç " value="äºç»´ç "></el-option> |
| | | <el-option label="é²ä¼ªç " value="é²ä¼ªç "></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©ç¶æ" clearable> |
| | | <el-option label="å·²çæ" value="å·²çæ"></el-option> |
| | | <el-option label="å·²åé
" value="å·²åé
"></el-option> |
| | | <el-option label="已使ç¨" value="已使ç¨"></el-option> |
| | | <el-option label="å·²ä½åº" value="å·²ä½åº"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | | æ°å¢æ è¯ |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- äº§åæ è¯å表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="productName" label="产ååç§°" width="150" /> |
| | | <el-table-column prop="productCode" label="产åç¼ç " width="120" /> |
| | | <el-table-column prop="batchNo" label="æ¹æ¬¡å·" width="120" /> |
| | | <el-table-column prop="identifierType" label="æ è¯ç±»å" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getIdentifierTypeType(scope.row.identifierType)"> |
| | | {{ scope.row.identifierType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="identifierCode" label="æ è¯ç " /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="generateTime" label="çææ¶é´" width="160" /> |
| | | <el-table-column label="æä½" fixed="right" align="center" width="280"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleView(scope.row)">æ¥ç</el-button> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="success" @click="generateQRCode(scope.row)">çæäºç»´ç </el-button> |
| | | <el-button link type="primary" @click="handleExport(scope.row)">导åº</el-button> |
| | | <el-button link type="primary" @click="handleReassign(scope.row)" v-if="scope.row.status === 'å·²åé
'">éæ°åé
</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产ååç§°" prop="productName"> |
| | | <el-input v-model="form.productName" placeholder="请è¾å
¥äº§ååç§°"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产åç¼ç " prop="productCode"> |
| | | <el-input v-model="form.productCode" placeholder="请è¾å
¥äº§åç¼ç "></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¹æ¬¡å·" prop="batchNo"> |
| | | <el-input v-model="form.batchNo" placeholder="请è¾å
¥æ¹æ¬¡å·"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ è¯ç±»å" prop="identifierType"> |
| | | <el-select v-model="form.identifierType" placeholder="è¯·éæ©æ è¯ç±»å" style="width: 100%"> |
| | | <el-option label="äºç»´ç " value="äºç»´ç "></el-option> |
| | | <el-option label="é²ä¼ªç " value="é²ä¼ªç "></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="çææ°é" prop="quantity"> |
| | | <el-input-number v-model="form.quantity" :min="1" :max="10000" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="å·²çæ" value="å·²çæ"></el-option> |
| | | <el-option label="å·²åé
" value="å·²åé
"></el-option> |
| | | <el-option label="已使ç¨" value="已使ç¨"></el-option> |
| | | <el-option label="å·²ä½åº" value="å·²ä½åº"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input type="textarea" v-model="form.remark" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" rows="3"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ è¯çæå¯¹è¯æ¡ --> |
| | | <el-dialog v-model="generateDialogVisible" title="æ è¯çæ" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="产ååç§°"> |
| | | <span>{{ currentProduct.productName }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="产åç¼ç "> |
| | | <span>{{ currentProduct.productCode }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æ¹æ¬¡å·"> |
| | | <span>{{ currentProduct.batchNo }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æ è¯ç±»å"> |
| | | <span>{{ currentProduct.identifierType }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="çææ°é" prop="generateQuantity"> |
| | | <el-input-number v-model="generateQuantity" :min="1" :max="10000" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | <el-form-item label="ç¼ç è§å" prop="codeRule"> |
| | | <el-select v-model="codeRule" placeholder="è¯·éæ©ç¼ç è§å" style="width: 100%"> |
| | | <el-option label="产åç¼ç +æ¹æ¬¡å·+åºå·" value="产åç¼ç +æ¹æ¬¡å·+åºå·"></el-option> |
| | | <el-option label="æ¶é´æ³+éæºæ°" value="æ¶é´æ³+éæºæ°"></el-option> |
| | | <el-option label="èªå®ä¹è§å" value="èªå®ä¹è§å"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="èªå®ä¹åç¼" prop="customPrefix" v-if="codeRule === 'èªå®ä¹è§å'"> |
| | | <el-input v-model="customPrefix" placeholder="请è¾å
¥èªå®ä¹åç¼"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="generateDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="generateIdentifiers">ç æ</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- éæ°åé
å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="reassignDialogVisible" title="éæ°åé
æ è¯" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="产ååç§°"> |
| | | <span>{{ currentProduct.productName }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æ è¯ç "> |
| | | <span>{{ currentProduct.identifierCode }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æ°æ¹æ¬¡å·" prop="newBatchNo"> |
| | | <el-input v-model="newBatchNo" placeholder="请è¾å
¥æ°æ¹æ¬¡å·"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="åé
åå " prop="reassignReason"> |
| | | <el-input type="textarea" v-model="reassignReason" rows="3" placeholder="请è¾å
¥éæ°åé
åå "></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="reassignDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="saveReassign">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- äºç»´ç é¢è§å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="qrCodeDialogVisible" title="äºç»´ç é¢è§" width="500px" center> |
| | | <div class="qr-preview-container"> |
| | | <div v-if="qrCodeUrl" class="qr-image-container"> |
| | | <img :src="qrCodeUrl" alt="äºç»´ç " class="qr-image" /> |
| | | <div class="qr-info"> |
| | | <p><strong>产ååç§°ï¼</strong>{{ currentQRProduct.productName }}</p> |
| | | <p><strong>产åç¼ç ï¼</strong>{{ currentQRProduct.productCode }}</p> |
| | | <p><strong>æ¹æ¬¡å·ï¼</strong>{{ currentQRProduct.batchNo }}</p> |
| | | <p><strong>æ è¯ç ï¼</strong>{{ currentQRProduct.identifierCode }}</p> |
| | | <p><strong>æ è¯ç±»åï¼</strong>{{ currentQRProduct.identifierType }}</p> |
| | | </div> |
| | | </div> |
| | | <div v-else class="qr-loading"> |
| | | <el-icon class="is-loading"><Loading /></el-icon> |
| | | <p>æ£å¨çæäºç»´ç ...</p> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="qrCodeDialogVisible = false">å
³é</el-button> |
| | | <el-button |
| | | v-if="qrCodeUrl" |
| | | type="primary" |
| | | @click="copyQRContent" |
| | | icon="CopyDocument" |
| | | > |
| | | å¤å¶å
容 |
| | | </el-button> |
| | | <el-button |
| | | v-if="qrCodeUrl" |
| | | type="success" |
| | | @click="downloadQRCode" |
| | | icon="Download" |
| | | > |
| | | ä¸è½½äºç»´ç |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search, Loading, Download } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | import QRCode from 'qrcode' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | productName: '', |
| | | identifierType: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const identifierList = ref([ |
| | | { |
| | | id: 1, |
| | | productName: 'å·¥ä¸ä¼ æå¨Aå', |
| | | productCode: 'SENSOR001', |
| | | batchNo: 'B202312001', |
| | | identifierType: 'äºç»´ç ', |
| | | identifierCode: 'QR_SENSOR001_B202312001_001', |
| | | status: 'å·²åé
', |
| | | generateTime: '2023-12-01 10:00:00', |
| | | remark: 'éè¦äº§åæ è¯' |
| | | }, |
| | | { |
| | | id: 2, |
| | | productName: 'æ§å¶é¢æ¿Bå', |
| | | productCode: 'PANEL002', |
| | | batchNo: 'B202312002', |
| | | identifierType: 'é²ä¼ªç ', |
| | | identifierCode: 'SEC_PANEL002_B202312002_001', |
| | | status: 'å·²çæ', |
| | | generateTime: '2023-12-02 14:30:00', |
| | | remark: '常è§äº§åæ è¯' |
| | | }, |
| | | { |
| | | id: 3, |
| | | productName: 'æ°æ®ééå¨Cå', |
| | | productCode: 'COLLECTOR003', |
| | | batchNo: 'B202312003', |
| | | identifierType: 'é²ä¼ªç ', |
| | | identifierCode: 'SEC_COLLECTOR003_B202312003_001', |
| | | status: '已使ç¨', |
| | | generateTime: '2023-12-03 09:15:00', |
| | | remark: 'æµè¯äº§åæ è¯' |
| | | } |
| | | ]) |
| | | |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢æ è¯') |
| | | const form = reactive({ |
| | | productName: '', |
| | | productCode: '', |
| | | batchNo: '', |
| | | identifierType: '', |
| | | quantity: 1, |
| | | status: 'å·²çæ', |
| | | remark: '' |
| | | }) |
| | | |
| | | const rules = { |
| | | productName: [{ required: true, message: '请è¾å
¥äº§ååç§°', trigger: 'blur' }], |
| | | productCode: [{ required: true, message: '请è¾å
¥äº§åç¼ç ', trigger: 'blur' }], |
| | | batchNo: [{ required: true, message: '请è¾å
¥æ¹æ¬¡å·', trigger: 'blur' }], |
| | | identifierType: [{ required: true, message: 'è¯·éæ©æ è¯ç±»å', trigger: 'change' }], |
| | | quantity: [{ required: true, message: '请è¾å
¥çææ°é', trigger: 'blur' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const generateDialogVisible = ref(false) |
| | | const reassignDialogVisible = ref(false) |
| | | const currentProduct = ref({}) |
| | | const generateQuantity = ref(1) |
| | | const codeRule = ref('') |
| | | const customPrefix = ref('') |
| | | const newBatchNo = ref('') |
| | | const reassignReason = ref('') |
| | | const formRef = ref() |
| | | |
| | | // äºç»´ç ç¸å
³åé |
| | | const qrCodeDialogVisible = ref(false) |
| | | const qrCodeUrl = ref('') |
| | | const currentQRProduct = ref({}) |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredList = computed(() => { |
| | | let list = identifierList.value |
| | | if (searchForm.productName) { |
| | | list = list.filter(item => item.productName.includes(searchForm.productName)) |
| | | } |
| | | if (searchForm.identifierType) { |
| | | list = list.filter(item => item.identifierType === searchForm.identifierType) |
| | | } |
| | | if (searchForm.status) { |
| | | list = list.filter(item => item.status === searchForm.status) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | | const getIdentifierTypeType = (type) => { |
| | | const typeMap = { |
| | | 'äºç»´ç ': 'success', |
| | | 'é²ä¼ªç ': 'warning' |
| | | } |
| | | return typeMap[type] || 'info' |
| | | } |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'å·²çæ': 'info', |
| | | 'å·²åé
': 'primary', |
| | | '已使ç¨': 'success', |
| | | 'å·²ä½åº': 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | // æç´¢é»è¾å·²å¨computedä¸å¤ç |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.productName = '' |
| | | searchForm.identifierType = '' |
| | | searchForm.status = '' |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å¢æ è¯' |
| | | isEdit.value = false |
| | | form.productName = '' |
| | | form.productCode = '' |
| | | form.batchNo = '' |
| | | form.identifierType = '' |
| | | form.quantity = 1 |
| | | form.status = 'å·²çæ' |
| | | form.remark = '' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleView = (row) => { |
| | | // æ¥çæ è¯è¯¦æ
|
| | | ElMessage.info('æ¥çæ è¯è¯¦æ
åè½å¾
å®ç°') |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ç¼è¾æ è¯' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleExport = (row) => { |
| | | // å¯¼åºæ è¯ |
| | | ElMessage.success(`å·²å¯¼åºæ è¯: ${row.identifierCode}`) |
| | | } |
| | | |
| | | const handleReassign = (row) => { |
| | | currentProduct.value = row |
| | | newBatchNo.value = '' |
| | | reassignReason.value = '' |
| | | reassignDialogVisible.value = true |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥æ è¯åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = identifierList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | identifierList.value.splice(index, 1) |
| | | pagination.total-- |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // çæäºç»´ç |
| | | const generateQRCode = async (row) => { |
| | | try { |
| | | // æ£æ¥å¿
è¦å段 |
| | | if (!row.productName || !row.productCode || !row.batchNo) { |
| | | ElMessage.warning('产åä¿¡æ¯ä¸å®æ´ï¼æ æ³çæäºç»´ç ') |
| | | return |
| | | } |
| | | |
| | | currentQRProduct.value = row |
| | | qrCodeUrl.value = '' |
| | | qrCodeDialogVisible.value = true |
| | | |
| | | // æå»ºäºç»´ç å
容 |
| | | let qrContent = '' |
| | | if (row.identifierType === 'äºç»´ç ') { |
| | | qrContent = `${row.productName}|${row.productCode}|${row.batchNo}|${row.identifierCode}` |
| | | } else if (row.identifierType === 'é²ä¼ªç ') { |
| | | // é²ä¼ªç æ ¼å¼ï¼SEC_产åç¼ç _æ¹æ¬¡å·_æ¶é´æ³_éæºæ° |
| | | const timestamp = Date.now() |
| | | const random = Math.random().toString(36).substr(2, 8) |
| | | qrContent = `SEC_${row.productCode}_${row.batchNo}_${timestamp}_${random}` |
| | | } |
| | | |
| | | // çæäºç»´ç |
| | | qrCodeUrl.value = await QRCode.toDataURL(qrContent, { |
| | | width: 256, |
| | | margin: 2, |
| | | color: { |
| | | dark: '#000000', |
| | | light: '#FFFFFF' |
| | | }, |
| | | errorCorrectionLevel: row.identifierType === 'é²ä¼ªç ' ? 'H' : 'M' |
| | | }) |
| | | |
| | | ElMessage.success('äºç»´ç çææåï¼') |
| | | |
| | | } catch (error) { |
| | | console.error('çæäºç»´ç 失败:', error) |
| | | ElMessage.error('çæäºç»´ç 失败ï¼' + error.message) |
| | | qrCodeDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | // ä¸è½½äºç»´ç |
| | | const downloadQRCode = () => { |
| | | if (!qrCodeUrl.value) { |
| | | ElMessage.warning('请å
çæäºç»´ç ') |
| | | return |
| | | } |
| | | |
| | | const a = document.createElement('a') |
| | | a.href = qrCodeUrl.value |
| | | a.download = `${currentQRProduct.value.productName}_${currentQRProduct.value.identifierType}_${new Date().getTime()}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | ElMessage.success('ä¸è½½æåï¼') |
| | | } |
| | | |
| | | // å¤å¶äºç»´ç å
容 |
| | | const copyQRContent = async () => { |
| | | if (!currentQRProduct.value) { |
| | | ElMessage.warning('没æå¯å¤å¶çå
容') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | let content = '' |
| | | if (currentQRProduct.value.identifierType === 'äºç»´ç ') { |
| | | content = `${currentQRProduct.value.productName}|${currentQRProduct.value.productCode}|${currentQRProduct.value.batchNo}|${currentQRProduct.value.identifierCode}` |
| | | } else if (currentQRProduct.value.identifierType === 'é²ä¼ªç ') { |
| | | const timestamp = Date.now() |
| | | const random = Math.random().toString(36).substr(2, 8) |
| | | content = `SEC_${currentQRProduct.value.productCode}_${currentQRProduct.value.batchNo}_${timestamp}_${random}` |
| | | } |
| | | |
| | | await navigator.clipboard.writeText(content) |
| | | ElMessage.success('å
容已å¤å¶å°åªè´´æ¿') |
| | | } catch (error) { |
| | | // éçº§æ¹æ¡ |
| | | const textArea = document.createElement('textarea') |
| | | textArea.value = content |
| | | document.body.appendChild(textArea) |
| | | textArea.select() |
| | | document.execCommand('copy') |
| | | document.body.removeChild(textArea) |
| | | ElMessage.success('å
容已å¤å¶å°åªè´´æ¿') |
| | | } |
| | | } |
| | | |
| | | const generateIdentifiers = () => { |
| | | if (!codeRule.value) { |
| | | ElMessage.warning('è¯·éæ©ç¼ç è§å') |
| | | return |
| | | } |
| | | |
| | | // çææ è¯çé»è¾ |
| | | const newIdentifiers = [] |
| | | for (let i = 1; i <= generateQuantity.value; i++) { |
| | | let identifierCode = '' |
| | | if (codeRule.value === '产åç¼ç +æ¹æ¬¡å·+åºå·') { |
| | | identifierCode = `${currentProduct.value.productCode}_${currentProduct.value.batchNo}_${String(i).padStart(3, '0')}` |
| | | } else if (codeRule.value === 'æ¶é´æ³+éæºæ°') { |
| | | identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}` |
| | | } else if (codeRule.value === 'èªå®ä¹è§å') { |
| | | identifierCode = `${customPrefix.value || 'CUSTOM'}_${Date.now()}_${i}` |
| | | } |
| | | |
| | | newIdentifiers.push({ |
| | | id: Math.max(...identifierList.value.map(item => item.id)) + i, |
| | | productName: currentProduct.value.productName, |
| | | productCode: currentProduct.value.productCode, |
| | | batchNo: currentProduct.value.batchNo, |
| | | identifierType: currentProduct.value.identifierType, |
| | | identifierCode: identifierCode, |
| | | status: 'å·²çæ', |
| | | generateTime: new Date().toLocaleString(), |
| | | remark: 'æ¹éçæ' |
| | | }) |
| | | } |
| | | |
| | | identifierList.value.push(...newIdentifiers) |
| | | pagination.total += newIdentifiers.length |
| | | ElMessage.success(`æåçæ ${newIdentifiers.length} 个æ è¯`) |
| | | generateDialogVisible.value = false |
| | | } |
| | | |
| | | const saveReassign = () => { |
| | | if (!newBatchNo.value) { |
| | | ElMessage.warning('请è¾å
¥æ°æ¹æ¬¡å·') |
| | | return |
| | | } |
| | | |
| | | const index = identifierList.value.findIndex(item => item.id === currentProduct.value.id) |
| | | if (index > -1) { |
| | | identifierList.value[index].batchNo = newBatchNo.value |
| | | identifierList.value[index].status = 'å·²åé
' |
| | | ElMessage.success('æ è¯éæ°åé
æå') |
| | | reassignDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = identifierList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | identifierList.value[index] = { ...form, id: editId.value } |
| | | ElMessage.success('ç¼è¾æå') |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | const newId = Math.max(...identifierList.value.map(item => item.id)) + 1 |
| | | |
| | | // æ ¹æ®æ è¯ç±»åçæä¸åçæ è¯ç |
| | | let identifierCode = '' |
| | | if (form.identifierType === 'äºç»´ç ') { |
| | | identifierCode = `QR_${form.productCode}_${form.batchNo}_001` |
| | | } else if (form.identifierType === 'é²ä¼ªç ') { |
| | | identifierCode = `SEC_${form.productCode}_${form.batchNo}_001` |
| | | } |
| | | |
| | | identifierList.value.push({ |
| | | ...form, |
| | | id: newId, |
| | | identifierCode: identifierCode, |
| | | generateTime: new Date().toLocaleString() |
| | | }) |
| | | pagination.total++ |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val.page |
| | | pagination.pageSize = val.limit |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .quick-actions-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .quick-actions-row .el-alert { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .quick-actions-row .el-alert p { |
| | | margin: 5px 0; |
| | | font-size: 14px; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | /* äºç»´ç é¢è§æ ·å¼ */ |
| | | .qr-preview-container { |
| | | text-align: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .qr-image-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .qr-image { |
| | | max-width: 100%; |
| | | height: auto; |
| | | border: 2px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .qr-info { |
| | | text-align: left; |
| | | background: #f8f9fa; |
| | | padding: 15px; |
| | | border-radius: 8px; |
| | | min-width: 300px; |
| | | } |
| | | |
| | | .qr-info p { |
| | | margin: 8px 0; |
| | | color: #666; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .qr-loading { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 15px; |
| | | padding: 40px 0; |
| | | } |
| | | |
| | | .qr-loading .el-icon { |
| | | font-size: 32px; |
| | | color: #409EFF; |
| | | } |
| | | |
| | | .qr-loading p { |
| | | color: #666; |
| | | margin: 0; |
| | | } |
| | | </style> |
| | |
| | | <div style="display: flex;flex-direction: row;align-items: center;"> |
| | | <div> |
| | | <span class="search_title">ç±»åï¼</span> |
| | | <el-select v-model="searchForm.inspectType" clearable style="width: 240px" @change="handleQuery"> |
| | | <el-select v-model="searchForm.inspectType" clearable style="width: 200px" @change="handleQuery"> |
| | | <el-option label="åæææ£éª" :value="0" /> |
| | | <el-option label="è¿ç¨æ£éª" :value="1" /> |
| | | <el-option label="åºåæ£éª" :value="2" /> |
| | |
| | | </div> |
| | | <div style="margin-left: 10px"> |
| | | <span class="search_title">ç¶æï¼</span> |
| | | <el-select v-model="searchForm.inspectState" clearable style="width: 240px" @change="handleQuery"> |
| | | <el-select v-model="searchForm.inspectState" clearable style="width: 200px" @change="handleQuery"> |
| | | <el-option label="å¾
å¤ç" :value="0" /> |
| | | <el-option label="å·²å¤ç" :value="1" /> |
| | | </el-select> |
| | |
| | | <span class="search_title">产ååç§°ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.productName" |
| | | style="width: 240px" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥äº§ååç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | |
| | | </div> |
| | | <span style="margin-left: 10px" class="search_title">æ£æµæ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | style="width: 300px" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="quality-dashboard"> |
| | | <el-row :gutter="16"> |
| | | <el-col :xs="24" :sm="12"> |
| | | <el-card shadow="hover" class="panel"> |
| | | <template #header> |
| | | <div class="panel-title"> |
| | | æ£æµæ ·åå¨æç¶æ |
| | | <div class="actions"> |
| | | <el-switch v-model="voiceEnabled" active-text="è¯é³é¢è¦" inactive-text="éé³" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <div class="status-list"> |
| | | <div v-for="item in sampleStatus" :key="item.id" class="status-item" :class="item.status"> |
| | | <div class="left"> |
| | | <span class="dot" :class="item.status"></span> |
| | | <span class="name">{{ item.name }}</span> |
| | | </div> |
| | | <div class="right"> |
| | | <el-tag :type="statusTagType(item.status)" size="small">{{ statusLabel(item.status) }}</el-tag> |
| | | <span class="time">{{ item.time }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12"> |
| | | <el-card shadow="hover" class="panel"> |
| | | <template #header> |
| | | <div class="panel-title">任塿è¡ï¼Top 10ï¼</div> |
| | | </template> |
| | | <EChart :xAxis="tasksXAxis" :yAxis="[{ type: 'value' }]" :series="tasksSeries" :grid="{ left: 40, right: 20, top: 20, bottom: 40 }" :tooltip="{ trigger: 'axis' }" :barColors="['#3b82f6']" :chartStyle="{ height: '320px', width: '100%' }" /> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="16" style="margin-top: 16px;"> |
| | | <el-col :xs="24" :sm="14"> |
| | | <el-card shadow="hover" class="panel"> |
| | | <template #header> |
| | | <div class="panel-title">åå²è¶å¿</div> |
| | | </template> |
| | | <EChart :xAxis="[{ type: 'category', data: trendXAxis }]" :yAxis="[{ type: 'value', name: 'æ°é' }]" :series="trendSeries" :tooltip="{ trigger: 'axis' }" :legend="{ top: 0 }" :lineColors="['#10b981', '#f59e0b']" :chartStyle="{ height: '340px', width: '100%' }" /> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="10"> |
| | | <el-card shadow="hover" class="panel"> |
| | | <template #header> |
| | | <div class="panel-title">åæ ¼çåæ</div> |
| | | </template> |
| | | <EChart :series="passRateSeries" :legend="{ show: false }" :chartStyle="{ height: '340px', width: '100%' }" /> |
| | | <div class="passrate-text"> |
| | | å½ååæ ¼çï¼<b>{{ (passRate * 100).toFixed(1) }}%</b> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="16" style="margin-top: 16px;"> |
| | | <el-col :xs="24"> |
| | | <el-card shadow="hover" class="panel"> |
| | | <template #header> |
| | | <div class="panel-title">SPC æ§å¶å¾ï¼Xbarï¼</div> |
| | | </template> |
| | | <EChart :xAxis="[{ type: 'category', data: spcXAxis }]" :yAxis="[{ type: 'value', name: 'æµéå¼' }]" :series="spcSeries" :legend="{ top: 0 }" :tooltip="{ trigger: 'axis' }" :lineColors="['#2563eb', '#ef4444', '#f97316', '#22c55e']" :chartStyle="{ height: '380px', width: '100%' }" /> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, onBeforeUnmount, reactive, ref } from 'vue' |
| | | import EChart from '@/components/Echarts/echarts.vue' |
| | | |
| | | const voiceEnabled = ref(true) |
| | | let dataTimer = null |
| | | |
| | | // 1) æ ·åå¨æç¶æï¼æ»å¨æ´æ°ï¼ |
| | | const sampleStatus = ref([]) |
| | | const statusPool = ['processing', 'warning', 'error', 'success'] |
| | | function statusLabel(s) { |
| | | return s === 'processing' ? 'æ£æµä¸' : s === 'warning' ? 'é¢è¦' : s === 'error' ? 'ä¸åæ ¼' : 'åæ ¼' |
| | | } |
| | | function statusTagType(s) { |
| | | return s === 'processing' ? 'info' : s === 'warning' ? 'warning' : s === 'error' ? 'danger' : 'success' |
| | | } |
| | | function randomSample() { |
| | | const id = Math.random().toString(36).slice(2, 8) |
| | | const status = statusPool[Math.floor(Math.random() * statusPool.length)] |
| | | const name = `æ ·å-${Math.floor(Math.random() * 900 + 100)}` |
| | | const time = new Date().toLocaleTimeString('zh-CN', { hour12: false }) |
| | | return { id, name, status, time } |
| | | } |
| | | |
| | | // 2) 任塿è¡ï¼æ±ç¶å¾ï¼ |
| | | const tasksXAxis = reactive([{ type: 'category', data: [] }]) |
| | | const tasksSeries = ref([ |
| | | { |
| | | type: 'bar', |
| | | data: [], |
| | | label: { show: true, position: 'inside', align: 'center', verticalAlign: 'middle', color: '#fff' }, |
| | | encode: undefined, |
| | | }, |
| | | ]) |
| | | |
| | | // 3) åå²è¶å¿ï¼æçº¿ï¼ |
| | | const trendXAxis = ref([]) |
| | | const trendSeries = ref([ |
| | | { name: 'æ¥æ ·æ°', type: 'line', smooth: true, data: [] }, |
| | | { name: '宿æ°', type: 'line', smooth: true, data: [] }, |
| | | ]) |
| | | |
| | | // 4) åæ ¼çåæï¼ä»ªè¡¨çï¼ |
| | | const passRate = ref(0.92) |
| | | const passRateSeries = ref([ |
| | | { |
| | | type: 'gauge', |
| | | progress: { show: true, width: 12 }, |
| | | axisLine: { lineStyle: { width: 12 } }, |
| | | pointer: { show: true }, |
| | | detail: { valueAnimation: true, formatter: (v) => `${(v * 100).toFixed(1)}%` }, |
| | | data: [{ value: passRate.value }], |
| | | }, |
| | | ]) |
| | | |
| | | // 5) SPC æ§å¶å¾ |
| | | const spcXAxis = ref([]) |
| | | const spcData = ref([]) // å®é
æµéå¼ |
| | | const CL = ref(50) |
| | | const UCL = ref(55) |
| | | const LCL = ref(45) |
| | | const spcSeries = ref([ |
| | | { |
| | | name: 'æµéåå¼', |
| | | type: 'line', |
| | | smooth: false, |
| | | symbol: 'circle', |
| | | data: [], |
| | | markLine: { |
| | | symbol: 'none', |
| | | lineStyle: { type: 'dashed', color: '#999' }, |
| | | data: [ |
| | | { yAxis: () => UCL.value, name: 'UCL' }, |
| | | { yAxis: () => CL.value, name: 'CL' }, |
| | | { yAxis: () => LCL.value, name: 'LCL' }, |
| | | ], |
| | | label: { formatter: ({ name }) => name }, |
| | | }, |
| | | }, |
| | | { name: 'UCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#ef4444' } }, |
| | | { name: 'CL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#f97316' } }, |
| | | { name: 'LCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#22c55e' } }, |
| | | ]) |
| | | |
| | | // è¯é³ææ¥ |
| | | function speak(text) { |
| | | if (!voiceEnabled.value) return |
| | | if (!('speechSynthesis' in window)) return |
| | | const utter = new SpeechSynthesisUtterance(text) |
| | | utter.lang = 'zh-CN' |
| | | try { |
| | | window.speechSynthesis.cancel() |
| | | window.speechSynthesis.speak(utter) |
| | | } catch (e) { |
| | | // ignore |
| | | } |
| | | } |
| | | |
| | | function refreshFakeData() { |
| | | // æ ·åç¶ææ»å¨ |
| | | const next = randomSample() |
| | | sampleStatus.value = [next, ...sampleStatus.value].slice(0, 8) |
| | | |
| | | // ä»»å¡æè¡ |
| | | const tasks = Array.from({ length: 10 }).map((_, i) => ({ name: `ä»»å¡-${i + 1}`, count: Math.floor(Math.random() * 100 + 20) })) |
| | | tasks.sort((a, b) => a.count - b.count) |
| | | tasksXAxis.data = tasks.map(t => t.name) |
| | | tasksSeries.value[0].data = tasks.map(t => t.count) |
| | | |
| | | // åå²è¶å¿ï¼è¿½å ç¹ï¼ |
| | | const nowLabel = new Date().toLocaleTimeString('zh-CN', { minute: '2-digit', second: '2-digit' }) |
| | | if (trendXAxis.value.length > 15) { |
| | | trendXAxis.value.shift() |
| | | trendSeries.value[0].data.shift() |
| | | trendSeries.value[1].data.shift() |
| | | } |
| | | trendXAxis.value.push(nowLabel) |
| | | const incoming = Math.floor(Math.random() * 30 + 20) |
| | | const finished = Math.max(0, incoming - Math.floor(Math.random() * 10)) |
| | | trendSeries.value[0].data.push(incoming) |
| | | trendSeries.value[1].data.push(finished) |
| | | |
| | | // åæ ¼çï¼è½»å¾®æ³¢å¨ï¼ |
| | | const delta = (Math.random() - 0.5) * 0.02 |
| | | passRate.value = Math.min(0.99, Math.max(0.6, passRate.value + delta)) |
| | | passRateSeries.value[0].data[0].value = passRate.value |
| | | |
| | | // SPC æ°æ®ï¼çªå£ç§»å¨ï¼ |
| | | const nextVal = CL.value + (Math.random() - 0.5) * 8 // æ³¢å¨ |
| | | if (spcXAxis.value.length > 30) { |
| | | spcXAxis.value.shift() |
| | | spcData.value.shift() |
| | | } |
| | | spcXAxis.value.push(`${spcXAxis.value.length + 1}`) |
| | | spcData.value.push(parseFloat(nextVal.toFixed(2))) |
| | | spcSeries.value[0].data = [...spcData.value] |
| | | spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value) |
| | | spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value) |
| | | spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value) |
| | | |
| | | // è§¦åææ¥ï¼åæ ¼çè¿ä½æ SPC è¶
é |
| | | if (passRate.value < 0.8) { |
| | | speak(`é¢è¦ï¼å½ååæ ¼ç为 ${(passRate.value * 100).toFixed(0)}%ï¼ä½äº 80% éå¼`) |
| | | } |
| | | const last = spcData.value[spcData.value.length - 1] |
| | | if (last > UCL.value) { |
| | | speak(`é¢è¦ï¼ææ°æµéå¼ ${last.toFixed(2)} è¶
è¿ä¸é`) |
| | | } |
| | | if (last < LCL.value) { |
| | | speak(`é¢è¦ï¼ææ°æµéå¼ ${last.toFixed(2)} ä½äºä¸é`) |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | // åå§åå æ¡åæ°æ® |
| | | sampleStatus.value = Array.from({ length: 5 }).map(() => randomSample()) |
| | | for (let i = 0; i < 10; i++) { |
| | | trendXAxis.value.push(`T-${i}`) |
| | | trendSeries.value[0].data.push(Math.floor(Math.random() * 30 + 20)) |
| | | trendSeries.value[1].data.push(Math.floor(Math.random() * 25 + 15)) |
| | | } |
| | | for (let i = 0; i < 20; i++) { |
| | | spcXAxis.value.push(`${i + 1}`) |
| | | const v = CL.value + (Math.random() - 0.5) * 6 |
| | | spcData.value.push(parseFloat(v.toFixed(2))) |
| | | } |
| | | spcSeries.value[0].data = [...spcData.value] |
| | | spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value) |
| | | spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value) |
| | | spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value) |
| | | |
| | | dataTimer = setInterval(refreshFakeData, 10000) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (dataTimer) clearInterval(dataTimer) |
| | | try { window.speechSynthesis && window.speechSynthesis.cancel() } catch (e) {} |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .quality-dashboard { |
| | | padding: 8px; |
| | | } |
| | | .panel-title { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | font-weight: 600; |
| | | } |
| | | .status-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | max-height: 320px; |
| | | overflow: auto; |
| | | } |
| | | .status-item { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 8px 10px; |
| | | border-radius: 6px; |
| | | background: var(--el-fill-color-light); |
| | | } |
| | | .status-item .left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .status-item .right { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | .status-item .name { font-weight: 500; } |
| | | .status-item .time { color: var(--el-text-color-secondary); font-size: 12px; } |
| | | .dot { |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | display: inline-block; |
| | | } |
| | | .dot.processing { background: #60a5fa; } |
| | | .dot.warning { background: #f59e0b; } |
| | | .dot.error { background: #ef4444; } |
| | | .dot.success { background: #10b981; } |
| | | .passrate-text { |
| | | text-align: center; |
| | | margin-top: 8px; |
| | | } |
| | | </style> |
| | | |
| | | |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="report-management"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>æ¥è¡¨ç®¡ç</h2> |
| | | <p>æä¾æ ·åè¿åº¦ã设å¤ä½¿ç¨ãæ£æµé¡¹ç®ãé¢ç¨è®°å½çåç±»èªå¨ç»è®¡æ¥è¡¨</p> |
| | | </div> |
| | | |
| | | <!-- ç鿡件 --> |
| | | <el-card class="filter-card" shadow="never"> |
| | | <el-form :model="filterForm" inline> |
| | | <el-form-item label="æ¶é´èå´"> |
| | | <el-date-picker |
| | | v-model="filterForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | @change="handleFilterChange" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥è¡¨ç±»å"> |
| | | <el-select v-model="filterForm.reportType" placeholder="è¯·éæ©æ¥è¡¨ç±»å" @change="handleFilterChange"> |
| | | <el-option label="æ ·åè¿åº¦æ¥è¡¨" value="sample" /> |
| | | <el-option label="设å¤ä½¿ç¨æ¥è¡¨" value="equipment" /> |
| | | <el-option label="æ£æµé¡¹ç®æ¥è¡¨" value="inspection" /> |
| | | <el-option label="é¢ç¨è®°å½æ¥è¡¨" value="usage" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleFilterChange">æ¥è¯¢</el-button> |
| | | <el-button @click="resetFilter">éç½®</el-button> |
| | | <el-button type="success" @click="exportReport">å¯¼åºæ¥è¡¨</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- ç»è®¡å¡ç --> |
| | | <div class="statistics-cards"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><Box /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.totalSamples }}</div> |
| | | <div class="stat-label">æ»æ ·åæ°</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><Tools /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.activeEquipment }}</div> |
| | | <div class="stat-label">å¨ç¨è®¾å¤</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><Document /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.completedInspections }}</div> |
| | | <div class="stat-label">å·²å®ææ£æµ</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><ShoppingCart /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.totalUsage }}</div> |
| | | <div class="stat-label">æ»é¢ç¨æ¬¡æ°</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <div class="charts-container"> |
| | | <el-row :gutter="20"> |
| | | <!-- æ ·åè¿åº¦å¾è¡¨ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ ·åè¿åº¦ç»è®¡</span> |
| | | <el-button type="text" @click="refreshSampleChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="sampleChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- 设å¤ä½¿ç¨å¾è¡¨ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>设å¤ä½¿ç¨çç»è®¡</span> |
| | | <el-button type="text" @click="refreshEquipmentChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="equipmentChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20" style="margin-top: 20px;"> |
| | | <!-- æ£æµé¡¹ç®ç»è®¡ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ£æµé¡¹ç®åå¸</span> |
| | | <el-button type="text" @click="refreshInspectionChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="inspectionChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- é¢ç¨è®°å½è¶å¿ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>é¢ç¨è®°å½è¶å¿</span> |
| | | <el-button type="text" @click="refreshUsageChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="usageChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- è¯¦ç»æ°æ®è¡¨æ ¼ --> |
| | | <el-card class="table-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>è¯¦ç»æ°æ®</span> |
| | | <div> |
| | | <el-button type="primary" size="small" @click="refreshTable">å·æ°</el-button> |
| | | <el-button type="success" size="small" @click="exportTable">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <el-table |
| | | :data="tableData" |
| | | style="width: 100%" |
| | | v-loading="tableLoading" |
| | | stripe |
| | | border |
| | | > |
| | | <el-table-column prop="id" label="ç¼å·" width="80" /> |
| | | <el-table-column prop="name" label="åç§°" /> |
| | | <el-table-column prop="type" label="ç±»å" width="120" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="progress" label="è¿åº¦" width="120"> |
| | | <template #default="scope"> |
| | | <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createTime" label="å建æ¶é´" width="180" /> |
| | | <el-table-column prop="updateTime" label="æ´æ°æ¶é´" width="180" /> |
| | | <el-table-column label="æä½" width="150" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="text" size="small" @click="viewDetail(scope.row)">æ¥ç</el-button> |
| | | <el-button type="text" size="small" @click="editItem(scope.row)">ç¼è¾</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <div class="pagination-container"> |
| | | <el-pagination |
| | | v-model:current-page="pagination.currentPage" |
| | | v-model:page-size="pagination.pageSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import * as echarts from 'echarts' |
| | | import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const filterForm = reactive({ |
| | | dateRange: [], |
| | | reportType: 'sample' |
| | | }) |
| | | |
| | | const statistics = reactive({ |
| | | totalSamples: 1250, |
| | | activeEquipment: 45, |
| | | completedInspections: 890, |
| | | totalUsage: 2340 |
| | | }) |
| | | |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 20, |
| | | total: 0 |
| | | }) |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const sampleChartRef = ref(null) |
| | | const equipmentChartRef = ref(null) |
| | | const inspectionChartRef = ref(null) |
| | | const usageChartRef = ref(null) |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | let sampleChart = null |
| | | let equipmentChart = null |
| | | let inspectionChart = null |
| | | let usageChart = null |
| | | |
| | | // åå§åæ°æ® |
| | | const initData = () => { |
| | | // 模æè¡¨æ ¼æ°æ® |
| | | tableData.value = [ |
| | | { |
| | | id: 'SP001', |
| | | name: 'æ ·åA-001', |
| | | type: 'é屿æ', |
| | | status: 'æ£æµä¸', |
| | | progress: 75, |
| | | createTime: '2024-01-15 09:30:00', |
| | | updateTime: '2024-01-20 14:20:00' |
| | | }, |
| | | { |
| | | id: 'SP002', |
| | | name: 'æ ·åB-002', |
| | | type: '塿å¶å', |
| | | status: '已宿', |
| | | progress: 100, |
| | | createTime: '2024-01-10 10:15:00', |
| | | updateTime: '2024-01-18 16:45:00' |
| | | }, |
| | | { |
| | | id: 'SP003', |
| | | name: 'æ ·åC-003', |
| | | type: 'çµåå
ä»¶', |
| | | status: 'å¾
æ£æµ', |
| | | progress: 0, |
| | | createTime: '2024-01-22 08:45:00', |
| | | updateTime: '2024-01-22 08:45:00' |
| | | }, |
| | | { |
| | | id: 'EQ001', |
| | | name: 'æ£æµè®¾å¤A', |
| | | type: 'å
谱仪', |
| | | status: '使ç¨ä¸', |
| | | progress: 60, |
| | | createTime: '2024-01-05 14:20:00', |
| | | updateTime: '2024-01-20 11:30:00' |
| | | }, |
| | | { |
| | | id: 'EQ002', |
| | | name: 'æ£æµè®¾å¤B', |
| | | type: 'æ¾å¾®é', |
| | | status: '空é²', |
| | | progress: 0, |
| | | createTime: '2024-01-08 16:10:00', |
| | | updateTime: '2024-01-19 09:15:00' |
| | | } |
| | | ] |
| | | |
| | | pagination.total = tableData.value.length |
| | | } |
| | | |
| | | // åå§åæ ·åè¿åº¦å¾è¡¨ |
| | | const initSampleChart = () => { |
| | | if (sampleChartRef.value) { |
| | | sampleChart = echarts.init(sampleChartRef.value) |
| | | const option = { |
| | | title: { |
| | | text: 'æ ·åè¿åº¦åå¸', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b}: {c} ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'æ ·åç¶æ', |
| | | type: 'pie', |
| | | radius: ['40%', '70%'], |
| | | avoidLabelOverlap: false, |
| | | label: { |
| | | show: false, |
| | | position: 'center' |
| | | }, |
| | | emphasis: { |
| | | label: { |
| | | show: true, |
| | | fontSize: '18', |
| | | fontWeight: 'bold' |
| | | } |
| | | }, |
| | | labelLine: { |
| | | show: false |
| | | }, |
| | | data: [ |
| | | { value: 450, name: '已宿' }, |
| | | { value: 320, name: 'æ£æµä¸' }, |
| | | { value: 280, name: 'å¾
æ£æµ' }, |
| | | { value: 200, name: 'å·²æå' } |
| | | ] |
| | | } |
| | | ] |
| | | } |
| | | sampleChart.setOption(option) |
| | | } |
| | | } |
| | | |
| | | // åå§å设å¤ä½¿ç¨å¾è¡¨ |
| | | const initEquipmentChart = () => { |
| | | if (equipmentChartRef.value) { |
| | | equipmentChart = echarts.init(equipmentChartRef.value) |
| | | const option = { |
| | | title: { |
| | | text: '设å¤ä½¿ç¨ç', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: ['å
谱仪', 'æ¾å¾®é', '硬度计', 'æåæº', 'å²å»æº', 'éç¸ä»ª'] |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: '使ç¨ç(%)' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '使ç¨ç', |
| | | type: 'bar', |
| | | data: [85, 60, 75, 90, 45, 70], |
| | | label: { |
| | | show: true, |
| | | position: 'inside', |
| | | align: 'center', |
| | | verticalAlign: 'middle', |
| | | formatter: '{c}%', |
| | | color: '#fff' |
| | | }, |
| | | itemStyle: { |
| | | color: function(params) { |
| | | const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0'] |
| | | return colors[params.dataIndex] |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | equipmentChart.setOption(option) |
| | | } |
| | | } |
| | | |
| | | // åå§åæ£æµé¡¹ç®å¾è¡¨ |
| | | const initInspectionChart = () => { |
| | | if (inspectionChartRef.value) { |
| | | inspectionChart = echarts.init(inspectionChartRef.value) |
| | | const option = { |
| | | title: { |
| | | text: 'æ£æµé¡¹ç®åå¸', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'æ£æµé¡¹ç®', |
| | | type: 'pie', |
| | | radius: '50%', |
| | | data: [ |
| | | { value: 335, name: 'ç©çæ§è½' }, |
| | | { value: 310, name: 'åå¦åæ' }, |
| | | { value: 234, name: '尺寸æµé' }, |
| | | { value: 135, name: 'å¤è§æ£æ¥' }, |
| | | { value: 148, name: 'å
¶ä»æ£æµ' } |
| | | ], |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: 'rgba(0, 0, 0, 0.5)' |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | inspectionChart.setOption(option) |
| | | } |
| | | } |
| | | |
| | | // åå§åé¢ç¨è®°å½å¾è¡¨ |
| | | const initUsageChart = () => { |
| | | if (usageChartRef.value) { |
| | | usageChart = echarts.init(usageChartRef.value) |
| | | const option = { |
| | | title: { |
| | | text: 'é¢ç¨è®°å½è¶å¿', |
| | | left: 'center' |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | legend: { |
| | | data: ['é¢ç¨æ¬¡æ°', 'å½è¿æ¬¡æ°'] |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | boundaryGap: false, |
| | | data: ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'] |
| | | }, |
| | | yAxis: { |
| | | type: 'value' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'é¢ç¨æ¬¡æ°', |
| | | type: 'line', |
| | | stack: 'Total', |
| | | data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330] |
| | | }, |
| | | { |
| | | name: 'å½è¿æ¬¡æ°', |
| | | type: 'line', |
| | | stack: 'Total', |
| | | data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320] |
| | | } |
| | | ] |
| | | } |
| | | usageChart.setOption(option) |
| | | } |
| | | } |
| | | |
| | | // äºä»¶å¤ç彿° |
| | | const handleFilterChange = () => { |
| | | ElMessage.success('çéæ¡ä»¶å·²æ´æ°') |
| | | // è¿éå¯ä»¥æ ¹æ®ç鿡件鿰å è½½æ°æ® |
| | | } |
| | | |
| | | const resetFilter = () => { |
| | | filterForm.dateRange = [] |
| | | filterForm.reportType = 'sample' |
| | | ElMessage.info('ç鿡件已éç½®') |
| | | } |
| | | |
| | | const exportReport = () => { |
| | | ElMessage.success('æ¥è¡¨å¯¼åºåè½å¼åä¸...') |
| | | } |
| | | |
| | | const refreshSampleChart = () => { |
| | | initSampleChart() |
| | | ElMessage.success('æ ·åè¿åº¦å¾è¡¨å·²å·æ°') |
| | | } |
| | | |
| | | const refreshEquipmentChart = () => { |
| | | initEquipmentChart() |
| | | ElMessage.success('设å¤ä½¿ç¨å¾è¡¨å·²å·æ°') |
| | | } |
| | | |
| | | const refreshInspectionChart = () => { |
| | | initInspectionChart() |
| | | ElMessage.success('æ£æµé¡¹ç®å¾è¡¨å·²å·æ°') |
| | | } |
| | | |
| | | const refreshUsageChart = () => { |
| | | initUsageChart() |
| | | ElMessage.success('é¢ç¨è®°å½å¾è¡¨å·²å·æ°') |
| | | } |
| | | |
| | | const refreshTable = () => { |
| | | tableLoading.value = true |
| | | setTimeout(() => { |
| | | tableLoading.value = false |
| | | ElMessage.success('è¡¨æ ¼æ°æ®å·²å·æ°') |
| | | }, 1000) |
| | | } |
| | | |
| | | const exportTable = () => { |
| | | ElMessage.success('è¡¨æ ¼å¯¼åºåè½å¼åä¸...') |
| | | } |
| | | |
| | | const handleSizeChange = (val) => { |
| | | pagination.pageSize = val |
| | | // éæ°å è½½æ°æ® |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val |
| | | // éæ°å è½½æ°æ® |
| | | } |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | '已宿': 'success', |
| | | 'æ£æµä¸': 'warning', |
| | | 'å¾
æ£æµ': 'info', |
| | | 'å·²æå': 'danger', |
| | | '使ç¨ä¸': 'primary', |
| | | '空é²': 'info' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const getProgressStatus = (progress) => { |
| | | if (progress === 100) return 'success' |
| | | if (progress >= 80) return 'warning' |
| | | if (progress >= 50) return '' |
| | | return 'exception' |
| | | } |
| | | |
| | | const viewDetail = (row) => { |
| | | ElMessage.info(`æ¥ç详æ
: ${row.name}`) |
| | | } |
| | | |
| | | const editItem = (row) => { |
| | | ElMessage.info(`ç¼è¾é¡¹ç®: ${row.name}`) |
| | | } |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | initData() |
| | | nextTick(() => { |
| | | initSampleChart() |
| | | initEquipmentChart() |
| | | initInspectionChart() |
| | | initUsageChart() |
| | | }) |
| | | |
| | | // çå¬çªå£å¤§å°ååï¼éæ°è°æ´å¾è¡¨å¤§å° |
| | | window.addEventListener('resize', () => { |
| | | sampleChart?.resize() |
| | | equipmentChart?.resize() |
| | | inspectionChart?.resize() |
| | | usageChart?.resize() |
| | | }) |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .report-management { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | color: #303133; |
| | | margin-bottom: 8px; |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .page-header p { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | margin: 0; |
| | | } |
| | | |
| | | .filter-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .statistics-cards { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .stat-card { |
| | | height: 120px; |
| | | } |
| | | |
| | | .stat-content { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 100%; |
| | | } |
| | | |
| | | .stat-icon { |
| | | width: 60px; |
| | | height: 60px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 20px; |
| | | font-size: 24px; |
| | | color: white; |
| | | } |
| | | |
| | | .stat-card:nth-child(1) .stat-icon { |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | } |
| | | |
| | | .stat-card:nth-child(2) .stat-icon { |
| | | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| | | } |
| | | |
| | | .stat-card:nth-child(3) .stat-icon { |
| | | background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
| | | } |
| | | |
| | | .stat-card:nth-child(4) .stat-icon { |
| | | background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); |
| | | } |
| | | |
| | | .stat-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .stat-number { |
| | | font-size: 28px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .charts-container { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .chart-container { |
| | | height: 300px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .table-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .pagination-container { |
| | | margin-top: 20px; |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 15px 20px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 20px; |
| | | } |
| | | |
| | | :deep(.el-table) { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | :deep(.el-progress) { |
| | | margin: 0; |
| | | } |
| | | |
| | | :deep(.el-tag) { |
| | | margin: 0; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="report-management"> |
| | | <!-- ç鿡件 --> |
| | | <el-card class="filter-card" shadow="never"> |
| | | <el-form :model="filterForm" inline> |
| | | <el-form-item label="æ¶é´èå´"> |
| | | <el-date-picker |
| | | style="width: 300px" |
| | | v-model="filterForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | @change="handleFilterChange" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥è¡¨ç±»å"> |
| | | <el-select v-model="filterForm.reportType" placeholder="è¯·éæ©æ¥è¡¨ç±»å" @change="handleFilterChange" style="width: 300px"> |
| | | <el-option label="æ ·åè¿åº¦æ¥è¡¨" value="sample" /> |
| | | <el-option label="设å¤ä½¿ç¨æ¥è¡¨" value="equipment" /> |
| | | <el-option label="æ£æµé¡¹ç®æ¥è¡¨" value="inspection" /> |
| | | <el-option label="é¢ç¨è®°å½æ¥è¡¨" value="usage" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleFilterChange">æ¥è¯¢</el-button> |
| | | <el-button @click="resetFilter">éç½®</el-button> |
| | | <el-button type="success" @click="exportReport">å¯¼åºæ¥è¡¨</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- ç»è®¡å¡ç --> |
| | | <div class="statistics-cards"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><Box /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.totalSamples }}</div> |
| | | <div class="stat-label">æ»æ ·åæ°</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><Tools /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.activeEquipment }}</div> |
| | | <div class="stat-label">å¨ç¨è®¾å¤</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><Document /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.completedInspections }}</div> |
| | | <div class="stat-label">å·²å®ææ£æµ</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><ShoppingCart /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.totalUsage }}</div> |
| | | <div class="stat-label">æ»é¢ç¨æ¬¡æ°</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <div class="charts-container"> |
| | | <el-row :gutter="20"> |
| | | <!-- æ ·åè¿åº¦å¾è¡¨ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ ·åè¿åº¦ç»è®¡</span> |
| | | <el-button link @click="refreshSampleChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="sampleChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- 设å¤ä½¿ç¨å¾è¡¨ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>设å¤ä½¿ç¨çç»è®¡</span> |
| | | <el-button link @click="refreshEquipmentChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="equipmentChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20" style="margin-top: 20px;"> |
| | | <!-- æ£æµé¡¹ç®ç»è®¡ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ£æµé¡¹ç®åå¸</span> |
| | | <el-button link @click="refreshInspectionChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="inspectionChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- é¢ç¨è®°å½è¶å¿ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>é¢ç¨è®°å½è¶å¿</span> |
| | | <el-button link @click="refreshUsageChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="usageChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- è¯¦ç»æ°æ®è¡¨æ ¼ --> |
| | | <el-card class="table-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>è¯¦ç»æ°æ®</span> |
| | | <div> |
| | | <el-button type="primary" size="small" @click="refreshTable">å·æ°</el-button> |
| | | <el-button type="success" size="small" @click="exportTable">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <el-table |
| | | :data="tableData" |
| | | style="width: 100%" |
| | | v-loading="tableLoading" |
| | | stripe |
| | | border |
| | | > |
| | | <el-table-column prop="id" label="ç¼å·" width="80" /> |
| | | <el-table-column prop="name" label="åç§°" /> |
| | | <el-table-column prop="type" label="ç±»å" width="120" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="progress" label="è¿åº¦" width="120"> |
| | | <template #default="scope"> |
| | | <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createTime" label="å建æ¶é´" width="180" /> |
| | | <el-table-column prop="updateTime" label="æ´æ°æ¶é´" width="180" /> |
| | | <el-table-column label="æä½" width="150" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button link size="small" @click="viewDetail(scope.row)">æ¥ç</el-button> |
| | | <el-button link size="small" @click="editItem(scope.row)">ç¼è¾</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <div class="pagination-container"> |
| | | <el-pagination |
| | | v-model:current-page="pagination.currentPage" |
| | | v-model:page-size="pagination.pageSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import * as echarts from 'echarts' |
| | | import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const filterForm = reactive({ |
| | | dateRange: [], |
| | | reportType: 'sample' |
| | | }) |
| | | |
| | | const statistics = reactive({ |
| | | totalSamples: 1250, |
| | | activeEquipment: 45, |
| | | completedInspections: 890, |
| | | totalUsage: 2340 |
| | | }) |
| | | |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 20, |
| | | total: 0 |
| | | }) |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const sampleChartRef = ref(null) |
| | | const equipmentChartRef = ref(null) |
| | | const inspectionChartRef = ref(null) |
| | | const usageChartRef = ref(null) |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | let sampleChart = null |
| | | let equipmentChart = null |
| | | let inspectionChart = null |
| | | let usageChart = null |
| | | |
| | | // åå§åæ°æ® |
| | | const initData = () => { |
| | | // 模æè¡¨æ ¼æ°æ® |
| | | tableData.value = [ |
| | | { |
| | | id: 'SP001', |
| | | name: 'æ ·åA-001', |
| | | type: 'é屿æ', |
| | | status: 'æ£æµä¸', |
| | | progress: 75, |
| | | createTime: '2024-01-15 09:30:00', |
| | | updateTime: '2024-01-20 14:20:00' |
| | | }, |
| | | { |
| | | id: 'SP002', |
| | | name: 'æ ·åB-002', |
| | | type: '塿å¶å', |
| | | status: '已宿', |
| | | progress: 100, |
| | | createTime: '2024-01-10 10:15:00', |
| | | updateTime: '2024-01-18 16:45:00' |
| | | }, |
| | | { |
| | | id: 'SP003', |
| | | name: 'æ ·åC-003', |
| | | type: 'çµåå
ä»¶', |
| | | status: 'å¾
æ£æµ', |
| | | progress: 0, |
| | | createTime: '2024-01-22 08:45:00', |
| | | updateTime: '2024-01-22 08:45:00' |
| | | }, |
| | | { |
| | | id: 'EQ001', |
| | | name: 'æ£æµè®¾å¤A', |
| | | type: 'å
谱仪', |
| | | status: '使ç¨ä¸', |
| | | progress: 60, |
| | | createTime: '2024-01-05 14:20:00', |
| | | updateTime: '2024-01-20 11:30:00' |
| | | }, |
| | | { |
| | | id: 'EQ002', |
| | | name: 'æ£æµè®¾å¤B', |
| | | type: 'æ¾å¾®é', |
| | | status: '空é²', |
| | | progress: 0, |
| | | createTime: '2024-01-08 16:10:00', |
| | | updateTime: '2024-01-19 09:15:00' |
| | | } |
| | | ] |
| | | |
| | | pagination.total = tableData.value.length |
| | | } |
| | | |
| | | // åå§åæ ·åè¿åº¦å¾è¡¨ |
| | | const initSampleChart = () => { |
| | | if (sampleChartRef.value) { |
| | | sampleChart = echarts.init(sampleChartRef.value) |
| | | const option = { |
| | | title: { |
| | | show: false |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b}: {c} ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'æ ·åç¶æ', |
| | | type: 'pie', |
| | | radius: ['40%', '70%'], |
| | | avoidLabelOverlap: false, |
| | | label: { |
| | | show: false, |
| | | position: 'center' |
| | | }, |
| | | emphasis: { |
| | | label: { |
| | | show: true, |
| | | fontSize: '18', |
| | | fontWeight: 'bold' |
| | | } |
| | | }, |
| | | labelLine: { |
| | | show: false |
| | | }, |
| | | data: [ |
| | | { value: 450, name: '已宿' }, |
| | | { value: 320, name: 'æ£æµä¸' }, |
| | | { value: 280, name: 'å¾
æ£æµ' }, |
| | | { value: 200, name: 'å·²æå' } |
| | | ] |
| | | } |
| | | ] |
| | | } |
| | | sampleChart.setOption(option) |
| | | } |
| | | } |
| | | |
| | | // åå§å设å¤ä½¿ç¨å¾è¡¨ |
| | | const initEquipmentChart = () => { |
| | | if (equipmentChartRef.value) { |
| | | equipmentChart = echarts.init(equipmentChartRef.value) |
| | | const option = { |
| | | title: { |
| | | show: false |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: ['å
谱仪', 'æ¾å¾®é', '硬度计', 'æåæº', 'å²å»æº', 'éç¸ä»ª'] |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: '使ç¨ç(%)' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '使ç¨ç', |
| | | type: 'bar', |
| | | data: [85, 60, 75, 90, 45, 70], |
| | | itemStyle: { |
| | | color: function(params) { |
| | | const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0'] |
| | | return colors[params.dataIndex] |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | equipmentChart.setOption(option) |
| | | } |
| | | } |
| | | |
| | | // åå§åæ£æµé¡¹ç®å¾è¡¨ |
| | | const initInspectionChart = () => { |
| | | if (inspectionChartRef.value) { |
| | | inspectionChart = echarts.init(inspectionChartRef.value) |
| | | const option = { |
| | | title: { |
| | | show: false |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'æ£æµé¡¹ç®', |
| | | type: 'pie', |
| | | radius: '50%', |
| | | data: [ |
| | | { value: 335, name: 'ç©çæ§è½' }, |
| | | { value: 310, name: 'åå¦åæ' }, |
| | | { value: 234, name: '尺寸æµé' }, |
| | | { value: 135, name: 'å¤è§æ£æ¥' }, |
| | | { value: 148, name: 'å
¶ä»æ£æµ' } |
| | | ], |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: 'rgba(0, 0, 0, 0.5)' |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | inspectionChart.setOption(option) |
| | | } |
| | | } |
| | | |
| | | // åå§åé¢ç¨è®°å½å¾è¡¨ |
| | | const initUsageChart = () => { |
| | | if (usageChartRef.value) { |
| | | usageChart = echarts.init(usageChartRef.value) |
| | | const option = { |
| | | title: { |
| | | show: false |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | legend: { |
| | | data: ['é¢ç¨æ¬¡æ°', 'å½è¿æ¬¡æ°'] |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | boundaryGap: false, |
| | | data: ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'] |
| | | }, |
| | | yAxis: { |
| | | type: 'value' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'é¢ç¨æ¬¡æ°', |
| | | type: 'line', |
| | | stack: 'Total', |
| | | data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330] |
| | | }, |
| | | { |
| | | name: 'å½è¿æ¬¡æ°', |
| | | type: 'line', |
| | | stack: 'Total', |
| | | data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320] |
| | | } |
| | | ] |
| | | } |
| | | usageChart.setOption(option) |
| | | } |
| | | } |
| | | |
| | | // äºä»¶å¤ç彿° |
| | | const handleFilterChange = () => { |
| | | ElMessage.success('çéæ¡ä»¶å·²æ´æ°') |
| | | // è¿éå¯ä»¥æ ¹æ®ç鿡件鿰å è½½æ°æ® |
| | | } |
| | | |
| | | const resetFilter = () => { |
| | | filterForm.dateRange = [] |
| | | filterForm.reportType = 'sample' |
| | | ElMessage.info('ç鿡件已éç½®') |
| | | } |
| | | |
| | | const exportReport = () => { |
| | | ElMessage.success('æ¥è¡¨å¯¼åºåè½å¼åä¸...') |
| | | } |
| | | |
| | | const refreshSampleChart = () => { |
| | | initSampleChart() |
| | | ElMessage.success('æ ·åè¿åº¦å¾è¡¨å·²å·æ°') |
| | | } |
| | | |
| | | const refreshEquipmentChart = () => { |
| | | initEquipmentChart() |
| | | ElMessage.success('设å¤ä½¿ç¨å¾è¡¨å·²å·æ°') |
| | | } |
| | | |
| | | const refreshInspectionChart = () => { |
| | | initInspectionChart() |
| | | ElMessage.success('æ£æµé¡¹ç®å¾è¡¨å·²å·æ°') |
| | | } |
| | | |
| | | const refreshUsageChart = () => { |
| | | initUsageChart() |
| | | ElMessage.success('é¢ç¨è®°å½å¾è¡¨å·²å·æ°') |
| | | } |
| | | |
| | | const refreshTable = () => { |
| | | tableLoading.value = true |
| | | setTimeout(() => { |
| | | tableLoading.value = false |
| | | ElMessage.success('è¡¨æ ¼æ°æ®å·²å·æ°') |
| | | }, 1000) |
| | | } |
| | | |
| | | const exportTable = () => { |
| | | ElMessage.success('è¡¨æ ¼å¯¼åºåè½å¼åä¸...') |
| | | } |
| | | |
| | | const handleSizeChange = (val) => { |
| | | pagination.pageSize = val |
| | | // éæ°å è½½æ°æ® |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val |
| | | // éæ°å è½½æ°æ® |
| | | } |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | '已宿': 'success', |
| | | 'æ£æµä¸': 'warning', |
| | | 'å¾
æ£æµ': 'info', |
| | | 'å·²æå': 'danger', |
| | | '使ç¨ä¸': 'primary', |
| | | '空é²': 'info' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const getProgressStatus = (progress) => { |
| | | if (progress === 100) return 'success' |
| | | if (progress >= 80) return 'warning' |
| | | if (progress >= 50) return '' |
| | | return 'exception' |
| | | } |
| | | |
| | | const viewDetail = (row) => { |
| | | ElMessage.info(`æ¥ç详æ
: ${row.name}`) |
| | | } |
| | | |
| | | const editItem = (row) => { |
| | | ElMessage.info(`ç¼è¾é¡¹ç®: ${row.name}`) |
| | | } |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | initData() |
| | | nextTick(() => { |
| | | initSampleChart() |
| | | initEquipmentChart() |
| | | initInspectionChart() |
| | | initUsageChart() |
| | | }) |
| | | |
| | | // çå¬çªå£å¤§å°ååï¼éæ°è°æ´å¾è¡¨å¤§å° |
| | | window.addEventListener('resize', () => { |
| | | sampleChart?.resize() |
| | | equipmentChart?.resize() |
| | | inspectionChart?.resize() |
| | | usageChart?.resize() |
| | | }) |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .report-management { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | color: #303133; |
| | | margin-bottom: 8px; |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .page-header p { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | margin: 0; |
| | | } |
| | | |
| | | .filter-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .statistics-cards { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .stat-card { |
| | | height: 120px; |
| | | } |
| | | |
| | | .stat-content { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 100%; |
| | | } |
| | | |
| | | .stat-icon { |
| | | width: 60px; |
| | | height: 60px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 20px; |
| | | font-size: 24px; |
| | | color: white; |
| | | } |
| | | |
| | | .stat-card:nth-child(1) .stat-icon { |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | } |
| | | |
| | | .stat-card:nth-child(2) .stat-icon { |
| | | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| | | } |
| | | |
| | | .stat-card:nth-child(3) .stat-icon { |
| | | background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
| | | } |
| | | |
| | | .stat-card:nth-child(4) .stat-icon { |
| | | background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); |
| | | } |
| | | |
| | | .stat-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .stat-number { |
| | | font-size: 28px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .charts-container { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .chart-container { |
| | | height: 300px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .table-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .pagination-container { |
| | | margin-top: 20px; |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 15px 20px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 20px; |
| | | } |
| | | |
| | | :deep(.el-table) { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | :deep(.el-progress) { |
| | | margin: 0; |
| | | } |
| | | |
| | | :deep(.el-tag) { |
| | | margin: 0; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.name" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.region" placeholder="è¯·éæ©åºå" clearable> |
| | | <el-option label="åä¸åº" value="åä¸åº"></el-option> |
| | | <el-option label="åååº" value="åååº"></el-option> |
| | | <el-option label="åååº" value="åååº"></el-option> |
| | | <el-option label="西ååº" value="西ååº"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.level" placeholder="è¯·éæ©å®¢æ·ç级" clearable> |
| | | <el-option label="VIP客æ·" value="VIP客æ·"></el-option> |
| | | <el-option label="éè¦å®¢æ·" value="éè¦å®¢æ·"></el-option> |
| | | <el-option label="æ®é客æ·" value="æ®é客æ·"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | | æ°å¢å®¢æ· |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 客æ·å表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="name" label="客æ·åç§°" width="150" /> |
| | | <el-table-column prop="contactPerson" label="è系人" width="100" /> |
| | | <el-table-column prop="phone" label="èç³»çµè¯" width="140" /> |
| | | <el-table-column prop="email" label="é®ç®±" /> |
| | | <el-table-column prop="region" label="åºå" width="100" /> |
| | | <el-table-column prop="level" label="客æ·ç级" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getLevelType(scope.row.level)"> |
| | | {{ scope.row.level }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="salesperson" label="è´è´£ä¸å¡å" width="120" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="80"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="primary" @click="handleAllocation(scope.row)">åé
</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°" prop="name"> |
| | | <el-input v-model="form.name" placeholder="请è¾å
¥å®¢æ·åç§°"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è系人" prop="contactPerson"> |
| | | <el-input v-model="form.contactPerson" placeholder="请è¾å
¥è系人"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯" prop="phone"> |
| | | <el-input v-model="form.phone" placeholder="请è¾å
¥èç³»çµè¯"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é®ç®±" prop="email"> |
| | | <el-input v-model="form.email" placeholder="请è¾å
¥é®ç®±"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºå" prop="region"> |
| | | <el-select v-model="form.region" placeholder="è¯·éæ©åºå" style="width: 100%"> |
| | | <el-option label="åä¸åº" value="åä¸åº"></el-option> |
| | | <el-option label="åååº" value="åååº"></el-option> |
| | | <el-option label="åååº" value="åååº"></el-option> |
| | | <el-option label="西ååº" value="西ååº"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·ç级" prop="level"> |
| | | <el-select v-model="form.level" placeholder="è¯·éæ©å®¢æ·ç级" style="width: 100%"> |
| | | <el-option label="VIP客æ·" value="VIP客æ·"></el-option> |
| | | <el-option label="éè¦å®¢æ·" value="éè¦å®¢æ·"></el-option> |
| | | <el-option label="æ®é客æ·" value="æ®é客æ·"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è´è´£ä¸å¡å" prop="salesperson"> |
| | | <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%"> |
| | | <el-option label="å¼ ä¸" value="å¼ ä¸"></el-option> |
| | | <el-option label="æå" value="æå"></el-option> |
| | | <el-option label="çäº" value="çäº"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="æ´»è·" value="æ´»è·"></el-option> |
| | | <el-option label="æ½å¨" value="æ½å¨"></el-option> |
| | | <el-option label="æµå¤±" value="æµå¤±"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 客æ·åé
å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="allocationDialogVisible" title="客æ·åé
" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <span>{{ currentCustomer.name }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="å½åä¸å¡å"> |
| | | <span>{{ currentCustomer.salesperson }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="éæ°åé
"> |
| | | <el-select v-model="newSalesperson" placeholder="è¯·éæ©æ°ä¸å¡å" style="width: 100%"> |
| | | <el-option label="å¼ ä¸" value="å¼ ä¸"></el-option> |
| | | <el-option label="æå" value="æå"></el-option> |
| | | <el-option label="çäº" value="çäº"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="åé
åå "> |
| | | <el-input v-model="allocationReason" type="textarea" rows="3" placeholder="请è¾å
¥åé
åå "></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="allocationDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="saveAllocation">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | name: '', |
| | | region: '', |
| | | level: '' |
| | | }) |
| | | |
| | | const customerList = ref([ |
| | | { |
| | | id: 1, |
| | | name: '䏿µ·ç§ææéå
¬å¸', |
| | | contactPerson: 'å¼ ç»ç', |
| | | phone: '021-12345678', |
| | | email: 'zhang@shanghai-tech.com', |
| | | region: 'åä¸åº', |
| | | level: 'VIP客æ·', |
| | | salesperson: 'å¼ ä¸', |
| | | status: 'æ´»è·' |
| | | }, |
| | | { |
| | | id: 2, |
| | | name: 'æ·±å³çµåæéå
¬å¸', |
| | | contactPerson: 'ææ»', |
| | | phone: '0755-87654321', |
| | | email: 'li@shenzhen-elec.com', |
| | | region: 'åååº', |
| | | level: 'éè¦å®¢æ·', |
| | | salesperson: 'æå', |
| | | status: 'æ´»è·' |
| | | }, |
| | | { |
| | | id: 3, |
| | | name: 'å京贸æå
¬å¸', |
| | | contactPerson: 'çç»ç', |
| | | phone: '010-11223344', |
| | | email: 'wang@beijing-trade.com', |
| | | region: 'åååº', |
| | | level: 'æ®é客æ·', |
| | | salesperson: 'çäº', |
| | | status: 'æ½å¨' |
| | | } |
| | | ]) |
| | | |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢å®¢æ·') |
| | | const form = reactive({ |
| | | name: '', |
| | | contactPerson: '', |
| | | phone: '', |
| | | email: '', |
| | | region: '', |
| | | level: '', |
| | | salesperson: '', |
| | | status: 'æ´»è·' |
| | | }) |
| | | |
| | | const rules = { |
| | | name: [{ required: true, message: '请è¾å
¥å®¢æ·åç§°', trigger: 'blur' }], |
| | | contactPerson: [{ required: true, message: '请è¾å
¥è系人', trigger: 'blur' }], |
| | | phone: [{ required: true, message: '请è¾å
¥èç³»çµè¯', trigger: 'blur' }], |
| | | email: [{ required: true, message: '请è¾å
¥é®ç®±', trigger: 'blur' }], |
| | | region: [{ required: true, message: 'è¯·éæ©åºå', trigger: 'change' }], |
| | | level: [{ required: true, message: 'è¯·éæ©å®¢æ·ç级', trigger: 'change' }], |
| | | salesperson: [{ required: true, message: 'è¯·éæ©ä¸å¡å', trigger: 'change' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const allocationDialogVisible = ref(false) |
| | | const currentCustomer = ref({}) |
| | | const newSalesperson = ref('') |
| | | const allocationReason = ref('') |
| | | const formRef = ref() |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredList = computed(() => { |
| | | let list = customerList.value |
| | | if (searchForm.name) { |
| | | list = list.filter(item => item.name.includes(searchForm.name)) |
| | | } |
| | | if (searchForm.region) { |
| | | list = list.filter(item => item.region === searchForm.region) |
| | | } |
| | | if (searchForm.level) { |
| | | list = list.filter(item => item.level === searchForm.level) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | | const getLevelType = (level) => { |
| | | const levelMap = { |
| | | 'VIP客æ·': 'danger', |
| | | 'éè¦å®¢æ·': 'warning', |
| | | 'æ®é客æ·': 'info' |
| | | } |
| | | return levelMap[level] || 'info' |
| | | } |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'æ´»è·': 'success', |
| | | 'æ½å¨': 'warning', |
| | | 'æµå¤±': 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | // æç´¢é»è¾å·²å¨computedä¸å¤ç |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.name = '' |
| | | searchForm.region = '' |
| | | searchForm.level = '' |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å¢å®¢æ·' |
| | | isEdit.value = false |
| | | form.name = '' |
| | | form.contactPerson = '' |
| | | form.phone = '' |
| | | form.email = '' |
| | | form.region = '' |
| | | form.level = '' |
| | | form.salesperson = '' |
| | | form.status = 'æ´»è·' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ç¼è¾å®¢æ·' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥å®¢æ·åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = customerList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | customerList.value.splice(index, 1) |
| | | pagination.total-- |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleAllocation = (row) => { |
| | | currentCustomer.value = row |
| | | newSalesperson.value = '' |
| | | allocationReason.value = '' |
| | | allocationDialogVisible.value = true |
| | | } |
| | | |
| | | const saveAllocation = () => { |
| | | if (!newSalesperson.value) { |
| | | ElMessage.warning('è¯·éæ©æ°ä¸å¡å') |
| | | return |
| | | } |
| | | |
| | | const index = customerList.value.findIndex(item => item.id === currentCustomer.value.id) |
| | | if (index > -1) { |
| | | customerList.value[index].salesperson = newSalesperson.value |
| | | ElMessage.success('客æ·åé
æå') |
| | | allocationDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = customerList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | customerList.value[index] = { ...form, id: editId.value } |
| | | ElMessage.success('ç¼è¾æå') |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | const newId = Math.max(...customerList.value.map(item => item.id)) + 1 |
| | | customerList.value.push({ |
| | | ...form, |
| | | id: newId |
| | | }) |
| | | pagination.total++ |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val.page |
| | | pagination.pageSize = val.limit |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.orderNo" |
| | | placeholder="请è¾å
¥è®¢åå·" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.customer" placeholder="è¯·éæ©å®¢æ·" clearable> |
| | | <el-option label="䏿µ·ç§ææéå
¬å¸" value="䏿µ·ç§ææéå
¬å¸"></el-option> |
| | | <el-option label="æ·±å³çµåæéå
¬å¸" value="æ·±å³çµåæéå
¬å¸"></el-option> |
| | | <el-option label="å京贸æå
¬å¸" value="å京贸æå
¬å¸"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©è®¢åç¶æ" clearable> |
| | | <el-option label="å¾
å®¡æ ¸" value="å¾
å®¡æ ¸"></el-option> |
| | | <el-option label="å·²å®¡æ ¸" value="å·²å®¡æ ¸"></el-option> |
| | | <el-option label="å·²åè´§" value="å·²åè´§"></el-option> |
| | | <el-option label="已宿" value="已宿"></el-option> |
| | | <el-option label="已忶" value="已忶"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | | æ°å¢è®¢å |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 订åå表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="orderNo" label="订åå·" width="150" /> |
| | | <el-table-column prop="customer" label="客æ·åç§°" /> |
| | | <el-table-column prop="salesperson" label="ä¸å¡å" width="100" /> |
| | | <el-table-column prop="orderDate" label="ä¸åæ¥æ" width="120" /> |
| | | <el-table-column prop="amount" label="订åéé¢" width="120"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.amount.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="paymentMethod" label="仿¬¾æ¹å¼" width="120" /> |
| | | <el-table-column prop="status" label="订åç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="250" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleView(scope.row)">æ¥ç</el-button> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)" v-if="scope.row.status === 'å¾
å®¡æ ¸'">ç¼è¾</el-button> |
| | | <el-button link type="primary" @click="handleReview(scope.row)" v-if="scope.row.status === 'å¾
å®¡æ ¸'">å®¡æ ¸</el-button> |
| | | <el-button link type="primary" @click="handleTransfer(scope.row)" v-if="scope.row.status === 'å·²å®¡æ ¸'">转å</el-button> |
| | | <el-button link type="danger" @click="handleCancel(scope.row)" v-if="scope.row.status === 'å¾
å®¡æ ¸'">åæ¶</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°" prop="customer"> |
| | | <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%"> |
| | | <el-option label="䏿µ·ç§ææéå
¬å¸" value="䏿µ·ç§ææéå
¬å¸"></el-option> |
| | | <el-option label="æ·±å³çµåæéå
¬å¸" value="æ·±å³çµåæéå
¬å¸"></el-option> |
| | | <el-option label="å京贸æå
¬å¸" value="å京贸æå
¬å¸"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸å¡å" prop="salesperson"> |
| | | <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%"> |
| | | <el-option label="å¼ ä¸" value="å¼ ä¸"></el-option> |
| | | <el-option label="æå" value="æå"></el-option> |
| | | <el-option label="çäº" value="çäº"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è®¢åæ¥æ" prop="orderDate"> |
| | | <el-date-picker |
| | | v-model="form.orderDate" |
| | | type="date" |
| | | placeholder="éæ©è®¢åæ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="订åéé¢" prop="amount"> |
| | | <el-input-number v-model="form.amount" :precision="2" :min="0" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="form.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%"> |
| | | <el-option label="å
¨æ¬¾å°ä»" value="å
¨æ¬¾å°ä»"></el-option> |
| | | <el-option label="åæä»æ¬¾" value="åæä»æ¬¾"></el-option> |
| | | <el-option label="æç»" value="æç»"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="订åç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="å¾
å®¡æ ¸" value="å¾
å®¡æ ¸"></el-option> |
| | | <el-option label="å·²å®¡æ ¸" value="å·²å®¡æ ¸"></el-option> |
| | | <el-option label="å·²åè´§" value="å·²åè´§"></el-option> |
| | | <el-option label="已宿" value="已宿"></el-option> |
| | | <el-option label="已忶" value="已忶"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input type="textarea" v-model="form.remark" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" rows="3"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 订åå®¡æ ¸å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="reviewDialogVisible" title="订åå®¡æ ¸" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="订åå·"> |
| | | <span>{{ currentOrder.orderNo }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <span>{{ currentOrder.customer }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="订åéé¢"> |
| | | <span>Â¥{{ currentOrder.amount.toFixed(2) }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="å®¡æ ¸ç»æ" prop="reviewResult"> |
| | | <el-radio-group v-model="reviewResult"> |
| | | <el-radio label="éè¿">éè¿</el-radio> |
| | | <el-radio label="æç»">æç»</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="å®¡æ ¸æè§" prop="reviewComment"> |
| | | <el-input type="textarea" v-model="reviewComment" rows="3" placeholder="请è¾å
¥å®¡æ ¸æè§"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="reviewDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="saveReview">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 订å转åå¯¹è¯æ¡ --> |
| | | <el-dialog v-model="transferDialogVisible" title="订å转å" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="订åå·"> |
| | | <span>{{ currentOrder.orderNo }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="å½åä¸å¡å"> |
| | | <span>{{ currentOrder.salesperson }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="转åç»" prop="newSalesperson"> |
| | | <el-select v-model="newSalesperson" placeholder="è¯·éæ©æ°ä¸å¡å" style="width: 100%"> |
| | | <el-option label="å¼ ä¸" value="å¼ ä¸"></el-option> |
| | | <el-option label="æå" value="æå"></el-option> |
| | | <el-option label="çäº" value="çäº"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="转ååå " prop="transferReason"> |
| | | <el-input type="textarea" v-model="transferReason" rows="3" placeholder="请è¾å
¥è½¬ååå "></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="transferDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="saveTransfer">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | orderNo: '', |
| | | customer: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const orderList = ref([ |
| | | { |
| | | id: 1, |
| | | orderNo: 'ORD202312001', |
| | | customer: '䏿µ·ç§ææéå
¬å¸', |
| | | salesperson: 'å¼ ä¸', |
| | | orderDate: '2023-12-01', |
| | | amount: 50000.00, |
| | | paymentMethod: 'å
¨æ¬¾å°ä»', |
| | | status: 'å¾
å®¡æ ¸', |
| | | remark: 'éè¦å®¢æ·è®¢å' |
| | | }, |
| | | { |
| | | id: 2, |
| | | orderNo: 'ORD202312002', |
| | | customer: 'æ·±å³çµåæéå
¬å¸', |
| | | salesperson: 'æå', |
| | | orderDate: '2023-12-02', |
| | | amount: 35000.00, |
| | | paymentMethod: 'åæä»æ¬¾', |
| | | status: 'å·²å®¡æ ¸', |
| | | remark: '常è§è®¢å' |
| | | }, |
| | | { |
| | | id: 3, |
| | | orderNo: 'ORD202312003', |
| | | customer: 'å京贸æå
¬å¸', |
| | | salesperson: 'çäº', |
| | | orderDate: '2023-12-03', |
| | | amount: 28000.00, |
| | | paymentMethod: 'æç»', |
| | | status: 'å·²åè´§', |
| | | remark: 'æ°å®¢æ·è®¢å' |
| | | } |
| | | ]) |
| | | |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢è®¢å') |
| | | const form = reactive({ |
| | | customer: '', |
| | | salesperson: '', |
| | | orderDate: '', |
| | | amount: 0, |
| | | paymentMethod: '', |
| | | status: 'å¾
å®¡æ ¸', |
| | | remark: '' |
| | | }) |
| | | |
| | | const rules = { |
| | | customer: [{ required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' }], |
| | | salesperson: [{ required: true, message: 'è¯·éæ©ä¸å¡å', trigger: 'change' }], |
| | | orderDate: [{ required: true, message: 'è¯·éæ©è®¢åæ¥æ', trigger: 'change' }], |
| | | amount: [{ required: true, message: '请è¾å
¥è®¢åéé¢', trigger: 'blur' }], |
| | | paymentMethod: [{ required: true, message: 'è¯·éæ©ä»æ¬¾æ¹å¼', trigger: 'change' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const reviewDialogVisible = ref(false) |
| | | const transferDialogVisible = ref(false) |
| | | const currentOrder = ref({}) |
| | | const reviewResult = ref('') |
| | | const reviewComment = ref('') |
| | | const newSalesperson = ref('') |
| | | const transferReason = ref('') |
| | | const formRef = ref() |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredList = computed(() => { |
| | | let list = orderList.value |
| | | if (searchForm.orderNo) { |
| | | list = list.filter(item => item.orderNo.includes(searchForm.orderNo)) |
| | | } |
| | | if (searchForm.customer) { |
| | | list = list.filter(item => item.customer === searchForm.customer) |
| | | } |
| | | if (searchForm.status) { |
| | | list = list.filter(item => item.status === searchForm.status) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'å¾
å®¡æ ¸': 'warning', |
| | | 'å·²å®¡æ ¸': 'primary', |
| | | 'å·²åè´§': 'success', |
| | | '已宿': 'success', |
| | | '已忶': 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | // æç´¢é»è¾å·²å¨computedä¸å¤ç |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.orderNo = '' |
| | | searchForm.customer = '' |
| | | searchForm.status = '' |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å¢è®¢å' |
| | | isEdit.value = false |
| | | form.customer = '' |
| | | form.salesperson = '' |
| | | form.orderDate = '' |
| | | form.amount = 0 |
| | | form.paymentMethod = '' |
| | | form.status = 'å¾
å®¡æ ¸' |
| | | form.remark = '' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleView = (row) => { |
| | | // æ¥ç订å详æ
|
| | | ElMessage.info('æ¥ç订å详æ
åè½å¾
å®ç°') |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ç¼è¾è®¢å' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleReview = (row) => { |
| | | currentOrder.value = row |
| | | reviewResult.value = '' |
| | | reviewComment.value = '' |
| | | reviewDialogVisible.value = true |
| | | } |
| | | |
| | | const handleTransfer = (row) => { |
| | | currentOrder.value = row |
| | | newSalesperson.value = '' |
| | | transferReason.value = '' |
| | | transferDialogVisible.value = true |
| | | } |
| | | |
| | | const handleCancel = (row) => { |
| | | ElMessageBox.confirm('ç¡®è®¤åæ¶è¯¥è®¢ååï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = orderList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | orderList.value[index].status = '已忶' |
| | | ElMessage.success('订å已忶') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥è®¢ååï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = orderList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | orderList.value.splice(index, 1) |
| | | pagination.total-- |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const saveReview = () => { |
| | | if (!reviewResult.value) { |
| | | ElMessage.warning('è¯·éæ©å®¡æ ¸ç»æ') |
| | | return |
| | | } |
| | | |
| | | const index = orderList.value.findIndex(item => item.id === currentOrder.value.id) |
| | | if (index > -1) { |
| | | if (reviewResult.value === 'éè¿') { |
| | | orderList.value[index].status = 'å·²å®¡æ ¸' |
| | | ElMessage.success('订åå®¡æ ¸éè¿') |
| | | } else { |
| | | orderList.value[index].status = '已忶' |
| | | ElMessage.success('订åå®¡æ ¸æç»') |
| | | } |
| | | reviewDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const saveTransfer = () => { |
| | | if (!newSalesperson.value) { |
| | | ElMessage.warning('è¯·éæ©æ°ä¸å¡å') |
| | | return |
| | | } |
| | | |
| | | const index = orderList.value.findIndex(item => item.id === currentOrder.value.id) |
| | | if (index > -1) { |
| | | orderList.value[index].salesperson = newSalesperson.value |
| | | ElMessage.success('订å转åæå') |
| | | transferDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = orderList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | orderList.value[index] = { ...form, id: editId.value } |
| | | ElMessage.success('ç¼è¾æå') |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | const newId = Math.max(...orderList.value.map(item => item.id)) + 1 |
| | | const orderNo = `ORD${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}${String(newId).padStart(3, '0')}` |
| | | orderList.value.push({ |
| | | ...form, |
| | | id: newId, |
| | | orderNo: orderNo |
| | | }) |
| | | pagination.total++ |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val.page |
| | | pagination.pageSize = val.limit |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.orderNo" |
| | | placeholder="请è¾å
¥è®¢åå·" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.paymentStatus" placeholder="è¯·éæ©ä»æ¬¾ç¶æ" clearable> |
| | | <el-option label="æªä»æ¬¾" value="æªä»æ¬¾"></el-option> |
| | | <el-option label="已仿¬¾" value="已仿¬¾"></el-option> |
| | | <el-option label="é¨å仿¬¾" value="é¨å仿¬¾"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.shippingStatus" placeholder="è¯·éæ©åè´§ç¶æ" clearable> |
| | | <el-option label="å¾
åè´§" value="å¾
åè´§"></el-option> |
| | | <el-option label="å·²åè´§" value="å·²åè´§"></el-option> |
| | | <el-option label="å·²ç¾æ¶" value="å·²ç¾æ¶"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | | æ°å¢è®°å½ |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æ¯ä»ä¸åè´§å表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="orderNo" label="订åå·" /> |
| | | <el-table-column prop="customer" label="客æ·åç§°" /> |
| | | <el-table-column prop="orderAmount" label="订åéé¢" width="120"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.orderAmount.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="paymentMethod" label="仿¬¾æ¹å¼" width="120" /> |
| | | <el-table-column prop="paymentStatus" label="仿¬¾ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getPaymentStatusType(scope.row.paymentStatus)"> |
| | | {{ scope.row.paymentStatus }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="shippingStatus" label="åè´§ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getShippingStatusType(scope.row.shippingStatus)"> |
| | | {{ scope.row.shippingStatus }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="shippingDate" label="åè´§æ¥æ" width="120" /> |
| | | <el-table-column label="æä½" width="250" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleView(scope.row)">æ¥ç</el-button> |
| | | <el-button link type="primary" @click="handlePayment(scope.row)" v-if="scope.row.paymentStatus !== '已仿¬¾'">仿¬¾</el-button> |
| | | <el-button link type="primary" @click="handleShipping(scope.row)" v-if="scope.row.paymentStatus === '已仿¬¾' && scope.row.shippingStatus === 'å¾
åè´§'">åè´§</el-button> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="订åå·" prop="orderNo"> |
| | | <el-input v-model="form.orderNo" placeholder="请è¾å
¥è®¢åå·"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°" prop="customer"> |
| | | <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%"> |
| | | <el-option label="䏿µ·ç§ææéå
¬å¸" value="䏿µ·ç§ææéå
¬å¸"></el-option> |
| | | <el-option label="æ·±å³çµåæéå
¬å¸" value="æ·±å³çµåæéå
¬å¸"></el-option> |
| | | <el-option label="å京贸æå
¬å¸" value="å京贸æå
¬å¸"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="订åéé¢" prop="orderAmount"> |
| | | <el-input-number v-model="form.orderAmount" :precision="2" :min="0" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="form.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%"> |
| | | <el-option label="å
¨æ¬¾å°ä»" value="å
¨æ¬¾å°ä»"></el-option> |
| | | <el-option label="åæä»æ¬¾" value="åæä»æ¬¾"></el-option> |
| | | <el-option label="æç»" value="æç»"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾ç¶æ" prop="paymentStatus"> |
| | | <el-select v-model="form.paymentStatus" placeholder="è¯·éæ©ä»æ¬¾ç¶æ" style="width: 100%"> |
| | | <el-option label="æªä»æ¬¾" value="æªä»æ¬¾"></el-option> |
| | | <el-option label="已仿¬¾" value="已仿¬¾"></el-option> |
| | | <el-option label="é¨å仿¬¾" value="é¨å仿¬¾"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åè´§ç¶æ" prop="shippingStatus"> |
| | | <el-select v-model="form.shippingStatus" placeholder="è¯·éæ©åè´§ç¶æ" style="width: 100%"> |
| | | <el-option label="å¾
åè´§" value="å¾
åè´§"></el-option> |
| | | <el-option label="å·²åè´§" value="å·²åè´§"></el-option> |
| | | <el-option label="å·²ç¾æ¶" value="å·²ç¾æ¶"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åè´§æ¥æ" prop="shippingDate"> |
| | | <el-date-picker |
| | | v-model="form.shippingDate" |
| | | type="date" |
| | | placeholder="éæ©åè´§æ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç©æµåå·" prop="trackingNo"> |
| | | <el-input v-model="form.trackingNo" placeholder="请è¾å
¥ç©æµåå·"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input type="textarea" v-model="form.remark" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" rows="3"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 仿¬¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="paymentDialogVisible" title="订å仿¬¾" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="订åå·"> |
| | | <span>{{ currentRecord.orderNo }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <span>{{ currentRecord.customer }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="订åéé¢"> |
| | | <span>Â¥{{ currentRecord.orderAmount.toFixed(2) }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾éé¢" prop="paymentAmount"> |
| | | <el-input-number v-model="paymentAmount" :precision="2" :min="0" :max="currentRecord.orderAmount" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%"> |
| | | <el-option label="ç°é" value="ç°é"></el-option> |
| | | <el-option label="é¶è¡è½¬è´¦" value="é¶è¡è½¬è´¦"></el-option> |
| | | <el-option label="æ¯ä»å®" value="æ¯ä»å®"></el-option> |
| | | <el-option label="微信æ¯ä»" value="微信æ¯ä»"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾å¤æ³¨" prop="paymentRemark"> |
| | | <el-input type="textarea" v-model="paymentRemark" rows="3" placeholder="请è¾å
¥ä»æ¬¾å¤æ³¨"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="paymentDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="savePayment">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- åè´§å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="shippingDialogVisible" title="订ååè´§" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="订åå·"> |
| | | <span>{{ currentRecord.orderNo }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <span>{{ currentRecord.customer }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="åè´§æ¥æ" prop="shippingDate"> |
| | | <el-date-picker |
| | | v-model="shippingDate" |
| | | type="date" |
| | | placeholder="éæ©åè´§æ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ç©æµå
¬å¸" prop="logisticsCompany"> |
| | | <el-select v-model="logisticsCompany" placeholder="è¯·éæ©ç©æµå
¬å¸" style="width: 100%"> |
| | | <el-option label="顺丰éè¿" value="顺丰éè¿"></el-option> |
| | | <el-option label="åééé" value="åééé"></el-option> |
| | | <el-option label="ä¸éå¿«é" value="ä¸éå¿«é"></el-option> |
| | | <el-option label="ç³éå¿«é" value="ç³éå¿«é"></el-option> |
| | | <el-option label="éµè¾¾éé" value="éµè¾¾éé"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç©æµåå·" prop="trackingNo"> |
| | | <el-input v-model="trackingNo" placeholder="请è¾å
¥ç©æµåå·"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="åè´§å¤æ³¨" prop="shippingRemark"> |
| | | <el-input type="textarea" v-model="shippingRemark" rows="3" placeholder="请è¾å
¥åè´§å¤æ³¨"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="shippingDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="saveShipping">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | orderNo: '', |
| | | paymentStatus: '', |
| | | shippingStatus: '' |
| | | }) |
| | | |
| | | const recordList = ref([ |
| | | { |
| | | id: 1, |
| | | orderNo: 'ORD202312001', |
| | | customer: '䏿µ·ç§ææéå
¬å¸', |
| | | orderAmount: 50000.00, |
| | | paymentMethod: 'å
¨æ¬¾å°ä»', |
| | | paymentStatus: '已仿¬¾', |
| | | shippingStatus: 'å·²åè´§', |
| | | shippingDate: '2023-12-05', |
| | | trackingNo: 'SF1234567890', |
| | | remark: 'éè¦å®¢æ·è®¢å' |
| | | }, |
| | | { |
| | | id: 2, |
| | | orderNo: 'ORD202312002', |
| | | customer: 'æ·±å³çµåæéå
¬å¸', |
| | | orderAmount: 35000.00, |
| | | paymentMethod: 'åæä»æ¬¾', |
| | | paymentStatus: 'é¨å仿¬¾', |
| | | shippingStatus: 'å¾
åè´§', |
| | | shippingDate: '', |
| | | trackingNo: '', |
| | | remark: '常è§è®¢å' |
| | | }, |
| | | { |
| | | id: 3, |
| | | orderNo: 'ORD202312003', |
| | | customer: 'å京贸æå
¬å¸', |
| | | orderAmount: 28000.00, |
| | | paymentMethod: 'æç»', |
| | | paymentStatus: 'æªä»æ¬¾', |
| | | shippingStatus: 'å¾
åè´§', |
| | | shippingDate: '', |
| | | trackingNo: '', |
| | | remark: 'æ°å®¢æ·è®¢å' |
| | | } |
| | | ]) |
| | | |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢è®°å½') |
| | | const form = reactive({ |
| | | orderNo: '', |
| | | customer: '', |
| | | orderAmount: 0, |
| | | paymentMethod: '', |
| | | paymentStatus: 'æªä»æ¬¾', |
| | | shippingStatus: 'å¾
åè´§', |
| | | shippingDate: '', |
| | | trackingNo: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const rules = { |
| | | orderNo: [{ required: true, message: '请è¾å
¥è®¢åå·', trigger: 'blur' }], |
| | | customer: [{ required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' }], |
| | | orderAmount: [{ required: true, message: '请è¾å
¥è®¢åéé¢', trigger: 'blur' }], |
| | | paymentMethod: [{ required: true, message: 'è¯·éæ©ä»æ¬¾æ¹å¼', trigger: 'change' }], |
| | | paymentStatus: [{ required: true, message: 'è¯·éæ©ä»æ¬¾ç¶æ', trigger: 'change' }], |
| | | shippingStatus: [{ required: true, message: 'è¯·éæ©åè´§ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const paymentDialogVisible = ref(false) |
| | | const shippingDialogVisible = ref(false) |
| | | const currentRecord = ref({}) |
| | | const paymentAmount = ref(0) |
| | | const paymentMethod = ref('') |
| | | const paymentRemark = ref('') |
| | | const shippingDate = ref('') |
| | | const logisticsCompany = ref('') |
| | | const trackingNo = ref('') |
| | | const shippingRemark = ref('') |
| | | const formRef = ref() |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredList = computed(() => { |
| | | let list = recordList.value |
| | | if (searchForm.orderNo) { |
| | | list = list.filter(item => item.orderNo.includes(searchForm.orderNo)) |
| | | } |
| | | if (searchForm.paymentStatus) { |
| | | list = list.filter(item => item.paymentStatus === searchForm.paymentStatus) |
| | | } |
| | | if (searchForm.shippingStatus) { |
| | | list = list.filter(item => item.shippingStatus === searchForm.shippingStatus) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | | const getPaymentStatusType = (status) => { |
| | | const statusMap = { |
| | | 'æªä»æ¬¾': 'danger', |
| | | '已仿¬¾': 'success', |
| | | 'é¨å仿¬¾': 'warning' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const getShippingStatusType = (status) => { |
| | | const statusMap = { |
| | | 'å¾
åè´§': 'warning', |
| | | 'å·²åè´§': 'primary', |
| | | 'å·²ç¾æ¶': 'success' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | // æç´¢é»è¾å·²å¨computedä¸å¤ç |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.orderNo = '' |
| | | searchForm.paymentStatus = '' |
| | | searchForm.shippingStatus = '' |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å¢è®°å½' |
| | | isEdit.value = false |
| | | form.orderNo = '' |
| | | form.customer = '' |
| | | form.orderAmount = 0 |
| | | form.paymentMethod = '' |
| | | form.paymentStatus = 'æªä»æ¬¾' |
| | | form.shippingStatus = 'å¾
åè´§' |
| | | form.shippingDate = '' |
| | | form.trackingNo = '' |
| | | form.remark = '' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleView = (row) => { |
| | | // æ¥çè®°å½è¯¦æ
|
| | | ElMessage.info('æ¥çè®°å½è¯¦æ
åè½å¾
å®ç°') |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ç¼è¾è®°å½' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handlePayment = (row) => { |
| | | currentRecord.value = row |
| | | paymentAmount.value = row.orderAmount |
| | | paymentMethod.value = '' |
| | | paymentRemark.value = '' |
| | | paymentDialogVisible.value = true |
| | | } |
| | | |
| | | const handleShipping = (row) => { |
| | | currentRecord.value = row |
| | | shippingDate.value = '' |
| | | logisticsCompany.value = '' |
| | | trackingNo.value = '' |
| | | shippingRemark.value = '' |
| | | shippingDialogVisible.value = true |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥è®°å½åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = recordList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | recordList.value.splice(index, 1) |
| | | pagination.total-- |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const savePayment = () => { |
| | | if (!paymentMethod.value) { |
| | | ElMessage.warning('è¯·éæ©ä»æ¬¾æ¹å¼') |
| | | return |
| | | } |
| | | |
| | | const index = recordList.value.findIndex(item => item.id === currentRecord.value.id) |
| | | if (index > -1) { |
| | | if (paymentAmount.value >= currentRecord.value.orderAmount) { |
| | | recordList.value[index].paymentStatus = '已仿¬¾' |
| | | } else if (paymentAmount.value > 0) { |
| | | recordList.value[index].paymentStatus = 'é¨å仿¬¾' |
| | | } |
| | | ElMessage.success('仿¬¾è®°å½å·²ä¿å') |
| | | paymentDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const saveShipping = () => { |
| | | if (!shippingDate.value || !logisticsCompany.value || !trackingNo.value) { |
| | | ElMessage.warning('请填å宿´çåè´§ä¿¡æ¯') |
| | | return |
| | | } |
| | | |
| | | const index = recordList.value.findIndex(item => item.id === currentRecord.value.id) |
| | | if (index > -1) { |
| | | recordList.value[index].shippingStatus = 'å·²åè´§' |
| | | recordList.value[index].shippingDate = shippingDate.value |
| | | recordList.value[index].trackingNo = trackingNo.value |
| | | ElMessage.success('åè´§ä¿¡æ¯å·²ä¿å') |
| | | shippingDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = recordList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | recordList.value[index] = { ...form, id: editId.value } |
| | | ElMessage.success('ç¼è¾æå') |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | const newId = Math.max(...recordList.value.map(item => item.id)) + 1 |
| | | recordList.value.push({ |
| | | ...form, |
| | | id: newId |
| | | }) |
| | | pagination.total++ |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val.page |
| | | pagination.pageSize = val.limit |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.name" |
| | | placeholder="请è¾å
¥ä¸å¡åå§å" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.department" placeholder="è¯·éæ©é¨é¨" clearable> |
| | | <el-option label="éå®é¨" value="éå®é¨"></el-option> |
| | | <el-option label="å¸åºé¨" value="å¸åºé¨"></el-option> |
| | | <el-option label="客æé¨" value="客æé¨"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©ç¶æ" clearable> |
| | | <el-option label="å¨è" value="å¨è"></el-option> |
| | | <el-option label="离è" value="离è"></el-option> |
| | | <el-option label="è¯ç¨æ" value="è¯ç¨æ"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button type="primary" style="float: right;" @click="handleAdd">æ°å¢ä¸å¡å</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- ä¸å¡åå表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="name" label="å§å" width="120" /> |
| | | <el-table-column prop="phone" label="èç³»çµè¯" width="140" /> |
| | | <el-table-column prop="email" label="é®ç®±" width="200" /> |
| | | <el-table-column prop="department" label="é¨é¨" width="100" /> |
| | | <el-table-column prop="position" label="èä½" width="100" /> |
| | | <el-table-column prop="hireDate" label="å
¥èæ¥æ" width="120" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="80"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="permissions" label="æé"> |
| | | <template #default="scope"> |
| | | <el-tag v-for="perm in scope.row.permissions" :key="perm" size="small" style="margin-right: 5px;"> |
| | | {{ perm }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="primary" @click="handlePermissions(scope.row)">æé</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å§å" prop="name"> |
| | | <el-input v-model="form.name" placeholder="请è¾å
¥å§å"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯" prop="phone"> |
| | | <el-input v-model="form.phone" placeholder="请è¾å
¥èç³»çµè¯"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é®ç®±" prop="email"> |
| | | <el-input v-model="form.email" placeholder="请è¾å
¥é®ç®±"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¨é¨" prop="department"> |
| | | <el-select v-model="form.department" placeholder="è¯·éæ©é¨é¨" style="width: 100%"> |
| | | <el-option label="éå®é¨" value="éå®é¨"></el-option> |
| | | <el-option label="å¸åºé¨" value="å¸åºé¨"></el-option> |
| | | <el-option label="客æé¨" value="客æé¨"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èä½" prop="position"> |
| | | <el-input v-model="form.position" placeholder="请è¾å
¥èä½"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¥èæ¥æ" prop="hireDate"> |
| | | <el-date-picker |
| | | v-model="form.hireDate" |
| | | type="date" |
| | | placeholder="éæ©å
¥èæ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="å¨è" value="å¨è"></el-option> |
| | | <el-option label="离è" value="离è"></el-option> |
| | | <el-option label="è¯ç¨æ" value="è¯ç¨æ"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æéè®¾ç½®å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="permissionDialogVisible" title="æé设置" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="ä¸å¡åå§å"> |
| | | <span>{{ currentSalesperson.name }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æé设置"> |
| | | <el-checkbox-group v-model="currentPermissions"> |
| | | <el-checkbox label="订å管ç">订å管ç</el-checkbox> |
| | | <el-checkbox label="客æ·ç®¡ç">客æ·ç®¡ç</el-checkbox> |
| | | <el-checkbox label="è´¢å¡ç®¡ç">è´¢å¡ç®¡ç</el-checkbox> |
| | | <el-checkbox label="å货管ç">å货管ç</el-checkbox> |
| | | <el-checkbox label="æ¥è¡¨æ¥ç">æ¥è¡¨æ¥ç</el-checkbox> |
| | | <el-checkbox label="ç³»ç»è®¾ç½®">ç³»ç»è®¾ç½®</el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="permissionDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="savePermissions">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | name: '', |
| | | department: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const salespersonList = ref([ |
| | | { |
| | | id: 1, |
| | | name: 'å¼ ä¸', |
| | | phone: '13800138001', |
| | | email: 'zhangsan@company.com', |
| | | department: 'éå®é¨', |
| | | position: 'éå®ç»ç', |
| | | hireDate: '2023-01-15', |
| | | status: 'å¨è', |
| | | permissions: ['订å管ç', '客æ·ç®¡ç', 'è´¢å¡ç®¡ç'] |
| | | }, |
| | | { |
| | | id: 2, |
| | | name: 'æå', |
| | | phone: '13800138002', |
| | | email: 'lisi@company.com', |
| | | department: 'å¸åºé¨', |
| | | position: 'å¸åºä¸å', |
| | | hireDate: '2023-03-20', |
| | | status: 'å¨è', |
| | | permissions: ['客æ·ç®¡ç', 'æ¥è¡¨æ¥ç'] |
| | | }, |
| | | { |
| | | id: 3, |
| | | name: 'çäº', |
| | | phone: '13800138003', |
| | | email: 'wangwu@company.com', |
| | | department: '客æé¨', |
| | | position: '客æä¸»ç®¡', |
| | | hireDate: '2022-11-10', |
| | | status: 'å¨è', |
| | | permissions: ['客æ·ç®¡ç', 'å货管ç'] |
| | | } |
| | | ]) |
| | | |
| | | const pagination = ref({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢ä¸å¡å') |
| | | const form = reactive({ |
| | | name: '', |
| | | phone: '', |
| | | email: '', |
| | | department: '', |
| | | position: '', |
| | | hireDate: '', |
| | | status: 'å¨è' |
| | | }) |
| | | |
| | | const rules = { |
| | | name: [{ required: true, message: '请è¾å
¥å§å', trigger: 'blur' }], |
| | | phone: [{ required: true, message: '请è¾å
¥èç³»çµè¯', trigger: 'blur' }], |
| | | email: [{ required: true, message: '请è¾å
¥é®ç®±', trigger: 'blur' }], |
| | | department: [{ required: true, message: 'è¯·éæ©é¨é¨', trigger: 'change' }], |
| | | position: [{ required: true, message: '请è¾å
¥èä½', trigger: 'blur' }], |
| | | hireDate: [{ required: true, message: 'è¯·éæ©å
¥èæ¥æ', trigger: 'change' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const permissionDialogVisible = ref(false) |
| | | const currentSalesperson = ref({}) |
| | | const currentPermissions = ref([]) |
| | | const formRef = ref() |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredList = computed(() => { |
| | | let list = salespersonList.value |
| | | if (searchForm.name) { |
| | | list = list.filter(item => item.name.includes(searchForm.name)) |
| | | } |
| | | if (searchForm.department) { |
| | | list = list.filter(item => item.department === searchForm.department) |
| | | } |
| | | if (searchForm.status) { |
| | | list = list.filter(item => item.status === searchForm.status) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'å¨è': 'success', |
| | | '离è': 'danger', |
| | | 'è¯ç¨æ': 'warning' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | // æç´¢é»è¾å·²å¨computedä¸å¤ç |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.name = '' |
| | | searchForm.department = '' |
| | | searchForm.status = '' |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å¢ä¸å¡å' |
| | | isEdit.value = false |
| | | form.name = '' |
| | | form.phone = '' |
| | | form.email = '' |
| | | form.department = '' |
| | | form.position = '' |
| | | form.hireDate = '' |
| | | form.status = 'å¨è' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ç¼è¾ä¸å¡å' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥ä¸å¡ååï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = salespersonList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | salespersonList.value.splice(index, 1) |
| | | pagination.value.total-- |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handlePermissions = (row) => { |
| | | currentSalesperson.value = row |
| | | currentPermissions.value = [...row.permissions] |
| | | permissionDialogVisible.value = true |
| | | } |
| | | |
| | | const savePermissions = () => { |
| | | const index = salespersonList.value.findIndex(item => item.id === currentSalesperson.value.id) |
| | | if (index > -1) { |
| | | salespersonList.value[index].permissions = [...currentPermissions.value] |
| | | ElMessage.success('æé设置æå') |
| | | permissionDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = salespersonList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | salespersonList.value[index] = { ...form, id: editId.value } |
| | | ElMessage.success('ç¼è¾æå') |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | const newId = Math.max(...salespersonList.value.map(item => item.id)) + 1 |
| | | salespersonList.value.push({ |
| | | ...form, |
| | | id: newId, |
| | | permissions: [] |
| | | }) |
| | | pagination.value.total++ |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.value.currentPage = val.page |
| | | pagination.value.pageSize = val.limit |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>äºç»´ç çæå¨æ¼ç¤º</span> |
| | | <el-tag type="success">åè½å®æ´ç</el-tag> |
| | | </div> |
| | | </template> |
| | | |
| | | <el-alert |
| | | title="åè½è¯´æ" |
| | | type="info" |
| | | :closable="false" |
| | | show-icon |
| | | > |
| | | <p>æ¬æ¼ç¤ºé¡µé¢å±ç¤ºäºäºç»´ç çæå¨ç宿´åè½ï¼å
æ¬ï¼</p> |
| | | <ul> |
| | | <li>åºç¡äºç»´ç çæ</li> |
| | | <li>é²ä¼ªç çæ</li> |
| | | <li>æ¹éçæåè½</li> |
| | | <li>å¾çä¸è½½åè½</li> |
| | | </ul> |
| | | </el-alert> |
| | | |
| | | <el-divider content-position="center">å¿«éä½éª</el-divider> |
| | | |
| | | <!-- å¿«éçæç¤ºä¾ --> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-card shadow="hover" class="demo-card"> |
| | | <template #header> |
| | | <div class="demo-header"> |
| | | <span>ç½åäºç»´ç </span> |
| | | <el-button type="primary" size="small" @click="generateDemo('url')"> |
| | | çæ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | <div class="demo-content"> |
| | | <p>çææåç¹å®ç½åçäºç»´ç </p> |
| | | <p class="demo-text">https://www.example.com</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card shadow="hover" class="demo-card"> |
| | | <template #header> |
| | | <div class="demo-header"> |
| | | <span>èç³»æ¹å¼</span> |
| | | <el-button type="primary" size="small" @click="generateDemo('contact')"> |
| | | çæ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | <div class="demo-content"> |
| | | <p>çæå
å«è系信æ¯çäºç»´ç </p> |
| | | <p class="demo-text">å§åï¼å¼ ä¸<br>çµè¯ï¼13800138000</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card shadow="hover" class="demo-card"> |
| | | <template #header> |
| | | <div class="demo-header"> |
| | | <span>产åä¿¡æ¯</span> |
| | | <el-button type="primary" size="small" @click="generateDemo('product')"> |
| | | çæ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | <div class="demo-content"> |
| | | <p>çæäº§åä¿¡æ¯çäºç»´ç </p> |
| | | <p class="demo-text">产åï¼å·¥ä¸ä¼ æå¨<br>åå·ï¼SENSOR-001</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-divider content-position="center">åè½å
¥å£</el-divider> |
| | | |
| | | <!-- åè½å
¥å£ --> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-card shadow="hover" class="feature-card"> |
| | | <div class="feature-content"> |
| | | <div class="feature-icon"> |
| | | <el-icon size="40" color="#409EFF"><QrCode /></el-icon> |
| | | </div> |
| | | <div class="feature-text"> |
| | | <h3>åºç¡äºç»´ç çæ</h3> |
| | | <p>å¿«éçæåç§å
容çäºç»´ç ï¼æ¯æèªå®ä¹æ ·å¼åä¸è½½</p> |
| | | <el-button type="primary" @click="goToSimple">å¼å§ä½¿ç¨</el-button> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <el-card shadow="hover" class="feature-card"> |
| | | <div class="feature-content"> |
| | | <div class="feature-icon"> |
| | | <el-icon size="40" color="#E6A23C"><Shield /></el-icon> |
| | | </div> |
| | | <div class="feature-text"> |
| | | <h3>é²ä¼ªç çæ</h3> |
| | | <p>çæå
·æé²ä¼ªåè½çäºç»´ç ï¼æ¯ææ¹éçæåä¸è½½</p> |
| | | <el-button type="warning" @click="goToAdvanced">å¼å§ä½¿ç¨</el-button> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-divider content-position="center">使ç¨è¯´æ</el-divider> |
| | | |
| | | <!-- 使ç¨è¯´æ --> |
| | | <el-collapse v-model="activeNames"> |
| | | <el-collapse-item title="äºç»´ç çææ¥éª¤" name="1"> |
| | | <div class="instruction-content"> |
| | | <ol> |
| | | <li>éæ©è¦çæçå
容类åï¼ææ¬ãç½åãèç³»æ¹å¼çï¼</li> |
| | | <li>è¾å
¥å
·ä½å
容</li> |
| | | <li>éæ©äºç»´ç 尺寸åé¢è²</li> |
| | | <li>ç¹å»çææé®</li> |
| | | <li>é¢è§çæçäºç»´ç </li> |
| | | <li>ä¸è½½å¾çå°æ¬å°</li> |
| | | </ol> |
| | | </div> |
| | | </el-collapse-item> |
| | | |
| | | <el-collapse-item title="é²ä¼ªç ç¹ç¹" name="2"> |
| | | <div class="instruction-content"> |
| | | <ul> |
| | | <li><strong>å¯ä¸æ§</strong>ï¼æ¯ä¸ªé²ä¼ªç é½å
嫿¶é´æ³åéæºæ°</li> |
| | | <li><strong>é«çº é</strong>ï¼ä½¿ç¨æé«çº é级å«ï¼æ«ææåçæ´é«</li> |
| | | <li><strong>æ¹éçæ</strong>ï¼æ¯æä¸æ¬¡çæå¤ä¸ªé²ä¼ªç </li> |
| | | <li><strong>æ ¼å¼è§è</strong>ï¼SEC_产åç¼ç _æ¹æ¬¡å·_æ¶é´æ³_éæºæ°</li> |
| | | </ul> |
| | | </div> |
| | | </el-collapse-item> |
| | | |
| | | <el-collapse-item title="åºç¨åºæ¯" name="3"> |
| | | <div class="instruction-content"> |
| | | <div class="scenario-grid"> |
| | | <div class="scenario-item"> |
| | | <el-icon color="#67C23A"><Goods /></el-icon> |
| | | <span>产åå
è£
</span> |
| | | </div> |
| | | <div class="scenario-item"> |
| | | <el-icon color="#409EFF"><Document /></el-icon> |
| | | <span>ææ¡£éªè¯</span> |
| | | </div> |
| | | <div class="scenario-item"> |
| | | <el-icon color="#E6A23C"><Tickets /></el-icon> |
| | | <span>票æ®é²ä¼ª</span> |
| | | </div> |
| | | <div class="scenario-item"> |
| | | <el-icon color="#F56C6C"><Medal /></el-icon> |
| | | <span>è¯ä¹¦éªè¯</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-collapse-item> |
| | | </el-collapse> |
| | | </el-card> |
| | | |
| | | <!-- çæçäºç»´ç é¢è§ --> |
| | | <el-dialog v-model="previewVisible" title="çæçäºç»´ç " width="400px" center> |
| | | <div class="preview-container"> |
| | | <img v-if="previewUrl" :src="previewUrl" alt="é¢è§äºç»´ç " /> |
| | | <div class="preview-info"> |
| | | <p><strong>å
容ï¼</strong>{{ previewContent }}</p> |
| | | <p><strong>ç±»åï¼</strong>{{ previewType }}</p> |
| | | </div> |
| | | <div class="preview-actions"> |
| | | <el-button type="success" @click="downloadPreview" icon="Download"> |
| | | ä¸è½½å¾ç |
| | | </el-button> |
| | | <el-button @click="previewVisible = false">å
³é</el-button> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { ElMessage } from 'element-plus' |
| | | import { QrCode, Shield, Goods, Document, Tickets, Medal } from '@element-plus/icons-vue' |
| | | import QRCode from 'qrcode' |
| | | |
| | | defineOptions({ |
| | | name: 'QRCodeDemo' |
| | | }) |
| | | |
| | | const router = useRouter() |
| | | const activeNames = ref(['1']) |
| | | const previewVisible = ref(false) |
| | | const previewUrl = ref('') |
| | | const previewContent = ref('') |
| | | const previewType = ref('') |
| | | |
| | | // çææ¼ç¤ºäºç»´ç |
| | | const generateDemo = async (type) => { |
| | | try { |
| | | let content = '' |
| | | let typeName = '' |
| | | |
| | | switch (type) { |
| | | case 'url': |
| | | content = 'https://www.example.com' |
| | | typeName = 'ç½åäºç»´ç ' |
| | | break |
| | | case 'contact': |
| | | content = 'BEGIN:VCARD\nVERSION:3.0\nFN:å¼ ä¸\nTEL:13800138000\nEND:VCARD' |
| | | typeName = 'èç³»æ¹å¼äºç»´ç ' |
| | | break |
| | | case 'product': |
| | | content = '产ååç§°ï¼å·¥ä¸ä¼ æå¨\nåå·ï¼SENSOR-001\nè§æ ¼ï¼é«ç²¾åº¦å\nç¨éï¼å·¥ä¸èªå¨å' |
| | | typeName = '产åä¿¡æ¯äºç»´ç ' |
| | | break |
| | | } |
| | | |
| | | const qrCodeUrl = await QRCode.toDataURL(content, { |
| | | width: 256, |
| | | margin: 2, |
| | | color: { |
| | | dark: '#000000', |
| | | light: '#FFFFFF' |
| | | } |
| | | }) |
| | | |
| | | previewUrl.value = qrCodeUrl |
| | | previewContent.value = content |
| | | previewType.value = typeName |
| | | previewVisible.value = true |
| | | |
| | | } catch (error) { |
| | | console.error('çææ¼ç¤ºäºç»´ç 失败:', error) |
| | | ElMessage.error('çæå¤±è´¥ï¼' + error.message) |
| | | } |
| | | } |
| | | |
| | | // ä¸è½½é¢è§çäºç»´ç |
| | | const downloadPreview = () => { |
| | | if (!previewUrl.value) { |
| | | ElMessage.warning('没æå¯ä¸è½½çäºç»´ç ') |
| | | return |
| | | } |
| | | |
| | | const a = document.createElement('a') |
| | | a.href = previewUrl.value |
| | | a.download = `${previewType.value}_${new Date().getTime()}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | ElMessage.success('ä¸è½½æåï¼') |
| | | } |
| | | |
| | | // 跳转å°ç®åçé¡µé¢ |
| | | const goToSimple = () => { |
| | | router.push('/tool/qrCodeSimple') |
| | | } |
| | | |
| | | // 跳转å°é«çº§çé¡µé¢ |
| | | const goToAdvanced = () => { |
| | | router.push('/tool/qrCodeGenerator') |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .box-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .demo-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .demo-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .demo-content { |
| | | text-align: center; |
| | | } |
| | | |
| | | .demo-text { |
| | | font-size: 12px; |
| | | color: #666; |
| | | background: #f8f9fa; |
| | | padding: 8px; |
| | | border-radius: 4px; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .feature-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .feature-content { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .feature-icon { |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .feature-text h3 { |
| | | margin: 0 0 10px 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .feature-text p { |
| | | margin: 0 0 15px 0; |
| | | color: #606266; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | .instruction-content { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .instruction-content ol, |
| | | .instruction-content ul { |
| | | margin: 0; |
| | | padding-left: 20px; |
| | | } |
| | | |
| | | .instruction-content li { |
| | | margin: 8px 0; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .scenario-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, 1fr); |
| | | gap: 20px; |
| | | margin-top: 15px; |
| | | } |
| | | |
| | | .scenario-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | padding: 15px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .scenario-item span { |
| | | font-weight: 500; |
| | | color: #303133; |
| | | } |
| | | |
| | | .preview-container { |
| | | text-align: center; |
| | | } |
| | | |
| | | .preview-container img { |
| | | max-width: 100%; |
| | | height: auto; |
| | | border: 2px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .preview-info { |
| | | text-align: left; |
| | | background: #f8f9fa; |
| | | padding: 15px; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .preview-info p { |
| | | margin: 8px 0; |
| | | color: #666; |
| | | } |
| | | |
| | | .preview-actions { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 15px; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>äºç»´ç ä¸é²ä¼ªç çæå¨</span> |
| | | <el-button type="primary" @click="showBatchDialog" icon="Plus"> |
| | | æ¹éçæ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- éæäºç»´ç çæç»ä»¶ --> |
| | | <QRCodeGenerator ref="qrGeneratorRef" /> |
| | | </el-card> |
| | | |
| | | <!-- æ¹éçæå¯¹è¯æ¡ --> |
| | | <el-dialog v-model="batchDialogVisible" title="æ¹éçæè®¾ç½®" width="800px"> |
| | | <el-form :model="batchForm" :rules="batchRules" ref="batchFormRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ è¯ç±»å" prop="type"> |
| | | <el-select v-model="batchForm.type" placeholder="è¯·éæ©æ è¯ç±»å" style="width: 100%"> |
| | | <el-option label="äºç»´ç " value="qrcode"></el-option> |
| | | <el-option label="é²ä¼ªç " value="security"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="çææ°é" prop="quantity"> |
| | | <el-input-number |
| | | v-model="batchForm.quantity" |
| | | :min="1" |
| | | :max="1000" |
| | | :step="10" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åç¼" prop="prefix"> |
| | | <el-input v-model="batchForm.prefix" placeholder="请è¾å
¥åç¼ï¼å¦ï¼PROD_"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµ·å§ç¼å·" prop="startNumber"> |
| | | <el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="尺寸" prop="size"> |
| | | <el-input-number |
| | | v-model="batchForm.size" |
| | | :min="100" |
| | | :max="500" |
| | | :step="50" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¾¹è·" prop="margin"> |
| | | <el-input-number |
| | | v-model="batchForm.margin" |
| | | :min="0" |
| | | :max="10" |
| | | :step="1" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | </el-col> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="忝è²" prop="foregroundColor"> |
| | | <el-color-picker v-model="batchForm.foregroundColor" style="width: 100%"></el-color-picker> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èæ¯è²" prop="backgroundColor"> |
| | | <el-color-picker v-model="batchForm.backgroundColor" style="width: 100%"></el-color-picker> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input |
| | | v-model="batchForm.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | ></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="batchDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="startBatchGeneration" :loading="generating"> |
| | | å¼å§çæ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ¹éçæè¿åº¦ --> |
| | | <el-dialog v-model="progressDialogVisible" title="æ¹éçæè¿åº¦" width="500px" :close-on-click-modal="false"> |
| | | <div class="progress-container"> |
| | | <el-progress |
| | | :percentage="progressPercentage" |
| | | :status="progressStatus" |
| | | :stroke-width="20" |
| | | ></el-progress> |
| | | <p class="progress-text">{{ progressText }}</p> |
| | | <div class="progress-details"> |
| | | <p>å·²çæ: {{ generatedCount }} / {{ totalCount }}</p> |
| | | <p>å½åå
容: {{ currentContent }}</p> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="cancelGeneration" :disabled="!canCancel">åæ¶</el-button> |
| | | <el-button type="primary" @click="downloadBatchResults" v-if="generationCompleted"> |
| | | ä¸è½½ç»æ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import QRCodeGenerator from '@/components/QRCodeGenerator/index.vue' |
| | | import QRCode from 'qrcode' |
| | | import JSZip from 'jszip' |
| | | |
| | | defineOptions({ |
| | | name: 'QRCodeGeneratorPage' |
| | | }) |
| | | |
| | | // ç»ä»¶å¼ç¨ |
| | | const qrGeneratorRef = ref() |
| | | |
| | | // æ¹éçæç¸å
³ |
| | | const batchDialogVisible = ref(false) |
| | | const progressDialogVisible = ref(false) |
| | | const generating = ref(false) |
| | | const generationCompleted = ref(false) |
| | | const canCancel = ref(true) |
| | | |
| | | const batchForm = reactive({ |
| | | type: 'qrcode', |
| | | quantity: 100, |
| | | prefix: 'PROD_', |
| | | startNumber: 1, |
| | | size: 200, |
| | | margin: 2, |
| | | foregroundColor: '#000000', |
| | | backgroundColor: '#FFFFFF', |
| | | remark: '' |
| | | }) |
| | | |
| | | const batchRules = { |
| | | type: [{ required: true, message: 'è¯·éæ©æ è¯ç±»å', trigger: 'change' }], |
| | | quantity: [{ required: true, message: '请è¾å
¥çææ°é', trigger: 'blur' }], |
| | | prefix: [{ required: true, message: '请è¾å
¥åç¼', trigger: 'blur' }], |
| | | startNumber: [{ required: true, message: '请è¾å
¥èµ·å§ç¼å·', trigger: 'blur' }] |
| | | } |
| | | |
| | | // è¿åº¦ç¸å
³ |
| | | const progressPercentage = ref(0) |
| | | const progressStatus = ref('') |
| | | const progressText = ref('åå¤ä¸...') |
| | | const generatedCount = ref(0) |
| | | const totalCount = ref(0) |
| | | const currentContent = ref('') |
| | | |
| | | // çæç»æ |
| | | const batchResults = ref([]) |
| | | |
| | | // æ¾ç¤ºæ¹éçæå¯¹è¯æ¡ |
| | | const showBatchDialog = () => { |
| | | batchDialogVisible.value = true |
| | | // é置表å |
| | | Object.assign(batchForm, { |
| | | type: 'qrcode', |
| | | quantity: 100, |
| | | prefix: 'PROD_', |
| | | startNumber: 1, |
| | | size: 200, |
| | | margin: 2, |
| | | foregroundColor: '#000000', |
| | | backgroundColor: '#FFFFFF', |
| | | remark: '' |
| | | }) |
| | | } |
| | | |
| | | // å¼å§æ¹éçæ |
| | | const startBatchGeneration = async () => { |
| | | try { |
| | | await batchFormRef.value.validate() |
| | | |
| | | if (!batchForm.prefix.trim()) { |
| | | ElMessage.warning('请è¾å
¥åç¼') |
| | | return |
| | | } |
| | | |
| | | batchDialogVisible.value = false |
| | | progressDialogVisible.value = true |
| | | generating.value = true |
| | | generationCompleted.value = false |
| | | canCancel.value = true |
| | | |
| | | // éç½®è¿åº¦ |
| | | progressPercentage.value = 0 |
| | | progressStatus.value = '' |
| | | progressText.value = 'å¼å§çæ...' |
| | | generatedCount.value = 0 |
| | | totalCount.value = batchForm.quantity |
| | | batchResults.value = [] |
| | | |
| | | await generateBatchCodes() |
| | | |
| | | } catch (error) { |
| | | console.error('æ¹éçæå¤±è´¥:', error) |
| | | ElMessage.error('æ¹éçæå¤±è´¥ï¼' + error.message) |
| | | } |
| | | } |
| | | |
| | | // çæé²ä¼ªç å
容 |
| | | const generateSecurityCode = (content) => { |
| | | const timestamp = Date.now() |
| | | const random = Math.random().toString(36).substr(2, 8) |
| | | return `SEC_${content}_${timestamp}_${random}` |
| | | } |
| | | |
| | | // æ¹éçæç |
| | | const generateBatchCodes = async () => { |
| | | try { |
| | | for (let i = 0; i < batchForm.quantity; i++) { |
| | | if (!canCancel.value) { |
| | | progressText.value = 'çæå·²åæ¶' |
| | | progressStatus.value = 'exception' |
| | | break |
| | | } |
| | | |
| | | const number = batchForm.startNumber + i |
| | | const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}` |
| | | currentContent.value = content |
| | | |
| | | let codeUrl |
| | | if (batchForm.type === 'qrcode') { |
| | | codeUrl = await QRCode.toDataURL(content, { |
| | | width: batchForm.size, |
| | | margin: batchForm.margin, |
| | | color: { |
| | | dark: batchForm.foregroundColor, |
| | | light: batchForm.backgroundColor |
| | | }, |
| | | errorCorrectionLevel: 'M' |
| | | }) |
| | | } else { |
| | | const securityContent = generateSecurityCode(content) |
| | | codeUrl = await QRCode.toDataURL(securityContent, { |
| | | width: batchForm.size, |
| | | margin: batchForm.margin, |
| | | color: { |
| | | dark: batchForm.foregroundColor, |
| | | light: batchForm.backgroundColor |
| | | }, |
| | | errorCorrectionLevel: 'H' |
| | | }) |
| | | } |
| | | |
| | | batchResults.value.push({ |
| | | content, |
| | | url: codeUrl, |
| | | type: batchForm.type, |
| | | generateTime: new Date().toLocaleString() |
| | | }) |
| | | |
| | | generatedCount.value = i + 1 |
| | | progressPercentage.value = Math.round(((i + 1) / batchForm.quantity) * 100) |
| | | progressText.value = `æ£å¨çæç¬¬ ${i + 1} 个ç ...` |
| | | |
| | | // æ·»å å°å»¶è¿ï¼è®©ç¨æ·çå°è¿åº¦ |
| | | await new Promise(resolve => setTimeout(resolve, 50)) |
| | | } |
| | | |
| | | if (canCancel.value) { |
| | | progressText.value = 'çæå®æï¼' |
| | | progressStatus.value = 'success' |
| | | generationCompleted.value = true |
| | | ElMessage.success(`æ¹éçæå®æï¼å
±çæ ${batchForm.quantity} 个ç `) |
| | | } |
| | | |
| | | } catch (error) { |
| | | console.error('æ¹éçæå¤±è´¥:', error) |
| | | progressText.value = 'çæå¤±è´¥ï¼' + error.message |
| | | progressStatus.value = 'exception' |
| | | ElMessage.error('æ¹éçæå¤±è´¥ï¼' + error.message) |
| | | } finally { |
| | | generating.value = false |
| | | } |
| | | } |
| | | |
| | | // åæ¶çæ |
| | | const cancelGeneration = () => { |
| | | canCancel.value = false |
| | | progressText.value = 'æ£å¨åæ¶...' |
| | | setTimeout(() => { |
| | | progressDialogVisible.value = false |
| | | ElMessage.info('çæå·²åæ¶') |
| | | }, 1000) |
| | | } |
| | | |
| | | // ä¸è½½æ¹éçæç»æ |
| | | const downloadBatchResults = async () => { |
| | | if (batchResults.value.length === 0) { |
| | | ElMessage.warning('没æå¯ä¸è½½çç»æ') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const zip = new JSZip() |
| | | |
| | | // å建说ææä»¶ |
| | | const readme = `æ¹éçæè¯´æ |
| | | ç±»å: ${batchForm.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '} |
| | | æ°é: ${batchResults.value.length} |
| | | åç¼: ${batchForm.prefix} |
| | | èµ·å§ç¼å·: ${batchForm.startNumber} |
| | | 尺寸: ${batchForm.size}x${batchForm.size}px |
| | | çææ¶é´: ${new Date().toLocaleString()} |
| | | 夿³¨: ${batchForm.remark || 'æ '} |
| | | |
| | | æä»¶å表: |
| | | ${batchResults.value.map((item, index) => `${index + 1}. ${item.content}.png`).join('\n')} |
| | | ` |
| | | |
| | | zip.file('README.txt', readme) |
| | | |
| | | // æ·»å å¾çæä»¶ |
| | | batchResults.value.forEach((result, index) => { |
| | | const base64Data = result.url.split(',')[1] |
| | | const byteCharacters = atob(base64Data) |
| | | const byteNumbers = new Array(byteCharacters.length) |
| | | for (let i = 0; i < byteCharacters.length; i++) { |
| | | byteNumbers[i] = byteCharacters.charCodeAt(i) |
| | | } |
| | | const byteArray = new Uint8Array(byteNumbers) |
| | | |
| | | zip.file(`${result.content}.png`, byteArray) |
| | | }) |
| | | |
| | | const content = await zip.generateAsync({ type: 'blob' }) |
| | | const a = document.createElement('a') |
| | | a.href = URL.createObjectURL(content) |
| | | a.download = `æ¹é${batchForm.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}_${batchForm.prefix}_${new Date().getTime()}.zip` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | URL.revokeObjectURL(a.href) |
| | | |
| | | ElMessage.success('æ¹éä¸è½½å®æï¼') |
| | | progressDialogVisible.value = false |
| | | |
| | | } catch (error) { |
| | | console.error('æ¹éä¸è½½å¤±è´¥:', error) |
| | | ElMessage.error('æ¹éä¸è½½å¤±è´¥ï¼' + error.message) |
| | | } |
| | | } |
| | | |
| | | // 表åå¼ç¨ |
| | | const batchFormRef = ref() |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .box-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .progress-container { |
| | | text-align: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .progress-text { |
| | | margin: 20px 0; |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .progress-details { |
| | | margin-top: 20px; |
| | | text-align: left; |
| | | background: #f8f9fa; |
| | | padding: 15px; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .progress-details p { |
| | | margin: 8px 0; |
| | | color: #666; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-row :gutter="20"> |
| | | <!-- 左侧ï¼çæè¡¨å --> |
| | | <el-col :span="12"> |
| | | <el-card class="box-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>äºç»´ç çæ</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-form-item label="å
容" prop="content"> |
| | | <el-input |
| | | v-model="form.content" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥è¦çæäºç»´ç çå
容ï¼å¦ï¼ç½åãææ¬ãèç³»æ¹å¼ç" |
| | | ></el-input> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="尺寸"> |
| | | <el-select v-model="form.size" style="width: 100%"> |
| | | <el-option label="å°å°ºå¯¸ (128x128)" value="128"></el-option> |
| | | <el-option label="æ å尺寸 (256x256)" value="256"></el-option> |
| | | <el-option label="大尺寸 (512x512)" value="512"></el-option> |
| | | <el-option label="è¶
大尺寸 (1024x1024)" value="1024"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="忝è²"> |
| | | <el-color-picker v-model="form.foregroundColor"></el-color-picker> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="èæ¯è²"> |
| | | <el-color-picker v-model="form.backgroundColor"></el-color-picker> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button type="primary" @click="generateQRCode" :loading="generating" size="large"> |
| | | çæäºç»´ç |
| | | </el-button> |
| | | <el-button @click="resetForm">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- å³ä¾§ï¼é¢è§åä¸è½½ --> |
| | | <el-col :span="12"> |
| | | <el-card class="box-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>é¢è§ä¸ä¸è½½</span> |
| | | <el-button |
| | | v-if="qrCodeUrl" |
| | | type="success" |
| | | @click="downloadQRCode" |
| | | icon="Download" |
| | | size="small" |
| | | > |
| | | ä¸è½½å¾ç |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | |
| | | <div v-if="qrCodeUrl" class="preview-container"> |
| | | <div class="qr-preview"> |
| | | <img :src="qrCodeUrl" alt="çæçäºç»´ç " /> |
| | | </div> |
| | | <div class="qr-info"> |
| | | <p><strong>å
容ï¼</strong>{{ form.content }}</p> |
| | | <p><strong>尺寸ï¼</strong>{{ form.size }}x{{ form.size }}px</p> |
| | | <p><strong>çææ¶é´ï¼</strong>{{ generateTime }}</p> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else class="empty-preview"> |
| | | <el-empty description="请å
çæäºç»´ç " :image-size="100"></el-empty> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- é²ä¼ªç çæåºå --> |
| | | <el-row :gutter="20" style="margin-top: 20px;"> |
| | | <el-col :span="24"> |
| | | <el-card class="box-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>é²ä¼ªç çæ</span> |
| | | <el-button type="warning" @click="showSecurityDialog" icon="Shield"> |
| | | çæé²ä¼ªç |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="security-info"> |
| | | <p>é²ä¼ªç ç¹ç¹ï¼</p> |
| | | <ul> |
| | | <li>å
嫿¶é´æ³åéæºæ°ï¼ç¡®ä¿å¯ä¸æ§</li> |
| | | <li>ä½¿ç¨æé«çº é级å«ï¼æé«æ«ææåç</li> |
| | | <li>æ¯ææ¹éçæåä¸è½½</li> |
| | | <li>éç¨äºäº§åé²ä¼ªãææ¡£éªè¯çåºæ¯</li> |
| | | </ul> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- é²ä¼ªç çæå¯¹è¯æ¡ --> |
| | | <el-dialog v-model="securityDialogVisible" title="é²ä¼ªç çæ" width="600px"> |
| | | <el-form :model="securityForm" :rules="securityRules" ref="securityFormRef" label-width="100px"> |
| | | <el-form-item label="产ååç§°" prop="productName"> |
| | | <el-input v-model="securityForm.productName" placeholder="请è¾å
¥äº§ååç§°"></el-input> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="产åç¼ç " prop="productCode"> |
| | | <el-input v-model="securityForm.productCode" placeholder="请è¾å
¥äº§åç¼ç "></el-input> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æ¹æ¬¡å·" prop="batchNo"> |
| | | <el-input v-model="securityForm.batchNo" placeholder="请è¾å
¥æ¹æ¬¡å·"></el-input> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="çææ°é" prop="quantity"> |
| | | <el-input-number |
| | | v-model="securityForm.quantity" |
| | | :min="1" |
| | | :max="100" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="尺寸"> |
| | | <el-select v-model="securityForm.size" style="width: 100%"> |
| | | <el-option label="æ å尺寸 (256x256)" value="256"></el-option> |
| | | <el-option label="大尺寸 (512x512)" value="512"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="securityDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="generateSecurityCodes" :loading="generatingSecurity"> |
| | | å¼å§çæ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- é²ä¼ªç ç»æå±ç¤º --> |
| | | <el-dialog v-model="securityResultVisible" title="é²ä¼ªç çæç»æ" width="80%" top="5vh"> |
| | | <div v-if="securityResults.length > 0" class="security-results"> |
| | | <div class="results-header"> |
| | | <p>å
±çæ {{ securityResults.length }} 个é²ä¼ªç </p> |
| | | <el-button type="success" @click="downloadAllSecurityCodes" icon="Download"> |
| | | ä¸è½½å
¨é¨ |
| | | </el-button> |
| | | </div> |
| | | |
| | | <div class="results-grid"> |
| | | <div |
| | | v-for="(result, index) in securityResults" |
| | | :key="index" |
| | | class="result-item" |
| | | > |
| | | <img :src="result.url" :alt="result.content" /> |
| | | <p class="result-content">{{ result.content }}</p> |
| | | <el-button size="small" @click="downloadSingleSecurityCode(result)"> |
| | | ä¸è½½ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import QRCode from 'qrcode' |
| | | import JSZip from 'jszip' |
| | | |
| | | defineOptions({ |
| | | name: 'QRCodeSimple' |
| | | }) |
| | | |
| | | // äºç»´ç çæè¡¨å |
| | | const form = reactive({ |
| | | content: '', |
| | | size: '256', |
| | | foregroundColor: '#000000', |
| | | backgroundColor: '#FFFFFF' |
| | | }) |
| | | |
| | | const rules = { |
| | | content: [{ required: true, message: '请è¾å
¥å
容', trigger: 'blur' }] |
| | | } |
| | | |
| | | // é²ä¼ªç çæè¡¨å |
| | | const securityForm = reactive({ |
| | | productName: '', |
| | | productCode: '', |
| | | batchNo: '', |
| | | quantity: 10, |
| | | size: '256' |
| | | }) |
| | | |
| | | const securityRules = { |
| | | productName: [{ required: true, message: '请è¾å
¥äº§ååç§°', trigger: 'blur' }], |
| | | productCode: [{ required: true, message: '请è¾å
¥äº§åç¼ç ', trigger: 'blur' }], |
| | | batchNo: [{ required: true, message: '请è¾å
¥æ¹æ¬¡å·', trigger: 'blur' }], |
| | | quantity: [{ required: true, message: '请è¾å
¥çææ°é', trigger: 'blur' }] |
| | | } |
| | | |
| | | // ååºå¼æ°æ® |
| | | const formRef = ref() |
| | | const securityFormRef = ref() |
| | | const generating = ref(false) |
| | | const generatingSecurity = ref(false) |
| | | const qrCodeUrl = ref('') |
| | | const generateTime = ref('') |
| | | const securityDialogVisible = ref(false) |
| | | const securityResultVisible = ref(false) |
| | | const securityResults = ref([]) |
| | | |
| | | // çæäºç»´ç |
| | | const generateQRCode = async () => { |
| | | try { |
| | | await formRef.value.validate() |
| | | |
| | | if (!form.content.trim()) { |
| | | ElMessage.warning('请è¾å
¥è¦çæäºç»´ç çå
容') |
| | | return |
| | | } |
| | | |
| | | generating.value = true |
| | | |
| | | qrCodeUrl.value = await QRCode.toDataURL(form.content, { |
| | | width: parseInt(form.size), |
| | | margin: 2, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | }, |
| | | errorCorrectionLevel: 'M' |
| | | }) |
| | | |
| | | generateTime.value = new Date().toLocaleString() |
| | | ElMessage.success('äºç»´ç çææåï¼') |
| | | |
| | | } catch (error) { |
| | | console.error('çæäºç»´ç 失败:', error) |
| | | ElMessage.error('çæäºç»´ç 失败ï¼' + error.message) |
| | | } finally { |
| | | generating.value = false |
| | | } |
| | | } |
| | | |
| | | // ä¸è½½äºç»´ç |
| | | const downloadQRCode = () => { |
| | | if (!qrCodeUrl.value) { |
| | | ElMessage.warning('请å
çæäºç»´ç ') |
| | | return |
| | | } |
| | | |
| | | const a = document.createElement('a') |
| | | a.href = qrCodeUrl.value |
| | | a.download = `äºç»´ç _${new Date().getTime()}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | ElMessage.success('ä¸è½½æåï¼') |
| | | } |
| | | |
| | | // é置表å |
| | | const resetForm = () => { |
| | | formRef.value.resetFields() |
| | | qrCodeUrl.value = '' |
| | | generateTime.value = '' |
| | | } |
| | | |
| | | // æ¾ç¤ºé²ä¼ªç å¯¹è¯æ¡ |
| | | const showSecurityDialog = () => { |
| | | securityDialogVisible.value = true |
| | | // é置表å |
| | | Object.assign(securityForm, { |
| | | productName: '', |
| | | productCode: '', |
| | | batchNo: '', |
| | | quantity: 10, |
| | | size: '256' |
| | | }) |
| | | } |
| | | |
| | | // çæé²ä¼ªç |
| | | const generateSecurityCodes = async () => { |
| | | try { |
| | | await securityFormRef.value.validate() |
| | | |
| | | generatingSecurity.value = true |
| | | securityResults.value = [] |
| | | |
| | | for (let i = 1; i <= securityForm.quantity; i++) { |
| | | const timestamp = Date.now() + i // ç¡®ä¿æ¯ä¸ªç 齿ä¸åçæ¶é´æ³ |
| | | const random = Math.random().toString(36).substr(2, 8) |
| | | const content = `SEC_${securityForm.productCode}_${securityForm.batchNo}_${timestamp}_${random}` |
| | | |
| | | const codeUrl = await QRCode.toDataURL(content, { |
| | | width: parseInt(securityForm.size), |
| | | margin: 2, |
| | | color: { |
| | | dark: '#000000', |
| | | light: '#FFFFFF' |
| | | }, |
| | | errorCorrectionLevel: 'H' // æé«çº éçº§å« |
| | | }) |
| | | |
| | | securityResults.value.push({ |
| | | content, |
| | | url: codeUrl, |
| | | productName: securityForm.productName, |
| | | productCode: securityForm.productCode, |
| | | batchNo: securityForm.batchNo, |
| | | generateTime: new Date().toLocaleString() |
| | | }) |
| | | } |
| | | |
| | | securityDialogVisible.value = false |
| | | securityResultVisible.value = true |
| | | ElMessage.success(`é²ä¼ªç çæå®æï¼å
±çæ ${securityForm.quantity} 个`) |
| | | |
| | | } catch (error) { |
| | | console.error('çæé²ä¼ªç 失败:', error) |
| | | ElMessage.error('çæé²ä¼ªç 失败ï¼' + error.message) |
| | | } finally { |
| | | generatingSecurity.value = false |
| | | } |
| | | } |
| | | |
| | | // ä¸è½½å个é²ä¼ªç |
| | | const downloadSingleSecurityCode = (result) => { |
| | | const a = document.createElement('a') |
| | | a.href = result.url |
| | | a.download = `${result.content}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | ElMessage.success('ä¸è½½æåï¼') |
| | | } |
| | | |
| | | // ä¸è½½ææé²ä¼ªç |
| | | const downloadAllSecurityCodes = async () => { |
| | | if (securityResults.value.length === 0) { |
| | | ElMessage.warning('没æå¯ä¸è½½çé²ä¼ªç ') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const zip = new JSZip() |
| | | |
| | | // å建说ææä»¶ |
| | | const readme = `é²ä¼ªç çæè¯´æ |
| | | 产ååç§°: ${securityForm.productName} |
| | | 产åç¼ç : ${securityForm.productCode} |
| | | æ¹æ¬¡å·: ${securityForm.batchNo} |
| | | æ°é: ${securityResults.value.length} |
| | | 尺寸: ${securityForm.size}x${securityForm.size}px |
| | | çææ¶é´: ${new Date().toLocaleString()} |
| | | |
| | | æä»¶å表: |
| | | ${securityResults.value.map((item, index) => `${index + 1}. ${item.content}.png`).join('\n')} |
| | | ` |
| | | |
| | | zip.file('README.txt', readme) |
| | | |
| | | // æ·»å å¾çæä»¶ |
| | | securityResults.value.forEach((result) => { |
| | | const base64Data = result.url.split(',')[1] |
| | | const byteCharacters = atob(base64Data) |
| | | const byteNumbers = new Array(byteCharacters.length) |
| | | for (let i = 0; i < byteCharacters.length; i++) { |
| | | byteNumbers[i] = byteCharacters.charCodeAt(i) |
| | | } |
| | | const byteArray = new Uint8Array(byteNumbers) |
| | | |
| | | zip.file(`${result.content}.png`, byteArray) |
| | | }) |
| | | |
| | | const content = await zip.generateAsync({ type: 'blob' }) |
| | | const a = document.createElement('a') |
| | | a.href = URL.createObjectURL(content) |
| | | a.download = `é²ä¼ªç _${securityForm.productCode}_${securityForm.batchNo}_${new Date().getTime()}.zip` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | URL.revokeObjectURL(a.href) |
| | | |
| | | ElMessage.success('æ¹éä¸è½½å®æï¼') |
| | | |
| | | } catch (error) { |
| | | console.error('æ¹éä¸è½½å¤±è´¥:', error) |
| | | ElMessage.error('æ¹éä¸è½½å¤±è´¥ï¼' + error.message) |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .box-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .preview-container { |
| | | text-align: center; |
| | | } |
| | | |
| | | .qr-preview img { |
| | | max-width: 100%; |
| | | height: auto; |
| | | border: 2px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .qr-info { |
| | | margin-top: 20px; |
| | | text-align: left; |
| | | background: #f8f9fa; |
| | | padding: 15px; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .qr-info p { |
| | | margin: 8px 0; |
| | | color: #666; |
| | | } |
| | | |
| | | .empty-preview { |
| | | padding: 40px 0; |
| | | } |
| | | |
| | | .security-info { |
| | | padding: 20px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .security-info p { |
| | | font-weight: bold; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .security-info ul { |
| | | margin: 0; |
| | | padding-left: 20px; |
| | | } |
| | | |
| | | .security-info li { |
| | | margin: 8px 0; |
| | | color: #666; |
| | | } |
| | | |
| | | .security-results { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .results-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | padding-bottom: 15px; |
| | | border-bottom: 1px solid #e0e0e0; |
| | | } |
| | | |
| | | .results-header p { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | margin: 0; |
| | | } |
| | | |
| | | .results-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
| | | gap: 20px; |
| | | } |
| | | |
| | | .result-item { |
| | | text-align: center; |
| | | padding: 15px; |
| | | border: 1px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | background: #fff; |
| | | } |
| | | |
| | | .result-item img { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .result-content { |
| | | font-size: 12px; |
| | | color: #666; |
| | | margin: 10px 0; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | </style> |