| | |
| | | <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-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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |