From 465c270f98a50d31b8146a5a91a2539211f67a2e Mon Sep 17 00:00:00 2001 From: gaoluyang <2820782392@qq.com> Date: 星期一, 18 八月 2025 13:58:48 +0800 Subject: [PATCH] 中强恒兴销售管理,采购管理页面添加 --- src/views/salesManagement/customerManagement/index.vue | 423 ++++ src/views/procurementManagement/qualityInspection/index.vue | 285 ++ src/views/procurementManagement/purchaseOrder/index.vue | 188 + src/views/procurementManagement/arrivalManagement/index.vue | 189 + src/components/PIMTable/PIMTable.vue | 3 src/views/procurementManagement/index.vue | 378 +++ src/views/tool/qrCodeDemo/index.vue | 405 ++++ src/views/salesManagement/paymentShipping/index.vue | 534 +++++ src/components/QRCodeGenerator/index.vue | 541 +++++ src/views/procurementManagement/priceManagement/index.vue | 276 ++ src/views/tool/qrCodeGenerator/index.vue | 433 ++++ src/views/productManagement/productIdentifier/index.vue | 708 +++++++ src/views/salesManagement/salespersonManagement/index.vue | 392 +++ src/views/salesManagement/orderManagement/index.vue | 495 ++++ src/views/tool/qrCodeSimple/index.vue | 526 +++++ src/views/procurementManagement/returnManagement/index.vue | 234 ++ 16 files changed, 6,007 insertions(+), 3 deletions(-) diff --git a/src/components/PIMTable/PIMTable.vue b/src/components/PIMTable/PIMTable.vue index 955173d..c0a67a6 100644 --- a/src/components/PIMTable/PIMTable.vue +++ b/src/components/PIMTable/PIMTable.vue @@ -124,7 +124,6 @@ <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" @@ -149,7 +148,6 @@ (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" @@ -178,7 +176,6 @@ :show-file-list="false" > <el-button - :size="o.size ? o.size : 'small'" link type="primary" :disabled="o.disabled ? o.disabled(scope.row) : false" diff --git a/src/components/QRCodeGenerator/index.vue b/src/components/QRCodeGenerator/index.vue new file mode 100644 index 0000000..1708130 --- /dev/null +++ b/src/components/QRCodeGenerator/index.vue @@ -0,0 +1,541 @@ +<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="璇疯緭鍏ュ墠缂�锛屽锛歅ROD_"></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) => { + // 灏哹ase64杞崲涓篵lob + 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> diff --git a/src/views/procurementManagement/arrivalManagement/index.vue b/src/views/procurementManagement/arrivalManagement/index.vue new file mode 100644 index 0000000..b5f1ce4 --- /dev/null +++ b/src/views/procurementManagement/arrivalManagement/index.vue @@ -0,0 +1,189 @@ +<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: '渚涘簲鍟咥', + status: 'received', + arrivalQuantity: 250, + arrivalTime: '2024-12-01 15:30:00', + remark: '姝e父鍒拌揣' + } +] + +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> diff --git a/src/views/procurementManagement/index.vue b/src/views/procurementManagement/index.vue new file mode 100644 index 0000000..47ec043 --- /dev/null +++ b/src/views/procurementManagement/index.vue @@ -0,0 +1,378 @@ +<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: '浠锋牸璋冩暣锛氬晢鍝丅 浠� 楼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> diff --git a/src/views/procurementManagement/priceManagement/index.vue b/src/views/procurementManagement/priceManagement/index.vue new file mode 100644 index 0000000..12f785b --- /dev/null +++ b/src/views/procurementManagement/priceManagement/index.vue @@ -0,0 +1,276 @@ +<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="渚涘簲鍟咥" value="渚涘簲鍟咥" /> + <el-option label="渚涘簲鍟咮" value="渚涘簲鍟咮" /> + <el-option label="渚涘簲鍟咰" value="渚涘簲鍟咰" /> + </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: '渚涘簲鍟咥', + 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: '渚涘簲鍟咮', + 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> diff --git a/src/views/procurementManagement/purchaseOrder/index.vue b/src/views/procurementManagement/purchaseOrder/index.vue new file mode 100644 index 0000000..73b4e84 --- /dev/null +++ b/src/views/procurementManagement/purchaseOrder/index.vue @@ -0,0 +1,188 @@ +<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="渚涘簲鍟咥" value="渚涘簲鍟咥" /> + <el-option label="渚涘簲鍟咮" value="渚涘簲鍟咮" /> + </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: '渚涘簲鍟咥', + 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> diff --git a/src/views/procurementManagement/qualityInspection/index.vue b/src/views/procurementManagement/qualityInspection/index.vue new file mode 100644 index 0000000..7225795 --- /dev/null +++ b/src/views/procurementManagement/qualityInspection/index.vue @@ -0,0 +1,285 @@ +<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: '渚涘簲鍟咥', + 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> diff --git a/src/views/procurementManagement/returnManagement/index.vue b/src/views/procurementManagement/returnManagement/index.vue new file mode 100644 index 0000000..c72fbaf --- /dev/null +++ b/src/views/procurementManagement/returnManagement/index.vue @@ -0,0 +1,234 @@ +<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: '渚涘簲鍟咥', + 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> diff --git a/src/views/productManagement/productIdentifier/index.vue b/src/views/productManagement/productIdentifier/index.vue new file mode 100644 index 0000000..59d8f90 --- /dev/null +++ b/src/views/productManagement/productIdentifier/index.vue @@ -0,0 +1,708 @@ +<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>姝e湪鐢熸垚浜岀淮鐮�...</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: '宸ヤ笟浼犳劅鍣ˋ鍨�', + 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: '鏁版嵁閲囬泦鍣–鍨�', + 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> diff --git a/src/views/salesManagement/customerManagement/index.vue b/src/views/salesManagement/customerManagement/index.vue new file mode 100644 index 0000000..131124b --- /dev/null +++ b/src/views/salesManagement/customerManagement/index.vue @@ -0,0 +1,423 @@ +<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> diff --git a/src/views/salesManagement/orderManagement/index.vue b/src/views/salesManagement/orderManagement/index.vue new file mode 100644 index 0000000..54148b9 --- /dev/null +++ b/src/views/salesManagement/orderManagement/index.vue @@ -0,0 +1,495 @@ +<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> diff --git a/src/views/salesManagement/paymentShipping/index.vue b/src/views/salesManagement/paymentShipping/index.vue new file mode 100644 index 0000000..20c5b40 --- /dev/null +++ b/src/views/salesManagement/paymentShipping/index.vue @@ -0,0 +1,534 @@ +<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> diff --git a/src/views/salesManagement/salespersonManagement/index.vue b/src/views/salesManagement/salespersonManagement/index.vue new file mode 100644 index 0000000..894d631 --- /dev/null +++ b/src/views/salesManagement/salespersonManagement/index.vue @@ -0,0 +1,392 @@ +<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> diff --git a/src/views/tool/qrCodeDemo/index.vue b/src/views/tool/qrCodeDemo/index.vue new file mode 100644 index 0000000..d62af71 --- /dev/null +++ b/src/views/tool/qrCodeDemo/index.vue @@ -0,0 +1,405 @@ +<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>鍨嬪彿锛歋ENSOR-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>锛歋EC_浜у搧缂栫爜_鎵规鍙穇鏃堕棿鎴砡闅忔満鏁�</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鍨嬪彿锛歋ENSOR-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> diff --git a/src/views/tool/qrCodeGenerator/index.vue b/src/views/tool/qrCodeGenerator/index.vue new file mode 100644 index 0000000..5fff74a --- /dev/null +++ b/src/views/tool/qrCodeGenerator/index.vue @@ -0,0 +1,433 @@ +<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="璇疯緭鍏ュ墠缂�锛屽锛歅ROD_"></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 = `姝e湪鐢熸垚绗� ${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 = '姝e湪鍙栨秷...' + 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> diff --git a/src/views/tool/qrCodeSimple/index.vue b/src/views/tool/qrCodeSimple/index.vue new file mode 100644 index 0000000..6f84fce --- /dev/null +++ b/src/views/tool/qrCodeSimple/index.vue @@ -0,0 +1,526 @@ +<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>閫傜敤浜庝骇鍝侀槻浼�佹枃妗i獙璇佺瓑鍦烘櫙</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> -- Gitblit v1.9.3