From 653120dde7588e5be464166d3c316dc80227dbb0 Mon Sep 17 00:00:00 2001 From: spring <2396852758@qq.com> Date: 星期一, 01 九月 2025 16:21:00 +0800 Subject: [PATCH] Merge remote-tracking branch 'refs/remotes/origin/dev_ZQHX' into dev --- src/views/procurementManagement/qualityInspection/index.vue | 285 + src/views/procurementManagement/purchaseOrder/index.vue | 188 + src/views/procurementManagement/index.vue | 378 ++ src/views/salesManagement/paymentShipping/index.vue | 534 +++ src/views/equipmentManagement/ledger/Form.vue | 21 src/views/productManagement/productIdentifier/index.vue | 708 ++++ src/views/collaborativeApproval/sealManagement/index.vue | 588 +++ src/views/salesManagement/salespersonManagement/index.vue | 392 ++ .env.staging | 4 src/layout/components/Sidebar/Logo.vue | 2 index.html | 6 src/layout/components/Sidebar/index.vue | 1 src/views/collaborativeApproval/planTemplate/index.vue | 750 +++++ src/views/qualityManagement/visualization/qualityDashboard.vue | 307 ++ src/views/equipmentManagement/ledger/index.vue | 16 vite.config.js | 4 src/views/collaborativeApproval/knowledgeBase/index.vue | 4 src/views/reportAnalysis/reportManagement.vue | 733 ++++ src/views/collaborativeApproval/attendanceManagement/index.vue | 16 src/views/salesManagement/customerManagement/index.vue | 423 ++ src/views/collaborativeApproval/meetingBoard/index.vue | 8 src/views/procurementManagement/arrivalManagement/index.vue | 189 + src/components/PIMTable/PIMTable.vue | 3 src/views/equipmentManagement/brand/index.vue | 217 + src/components/QRCodeGenerator/index.vue | 541 +++ src/views/procurementManagement/priceManagement/index.vue | 276 + .env.development | 4 /dev/null | 22 src/views/collaborativeApproval/notificationManagement/index.vue | 6 src/views/collaborativeApproval/warningSystem/index.vue | 4 src/views/reportAnalysis/reportManagement/index.vue | 716 ++++ src/main.js | 2 package.json | 2 .env.production | 6 src/api/equipmentManagement/brand.js | 93 src/views/salesManagement/orderManagement/index.vue | 495 +++ src/views/collaborativeApproval/officeSupplies/index.vue | 670 ++++ src/views/qualityManagement/nonconformingManagement/index.vue | 7 src/views/procurementManagement/returnManagement/index.vue | 234 + 39 files changed, 8,792 insertions(+), 63 deletions(-) diff --git a/.env.development b/.env.development index aeebf98..6e4d273 100644 --- a/.env.development +++ b/.env.development @@ -1,8 +1,8 @@ # 椤甸潰鏍囬 -VITE_APP_TITLE = 鍩烘櫤娌逛簳绠$悊绯荤粺 +VITE_APP_TITLE = 涓己鎭掑叴绠$悊绯荤粺 # 寮�鍙戠幆澧冮厤缃� VITE_APP_ENV = 'development' -# 鍩烘櫤娌逛簳绠$悊绯荤粺/寮�鍙戠幆澧� +# 涓己鎭掑叴绠$悊绯荤粺/寮�鍙戠幆澧� VITE_APP_BASE_API = '/dev-api' diff --git a/.env.production b/.env.production index 666be0e..939700e 100644 --- a/.env.production +++ b/.env.production @@ -1,11 +1,11 @@ # 椤甸潰鏍囬 -VITE_APP_TITLE = MOM锛堝埗閫犺繍钀ョ鐞嗙郴缁燂級 +VITE_APP_TITLE = 涓己鎭掑叴绠$悊绯荤粺 # 鐢熶骇鐜閰嶇疆 VITE_APP_ENV = 'production' -# MIS绯荤粺锛堢鐞嗕俊鎭郴缁燂級/鐢熶骇鐜 +# 涓己鎭掑叴绠$悊绯荤粺/鐢熶骇鐜 VITE_APP_BASE_API = '/prod-api' # 鏄惁鍦ㄦ墦鍖呮椂寮�鍚帇缂╋紝鏀寔 gzip 鍜� brotli -VITE_BUILD_COMPRESS = gzip +VITE_BUILD_COMPRESS = gzip \ No newline at end of file diff --git a/.env.staging b/.env.staging index f4d770d..ae0e938 100644 --- a/.env.staging +++ b/.env.staging @@ -1,10 +1,10 @@ # 椤甸潰鏍囬 -VITE_APP_TITLE = 鍩烘櫤娌逛簳绠$悊绯荤粺 +VITE_APP_TITLE = 涓己鎭掑叴绠$悊绯荤粺 # 鐢熶骇鐜閰嶇疆 VITE_APP_ENV = 'staging' -# 鍩烘櫤娌逛簳绠$悊绯荤粺/鐢熶骇鐜 +# 涓己鎭掑叴绠$悊绯荤粺/鐢熶骇鐜 VITE_APP_BASE_API = '/stage-api' # 鏄惁鍦ㄦ墦鍖呮椂寮�鍚帇缂╋紝鏀寔 gzip 鍜� brotli diff --git a/index.html b/index.html index b9fd2a5..4c08c54 100644 --- a/index.html +++ b/index.html @@ -6,8 +6,8 @@ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="renderer" content="webkit"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> - <link rel="icon" href="/favicon.ico"> - <title>MOM锛堝埗閫犺繍钀ョ鐞嗙郴缁燂級</title> + <link rel="icon" href="/ZQHXico.ico"> + <title>涓己鎭掑叴绠$悊绯荤粺</title> <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]--> <style> html, @@ -212,4 +212,4 @@ <script type="module" src="/src/main.js"></script> </body> -</html> +</html> \ No newline at end of file diff --git a/package.json b/package.json index 3fd8e3d..dfb3cec 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ruoyi", "version": "3.8.9", - "description": "MES", + "description": "涓己鎭掑叴绠$悊绯荤粺", "author": "鑻ヤ緷", "license": "MIT", "type": "module", diff --git a/src/api/equipmentManagement/brand.js b/src/api/equipmentManagement/brand.js new file mode 100644 index 0000000..040cb38 --- /dev/null +++ b/src/api/equipmentManagement/brand.js @@ -0,0 +1,93 @@ +// 璁惧鍝佺墝绠$悊 - 鏈湴鍋囨暟鎹� API锛堜娇鐢� localStorage 鎸佷箙鍖栵級 + +const STORAGE_KEY = 'EQUIPMENT_BRANDS'; + +function readStore() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) { + const parsed = JSON.parse(raw); + if (Array.isArray(parsed)) return parsed; + } + } catch (e) { + // ignore + } + // 鍒濆鍖栦竴浜涚ず渚嬫暟鎹� + const initial = [ + { id: 1, name: '瑗块棬瀛�', country: '寰峰浗', description: '宸ヤ笟鑷姩鍖栦笌鐢垫皵宸ョ▼鍝佺墝', createdAt: Date.now() - 86400000 * 10 }, + { id: 2, name: '鏂借�愬痉', country: '娉曞浗', description: '鑳芥簮绠$悊涓庤嚜鍔ㄥ寲', createdAt: Date.now() - 86400000 * 7 }, + { id: 3, name: '涓夎彵鐢垫満', country: '鏃ユ湰', description: '鐢垫皵涓庤嚜鍔ㄥ寲璁惧', createdAt: Date.now() - 86400000 * 3 }, + ]; + localStorage.setItem(STORAGE_KEY, JSON.stringify(initial)); + return initial; +} + +function writeStore(list) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(list)); +} + +function nextId(list) { + const maxId = list.reduce((max, item) => Math.max(max, Number(item.id) || 0), 0); + return maxId + 1; +} + +export function getBrandPage(params = {}) { + const { current = 1, size = 10, name } = params; + const list = readStore(); + let filtered = list; + if (name) { + const kw = String(name).trim(); + filtered = filtered.filter((b) => + (b.name && b.name.includes(kw)) || (b.country && b.country.includes(kw)) + ); + } + const start = (current - 1) * size; + const end = start + Number(size); + const records = filtered.slice(start, end); + return Promise.resolve({ + code: 200, + data: { + total: filtered.length, + records, + }, + msg: 'ok', + }); +} + +export function getBrandById(id) { + const list = readStore(); + const item = list.find((i) => String(i.id) === String(id)); + return Promise.resolve({ code: 200, data: item || null, msg: 'ok' }); +} + +export function addBrand(data) { + const list = readStore(); + const item = { ...data }; + item.id = nextId(list); + item.createdAt = Date.now(); + list.unshift(item); + writeStore(list); + return Promise.resolve({ code: 200, data: item, msg: '鏂板鎴愬姛' }); +} + +export function editBrand(data) { + const list = readStore(); + const index = list.findIndex((i) => String(i.id) === String(data.id)); + if (index !== -1) { + list[index] = { ...list[index], ...data }; + writeStore(list); + return Promise.resolve({ code: 200, data: list[index], msg: '淇敼鎴愬姛' }); + } + return Promise.resolve({ code: 404, data: null, msg: '鏈壘鍒拌鍝佺墝' }); +} + +export function delBrand(idOrIds) { + const list = readStore(); + const ids = Array.isArray(idOrIds) ? idOrIds.map(String) : [String(idOrIds)]; + const newList = list.filter((i) => !ids.includes(String(i.id))); + writeStore(newList); + return Promise.resolve({ code: 200, data: null, msg: '鍒犻櫎鎴愬姛' }); +} + + + 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/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue index 2f5d0a9..bdb0daa 100644 --- a/src/layout/components/Sidebar/Logo.vue +++ b/src/layout/components/Sidebar/Logo.vue @@ -16,7 +16,7 @@ <script setup> import { ref, computed, onMounted, watch } from 'vue' import useUserStore from '@/store/modules/user' -import defaultLogo from '@/assets/indexViews/JZYJLogo.png' // 瀵煎叆榛樿logo +import defaultLogo from '@/assets/indexViews/ZQHXLogo.png' // 瀵煎叆榛樿logo defineProps({ collapse: { diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue index 33b2e85..9044945 100644 --- a/src/layout/components/Sidebar/index.vue +++ b/src/layout/components/Sidebar/index.vue @@ -30,7 +30,6 @@ const sideTheme = computed(() => settingsStore.sideTheme) const theme = computed(() => settingsStore.theme) const isCollapse = computed(() => !appStore.sidebar.opened) -console.log(44444, settingsStore.isDark, sideTheme.value) // 鑾峰彇鑿滃崟鑳屾櫙鑹� const getMenuBackground = computed(() => { diff --git a/src/main.js b/src/main.js index b63769d..333d79d 100644 --- a/src/main.js +++ b/src/main.js @@ -76,7 +76,7 @@ app.config.globalProperties.addDateRange = addDateRange; app.config.globalProperties.selectDictLabel = selectDictLabel; app.config.globalProperties.selectDictLabels = selectDictLabels; -app.config.globalProperties.javaApi = "http://10.136.12.71:8014"; +app.config.globalProperties.javaApi = "http://114.132.189.42:8080"; app.config.globalProperties.HaveJson = (val) => { return JSON.parse(JSON.stringify(val)); }; diff --git a/src/views/collaborativeApproval/approvalProcess/index1.vue b/src/views/collaborativeApproval/approvalProcess/index1.vue deleted file mode 100644 index c46c68a..0000000 --- a/src/views/collaborativeApproval/approvalProcess/index1.vue +++ /dev/null @@ -1,22 +0,0 @@ -<template> - <div class="container"> - <!-- 寮曞叆index.vue缁勪欢骞朵紶閫掑弬鏁� --> - <ApprovalProcessIndex :approveType="1" /> - </div> -</template> - -<script setup> -import ApprovalProcessIndex from './index.vue' - -// 瀹氫箟缁勪欢鍚嶇О -defineOptions({ - name: 'ApprovalProcessIndex1' -}) -</script> - -<style scoped> -.container { - width: 100%; - height: 100%; -} -</style> diff --git a/src/views/collaborativeApproval/approvalProcess/index2.vue b/src/views/collaborativeApproval/approvalProcess/index2.vue deleted file mode 100644 index 7c15c3e..0000000 --- a/src/views/collaborativeApproval/approvalProcess/index2.vue +++ /dev/null @@ -1,22 +0,0 @@ -<template> - <div class="container"> - <!-- 寮曞叆index.vue缁勪欢骞朵紶閫掑弬鏁� --> - <ApprovalProcessIndex :approveType="2" /> - </div> -</template> - -<script setup> -import ApprovalProcessIndex from './index.vue' - -// 瀹氫箟缁勪欢鍚嶇О -defineOptions({ - name: 'ApprovalProcessIndex1' -}) -</script> - -<style scoped> -.container { - width: 100%; - height: 100%; -} -</style> \ No newline at end of file diff --git a/src/views/collaborativeApproval/approvalProcess/index3.vue b/src/views/collaborativeApproval/approvalProcess/index3.vue deleted file mode 100644 index 3afb6f5..0000000 --- a/src/views/collaborativeApproval/approvalProcess/index3.vue +++ /dev/null @@ -1,22 +0,0 @@ -<template> - <div class="container"> - <!-- 寮曞叆index.vue缁勪欢骞朵紶閫掑弬鏁� --> - <ApprovalProcessIndex :approveType="3" /> - </div> -</template> - -<script setup> -import ApprovalProcessIndex from './index.vue' - -// 瀹氫箟缁勪欢鍚嶇О -defineOptions({ - name: 'ApprovalProcessIndex1' -}) -</script> - -<style scoped> -.container { - width: 100%; - height: 100%; -} -</style> \ No newline at end of file diff --git a/src/views/collaborativeApproval/approvalProcess/index4.vue b/src/views/collaborativeApproval/approvalProcess/index4.vue deleted file mode 100644 index 77236af..0000000 --- a/src/views/collaborativeApproval/approvalProcess/index4.vue +++ /dev/null @@ -1,22 +0,0 @@ -<template> - <div class="container"> - <!-- 寮曞叆index.vue缁勪欢骞朵紶閫掑弬鏁� --> - <ApprovalProcessIndex :approveType="4" /> - </div> -</template> - -<script setup> -import ApprovalProcessIndex from './index.vue' - -// 瀹氫箟缁勪欢鍚嶇О -defineOptions({ - name: 'ApprovalProcessIndex1' -}) -</script> - -<style scoped> -.container { - width: 100%; - height: 100%; -} -</style> \ No newline at end of file diff --git a/src/views/collaborativeApproval/attendanceManagement/index.vue b/src/views/collaborativeApproval/attendanceManagement/index.vue index e46ff26..7fedb46 100644 --- a/src/views/collaborativeApproval/attendanceManagement/index.vue +++ b/src/views/collaborativeApproval/attendanceManagement/index.vue @@ -25,8 +25,8 @@ </el-table-column> <el-table-column label="鎿嶄綔" fixed="right"> <template #default="scope"> - <el-button type="primary" size="small" @click="openDialog('holiday', 'edit', scope.row)">缂栬緫</el-button> - <el-button type="danger" size="small" @click="deleteItem('holiday', scope.row)">鍒犻櫎</el-button> + <el-button type="primary" link @click="openDialog('holiday', 'edit', scope.row)">缂栬緫</el-button> + <el-button type="danger" link @click="deleteItem('holiday', scope.row)">鍒犻櫎</el-button> </template> </el-table-column> </el-table> @@ -52,8 +52,8 @@ </el-table-column> <el-table-column label="鎿嶄綔" fixed="right"> <template #default="scope"> - <el-button type="primary" size="small" @click="openDialog('annual', 'edit', scope.row)">缂栬緫</el-button> - <el-button type="danger" size="small" @click="deleteItem('annual', scope.row)">鍒犻櫎</el-button> + <el-button type="primary" link @click="openDialog('annual', 'edit', scope.row)">缂栬緫</el-button> + <el-button type="danger" link @click="deleteItem('annual', scope.row)">鍒犻櫎</el-button> </template> </el-table-column> </el-table> @@ -84,8 +84,8 @@ </el-table-column> <el-table-column label="鎿嶄綔" fixed="right"> <template #default="scope"> - <el-button type="primary" size="small" @click="openDialog('overtime', 'edit', scope.row)">缂栬緫</el-button> - <el-button type="danger" size="small" @click="deleteItem('overtime', scope.row)">鍒犻櫎</el-button> + <el-button type="primary" link @click="openDialog('overtime', 'edit', scope.row)">缂栬緫</el-button> + <el-button type="danger" link @click="deleteItem('overtime', scope.row)">鍒犻櫎</el-button> </template> </el-table-column> </el-table> @@ -118,8 +118,8 @@ </el-table-column> <el-table-column label="鎿嶄綔" fixed="right"> <template #default="scope"> - <el-button type="primary" size="small" @click="openDialog('worktime', 'edit', scope.row)">缂栬緫</el-button> - <el-button type="danger" size="small" @click="deleteItem('worktime', scope.row)">鍒犻櫎</el-button> + <el-button type="primary" link @click="openDialog('worktime', 'edit', scope.row)">缂栬緫</el-button> + <el-button type="danger" link @click="deleteItem('worktime', scope.row)">鍒犻櫎</el-button> </template> </el-table-column> </el-table> diff --git a/src/views/collaborativeApproval/knowledgeBase/index.vue b/src/views/collaborativeApproval/knowledgeBase/index.vue index 287972c..279738e 100644 --- a/src/views/collaborativeApproval/knowledgeBase/index.vue +++ b/src/views/collaborativeApproval/knowledgeBase/index.vue @@ -410,7 +410,7 @@ problem: "澶ч鍚堝悓瀹℃壒娴佺▼澶嶆潅锛屽鎵规椂闂撮暱锛屽奖鍝嶄笟鍔¤繘灞�", solution: "寤虹珛缁胯壊閫氶亾锛屽绗﹀悎鏉′欢鐨勫悎鍚岄噰鐢ㄧ畝鍖栧鎵规祦绋嬶紝鐢遍儴闂ㄨ礋璐d汉鐩存帴瀹℃壒锛屽钩鍧囧鎵规椂闂翠粠3澶╃缉鐭嚦1澶�", keyPoints: "缁胯壊閫氶亾鏉′欢,绠�鍖栨祦绋�,瀹℃壒鏉冮檺,鏃堕棿鎺у埗", - creator: "寮犵粡鐞�", + creator: "闄堝織寮�", usageCount: 15, createTime: "2025-01-15 10:30:00" }, @@ -488,7 +488,7 @@ problem: `鍦�${randomScenario}杩囩▼涓亣鍒扮殑闂鎻忚堪...`, solution: `閽堝${randomScenario}鐨勮В鍐虫柟妗堝拰鎿嶄綔姝ラ...`, keyPoints: "鍏抽敭瑕佺偣1,鍏抽敭瑕佺偣2,鍏抽敭瑕佺偣3,鍏抽敭瑕佺偣4", - creator: ["寮犵粡鐞�", "鏉庝富绠�", "鐜嬩笓鍛�", "鍒樻�荤洃"][Math.floor(Math.random() * 4)], + creator: ["闄堝織寮�", "鍒橀泤濠�", "鐜嬪缓鍥�", "璧典附鍗�"][Math.floor(Math.random() * 4)], usageCount: Math.floor(Math.random() * 20) + 1, createTime: now.toLocaleString() }; diff --git a/src/views/collaborativeApproval/meetingBoard/index.vue b/src/views/collaborativeApproval/meetingBoard/index.vue index f60287a..b6b803b 100644 --- a/src/views/collaborativeApproval/meetingBoard/index.vue +++ b/src/views/collaborativeApproval/meetingBoard/index.vue @@ -164,8 +164,8 @@ startTime: '2025-01-15 09:00:00', endTime: '2025-01-15 10:30:00', location: '浼氳瀹', - host: '寮犵粡鐞�', - participants: ['寮犵粡鐞�', '鏉庡伐绋嬪笀', '鐜嬭璁″笀', '璧垫祴璇曞憳'], + host: '闄堝織寮�', + participants: ['闄堝織寮�', '鍒橀泤濠�', '鐜嬪缓鍥�', '璧典附鍗�'], agenda: [ { time: '09:00-09:15', content: '涓婂懆宸ヤ綔鎬荤粨', status: 'completed' }, { time: '09:15-09:45', content: '鏈懆寮�鍙戣鍒�', status: 'active' }, @@ -180,8 +180,8 @@ startTime: '2025-01-15 14:00:00', endTime: '2025-01-15 15:00:00', location: '绾夸笂浼氳', - host: '闄堟�荤洃', - participants: ['闄堟�荤洃', '鍒樹骇鍝佺粡鐞�', '瀛欏鎴风粡鐞�', '瀹㈡埛浠h〃'], + host: '闄堝織寮�', + participants: ['闄堝織寮�', '鍒橀泤濠�', '瀛欐槑鍗�', '瀹㈡埛浠h〃'], agenda: [ { time: '14:00-14:20', content: '闇�姹傝儗鏅粙缁�', status: 'pending' }, { time: '14:20-14:40', content: '鍔熻兘闇�姹傚垎鏋�', status: 'pending' }, diff --git a/src/views/collaborativeApproval/notificationManagement/index.vue b/src/views/collaborativeApproval/notificationManagement/index.vue index 3138e51..e75103f 100644 --- a/src/views/collaborativeApproval/notificationManagement/index.vue +++ b/src/views/collaborativeApproval/notificationManagement/index.vue @@ -664,9 +664,9 @@ console.error('鑾峰彇鍛樺伐鍒楄〃澶辫触:', error); // 濡傛灉鎺ュ彛閮藉け璐ワ紝浣跨敤榛樿鏁版嵁 employees.value = [ - { label: "寮犱笁", value: "001", dept: "鎶�鏈儴", phone: "13800138001", email: "zhangsan@company.com", status: "0" }, - { label: "鏉庡洓", value: "002", dept: "閿�鍞儴", phone: "13800138002", email: "lisi@company.com", status: "0" }, - { label: "鐜嬩簲", value: "003", dept: "浜轰簨閮�", phone: "13800138003", email: "wangwu@company.com", status: "0" } + { label: "闄堝織寮�", value: "001", dept: "鎶�鏈儴", phone: "13800138001", email: "chenzhiqiang@company.com", status: "0" }, + { label: "鍒橀泤濠�", value: "002", dept: "閿�鍞儴", phone: "13800138002", email: "liuyating@company.com", status: "0" }, + { label: "鐜嬪缓鍥�", value: "003", dept: "浜轰簨閮�", phone: "13800138003", email: "wangjianguo@company.com", status: "0" } ]; } finally { employeesLoading.value = false; diff --git a/src/views/collaborativeApproval/officeSupplies/index.vue b/src/views/collaborativeApproval/officeSupplies/index.vue new file mode 100644 index 0000000..1717f98 --- /dev/null +++ b/src/views/collaborativeApproval/officeSupplies/index.vue @@ -0,0 +1,670 @@ +<template> + <div class="app-container"> + <el-card class="box-card"> + <template #header> + <div class="card-header"> + <span>鍔炲叕鐗╄祫鐢宠绠$悊</span> + <el-button type="primary" @click="showApplyDialog = true"> + <el-icon><Plus /></el-icon> + 鏂板缓鐢宠 + </el-button> + </div> + </template> + + <!-- 鎼滅储鍖哄煙 --> + <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch"> + <el-form-item label="鐢宠缂栧彿" prop="applyNo"> + <el-input + v-model="queryParams.applyNo" + placeholder="璇疯緭鍏ョ敵璇风紪鍙�" + clearable + style="width: 200px" + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="鐢宠浜�" prop="applicant"> + <el-input + v-model="queryParams.applicant" + placeholder="璇疯緭鍏ョ敵璇蜂汉" + clearable + style="width: 200px" + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="鐢宠鐘舵��" prop="status"> + <el-select v-model="queryParams.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 200px"> + <el-option label="寰呭鎵�" value="pending" /> + <el-option label="宸查�氳繃" value="approved" /> + <el-option label="宸叉嫆缁�" value="rejected" /> + <el-option label="宸插彂鏀�" value="issued" /> + </el-select> + </el-form-item> + <el-form-item> + <el-button type="primary" @click="handleQuery"> + <el-icon><Search /></el-icon> + 鎼滅储 + </el-button> + <el-button @click="resetQuery"> + <el-icon><Refresh /></el-icon> + 閲嶇疆 + </el-button> + </el-form-item> + <el-form-item> + <el-button type="primary" @click="handleExport"> + <el-icon><Download /></el-icon> + 瀵煎嚭 + </el-button> + <el-button type="success" @click="handleBatchApprove" :disabled="multipleSelection.length === 0"> + <el-icon><Check /></el-icon> + 鎵归噺瀹℃壒 + </el-button> + </el-form-item> + </el-form> + + <!-- 琛ㄦ牸鍖哄煙 --> + <el-table + v-loading="loading" + :data="suppliesList" + @selection-change="handleSelectionChange" + style="width: 100%" + > + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="鐢宠缂栧彿" align="center" prop="applyNo" width="180" /> + <el-table-column label="鐢宠浜�" align="center" prop="applicant" width="120" /> + <el-table-column label="閮ㄩ棬" align="center" prop="department" width="120" /> + <el-table-column label="鐗╄祫绫诲瀷" align="center" prop="supplyType" width="120" /> + <el-table-column label="鐢宠鏁伴噺" align="center" prop="quantity" width="100" /> + <el-table-column label="鐢宠鍘熷洜" align="center" prop="reason" min-width="200" show-overflow-tooltip /> + <el-table-column label="鐢宠鐘舵��" align="center" prop="status" width="100"> + <template #default="scope"> + <el-tag :type="getStatusType(scope.row.status)"> + {{ getStatusText(scope.row.status) }} + </el-tag> + </template> + </el-table-column> + <el-table-column label="鐢宠鏃堕棿" align="center" prop="applyTime" width="180" /> + <el-table-column label="瀹℃壒浜�" align="center" prop="approver" width="120" /> + <el-table-column label="瀹℃壒鏃堕棿" align="center" prop="approveTime" width="180" /> + <el-table-column label="鍙戞斁鏃堕棿" align="center" prop="issueTime" width="180" /> + <el-table-column label="鎿嶄綔" align="center" fixed="right" class-name="small-padding fixed-width" width="200"> + <template #default="scope"> + <el-button + v-if="scope.row.status === 'pending'" + type="primary" + link + @click="handleApprove(scope.row)" + > + 瀹℃壒 + </el-button> + <el-button + v-if="scope.row.status === 'approved'" + type="success" + link + @click="handleIssue(scope.row)" + > + 鍙戞斁 + </el-button> + <el-button + type="info" + link + @click="handleDetail(scope.row)" + > + 璇︽儏 + </el-button> + <el-button + v-if="scope.row.status === 'pending'" + type="danger" + link + @click="handleDelete(scope.row)" + > + 鍒犻櫎 + </el-button> + </template> + </el-table-column> + </el-table> + + <!-- 鍒嗛〉 --> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </el-card> + + <!-- 鐢宠瀵硅瘽妗� --> + <el-dialog + v-model="showApplyDialog" + title="鍔炲叕鐗╄祫鐢宠" + width="600px" + append-to-body + > + <el-form ref="applyFormRef" :model="applyForm" :rules="applyRules" label-width="100px"> + <el-form-item label="鐗╄祫绫诲瀷" prop="supplyType"> + <el-select v-model="applyForm.supplyType" placeholder="璇烽�夋嫨鐗╄祫绫诲瀷" style="width: 100%"> + <el-option label="鍔炲叕鐢ㄥ搧" value="office" /> + <el-option label="鐢靛瓙璁惧" value="electronic" /> + <el-option label="娓呮磥鐢ㄥ搧" value="cleaning" /> + <el-option label="鍏朵粬" value="other" /> + </el-select> + </el-form-item> + <el-form-item label="鍏蜂綋鐗╁搧" prop="itemName"> + <el-input v-model="applyForm.itemName" placeholder="璇疯緭鍏ュ叿浣撶墿鍝佸悕绉�" /> + </el-form-item> + <el-form-item label="鐢宠鏁伴噺" prop="quantity"> + <el-input-number v-model="applyForm.quantity" :min="1" :max="999" style="width: 100%" /> + </el-form-item> + <el-form-item label="鐢宠鍘熷洜" prop="reason"> + <el-input + v-model="applyForm.reason" + type="textarea" + :rows="3" + placeholder="璇疯緭鍏ョ敵璇峰師鍥�" + /> + </el-form-item> + <el-form-item label="绱ф�ョ▼搴�" prop="urgency"> + <el-radio-group v-model="applyForm.urgency"> + <el-radio label="normal">鏅��</el-radio> + <el-radio label="urgent">绱ф��</el-radio> + <el-radio label="very_urgent">闈炲父绱ф��</el-radio> + </el-radio-group> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="showApplyDialog = false">鍙� 娑�</el-button> + <el-button type="primary" @click="submitApply">纭� 瀹�</el-button> + </div> + </template> + </el-dialog> + + <!-- 瀹℃壒瀵硅瘽妗� --> + <el-dialog + v-model="showApproveDialog" + title="瀹℃壒鐢宠" + width="500px" + append-to-body + > + <el-form ref="approveFormRef" :model="approveForm" :rules="approveRules" label-width="100px"> + <el-form-item label="瀹℃壒缁撴灉" prop="approveResult"> + <el-radio-group v-model="approveForm.approveResult"> + <el-radio label="approved">閫氳繃</el-radio> + <el-radio label="rejected">鎷掔粷</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="瀹℃壒鎰忚" prop="approveComment"> + <el-input + v-model="approveForm.approveComment" + type="textarea" + :rows="3" + placeholder="璇疯緭鍏ュ鎵规剰瑙�" + /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="showApproveDialog = false">鍙� 娑�</el-button> + <el-button type="primary" @click="submitApprove">纭� 瀹�</el-button> + </div> + </template> + </el-dialog> + + <!-- 璇︽儏瀵硅瘽妗� --> + <el-dialog + v-model="showDetailDialog" + title="鐢宠璇︽儏" + width="700px" + append-to-body + > + <el-descriptions :column="2" border> + <el-descriptions-item label="鐢宠缂栧彿">{{ currentDetail.applyNo }}</el-descriptions-item> + <el-descriptions-item label="鐢宠浜�">{{ currentDetail.applicant }}</el-descriptions-item> + <el-descriptions-item label="閮ㄩ棬">{{ currentDetail.department }}</el-descriptions-item> + <el-descriptions-item label="鐗╄祫绫诲瀷">{{ currentDetail.supplyType }}</el-descriptions-item> + <el-descriptions-item label="鍏蜂綋鐗╁搧">{{ currentDetail.itemName }}</el-descriptions-item> + <el-descriptions-item label="鐢宠鏁伴噺">{{ currentDetail.quantity }}</el-descriptions-item> + <el-descriptions-item label="鐢宠鍘熷洜" :span="2">{{ currentDetail.reason }}</el-descriptions-item> + <el-descriptions-item label="鐢宠鐘舵��"> + <el-tag :type="getStatusType(currentDetail.status)"> + {{ getStatusText(currentDetail.status) }} + </el-tag> + </el-descriptions-item> + <el-descriptions-item label="鐢宠鏃堕棿">{{ currentDetail.applyTime }}</el-descriptions-item> + <el-descriptions-item label="瀹℃壒浜�">{{ currentDetail.approver || '-' }}</el-descriptions-item> + <el-descriptions-item label="瀹℃壒鏃堕棿">{{ currentDetail.approveTime || '-' }}</el-descriptions-item> + <el-descriptions-item label="瀹℃壒鎰忚" :span="2">{{ currentDetail.approveComment || '-' }}</el-descriptions-item> + <el-descriptions-item label="鍙戞斁鏃堕棿">{{ currentDetail.issueTime || '-' }}</el-descriptions-item> + <el-descriptions-item label="鍙戞斁浜�">{{ currentDetail.issuer || '-' }}</el-descriptions-item> + </el-descriptions> + </el-dialog> + </div> +</template> + +<script setup> +import { ref, reactive, onMounted } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import { Plus, Search, Refresh, Download, Check } from '@element-plus/icons-vue' + +// 鍝嶅簲寮忔暟鎹� +const loading = ref(false) +const showSearch = ref(true) +const showApplyDialog = ref(false) +const showApproveDialog = ref(false) +const showDetailDialog = ref(false) +const multipleSelection = ref([]) +const total = ref(0) +const suppliesList = ref([]) +const currentDetail = ref({}) + +// 鏌ヨ鍙傛暟 +const queryParams = reactive({ + pageNum: 1, + pageSize: 10, + applyNo: '', + applicant: '', + status: '' +}) + +// 鐢宠琛ㄥ崟 +const applyForm = reactive({ + supplyType: '', + itemName: '', + quantity: 1, + reason: '', + urgency: 'normal' +}) + +// 瀹℃壒琛ㄥ崟 +const approveForm = reactive({ + approveResult: 'approved', + approveComment: '' +}) + +// 琛ㄥ崟鏍¢獙瑙勫垯 +const applyRules = { + supplyType: [{ required: true, message: '璇烽�夋嫨鐗╄祫绫诲瀷', trigger: 'change' }], + itemName: [{ required: true, message: '璇疯緭鍏ュ叿浣撶墿鍝佸悕绉�', trigger: 'blur' }], + quantity: [{ required: true, message: '璇疯緭鍏ョ敵璇锋暟閲�', trigger: 'blur' }], + reason: [{ required: true, message: '璇疯緭鍏ョ敵璇峰師鍥�', trigger: 'blur' }] +} + +const approveRules = { + approveResult: [{ required: true, message: '璇烽�夋嫨瀹℃壒缁撴灉', trigger: 'change' }], + approveComment: [{ required: true, message: '璇疯緭鍏ュ鎵规剰瑙�', trigger: 'blur' }] +} + +// 鍋囨暟鎹� +const mockData = [ + { + id: 1, + applyNo: 'WS20241201001', + applicant: '闄堝織寮�', + department: '鎶�鏈儴', + supplyType: '鍔炲叕鐢ㄥ搧', + itemName: 'A4鎵撳嵃绾�', + quantity: 10, + reason: '鏃ュ父鍔炲叕鎵撳嵃闇�瑕�', + status: 'pending', + applyTime: '2025-12-01 09:30:00', + approver: '', + approveTime: '', + approveComment: '', + issueTime: '', + issuer: '' + }, + { + id: 2, + applyNo: 'WS20241201002', + applicant: '鍒橀泤濠�', + department: '浜轰簨閮�', + supplyType: '鐢靛瓙璁惧', + itemName: '鏃犵嚎榧犳爣', + quantity: 2, + reason: '鏂板憳宸ュ叆鑱岄厤澶�', + status: 'approved', + applyTime: '2025-12-01 10:15:00', + approver: '鐜嬪缓鍥�', + approveTime: '2025-12-01 14:20:00', + approveComment: '鍚屾剰鐢宠锛岃鍙婃椂鍙戞斁', + issueTime: '', + issuer: '' + }, + { + id: 3, + applyNo: 'WS20241201003', + applicant: '鐜嬪缓鍥�', + department: '璐㈠姟閮�', + supplyType: '娓呮磥鐢ㄥ搧', + itemName: '娲楁墜娑�', + quantity: 5, + reason: '鍔炲叕瀹ゆ竻娲佺敤鍝佽ˉ鍏�', + status: 'issued', + applyTime: '2025-12-01 11:00:00', + approver: '鍒橀泤濠�', + approveTime: '2025-12-01 15:30:00', + approveComment: '鍚屾剰鐢宠', + issueTime: '2025-12-01 16:00:00', + issuer: '閽变紵鏄�' + }, + { + id: 4, + applyNo: 'WS20241201004', + applicant: '璧典附鍗�', + department: '甯傚満閮�', + supplyType: '鍏朵粬', + itemName: '鏂囦欢澶�', + quantity: 20, + reason: '椤圭洰璧勬枡鏁寸悊闇�瑕�', + status: 'rejected', + applyTime: '2025-12-01 13:45:00', + approver: '闄堝織寮�', + approveTime: '2025-12-01 17:00:00', + approveComment: '鏁伴噺杩囧锛屽缓璁噺灏戝埌10涓�', + issueTime: '', + issuer: '' + }, + { + id: 5, + applyNo: 'WS20241202001', + applicant: '閽变紵鏄�', + department: '杩愯惀閮�', + supplyType: '鍔炲叕鐢ㄥ搧', + itemName: '绛惧瓧绗�', + quantity: 50, + reason: '閮ㄩ棬鏃ュ父鍔炲叕鐢ㄥ搧琛ュ厖', + status: 'pending', + applyTime: '2025-12-02 08:30:00', + approver: '', + approveTime: '', + approveComment: '', + issueTime: '', + issuer: '' + }, + { + id: 6, + applyNo: 'WS20241202002', + applicant: '瀛欐槑鍗�', + department: '鎶�鏈儴', + supplyType: '鐢靛瓙璁惧', + itemName: '閿洏', + quantity: 3, + reason: '鏂板憳宸ヨ澶囬厤澶�', + status: 'approved', + applyTime: '2025-12-02 14:20:00', + approver: '闄堝織寮�', + approveTime: '2025-12-02 16:00:00', + approveComment: '鍚屾剰鐢宠', + issueTime: '', + issuer: '' + }, + { + id: 7, + applyNo: 'WS20241203001', + applicant: '鍛ㄧ編鐜�', + department: '浜轰簨閮�', + supplyType: '娓呮磥鐢ㄥ搧', + itemName: '绾稿肪', + quantity: 30, + reason: '鍔炲叕鍖哄煙娓呮磥鐢ㄥ搧琛ュ厖', + status: 'issued', + applyTime: '2025-12-03 09:15:00', + approver: '璧典附鍗�', + approveTime: '2025-12-03 10:30:00', + approveComment: '鍚屾剰鐢宠', + issueTime: '2025-12-03 11:00:00', + issuer: '瀛欐槑鍗�' + }, + { + id: 8, + applyNo: 'WS20241203002', + applicant: '鍚村織寮�', + department: '璐㈠姟閮�', + supplyType: '鍏朵粬', + itemName: '璁$畻鍣�', + quantity: 2, + reason: '璐㈠姟鏍哥畻宸ヤ綔闇�瑕�', + status: 'rejected', + applyTime: '2025-12-03 15:45:00', + approver: '鐜嬪缓鍥�', + approveTime: '2025-12-03 17:20:00', + approveComment: '宸叉湁璁$畻鍣紝鏆備笉闇�瑕�', + issueTime: '', + issuer: '' + } +] + +// 鑾峰彇鍒楄〃鏁版嵁 +const getList = () => { + loading.value = true + // 妯℃嫙寮傛璇锋眰 + setTimeout(() => { + let filteredData = [...mockData] + + // 鏍规嵁鏌ヨ鏉′欢杩囨护 + if (queryParams.applyNo) { + filteredData = filteredData.filter(item => + item.applyNo.toLowerCase().includes(queryParams.applyNo.toLowerCase()) + ) + } + if (queryParams.applicant) { + filteredData = filteredData.filter(item => + item.applicant.toLowerCase().includes(queryParams.applicant.toLowerCase()) + ) + } + if (queryParams.status) { + filteredData = filteredData.filter(item => + item.status === queryParams.status + ) + } + + // 鎸夌敵璇锋椂闂村�掑簭鎺掑垪 + filteredData.sort((a, b) => new Date(b.applyTime) - new Date(a.applyTime)) + + total.value = filteredData.length + suppliesList.value = filteredData.slice( + (queryParams.pageNum - 1) * queryParams.pageSize, + queryParams.pageNum * queryParams.pageSize + ) + loading.value = false + }, 500) +} + +// 鏌ヨ +const handleQuery = () => { + queryParams.pageNum = 1 + getList() +} + +// 閲嶇疆鏌ヨ +const resetQuery = () => { + queryParams.applyNo = '' + queryParams.applicant = '' + queryParams.status = '' + handleQuery() +} + +// 澶氶�� +const handleSelectionChange = (selection) => { + multipleSelection.value = selection +} + +// 鑾峰彇鐘舵�佺被鍨� +const getStatusType = (status) => { + const statusMap = { + pending: 'warning', + approved: 'success', + rejected: 'danger', + issued: 'info' + } + return statusMap[status] || 'info' +} + +// 鑾峰彇鐘舵�佹枃鏈� +const getStatusText = (status) => { + const statusMap = { + pending: '寰呭鎵�', + approved: '宸查�氳繃', + rejected: '宸叉嫆缁�', + issued: '宸插彂鏀�' + } + return statusMap[status] || status +} + +// 鎻愪氦鐢宠 +const submitApply = () => { + const newApply = { + id: mockData.length + 1, + applyNo: `WS${new Date().getTime()}`, + applicant: '褰撳墠鐢ㄦ埛', + department: '鎶�鏈儴', + supplyType: applyForm.supplyType, + itemName: applyForm.itemName, + quantity: applyForm.quantity, + reason: applyForm.reason, + status: 'pending', + applyTime: new Date().toLocaleString(), + approver: '', + approveTime: '', + approveComment: '', + issueTime: '', + issuer: '' + } + + mockData.unshift(newApply) + showApplyDialog.value = false + ElMessage.success('鐢宠鎻愪氦鎴愬姛') + getList() + + // 閲嶇疆琛ㄥ崟 + Object.assign(applyForm, { + supplyType: '', + itemName: '', + quantity: 1, + reason: '', + urgency: 'normal' + }) +} + +// 瀹℃壒 +const handleApprove = (row) => { + currentDetail.value = row + showApproveDialog.value = true +} + +// 鎻愪氦瀹℃壒 +const submitApprove = () => { + const index = mockData.findIndex(item => item.id === currentDetail.value.id) + if (index !== -1) { + mockData[index].status = approveForm.approveResult + mockData[index].approver = '褰撳墠瀹℃壒浜�' + mockData[index].approveTime = new Date().toLocaleString() + mockData[index].approveComment = approveForm.approveComment + } + + showApproveDialog.value = false + ElMessage.success('瀹℃壒瀹屾垚') + getList() + + // 閲嶇疆琛ㄥ崟 + Object.assign(approveForm, { + approveResult: 'approved', + approveComment: '' + }) +} + +// 鍙戞斁 +const handleIssue = (row) => { + const index = mockData.findIndex(item => item.id === row.id) + if (index !== -1) { + mockData[index].status = 'issued' + mockData[index].issueTime = new Date().toLocaleString() + mockData[index].issuer = '褰撳墠鍙戞斁浜�' + } + + ElMessage.success('鍙戞斁瀹屾垚') + getList() +} + +// 鏌ョ湅璇︽儏 +const handleDetail = (row) => { + currentDetail.value = row + showDetailDialog.value = true +} + +// 鍒犻櫎 +const handleDelete = (row) => { + ElMessageBox.confirm('纭鍒犻櫎璇ョ敵璇峰悧锛�', '鎻愮ず', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + }).then(() => { + const index = mockData.findIndex(item => item.id === row.id) + if (index !== -1) { + mockData.splice(index, 1) + } + ElMessage.success('鍒犻櫎鎴愬姛') + getList() + }) +} + +// 鎵归噺瀹℃壒 +const handleBatchApprove = () => { + if (multipleSelection.value.length === 0) { + ElMessage.warning('璇烽�夋嫨瑕佸鎵圭殑璁板綍') + return + } + + ElMessageBox.confirm(`纭鎵归噺瀹℃壒閫変腑鐨� ${multipleSelection.value.length} 鏉¤褰曞悧锛焋, '鎻愮ず', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + }).then(() => { + multipleSelection.value.forEach(row => { + const index = mockData.findIndex(item => item.id === row.id) + if (index !== -1) { + mockData[index].status = 'approved' + mockData[index].approver = '褰撳墠瀹℃壒浜�' + mockData[index].approveTime = new Date().toLocaleString() + mockData[index].approveComment = '鎵归噺瀹℃壒閫氳繃' + } + }) + ElMessage.success('鎵归噺瀹℃壒瀹屾垚') + getList() + }) +} + +// 瀵煎嚭 +const handleExport = () => { + ElMessage.success('瀵煎嚭鍔熻兘寮�鍙戜腑...') +} + +// 椤甸潰鍔犺浇鏃惰幏鍙栨暟鎹� +onMounted(() => { + getList() +}) +</script> + +<style scoped> +.app-container { + padding: 20px; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.mb8 { + margin-bottom: 8px; +} + +.dialog-footer { + text-align: right; +} + +:deep(.el-descriptions__label) { + width: 120px; +} +</style> diff --git a/src/views/collaborativeApproval/planTemplate/index.vue b/src/views/collaborativeApproval/planTemplate/index.vue new file mode 100644 index 0000000..f46a203 --- /dev/null +++ b/src/views/collaborativeApproval/planTemplate/index.vue @@ -0,0 +1,750 @@ +<template> + <div class="app-container"> + <!-- 椤堕儴鎿嶄綔鏍� --> + <div class="header-actions"> + <div class="left-actions"> + <el-select v-model="currentLevel" placeholder="閫夋嫨璁″垝绾у埆" style="width: 150px" @change="handleLevelChange"> + <el-option label="涓汉璁″垝" value="personal" /> + <el-option label="灏忕粍璁″垝" value="group" /> + <el-option label="閮ㄩ棬璁″垝" value="department" /> + <el-option label="鍏徃璁″垝" value="company" /> + </el-select> + <el-select v-model="currentPeriod" placeholder="閫夋嫨鏃堕棿鍛ㄦ湡" style="width: 120px; margin-left: 10px" @change="handlePeriodChange"> + <el-option label="鍛ㄨ鍒�" value="week" /> + <el-option label="鏈堣鍒�" value="month" /> + <el-option label="骞磋鍒�" value="year" /> + </el-select> + <el-date-picker + v-model="currentDate" + :type="datePickerType" + placeholder="閫夋嫨鏃ユ湡" + style="width: 180px; margin-left: 10px" + @change="handleDateChange" + /> + </div> + <div class="right-actions"> + <el-button type="primary" @click="handleAddPlan">鏂板璁″垝</el-button> + <el-button @click="handleExport">瀵煎嚭璁″垝</el-button> + <el-button @click="handleShare">鍏变韩璁″垝</el-button> + </div> + </div> + + <!-- 璁″垝姒傝鍗$墖 --> + <div class="overview-cards"> + <el-row :gutter="20"> + <el-col :span="6"> + <el-card class="overview-card"> + <div class="card-content"> + <div class="card-icon personal"> + <el-icon><User /></el-icon> + </div> + <div class="card-info"> + <div class="card-title">涓汉璁″垝</div> + <div class="card-number">{{ overviewData.personal.total }}</div> + <div class="card-progress"> + <el-progress :percentage="overviewData.personal.completion" :stroke-width="6" /> + </div> + </div> + </div> + </el-card> + </el-col> + <el-col :span="6"> + <el-card class="overview-card"> + <div class="card-content"> + <div class="card-icon group"> + <el-icon><UserFilled /></el-icon> + </div> + <div class="card-info"> + <div class="card-title">灏忕粍璁″垝</div> + <div class="card-number">{{ overviewData.group.total }}</div> + <div class="card-progress"> + <el-progress :percentage="overviewData.group.completion" :stroke-width="6" /> + </div> + </div> + </div> + </el-card> + </el-col> + <el-col :span="6"> + <el-card class="overview-card"> + <div class="card-content"> + <div class="card-icon department"> + <el-icon><OfficeBuilding /></el-icon> + </div> + <div class="card-info"> + <div class="card-title">閮ㄩ棬璁″垝</div> + <div class="card-number">{{ overviewData.department.total }}</div> + <div class="card-progress"> + <el-progress :percentage="overviewData.department.completion" :stroke-width="6" /> + </div> + </div> + </div> + </el-card> + </el-col> + <el-col :span="6"> + <el-card class="overview-card"> + <div class="card-content"> + <div class="card-icon company"> + <el-icon><House /></el-icon> + </div> + <div class="card-info"> + <div class="card-title">鍏徃璁″垝</div> + <div class="card-number">{{ overviewData.company.total }}</div> + <div class="card-progress"> + <el-progress :percentage="overviewData.company.completion" :stroke-width="6" /> + </div> + </div> + </div> + </el-card> + </el-col> + </el-row> + </div> + + <!-- 璁″垝鍒楄〃 --> + <div class="plan-content"> + <el-card> + <template #header> + <div class="card-header"> + <span>{{ getCurrentLevelText() }} - {{ getCurrentPeriodText() }}</span> + <div> + <el-button size="small" @click="handleRefresh">鍒锋柊</el-button> + <el-button size="small" @click="handleFilter">绛涢��</el-button> + </div> + </div> + </template> + + <div class="plan-list"> + <div v-for="plan in planList" :key="plan.id" class="plan-item"> + <div class="plan-header"> + <div class="plan-title"> + <el-tag :type="getPriorityType(plan.priority)" size="small">{{ getPriorityText(plan.priority) }}</el-tag> + <span class="title-text">{{ plan.title }}</span> + </div> + <div class="plan-actions"> + <el-button size="small" @click="handleEditPlan(plan)">缂栬緫</el-button> + <el-button size="small" @click="handleViewDetail(plan)">璇︽儏</el-button> + <el-dropdown @command="handleMoreAction"> + <el-button size="small"> + 鏇村<el-icon class="el-icon--right"><ArrowDown /></el-icon> + </el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item command="share">鍏变韩</el-dropdown-item> + <el-dropdown-item command="copy">澶嶅埗</el-dropdown-item> + <el-dropdown-item command="delete" divided>鍒犻櫎</el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + </div> + </div> + + <div class="plan-content"> + <div class="plan-description">{{ plan.description }}</div> + <div class="plan-meta"> + <div class="meta-item"> + <el-icon><Calendar /></el-icon> + <span>{{ plan.startDate }} - {{ plan.endDate }}</span> + </div> + <div class="meta-item"> + <el-icon><User /></el-icon> + <span>{{ plan.assignee }}</span> + </div> + <div class="meta-item"> + <el-icon><Clock /></el-icon> + <span>杩涘害: {{ plan.progress }}%</span> + </div> + <div class="meta-item"> + <el-icon><Flag /></el-icon> + <span>{{ getStatusText(plan.status) }}</span> + </div> + </div> + + <div class="plan-progress"> + <el-progress + :percentage="plan.progress" + :color="getProgressColor(plan.progress)" + :stroke-width="8" + /> + </div> + + <div class="plan-tags"> + <el-tag v-for="tag in plan.tags" :key="tag" size="small" style="margin-right: 5px"> + {{ tag }} + </el-tag> + </div> + </div> + </div> + </div> + </el-card> + </div> + + <!-- 鏂板/缂栬緫璁″垝瀵硅瘽妗� --> + <el-dialog + v-model="planDialogVisible" + :title="dialogTitle" + width="600px" + @close="handleDialogClose" + > + <el-form :model="planForm" :rules="planRules" ref="planFormRef" label-width="100px"> + <el-form-item label="璁″垝鏍囬" prop="title"> + <el-input v-model="planForm.title" placeholder="璇疯緭鍏ヨ鍒掓爣棰�" /> + </el-form-item> + <el-form-item label="璁″垝鎻忚堪" prop="description"> + <el-input + v-model="planForm.description" + type="textarea" + :rows="3" + placeholder="璇疯緭鍏ヨ鍒掓弿杩�" + /> + </el-form-item> + <el-form-item label="璁″垝绾у埆" prop="level"> + <el-select v-model="planForm.level" placeholder="閫夋嫨璁″垝绾у埆" style="width: 100%"> + <el-option label="涓汉璁″垝" value="personal" /> + <el-option label="灏忕粍璁″垝" value="group" /> + <el-option label="閮ㄩ棬璁″垝" value="department" /> + <el-option label="鍏徃璁″垝" value="company" /> + </el-select> + </el-form-item> + <el-form-item label="鏃堕棿鍛ㄦ湡" prop="period"> + <el-select v-model="planForm.period" placeholder="閫夋嫨鏃堕棿鍛ㄦ湡" style="width: 100%"> + <el-option label="鍛ㄨ鍒�" value="week" /> + <el-option label="鏈堣鍒�" value="month" /> + <el-option label="骞磋鍒�" value="year" /> + </el-select> + </el-form-item> + <el-form-item label="寮�濮嬫椂闂�" prop="startDate"> + <el-date-picker + v-model="planForm.startDate" + type="date" + placeholder="閫夋嫨寮�濮嬫椂闂�" + style="width: 100%" + /> + </el-form-item> + <el-form-item label="缁撴潫鏃堕棿" prop="endDate"> + <el-date-picker + v-model="planForm.endDate" + type="date" + placeholder="閫夋嫨缁撴潫鏃堕棿" + style="width: 100%" + /> + </el-form-item> + <el-form-item label="璐熻矗浜�" prop="assignee"> + <el-input v-model="planForm.assignee" placeholder="璇疯緭鍏ヨ礋璐d汉" /> + </el-form-item> + <el-form-item label="浼樺厛绾�" prop="priority"> + <el-select v-model="planForm.priority" placeholder="閫夋嫨浼樺厛绾�" style="width: 100%"> + <el-option label="楂�" value="high" /> + <el-option label="涓�" value="medium" /> + <el-option label="浣�" value="low" /> + </el-select> + </el-form-item> + <el-form-item label="鏍囩"> + <el-input v-model="planForm.tags" placeholder="璇疯緭鍏ユ爣绛撅紝鐢ㄩ�楀彿鍒嗛殧" /> + </el-form-item> + </el-form> + <template #footer> + <span class="dialog-footer"> + <el-button @click="planDialogVisible = false">鍙栨秷</el-button> + <el-button type="primary" @click="handleSavePlan">淇濆瓨</el-button> + </span> + </template> + </el-dialog> + </div> +</template> + +<script setup> +import { ref, reactive, computed, onMounted } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import { + User, + UserFilled, + OfficeBuilding, + House, + Calendar, + Clock, + Flag, + ArrowDown +} from '@element-plus/icons-vue' + +// 鍝嶅簲寮忔暟鎹� +const currentLevel = ref('personal') +const currentPeriod = ref('week') +const currentDate = ref(new Date()) +const planDialogVisible = ref(false) +const dialogTitle = ref('鏂板璁″垝') +const planFormRef = ref() + +// 琛ㄥ崟鏁版嵁 +const planForm = reactive({ + title: '', + description: '', + level: 'personal', + period: 'week', + startDate: '', + endDate: '', + assignee: '', + priority: 'medium', + tags: '' +}) + +// 琛ㄥ崟楠岃瘉瑙勫垯 +const planRules = { + title: [{ required: true, message: '璇疯緭鍏ヨ鍒掓爣棰�', trigger: 'blur' }], + description: [{ required: true, message: '璇疯緭鍏ヨ鍒掓弿杩�', trigger: 'blur' }], + level: [{ required: true, message: '璇烽�夋嫨璁″垝绾у埆', trigger: 'change' }], + period: [{ required: true, message: '璇烽�夋嫨鏃堕棿鍛ㄦ湡', trigger: 'change' }], + startDate: [{ required: true, message: '璇烽�夋嫨寮�濮嬫椂闂�', trigger: 'change' }], + endDate: [{ required: true, message: '璇烽�夋嫨缁撴潫鏃堕棿', trigger: 'change' }], + assignee: [{ required: true, message: '璇疯緭鍏ヨ礋璐d汉', trigger: 'blur' }], + priority: [{ required: true, message: '璇烽�夋嫨浼樺厛绾�', trigger: 'change' }] +} + +// 姒傝鏁版嵁 +const overviewData = reactive({ + personal: { total: 12, completion: 75 }, + group: { total: 8, completion: 60 }, + department: { total: 15, completion: 45 }, + company: { total: 6, completion: 30 } +}) + +// 璁″垝鍒楄〃鏁版嵁 +const planList = ref([ + { + id: 1, + title: '浜у搧闇�姹傚垎鏋�', + description: '瀵规柊浜у搧杩涜璇︾粏鐨勯渶姹傚垎鏋愬拰甯傚満璋冪爺锛屽埗瀹氫骇鍝佽鍒掓柟妗�', + level: 'personal', + period: 'week', + startDate: '2025-01-15', + endDate: '2025-01-21', + assignee: '闄堝織寮�', + priority: 'high', + status: 'in_progress', + progress: 80, + tags: ['浜у搧', '鍒嗘瀽', '璋冪爺'] + }, + { + id: 2, + title: '鎶�鏈灦鏋勮璁�', + description: '璁捐绯荤粺鎶�鏈灦鏋勶紝鍖呮嫭鏁版嵁搴撹璁°�佹帴鍙h璁$瓑', + level: 'group', + period: 'month', + startDate: '2025-01-01', + endDate: '2025-01-31', + assignee: '鍒橀泤濠�', + priority: 'high', + status: 'completed', + progress: 100, + tags: ['鎶�鏈�', '鏋舵瀯', '璁捐'] + }, + { + id: 3, + title: '甯傚満鎺ㄥ箍璁″垝', + description: '鍒跺畾骞村害甯傚満鎺ㄥ箍绛栫暐鍜岃惀閿�璁″垝', + level: 'department', + period: 'year', + startDate: '2025-01-01', + endDate: '2025-12-31', + assignee: '鐜嬪缓鍥�', + priority: 'medium', + status: 'not_started', + progress: 0, + tags: ['甯傚満', '鎺ㄥ箍', '钀ラ攢'] + }, + { + id: 4, + title: '鍥㈤槦寤鸿娲诲姩', + description: '缁勭粐鍥㈤槦寤鸿娲诲姩锛屾彁鍗囧洟闃熷嚌鑱氬姏鍜屽崗浣滄晥鐜�', + level: 'company', + period: 'month', + startDate: '2025-01-15', + endDate: '2025-02-15', + assignee: '璧典附鍗�', + priority: 'low', + status: 'in_progress', + progress: 30, + tags: ['鍥㈤槦', '寤鸿', '娲诲姩'] + } +]) + +// 璁$畻灞炴�� +const datePickerType = computed(() => { + switch (currentPeriod.value) { + case 'week': + return 'week' + case 'month': + return 'month' + case 'year': + return 'year' + default: + return 'date' + } +}) + +// 鏂规硶 +const handleLevelChange = (value) => { + console.log('璁″垝绾у埆鍙樻洿:', value) + // 杩欓噷鍙互鏍规嵁绾у埆绛涢�夋暟鎹� +} + +const handlePeriodChange = (value) => { + console.log('鏃堕棿鍛ㄦ湡鍙樻洿:', value) + // 杩欓噷鍙互鏍规嵁鍛ㄦ湡绛涢�夋暟鎹� +} + +const handleDateChange = (value) => { + console.log('鏃ユ湡鍙樻洿:', value) + // 杩欓噷鍙互鏍规嵁鏃ユ湡绛涢�夋暟鎹� +} + +const handleAddPlan = () => { + dialogTitle.value = '鏂板璁″垝' + planDialogVisible.value = true + // 閲嶇疆琛ㄥ崟 + Object.keys(planForm).forEach(key => { + planForm[key] = '' + }) + planForm.level = 'personal' + planForm.period = 'week' + planForm.priority = 'medium' +} + +const handleEditPlan = (plan) => { + dialogTitle.value = '缂栬緫璁″垝' + planDialogVisible.value = true + // 濉厖琛ㄥ崟鏁版嵁 + Object.keys(planForm).forEach(key => { + if (key === 'tags') { + planForm[key] = plan[key].join(', ') + } else { + planForm[key] = plan[key] + } + }) +} + +const handleViewDetail = (plan) => { + ElMessage.info(`鏌ョ湅璁″垝璇︽儏: ${plan.title}`) +} + +const handleMoreAction = (command) => { + switch (command) { + case 'share': + ElMessage.success('璁″垝宸插叡浜�') + break + case 'copy': + ElMessage.success('璁″垝宸插鍒�') + break + case 'delete': + ElMessageBox.confirm('纭畾瑕佸垹闄よ繖涓鍒掑悧锛�', '鎻愮ず', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + }).then(() => { + ElMessage.success('璁″垝宸插垹闄�') + }) + break + } +} + +const handleSavePlan = async () => { + try { + await planFormRef.value.validate() + ElMessage.success('璁″垝淇濆瓨鎴愬姛') + planDialogVisible.value = false + } catch (error) { + console.log('琛ㄥ崟楠岃瘉澶辫触:', error) + } +} + +const handleDialogClose = () => { + planFormRef.value?.resetFields() +} + +const handleRefresh = () => { + ElMessage.success('鏁版嵁宸插埛鏂�') +} + +const handleFilter = () => { + ElMessage.info('鎵撳紑绛涢�夐潰鏉�') +} + +const handleExport = () => { + ElMessage.success('璁″垝宸插鍑�') +} + +const handleShare = () => { + ElMessage.success('璁″垝宸插叡浜�') +} + +const getCurrentLevelText = () => { + const levelMap = { + personal: '涓汉璁″垝', + group: '灏忕粍璁″垝', + department: '閮ㄩ棬璁″垝', + company: '鍏徃璁″垝' + } + return levelMap[currentLevel.value] || '涓汉璁″垝' +} + +const getCurrentPeriodText = () => { + const periodMap = { + week: '鍛ㄨ鍒�', + month: '鏈堣鍒�', + year: '骞磋鍒�' + } + return periodMap[currentPeriod.value] || '鍛ㄨ鍒�' +} + +const getPriorityType = (priority) => { + const typeMap = { + high: 'danger', + medium: 'warning', + low: 'info' + } + return typeMap[priority] || 'info' +} + +const getPriorityText = (priority) => { + const textMap = { + high: '楂�', + medium: '涓�', + low: '浣�' + } + return textMap[priority] || '涓�' +} + +const getStatusText = (status) => { + const statusMap = { + not_started: '鏈紑濮�', + in_progress: '杩涜涓�', + completed: '宸插畬鎴�', + paused: '宸叉殏鍋�' + } + return statusMap[status] || '鏈煡' +} + +const getProgressColor = (progress) => { + if (progress >= 80) return '#67C23A' + if (progress >= 50) return '#E6A23C' + return '#F56C6C' +} + +onMounted(() => { + console.log('澶氱骇璁″垝妯℃澘椤甸潰宸插姞杞�') +}) +</script> + +<style scoped> +.app-container { + padding: 20px; + background-color: #f5f5f5; + min-height: 100vh; +} + +.header-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + background: white; + padding: 20px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.left-actions { + display: flex; + align-items: center; +} + +.right-actions { + display: flex; + gap: 10px; +} + +.overview-cards { + margin-bottom: 20px; +} + +.overview-card { + height: 120px; +} + +.card-content { + display: flex; + align-items: center; + height: 100%; +} + +.card-icon { + width: 60px; + height: 60px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-right: 15px; + font-size: 24px; + color: white; +} + +.card-icon.personal { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.card-icon.group { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); +} + +.card-icon.department { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); +} + +.card-icon.company { + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); +} + +.card-info { + flex: 1; +} + +.card-title { + font-size: 14px; + color: #666; + margin-bottom: 5px; +} + +.card-number { + font-size: 24px; + font-weight: bold; + color: #333; + margin-bottom: 10px; +} + +.card-progress { + width: 100%; +} + +.plan-content { + background: white; + border-radius: 8px; + overflow: hidden; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + color: #333; +} + +.header-actions { + display: flex; + gap: 10px; +} + +.plan-list { + padding: 20px 0; +} + +.plan-item { + border: 1px solid #e4e7ed; + border-radius: 8px; + margin-bottom: 15px; + padding: 20px; + transition: all 0.3s ease; +} + +.plan-item:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} + +.plan-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.plan-title { + display: flex; + align-items: center; + gap: 10px; +} + +.title-text { + font-size: 16px; + font-weight: bold; + color: #333; +} + +.plan-actions { + display: flex; + gap: 10px; +} + +.plan-content { + margin-bottom: 15px; +} + +.plan-description { + color: #666; + margin-bottom: 15px; + line-height: 1.6; +} + +.plan-meta { + display: flex; + flex-wrap: wrap; + gap: 20px; + margin-bottom: 15px; +} + +.meta-item { + display: flex; + align-items: center; + gap: 5px; + color: #666; + font-size: 14px; +} + +.plan-progress { + margin-bottom: 15px; +} + +.plan-tags { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.dialog-footer { + display: flex; + justify-content: flex-end; + gap: 10px; +} + +/* 鍝嶅簲寮忚璁� */ +@media (max-width: 768px) { + .header-actions { + flex-direction: column; + gap: 15px; + } + + .left-actions { + flex-wrap: wrap; + gap: 10px; + } + + .plan-meta { + flex-direction: column; + gap: 10px; + } + + .plan-header { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } +} +</style> diff --git a/src/views/collaborativeApproval/sealManagement/index.vue b/src/views/collaborativeApproval/sealManagement/index.vue new file mode 100644 index 0000000..2d3e62f --- /dev/null +++ b/src/views/collaborativeApproval/sealManagement/index.vue @@ -0,0 +1,588 @@ +<template> + <div class="app-container"> + <el-card class="box-card"> + <template #header> + <div class="card-header"> + <span>鐢ㄥ嵃绠$悊涓庤绔犲埗搴﹀彂甯�</span> + <el-button type="primary" @click="showSealApplyDialog = true"> + <el-icon><Plus /></el-icon> + 鐢宠鐢ㄥ嵃 + </el-button> + </div> + </template> + + <el-tabs v-model="activeTab" type="border-card"> + <!-- 鐢ㄥ嵃鐢宠绠$悊 --> + <el-tab-pane label="鐢ㄥ嵃鐢宠绠$悊" name="seal"> + <div class="tab-content"> + <el-row :gutter="20" class="mb-20"> + <el-col :span="6"> + <el-input v-model="sealSearchForm.keyword" placeholder="璇疯緭鍏ョ敵璇锋爣棰樻垨鐢宠浜�" clearable /> + </el-col> + <el-col :span="4"> + <el-select v-model="sealSearchForm.status" placeholder="瀹℃壒鐘舵��" clearable> + <el-option label="寰呭鎵�" value="pending" /> + <el-option label="宸查�氳繃" value="approved" /> + <el-option label="宸叉嫆缁�" value="rejected" /> + </el-select> + </el-col> + <el-col :span="4"> + <el-button type="primary" @click="searchSealApplications">鎼滅储</el-button> + <el-button @click="resetSealSearch">閲嶇疆</el-button> + </el-col> + </el-row> + + <el-table :data="sealApplications" style="width: 100%"> + <el-table-column prop="id" label="鐢宠缂栧彿" width="120" /> + <el-table-column prop="title" label="鐢宠鏍囬" min-width="200" /> + <el-table-column prop="applicant" label="鐢宠浜�" width="120" /> + <el-table-column prop="department" label="鎵�灞為儴闂�" width="150" /> + <el-table-column prop="sealType" label="鐢ㄥ嵃绫诲瀷" width="120" /> + <el-table-column prop="applyTime" label="鐢宠鏃堕棿" width="180" /> + <el-table-column prop="status" label="鐘舵��" width="100"> + <template #default="scope"> + <el-tag :type="getStatusType(scope.row.status)"> + {{ getStatusText(scope.row.status) }} + </el-tag> + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" width="200" fixed="right"> + <template #default="scope"> + <el-button link @click="viewSealDetail(scope.row)">鏌ョ湅</el-button> + <el-button + v-if="scope.row.status === 'pending'" + link + type="primary" + @click="approveSeal(scope.row)" + > + 瀹℃壒 + </el-button> + <el-button + v-if="scope.row.status === 'pending'" + link + type="danger" + @click="rejectSeal(scope.row)" + > + 鎷掔粷 + </el-button> + </template> + </el-table-column> + </el-table> + </div> + </el-tab-pane> + + <!-- 瑙勭珷鍒跺害绠$悊 --> + <el-tab-pane label="瑙勭珷鍒跺害绠$悊" name="regulations"> + <div class="tab-content"> + <el-row :gutter="20" class="mb-20"> + <el-col :span="6"> + <el-input v-model="regulationSearchForm.keyword" placeholder="璇疯緭鍏ュ埗搴︽爣棰樻垨鍙戝竷浜�" clearable /> + </el-col> + <el-col :span="4"> + <el-select v-model="regulationSearchForm.category" placeholder="鍒跺害鍒嗙被" clearable> + <el-option label="浜轰簨鍒跺害" value="hr" /> + <el-option label="璐㈠姟鍒跺害" value="finance" /> + <el-option label="瀹夊叏鍒跺害" value="safety" /> + <el-option label="鎶�鏈埗搴�" value="tech" /> + </el-select> + </el-col> + <el-col :span="8"> + <el-button type="primary" @click="searchRegulations">鎼滅储</el-button> + <el-button @click="resetRegulationSearch">閲嶇疆</el-button> + <el-button type="success" @click="showRegulationDialog = true"> + 鍙戝竷鍒跺害 + </el-button> + </el-col> + </el-row> + + <el-table :data="regulations" style="width: 100%"> + <el-table-column prop="id" label="鍒跺害缂栧彿" width="120" /> + <el-table-column prop="title" label="鍒跺害鏍囬" min-width="200" /> + <el-table-column prop="category" label="鍒嗙被" width="120"> + <template #default="scope"> + <el-tag>{{ getCategoryText(scope.row.category) }}</el-tag> + </template> + </el-table-column> + <el-table-column prop="version" label="鐗堟湰" width="80" /> + <el-table-column prop="publisher" label="鍙戝竷浜�" width="120" /> + <el-table-column prop="publishTime" label="鍙戝竷鏃堕棿" width="180" /> + <el-table-column prop="status" label="鐘舵��" width="100"> + <template #default="scope"> + <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> + {{ scope.row.status === 'active' ? '鐢熸晥涓�' : '宸插簾姝�' }} + </el-tag> + </template> + </el-table-column> + <el-table-column prop="readCount" label="宸茶浜烘暟" width="100" /> + <el-table-column label="鎿嶄綔" width="250" fixed="right"> + <template #default="scope"> + <el-button link @click="viewRegulation(scope.row)">鏌ョ湅</el-button> + <el-button link type="primary" @click="editRegulation(scope.row)">缂栬緫</el-button> + <el-button link type="success" @click="viewVersionHistory(scope.row)">鐗堟湰鍘嗗彶</el-button> + <el-button link type="warning" @click="viewReadStatus(scope.row)">闃呰鐘舵��</el-button> + </template> + </el-table-column> + </el-table> + </div> + </el-tab-pane> + </el-tabs> + </el-card> + + <!-- 鐢ㄥ嵃鐢宠瀵硅瘽妗� --> + <el-dialog v-model="showSealApplyDialog" title="鐢宠鐢ㄥ嵃" width="600px"> + <el-form :model="sealForm" :rules="sealRules" ref="sealFormRef" label-width="100px"> + <el-form-item label="鐢宠鏍囬" prop="title"> + <el-input v-model="sealForm.title" placeholder="璇疯緭鍏ョ敵璇锋爣棰�" /> + </el-form-item> + <el-form-item label="鐢ㄥ嵃绫诲瀷" prop="sealType"> + <el-select v-model="sealForm.sealType" placeholder="璇烽�夋嫨鐢ㄥ嵃绫诲瀷" style="width: 100%"> + <el-option label="鍏珷" value="official" /> + <el-option label="鍚堝悓涓撶敤绔�" value="contract" /> + <el-option label="璐㈠姟涓撶敤绔�" value="finance" /> + <el-option label="娉曚汉绔�" value="legal" /> + </el-select> + </el-form-item> + <el-form-item label="鐢宠鍘熷洜" prop="reason"> + <el-input v-model="sealForm.reason" type="textarea" :rows="4" placeholder="璇疯缁嗚鏄庣敤鍗板師鍥�" /> + </el-form-item> + <el-form-item label="绱ф�ョ▼搴�" prop="urgency"> + <el-radio-group v-model="sealForm.urgency"> + <el-radio label="normal">鏅��</el-radio> + <el-radio label="urgent">绱ф��</el-radio> + <el-radio label="very-urgent">鐗规��</el-radio> + </el-radio-group> + </el-form-item> + </el-form> + <template #footer> + <span class="dialog-footer"> + <el-button @click="showSealApplyDialog = false">鍙栨秷</el-button> + <el-button type="primary" @click="submitSealApplication">鎻愪氦鐢宠</el-button> + </span> + </template> + </el-dialog> + + <!-- 瑙勭珷鍒跺害鍙戝竷瀵硅瘽妗� --> + <el-dialog v-model="showRegulationDialog" title="鍙戝竷瑙勭珷鍒跺害" width="800px"> + <el-form :model="regulationForm" :rules="regulationRules" ref="regulationFormRef" label-width="100px"> + <el-form-item label="鍒跺害鏍囬" prop="title"> + <el-input v-model="regulationForm.title" placeholder="璇疯緭鍏ュ埗搴︽爣棰�" /> + </el-form-item> + <el-form-item label="鍒跺害鍒嗙被" prop="category"> + <el-select v-model="regulationForm.category" placeholder="璇烽�夋嫨鍒跺害鍒嗙被" style="width: 100%"> + <el-option label="浜轰簨鍒跺害" value="hr" /> + <el-option label="璐㈠姟鍒跺害" value="finance" /> + <el-option label="瀹夊叏鍒跺害" value="safety" /> + <el-option label="鎶�鏈埗搴�" value="tech" /> + </el-select> + </el-form-item> + <el-form-item label="鍒跺害鍐呭" prop="content"> + <el-input v-model="regulationForm.content" type="textarea" :rows="10" placeholder="璇疯緭鍏ュ埗搴﹁缁嗗唴瀹�" /> + </el-form-item> + <el-form-item label="鐢熸晥鏃堕棿" prop="effectiveTime"> + <el-date-picker v-model="regulationForm.effectiveTime" type="datetime" placeholder="閫夋嫨鐢熸晥鏃堕棿" style="width: 100%" /> + </el-form-item> + <el-form-item label="閫傜敤鑼冨洿" prop="scope"> + <el-checkbox-group v-model="regulationForm.scope"> + <el-checkbox label="all">鍏ㄤ綋鍛樺伐</el-checkbox> + <el-checkbox label="manager">绠$悊灞�</el-checkbox> + <el-checkbox label="hr">浜轰簨閮ㄩ棬</el-checkbox> + <el-checkbox label="finance">璐㈠姟閮ㄩ棬</el-checkbox> + <el-checkbox label="tech">鎶�鏈儴闂�</el-checkbox> + </el-checkbox-group> + </el-form-item> + <el-form-item label="鏄惁闇�瑕佺‘璁�" prop="requireConfirm"> + <el-switch v-model="regulationForm.requireConfirm" /> + <span class="ml-10">寮�鍚悗鍛樺伐闇�瑕侀槄璇荤‘璁�</span> + </el-form-item> + </el-form> + <template #footer> + <span class="dialog-footer"> + <el-button @click="showRegulationDialog = false">鍙栨秷</el-button> + <el-button type="primary" @click="submitRegulation">鍙戝竷鍒跺害</el-button> + </span> + </template> + </el-dialog> + + <!-- 鐢ㄥ嵃璇︽儏瀵硅瘽妗� --> + <el-dialog v-model="showSealDetailDialog" title="鐢ㄥ嵃鐢宠璇︽儏" width="700px"> + <div v-if="currentSealDetail" class="mb10"> + <el-descriptions :column="2" border> + <el-descriptions-item label="鐢宠缂栧彿">{{ currentSealDetail.id }}</el-descriptions-item> + <el-descriptions-item label="鐢宠鏍囬">{{ currentSealDetail.title }}</el-descriptions-item> + <el-descriptions-item label="鐢宠浜�">{{ currentSealDetail.applicant }}</el-descriptions-item> + <el-descriptions-item label="鎵�灞為儴闂�">{{ currentSealDetail.department }}</el-descriptions-item> + <el-descriptions-item label="鐢ㄥ嵃绫诲瀷">{{ currentSealDetail.sealType }}</el-descriptions-item> + <el-descriptions-item label="鐢宠鏃堕棿">{{ currentSealDetail.applyTime }}</el-descriptions-item> + <el-descriptions-item label="鐘舵��"> + <el-tag :type="getStatusType(currentSealDetail.status)"> + {{ getStatusText(currentSealDetail.status) }} + </el-tag> + </el-descriptions-item> + <el-descriptions-item label="鐢宠鍘熷洜" :span="2">{{ currentSealDetail.reason }}</el-descriptions-item> + </el-descriptions> + </div> + </el-dialog> + + <!-- 瑙勭珷鍒跺害璇︽儏瀵硅瘽妗� --> + <el-dialog v-model="showRegulationDetailDialog" title="瑙勭珷鍒跺害璇︽儏" width="800px"> + <div v-if="currentRegulationDetail"> + <el-descriptions :column="2" border> + <el-descriptions-item label="鍒跺害缂栧彿">{{ currentRegulationDetail.id }}</el-descriptions-item> + <el-descriptions-item label="鍒跺害鏍囬">{{ currentRegulationDetail.title }}</el-descriptions-item> + <el-descriptions-item label="鍒嗙被">{{ getCategoryText(currentRegulationDetail.category) }}</el-descriptions-item> + <el-descriptions-item label="鐗堟湰">{{ currentRegulationDetail.version }}</el-descriptions-item> + <el-descriptions-item label="鍙戝竷浜�">{{ currentRegulationDetail.publisher }}</el-descriptions-item> + <el-descriptions-item label="鍙戝竷鏃堕棿">{{ currentRegulationDetail.publishTime }}</el-descriptions-item> + </el-descriptions> + <div class="mt-20"> + <h4>鍒跺害鍐呭</h4> + <div class="regulation-content">{{ currentRegulationDetail.content }}</div> + </div> + </div> + </el-dialog> + + <!-- 鐗堟湰鍘嗗彶瀵硅瘽妗� --> + <el-dialog v-model="showVersionHistoryDialog" title="鐗堟湰鍘嗗彶" width="800px"> + <el-table :data="versionHistory" style="width: 100%;margin-bottom: 10px"> + <el-table-column prop="version" label="鐗堟湰鍙�" width="100" /> + <el-table-column prop="updateTime" label="鏇存柊鏃堕棿" width="180" /> + <el-table-column prop="updater" label="鏇存柊浜�" width="120" /> + <el-table-column prop="changeLog" label="鍙樻洿璇存槑" /> + </el-table> + </el-dialog> + + <!-- 闃呰鐘舵�佸璇濇 --> + <el-dialog v-model="showReadStatusDialog" title="闃呰鐘舵��" width="800px"> + <el-table :data="readStatusList" style="width: 100%;margin-bottom: 10px"> + <el-table-column prop="employee" label="鍛樺伐濮撳悕" width="120" /> + <el-table-column prop="department" label="鎵�灞為儴闂�" width="150" /> + <el-table-column prop="readTime" label="闃呰鏃堕棿" width="180" /> + <el-table-column prop="confirmTime" label="纭鏃堕棿" width="180" /> + <el-table-column prop="status" label="鐘舵��" width="100"> + <template #default="scope"> + <el-tag :type="scope.row.status === 'confirmed' ? 'success' : 'warning'"> + {{ scope.row.status === 'confirmed' ? '宸茬‘璁�' : '鏈‘璁�' }} + </el-tag> + </template> + </el-table-column> + </el-table> + </el-dialog> + </div> +</template> + +<script setup> +import { ref, reactive, onMounted } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import { Plus } from '@element-plus/icons-vue' + +// 鍝嶅簲寮忔暟鎹� +const activeTab = ref('seal') + +// 鐢ㄥ嵃鐢宠鐩稿叧 +const showSealApplyDialog = ref(false) +const showSealDetailDialog = ref(false) +const currentSealDetail = ref(null) +const sealFormRef = ref() +const sealForm = reactive({ + title: '', + sealType: '', + reason: '', + urgency: 'normal' +}) + +const sealRules = { + title: [{ required: true, message: '璇疯緭鍏ョ敵璇锋爣棰�', trigger: 'blur' }], + sealType: [{ required: true, message: '璇烽�夋嫨鐢ㄥ嵃绫诲瀷', trigger: 'change' }], + reason: [{ required: true, message: '璇疯緭鍏ョ敵璇峰師鍥�', trigger: 'blur' }] +} + +const sealSearchForm = reactive({ + keyword: '', + status: '' +}) + +// 瑙勭珷鍒跺害鐩稿叧 +const showRegulationDialog = ref(false) +const showRegulationDetailDialog = ref(false) +const showVersionHistoryDialog = ref(false) +const showReadStatusDialog = ref(false) +const currentRegulationDetail = ref(null) +const regulationFormRef = ref() +const regulationForm = reactive({ + title: '', + category: '', + content: '', + effectiveTime: '', + scope: [], + requireConfirm: true +}) + +const regulationRules = { + title: [{ required: true, message: '璇疯緭鍏ュ埗搴︽爣棰�', trigger: 'blur' }], + category: [{ required: true, message: '璇烽�夋嫨鍒跺害鍒嗙被', trigger: 'change' }], + content: [{ required: true, message: '璇疯緭鍏ュ埗搴﹀唴瀹�', trigger: 'blur' }], + effectiveTime: [{ required: true, message: '璇烽�夋嫨鐢熸晥鏃堕棿', trigger: 'change' }], + scope: [{ required: true, message: '璇烽�夋嫨閫傜敤鑼冨洿', trigger: 'change' }] +} + +const regulationSearchForm = reactive({ + keyword: '', + category: '' +}) + +// 鍋囨暟鎹� +const sealApplications = ref([ + { + id: 'SEAL001', + title: '鍚堝悓鐢ㄥ嵃鐢宠', + applicant: '闄堝織寮�', + department: '閿�鍞儴', + sealType: '鍚堝悓涓撶敤绔�', + applyTime: '2025-01-15 10:30:00', + status: 'pending', + reason: '瀹㈡埛鍚堝悓闇�瑕佺洊绔�' + }, + { + id: 'SEAL002', + title: '璐㈠姟鎶ュ憡鐢ㄥ嵃', + applicant: '鐜嬪缓鍥�', + department: '璐㈠姟閮�', + sealType: '璐㈠姟涓撶敤绔�', + applyTime: '2025-01-14 14:20:00', + status: 'approved', + reason: '瀛e害璐㈠姟鎶ュ憡闇�瑕佺洊绔�' + }, + { + id: 'SEAL003', + title: '鍏徃绔犵▼鐢ㄥ嵃', + applicant: '瀛欐槑鍗�', + department: '娉曞姟閮�', + sealType: '鍏珷', + applyTime: '2025-01-13 09:15:00', + status: 'rejected', + reason: '鍏徃绔犵▼淇敼闇�瑕佺洊绔�' + } +]) + +const regulations = ref([ + { + id: 'REG001', + title: '鍛樺伐鑰冨嫟绠$悊鍒跺害', + category: 'hr', + version: 'v2.1', + publisher: '浜轰簨閮�', + publishTime: '2025-01-10 09:00:00', + status: 'active', + readCount: 45, + content: '涓鸿鑼冨憳宸ヨ�冨嫟绠$悊锛屾彁楂樺伐浣滄晥鐜囷紝鐗瑰埗瀹氭湰鍒跺害...' + }, + { + id: 'REG002', + title: '璐㈠姟鎶ラ攢鍒跺害', + category: 'finance', + version: 'v1.5', + publisher: '璐㈠姟閮�', + publishTime: '2025-01-08 14:30:00', + status: 'active', + readCount: 38, + content: '涓鸿鑼冭储鍔℃姤閿�娴佺▼锛屽姞寮鸿储鍔$鐞嗭紝鐗瑰埗瀹氭湰鍒跺害...' + }, + { + id: 'REG003', + title: '瀹夊叏鐢熶骇绠$悊鍒跺害', + category: 'safety', + version: 'v3.0', + publisher: '瀹夊叏閮�', + publishTime: '2025-01-05 16:00:00', + status: 'active', + readCount: 52, + content: '涓虹‘淇濆憳宸ヤ汉韬畨鍏紝棰勯槻瀹夊叏浜嬫晠鍙戠敓锛岀壒鍒跺畾鏈埗搴�...' + } +]) + +const versionHistory = ref([ + { version: 'v2.1', updateTime: '2025-01-10 09:00:00', updater: '浜轰簨閮�', changeLog: '鏇存柊鑰冨嫟鏃堕棿瑙勫畾' }, + { version: 'v2.0', updateTime: '2023-12-15 10:30:00', updater: '浜轰簨閮�', changeLog: '鏂板鍔犵彮绠$悊瑙勫畾' }, + { version: 'v1.0', updateTime: '2023-11-01 14:00:00', updater: '浜轰簨閮�', changeLog: '棣栨鍙戝竷' } +]) + +const readStatusList = ref([ + { employee: '闄堝織寮�', department: '閿�鍞儴', readTime: '2025-01-11 10:30:00', confirmTime: '2025-01-11 10:35:00', status: 'confirmed' }, + { employee: '鍒橀泤濠�', department: '鎶�鏈儴', readTime: '2025-01-11 14:20:00', confirmTime: '', status: 'unconfirmed' }, + { employee: '鐜嬪缓鍥�', department: '璐㈠姟閮�', readTime: '2025-01-12 09:15:00', confirmTime: '2025-01-12 09:20:00', status: 'confirmed' } +]) + +// 鏂规硶 +const getStatusType = (status) => { + const statusMap = { + pending: 'warning', + approved: 'success', + rejected: 'danger' + } + return statusMap[status] || 'info' +} + +const getStatusText = (status) => { + const statusMap = { + pending: '寰呭鎵�', + approved: '宸查�氳繃', + rejected: '宸叉嫆缁�' + } + return statusMap[status] || '鏈煡' +} + +const getCategoryText = (category) => { + const categoryMap = { + hr: '浜轰簨鍒跺害', + finance: '璐㈠姟鍒跺害', + safety: '瀹夊叏鍒跺害', + tech: '鎶�鏈埗搴�' + } + return categoryMap[category] || '鏈煡' +} + +const searchSealApplications = () => { + ElMessage.success('鎼滅储瀹屾垚') +} + +const resetSealSearch = () => { + sealSearchForm.keyword = '' + sealSearchForm.status = '' + searchSealApplications() +} + +const searchRegulations = () => { + ElMessage.success('鎼滅储瀹屾垚') +} + +const resetRegulationSearch = () => { + regulationSearchForm.keyword = '' + regulationSearchForm.category = '' + searchRegulations() +} + +const submitSealApplication = async () => { + try { + await sealFormRef.value.validate() + ElMessage.success('鐢宠鎻愪氦鎴愬姛') + showSealApplyDialog.value = false + Object.assign(sealForm, { + title: '', + sealType: '', + reason: '', + urgency: 'normal' + }) + } catch (error) { + ElMessage.error('璇峰畬鍠勭敵璇蜂俊鎭�') + } +} + +const submitRegulation = async () => { + try { + await regulationFormRef.value.validate() + ElMessage.success('鍒跺害鍙戝竷鎴愬姛') + showRegulationDialog.value = false + Object.assign(regulationForm, { + title: '', + category: '', + content: '', + effectiveTime: '', + scope: [], + requireConfirm: true + }) + } catch (error) { + ElMessage.error('璇峰畬鍠勫埗搴︿俊鎭�') + } +} + +const viewSealDetail = (row) => { + currentSealDetail.value = row + showSealDetailDialog.value = true +} + +const approveSeal = (row) => { + ElMessageBox.confirm('纭閫氳繃璇ョ敤鍗扮敵璇凤紵', '鎻愮ず', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + }).then(() => { + row.status = 'approved' + ElMessage.success('瀹℃壒閫氳繃') + }) +} + +const rejectSeal = (row) => { + ElMessageBox.prompt('璇疯緭鍏ユ嫆缁濆師鍥�', '鎻愮ず', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + inputPattern: /\S+/, + inputErrorMessage: '鎷掔粷鍘熷洜涓嶈兘涓虹┖' + }).then(({ value }) => { + row.status = 'rejected' + ElMessage.success('宸叉嫆缁濈敵璇�') + }) +} + +const viewRegulation = (row) => { + currentRegulationDetail.value = row + showRegulationDetailDialog.value = true +} + +const editRegulation = (row) => { + ElMessage.info('缂栬緫鍔熻兘寮�鍙戜腑...') +} + +const viewVersionHistory = (row) => { + showVersionHistoryDialog.value = true +} + +const viewReadStatus = (row) => { + showReadStatusDialog.value = true +} + +onMounted(() => { + // 鍒濆鍖� +}) +</script> + +<style scoped> +.app-container { + padding: 20px; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.tab-content { + padding: 20px 0; +} + +.mb-20 { + margin-bottom: 20px; +} + +.mt-20 { + margin-top: 20px; +} + +.ml-10 { + margin-left: 10px; +} + +.regulation-content { + background-color: #f5f5f5; + padding: 15px; + border-radius: 4px; + line-height: 1.6; + white-space: pre-wrap; +} + +.dialog-footer { + display: flex; + justify-content: flex-end; + gap: 10px; +} +</style> diff --git a/src/views/collaborativeApproval/warningSystem/index.vue b/src/views/collaborativeApproval/warningSystem/index.vue index b04c583..f6c7f6e 100644 --- a/src/views/collaborativeApproval/warningSystem/index.vue +++ b/src/views/collaborativeApproval/warningSystem/index.vue @@ -96,7 +96,7 @@ levelText: '绾㈣壊棰勮', status: 'pending', statusText: '寰呭鐞�', - responsible: '寮犵粡鐞�', + responsible: '闄堝織寮�', description: 'A椤圭洰棰勭畻鎵ц鐜囧凡杈�95%锛岄璁″皢瓒呭嚭棰勭畻鑼冨洿銆�', impact: '褰卞搷椤圭洰鏁翠綋璐㈠姟鎸囨爣锛屽彲鑳藉鑷撮」鐩簭鎹�', suggestions: '鏆傚仠闈炲繀瑕佹敮鍑猴紝浼樺寲璧勬簮閰嶇疆锛岀敵璇烽绠楄皟鏁�' @@ -148,7 +148,7 @@ levelText: '绾㈣壊棰勮', status: 'pending', statusText: '寰呭鐞�', - responsible: '闄堟�荤洃', + responsible: '闄堝織寮�', description: '浜у搧D鍦ㄥ鎴风幇鍦哄嚭鐜拌川閲忛棶棰樸��', impact: '褰卞搷瀹㈡埛婊℃剰搴︼紝鍙兘閫犳垚缁忔祹鎹熷け', suggestions: '绔嬪嵆鍙洖闂浜у搧锛屽垎鏋愬師鍥狅紝鍒跺畾鏀硅繘鎺柦' diff --git a/src/views/equipmentManagement/brand/index.vue b/src/views/equipmentManagement/brand/index.vue new file mode 100644 index 0000000..6607cc8 --- /dev/null +++ b/src/views/equipmentManagement/brand/index.vue @@ -0,0 +1,217 @@ +<template> + <div class="app-container"> + <el-form :model="filters" :inline="true"> + <el-form-item label="鍝佺墝鍚嶇О/鍥藉"> + <el-input + v-model="filters.name" + style="width: 240px" + placeholder="璇疯緭鍏ュ叧閿瘝" + clearable + prefix-icon="Search" + @change="getTableData" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" @click="getTableData">鎼滅储</el-button> + <el-button @click="resetFilters">閲嶇疆</el-button> + </el-form-item> + </el-form> + + <div class="table_list"> + <div class="actions"> + <div></div> + <div> + <el-button type="primary" @click="openAdd" icon="Plus"> 鏂板 </el-button> + <el-button + type="danger" + icon="Delete" + :disabled="multipleSelection.length <= 0" + @click="handleBatchDelete" + >鎵归噺鍒犻櫎</el-button> + </div> + </div> + + <PIMTable + rowKey="id" + isSelection + :column="columns" + :tableData="dataList" + :page="{ + current: pagination.currentPage, + size: pagination.pageSize, + total: pagination.total, + }" + @selection-change="handleSelectionChange" + @pagination="changePage" + > + </PIMTable> + </div> + + <el-dialog v-model="visible" :title="dialogTitle" width="520px" destroy-on-close> + <el-form :model="form" ref="formRef" :rules="rules" label-width="90px"> + <el-form-item label="鍝佺墝鍚嶇О" prop="name"> + <el-input v-model="form.name" placeholder="璇疯緭鍏ュ搧鐗屽悕绉�" /> + </el-form-item> + <el-form-item label="鎵�灞炲浗瀹�" prop="country"> + <el-input v-model="form.country" placeholder="璇疯緭鍏ュ浗瀹�/鍦板尯" /> + </el-form-item> + <el-form-item label="鎻忚堪" prop="description"> + <el-input v-model="form.description" type="textarea" :rows="3" placeholder="鍙~鍐欏搧鐗岀畝浠�" /> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="visible = false">鍙栨秷</el-button> + <el-button type="primary" @click="handleSubmit">纭畾</el-button> + </template> + </el-dialog> + </div> + +</template> + +<script setup> +import { ref, getCurrentInstance, onMounted } from 'vue' +import { ElMessageBox, ElMessage } from 'element-plus' +import { usePaginationApi } from '@/hooks/usePaginationApi' +import { getBrandPage, addBrand, editBrand, delBrand } from '@/api/equipmentManagement/brand' + +defineOptions({ name: '璁惧鍝佺墝绠$悊' }) + +const { proxy } = getCurrentInstance() + +const multipleSelection = ref([]) +const formRef = ref() +const visible = ref(false) +const dialogTitle = ref('鏂板鍝佺墝') +const form = ref({ id: undefined, name: '', country: '', description: '' }) + +const rules = { + name: [{ required: true, message: '璇疯緭鍏ュ搧鐗屽悕绉�', trigger: 'blur' }], + country: [{ required: true, message: '璇疯緭鍏ユ墍灞炲浗瀹�', trigger: 'blur' }] +} + +const { + filters, + columns, + dataList, + pagination, + getTableData, + resetFilters, + onCurrentChange, +} = usePaginationApi( + getBrandPage, + { name: undefined }, + [ + { label: '鍝佺墝鍚嶇О', align: 'center', prop: 'name' }, + { label: '鎵�灞炲浗瀹�', align: 'center', prop: 'country' }, + { label: '鎻忚堪', align: 'center', prop: 'description' }, + { label: '鍒涘缓鏃堕棿', align: 'center', prop: 'createdAt' }, + { + dataType: 'action', + label: '鎿嶄綔', + align: 'center', + fixed: 'right', + width: 140, + operation: [ + { + name: '缂栬緫', + type: 'text', + clickFun: (row) => openEdit(row), + }, + { + name: '鍒犻櫎', + type: 'text', + clickFun: (row) => handleDelete(row.id), + } + ] + } + ] +) + +const handleSelectionChange = (list) => { + multipleSelection.value = list +} + +const changePage = ({ page, limit }) => { + pagination.currentPage = page + pagination.pageSize = limit + onCurrentChange(page) +} + +function resetForm() { + form.value = { id: undefined, name: '', country: '', description: '' } +} + +function openAdd() { + resetForm() + dialogTitle.value = '鏂板鍝佺墝' + visible.value = true +} + +function openEdit(row) { + form.value = { id: row.id, name: row.name, country: row.country, description: row.description } + dialogTitle.value = '缂栬緫鍝佺墝' + visible.value = true +} + +function handleSubmit() { + formRef.value.validate(async (valid) => { + if (!valid) return + const isEdit = Boolean(form.value.id) + const api = isEdit ? editBrand : addBrand + const { code, msg } = await api({ ...form.value }) + if (code === 200) { + ElMessage.success(isEdit ? '淇敼鎴愬姛' : '鏂板鎴愬姛') + visible.value = false + getTableData() + } else { + ElMessage.error(msg || '鎿嶄綔澶辫触') + } + }) +} + +function handleDelete(id) { + ElMessageBox.confirm('姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ュ搧鐗�, 鏄惁缁х画?', '鎻愮ず', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning', + }).then(async () => { + const { code } = await delBrand(id) + if (code === 200) { + ElMessage.success('鍒犻櫎鎴愬姛') + getTableData() + } + }) +} + +function handleBatchDelete() { + if (multipleSelection.value.length === 0) return + ElMessageBox.confirm('灏嗗垹闄ら�変腑鐨勫搧鐗岋紝鏄惁缁х画锛�', '鎻愮ず', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning', + }).then(async () => { + const ids = multipleSelection.value.map((i) => i.id) + const { code } = await delBrand(ids) + if (code === 200) { + ElMessage.success('鍒犻櫎鎴愬姛') + getTableData() + } + }) +} + +onMounted(() => { + getTableData() +}) + +</script> + +<style scoped lang="scss"> +.table_list { margin-top: unset; } +.actions { + display: flex; + justify-content: space-between; + margin-bottom: 10px; +} +</style> + + diff --git a/src/views/equipmentManagement/ledger/Form.vue b/src/views/equipmentManagement/ledger/Form.vue index 0951fd8..108dbe4 100644 --- a/src/views/equipmentManagement/ledger/Form.vue +++ b/src/views/equipmentManagement/ledger/Form.vue @@ -12,13 +12,28 @@ </el-form-item> </el-col> <el-col :span="12"> + <el-form-item label="璁惧鍝佺墝" prop="deviceBrand"> + <el-input v-model="form.deviceBrand" placeholder="璇疯緭鍏ヨ澶囧搧鐗�" /> + </el-form-item> + </el-col> + <el-col :span="12"> <el-form-item label="渚涘簲鍟�" prop="supplierName"> <el-input v-model="form.supplierName" placeholder="璇疯緭鍏ヤ緵搴斿晢" /> </el-form-item> </el-col> <el-col :span="12"> + <el-form-item label="瀛樻斁浣嶇疆" prop="storageLocation"> + <el-input v-model="form.storageLocation" placeholder="璇疯緭鍏ュ瓨鏀句綅缃�" /> + </el-form-item> + </el-col> + <el-col :span="12"> <el-form-item label="鍗曚綅" prop="unit"> <el-input v-model="form.unit" placeholder="璇疯緭鍏ュ崟浣�" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="鍚敤鎶樻棫" prop="enableDepreciation"> + <el-switch v-model="form.enableDepreciation" :active-value="true" :inactive-value="false" /> </el-form-item> </el-col> <el-col :span="12"> @@ -133,7 +148,10 @@ const { form, resetForm } = useFormData({ deviceName: undefined, // 璁惧鍚嶇О deviceModel: undefined, // 瑙勬牸鍨嬪彿 + deviceBrand: undefined, // 璁惧鍝佺墝 supplierName: undefined, // 渚涘簲鍟� + storageLocation: undefined, // 瀛樻斁浣嶇疆 + enableDepreciation: false, // 鏄惁鍚敤鎶樻棫 unit: undefined, // 鍗曚綅 number: undefined, // 鏁伴噺 taxIncludingPriceUnit: undefined, // 鍚◣鍗曚环 @@ -152,7 +170,10 @@ if (code == 200) { form.deviceName = data.deviceName; form.deviceModel = data.deviceModel; + form.deviceBrand = data.deviceBrand; form.supplierName = data.supplierName; + form.storageLocation = data.storageLocation; + form.enableDepreciation = data.enableDepreciation; form.unit = data.unit; form.number = data.number; form.taxIncludingPriceUnit = data.taxIncludingPriceUnit; diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue index 28e5ec8..7356ada 100644 --- a/src/views/equipmentManagement/ledger/index.vue +++ b/src/views/equipmentManagement/ledger/index.vue @@ -146,6 +146,11 @@ prop: "deviceModel", }, { + label: "璁惧鍝佺墝", + align: "center", + prop: "deviceBrand", + }, + { label: "渚涘簲鍟�", align: "center", prop: "supplierName", @@ -154,6 +159,11 @@ label: "鍗曚綅", align: "center", prop: "unit", + }, + { + label: "瀛樻斁浣嶇疆", + align: "center", + prop: "storageLocation", }, { label: "鏁伴噺", @@ -181,6 +191,12 @@ prop: "unTaxIncludingPriceTotal", }, { + label: "鍚敤鎶樻棫", + align: "center", + prop: "enableDepreciation", + formatData: (v) => (v ? "鏄�" : "鍚�"), + }, + { label: "褰曞叆浜�", align: "center", prop: "createUser", diff --git a/src/views/procurementManagement/arrivalManagement/index.vue b/src/views/procurementManagement/arrivalManagement/index.vue new file mode 100644 index 0000000..0143538 --- /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: '2025-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..dd7c3c4 --- /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: '2025-12-01 18:30', + content: '鏂板閲囪喘璁㈠崟 PO20241201004', + type: 'primary' + }, + { + time: '2025-12-01 17:45', + content: '瀹屾垚璐ㄦ鍗� QI20241201002', + type: 'success' + }, + { + time: '2025-12-01 16:20', + content: '鍒拌揣鍗� AR20241201003 宸叉敹璐�', + type: 'success' + }, + { + time: '2025-12-01 15:15', + content: '浠锋牸璋冩暣锛氬晢鍝丅 浠� 楼80 璋冩暣涓� 楼75', + type: 'warning' + }, + { + time: '2025-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..006ce3c --- /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: '2025-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: '2025-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..79ca2fe --- /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: '2025-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..fd25ccf --- /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: '2025-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..d86e524 --- /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: '2025-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/qualityManagement/nonconformingManagement/index.vue b/src/views/qualityManagement/nonconformingManagement/index.vue index d3ac667..169a2f3 100644 --- a/src/views/qualityManagement/nonconformingManagement/index.vue +++ b/src/views/qualityManagement/nonconformingManagement/index.vue @@ -4,7 +4,7 @@ <div style="display: flex;flex-direction: row;align-items: center;"> <div> <span class="search_title">绫诲瀷锛�</span> - <el-select v-model="searchForm.inspectType" clearable style="width: 240px" @change="handleQuery"> + <el-select v-model="searchForm.inspectType" clearable style="width: 200px" @change="handleQuery"> <el-option label="鍘熸潗鏂欐楠�" :value="0" /> <el-option label="杩囩▼妫�楠�" :value="1" /> <el-option label="鍑哄巶妫�楠�" :value="2" /> @@ -12,7 +12,7 @@ </div> <div style="margin-left: 10px"> <span class="search_title">鐘舵�侊細</span> - <el-select v-model="searchForm.inspectState" clearable style="width: 240px" @change="handleQuery"> + <el-select v-model="searchForm.inspectState" clearable style="width: 200px" @change="handleQuery"> <el-option label="寰呭鐞�" :value="0" /> <el-option label="宸插鐞�" :value="1" /> </el-select> @@ -21,7 +21,7 @@ <span class="search_title">浜у搧鍚嶇О锛�</span> <el-input v-model="searchForm.productName" - style="width: 240px" + style="width: 200px" placeholder="璇疯緭鍏ヤ骇鍝佸悕绉版悳绱�" @change="handleQuery" clearable @@ -30,6 +30,7 @@ </div> <span style="margin-left: 10px" class="search_title">妫�娴嬫棩鏈燂細</span> <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" + style="width: 300px" placeholder="璇烽�夋嫨" clearable @change="changeDaterange" /> <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button> </div> diff --git a/src/views/qualityManagement/visualization/qualityDashboard.vue b/src/views/qualityManagement/visualization/qualityDashboard.vue new file mode 100644 index 0000000..57462b7 --- /dev/null +++ b/src/views/qualityManagement/visualization/qualityDashboard.vue @@ -0,0 +1,307 @@ +<template> + <div class="quality-dashboard"> + <el-row :gutter="16"> + <el-col :xs="24" :sm="12"> + <el-card shadow="hover" class="panel"> + <template #header> + <div class="panel-title"> + 妫�娴嬫牱鍝佸姩鎬佺姸鎬� + <div class="actions"> + <el-switch v-model="voiceEnabled" active-text="璇煶棰勮" inactive-text="闈欓煶" /> + </div> + </div> + </template> + <div class="status-list"> + <div v-for="item in sampleStatus" :key="item.id" class="status-item" :class="item.status"> + <div class="left"> + <span class="dot" :class="item.status"></span> + <span class="name">{{ item.name }}</span> + </div> + <div class="right"> + <el-tag :type="statusTagType(item.status)" size="small">{{ statusLabel(item.status) }}</el-tag> + <span class="time">{{ item.time }}</span> + </div> + </div> + </div> + </el-card> + </el-col> + <el-col :xs="24" :sm="12"> + <el-card shadow="hover" class="panel"> + <template #header> + <div class="panel-title">浠诲姟鎺掕锛圱op 10锛�</div> + </template> + <EChart :xAxis="tasksXAxis" :yAxis="[{ type: 'value' }]" :series="tasksSeries" :grid="{ left: 40, right: 20, top: 20, bottom: 40 }" :tooltip="{ trigger: 'axis' }" :barColors="['#3b82f6']" :chartStyle="{ height: '320px', width: '100%' }" /> + </el-card> + </el-col> + </el-row> + + <el-row :gutter="16" style="margin-top: 16px;"> + <el-col :xs="24" :sm="14"> + <el-card shadow="hover" class="panel"> + <template #header> + <div class="panel-title">鍘嗗彶瓒嬪娍</div> + </template> + <EChart :xAxis="[{ type: 'category', data: trendXAxis }]" :yAxis="[{ type: 'value', name: '鏁伴噺' }]" :series="trendSeries" :tooltip="{ trigger: 'axis' }" :legend="{ top: 0 }" :lineColors="['#10b981', '#f59e0b']" :chartStyle="{ height: '340px', width: '100%' }" /> + </el-card> + </el-col> + <el-col :xs="24" :sm="10"> + <el-card shadow="hover" class="panel"> + <template #header> + <div class="panel-title">鍚堟牸鐜囧垎鏋�</div> + </template> + <EChart :series="passRateSeries" :legend="{ show: false }" :chartStyle="{ height: '340px', width: '100%' }" /> + <div class="passrate-text"> + 褰撳墠鍚堟牸鐜囷細<b>{{ (passRate * 100).toFixed(1) }}%</b> + </div> + </el-card> + </el-col> + </el-row> + + <el-row :gutter="16" style="margin-top: 16px;"> + <el-col :xs="24"> + <el-card shadow="hover" class="panel"> + <template #header> + <div class="panel-title">SPC 鎺у埗鍥撅紙Xbar锛�</div> + </template> + <EChart :xAxis="[{ type: 'category', data: spcXAxis }]" :yAxis="[{ type: 'value', name: '娴嬮噺鍊�' }]" :series="spcSeries" :legend="{ top: 0 }" :tooltip="{ trigger: 'axis' }" :lineColors="['#2563eb', '#ef4444', '#f97316', '#22c55e']" :chartStyle="{ height: '380px', width: '100%' }" /> + </el-card> + </el-col> + </el-row> + </div> + +</template> + +<script setup> +import { onMounted, onBeforeUnmount, reactive, ref } from 'vue' +import EChart from '@/components/Echarts/echarts.vue' + +const voiceEnabled = ref(true) +let dataTimer = null + +// 1) 鏍峰搧鍔ㄦ�佺姸鎬侊紙婊氬姩鏇存柊锛� +const sampleStatus = ref([]) +const statusPool = ['processing', 'warning', 'error', 'success'] +function statusLabel(s) { + return s === 'processing' ? '妫�娴嬩腑' : s === 'warning' ? '棰勮' : s === 'error' ? '涓嶅悎鏍�' : '鍚堟牸' +} +function statusTagType(s) { + return s === 'processing' ? 'info' : s === 'warning' ? 'warning' : s === 'error' ? 'danger' : 'success' +} +function randomSample() { + const id = Math.random().toString(36).slice(2, 8) + const status = statusPool[Math.floor(Math.random() * statusPool.length)] + const name = `鏍峰搧-${Math.floor(Math.random() * 900 + 100)}` + const time = new Date().toLocaleTimeString('zh-CN', { hour12: false }) + return { id, name, status, time } +} + +// 2) 浠诲姟鎺掕锛堟煴鐘跺浘锛� +const tasksXAxis = reactive([{ type: 'category', data: [] }]) +const tasksSeries = ref([ + { + type: 'bar', + data: [], + label: { show: true, position: 'inside', align: 'center', verticalAlign: 'middle', color: '#fff' }, + encode: undefined, + }, +]) + +// 3) 鍘嗗彶瓒嬪娍锛堟姌绾匡級 +const trendXAxis = ref([]) +const trendSeries = ref([ + { name: '鏉ユ牱鏁�', type: 'line', smooth: true, data: [] }, + { name: '瀹屾垚鏁�', type: 'line', smooth: true, data: [] }, +]) + +// 4) 鍚堟牸鐜囧垎鏋愶紙浠〃鐩橈級 +const passRate = ref(0.92) +const passRateSeries = ref([ + { + type: 'gauge', + progress: { show: true, width: 12 }, + axisLine: { lineStyle: { width: 12 } }, + pointer: { show: true }, + detail: { valueAnimation: true, formatter: (v) => `${(v * 100).toFixed(1)}%` }, + data: [{ value: passRate.value }], + }, +]) + +// 5) SPC 鎺у埗鍥� +const spcXAxis = ref([]) +const spcData = ref([]) // 瀹為檯娴嬮噺鍊� +const CL = ref(50) +const UCL = ref(55) +const LCL = ref(45) +const spcSeries = ref([ + { + name: '娴嬮噺鍧囧��', + type: 'line', + smooth: false, + symbol: 'circle', + data: [], + markLine: { + symbol: 'none', + lineStyle: { type: 'dashed', color: '#999' }, + data: [ + { yAxis: () => UCL.value, name: 'UCL' }, + { yAxis: () => CL.value, name: 'CL' }, + { yAxis: () => LCL.value, name: 'LCL' }, + ], + label: { formatter: ({ name }) => name }, + }, + }, + { name: 'UCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#ef4444' } }, + { name: 'CL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#f97316' } }, + { name: 'LCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#22c55e' } }, +]) + +// 璇煶鎾姤 +function speak(text) { + if (!voiceEnabled.value) return + if (!('speechSynthesis' in window)) return + const utter = new SpeechSynthesisUtterance(text) + utter.lang = 'zh-CN' + try { + window.speechSynthesis.cancel() + window.speechSynthesis.speak(utter) + } catch (e) { + // ignore + } +} + +function refreshFakeData() { + // 鏍峰搧鐘舵�佹粴鍔� + const next = randomSample() + sampleStatus.value = [next, ...sampleStatus.value].slice(0, 8) + + // 浠诲姟鎺掕 + const tasks = Array.from({ length: 10 }).map((_, i) => ({ name: `浠诲姟-${i + 1}`, count: Math.floor(Math.random() * 100 + 20) })) + tasks.sort((a, b) => a.count - b.count) + tasksXAxis.data = tasks.map(t => t.name) + tasksSeries.value[0].data = tasks.map(t => t.count) + + // 鍘嗗彶瓒嬪娍锛堣拷鍔犵偣锛� + const nowLabel = new Date().toLocaleTimeString('zh-CN', { minute: '2-digit', second: '2-digit' }) + if (trendXAxis.value.length > 15) { + trendXAxis.value.shift() + trendSeries.value[0].data.shift() + trendSeries.value[1].data.shift() + } + trendXAxis.value.push(nowLabel) + const incoming = Math.floor(Math.random() * 30 + 20) + const finished = Math.max(0, incoming - Math.floor(Math.random() * 10)) + trendSeries.value[0].data.push(incoming) + trendSeries.value[1].data.push(finished) + + // 鍚堟牸鐜囷紙杞诲井娉㈠姩锛� + const delta = (Math.random() - 0.5) * 0.02 + passRate.value = Math.min(0.99, Math.max(0.6, passRate.value + delta)) + passRateSeries.value[0].data[0].value = passRate.value + + // SPC 鏁版嵁锛堢獥鍙gЩ鍔級 + const nextVal = CL.value + (Math.random() - 0.5) * 8 // 娉㈠姩 + if (spcXAxis.value.length > 30) { + spcXAxis.value.shift() + spcData.value.shift() + } + spcXAxis.value.push(`${spcXAxis.value.length + 1}`) + spcData.value.push(parseFloat(nextVal.toFixed(2))) + spcSeries.value[0].data = [...spcData.value] + spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value) + spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value) + spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value) + + // 瑙﹀彂鎾姤锛氬悎鏍肩巼杩囦綆鎴� SPC 瓒呴檺 + if (passRate.value < 0.8) { + speak(`棰勮锛屽綋鍓嶅悎鏍肩巼涓� ${(passRate.value * 100).toFixed(0)}%锛屼綆浜� 80% 闃堝�糮) + } + const last = spcData.value[spcData.value.length - 1] + if (last > UCL.value) { + speak(`棰勮锛屾渶鏂版祴閲忓�� ${last.toFixed(2)} 瓒呰繃涓婇檺`) + } + if (last < LCL.value) { + speak(`棰勮锛屾渶鏂版祴閲忓�� ${last.toFixed(2)} 浣庝簬涓嬮檺`) + } +} + +onMounted(() => { + // 鍒濆鍖栧嚑鏉″亣鏁版嵁 + sampleStatus.value = Array.from({ length: 5 }).map(() => randomSample()) + for (let i = 0; i < 10; i++) { + trendXAxis.value.push(`T-${i}`) + trendSeries.value[0].data.push(Math.floor(Math.random() * 30 + 20)) + trendSeries.value[1].data.push(Math.floor(Math.random() * 25 + 15)) + } + for (let i = 0; i < 20; i++) { + spcXAxis.value.push(`${i + 1}`) + const v = CL.value + (Math.random() - 0.5) * 6 + spcData.value.push(parseFloat(v.toFixed(2))) + } + spcSeries.value[0].data = [...spcData.value] + spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value) + spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value) + spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value) + + dataTimer = setInterval(refreshFakeData, 10000) +}) + +onBeforeUnmount(() => { + if (dataTimer) clearInterval(dataTimer) + try { window.speechSynthesis && window.speechSynthesis.cancel() } catch (e) {} +}) +</script> + +<style scoped> +.quality-dashboard { + padding: 8px; +} +.panel-title { + display: flex; + align-items: center; + justify-content: space-between; + font-weight: 600; +} +.status-list { + display: flex; + flex-direction: column; + gap: 8px; + max-height: 320px; + overflow: auto; +} +.status-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 10px; + border-radius: 6px; + background: var(--el-fill-color-light); +} +.status-item .left { + display: flex; + align-items: center; + gap: 8px; +} +.status-item .right { + display: flex; + align-items: center; + gap: 10px; +} +.status-item .name { font-weight: 500; } +.status-item .time { color: var(--el-text-color-secondary); font-size: 12px; } +.dot { + width: 8px; + height: 8px; + border-radius: 50%; + display: inline-block; +} +.dot.processing { background: #60a5fa; } +.dot.warning { background: #f59e0b; } +.dot.error { background: #ef4444; } +.dot.success { background: #10b981; } +.passrate-text { + text-align: center; + margin-top: 8px; +} +</style> + + diff --git a/src/views/reportAnalysis/reportManagement.vue b/src/views/reportAnalysis/reportManagement.vue new file mode 100644 index 0000000..3c87550 --- /dev/null +++ b/src/views/reportAnalysis/reportManagement.vue @@ -0,0 +1,733 @@ +<template> + <div class="report-management"> + <!-- 椤甸潰鏍囬 --> + <div class="page-header"> + <h2>鎶ヨ〃绠$悊</h2> + <p>鎻愪緵鏍峰搧杩涘害銆佽澶囦娇鐢ㄣ�佹娴嬮」鐩�侀鐢ㄨ褰曠瓑鍚勭被鑷姩缁熻鎶ヨ〃</p> + </div> + + <!-- 绛涢�夋潯浠� --> + <el-card class="filter-card" shadow="never"> + <el-form :model="filterForm" inline> + <el-form-item label="鏃堕棿鑼冨洿"> + <el-date-picker + v-model="filterForm.dateRange" + type="daterange" + range-separator="鑷�" + start-placeholder="寮�濮嬫棩鏈�" + end-placeholder="缁撴潫鏃ユ湡" + format="YYYY-MM-DD" + value-format="YYYY-MM-DD" + @change="handleFilterChange" + /> + </el-form-item> + <el-form-item label="鎶ヨ〃绫诲瀷"> + <el-select v-model="filterForm.reportType" placeholder="璇烽�夋嫨鎶ヨ〃绫诲瀷" @change="handleFilterChange"> + <el-option label="鏍峰搧杩涘害鎶ヨ〃" value="sample" /> + <el-option label="璁惧浣跨敤鎶ヨ〃" value="equipment" /> + <el-option label="妫�娴嬮」鐩姤琛�" value="inspection" /> + <el-option label="棰嗙敤璁板綍鎶ヨ〃" value="usage" /> + </el-select> + </el-form-item> + <el-form-item> + <el-button type="primary" @click="handleFilterChange">鏌ヨ</el-button> + <el-button @click="resetFilter">閲嶇疆</el-button> + <el-button type="success" @click="exportReport">瀵煎嚭鎶ヨ〃</el-button> + </el-form-item> + </el-form> + </el-card> + + <!-- 缁熻鍗$墖 --> + <div class="statistics-cards"> + <el-row :gutter="20"> + <el-col :span="6"> + <el-card class="stat-card" shadow="hover"> + <div class="stat-content"> + <div class="stat-icon"> + <el-icon><Box /></el-icon> + </div> + <div class="stat-info"> + <div class="stat-number">{{ statistics.totalSamples }}</div> + <div class="stat-label">鎬绘牱鍝佹暟</div> + </div> + </div> + </el-card> + </el-col> + <el-col :span="6"> + <el-card class="stat-card" shadow="hover"> + <div class="stat-content"> + <div class="stat-icon"> + <el-icon><Tools /></el-icon> + </div> + <div class="stat-info"> + <div class="stat-number">{{ statistics.activeEquipment }}</div> + <div class="stat-label">鍦ㄧ敤璁惧</div> + </div> + </div> + </el-card> + </el-col> + <el-col :span="6"> + <el-card class="stat-card" shadow="hover"> + <div class="stat-content"> + <div class="stat-icon"> + <el-icon><Document /></el-icon> + </div> + <div class="stat-info"> + <div class="stat-number">{{ statistics.completedInspections }}</div> + <div class="stat-label">宸插畬鎴愭娴�</div> + </div> + </div> + </el-card> + </el-col> + <el-col :span="6"> + <el-card class="stat-card" shadow="hover"> + <div class="stat-content"> + <div class="stat-icon"> + <el-icon><ShoppingCart /></el-icon> + </div> + <div class="stat-info"> + <div class="stat-number">{{ statistics.totalUsage }}</div> + <div class="stat-label">鎬婚鐢ㄦ鏁�</div> + </div> + </div> + </el-card> + </el-col> + </el-row> + </div> + + <!-- 鍥捐〃鍖哄煙 --> + <div class="charts-container"> + <el-row :gutter="20"> + <!-- 鏍峰搧杩涘害鍥捐〃 --> + <el-col :span="12"> + <el-card class="chart-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>鏍峰搧杩涘害缁熻</span> + <el-button type="text" @click="refreshSampleChart">鍒锋柊</el-button> + </div> + </template> + <div ref="sampleChartRef" class="chart-container"></div> + </el-card> + </el-col> + + <!-- 璁惧浣跨敤鍥捐〃 --> + <el-col :span="12"> + <el-card class="chart-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>璁惧浣跨敤鐜囩粺璁�</span> + <el-button type="text" @click="refreshEquipmentChart">鍒锋柊</el-button> + </div> + </template> + <div ref="equipmentChartRef" class="chart-container"></div> + </el-card> + </el-col> + </el-row> + + <el-row :gutter="20" style="margin-top: 20px;"> + <!-- 妫�娴嬮」鐩粺璁� --> + <el-col :span="12"> + <el-card class="chart-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>妫�娴嬮」鐩垎甯�</span> + <el-button type="text" @click="refreshInspectionChart">鍒锋柊</el-button> + </div> + </template> + <div ref="inspectionChartRef" class="chart-container"></div> + </el-card> + </el-col> + + <!-- 棰嗙敤璁板綍瓒嬪娍 --> + <el-col :span="12"> + <el-card class="chart-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>棰嗙敤璁板綍瓒嬪娍</span> + <el-button type="text" @click="refreshUsageChart">鍒锋柊</el-button> + </div> + </template> + <div ref="usageChartRef" class="chart-container"></div> + </el-card> + </el-col> + </el-row> + </div> + + <!-- 璇︾粏鏁版嵁琛ㄦ牸 --> + <el-card class="table-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>璇︾粏鏁版嵁</span> + <div> + <el-button type="primary" size="small" @click="refreshTable">鍒锋柊</el-button> + <el-button type="success" size="small" @click="exportTable">瀵煎嚭</el-button> + </div> + </div> + </template> + + <el-table + :data="tableData" + style="width: 100%" + v-loading="tableLoading" + stripe + border + > + <el-table-column prop="id" label="缂栧彿" width="80" /> + <el-table-column prop="name" label="鍚嶇О" /> + <el-table-column prop="type" label="绫诲瀷" width="120" /> + <el-table-column prop="status" label="鐘舵��" width="100"> + <template #default="scope"> + <el-tag :type="getStatusType(scope.row.status)"> + {{ scope.row.status }} + </el-tag> + </template> + </el-table-column> + <el-table-column prop="progress" label="杩涘害" width="120"> + <template #default="scope"> + <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" /> + </template> + </el-table-column> + <el-table-column prop="createTime" label="鍒涘缓鏃堕棿" width="180" /> + <el-table-column prop="updateTime" label="鏇存柊鏃堕棿" width="180" /> + <el-table-column label="鎿嶄綔" width="150" fixed="right"> + <template #default="scope"> + <el-button type="text" size="small" @click="viewDetail(scope.row)">鏌ョ湅</el-button> + <el-button type="text" size="small" @click="editItem(scope.row)">缂栬緫</el-button> + </template> + </el-table-column> + </el-table> + + <div class="pagination-container"> + <el-pagination + v-model:current-page="pagination.currentPage" + v-model:page-size="pagination.pageSize" + :page-sizes="[10, 20, 50, 100]" + :total="pagination.total" + layout="total, sizes, prev, pager, next, jumper" + @size-change="handleSizeChange" + @current-change="handleCurrentChange" + /> + </div> + </el-card> + </div> +</template> + +<script setup> +import { ref, reactive, onMounted, nextTick } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import * as echarts from 'echarts' +import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue' + +// 鍝嶅簲寮忔暟鎹� +const filterForm = reactive({ + dateRange: [], + reportType: 'sample' +}) + +const statistics = reactive({ + totalSamples: 1250, + activeEquipment: 45, + completedInspections: 890, + totalUsage: 2340 +}) + +const tableData = ref([]) +const tableLoading = ref(false) +const pagination = reactive({ + currentPage: 1, + pageSize: 20, + total: 0 +}) + +// 鍥捐〃寮曠敤 +const sampleChartRef = ref(null) +const equipmentChartRef = ref(null) +const inspectionChartRef = ref(null) +const usageChartRef = ref(null) + +// 鍥捐〃瀹炰緥 +let sampleChart = null +let equipmentChart = null +let inspectionChart = null +let usageChart = null + +// 鍒濆鍖栨暟鎹� +const initData = () => { + // 妯℃嫙琛ㄦ牸鏁版嵁 + tableData.value = [ + { + id: 'SP001', + name: '鏍峰搧A-001', + type: '閲戝睘鏉愭枡', + status: '妫�娴嬩腑', + progress: 75, + createTime: '2025-01-15 09:30:00', + updateTime: '2025-01-20 14:20:00' + }, + { + id: 'SP002', + name: '鏍峰搧B-002', + type: '濉戞枡鍒跺搧', + status: '宸插畬鎴�', + progress: 100, + createTime: '2025-01-10 10:15:00', + updateTime: '2025-01-18 16:45:00' + }, + { + id: 'SP003', + name: '鏍峰搧C-003', + type: '鐢靛瓙鍏冧欢', + status: '寰呮娴�', + progress: 0, + createTime: '2025-01-22 08:45:00', + updateTime: '2025-01-22 08:45:00' + }, + { + id: 'EQ001', + name: '妫�娴嬭澶嘇', + type: '鍏夎氨浠�', + status: '浣跨敤涓�', + progress: 60, + createTime: '2025-01-05 14:20:00', + updateTime: '2025-01-20 11:30:00' + }, + { + id: 'EQ002', + name: '妫�娴嬭澶嘊', + type: '鏄惧井闀�', + status: '绌洪棽', + progress: 0, + createTime: '2025-01-08 16:10:00', + updateTime: '2025-01-19 09:15:00' + } + ] + + pagination.total = tableData.value.length +} + +// 鍒濆鍖栨牱鍝佽繘搴﹀浘琛� +const initSampleChart = () => { + if (sampleChartRef.value) { + sampleChart = echarts.init(sampleChartRef.value) + const option = { + title: { + text: '鏍峰搧杩涘害鍒嗗竷', + left: 'center' + }, + tooltip: { + trigger: 'item', + formatter: '{a} <br/>{b}: {c} ({d}%)' + }, + legend: { + orient: 'vertical', + left: 'left' + }, + series: [ + { + name: '鏍峰搧鐘舵��', + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: false, + label: { + show: false, + position: 'center' + }, + emphasis: { + label: { + show: true, + fontSize: '18', + fontWeight: 'bold' + } + }, + labelLine: { + show: false + }, + data: [ + { value: 450, name: '宸插畬鎴�' }, + { value: 320, name: '妫�娴嬩腑' }, + { value: 280, name: '寰呮娴�' }, + { value: 200, name: '宸叉殏鍋�' } + ] + } + ] + } + sampleChart.setOption(option) + } +} + +// 鍒濆鍖栬澶囦娇鐢ㄥ浘琛� +const initEquipmentChart = () => { + if (equipmentChartRef.value) { + equipmentChart = echarts.init(equipmentChartRef.value) + const option = { + title: { + text: '璁惧浣跨敤鐜�', + left: 'center' + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + xAxis: { + type: 'category', + data: ['鍏夎氨浠�', '鏄惧井闀�', '纭害璁�', '鎷夊姏鏈�', '鍐插嚮鏈�', '閲戠浉浠�'] + }, + yAxis: { + type: 'value', + name: '浣跨敤鐜�(%)' + }, + series: [ + { + name: '浣跨敤鐜�', + type: 'bar', + data: [85, 60, 75, 90, 45, 70], + label: { + show: true, + position: 'inside', + align: 'center', + verticalAlign: 'middle', + formatter: '{c}%', + color: '#fff' + }, + itemStyle: { + color: function(params) { + const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0'] + return colors[params.dataIndex] + } + } + } + ] + } + equipmentChart.setOption(option) + } +} + +// 鍒濆鍖栨娴嬮」鐩浘琛� +const initInspectionChart = () => { + if (inspectionChartRef.value) { + inspectionChart = echarts.init(inspectionChartRef.value) + const option = { + title: { + text: '妫�娴嬮」鐩垎甯�', + left: 'center' + }, + tooltip: { + trigger: 'item' + }, + legend: { + orient: 'vertical', + left: 'left' + }, + series: [ + { + name: '妫�娴嬮」鐩�', + type: 'pie', + radius: '50%', + data: [ + { value: 335, name: '鐗╃悊鎬ц兘' }, + { value: 310, name: '鍖栧鍒嗘瀽' }, + { value: 234, name: '灏哄娴嬮噺' }, + { value: 135, name: '澶栬妫�鏌�' }, + { value: 148, name: '鍏朵粬妫�娴�' } + ], + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)' + } + } + } + ] + } + inspectionChart.setOption(option) + } +} + +// 鍒濆鍖栭鐢ㄨ褰曞浘琛� +const initUsageChart = () => { + if (usageChartRef.value) { + usageChart = echarts.init(usageChartRef.value) + const option = { + title: { + text: '棰嗙敤璁板綍瓒嬪娍', + left: 'center' + }, + tooltip: { + trigger: 'axis' + }, + legend: { + data: ['棰嗙敤娆℃暟', '褰掕繕娆℃暟'] + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�', '7鏈�', '8鏈�', '9鏈�', '10鏈�', '11鏈�', '12鏈�'] + }, + yAxis: { + type: 'value' + }, + series: [ + { + name: '棰嗙敤娆℃暟', + type: 'line', + stack: 'Total', + data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330] + }, + { + name: '褰掕繕娆℃暟', + type: 'line', + stack: 'Total', + data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320] + } + ] + } + usageChart.setOption(option) + } +} + +// 浜嬩欢澶勭悊鍑芥暟 +const handleFilterChange = () => { + ElMessage.success('绛涢�夋潯浠跺凡鏇存柊') + // 杩欓噷鍙互鏍规嵁绛涢�夋潯浠堕噸鏂板姞杞芥暟鎹� +} + +const resetFilter = () => { + filterForm.dateRange = [] + filterForm.reportType = 'sample' + ElMessage.info('绛涢�夋潯浠跺凡閲嶇疆') +} + +const exportReport = () => { + ElMessage.success('鎶ヨ〃瀵煎嚭鍔熻兘寮�鍙戜腑...') +} + +const refreshSampleChart = () => { + initSampleChart() + ElMessage.success('鏍峰搧杩涘害鍥捐〃宸插埛鏂�') +} + +const refreshEquipmentChart = () => { + initEquipmentChart() + ElMessage.success('璁惧浣跨敤鍥捐〃宸插埛鏂�') +} + +const refreshInspectionChart = () => { + initInspectionChart() + ElMessage.success('妫�娴嬮」鐩浘琛ㄥ凡鍒锋柊') +} + +const refreshUsageChart = () => { + initUsageChart() + ElMessage.success('棰嗙敤璁板綍鍥捐〃宸插埛鏂�') +} + +const refreshTable = () => { + tableLoading.value = true + setTimeout(() => { + tableLoading.value = false + ElMessage.success('琛ㄦ牸鏁版嵁宸插埛鏂�') + }, 1000) +} + +const exportTable = () => { + ElMessage.success('琛ㄦ牸瀵煎嚭鍔熻兘寮�鍙戜腑...') +} + +const handleSizeChange = (val) => { + pagination.pageSize = val + // 閲嶆柊鍔犺浇鏁版嵁 +} + +const handleCurrentChange = (val) => { + pagination.currentPage = val + // 閲嶆柊鍔犺浇鏁版嵁 +} + +const getStatusType = (status) => { + const statusMap = { + '宸插畬鎴�': 'success', + '妫�娴嬩腑': 'warning', + '寰呮娴�': 'info', + '宸叉殏鍋�': 'danger', + '浣跨敤涓�': 'primary', + '绌洪棽': 'info' + } + return statusMap[status] || 'info' +} + +const getProgressStatus = (progress) => { + if (progress === 100) return 'success' + if (progress >= 80) return 'warning' + if (progress >= 50) return '' + return 'exception' +} + +const viewDetail = (row) => { + ElMessage.info(`鏌ョ湅璇︽儏: ${row.name}`) +} + +const editItem = (row) => { + ElMessage.info(`缂栬緫椤圭洰: ${row.name}`) +} + +// 鐢熷懡鍛ㄦ湡 +onMounted(() => { + initData() + nextTick(() => { + initSampleChart() + initEquipmentChart() + initInspectionChart() + initUsageChart() + }) + + // 鐩戝惉绐楀彛澶у皬鍙樺寲锛岄噸鏂拌皟鏁村浘琛ㄥぇ灏� + window.addEventListener('resize', () => { + sampleChart?.resize() + equipmentChart?.resize() + inspectionChart?.resize() + usageChart?.resize() + }) +}) +</script> + +<style scoped> +.report-management { + padding: 20px; + background-color: #f5f5f5; + min-height: 100vh; +} + +.page-header { + margin-bottom: 20px; + text-align: center; +} + +.page-header h2 { + color: #303133; + margin-bottom: 8px; + font-size: 24px; + font-weight: 600; +} + +.page-header p { + color: #909399; + font-size: 14px; + margin: 0; +} + +.filter-card { + margin-bottom: 20px; +} + +.statistics-cards { + margin-bottom: 20px; +} + +.stat-card { + height: 120px; +} + +.stat-content { + display: flex; + align-items: center; + height: 100%; +} + +.stat-icon { + width: 60px; + height: 60px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-right: 20px; + font-size: 24px; + color: white; +} + +.stat-card:nth-child(1) .stat-icon { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.stat-card:nth-child(2) .stat-icon { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); +} + +.stat-card:nth-child(3) .stat-icon { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); +} + +.stat-card:nth-child(4) .stat-icon { + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); +} + +.stat-info { + flex: 1; +} + +.stat-number { + font-size: 28px; + font-weight: bold; + color: #303133; + margin-bottom: 8px; +} + +.stat-label { + font-size: 14px; + color: #909399; +} + +.charts-container { + margin-bottom: 20px; +} + +.chart-card { + margin-bottom: 20px; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.chart-container { + height: 300px; + width: 100%; +} + +.table-card { + margin-bottom: 20px; +} + +.pagination-container { + margin-top: 20px; + text-align: right; +} + +:deep(.el-card__header) { + padding: 15px 20px; + border-bottom: 1px solid #ebeef5; + background-color: #fafafa; +} + +:deep(.el-card__body) { + padding: 20px; +} + +:deep(.el-table) { + margin-bottom: 20px; +} + +:deep(.el-progress) { + margin: 0; +} + +:deep(.el-tag) { + margin: 0; +} +</style> diff --git a/src/views/reportAnalysis/reportManagement/index.vue b/src/views/reportAnalysis/reportManagement/index.vue new file mode 100644 index 0000000..3248712 --- /dev/null +++ b/src/views/reportAnalysis/reportManagement/index.vue @@ -0,0 +1,716 @@ +<template> + <div class="report-management"> + <!-- 绛涢�夋潯浠� --> + <el-card class="filter-card" shadow="never"> + <el-form :model="filterForm" inline> + <el-form-item label="鏃堕棿鑼冨洿"> + <el-date-picker + style="width: 300px" + v-model="filterForm.dateRange" + type="daterange" + range-separator="鑷�" + start-placeholder="寮�濮嬫棩鏈�" + end-placeholder="缁撴潫鏃ユ湡" + format="YYYY-MM-DD" + value-format="YYYY-MM-DD" + @change="handleFilterChange" + /> + </el-form-item> + <el-form-item label="鎶ヨ〃绫诲瀷"> + <el-select v-model="filterForm.reportType" placeholder="璇烽�夋嫨鎶ヨ〃绫诲瀷" @change="handleFilterChange" style="width: 300px"> + <el-option label="鏍峰搧杩涘害鎶ヨ〃" value="sample" /> + <el-option label="璁惧浣跨敤鎶ヨ〃" value="equipment" /> + <el-option label="妫�娴嬮」鐩姤琛�" value="inspection" /> + <el-option label="棰嗙敤璁板綍鎶ヨ〃" value="usage" /> + </el-select> + </el-form-item> + <el-form-item> + <el-button type="primary" @click="handleFilterChange">鏌ヨ</el-button> + <el-button @click="resetFilter">閲嶇疆</el-button> + <el-button type="success" @click="exportReport">瀵煎嚭鎶ヨ〃</el-button> + </el-form-item> + </el-form> + </el-card> + + <!-- 缁熻鍗$墖 --> + <div class="statistics-cards"> + <el-row :gutter="20"> + <el-col :span="6"> + <el-card class="stat-card" shadow="hover"> + <div class="stat-content"> + <div class="stat-icon"> + <el-icon><Box /></el-icon> + </div> + <div class="stat-info"> + <div class="stat-number">{{ statistics.totalSamples }}</div> + <div class="stat-label">鎬绘牱鍝佹暟</div> + </div> + </div> + </el-card> + </el-col> + <el-col :span="6"> + <el-card class="stat-card" shadow="hover"> + <div class="stat-content"> + <div class="stat-icon"> + <el-icon><Tools /></el-icon> + </div> + <div class="stat-info"> + <div class="stat-number">{{ statistics.activeEquipment }}</div> + <div class="stat-label">鍦ㄧ敤璁惧</div> + </div> + </div> + </el-card> + </el-col> + <el-col :span="6"> + <el-card class="stat-card" shadow="hover"> + <div class="stat-content"> + <div class="stat-icon"> + <el-icon><Document /></el-icon> + </div> + <div class="stat-info"> + <div class="stat-number">{{ statistics.completedInspections }}</div> + <div class="stat-label">宸插畬鎴愭娴�</div> + </div> + </div> + </el-card> + </el-col> + <el-col :span="6"> + <el-card class="stat-card" shadow="hover"> + <div class="stat-content"> + <div class="stat-icon"> + <el-icon><ShoppingCart /></el-icon> + </div> + <div class="stat-info"> + <div class="stat-number">{{ statistics.totalUsage }}</div> + <div class="stat-label">鎬婚鐢ㄦ鏁�</div> + </div> + </div> + </el-card> + </el-col> + </el-row> + </div> + + <!-- 鍥捐〃鍖哄煙 --> + <div class="charts-container"> + <el-row :gutter="20"> + <!-- 鏍峰搧杩涘害鍥捐〃 --> + <el-col :span="12"> + <el-card class="chart-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>鏍峰搧杩涘害缁熻</span> + <el-button link @click="refreshSampleChart">鍒锋柊</el-button> + </div> + </template> + <div ref="sampleChartRef" class="chart-container"></div> + </el-card> + </el-col> + + <!-- 璁惧浣跨敤鍥捐〃 --> + <el-col :span="12"> + <el-card class="chart-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>璁惧浣跨敤鐜囩粺璁�</span> + <el-button link @click="refreshEquipmentChart">鍒锋柊</el-button> + </div> + </template> + <div ref="equipmentChartRef" class="chart-container"></div> + </el-card> + </el-col> + </el-row> + + <el-row :gutter="20" style="margin-top: 20px;"> + <!-- 妫�娴嬮」鐩粺璁� --> + <el-col :span="12"> + <el-card class="chart-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>妫�娴嬮」鐩垎甯�</span> + <el-button link @click="refreshInspectionChart">鍒锋柊</el-button> + </div> + </template> + <div ref="inspectionChartRef" class="chart-container"></div> + </el-card> + </el-col> + + <!-- 棰嗙敤璁板綍瓒嬪娍 --> + <el-col :span="12"> + <el-card class="chart-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>棰嗙敤璁板綍瓒嬪娍</span> + <el-button link @click="refreshUsageChart">鍒锋柊</el-button> + </div> + </template> + <div ref="usageChartRef" class="chart-container"></div> + </el-card> + </el-col> + </el-row> + </div> + + <!-- 璇︾粏鏁版嵁琛ㄦ牸 --> + <el-card class="table-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>璇︾粏鏁版嵁</span> + <div> + <el-button type="primary" size="small" @click="refreshTable">鍒锋柊</el-button> + <el-button type="success" size="small" @click="exportTable">瀵煎嚭</el-button> + </div> + </div> + </template> + + <el-table + :data="tableData" + style="width: 100%" + v-loading="tableLoading" + stripe + border + > + <el-table-column prop="id" label="缂栧彿" width="80" /> + <el-table-column prop="name" label="鍚嶇О" /> + <el-table-column prop="type" label="绫诲瀷" width="120" /> + <el-table-column prop="status" label="鐘舵��" width="100"> + <template #default="scope"> + <el-tag :type="getStatusType(scope.row.status)"> + {{ scope.row.status }} + </el-tag> + </template> + </el-table-column> + <el-table-column prop="progress" label="杩涘害" width="120"> + <template #default="scope"> + <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" /> + </template> + </el-table-column> + <el-table-column prop="createTime" label="鍒涘缓鏃堕棿" width="180" /> + <el-table-column prop="updateTime" label="鏇存柊鏃堕棿" width="180" /> + <el-table-column label="鎿嶄綔" width="150" fixed="right"> + <template #default="scope"> + <el-button link size="small" @click="viewDetail(scope.row)">鏌ョ湅</el-button> + <el-button link size="small" @click="editItem(scope.row)">缂栬緫</el-button> + </template> + </el-table-column> + </el-table> + + <div class="pagination-container"> + <el-pagination + v-model:current-page="pagination.currentPage" + v-model:page-size="pagination.pageSize" + :page-sizes="[10, 20, 50, 100]" + :total="pagination.total" + layout="total, sizes, prev, pager, next, jumper" + @size-change="handleSizeChange" + @current-change="handleCurrentChange" + /> + </div> + </el-card> + </div> +</template> + +<script setup> +import { ref, reactive, onMounted, nextTick } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import * as echarts from 'echarts' +import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue' + +// 鍝嶅簲寮忔暟鎹� +const filterForm = reactive({ + dateRange: [], + reportType: 'sample' +}) + +const statistics = reactive({ + totalSamples: 1250, + activeEquipment: 45, + completedInspections: 890, + totalUsage: 2340 +}) + +const tableData = ref([]) +const tableLoading = ref(false) +const pagination = reactive({ + currentPage: 1, + pageSize: 20, + total: 0 +}) + +// 鍥捐〃寮曠敤 +const sampleChartRef = ref(null) +const equipmentChartRef = ref(null) +const inspectionChartRef = ref(null) +const usageChartRef = ref(null) + +// 鍥捐〃瀹炰緥 +let sampleChart = null +let equipmentChart = null +let inspectionChart = null +let usageChart = null + +// 鍒濆鍖栨暟鎹� +const initData = () => { + // 妯℃嫙琛ㄦ牸鏁版嵁 + tableData.value = [ + { + id: 'SP001', + name: '鏍峰搧A-001', + type: '閲戝睘鏉愭枡', + status: '妫�娴嬩腑', + progress: 75, + createTime: '2025-01-15 09:30:00', + updateTime: '2025-01-20 14:20:00' + }, + { + id: 'SP002', + name: '鏍峰搧B-002', + type: '濉戞枡鍒跺搧', + status: '宸插畬鎴�', + progress: 100, + createTime: '2025-01-10 10:15:00', + updateTime: '2025-01-18 16:45:00' + }, + { + id: 'SP003', + name: '鏍峰搧C-003', + type: '鐢靛瓙鍏冧欢', + status: '寰呮娴�', + progress: 0, + createTime: '2025-01-22 08:45:00', + updateTime: '2025-01-22 08:45:00' + }, + { + id: 'EQ001', + name: '妫�娴嬭澶嘇', + type: '鍏夎氨浠�', + status: '浣跨敤涓�', + progress: 60, + createTime: '2025-01-05 14:20:00', + updateTime: '2025-01-20 11:30:00' + }, + { + id: 'EQ002', + name: '妫�娴嬭澶嘊', + type: '鏄惧井闀�', + status: '绌洪棽', + progress: 0, + createTime: '2025-01-08 16:10:00', + updateTime: '2025-01-19 09:15:00' + } + ] + + pagination.total = tableData.value.length +} + +// 鍒濆鍖栨牱鍝佽繘搴﹀浘琛� +const initSampleChart = () => { + if (sampleChartRef.value) { + sampleChart = echarts.init(sampleChartRef.value) + const option = { + title: { + show: false + }, + tooltip: { + trigger: 'item', + formatter: '{a} <br/>{b}: {c} ({d}%)' + }, + legend: { + orient: 'vertical', + left: 'left' + }, + series: [ + { + name: '鏍峰搧鐘舵��', + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: false, + label: { + show: false, + position: 'center' + }, + emphasis: { + label: { + show: true, + fontSize: '18', + fontWeight: 'bold' + } + }, + labelLine: { + show: false + }, + data: [ + { value: 450, name: '宸插畬鎴�' }, + { value: 320, name: '妫�娴嬩腑' }, + { value: 280, name: '寰呮娴�' }, + { value: 200, name: '宸叉殏鍋�' } + ] + } + ] + } + sampleChart.setOption(option) + } +} + +// 鍒濆鍖栬澶囦娇鐢ㄥ浘琛� +const initEquipmentChart = () => { + if (equipmentChartRef.value) { + equipmentChart = echarts.init(equipmentChartRef.value) + const option = { + title: { + show: false + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + xAxis: { + type: 'category', + data: ['鍏夎氨浠�', '鏄惧井闀�', '纭害璁�', '鎷夊姏鏈�', '鍐插嚮鏈�', '閲戠浉浠�'] + }, + yAxis: { + type: 'value', + name: '浣跨敤鐜�(%)' + }, + series: [ + { + name: '浣跨敤鐜�', + type: 'bar', + data: [85, 60, 75, 90, 45, 70], + itemStyle: { + color: function(params) { + const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0'] + return colors[params.dataIndex] + } + } + } + ] + } + equipmentChart.setOption(option) + } +} + +// 鍒濆鍖栨娴嬮」鐩浘琛� +const initInspectionChart = () => { + if (inspectionChartRef.value) { + inspectionChart = echarts.init(inspectionChartRef.value) + const option = { + title: { + show: false + }, + tooltip: { + trigger: 'item' + }, + legend: { + orient: 'vertical', + left: 'left' + }, + series: [ + { + name: '妫�娴嬮」鐩�', + type: 'pie', + radius: '50%', + data: [ + { value: 335, name: '鐗╃悊鎬ц兘' }, + { value: 310, name: '鍖栧鍒嗘瀽' }, + { value: 234, name: '灏哄娴嬮噺' }, + { value: 135, name: '澶栬妫�鏌�' }, + { value: 148, name: '鍏朵粬妫�娴�' } + ], + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)' + } + } + } + ] + } + inspectionChart.setOption(option) + } +} + +// 鍒濆鍖栭鐢ㄨ褰曞浘琛� +const initUsageChart = () => { + if (usageChartRef.value) { + usageChart = echarts.init(usageChartRef.value) + const option = { + title: { + show: false + }, + tooltip: { + trigger: 'axis' + }, + legend: { + data: ['棰嗙敤娆℃暟', '褰掕繕娆℃暟'] + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�', '7鏈�', '8鏈�', '9鏈�', '10鏈�', '11鏈�', '12鏈�'] + }, + yAxis: { + type: 'value' + }, + series: [ + { + name: '棰嗙敤娆℃暟', + type: 'line', + stack: 'Total', + data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330] + }, + { + name: '褰掕繕娆℃暟', + type: 'line', + stack: 'Total', + data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320] + } + ] + } + usageChart.setOption(option) + } +} + +// 浜嬩欢澶勭悊鍑芥暟 +const handleFilterChange = () => { + ElMessage.success('绛涢�夋潯浠跺凡鏇存柊') + // 杩欓噷鍙互鏍规嵁绛涢�夋潯浠堕噸鏂板姞杞芥暟鎹� +} + +const resetFilter = () => { + filterForm.dateRange = [] + filterForm.reportType = 'sample' + ElMessage.info('绛涢�夋潯浠跺凡閲嶇疆') +} + +const exportReport = () => { + ElMessage.success('鎶ヨ〃瀵煎嚭鍔熻兘寮�鍙戜腑...') +} + +const refreshSampleChart = () => { + initSampleChart() + ElMessage.success('鏍峰搧杩涘害鍥捐〃宸插埛鏂�') +} + +const refreshEquipmentChart = () => { + initEquipmentChart() + ElMessage.success('璁惧浣跨敤鍥捐〃宸插埛鏂�') +} + +const refreshInspectionChart = () => { + initInspectionChart() + ElMessage.success('妫�娴嬮」鐩浘琛ㄥ凡鍒锋柊') +} + +const refreshUsageChart = () => { + initUsageChart() + ElMessage.success('棰嗙敤璁板綍鍥捐〃宸插埛鏂�') +} + +const refreshTable = () => { + tableLoading.value = true + setTimeout(() => { + tableLoading.value = false + ElMessage.success('琛ㄦ牸鏁版嵁宸插埛鏂�') + }, 1000) +} + +const exportTable = () => { + ElMessage.success('琛ㄦ牸瀵煎嚭鍔熻兘寮�鍙戜腑...') +} + +const handleSizeChange = (val) => { + pagination.pageSize = val + // 閲嶆柊鍔犺浇鏁版嵁 +} + +const handleCurrentChange = (val) => { + pagination.currentPage = val + // 閲嶆柊鍔犺浇鏁版嵁 +} + +const getStatusType = (status) => { + const statusMap = { + '宸插畬鎴�': 'success', + '妫�娴嬩腑': 'warning', + '寰呮娴�': 'info', + '宸叉殏鍋�': 'danger', + '浣跨敤涓�': 'primary', + '绌洪棽': 'info' + } + return statusMap[status] || 'info' +} + +const getProgressStatus = (progress) => { + if (progress === 100) return 'success' + if (progress >= 80) return 'warning' + if (progress >= 50) return '' + return 'exception' +} + +const viewDetail = (row) => { + ElMessage.info(`鏌ョ湅璇︽儏: ${row.name}`) +} + +const editItem = (row) => { + ElMessage.info(`缂栬緫椤圭洰: ${row.name}`) +} + +// 鐢熷懡鍛ㄦ湡 +onMounted(() => { + initData() + nextTick(() => { + initSampleChart() + initEquipmentChart() + initInspectionChart() + initUsageChart() + }) + + // 鐩戝惉绐楀彛澶у皬鍙樺寲锛岄噸鏂拌皟鏁村浘琛ㄥぇ灏� + window.addEventListener('resize', () => { + sampleChart?.resize() + equipmentChart?.resize() + inspectionChart?.resize() + usageChart?.resize() + }) +}) +</script> + +<style scoped> +.report-management { + padding: 20px; + background-color: #f5f5f5; + min-height: 100vh; +} + +.page-header { + margin-bottom: 20px; + text-align: center; +} + +.page-header h2 { + color: #303133; + margin-bottom: 8px; + font-size: 24px; + font-weight: 600; +} + +.page-header p { + color: #909399; + font-size: 14px; + margin: 0; +} + +.filter-card { + margin-bottom: 20px; +} + +.statistics-cards { + margin-bottom: 20px; +} + +.stat-card { + height: 120px; +} + +.stat-content { + display: flex; + align-items: center; + height: 100%; +} + +.stat-icon { + width: 60px; + height: 60px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-right: 20px; + font-size: 24px; + color: white; +} + +.stat-card:nth-child(1) .stat-icon { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.stat-card:nth-child(2) .stat-icon { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); +} + +.stat-card:nth-child(3) .stat-icon { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); +} + +.stat-card:nth-child(4) .stat-icon { + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); +} + +.stat-info { + flex: 1; +} + +.stat-number { + font-size: 28px; + font-weight: bold; + color: #303133; + margin-bottom: 8px; +} + +.stat-label { + font-size: 14px; + color: #909399; +} + +.charts-container { + margin-bottom: 20px; +} + +.chart-card { + margin-bottom: 20px; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.chart-container { + height: 300px; + width: 100%; +} + +.table-card { + margin-bottom: 20px; +} + +.pagination-container { + margin-top: 20px; + text-align: right; +} + +:deep(.el-card__header) { + padding: 15px 20px; + border-bottom: 1px solid #ebeef5; + background-color: #fafafa; +} + +:deep(.el-card__body) { + padding: 20px; +} + +:deep(.el-table) { + margin-bottom: 20px; +} + +:deep(.el-progress) { + margin: 0; +} + +:deep(.el-tag) { + margin: 0; +} +</style> diff --git a/src/views/salesManagement/customerManagement/index.vue b/src/views/salesManagement/customerManagement/index.vue new file mode 100644 index 0000000..f66b5f4 --- /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..aac840f --- /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..3169b79 --- /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: 'chenzhiqiang@company.com', + department: '閿�鍞儴', + position: '閿�鍞粡鐞�', + hireDate: '2023-01-15', + status: '鍦ㄨ亴', + permissions: ['璁㈠崟绠$悊', '瀹㈡埛绠$悊', '璐㈠姟绠$悊'] + }, + { + id: 2, + name: '鍒橀泤濠�', + phone: '13800138002', + email: 'liuyating@company.com', + department: '甯傚満閮�', + position: '甯傚満涓撳憳', + hireDate: '2023-03-20', + status: '鍦ㄨ亴', + permissions: ['瀹㈡埛绠$悊', '鎶ヨ〃鏌ョ湅'] + }, + { + id: 3, + name: '鐜嬪缓鍥�', + phone: '13800138003', + email: 'wangjianguo@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/vite.config.js b/vite.config.js index f2c352e..84f70c3 100644 --- a/vite.config.js +++ b/vite.config.js @@ -8,8 +8,8 @@ const { VITE_APP_ENV } = env; const baseUrl = VITE_APP_ENV == "development" - ? "http://192.168.1.147:7003" // 寮�鍙戠幆澧冨悗绔帴鍙� - : "http://10.136.12.71:8014"; // 鐢熶骇鐜鍚庣鎺ュ彛 + ? "http://114.132.189.42:8089" // 寮�鍙戠幆澧冨悗绔帴鍙� + : "http://114.132.189.42:1234"; // 鐢熶骇鐜鍚庣鎺ュ彛 return { // 閮ㄧ讲鐢熶骇鐜鍜屽紑鍙戠幆澧冧笅鐨刄RL銆� -- Gitblit v1.9.3