Merge remote-tracking branch 'refs/remotes/origin/dev_ZQHX' into dev
# Conflicts:
# .env.development
# .env.production
# .env.staging
# index.html
# package.json
# src/layout/components/Sidebar/Logo.vue
# src/main.js
# vite.config.js
已删除4个文件
已修改18个文件
已添加20个文件
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = åºæºæ²¹äºç®¡çç³»ç»
|
| | | VITE_APP_TITLE = ä¸å¼ºæå
´ç®¡çç³»ç»
|
| | |
|
| | | # å¼åç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'development'
|
| | |
|
| | | # åºæºæ²¹äºç®¡çç³»ç»/å¼åç¯å¢
|
| | | # ä¸å¼ºæå
´ç®¡çç³»ç»/å¼åç¯å¢
|
| | | VITE_APP_BASE_API = '/dev-api'
|
| | |
| | | # 页颿 é¢
|
| | | 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 |
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = åºæºæ²¹äºç®¡çç³»ç»
|
| | | VITE_APP_TITLE = ä¸å¼ºæå
´ç®¡çç³»ç»
|
| | |
|
| | | # ç产ç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'staging'
|
| | |
|
| | | # åºæºæ²¹äºç®¡çç³»ç»/ç产ç¯å¢
|
| | | # ä¸å¼ºæå
´ç®¡çç³»ç»/ç产ç¯å¢
|
| | | VITE_APP_BASE_API = '/stage-api'
|
| | |
|
| | | # æ¯å¦å¨æå
æ¶å¼å¯åç¼©ï¼æ¯æ gzip å brotli
|
| | |
| | | <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,
|
| | |
| | | <script type="module" src="/src/main.js"></script>
|
| | | </body>
|
| | |
|
| | | </html>
|
| | | </html> |
| | |
| | | { |
| | | "name": "ruoyi", |
| | | "version": "3.8.9", |
| | | "description": "MES", |
| | | "description": "ä¸å¼ºæå
´ç®¡çç³»ç»", |
| | | "author": "è¥ä¾", |
| | | "license": "MIT", |
| | | "type": "module", |
¶Ô±ÈÐÂÎļþ |
| | |
| | | // 设å¤åç管ç - æ¬å°åæ°æ® APIï¼ä½¿ç¨ localStorage æä¹
åï¼ |
| | | |
| | | const STORAGE_KEY = 'EQUIPMENT_BRANDS'; |
| | | |
| | | function readStore() { |
| | | try { |
| | | const raw = localStorage.getItem(STORAGE_KEY); |
| | | if (raw) { |
| | | const parsed = JSON.parse(raw); |
| | | if (Array.isArray(parsed)) return parsed; |
| | | } |
| | | } catch (e) { |
| | | // ignore |
| | | } |
| | | // åå§åä¸äºç¤ºä¾æ°æ® |
| | | const initial = [ |
| | | { id: 1, name: '西é¨å', country: 'å¾·å½', description: 'å·¥ä¸èªå¨åä¸çµæ°å·¥ç¨åç', createdAt: Date.now() - 86400000 * 10 }, |
| | | { id: 2, name: 'æ½èå¾·', country: 'æ³å½', description: 'è½æºç®¡çä¸èªå¨å', createdAt: Date.now() - 86400000 * 7 }, |
| | | { id: 3, name: 'ä¸è±çµæº', country: 'æ¥æ¬', description: 'çµæ°ä¸èªå¨å设å¤', createdAt: Date.now() - 86400000 * 3 }, |
| | | ]; |
| | | localStorage.setItem(STORAGE_KEY, JSON.stringify(initial)); |
| | | return initial; |
| | | } |
| | | |
| | | function writeStore(list) { |
| | | localStorage.setItem(STORAGE_KEY, JSON.stringify(list)); |
| | | } |
| | | |
| | | function nextId(list) { |
| | | const maxId = list.reduce((max, item) => Math.max(max, Number(item.id) || 0), 0); |
| | | return maxId + 1; |
| | | } |
| | | |
| | | export function getBrandPage(params = {}) { |
| | | const { current = 1, size = 10, name } = params; |
| | | const list = readStore(); |
| | | let filtered = list; |
| | | if (name) { |
| | | const kw = String(name).trim(); |
| | | filtered = filtered.filter((b) => |
| | | (b.name && b.name.includes(kw)) || (b.country && b.country.includes(kw)) |
| | | ); |
| | | } |
| | | const start = (current - 1) * size; |
| | | const end = start + Number(size); |
| | | const records = filtered.slice(start, end); |
| | | return Promise.resolve({ |
| | | code: 200, |
| | | data: { |
| | | total: filtered.length, |
| | | records, |
| | | }, |
| | | msg: 'ok', |
| | | }); |
| | | } |
| | | |
| | | export function getBrandById(id) { |
| | | const list = readStore(); |
| | | const item = list.find((i) => String(i.id) === String(id)); |
| | | return Promise.resolve({ code: 200, data: item || null, msg: 'ok' }); |
| | | } |
| | | |
| | | export function addBrand(data) { |
| | | const list = readStore(); |
| | | const item = { ...data }; |
| | | item.id = nextId(list); |
| | | item.createdAt = Date.now(); |
| | | list.unshift(item); |
| | | writeStore(list); |
| | | return Promise.resolve({ code: 200, data: item, msg: 'æ°å¢æå' }); |
| | | } |
| | | |
| | | export function editBrand(data) { |
| | | const list = readStore(); |
| | | const index = list.findIndex((i) => String(i.id) === String(data.id)); |
| | | if (index !== -1) { |
| | | list[index] = { ...list[index], ...data }; |
| | | writeStore(list); |
| | | return Promise.resolve({ code: 200, data: list[index], msg: 'ä¿®æ¹æå' }); |
| | | } |
| | | return Promise.resolve({ code: 404, data: null, msg: 'æªæ¾å°è¯¥åç' }); |
| | | } |
| | | |
| | | export function delBrand(idOrIds) { |
| | | const list = readStore(); |
| | | const ids = Array.isArray(idOrIds) ? idOrIds.map(String) : [String(idOrIds)]; |
| | | const newList = list.filter((i) => !ids.includes(String(i.id))); |
| | | writeStore(newList); |
| | | return Promise.resolve({ code: 200, data: null, msg: 'å 餿å' }); |
| | | } |
| | | |
| | | |
| | | |
| | |
| | | <template v-for="(o, key) in item.operation" :key="key"> |
| | | <el-button |
| | | v-show="o.type != 'upload'" |
| | | size="small" |
| | | v-if="o.showHide ? o.showHide(scope.row) : true" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
| | | :plain="o.plain" |
| | |
| | | (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id) |
| | | " |
| | | ref="uploadRef" |
| | | size="small" |
| | | :multiple="o.multiple ? o.multiple : false" |
| | | :limit="1" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
| | |
| | | :show-file-list="false" |
| | | > |
| | | <el-button |
| | | :size="o.size ? o.size : 'small'" |
| | | link |
| | | type="primary" |
| | | :disabled="o.disabled ? o.disabled(scope.row) : false" |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="qr-code-generator"> |
| | | <!-- äºç»´ç çæè¡¨å --> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="qr-form"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ è¯ç±»å" prop="type"> |
| | | <el-select v-model="form.type" placeholder="è¯·éæ©æ è¯ç±»å" style="width: 100%"> |
| | | <el-option label="äºç»´ç " value="qrcode"></el-option> |
| | | <el-option label="é²ä¼ªç " value="security"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
容" prop="content"> |
| | | <el-input |
| | | v-model="form.content" |
| | | placeholder="请è¾å
¥è¦ç¼ç çå
容" |
| | | :type="form.type === 'security' ? 'textarea' : 'text'" |
| | | :rows="form.type === 'security' ? 3 : 1" |
| | | ></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="尺寸" prop="size"> |
| | | <el-input-number |
| | | v-model="form.size" |
| | | :min="100" |
| | | :max="500" |
| | | :step="50" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¾¹è·" prop="margin"> |
| | | <el-input-number |
| | | v-model="form.margin" |
| | | :min="0" |
| | | :max="10" |
| | | :step="1" |
| | | style="width: 100%" |
| | | ></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="忝è²" prop="foregroundColor"> |
| | | <el-color-picker v-model="form.foregroundColor" style="width: 100%"></el-color-picker> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èæ¯è²" prop="backgroundColor"> |
| | | <el-color-picker v-model="form.backgroundColor" style="width: 100%"></el-color-picker> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="generateCode" :loading="generating"> |
| | | çæ{{ form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç ' }} |
| | | </el-button> |
| | | <el-button @click="resetForm">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <!-- çæçç æ¾ç¤ºåºå --> |
| | | <div v-if="generatedCodeUrl" class="code-display"> |
| | | <el-divider content-position="center"> |
| | | {{ form.type === 'qrcode' ? 'çæçäºç»´ç ' : 'çæçé²ä¼ªç ' }} |
| | | </el-divider> |
| | | |
| | | <div class="code-container"> |
| | | <div class="code-image"> |
| | | <img :src="generatedCodeUrl" :alt="form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '" /> |
| | | </div> |
| | | |
| | | <div class="code-info"> |
| | | <p><strong>å
容ï¼</strong>{{ form.content }}</p> |
| | | <p><strong>ç±»åï¼</strong>{{ form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç ' }}</p> |
| | | <p><strong>尺寸ï¼</strong>{{ form.size }}x{{ form.size }}px</p> |
| | | <p><strong>çææ¶é´ï¼</strong>{{ generateTime }}</p> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="code-actions"> |
| | | <el-button type="success" @click="downloadCode" icon="Download"> |
| | | ä¸è½½å¾ç |
| | | </el-button> |
| | | <el-button type="primary" @click="copyToClipboard" icon="CopyDocument"> |
| | | å¤å¶å
容 |
| | | </el-button> |
| | | <el-button @click="printCode" icon="Printer"> |
| | | æå° |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ¹éçæå¯¹è¯æ¡ --> |
| | | <el-dialog v-model="batchDialogVisible" title="æ¹éçæ" width="600px"> |
| | | <el-form :model="batchForm" label-width="120px"> |
| | | <el-form-item label="çææ°é"> |
| | | <el-input-number v-model="batchForm.quantity" :min="1" :max="100" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | <el-form-item label="åç¼"> |
| | | <el-input v-model="batchForm.prefix" placeholder="请è¾å
¥åç¼ï¼å¦ï¼PROD_"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="èµ·å§ç¼å·"> |
| | | <el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="batchDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="generateBatchCodes">å¼å§çæ</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ¹éçæç»æ --> |
| | | <div v-if="batchCodes.length > 0" class="batch-results"> |
| | | <el-divider content-position="center">æ¹éçæç»æ</el-divider> |
| | | |
| | | <div class="batch-grid"> |
| | | <div |
| | | v-for="(code, index) in batchCodes" |
| | | :key="index" |
| | | class="batch-item" |
| | | > |
| | | <img :src="code.url" :alt="code.content" /> |
| | | <p class="batch-content">{{ code.content }}</p> |
| | | <el-button size="small" @click="downloadSingleCode(code)">ä¸è½½</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="batch-actions"> |
| | | <el-button type="success" @click="downloadAllCodes">ä¸è½½å
¨é¨</el-button> |
| | | <el-button @click="clearBatchCodes">æ¸
ç©ºç»æ</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from 'vue' |
| | | import QRCode from 'qrcode' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Download, CopyDocument, Printer } from '@element-plus/icons-vue' |
| | | |
| | | // å®ä¹ç»ä»¶åç§° |
| | | defineOptions({ |
| | | name: 'QRCodeGenerator' |
| | | }) |
| | | |
| | | // è¡¨åæ°æ® |
| | | const form = reactive({ |
| | | type: 'qrcode', |
| | | content: '', |
| | | size: 200, |
| | | margin: 2, |
| | | foregroundColor: '#000000', |
| | | backgroundColor: '#FFFFFF' |
| | | }) |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | | type: [{ required: true, message: 'è¯·éæ©æ è¯ç±»å', trigger: 'change' }], |
| | | content: [{ required: true, message: '请è¾å
¥å
容', trigger: 'blur' }] |
| | | } |
| | | |
| | | // ååºå¼æ°æ® |
| | | const formRef = ref() |
| | | const generating = ref(false) |
| | | const generatedCodeUrl = ref('') |
| | | const generateTime = ref('') |
| | | const batchDialogVisible = ref(false) |
| | | const batchForm = reactive({ |
| | | quantity: 10, |
| | | prefix: '', |
| | | startNumber: 1 |
| | | }) |
| | | const batchCodes = ref([]) |
| | | |
| | | // çæäºç»´ç æé²ä¼ªç |
| | | const generateCode = async () => { |
| | | try { |
| | | await formRef.value.validate() |
| | | |
| | | if (!form.content.trim()) { |
| | | ElMessage.warning('请è¾å
¥è¦ç¼ç çå
容') |
| | | return |
| | | } |
| | | |
| | | generating.value = true |
| | | |
| | | if (form.type === 'qrcode') { |
| | | // çæäºç»´ç |
| | | generatedCodeUrl.value = await QRCode.toDataURL(form.content, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | }, |
| | | errorCorrectionLevel: 'M' |
| | | }) |
| | | } else { |
| | | // çæé²ä¼ªç ï¼ä½¿ç¨äºç»´ç ææ¯ï¼ä½å
å®¹æ ¼å¼ä¸åï¼ |
| | | const securityContent = generateSecurityCode(form.content) |
| | | generatedCodeUrl.value = await QRCode.toDataURL(securityContent, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | }, |
| | | errorCorrectionLevel: 'H' // é²ä¼ªç ä½¿ç¨æé«çº éçº§å« |
| | | }) |
| | | } |
| | | |
| | | generateTime.value = new Date().toLocaleString() |
| | | ElMessage.success('çææåï¼') |
| | | |
| | | } catch (error) { |
| | | console.error('çæå¤±è´¥:', error) |
| | | ElMessage.error('çæå¤±è´¥ï¼' + error.message) |
| | | } finally { |
| | | generating.value = false |
| | | } |
| | | } |
| | | |
| | | // çæé²ä¼ªç å
容 |
| | | const generateSecurityCode = (content) => { |
| | | const timestamp = Date.now() |
| | | const random = Math.random().toString(36).substr(2, 8) |
| | | return `SEC_${content}_${timestamp}_${random}` |
| | | } |
| | | |
| | | // ä¸è½½çæçç |
| | | const downloadCode = () => { |
| | | if (!generatedCodeUrl.value) { |
| | | ElMessage.warning('请å
çæç ') |
| | | return |
| | | } |
| | | |
| | | const a = document.createElement('a') |
| | | a.href = generatedCodeUrl.value |
| | | a.download = `${form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}_${new Date().getTime()}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | ElMessage.success('ä¸è½½æåï¼') |
| | | } |
| | | |
| | | // å¤å¶å
容å°åªè´´æ¿ |
| | | const copyToClipboard = async () => { |
| | | try { |
| | | await navigator.clipboard.writeText(form.content) |
| | | ElMessage.success('å
容已å¤å¶å°åªè´´æ¿') |
| | | } catch (error) { |
| | | // éçº§æ¹æ¡ |
| | | const textArea = document.createElement('textarea') |
| | | textArea.value = form.content |
| | | document.body.appendChild(textArea) |
| | | textArea.select() |
| | | document.execCommand('copy') |
| | | document.body.removeChild(textArea) |
| | | ElMessage.success('å
容已å¤å¶å°åªè´´æ¿') |
| | | } |
| | | } |
| | | |
| | | // æå°ç |
| | | const printCode = () => { |
| | | if (!generatedCodeUrl.value) { |
| | | ElMessage.warning('请å
çæç ') |
| | | return |
| | | } |
| | | |
| | | const printWindow = window.open('', '_blank') |
| | | printWindow.document.write(` |
| | | <html> |
| | | <head> |
| | | <title>æå°${form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}</title> |
| | | <style> |
| | | body { text-align: center; padding: 20px; } |
| | | img { max-width: 100%; height: auto; } |
| | | .info { margin: 20px 0; } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h2>${form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}</h2> |
| | | <img src="${generatedCodeUrl.value}" alt="${form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}" /> |
| | | <div class="info"> |
| | | <p><strong>å
容ï¼</strong>${form.content}</p> |
| | | <p><strong>çææ¶é´ï¼</strong>${generateTime.value}</p> |
| | | </div> |
| | | </body> |
| | | </html> |
| | | `) |
| | | printWindow.document.close() |
| | | printWindow.print() |
| | | } |
| | | |
| | | // é置表å |
| | | const resetForm = () => { |
| | | formRef.value.resetFields() |
| | | generatedCodeUrl.value = '' |
| | | generateTime.value = '' |
| | | batchCodes.value = [] |
| | | } |
| | | |
| | | // æ¹éçæ |
| | | const generateBatchCodes = async () => { |
| | | if (!batchForm.prefix.trim()) { |
| | | ElMessage.warning('请è¾å
¥åç¼') |
| | | return |
| | | } |
| | | |
| | | batchCodes.value = [] |
| | | generating.value = true |
| | | |
| | | try { |
| | | for (let i = 0; i < batchForm.quantity; i++) { |
| | | const number = batchForm.startNumber + i |
| | | const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}` |
| | | |
| | | let codeUrl |
| | | if (form.type === 'qrcode') { |
| | | codeUrl = await QRCode.toDataURL(content, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | } |
| | | }) |
| | | } else { |
| | | const securityContent = generateSecurityCode(content) |
| | | codeUrl = await QRCode.toDataURL(securityContent, { |
| | | width: form.size, |
| | | margin: form.margin, |
| | | color: { |
| | | dark: form.foregroundColor, |
| | | light: form.backgroundColor |
| | | } |
| | | }) |
| | | } |
| | | |
| | | batchCodes.value.push({ |
| | | content, |
| | | url: codeUrl |
| | | }) |
| | | } |
| | | |
| | | ElMessage.success(`æ¹éçæå®æï¼å
±çæ ${batchForm.quantity} 个ç `) |
| | | batchDialogVisible.value = false |
| | | |
| | | } catch (error) { |
| | | console.error('æ¹éçæå¤±è´¥:', error) |
| | | ElMessage.error('æ¹éçæå¤±è´¥ï¼' + error.message) |
| | | } finally { |
| | | generating.value = false |
| | | } |
| | | } |
| | | |
| | | // ä¸è½½å个æ¹éçæçç |
| | | const downloadSingleCode = (code) => { |
| | | const a = document.createElement('a') |
| | | a.href = code.url |
| | | a.download = `${code.content}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | } |
| | | |
| | | // ä¸è½½æææ¹éçæçç |
| | | const downloadAllCodes = async () => { |
| | | if (batchCodes.value.length === 0) { |
| | | ElMessage.warning('没æå¯ä¸è½½çç ') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | // 使ç¨JSZipæå
ä¸è½½ |
| | | const JSZip = await import('jszip') |
| | | const zip = new JSZip.default() |
| | | |
| | | batchCodes.value.forEach((code, index) => { |
| | | // å°base64转æ¢ä¸ºblob |
| | | const base64Data = code.url.split(',')[1] |
| | | const byteCharacters = atob(base64Data) |
| | | const byteNumbers = new Array(byteCharacters.length) |
| | | for (let i = 0; i < byteCharacters.length; i++) { |
| | | byteNumbers[i] = byteCharacters.charCodeAt(i) |
| | | } |
| | | const byteArray = new Uint8Array(byteNumbers) |
| | | |
| | | zip.file(`${code.content}.png`, byteArray) |
| | | }) |
| | | |
| | | const content = await zip.generateAsync({ type: 'blob' }) |
| | | const a = document.createElement('a') |
| | | a.href = URL.createObjectURL(content) |
| | | a.download = `æ¹é${form.type === 'qrcode' ? 'äºç»´ç ' : 'é²ä¼ªç '}_${new Date().getTime()}.zip` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | URL.revokeObjectURL(a.href) |
| | | |
| | | ElMessage.success('æ¹éä¸è½½å®æï¼') |
| | | } catch (error) { |
| | | console.error('æ¹éä¸è½½å¤±è´¥:', error) |
| | | ElMessage.error('æ¹éä¸è½½å¤±è´¥ï¼è¯·é个ä¸è½½') |
| | | } |
| | | } |
| | | |
| | | // æ¸
空æ¹éçæç»æ |
| | | const clearBatchCodes = () => { |
| | | batchCodes.value = [] |
| | | } |
| | | |
| | | // æ´é²æ¹æ³ç»ç¶ç»ä»¶ |
| | | defineExpose({ |
| | | generateCode, |
| | | downloadCode, |
| | | resetForm, |
| | | form |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .qr-code-generator { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .qr-form { |
| | | background: #f8f9fa; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .code-display { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .code-container { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: flex-start; |
| | | gap: 40px; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .code-image img { |
| | | border: 2px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .code-info { |
| | | text-align: left; |
| | | min-width: 200px; |
| | | } |
| | | |
| | | .code-info p { |
| | | margin: 8px 0; |
| | | color: #666; |
| | | } |
| | | |
| | | .code-actions { |
| | | text-align: center; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .code-actions .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | |
| | | .batch-results { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .batch-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
| | | gap: 20px; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .batch-item { |
| | | text-align: center; |
| | | padding: 15px; |
| | | border: 1px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | background: #fff; |
| | | } |
| | | |
| | | .batch-item img { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .batch-content { |
| | | font-size: 12px; |
| | | color: #666; |
| | | margin: 10px 0; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .batch-actions { |
| | | text-align: center; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | .batch-actions .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .code-container { |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | |
| | | .batch-grid { |
| | | grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <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: {
|
| | |
| | | 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(() => {
|
| | |
| | | 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));
|
| | | };
|
| | |
| | | </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> |
| | |
| | | </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> |
| | |
| | | </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> |
| | |
| | | </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> |
| | |
| | | problem: "大é¢ååå®¡æ¹æµç¨å¤æï¼å®¡æ¹æ¶é´é¿ï¼å½±åä¸å¡è¿å±", |
| | | solution: "建ç«ç»¿è²ééï¼å¯¹ç¬¦åæ¡ä»¶çååéç¨ç®åå®¡æ¹æµç¨ï¼ç±é¨é¨è´è´£äººç´æ¥å®¡æ¹ï¼å¹³åå®¡æ¹æ¶é´ä»3天缩çè³1天", |
| | | keyPoints: "绿è²é鿡件,ç®åæµç¨,å®¡æ¹æé,æ¶é´æ§å¶", |
| | | creator: "å¼ ç»ç", |
| | | creator: "éå¿å¼º", |
| | | usageCount: 15, |
| | | createTime: "2025-01-15 10:30:00" |
| | | }, |
| | |
| | | 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() |
| | | }; |
| | |
| | | startTime: '2025-01-15 09:00:00', |
| | | endTime: '2025-01-15 10:30:00', |
| | | location: 'ä¼è®®å®¤A', |
| | | host: 'å¼ ç»ç', |
| | | participants: ['å¼ ç»ç', 'æå·¥ç¨å¸', 'ç设计å¸', 'èµµæµè¯å'], |
| | | host: 'éå¿å¼º', |
| | | participants: ['éå¿å¼º', 'åé
å©·', 'ç建å½', '赵丽å'], |
| | | agenda: [ |
| | | { time: '09:00-09:15', content: 'ä¸å¨å·¥ä½æ»ç»', status: 'completed' }, |
| | | { time: '09:15-09:45', content: 'æ¬å¨å¼å计å', status: 'active' }, |
| | |
| | | startTime: '2025-01-15 14:00:00', |
| | | endTime: '2025-01-15 15:00:00', |
| | | location: '线ä¸ä¼è®®', |
| | | host: 'éæ»ç', |
| | | participants: ['éæ»ç', 'å产åç»ç', 'å客æ·ç»ç', '客æ·ä»£è¡¨'], |
| | | host: 'éå¿å¼º', |
| | | participants: ['éå¿å¼º', 'åé
å©·', 'åæå', '客æ·ä»£è¡¨'], |
| | | agenda: [ |
| | | { time: '14:00-14:20', content: 'éæ±èæ¯ä»ç»', status: 'pending' }, |
| | | { time: '14:20-14:40', content: 'åè½éæ±åæ', status: 'pending' }, |
| | |
| | | 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; |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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="请è¾å
¥è´è´£äºº" /> |
| | | </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: '请è¾å
¥è´è´£äºº', 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: 'è®¾è®¡ç³»ç»ææ¯æ¶æï¼å
æ¬æ°æ®åºè®¾è®¡ãæ¥å£è®¾è®¡ç', |
| | | 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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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: 'å£åº¦è´¢å¡æ¥åéè¦çç« ' |
| | | }, |
| | | { |
| | | 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> |
| | |
| | | levelText: '红è²é¢è¦', |
| | | status: 'pending', |
| | | statusText: 'å¾
å¤ç', |
| | | responsible: 'å¼ ç»ç', |
| | | responsible: 'éå¿å¼º', |
| | | description: 'A项ç®é¢ç®æ§è¡ç已达95%ï¼é¢è®¡å°è¶
åºé¢ç®èå´ã', |
| | | impact: 'å½±åé¡¹ç®æ´ä½è´¢å¡ææ ï¼å¯è½å¯¼è´é¡¹ç®äºæ', |
| | | suggestions: 'æåéå¿
è¦æ¯åºï¼ä¼åèµæºé
ç½®ï¼ç³è¯·é¢ç®è°æ´' |
| | |
| | | levelText: '红è²é¢è¦', |
| | | status: 'pending', |
| | | statusText: 'å¾
å¤ç', |
| | | responsible: 'éæ»ç', |
| | | responsible: 'éå¿å¼º', |
| | | description: '产åDå¨å®¢æ·ç°åºåºç°è´¨éé®é¢ã', |
| | | impact: 'å½±åå®¢æ·æ»¡æåº¦ï¼å¯è½é æç»æµæå¤±', |
| | | suggestions: 'ç«å³å¬åé®é¢äº§åï¼åæåå ï¼å¶å®æ¹è¿æªæ½' |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="åçåç§°/å½å®¶"> |
| | | <el-input |
| | | v-model="filters.name" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å
³é®è¯" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="openAdd" icon="Plus"> æ°å¢ </el-button> |
| | | <el-button |
| | | type="danger" |
| | | icon="Delete" |
| | | :disabled="multipleSelection.length <= 0" |
| | | @click="handleBatchDelete" |
| | | >æ¹éå é¤</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <el-dialog v-model="visible" :title="dialogTitle" width="520px" destroy-on-close> |
| | | <el-form :model="form" ref="formRef" :rules="rules" label-width="90px"> |
| | | <el-form-item label="åçåç§°" prop="name"> |
| | | <el-input v-model="form.name" placeholder="请è¾å
¥åçåç§°" /> |
| | | </el-form-item> |
| | | <el-form-item label="æå±å½å®¶" prop="country"> |
| | | <el-input v-model="form.country" placeholder="请è¾å
¥å½å®¶/å°åº" /> |
| | | </el-form-item> |
| | | <el-form-item label="æè¿°" prop="description"> |
| | | <el-input v-model="form.description" type="textarea" :rows="3" placeholder="å¯å¡«ååçç®ä»" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="visible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, getCurrentInstance, onMounted } from 'vue' |
| | | import { ElMessageBox, ElMessage } from 'element-plus' |
| | | import { usePaginationApi } from '@/hooks/usePaginationApi' |
| | | import { getBrandPage, addBrand, editBrand, delBrand } from '@/api/equipmentManagement/brand' |
| | | |
| | | defineOptions({ name: '设å¤åç管ç' }) |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const multipleSelection = ref([]) |
| | | const formRef = ref() |
| | | const visible = ref(false) |
| | | const dialogTitle = ref('æ°å¢åç') |
| | | const form = ref({ id: undefined, name: '', country: '', description: '' }) |
| | | |
| | | const rules = { |
| | | name: [{ required: true, message: '请è¾å
¥åçåç§°', trigger: 'blur' }], |
| | | country: [{ required: true, message: '请è¾å
¥æå±å½å®¶', trigger: 'blur' }] |
| | | } |
| | | |
| | | const { |
| | | filters, |
| | | columns, |
| | | dataList, |
| | | pagination, |
| | | getTableData, |
| | | resetFilters, |
| | | onCurrentChange, |
| | | } = usePaginationApi( |
| | | getBrandPage, |
| | | { name: undefined }, |
| | | [ |
| | | { label: 'åçåç§°', align: 'center', prop: 'name' }, |
| | | { label: 'æå±å½å®¶', align: 'center', prop: 'country' }, |
| | | { label: 'æè¿°', align: 'center', prop: 'description' }, |
| | | { label: 'å建æ¶é´', align: 'center', prop: 'createdAt' }, |
| | | { |
| | | dataType: 'action', |
| | | label: 'æä½', |
| | | align: 'center', |
| | | fixed: 'right', |
| | | width: 140, |
| | | operation: [ |
| | | { |
| | | name: 'ç¼è¾', |
| | | type: 'text', |
| | | clickFun: (row) => openEdit(row), |
| | | }, |
| | | { |
| | | name: 'å é¤', |
| | | type: 'text', |
| | | clickFun: (row) => handleDelete(row.id), |
| | | } |
| | | ] |
| | | } |
| | | ] |
| | | ) |
| | | |
| | | const handleSelectionChange = (list) => { |
| | | multipleSelection.value = list |
| | | } |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page |
| | | pagination.pageSize = limit |
| | | onCurrentChange(page) |
| | | } |
| | | |
| | | function resetForm() { |
| | | form.value = { id: undefined, name: '', country: '', description: '' } |
| | | } |
| | | |
| | | function openAdd() { |
| | | resetForm() |
| | | dialogTitle.value = 'æ°å¢åç' |
| | | visible.value = true |
| | | } |
| | | |
| | | function openEdit(row) { |
| | | form.value = { id: row.id, name: row.name, country: row.country, description: row.description } |
| | | dialogTitle.value = 'ç¼è¾åç' |
| | | visible.value = true |
| | | } |
| | | |
| | | function handleSubmit() { |
| | | formRef.value.validate(async (valid) => { |
| | | if (!valid) return |
| | | const isEdit = Boolean(form.value.id) |
| | | const api = isEdit ? editBrand : addBrand |
| | | const { code, msg } = await api({ ...form.value }) |
| | | if (code === 200) { |
| | | ElMessage.success(isEdit ? 'ä¿®æ¹æå' : 'æ°å¢æå') |
| | | visible.value = false |
| | | getTableData() |
| | | } else { |
| | | ElMessage.error(msg || 'æä½å¤±è´¥') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function handleDelete(id) { |
| | | ElMessageBox.confirm('æ¤æä½å°æ°¸ä¹
å é¤è¯¥åç, æ¯å¦ç»§ç»?', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }).then(async () => { |
| | | const { code } = await delBrand(id) |
| | | if (code === 200) { |
| | | ElMessage.success('å 餿å') |
| | | getTableData() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function handleBatchDelete() { |
| | | if (multipleSelection.value.length === 0) return |
| | | ElMessageBox.confirm('å°å é¤éä¸çåçï¼æ¯å¦ç»§ç»ï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }).then(async () => { |
| | | const ids = multipleSelection.value.map((i) => i.id) |
| | | const { code } = await delBrand(ids) |
| | | if (code === 200) { |
| | | ElMessage.success('å 餿å') |
| | | getTableData() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getTableData() |
| | | }) |
| | | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .table_list { margin-top: unset; } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | </style> |
| | | |
| | | |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="设å¤åç" prop="deviceBrand"> |
| | | <el-input v-model="form.deviceBrand" placeholder="请è¾å
¥è®¾å¤åç" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºå" prop="supplierName"> |
| | | <el-input v-model="form.supplierName" placeholder="请è¾å
¥ä¾åºå" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åæ¾ä½ç½®" prop="storageLocation"> |
| | | <el-input v-model="form.storageLocation" placeholder="请è¾å
¥åæ¾ä½ç½®" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½" prop="unit"> |
| | | <el-input v-model="form.unit" placeholder="请è¾å
¥åä½" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¯ç¨ææ§" prop="enableDepreciation"> |
| | | <el-switch v-model="form.enableDepreciation" :active-value="true" :inactive-value="false" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | const { form, resetForm } = useFormData({ |
| | | deviceName: undefined, // 设å¤åç§° |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | deviceBrand: undefined, // 设å¤åç |
| | | supplierName: undefined, // ä¾åºå |
| | | storageLocation: undefined, // åæ¾ä½ç½® |
| | | enableDepreciation: false, // æ¯å¦å¯ç¨ææ§ |
| | | unit: undefined, // åä½ |
| | | number: undefined, // æ°é |
| | | taxIncludingPriceUnit: undefined, // å«ç¨åä»· |
| | |
| | | if (code == 200) { |
| | | form.deviceName = data.deviceName; |
| | | form.deviceModel = data.deviceModel; |
| | | form.deviceBrand = data.deviceBrand; |
| | | form.supplierName = data.supplierName; |
| | | form.storageLocation = data.storageLocation; |
| | | form.enableDepreciation = data.enableDepreciation; |
| | | form.unit = data.unit; |
| | | form.number = data.number; |
| | | form.taxIncludingPriceUnit = data.taxIncludingPriceUnit; |
| | |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "设å¤åç", |
| | | align: "center", |
| | | prop: "deviceBrand", |
| | | }, |
| | | { |
| | | label: "ä¾åºå", |
| | | align: "center", |
| | | prop: "supplierName", |
| | |
| | | label: "åä½", |
| | | align: "center", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "åæ¾ä½ç½®", |
| | | align: "center", |
| | | prop: "storageLocation", |
| | | }, |
| | | { |
| | | label: "æ°é", |
| | |
| | | prop: "unTaxIncludingPriceTotal", |
| | | }, |
| | | { |
| | | label: "å¯ç¨ææ§", |
| | | align: "center", |
| | | prop: "enableDepreciation", |
| | | formatData: (v) => (v ? "æ¯" : "å¦"), |
| | | }, |
| | | { |
| | | label: "å½å
¥äºº", |
| | | align: "center", |
| | | prop: "createUser", |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="éè´è®¢åå·ï¼"> |
| | | <el-input v-model="searchForm.orderNo" placeholder="请è¾å
¥è®¢åå·" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="searchForm.supplierName" placeholder="请è¾å
¥ä¾åºååç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢å°è´§</el-button> |
| | | <el-button type="success" @click="handleBatchReceive">æ¹éæ¶è´§</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="å°è´§åå·" prop="arrivalNo" width="180" /> |
| | | <el-table-column label="éè´è®¢åå·" prop="orderNo" width="180" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="å°è´§ç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å°è´§æ°é" prop="arrivalQuantity" width="100" /> |
| | | <el-table-column label="å°è´§æ¶é´" prop="arrivalTime" width="180" /> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleReceive(row)">æ¶è´§</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢å°è´§' : 'ç¼è¾å°è´§'" width="600px"> |
| | | <el-form :model="formData" label-width="120px"> |
| | | <el-form-item label="éè´è®¢åå·"> |
| | | <el-select v-model="formData.orderNo" placeholder="è¯·éæ©éè´è®¢å" style="width: 100%"> |
| | | <el-option label="PO20241201001" value="PO20241201001" /> |
| | | <el-option label="PO20241201002" value="PO20241201002" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°"> |
| | | <el-input v-model="formData.supplierName" placeholder="ä¾åºååç§°" readonly /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | orderNo: '', |
| | | supplierName: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | orderNo: '', |
| | | supplierName: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | arrivalNo: 'AR20241201001', |
| | | orderNo: 'PO20241201001', |
| | | supplierName: 'ä¾åºåA', |
| | | status: 'received', |
| | | arrivalQuantity: 250, |
| | | arrivalTime: '2025-12-01 15:30:00', |
| | | remark: 'æ£å¸¸å°è´§' |
| | | } |
| | | ] |
| | | |
| | | const tableData = ref([...mockData]) |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { pending: 'warning', received: 'success', stored: 'info' } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { pending: 'å¾
æ¶è´§', received: 'å·²æ¶è´§', stored: 'å·²å
¥åº' } |
| | | return statusMap[status] || 'æªç¥' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | loading.value = true |
| | | setTimeout(() => { loading.value = false }, 500) |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | Object.assign(searchForm, { orderNo: '', supplierName: '' }) |
| | | } |
| | | |
| | | const openDialog = (type, row = {}) => { |
| | | dialogType.value = type |
| | | if (type === 'edit' && row.id) { |
| | | Object.assign(formData, { orderNo: row.orderNo, supplierName: row.supplierName, remark: row.remark }) |
| | | } else { |
| | | Object.assign(formData, { orderNo: '', supplierName: '', remark: '' }) |
| | | } |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | if (dialogType.value === 'add') { |
| | | const newArrival = { |
| | | id: Date.now(), |
| | | arrivalNo: `AR${Date.now()}`, |
| | | orderNo: formData.orderNo, |
| | | supplierName: formData.supplierName, |
| | | status: 'pending', |
| | | arrivalQuantity: 0, |
| | | arrivalTime: new Date().toLocaleString(), |
| | | remark: formData.remark |
| | | } |
| | | tableData.value.unshift(newArrival) |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | |
| | | const handleReceive = (row) => { |
| | | row.status = 'received' |
| | | ElMessage.success('æ¶è´§æå') |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('ç¡®å®è¦å é¤è¿æ¡è®°å½åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = tableData.value.findIndex(item => item.id === row.id) |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleBatchReceive = () => { |
| | | ElMessage.success('æ¹éæ¶è´§æå') |
| | | } |
| | | |
| | | const handleBatchDelete = () => { |
| | | ElMessage.success('æ¹éå 餿å') |
| | | } |
| | | |
| | | const handleSelectionChange = (rows) => { |
| | | selectedRows.value = rows |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { padding: 20px; } |
| | | .search-card { margin-bottom: 20px; } |
| | | .table-card { margin-bottom: 20px; } |
| | | .table-header { margin-bottom: 20px; } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>éè´ç®¡çç³»ç»</h2> |
| | | <p>ç»ä¸ç®¡çéè´å
¨æµç¨ï¼æåéè´æçä¸è´¨é</p> |
| | | </div> |
| | | |
| | | <!-- åè½æ¨¡åå¡ç --> |
| | | <el-row :gutter="20" class="module-cards"> |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/purchaseOrder')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#409EFF"><Document /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>éè´è®¢å管ç</h3> |
| | | <p>æ°å»ºãç¼è¾ãå é¤éè´è®¢åï¼éæ©ä¾åºåï¼å¡«åååæç»</p> |
| | | <div class="card-stats"> |
| | | <span>å¾
å®¡æ ¸: {{ stats.pendingOrders }}</span> |
| | | <span>å·²å®¡æ ¸: {{ stats.approvedOrders }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/arrivalManagement')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#67C23A"><Box /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>å°è´§ç®¡ç</h3> |
| | | <p>èªå¨å
³èéè´è®¢åï¼å½å
¥å°è´§ååä¿¡æ¯ï¼æ¯ææå°æ¥ç</p> |
| | | <div class="card-stats"> |
| | | <span>å¾
æ¶è´§: {{ stats.pendingArrivals }}</span> |
| | | <span>å·²æ¶è´§: {{ stats.receivedArrivals }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/qualityInspection')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#E6A23C"><Search /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>è´¨æ£ç®¡ç</h3> |
| | | <p>å°è´§åèªå¨çæè´¨æ£åï¼å¡«ååæ ¼ä¸ä¸åæ ¼ååæ°éååå </p> |
| | | <div class="card-stats"> |
| | | <span>å¾
è´¨æ£: {{ stats.pendingInspections }}</span> |
| | | <span>已宿: {{ stats.completedInspections }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20" class="module-cards"> |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/returnManagement')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#F56C6C"><RefreshLeft /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>é货管ç</h3> |
| | | <p>çæéè´éè´§ååè´¨æ£éè´§åï¼æ¯æçéæ¥è¯¢ä¸åæ®è¯¦æ
</p> |
| | | <div class="card-stats"> |
| | | <span>å¾
å®¡æ ¸: {{ stats.pendingReturns }}</span> |
| | | <span>å·²å®¡æ ¸: {{ stats.approvedReturns }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/priceManagement')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#909399"><Money /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>ä»·æ ¼ç®¡ç</h3> |
| | | <p>æ ¹æ®åååå¸åºä»·æ ¼ååè¿è¡éè´ä»·è°æ´ï¼èªå¨æ´æ°éè´åæ®</p> |
| | | <div class="card-stats"> |
| | | <span>ææä»·æ ¼: {{ stats.activePrices }}</span> |
| | | <span>å¾
çæ: {{ stats.pendingPrices }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="8"> |
| | | <el-card class="module-card" shadow="hover" @click="navigateTo('/procurementManagement/procurementLedger')"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon size="48" color="#9C27B0"><List /></el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <h3>éè´å°è´¦</h3> |
| | | <p>æ¥çéè´åå²è®°å½ï¼ç»è®¡åæéè´æ°æ®ï¼çæéè´æ¥è¡¨</p> |
| | | <div class="card-stats"> |
| | | <span>æ»è®¢å: {{ stats.totalOrders }}</span> |
| | | <span>æ»éé¢: Â¥{{ stats.totalAmount.toFixed(2) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- ç»è®¡æ¦è§ --> |
| | | <el-card class="stats-card" shadow="never"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>éè´ç»è®¡æ¦è§</span> |
| | | <el-button type="primary" size="small" @click="refreshStats">å·æ°æ°æ®</el-button> |
| | | </div> |
| | | </template> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ stats.totalOrders }}</div> |
| | | <div class="stat-label">éè´è®¢åæ»æ°</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ stats.totalAmount.toFixed(2) }}</div> |
| | | <div class="stat-label">éè´æ»éé¢(ä¸å
)</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ stats.avgDeliveryTime }}</div> |
| | | <div class="stat-label">å¹³å交ä»å¤©æ°</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-number">{{ stats.qualityRate }}%</div> |
| | | <div class="stat-label">è´¨æ£åæ ¼ç</div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- æè¿æ´»å¨ --> |
| | | <el-card class="activity-card" shadow="never"> |
| | | <template #header> |
| | | <span>æè¿æ´»å¨</span> |
| | | </template> |
| | | |
| | | <el-timeline> |
| | | <el-timeline-item |
| | | v-for="(activity, index) in recentActivities" |
| | | :key="index" |
| | | :timestamp="activity.time" |
| | | :type="activity.type" |
| | | > |
| | | {{ activity.content }} |
| | | </el-timeline-item> |
| | | </el-timeline> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { Document, Box, Search, RefreshLeft, Money, List } from '@element-plus/icons-vue' |
| | | |
| | | const router = useRouter() |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const stats = ref({ |
| | | pendingOrders: 5, |
| | | approvedOrders: 25, |
| | | pendingArrivals: 3, |
| | | receivedArrivals: 18, |
| | | pendingInspections: 2, |
| | | completedInspections: 15, |
| | | pendingReturns: 1, |
| | | approvedReturns: 3, |
| | | activePrices: 45, |
| | | pendingPrices: 2, |
| | | totalOrders: 30, |
| | | totalAmount: 125.8, |
| | | avgDeliveryTime: 7, |
| | | qualityRate: 96.5 |
| | | }) |
| | | |
| | | // æè¿æ´»å¨ |
| | | const recentActivities = ref([ |
| | | { |
| | | time: '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: 'ä»·æ ¼è°æ´ï¼ååB ä» Â¥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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="åååç§°ï¼"> |
| | | <el-input v-model="searchForm.productName" placeholder="请è¾å
¥åååç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="searchForm.supplierName" placeholder="请è¾å
¥ä¾åºååç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ä»·æ ¼ç¶æï¼"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©ç¶æ" clearable> |
| | | <el-option label="ææ" value="active" /> |
| | | <el-option label="å·²è¿æ" value="expired" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢ä»·æ ¼</el-button> |
| | | <el-button type="success" @click="handleBatchUpdate">æ¹éæ´æ°</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="åååç§°" prop="productName" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specification" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="åä»·æ ¼" prop="oldPrice" width="120"> |
| | | <template #default="{ row }">Â¥{{ row.oldPrice.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="æ°ä»·æ ¼" prop="newPrice" width="120"> |
| | | <template #default="{ row }">Â¥{{ row.newPrice.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="è°ä»·å¹
度" prop="priceChange" width="120"> |
| | | <template #default="{ row }"> |
| | | <span :style="{ color: row.priceChange >= 0 ? '#f56c6c' : '#67c23a' }"> |
| | | {{ row.priceChange >= 0 ? '+' : '' }}{{ row.priceChange.toFixed(2) }}% |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="çææ¶é´" prop="effectiveTime" width="180" /> |
| | | <el-table-column label="ç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleApply(row)" v-if="row.status === 'pending'">åºç¨</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢ä»·æ ¼' : 'ç¼è¾ä»·æ ¼'" width="600px"> |
| | | <el-form :model="formData" label-width="120px"> |
| | | <el-form-item label="åååç§°"> |
| | | <el-select v-model="formData.productName" placeholder="è¯·éæ©åå" style="width: 100%"> |
| | | <el-option label="ååA" value="ååA" /> |
| | | <el-option label="ååB" value="ååB" /> |
| | | <el-option label="ååC" value="ååC" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="è§æ ¼åå·"> |
| | | <el-input v-model="formData.specification" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°"> |
| | | <el-select v-model="formData.supplierName" placeholder="è¯·éæ©ä¾åºå" style="width: 100%"> |
| | | <el-option label="ä¾åºåA" value="ä¾åºåA" /> |
| | | <el-option label="ä¾åºåB" value="ä¾åºåB" /> |
| | | <el-option label="ä¾åºåC" value="ä¾åºåC" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="åä»·æ ¼"> |
| | | <el-input v-model="formData.oldPrice" placeholder="请è¾å
¥åä»·æ ¼" type="number" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ°ä»·æ ¼"> |
| | | <el-input v-model="formData.newPrice" placeholder="请è¾å
¥æ°ä»·æ ¼" type="number" /> |
| | | </el-form-item> |
| | | <el-form-item label="çææ¶é´"> |
| | | <el-date-picker v-model="formData.effectiveTime" type="datetime" placeholder="éæ©çææ¶é´" style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="è°ä»·åå "> |
| | | <el-select v-model="formData.reason" placeholder="è¯·éæ©è°ä»·åå " style="width: 100%"> |
| | | <el-option label="å¸åºä»·æ ¼åå¨" value="market" /> |
| | | <el-option label="ææ¬åå" value="cost" /> |
| | | <el-option label="ä¾åºåè°æ´" value="supplier" /> |
| | | <el-option label="å
¶ä»åå " value="other" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | productName: '', |
| | | supplierName: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | productName: '', |
| | | specification: '', |
| | | supplierName: '', |
| | | oldPrice: 0, |
| | | newPrice: 0, |
| | | effectiveTime: '', |
| | | reason: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | productName: 'ååA', |
| | | specification: 'è§æ ¼1', |
| | | supplierName: 'ä¾åºåA', |
| | | oldPrice: 50.00, |
| | | newPrice: 55.00, |
| | | priceChange: 10.00, |
| | | effectiveTime: '2025-12-01 00:00:00', |
| | | status: 'active', |
| | | reason: 'å¸åºä»·æ ¼åå¨', |
| | | remark: 'å¸åºä»·æ ¼ä¸æ¶¨' |
| | | }, |
| | | { |
| | | id: 2, |
| | | productName: 'ååB', |
| | | specification: 'è§æ ¼2', |
| | | supplierName: 'ä¾åºåB', |
| | | oldPrice: 80.00, |
| | | newPrice: 75.00, |
| | | priceChange: -6.25, |
| | | effectiveTime: '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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="searchForm.supplierName" placeholder="请è¾å
¥ä¾åºååç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="订åç¶æï¼"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©ç¶æ" clearable> |
| | | <el-option label="è稿" value="draft" /> |
| | | <el-option label="å¾
å®¡æ ¸" value="pending" /> |
| | | <el-option label="å·²å®¡æ ¸" value="approved" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢è®¢å</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete" :disabled="!selectedRows.length">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="订åç¼å·" prop="orderNo" width="180" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="订åç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æ»éé¢" prop="totalAmount" width="120"> |
| | | <template #default="{ row }">Â¥{{ row.totalAmount.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="å建æ¶é´" prop="createTime" width="180" /> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" size="small" @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" size="small" @click="viewDetails(row)">æ¥ç</el-button> |
| | | <el-button type="danger" size="small" @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢éè´è®¢å' : 'ç¼è¾éè´è®¢å'" width="800px"> |
| | | <el-form :model="formData" ref="formRef" label-width="120px"> |
| | | <el-form-item label="ä¾åºååç§°" prop="supplierName"> |
| | | <el-select v-model="formData.supplierName" placeholder="è¯·éæ©ä¾åºå" style="width: 100%"> |
| | | <el-option label="ä¾åºåA" value="ä¾åºåA" /> |
| | | <el-option label="ä¾åºåB" value="ä¾åºåB" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | supplierName: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | supplierName: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | orderNo: 'PO20241201001', |
| | | supplierName: 'ä¾åºåA', |
| | | status: 'approved', |
| | | totalAmount: 12500.00, |
| | | createTime: '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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="è´¨æ£åå·ï¼" style="width: 300px;"> |
| | | <el-input v-model="searchForm.inspectionNo" placeholder="请è¾å
¥è´¨æ£åå·" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="è´¨æ£ç¶æï¼" style="width: 300px;"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©ç¶æ" clearable> |
| | | <el-option label="å¾
è´¨æ£" value="pending" /> |
| | | <el-option label="è´¨æ£ä¸" value="inspecting" /> |
| | | <el-option label="已宿" value="completed" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢è´¨æ£å</el-button> |
| | | <el-button type="success" @click="handleBatchComplete">æ¹é宿</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="è´¨æ£åå·" prop="inspectionNo" width="180" /> |
| | | <el-table-column label="å°è´§åå·" prop="arrivalNo" width="180" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="è´¨æ£ç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åæ ¼æ°é" prop="qualifiedQuantity" width="100" /> |
| | | <el-table-column label="ä¸åæ ¼æ°é" prop="unqualifiedQuantity" width="100" /> |
| | | <el-table-column label="è´¨æ£æ¶é´" prop="inspectionTime" width="180" /> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleComplete(row)" v-if="row.status !== 'completed'">宿</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢è´¨æ£å' : 'ç¼è¾è´¨æ£å'" width="1000px"> |
| | | <el-form :model="formData" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å°è´§åå·"> |
| | | <el-select v-model="formData.arrivalNo" placeholder="è¯·éæ©å°è´§å" style="width: 100%"> |
| | | <el-option label="AR20241201001" value="AR20241201001" /> |
| | | <el-option label="AR20241201002" value="AR20241201002" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºååç§°"> |
| | | <el-input v-model="formData.supplierName" placeholder="ä¾åºååç§°" readonly /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-form-item label="è´¨æ£åå"> |
| | | <div class="product-list" style="width: 100%;"> |
| | | <el-table :data="formData.products" border width="100%"> |
| | | <el-table-column label="åååç§°" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.productName" placeholder="请è¾å
¥åååç§°" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="è§æ ¼åå·" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.specification" placeholder="请è¾å
¥è§æ ¼åå·" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å°è´§æ°é" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input-number v-model="row.arrivalQuantity" :min="0" placeholder="æ°é" style="width: 100%;"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åæ ¼æ°é" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input-number v-model="row.qualifiedQuantity" :min="0" placeholder="æ°é" style="width: 100%;"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä¸åæ ¼æ°é" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input-number v-model="row.unqualifiedQuantity" :min="0" placeholder="æ°é" style="width: 100%;"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä¸åæ ¼åå " width="200"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.unqualifiedReason" placeholder="请è¾å
¥ä¸åæ ¼åå " /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="100"> |
| | | <template #default="{ $index }"> |
| | | <el-button type="danger" link @click="removeProduct($index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div class="add-product-btn"> |
| | | <el-button type="primary" @click="addProduct">æ·»å åå</el-button> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="è´¨æ£å"> |
| | | <el-input v-model="formData.inspector" placeholder="请è¾å
¥è´¨æ£åå§å" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | inspectionNo: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | arrivalNo: '', |
| | | supplierName: '', |
| | | products: [], |
| | | inspector: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | inspectionNo: 'QI20241201001', |
| | | arrivalNo: 'AR20241201001', |
| | | supplierName: 'ä¾åºåA', |
| | | status: 'completed', |
| | | qualifiedQuantity: 240, |
| | | unqualifiedQuantity: 10, |
| | | inspectionTime: '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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="éè´§åå·ï¼" style="width: 300px;"> |
| | | <el-input v-model="searchForm.returnNo" placeholder="请è¾å
¥éè´§åå·" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="éè´§ç±»åï¼" style="width: 300px;"> |
| | | <el-select v-model="searchForm.returnType" placeholder="è¯·éæ©ç±»å" clearable> |
| | | <el-option label="éè´éè´§" value="purchase" /> |
| | | <el-option label="è´¨æ£éè´§" value="quality" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢éè´§å</el-button> |
| | | <el-button type="success" @click="handleBatchApprove">æ¹éå®¡æ ¸</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="éè´§åå·" prop="returnNo" width="180" /> |
| | | <el-table-column label="å
³èåå·" prop="relatedNo" width="180" /> |
| | | <el-table-column label="éè´§ç±»å" prop="returnType" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.returnType === 'purchase' ? 'danger' : 'warning'"> |
| | | {{ getReturnTypeText(row.returnType) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="éè´§ç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="éè´§éé¢" prop="returnAmount" width="120"> |
| | | <template #default="{ row }">Â¥{{ row.returnAmount.toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="å建æ¶é´" prop="createTime" width="180" /> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleApprove(row)" v-if="row.status === 'pending'">å®¡æ ¸</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢éè´§å' : 'ç¼è¾éè´§å'" width="600px"> |
| | | <el-form :model="formData" label-width="120px"> |
| | | <el-form-item label="éè´§ç±»å"> |
| | | <el-select v-model="formData.returnType" placeholder="è¯·éæ©éè´§ç±»å" style="width: 100%"> |
| | | <el-option label="éè´éè´§" value="purchase" /> |
| | | <el-option label="è´¨æ£éè´§" value="quality" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="å
³èåå·"> |
| | | <el-input v-model="formData.relatedNo" placeholder="请è¾å
¥å
³èåå·" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°"> |
| | | <el-input v-model="formData.supplierName" placeholder="请è¾å
¥ä¾åºååç§°" /> |
| | | </el-form-item> |
| | | <el-form-item label="éè´§åå "> |
| | | <el-select v-model="formData.returnReason" placeholder="è¯·éæ©éè´§åå " style="width: 100%"> |
| | | <el-option label="è´¨éé®é¢" value="quality" /> |
| | | <el-option label="è§æ ¼ä¸ç¬¦" value="specification" /> |
| | | <el-option label="æ°éé误" value="quantity" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | returnNo: '', |
| | | returnType: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | returnType: '', |
| | | relatedNo: '', |
| | | supplierName: '', |
| | | returnReason: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | returnNo: 'RT20241201001', |
| | | relatedNo: 'PO20241201001', |
| | | returnType: 'purchase', |
| | | supplierName: 'ä¾åºåA', |
| | | status: 'approved', |
| | | returnAmount: 500.00, |
| | | createTime: '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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.productName" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.identifierType" placeholder="è¯·éæ©æ è¯ç±»å" clearable> |
| | | <el-option label="äºç»´ç " value="äºç»´ç "></el-option> |
| | | <el-option label="é²ä¼ªç " value="é²ä¼ªç "></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©ç¶æ" clearable> |
| | | <el-option label="å·²çæ" value="å·²çæ"></el-option> |
| | | <el-option label="å·²åé
" value="å·²åé
"></el-option> |
| | | <el-option label="已使ç¨" value="已使ç¨"></el-option> |
| | | <el-option label="å·²ä½åº" value="å·²ä½åº"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | | æ°å¢æ è¯ |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- äº§åæ è¯å表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="productName" label="产ååç§°" width="150" /> |
| | | <el-table-column prop="productCode" label="产åç¼ç " width="120" /> |
| | | <el-table-column prop="batchNo" label="æ¹æ¬¡å·" width="120" /> |
| | | <el-table-column prop="identifierType" label="æ è¯ç±»å" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getIdentifierTypeType(scope.row.identifierType)"> |
| | | {{ scope.row.identifierType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="identifierCode" label="æ è¯ç " /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="generateTime" label="çææ¶é´" width="160" /> |
| | | <el-table-column label="æä½" fixed="right" align="center" width="280"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleView(scope.row)">æ¥ç</el-button> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="success" @click="generateQRCode(scope.row)">çæäºç»´ç </el-button> |
| | | <el-button link type="primary" @click="handleExport(scope.row)">导åº</el-button> |
| | | <el-button link type="primary" @click="handleReassign(scope.row)" v-if="scope.row.status === 'å·²åé
'">éæ°åé
</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产ååç§°" prop="productName"> |
| | | <el-input v-model="form.productName" placeholder="请è¾å
¥äº§ååç§°"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产åç¼ç " prop="productCode"> |
| | | <el-input v-model="form.productCode" placeholder="请è¾å
¥äº§åç¼ç "></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¹æ¬¡å·" prop="batchNo"> |
| | | <el-input v-model="form.batchNo" placeholder="请è¾å
¥æ¹æ¬¡å·"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ è¯ç±»å" prop="identifierType"> |
| | | <el-select v-model="form.identifierType" placeholder="è¯·éæ©æ è¯ç±»å" style="width: 100%"> |
| | | <el-option label="äºç»´ç " value="äºç»´ç "></el-option> |
| | | <el-option label="é²ä¼ªç " value="é²ä¼ªç "></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="çææ°é" prop="quantity"> |
| | | <el-input-number v-model="form.quantity" :min="1" :max="10000" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="å·²çæ" value="å·²çæ"></el-option> |
| | | <el-option label="å·²åé
" value="å·²åé
"></el-option> |
| | | <el-option label="已使ç¨" value="已使ç¨"></el-option> |
| | | <el-option label="å·²ä½åº" value="å·²ä½åº"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input type="textarea" v-model="form.remark" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" rows="3"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ è¯çæå¯¹è¯æ¡ --> |
| | | <el-dialog v-model="generateDialogVisible" title="æ è¯çæ" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="产ååç§°"> |
| | | <span>{{ currentProduct.productName }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="产åç¼ç "> |
| | | <span>{{ currentProduct.productCode }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æ¹æ¬¡å·"> |
| | | <span>{{ currentProduct.batchNo }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æ è¯ç±»å"> |
| | | <span>{{ currentProduct.identifierType }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="çææ°é" prop="generateQuantity"> |
| | | <el-input-number v-model="generateQuantity" :min="1" :max="10000" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | <el-form-item label="ç¼ç è§å" prop="codeRule"> |
| | | <el-select v-model="codeRule" placeholder="è¯·éæ©ç¼ç è§å" style="width: 100%"> |
| | | <el-option label="产åç¼ç +æ¹æ¬¡å·+åºå·" value="产åç¼ç +æ¹æ¬¡å·+åºå·"></el-option> |
| | | <el-option label="æ¶é´æ³+éæºæ°" value="æ¶é´æ³+éæºæ°"></el-option> |
| | | <el-option label="èªå®ä¹è§å" value="èªå®ä¹è§å"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="èªå®ä¹åç¼" prop="customPrefix" v-if="codeRule === 'èªå®ä¹è§å'"> |
| | | <el-input v-model="customPrefix" placeholder="请è¾å
¥èªå®ä¹åç¼"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="generateDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="generateIdentifiers">ç æ</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- éæ°åé
å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="reassignDialogVisible" title="éæ°åé
æ è¯" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="产ååç§°"> |
| | | <span>{{ currentProduct.productName }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æ è¯ç "> |
| | | <span>{{ currentProduct.identifierCode }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æ°æ¹æ¬¡å·" prop="newBatchNo"> |
| | | <el-input v-model="newBatchNo" placeholder="请è¾å
¥æ°æ¹æ¬¡å·"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="åé
åå " prop="reassignReason"> |
| | | <el-input type="textarea" v-model="reassignReason" rows="3" placeholder="请è¾å
¥éæ°åé
åå "></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="reassignDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="saveReassign">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- äºç»´ç é¢è§å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="qrCodeDialogVisible" title="äºç»´ç é¢è§" width="500px" center> |
| | | <div class="qr-preview-container"> |
| | | <div v-if="qrCodeUrl" class="qr-image-container"> |
| | | <img :src="qrCodeUrl" alt="äºç»´ç " class="qr-image" /> |
| | | <div class="qr-info"> |
| | | <p><strong>产ååç§°ï¼</strong>{{ currentQRProduct.productName }}</p> |
| | | <p><strong>产åç¼ç ï¼</strong>{{ currentQRProduct.productCode }}</p> |
| | | <p><strong>æ¹æ¬¡å·ï¼</strong>{{ currentQRProduct.batchNo }}</p> |
| | | <p><strong>æ è¯ç ï¼</strong>{{ currentQRProduct.identifierCode }}</p> |
| | | <p><strong>æ è¯ç±»åï¼</strong>{{ currentQRProduct.identifierType }}</p> |
| | | </div> |
| | | </div> |
| | | <div v-else class="qr-loading"> |
| | | <el-icon class="is-loading"><Loading /></el-icon> |
| | | <p>æ£å¨çæäºç»´ç ...</p> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="qrCodeDialogVisible = false">å
³é</el-button> |
| | | <el-button |
| | | v-if="qrCodeUrl" |
| | | type="primary" |
| | | @click="copyQRContent" |
| | | icon="CopyDocument" |
| | | > |
| | | å¤å¶å
容 |
| | | </el-button> |
| | | <el-button |
| | | v-if="qrCodeUrl" |
| | | type="success" |
| | | @click="downloadQRCode" |
| | | icon="Download" |
| | | > |
| | | ä¸è½½äºç»´ç |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search, Loading, Download } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | import QRCode from 'qrcode' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | productName: '', |
| | | identifierType: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const identifierList = ref([ |
| | | { |
| | | id: 1, |
| | | productName: 'å·¥ä¸ä¼ æå¨Aå', |
| | | productCode: 'SENSOR001', |
| | | batchNo: 'B202312001', |
| | | identifierType: 'äºç»´ç ', |
| | | identifierCode: 'QR_SENSOR001_B202312001_001', |
| | | status: 'å·²åé
', |
| | | generateTime: '2023-12-01 10:00:00', |
| | | remark: 'éè¦äº§åæ è¯' |
| | | }, |
| | | { |
| | | id: 2, |
| | | productName: 'æ§å¶é¢æ¿Bå', |
| | | productCode: 'PANEL002', |
| | | batchNo: 'B202312002', |
| | | identifierType: 'é²ä¼ªç ', |
| | | identifierCode: 'SEC_PANEL002_B202312002_001', |
| | | status: 'å·²çæ', |
| | | generateTime: '2023-12-02 14:30:00', |
| | | remark: '常è§äº§åæ è¯' |
| | | }, |
| | | { |
| | | id: 3, |
| | | productName: 'æ°æ®ééå¨Cå', |
| | | productCode: 'COLLECTOR003', |
| | | batchNo: 'B202312003', |
| | | identifierType: 'é²ä¼ªç ', |
| | | identifierCode: 'SEC_COLLECTOR003_B202312003_001', |
| | | status: '已使ç¨', |
| | | generateTime: '2023-12-03 09:15:00', |
| | | remark: 'æµè¯äº§åæ è¯' |
| | | } |
| | | ]) |
| | | |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢æ è¯') |
| | | const form = reactive({ |
| | | productName: '', |
| | | productCode: '', |
| | | batchNo: '', |
| | | identifierType: '', |
| | | quantity: 1, |
| | | status: 'å·²çæ', |
| | | remark: '' |
| | | }) |
| | | |
| | | const rules = { |
| | | productName: [{ required: true, message: '请è¾å
¥äº§ååç§°', trigger: 'blur' }], |
| | | productCode: [{ required: true, message: '请è¾å
¥äº§åç¼ç ', trigger: 'blur' }], |
| | | batchNo: [{ required: true, message: '请è¾å
¥æ¹æ¬¡å·', trigger: 'blur' }], |
| | | identifierType: [{ required: true, message: 'è¯·éæ©æ è¯ç±»å', trigger: 'change' }], |
| | | quantity: [{ required: true, message: '请è¾å
¥çææ°é', trigger: 'blur' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const generateDialogVisible = ref(false) |
| | | const reassignDialogVisible = ref(false) |
| | | const currentProduct = ref({}) |
| | | const generateQuantity = ref(1) |
| | | const codeRule = ref('') |
| | | const customPrefix = ref('') |
| | | const newBatchNo = ref('') |
| | | const reassignReason = ref('') |
| | | const formRef = ref() |
| | | |
| | | // äºç»´ç ç¸å
³åé |
| | | const qrCodeDialogVisible = ref(false) |
| | | const qrCodeUrl = ref('') |
| | | const currentQRProduct = ref({}) |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredList = computed(() => { |
| | | let list = identifierList.value |
| | | if (searchForm.productName) { |
| | | list = list.filter(item => item.productName.includes(searchForm.productName)) |
| | | } |
| | | if (searchForm.identifierType) { |
| | | list = list.filter(item => item.identifierType === searchForm.identifierType) |
| | | } |
| | | if (searchForm.status) { |
| | | list = list.filter(item => item.status === searchForm.status) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | | const getIdentifierTypeType = (type) => { |
| | | const typeMap = { |
| | | 'äºç»´ç ': 'success', |
| | | 'é²ä¼ªç ': 'warning' |
| | | } |
| | | return typeMap[type] || 'info' |
| | | } |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'å·²çæ': 'info', |
| | | 'å·²åé
': 'primary', |
| | | '已使ç¨': 'success', |
| | | 'å·²ä½åº': 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | // æç´¢é»è¾å·²å¨computedä¸å¤ç |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.productName = '' |
| | | searchForm.identifierType = '' |
| | | searchForm.status = '' |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å¢æ è¯' |
| | | isEdit.value = false |
| | | form.productName = '' |
| | | form.productCode = '' |
| | | form.batchNo = '' |
| | | form.identifierType = '' |
| | | form.quantity = 1 |
| | | form.status = 'å·²çæ' |
| | | form.remark = '' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleView = (row) => { |
| | | // æ¥çæ è¯è¯¦æ
|
| | | ElMessage.info('æ¥çæ è¯è¯¦æ
åè½å¾
å®ç°') |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ç¼è¾æ è¯' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleExport = (row) => { |
| | | // å¯¼åºæ è¯ |
| | | ElMessage.success(`å·²å¯¼åºæ è¯: ${row.identifierCode}`) |
| | | } |
| | | |
| | | const handleReassign = (row) => { |
| | | currentProduct.value = row |
| | | newBatchNo.value = '' |
| | | reassignReason.value = '' |
| | | reassignDialogVisible.value = true |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥æ è¯åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = identifierList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | identifierList.value.splice(index, 1) |
| | | pagination.total-- |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // çæäºç»´ç |
| | | const generateQRCode = async (row) => { |
| | | try { |
| | | // æ£æ¥å¿
è¦å段 |
| | | if (!row.productName || !row.productCode || !row.batchNo) { |
| | | ElMessage.warning('产åä¿¡æ¯ä¸å®æ´ï¼æ æ³çæäºç»´ç ') |
| | | return |
| | | } |
| | | |
| | | currentQRProduct.value = row |
| | | qrCodeUrl.value = '' |
| | | qrCodeDialogVisible.value = true |
| | | |
| | | // æå»ºäºç»´ç å
容 |
| | | let qrContent = '' |
| | | if (row.identifierType === 'äºç»´ç ') { |
| | | qrContent = `${row.productName}|${row.productCode}|${row.batchNo}|${row.identifierCode}` |
| | | } else if (row.identifierType === 'é²ä¼ªç ') { |
| | | // é²ä¼ªç æ ¼å¼ï¼SEC_产åç¼ç _æ¹æ¬¡å·_æ¶é´æ³_éæºæ° |
| | | const timestamp = Date.now() |
| | | const random = Math.random().toString(36).substr(2, 8) |
| | | qrContent = `SEC_${row.productCode}_${row.batchNo}_${timestamp}_${random}` |
| | | } |
| | | |
| | | // çæäºç»´ç |
| | | qrCodeUrl.value = await QRCode.toDataURL(qrContent, { |
| | | width: 256, |
| | | margin: 2, |
| | | color: { |
| | | dark: '#000000', |
| | | light: '#FFFFFF' |
| | | }, |
| | | errorCorrectionLevel: row.identifierType === 'é²ä¼ªç ' ? 'H' : 'M' |
| | | }) |
| | | |
| | | ElMessage.success('äºç»´ç çææåï¼') |
| | | |
| | | } catch (error) { |
| | | console.error('çæäºç»´ç 失败:', error) |
| | | ElMessage.error('çæäºç»´ç 失败ï¼' + error.message) |
| | | qrCodeDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | // ä¸è½½äºç»´ç |
| | | const downloadQRCode = () => { |
| | | if (!qrCodeUrl.value) { |
| | | ElMessage.warning('请å
çæäºç»´ç ') |
| | | return |
| | | } |
| | | |
| | | const a = document.createElement('a') |
| | | a.href = qrCodeUrl.value |
| | | a.download = `${currentQRProduct.value.productName}_${currentQRProduct.value.identifierType}_${new Date().getTime()}.png` |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | document.body.removeChild(a) |
| | | ElMessage.success('ä¸è½½æåï¼') |
| | | } |
| | | |
| | | // å¤å¶äºç»´ç å
容 |
| | | const copyQRContent = async () => { |
| | | if (!currentQRProduct.value) { |
| | | ElMessage.warning('没æå¯å¤å¶çå
容') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | let content = '' |
| | | if (currentQRProduct.value.identifierType === 'äºç»´ç ') { |
| | | content = `${currentQRProduct.value.productName}|${currentQRProduct.value.productCode}|${currentQRProduct.value.batchNo}|${currentQRProduct.value.identifierCode}` |
| | | } else if (currentQRProduct.value.identifierType === 'é²ä¼ªç ') { |
| | | const timestamp = Date.now() |
| | | const random = Math.random().toString(36).substr(2, 8) |
| | | content = `SEC_${currentQRProduct.value.productCode}_${currentQRProduct.value.batchNo}_${timestamp}_${random}` |
| | | } |
| | | |
| | | await navigator.clipboard.writeText(content) |
| | | ElMessage.success('å
容已å¤å¶å°åªè´´æ¿') |
| | | } catch (error) { |
| | | // éçº§æ¹æ¡ |
| | | const textArea = document.createElement('textarea') |
| | | textArea.value = content |
| | | document.body.appendChild(textArea) |
| | | textArea.select() |
| | | document.execCommand('copy') |
| | | document.body.removeChild(textArea) |
| | | ElMessage.success('å
容已å¤å¶å°åªè´´æ¿') |
| | | } |
| | | } |
| | | |
| | | const generateIdentifiers = () => { |
| | | if (!codeRule.value) { |
| | | ElMessage.warning('è¯·éæ©ç¼ç è§å') |
| | | return |
| | | } |
| | | |
| | | // çææ è¯çé»è¾ |
| | | const newIdentifiers = [] |
| | | for (let i = 1; i <= generateQuantity.value; i++) { |
| | | let identifierCode = '' |
| | | if (codeRule.value === '产åç¼ç +æ¹æ¬¡å·+åºå·') { |
| | | identifierCode = `${currentProduct.value.productCode}_${currentProduct.value.batchNo}_${String(i).padStart(3, '0')}` |
| | | } else if (codeRule.value === 'æ¶é´æ³+éæºæ°') { |
| | | identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}` |
| | | } else if (codeRule.value === 'èªå®ä¹è§å') { |
| | | identifierCode = `${customPrefix.value || 'CUSTOM'}_${Date.now()}_${i}` |
| | | } |
| | | |
| | | newIdentifiers.push({ |
| | | id: Math.max(...identifierList.value.map(item => item.id)) + i, |
| | | productName: currentProduct.value.productName, |
| | | productCode: currentProduct.value.productCode, |
| | | batchNo: currentProduct.value.batchNo, |
| | | identifierType: currentProduct.value.identifierType, |
| | | identifierCode: identifierCode, |
| | | status: 'å·²çæ', |
| | | generateTime: new Date().toLocaleString(), |
| | | remark: 'æ¹éçæ' |
| | | }) |
| | | } |
| | | |
| | | identifierList.value.push(...newIdentifiers) |
| | | pagination.total += newIdentifiers.length |
| | | ElMessage.success(`æåçæ ${newIdentifiers.length} 个æ è¯`) |
| | | generateDialogVisible.value = false |
| | | } |
| | | |
| | | const saveReassign = () => { |
| | | if (!newBatchNo.value) { |
| | | ElMessage.warning('请è¾å
¥æ°æ¹æ¬¡å·') |
| | | return |
| | | } |
| | | |
| | | const index = identifierList.value.findIndex(item => item.id === currentProduct.value.id) |
| | | if (index > -1) { |
| | | identifierList.value[index].batchNo = newBatchNo.value |
| | | identifierList.value[index].status = 'å·²åé
' |
| | | ElMessage.success('æ è¯éæ°åé
æå') |
| | | reassignDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = identifierList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | identifierList.value[index] = { ...form, id: editId.value } |
| | | ElMessage.success('ç¼è¾æå') |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | const newId = Math.max(...identifierList.value.map(item => item.id)) + 1 |
| | | |
| | | // æ ¹æ®æ è¯ç±»åçæä¸åçæ è¯ç |
| | | let identifierCode = '' |
| | | if (form.identifierType === 'äºç»´ç ') { |
| | | identifierCode = `QR_${form.productCode}_${form.batchNo}_001` |
| | | } else if (form.identifierType === 'é²ä¼ªç ') { |
| | | identifierCode = `SEC_${form.productCode}_${form.batchNo}_001` |
| | | } |
| | | |
| | | identifierList.value.push({ |
| | | ...form, |
| | | id: newId, |
| | | identifierCode: identifierCode, |
| | | generateTime: new Date().toLocaleString() |
| | | }) |
| | | pagination.total++ |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val.page |
| | | pagination.pageSize = val.limit |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .quick-actions-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .quick-actions-row .el-alert { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .quick-actions-row .el-alert p { |
| | | margin: 5px 0; |
| | | font-size: 14px; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | /* äºç»´ç é¢è§æ ·å¼ */ |
| | | .qr-preview-container { |
| | | text-align: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .qr-image-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .qr-image { |
| | | max-width: 100%; |
| | | height: auto; |
| | | border: 2px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .qr-info { |
| | | text-align: left; |
| | | background: #f8f9fa; |
| | | padding: 15px; |
| | | border-radius: 8px; |
| | | min-width: 300px; |
| | | } |
| | | |
| | | .qr-info p { |
| | | margin: 8px 0; |
| | | color: #666; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .qr-loading { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 15px; |
| | | padding: 40px 0; |
| | | } |
| | | |
| | | .qr-loading .el-icon { |
| | | font-size: 32px; |
| | | color: #409EFF; |
| | | } |
| | | |
| | | .qr-loading p { |
| | | color: #666; |
| | | margin: 0; |
| | | } |
| | | </style> |
| | |
| | | <div style="display: flex;flex-direction: row;align-items: center;"> |
| | | <div> |
| | | <span class="search_title">ç±»åï¼</span> |
| | | <el-select v-model="searchForm.inspectType" clearable style="width: 240px" @change="handleQuery"> |
| | | <el-select v-model="searchForm.inspectType" clearable style="width: 200px" @change="handleQuery"> |
| | | <el-option label="åæææ£éª" :value="0" /> |
| | | <el-option label="è¿ç¨æ£éª" :value="1" /> |
| | | <el-option label="åºåæ£éª" :value="2" /> |
| | |
| | | </div> |
| | | <div style="margin-left: 10px"> |
| | | <span class="search_title">ç¶æï¼</span> |
| | | <el-select v-model="searchForm.inspectState" clearable style="width: 240px" @change="handleQuery"> |
| | | <el-select v-model="searchForm.inspectState" clearable style="width: 200px" @change="handleQuery"> |
| | | <el-option label="å¾
å¤ç" :value="0" /> |
| | | <el-option label="å·²å¤ç" :value="1" /> |
| | | </el-select> |
| | |
| | | <span class="search_title">产ååç§°ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.productName" |
| | | style="width: 240px" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥äº§ååç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | |
| | | </div> |
| | | <span style="margin-left: 10px" class="search_title">æ£æµæ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | style="width: 300px" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="quality-dashboard"> |
| | | <el-row :gutter="16"> |
| | | <el-col :xs="24" :sm="12"> |
| | | <el-card shadow="hover" class="panel"> |
| | | <template #header> |
| | | <div class="panel-title"> |
| | | æ£æµæ ·åå¨æç¶æ |
| | | <div class="actions"> |
| | | <el-switch v-model="voiceEnabled" active-text="è¯é³é¢è¦" inactive-text="éé³" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <div class="status-list"> |
| | | <div v-for="item in sampleStatus" :key="item.id" class="status-item" :class="item.status"> |
| | | <div class="left"> |
| | | <span class="dot" :class="item.status"></span> |
| | | <span class="name">{{ item.name }}</span> |
| | | </div> |
| | | <div class="right"> |
| | | <el-tag :type="statusTagType(item.status)" size="small">{{ statusLabel(item.status) }}</el-tag> |
| | | <span class="time">{{ item.time }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12"> |
| | | <el-card shadow="hover" class="panel"> |
| | | <template #header> |
| | | <div class="panel-title">任塿è¡ï¼Top 10ï¼</div> |
| | | </template> |
| | | <EChart :xAxis="tasksXAxis" :yAxis="[{ type: 'value' }]" :series="tasksSeries" :grid="{ left: 40, right: 20, top: 20, bottom: 40 }" :tooltip="{ trigger: 'axis' }" :barColors="['#3b82f6']" :chartStyle="{ height: '320px', width: '100%' }" /> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="16" style="margin-top: 16px;"> |
| | | <el-col :xs="24" :sm="14"> |
| | | <el-card shadow="hover" class="panel"> |
| | | <template #header> |
| | | <div class="panel-title">åå²è¶å¿</div> |
| | | </template> |
| | | <EChart :xAxis="[{ type: 'category', data: trendXAxis }]" :yAxis="[{ type: 'value', name: 'æ°é' }]" :series="trendSeries" :tooltip="{ trigger: 'axis' }" :legend="{ top: 0 }" :lineColors="['#10b981', '#f59e0b']" :chartStyle="{ height: '340px', width: '100%' }" /> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="10"> |
| | | <el-card shadow="hover" class="panel"> |
| | | <template #header> |
| | | <div class="panel-title">åæ ¼çåæ</div> |
| | | </template> |
| | | <EChart :series="passRateSeries" :legend="{ show: false }" :chartStyle="{ height: '340px', width: '100%' }" /> |
| | | <div class="passrate-text"> |
| | | å½ååæ ¼çï¼<b>{{ (passRate * 100).toFixed(1) }}%</b> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="16" style="margin-top: 16px;"> |
| | | <el-col :xs="24"> |
| | | <el-card shadow="hover" class="panel"> |
| | | <template #header> |
| | | <div class="panel-title">SPC æ§å¶å¾ï¼Xbarï¼</div> |
| | | </template> |
| | | <EChart :xAxis="[{ type: 'category', data: spcXAxis }]" :yAxis="[{ type: 'value', name: 'æµéå¼' }]" :series="spcSeries" :legend="{ top: 0 }" :tooltip="{ trigger: 'axis' }" :lineColors="['#2563eb', '#ef4444', '#f97316', '#22c55e']" :chartStyle="{ height: '380px', width: '100%' }" /> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, onBeforeUnmount, reactive, ref } from 'vue' |
| | | import EChart from '@/components/Echarts/echarts.vue' |
| | | |
| | | const voiceEnabled = ref(true) |
| | | let dataTimer = null |
| | | |
| | | // 1) æ ·åå¨æç¶æï¼æ»å¨æ´æ°ï¼ |
| | | const sampleStatus = ref([]) |
| | | const statusPool = ['processing', 'warning', 'error', 'success'] |
| | | function statusLabel(s) { |
| | | return s === 'processing' ? 'æ£æµä¸' : s === 'warning' ? 'é¢è¦' : s === 'error' ? 'ä¸åæ ¼' : 'åæ ¼' |
| | | } |
| | | function statusTagType(s) { |
| | | return s === 'processing' ? 'info' : s === 'warning' ? 'warning' : s === 'error' ? 'danger' : 'success' |
| | | } |
| | | function randomSample() { |
| | | const id = Math.random().toString(36).slice(2, 8) |
| | | const status = statusPool[Math.floor(Math.random() * statusPool.length)] |
| | | const name = `æ ·å-${Math.floor(Math.random() * 900 + 100)}` |
| | | const time = new Date().toLocaleTimeString('zh-CN', { hour12: false }) |
| | | return { id, name, status, time } |
| | | } |
| | | |
| | | // 2) 任塿è¡ï¼æ±ç¶å¾ï¼ |
| | | const tasksXAxis = reactive([{ type: 'category', data: [] }]) |
| | | const tasksSeries = ref([ |
| | | { |
| | | type: 'bar', |
| | | data: [], |
| | | label: { show: true, position: 'inside', align: 'center', verticalAlign: 'middle', color: '#fff' }, |
| | | encode: undefined, |
| | | }, |
| | | ]) |
| | | |
| | | // 3) åå²è¶å¿ï¼æçº¿ï¼ |
| | | const trendXAxis = ref([]) |
| | | const trendSeries = ref([ |
| | | { name: 'æ¥æ ·æ°', type: 'line', smooth: true, data: [] }, |
| | | { name: '宿æ°', type: 'line', smooth: true, data: [] }, |
| | | ]) |
| | | |
| | | // 4) åæ ¼çåæï¼ä»ªè¡¨çï¼ |
| | | const passRate = ref(0.92) |
| | | const passRateSeries = ref([ |
| | | { |
| | | type: 'gauge', |
| | | progress: { show: true, width: 12 }, |
| | | axisLine: { lineStyle: { width: 12 } }, |
| | | pointer: { show: true }, |
| | | detail: { valueAnimation: true, formatter: (v) => `${(v * 100).toFixed(1)}%` }, |
| | | data: [{ value: passRate.value }], |
| | | }, |
| | | ]) |
| | | |
| | | // 5) SPC æ§å¶å¾ |
| | | const spcXAxis = ref([]) |
| | | const spcData = ref([]) // å®é
æµéå¼ |
| | | const CL = ref(50) |
| | | const UCL = ref(55) |
| | | const LCL = ref(45) |
| | | const spcSeries = ref([ |
| | | { |
| | | name: 'æµéåå¼', |
| | | type: 'line', |
| | | smooth: false, |
| | | symbol: 'circle', |
| | | data: [], |
| | | markLine: { |
| | | symbol: 'none', |
| | | lineStyle: { type: 'dashed', color: '#999' }, |
| | | data: [ |
| | | { yAxis: () => UCL.value, name: 'UCL' }, |
| | | { yAxis: () => CL.value, name: 'CL' }, |
| | | { yAxis: () => LCL.value, name: 'LCL' }, |
| | | ], |
| | | label: { formatter: ({ name }) => name }, |
| | | }, |
| | | }, |
| | | { name: 'UCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#ef4444' } }, |
| | | { name: 'CL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#f97316' } }, |
| | | { name: 'LCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#22c55e' } }, |
| | | ]) |
| | | |
| | | // è¯é³ææ¥ |
| | | function speak(text) { |
| | | if (!voiceEnabled.value) return |
| | | if (!('speechSynthesis' in window)) return |
| | | const utter = new SpeechSynthesisUtterance(text) |
| | | utter.lang = 'zh-CN' |
| | | try { |
| | | window.speechSynthesis.cancel() |
| | | window.speechSynthesis.speak(utter) |
| | | } catch (e) { |
| | | // ignore |
| | | } |
| | | } |
| | | |
| | | function refreshFakeData() { |
| | | // æ ·åç¶ææ»å¨ |
| | | const next = randomSample() |
| | | sampleStatus.value = [next, ...sampleStatus.value].slice(0, 8) |
| | | |
| | | // ä»»å¡æè¡ |
| | | const tasks = Array.from({ length: 10 }).map((_, i) => ({ name: `ä»»å¡-${i + 1}`, count: Math.floor(Math.random() * 100 + 20) })) |
| | | tasks.sort((a, b) => a.count - b.count) |
| | | tasksXAxis.data = tasks.map(t => t.name) |
| | | tasksSeries.value[0].data = tasks.map(t => t.count) |
| | | |
| | | // åå²è¶å¿ï¼è¿½å ç¹ï¼ |
| | | const nowLabel = new Date().toLocaleTimeString('zh-CN', { minute: '2-digit', second: '2-digit' }) |
| | | if (trendXAxis.value.length > 15) { |
| | | trendXAxis.value.shift() |
| | | trendSeries.value[0].data.shift() |
| | | trendSeries.value[1].data.shift() |
| | | } |
| | | trendXAxis.value.push(nowLabel) |
| | | const incoming = Math.floor(Math.random() * 30 + 20) |
| | | const finished = Math.max(0, incoming - Math.floor(Math.random() * 10)) |
| | | trendSeries.value[0].data.push(incoming) |
| | | trendSeries.value[1].data.push(finished) |
| | | |
| | | // åæ ¼çï¼è½»å¾®æ³¢å¨ï¼ |
| | | const delta = (Math.random() - 0.5) * 0.02 |
| | | passRate.value = Math.min(0.99, Math.max(0.6, passRate.value + delta)) |
| | | passRateSeries.value[0].data[0].value = passRate.value |
| | | |
| | | // SPC æ°æ®ï¼çªå£ç§»å¨ï¼ |
| | | const nextVal = CL.value + (Math.random() - 0.5) * 8 // æ³¢å¨ |
| | | if (spcXAxis.value.length > 30) { |
| | | spcXAxis.value.shift() |
| | | spcData.value.shift() |
| | | } |
| | | spcXAxis.value.push(`${spcXAxis.value.length + 1}`) |
| | | spcData.value.push(parseFloat(nextVal.toFixed(2))) |
| | | spcSeries.value[0].data = [...spcData.value] |
| | | spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value) |
| | | spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value) |
| | | spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value) |
| | | |
| | | // è§¦åææ¥ï¼åæ ¼çè¿ä½æ SPC è¶
é |
| | | if (passRate.value < 0.8) { |
| | | speak(`é¢è¦ï¼å½ååæ ¼ç为 ${(passRate.value * 100).toFixed(0)}%ï¼ä½äº 80% éå¼`) |
| | | } |
| | | const last = spcData.value[spcData.value.length - 1] |
| | | if (last > UCL.value) { |
| | | speak(`é¢è¦ï¼ææ°æµéå¼ ${last.toFixed(2)} è¶
è¿ä¸é`) |
| | | } |
| | | if (last < LCL.value) { |
| | | speak(`é¢è¦ï¼ææ°æµéå¼ ${last.toFixed(2)} ä½äºä¸é`) |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | // åå§åå æ¡åæ°æ® |
| | | sampleStatus.value = Array.from({ length: 5 }).map(() => randomSample()) |
| | | for (let i = 0; i < 10; i++) { |
| | | trendXAxis.value.push(`T-${i}`) |
| | | trendSeries.value[0].data.push(Math.floor(Math.random() * 30 + 20)) |
| | | trendSeries.value[1].data.push(Math.floor(Math.random() * 25 + 15)) |
| | | } |
| | | for (let i = 0; i < 20; i++) { |
| | | spcXAxis.value.push(`${i + 1}`) |
| | | const v = CL.value + (Math.random() - 0.5) * 6 |
| | | spcData.value.push(parseFloat(v.toFixed(2))) |
| | | } |
| | | spcSeries.value[0].data = [...spcData.value] |
| | | spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value) |
| | | spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value) |
| | | spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value) |
| | | |
| | | dataTimer = setInterval(refreshFakeData, 10000) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (dataTimer) clearInterval(dataTimer) |
| | | try { window.speechSynthesis && window.speechSynthesis.cancel() } catch (e) {} |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .quality-dashboard { |
| | | padding: 8px; |
| | | } |
| | | .panel-title { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | font-weight: 600; |
| | | } |
| | | .status-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | max-height: 320px; |
| | | overflow: auto; |
| | | } |
| | | .status-item { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 8px 10px; |
| | | border-radius: 6px; |
| | | background: var(--el-fill-color-light); |
| | | } |
| | | .status-item .left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .status-item .right { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | .status-item .name { font-weight: 500; } |
| | | .status-item .time { color: var(--el-text-color-secondary); font-size: 12px; } |
| | | .dot { |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | display: inline-block; |
| | | } |
| | | .dot.processing { background: #60a5fa; } |
| | | .dot.warning { background: #f59e0b; } |
| | | .dot.error { background: #ef4444; } |
| | | .dot.success { background: #10b981; } |
| | | .passrate-text { |
| | | text-align: center; |
| | | margin-top: 8px; |
| | | } |
| | | </style> |
| | | |
| | | |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="report-management"> |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <h2>æ¥è¡¨ç®¡ç</h2> |
| | | <p>æä¾æ ·åè¿åº¦ã设å¤ä½¿ç¨ãæ£æµé¡¹ç®ãé¢ç¨è®°å½çåç±»èªå¨ç»è®¡æ¥è¡¨</p> |
| | | </div> |
| | | |
| | | <!-- ç鿡件 --> |
| | | <el-card class="filter-card" shadow="never"> |
| | | <el-form :model="filterForm" inline> |
| | | <el-form-item label="æ¶é´èå´"> |
| | | <el-date-picker |
| | | v-model="filterForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | @change="handleFilterChange" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥è¡¨ç±»å"> |
| | | <el-select v-model="filterForm.reportType" placeholder="è¯·éæ©æ¥è¡¨ç±»å" @change="handleFilterChange"> |
| | | <el-option label="æ ·åè¿åº¦æ¥è¡¨" value="sample" /> |
| | | <el-option label="设å¤ä½¿ç¨æ¥è¡¨" value="equipment" /> |
| | | <el-option label="æ£æµé¡¹ç®æ¥è¡¨" value="inspection" /> |
| | | <el-option label="é¢ç¨è®°å½æ¥è¡¨" value="usage" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleFilterChange">æ¥è¯¢</el-button> |
| | | <el-button @click="resetFilter">éç½®</el-button> |
| | | <el-button type="success" @click="exportReport">å¯¼åºæ¥è¡¨</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- ç»è®¡å¡ç --> |
| | | <div class="statistics-cards"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><Box /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.totalSamples }}</div> |
| | | <div class="stat-label">æ»æ ·åæ°</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><Tools /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.activeEquipment }}</div> |
| | | <div class="stat-label">å¨ç¨è®¾å¤</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><Document /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.completedInspections }}</div> |
| | | <div class="stat-label">å·²å®ææ£æµ</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-card class="stat-card" shadow="hover"> |
| | | <div class="stat-content"> |
| | | <div class="stat-icon"> |
| | | <el-icon><ShoppingCart /></el-icon> |
| | | </div> |
| | | <div class="stat-info"> |
| | | <div class="stat-number">{{ statistics.totalUsage }}</div> |
| | | <div class="stat-label">æ»é¢ç¨æ¬¡æ°</div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <div class="charts-container"> |
| | | <el-row :gutter="20"> |
| | | <!-- æ ·åè¿åº¦å¾è¡¨ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ ·åè¿åº¦ç»è®¡</span> |
| | | <el-button type="text" @click="refreshSampleChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="sampleChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- 设å¤ä½¿ç¨å¾è¡¨ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>设å¤ä½¿ç¨çç»è®¡</span> |
| | | <el-button type="text" @click="refreshEquipmentChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="equipmentChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20" style="margin-top: 20px;"> |
| | | <!-- æ£æµé¡¹ç®ç»è®¡ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>æ£æµé¡¹ç®åå¸</span> |
| | | <el-button type="text" @click="refreshInspectionChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="inspectionChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- é¢ç¨è®°å½è¶å¿ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>é¢ç¨è®°å½è¶å¿</span> |
| | | <el-button type="text" @click="refreshUsageChart">å·æ°</el-button> |
| | | </div> |
| | | </template> |
| | | <div ref="usageChartRef" class="chart-container"></div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- è¯¦ç»æ°æ®è¡¨æ ¼ --> |
| | | <el-card class="table-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>è¯¦ç»æ°æ®</span> |
| | | <div> |
| | | <el-button type="primary" size="small" @click="refreshTable">å·æ°</el-button> |
| | | <el-button type="success" size="small" @click="exportTable">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <el-table |
| | | :data="tableData" |
| | | style="width: 100%" |
| | | v-loading="tableLoading" |
| | | stripe |
| | | border |
| | | > |
| | | <el-table-column prop="id" label="ç¼å·" width="80" /> |
| | | <el-table-column prop="name" label="åç§°" /> |
| | | <el-table-column prop="type" label="ç±»å" width="120" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="progress" label="è¿åº¦" width="120"> |
| | | <template #default="scope"> |
| | | <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createTime" label="å建æ¶é´" width="180" /> |
| | | <el-table-column prop="updateTime" label="æ´æ°æ¶é´" width="180" /> |
| | | <el-table-column label="æä½" width="150" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="text" size="small" @click="viewDetail(scope.row)">æ¥ç</el-button> |
| | | <el-button type="text" size="small" @click="editItem(scope.row)">ç¼è¾</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <div class="pagination-container"> |
| | | <el-pagination |
| | | v-model:current-page="pagination.currentPage" |
| | | v-model:page-size="pagination.pageSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import * as echarts from 'echarts' |
| | | import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const filterForm = reactive({ |
| | | dateRange: [], |
| | | reportType: 'sample' |
| | | }) |
| | | |
| | | const statistics = reactive({ |
| | | totalSamples: 1250, |
| | | activeEquipment: 45, |
| | | completedInspections: 890, |
| | | totalUsage: 2340 |
| | | }) |
| | | |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 20, |
| | | total: 0 |
| | | }) |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const sampleChartRef = ref(null) |
| | | const equipmentChartRef = ref(null) |
| | | const inspectionChartRef = ref(null) |
| | | const usageChartRef = ref(null) |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | let sampleChart = null |
| | | let equipmentChart = null |
| | | let inspectionChart = null |
| | | let usageChart = null |
| | | |
| | | // åå§åæ°æ® |
| | | const initData = () => { |
| | | // 模æè¡¨æ ¼æ°æ® |
| | | tableData.value = [ |
| | | { |
| | | id: 'SP001', |
| | | name: 'æ ·åA-001', |
| | | type: 'é屿æ', |
| | | status: 'æ£æµä¸', |
| | | progress: 75, |
| | | createTime: '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: 'æ£æµè®¾å¤A', |
| | | type: 'å
谱仪', |
| | | status: '使ç¨ä¸', |
| | | progress: 60, |
| | | createTime: '2025-01-05 14:20:00', |
| | | updateTime: '2025-01-20 11:30:00' |
| | | }, |
| | | { |
| | | id: 'EQ002', |
| | | name: 'æ£æµè®¾å¤B', |
| | | 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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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: 'æ£æµè®¾å¤A', |
| | | type: 'å
谱仪', |
| | | status: '使ç¨ä¸', |
| | | progress: 60, |
| | | createTime: '2025-01-05 14:20:00', |
| | | updateTime: '2025-01-20 11:30:00' |
| | | }, |
| | | { |
| | | id: 'EQ002', |
| | | name: 'æ£æµè®¾å¤B', |
| | | 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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.name" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.region" placeholder="è¯·éæ©åºå" clearable> |
| | | <el-option label="åä¸åº" value="åä¸åº"></el-option> |
| | | <el-option label="åååº" value="åååº"></el-option> |
| | | <el-option label="åååº" value="åååº"></el-option> |
| | | <el-option label="西ååº" value="西ååº"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.level" placeholder="è¯·éæ©å®¢æ·ç级" clearable> |
| | | <el-option label="VIP客æ·" value="VIP客æ·"></el-option> |
| | | <el-option label="éè¦å®¢æ·" value="éè¦å®¢æ·"></el-option> |
| | | <el-option label="æ®é客æ·" value="æ®é客æ·"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | | æ°å¢å®¢æ· |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 客æ·å表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="name" label="客æ·åç§°" width="150" /> |
| | | <el-table-column prop="contactPerson" label="è系人" width="100" /> |
| | | <el-table-column prop="phone" label="èç³»çµè¯" width="140" /> |
| | | <el-table-column prop="email" label="é®ç®±" /> |
| | | <el-table-column prop="region" label="åºå" width="100" /> |
| | | <el-table-column prop="level" label="客æ·ç级" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getLevelType(scope.row.level)"> |
| | | {{ scope.row.level }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="salesperson" label="è´è´£ä¸å¡å" width="120" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="80"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="primary" @click="handleAllocation(scope.row)">åé
</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°" prop="name"> |
| | | <el-input v-model="form.name" placeholder="请è¾å
¥å®¢æ·åç§°"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è系人" prop="contactPerson"> |
| | | <el-input v-model="form.contactPerson" placeholder="请è¾å
¥è系人"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯" prop="phone"> |
| | | <el-input v-model="form.phone" placeholder="请è¾å
¥èç³»çµè¯"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é®ç®±" prop="email"> |
| | | <el-input v-model="form.email" placeholder="请è¾å
¥é®ç®±"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºå" prop="region"> |
| | | <el-select v-model="form.region" placeholder="è¯·éæ©åºå" style="width: 100%"> |
| | | <el-option label="åä¸åº" value="åä¸åº"></el-option> |
| | | <el-option label="åååº" value="åååº"></el-option> |
| | | <el-option label="åååº" value="åååº"></el-option> |
| | | <el-option label="西ååº" value="西ååº"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·ç级" prop="level"> |
| | | <el-select v-model="form.level" placeholder="è¯·éæ©å®¢æ·ç级" style="width: 100%"> |
| | | <el-option label="VIP客æ·" value="VIP客æ·"></el-option> |
| | | <el-option label="éè¦å®¢æ·" value="éè¦å®¢æ·"></el-option> |
| | | <el-option label="æ®é客æ·" value="æ®é客æ·"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è´è´£ä¸å¡å" prop="salesperson"> |
| | | <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%"> |
| | | <el-option label="éå¿å¼º" value="éå¿å¼º"></el-option> |
| | | <el-option label="åé
å©·" value="åé
å©·"></el-option> |
| | | <el-option label="ç建å½" value="ç建å½"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="æ´»è·" value="æ´»è·"></el-option> |
| | | <el-option label="æ½å¨" value="æ½å¨"></el-option> |
| | | <el-option label="æµå¤±" value="æµå¤±"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 客æ·åé
å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="allocationDialogVisible" title="客æ·åé
" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <span>{{ currentCustomer.name }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="å½åä¸å¡å"> |
| | | <span>{{ currentCustomer.salesperson }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="éæ°åé
"> |
| | | <el-select v-model="newSalesperson" placeholder="è¯·éæ©æ°ä¸å¡å" style="width: 100%"> |
| | | <el-option label="éå¿å¼º" value="éå¿å¼º"></el-option> |
| | | <el-option label="åé
å©·" value="åé
å©·"></el-option> |
| | | <el-option label="ç建å½" value="ç建å½"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="åé
åå "> |
| | | <el-input v-model="allocationReason" type="textarea" rows="3" placeholder="请è¾å
¥åé
åå "></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="allocationDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="saveAllocation">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | name: '', |
| | | region: '', |
| | | level: '' |
| | | }) |
| | | |
| | | const customerList = ref([ |
| | | { |
| | | id: 1, |
| | | name: '䏿µ·ç§ææéå
¬å¸', |
| | | contactPerson: 'éå¿å¼º', |
| | | phone: '021-12345678', |
| | | email: 'zhang@shanghai-tech.com', |
| | | region: 'åä¸åº', |
| | | level: 'VIP客æ·', |
| | | salesperson: 'éå¿å¼º', |
| | | status: 'æ´»è·' |
| | | }, |
| | | { |
| | | id: 2, |
| | | name: 'æ·±å³çµåæéå
¬å¸', |
| | | contactPerson: 'åé
å©·', |
| | | phone: '0755-87654321', |
| | | email: 'li@shenzhen-elec.com', |
| | | region: 'åååº', |
| | | level: 'éè¦å®¢æ·', |
| | | salesperson: 'åé
å©·', |
| | | status: 'æ´»è·' |
| | | }, |
| | | { |
| | | id: 3, |
| | | name: 'å京贸æå
¬å¸', |
| | | contactPerson: 'ç建å½', |
| | | phone: '010-11223344', |
| | | email: 'wang@beijing-trade.com', |
| | | region: 'åååº', |
| | | level: 'æ®é客æ·', |
| | | salesperson: 'ç建å½', |
| | | status: 'æ½å¨' |
| | | } |
| | | ]) |
| | | |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢å®¢æ·') |
| | | const form = reactive({ |
| | | name: '', |
| | | contactPerson: '', |
| | | phone: '', |
| | | email: '', |
| | | region: '', |
| | | level: '', |
| | | salesperson: '', |
| | | status: 'æ´»è·' |
| | | }) |
| | | |
| | | const rules = { |
| | | name: [{ required: true, message: '请è¾å
¥å®¢æ·åç§°', trigger: 'blur' }], |
| | | contactPerson: [{ required: true, message: '请è¾å
¥è系人', trigger: 'blur' }], |
| | | phone: [{ required: true, message: '请è¾å
¥èç³»çµè¯', trigger: 'blur' }], |
| | | email: [{ required: true, message: '请è¾å
¥é®ç®±', trigger: 'blur' }], |
| | | region: [{ required: true, message: 'è¯·éæ©åºå', trigger: 'change' }], |
| | | level: [{ required: true, message: 'è¯·éæ©å®¢æ·ç级', trigger: 'change' }], |
| | | salesperson: [{ required: true, message: 'è¯·éæ©ä¸å¡å', trigger: 'change' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const allocationDialogVisible = ref(false) |
| | | const currentCustomer = ref({}) |
| | | const newSalesperson = ref('') |
| | | const allocationReason = ref('') |
| | | const formRef = ref() |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredList = computed(() => { |
| | | let list = customerList.value |
| | | if (searchForm.name) { |
| | | list = list.filter(item => item.name.includes(searchForm.name)) |
| | | } |
| | | if (searchForm.region) { |
| | | list = list.filter(item => item.region === searchForm.region) |
| | | } |
| | | if (searchForm.level) { |
| | | list = list.filter(item => item.level === searchForm.level) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | | const getLevelType = (level) => { |
| | | const levelMap = { |
| | | 'VIP客æ·': 'danger', |
| | | 'éè¦å®¢æ·': 'warning', |
| | | 'æ®é客æ·': 'info' |
| | | } |
| | | return levelMap[level] || 'info' |
| | | } |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'æ´»è·': 'success', |
| | | 'æ½å¨': 'warning', |
| | | 'æµå¤±': 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | // æç´¢é»è¾å·²å¨computedä¸å¤ç |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.name = '' |
| | | searchForm.region = '' |
| | | searchForm.level = '' |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å¢å®¢æ·' |
| | | isEdit.value = false |
| | | form.name = '' |
| | | form.contactPerson = '' |
| | | form.phone = '' |
| | | form.email = '' |
| | | form.region = '' |
| | | form.level = '' |
| | | form.salesperson = '' |
| | | form.status = 'æ´»è·' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ç¼è¾å®¢æ·' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥å®¢æ·åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = customerList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | customerList.value.splice(index, 1) |
| | | pagination.total-- |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleAllocation = (row) => { |
| | | currentCustomer.value = row |
| | | newSalesperson.value = '' |
| | | allocationReason.value = '' |
| | | allocationDialogVisible.value = true |
| | | } |
| | | |
| | | const saveAllocation = () => { |
| | | if (!newSalesperson.value) { |
| | | ElMessage.warning('è¯·éæ©æ°ä¸å¡å') |
| | | return |
| | | } |
| | | |
| | | const index = customerList.value.findIndex(item => item.id === currentCustomer.value.id) |
| | | if (index > -1) { |
| | | customerList.value[index].salesperson = newSalesperson.value |
| | | ElMessage.success('客æ·åé
æå') |
| | | allocationDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = customerList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | customerList.value[index] = { ...form, id: editId.value } |
| | | ElMessage.success('ç¼è¾æå') |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | const newId = Math.max(...customerList.value.map(item => item.id)) + 1 |
| | | customerList.value.push({ |
| | | ...form, |
| | | id: newId |
| | | }) |
| | | pagination.total++ |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val.page |
| | | pagination.pageSize = val.limit |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.orderNo" |
| | | placeholder="请è¾å
¥è®¢åå·" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.customer" placeholder="è¯·éæ©å®¢æ·" clearable> |
| | | <el-option label="䏿µ·ç§ææéå
¬å¸" value="䏿µ·ç§ææéå
¬å¸"></el-option> |
| | | <el-option label="æ·±å³çµåæéå
¬å¸" value="æ·±å³çµåæéå
¬å¸"></el-option> |
| | | <el-option label="å京贸æå
¬å¸" value="å京贸æå
¬å¸"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©è®¢åç¶æ" clearable> |
| | | <el-option label="å¾
å®¡æ ¸" value="å¾
å®¡æ ¸"></el-option> |
| | | <el-option label="å·²å®¡æ ¸" value="å·²å®¡æ ¸"></el-option> |
| | | <el-option label="å·²åè´§" value="å·²åè´§"></el-option> |
| | | <el-option label="已宿" value="已宿"></el-option> |
| | | <el-option label="已忶" value="已忶"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | | æ°å¢è®¢å |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 订åå表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="orderNo" label="订åå·" width="150" /> |
| | | <el-table-column prop="customer" label="客æ·åç§°" /> |
| | | <el-table-column prop="salesperson" label="ä¸å¡å" width="100" /> |
| | | <el-table-column prop="orderDate" label="ä¸åæ¥æ" width="120" /> |
| | | <el-table-column prop="amount" label="订åéé¢" width="120"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.amount.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="paymentMethod" label="仿¬¾æ¹å¼" width="120" /> |
| | | <el-table-column prop="status" label="订åç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="250" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleView(scope.row)">æ¥ç</el-button> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)" v-if="scope.row.status === 'å¾
å®¡æ ¸'">ç¼è¾</el-button> |
| | | <el-button link type="primary" @click="handleReview(scope.row)" v-if="scope.row.status === 'å¾
å®¡æ ¸'">å®¡æ ¸</el-button> |
| | | <el-button link type="primary" @click="handleTransfer(scope.row)" v-if="scope.row.status === 'å·²å®¡æ ¸'">转å</el-button> |
| | | <el-button link type="danger" @click="handleCancel(scope.row)" v-if="scope.row.status === 'å¾
å®¡æ ¸'">åæ¶</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°" prop="customer"> |
| | | <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%"> |
| | | <el-option label="䏿µ·ç§ææéå
¬å¸" value="䏿µ·ç§ææéå
¬å¸"></el-option> |
| | | <el-option label="æ·±å³çµåæéå
¬å¸" value="æ·±å³çµåæéå
¬å¸"></el-option> |
| | | <el-option label="å京贸æå
¬å¸" value="å京贸æå
¬å¸"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸å¡å" prop="salesperson"> |
| | | <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%"> |
| | | <el-option label="éå¿å¼º" value="éå¿å¼º"></el-option> |
| | | <el-option label="åé
å©·" value="åé
å©·"></el-option> |
| | | <el-option label="ç建å½" value="ç建å½"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è®¢åæ¥æ" prop="orderDate"> |
| | | <el-date-picker |
| | | v-model="form.orderDate" |
| | | type="date" |
| | | placeholder="éæ©è®¢åæ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="订åéé¢" prop="amount"> |
| | | <el-input-number v-model="form.amount" :precision="2" :min="0" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="form.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%"> |
| | | <el-option label="å
¨æ¬¾å°ä»" value="å
¨æ¬¾å°ä»"></el-option> |
| | | <el-option label="åæä»æ¬¾" value="åæä»æ¬¾"></el-option> |
| | | <el-option label="æç»" value="æç»"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="订åç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="å¾
å®¡æ ¸" value="å¾
å®¡æ ¸"></el-option> |
| | | <el-option label="å·²å®¡æ ¸" value="å·²å®¡æ ¸"></el-option> |
| | | <el-option label="å·²åè´§" value="å·²åè´§"></el-option> |
| | | <el-option label="已宿" value="已宿"></el-option> |
| | | <el-option label="已忶" value="已忶"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input type="textarea" v-model="form.remark" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" rows="3"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 订åå®¡æ ¸å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="reviewDialogVisible" title="订åå®¡æ ¸" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="订åå·"> |
| | | <span>{{ currentOrder.orderNo }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <span>{{ currentOrder.customer }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="订åéé¢"> |
| | | <span>Â¥{{ currentOrder.amount.toFixed(2) }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="å®¡æ ¸ç»æ" prop="reviewResult"> |
| | | <el-radio-group v-model="reviewResult"> |
| | | <el-radio label="éè¿">éè¿</el-radio> |
| | | <el-radio label="æç»">æç»</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="å®¡æ ¸æè§" prop="reviewComment"> |
| | | <el-input type="textarea" v-model="reviewComment" rows="3" placeholder="请è¾å
¥å®¡æ ¸æè§"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="reviewDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="saveReview">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 订å转åå¯¹è¯æ¡ --> |
| | | <el-dialog v-model="transferDialogVisible" title="订å转å" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="订åå·"> |
| | | <span>{{ currentOrder.orderNo }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="å½åä¸å¡å"> |
| | | <span>{{ currentOrder.salesperson }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="转åç»" prop="newSalesperson"> |
| | | <el-select v-model="newSalesperson" placeholder="è¯·éæ©æ°ä¸å¡å" style="width: 100%"> |
| | | <el-option label="éå¿å¼º" value="éå¿å¼º"></el-option> |
| | | <el-option label="åé
å©·" value="åé
å©·"></el-option> |
| | | <el-option label="ç建å½" value="ç建å½"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="转ååå " prop="transferReason"> |
| | | <el-input type="textarea" v-model="transferReason" rows="3" placeholder="请è¾å
¥è½¬ååå "></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="transferDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="saveTransfer">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | orderNo: '', |
| | | customer: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const orderList = ref([ |
| | | { |
| | | id: 1, |
| | | orderNo: 'ORD202312001', |
| | | customer: '䏿µ·ç§ææéå
¬å¸', |
| | | salesperson: 'éå¿å¼º', |
| | | orderDate: '2023-12-01', |
| | | amount: 50000.00, |
| | | paymentMethod: 'å
¨æ¬¾å°ä»', |
| | | status: 'å¾
å®¡æ ¸', |
| | | remark: 'éè¦å®¢æ·è®¢å' |
| | | }, |
| | | { |
| | | id: 2, |
| | | orderNo: 'ORD202312002', |
| | | customer: 'æ·±å³çµåæéå
¬å¸', |
| | | salesperson: 'åé
å©·', |
| | | orderDate: '2023-12-02', |
| | | amount: 35000.00, |
| | | paymentMethod: 'åæä»æ¬¾', |
| | | status: 'å·²å®¡æ ¸', |
| | | remark: '常è§è®¢å' |
| | | }, |
| | | { |
| | | id: 3, |
| | | orderNo: 'ORD202312003', |
| | | customer: 'å京贸æå
¬å¸', |
| | | salesperson: 'ç建å½', |
| | | orderDate: '2023-12-03', |
| | | amount: 28000.00, |
| | | paymentMethod: 'æç»', |
| | | status: 'å·²åè´§', |
| | | remark: 'æ°å®¢æ·è®¢å' |
| | | } |
| | | ]) |
| | | |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢è®¢å') |
| | | const form = reactive({ |
| | | customer: '', |
| | | salesperson: '', |
| | | orderDate: '', |
| | | amount: 0, |
| | | paymentMethod: '', |
| | | status: 'å¾
å®¡æ ¸', |
| | | remark: '' |
| | | }) |
| | | |
| | | const rules = { |
| | | customer: [{ required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' }], |
| | | salesperson: [{ required: true, message: 'è¯·éæ©ä¸å¡å', trigger: 'change' }], |
| | | orderDate: [{ required: true, message: 'è¯·éæ©è®¢åæ¥æ', trigger: 'change' }], |
| | | amount: [{ required: true, message: '请è¾å
¥è®¢åéé¢', trigger: 'blur' }], |
| | | paymentMethod: [{ required: true, message: 'è¯·éæ©ä»æ¬¾æ¹å¼', trigger: 'change' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const reviewDialogVisible = ref(false) |
| | | const transferDialogVisible = ref(false) |
| | | const currentOrder = ref({}) |
| | | const reviewResult = ref('') |
| | | const reviewComment = ref('') |
| | | const newSalesperson = ref('') |
| | | const transferReason = ref('') |
| | | const formRef = ref() |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredList = computed(() => { |
| | | let list = orderList.value |
| | | if (searchForm.orderNo) { |
| | | list = list.filter(item => item.orderNo.includes(searchForm.orderNo)) |
| | | } |
| | | if (searchForm.customer) { |
| | | list = list.filter(item => item.customer === searchForm.customer) |
| | | } |
| | | if (searchForm.status) { |
| | | list = list.filter(item => item.status === searchForm.status) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 'å¾
å®¡æ ¸': 'warning', |
| | | 'å·²å®¡æ ¸': 'primary', |
| | | 'å·²åè´§': 'success', |
| | | '已宿': 'success', |
| | | '已忶': 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | // æç´¢é»è¾å·²å¨computedä¸å¤ç |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.orderNo = '' |
| | | searchForm.customer = '' |
| | | searchForm.status = '' |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å¢è®¢å' |
| | | isEdit.value = false |
| | | form.customer = '' |
| | | form.salesperson = '' |
| | | form.orderDate = '' |
| | | form.amount = 0 |
| | | form.paymentMethod = '' |
| | | form.status = 'å¾
å®¡æ ¸' |
| | | form.remark = '' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleView = (row) => { |
| | | // æ¥ç订å详æ
|
| | | ElMessage.info('æ¥ç订å详æ
åè½å¾
å®ç°') |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ç¼è¾è®¢å' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleReview = (row) => { |
| | | currentOrder.value = row |
| | | reviewResult.value = '' |
| | | reviewComment.value = '' |
| | | reviewDialogVisible.value = true |
| | | } |
| | | |
| | | const handleTransfer = (row) => { |
| | | currentOrder.value = row |
| | | newSalesperson.value = '' |
| | | transferReason.value = '' |
| | | transferDialogVisible.value = true |
| | | } |
| | | |
| | | const handleCancel = (row) => { |
| | | ElMessageBox.confirm('ç¡®è®¤åæ¶è¯¥è®¢ååï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = orderList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | orderList.value[index].status = '已忶' |
| | | ElMessage.success('订å已忶') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥è®¢ååï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = orderList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | orderList.value.splice(index, 1) |
| | | pagination.total-- |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const saveReview = () => { |
| | | if (!reviewResult.value) { |
| | | ElMessage.warning('è¯·éæ©å®¡æ ¸ç»æ') |
| | | return |
| | | } |
| | | |
| | | const index = orderList.value.findIndex(item => item.id === currentOrder.value.id) |
| | | if (index > -1) { |
| | | if (reviewResult.value === 'éè¿') { |
| | | orderList.value[index].status = 'å·²å®¡æ ¸' |
| | | ElMessage.success('订åå®¡æ ¸éè¿') |
| | | } else { |
| | | orderList.value[index].status = '已忶' |
| | | ElMessage.success('订åå®¡æ ¸æç»') |
| | | } |
| | | reviewDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const saveTransfer = () => { |
| | | if (!newSalesperson.value) { |
| | | ElMessage.warning('è¯·éæ©æ°ä¸å¡å') |
| | | return |
| | | } |
| | | |
| | | const index = orderList.value.findIndex(item => item.id === currentOrder.value.id) |
| | | if (index > -1) { |
| | | orderList.value[index].salesperson = newSalesperson.value |
| | | ElMessage.success('订å转åæå') |
| | | transferDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = orderList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | orderList.value[index] = { ...form, id: editId.value } |
| | | ElMessage.success('ç¼è¾æå') |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | const newId = Math.max(...orderList.value.map(item => item.id)) + 1 |
| | | const orderNo = `ORD${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}${String(newId).padStart(3, '0')}` |
| | | orderList.value.push({ |
| | | ...form, |
| | | id: newId, |
| | | orderNo: orderNo |
| | | }) |
| | | pagination.total++ |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val.page |
| | | pagination.pageSize = val.limit |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.orderNo" |
| | | placeholder="请è¾å
¥è®¢åå·" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.paymentStatus" placeholder="è¯·éæ©ä»æ¬¾ç¶æ" clearable> |
| | | <el-option label="æªä»æ¬¾" value="æªä»æ¬¾"></el-option> |
| | | <el-option label="已仿¬¾" value="已仿¬¾"></el-option> |
| | | <el-option label="é¨å仿¬¾" value="é¨å仿¬¾"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.shippingStatus" placeholder="è¯·éæ©åè´§ç¶æ" clearable> |
| | | <el-option label="å¾
åè´§" value="å¾
åè´§"></el-option> |
| | | <el-option label="å·²åè´§" value="å·²åè´§"></el-option> |
| | | <el-option label="å·²ç¾æ¶" value="å·²ç¾æ¶"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | | æ°å¢è®°å½ |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- æ¯ä»ä¸åè´§å表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="orderNo" label="订åå·" /> |
| | | <el-table-column prop="customer" label="客æ·åç§°" /> |
| | | <el-table-column prop="orderAmount" label="订åéé¢" width="120"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.orderAmount.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="paymentMethod" label="仿¬¾æ¹å¼" width="120" /> |
| | | <el-table-column prop="paymentStatus" label="仿¬¾ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getPaymentStatusType(scope.row.paymentStatus)"> |
| | | {{ scope.row.paymentStatus }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="shippingStatus" label="åè´§ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getShippingStatusType(scope.row.shippingStatus)"> |
| | | {{ scope.row.shippingStatus }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="shippingDate" label="åè´§æ¥æ" width="120" /> |
| | | <el-table-column label="æä½" width="250" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleView(scope.row)">æ¥ç</el-button> |
| | | <el-button link type="primary" @click="handlePayment(scope.row)" v-if="scope.row.paymentStatus !== '已仿¬¾'">仿¬¾</el-button> |
| | | <el-button link type="primary" @click="handleShipping(scope.row)" v-if="scope.row.paymentStatus === '已仿¬¾' && scope.row.shippingStatus === 'å¾
åè´§'">åè´§</el-button> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="订åå·" prop="orderNo"> |
| | | <el-input v-model="form.orderNo" placeholder="请è¾å
¥è®¢åå·"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°" prop="customer"> |
| | | <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%"> |
| | | <el-option label="䏿µ·ç§ææéå
¬å¸" value="䏿µ·ç§ææéå
¬å¸"></el-option> |
| | | <el-option label="æ·±å³çµåæéå
¬å¸" value="æ·±å³çµåæéå
¬å¸"></el-option> |
| | | <el-option label="å京贸æå
¬å¸" value="å京贸æå
¬å¸"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="订åéé¢" prop="orderAmount"> |
| | | <el-input-number v-model="form.orderAmount" :precision="2" :min="0" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="form.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%"> |
| | | <el-option label="å
¨æ¬¾å°ä»" value="å
¨æ¬¾å°ä»"></el-option> |
| | | <el-option label="åæä»æ¬¾" value="åæä»æ¬¾"></el-option> |
| | | <el-option label="æç»" value="æç»"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾ç¶æ" prop="paymentStatus"> |
| | | <el-select v-model="form.paymentStatus" placeholder="è¯·éæ©ä»æ¬¾ç¶æ" style="width: 100%"> |
| | | <el-option label="æªä»æ¬¾" value="æªä»æ¬¾"></el-option> |
| | | <el-option label="已仿¬¾" value="已仿¬¾"></el-option> |
| | | <el-option label="é¨å仿¬¾" value="é¨å仿¬¾"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åè´§ç¶æ" prop="shippingStatus"> |
| | | <el-select v-model="form.shippingStatus" placeholder="è¯·éæ©åè´§ç¶æ" style="width: 100%"> |
| | | <el-option label="å¾
åè´§" value="å¾
åè´§"></el-option> |
| | | <el-option label="å·²åè´§" value="å·²åè´§"></el-option> |
| | | <el-option label="å·²ç¾æ¶" value="å·²ç¾æ¶"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åè´§æ¥æ" prop="shippingDate"> |
| | | <el-date-picker |
| | | v-model="form.shippingDate" |
| | | type="date" |
| | | placeholder="éæ©åè´§æ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç©æµåå·" prop="trackingNo"> |
| | | <el-input v-model="form.trackingNo" placeholder="请è¾å
¥ç©æµåå·"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input type="textarea" v-model="form.remark" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" rows="3"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 仿¬¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="paymentDialogVisible" title="订å仿¬¾" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="订åå·"> |
| | | <span>{{ currentRecord.orderNo }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <span>{{ currentRecord.customer }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="订åéé¢"> |
| | | <span>Â¥{{ currentRecord.orderAmount.toFixed(2) }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾éé¢" prop="paymentAmount"> |
| | | <el-input-number v-model="paymentAmount" :precision="2" :min="0" :max="currentRecord.orderAmount" style="width: 100%"></el-input-number> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%"> |
| | | <el-option label="ç°é" value="ç°é"></el-option> |
| | | <el-option label="é¶è¡è½¬è´¦" value="é¶è¡è½¬è´¦"></el-option> |
| | | <el-option label="æ¯ä»å®" value="æ¯ä»å®"></el-option> |
| | | <el-option label="微信æ¯ä»" value="微信æ¯ä»"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾å¤æ³¨" prop="paymentRemark"> |
| | | <el-input type="textarea" v-model="paymentRemark" rows="3" placeholder="请è¾å
¥ä»æ¬¾å¤æ³¨"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="paymentDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="savePayment">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- åè´§å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="shippingDialogVisible" title="订ååè´§" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="订åå·"> |
| | | <span>{{ currentRecord.orderNo }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <span>{{ currentRecord.customer }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="åè´§æ¥æ" prop="shippingDate"> |
| | | <el-date-picker |
| | | v-model="shippingDate" |
| | | type="date" |
| | | placeholder="éæ©åè´§æ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ç©æµå
¬å¸" prop="logisticsCompany"> |
| | | <el-select v-model="logisticsCompany" placeholder="è¯·éæ©ç©æµå
¬å¸" style="width: 100%"> |
| | | <el-option label="顺丰éè¿" value="顺丰éè¿"></el-option> |
| | | <el-option label="åééé" value="åééé"></el-option> |
| | | <el-option label="ä¸éå¿«é" value="ä¸éå¿«é"></el-option> |
| | | <el-option label="ç³éå¿«é" value="ç³éå¿«é"></el-option> |
| | | <el-option label="éµè¾¾éé" value="éµè¾¾éé"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç©æµåå·" prop="trackingNo"> |
| | | <el-input v-model="trackingNo" placeholder="请è¾å
¥ç©æµåå·"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="åè´§å¤æ³¨" prop="shippingRemark"> |
| | | <el-input type="textarea" v-model="shippingRemark" rows="3" placeholder="请è¾å
¥åè´§å¤æ³¨"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="shippingDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="saveShipping">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | orderNo: '', |
| | | paymentStatus: '', |
| | | shippingStatus: '' |
| | | }) |
| | | |
| | | const recordList = ref([ |
| | | { |
| | | id: 1, |
| | | orderNo: 'ORD202312001', |
| | | customer: '䏿µ·ç§ææéå
¬å¸', |
| | | orderAmount: 50000.00, |
| | | paymentMethod: 'å
¨æ¬¾å°ä»', |
| | | paymentStatus: '已仿¬¾', |
| | | shippingStatus: 'å·²åè´§', |
| | | shippingDate: '2023-12-05', |
| | | trackingNo: 'SF1234567890', |
| | | remark: 'éè¦å®¢æ·è®¢å' |
| | | }, |
| | | { |
| | | id: 2, |
| | | orderNo: 'ORD202312002', |
| | | customer: 'æ·±å³çµåæéå
¬å¸', |
| | | orderAmount: 35000.00, |
| | | paymentMethod: 'åæä»æ¬¾', |
| | | paymentStatus: 'é¨å仿¬¾', |
| | | shippingStatus: 'å¾
åè´§', |
| | | shippingDate: '', |
| | | trackingNo: '', |
| | | remark: '常è§è®¢å' |
| | | }, |
| | | { |
| | | id: 3, |
| | | orderNo: 'ORD202312003', |
| | | customer: 'å京贸æå
¬å¸', |
| | | orderAmount: 28000.00, |
| | | paymentMethod: 'æç»', |
| | | paymentStatus: 'æªä»æ¬¾', |
| | | shippingStatus: 'å¾
åè´§', |
| | | shippingDate: '', |
| | | trackingNo: '', |
| | | remark: 'æ°å®¢æ·è®¢å' |
| | | } |
| | | ]) |
| | | |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 10 |
| | | }) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('æ°å¢è®°å½') |
| | | const form = reactive({ |
| | | orderNo: '', |
| | | customer: '', |
| | | orderAmount: 0, |
| | | paymentMethod: '', |
| | | paymentStatus: 'æªä»æ¬¾', |
| | | shippingStatus: 'å¾
åè´§', |
| | | shippingDate: '', |
| | | trackingNo: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const rules = { |
| | | orderNo: [{ required: true, message: '请è¾å
¥è®¢åå·', trigger: 'blur' }], |
| | | customer: [{ required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' }], |
| | | orderAmount: [{ required: true, message: '请è¾å
¥è®¢åéé¢', trigger: 'blur' }], |
| | | paymentMethod: [{ required: true, message: 'è¯·éæ©ä»æ¬¾æ¹å¼', trigger: 'change' }], |
| | | paymentStatus: [{ required: true, message: 'è¯·éæ©ä»æ¬¾ç¶æ', trigger: 'change' }], |
| | | shippingStatus: [{ required: true, message: 'è¯·éæ©åè´§ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const paymentDialogVisible = ref(false) |
| | | const shippingDialogVisible = ref(false) |
| | | const currentRecord = ref({}) |
| | | const paymentAmount = ref(0) |
| | | const paymentMethod = ref('') |
| | | const paymentRemark = ref('') |
| | | const shippingDate = ref('') |
| | | const logisticsCompany = ref('') |
| | | const trackingNo = ref('') |
| | | const shippingRemark = ref('') |
| | | const formRef = ref() |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredList = computed(() => { |
| | | let list = recordList.value |
| | | if (searchForm.orderNo) { |
| | | list = list.filter(item => item.orderNo.includes(searchForm.orderNo)) |
| | | } |
| | | if (searchForm.paymentStatus) { |
| | | list = list.filter(item => item.paymentStatus === searchForm.paymentStatus) |
| | | } |
| | | if (searchForm.shippingStatus) { |
| | | list = list.filter(item => item.shippingStatus === searchForm.shippingStatus) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | | const getPaymentStatusType = (status) => { |
| | | const statusMap = { |
| | | 'æªä»æ¬¾': 'danger', |
| | | '已仿¬¾': 'success', |
| | | 'é¨å仿¬¾': 'warning' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const getShippingStatusType = (status) => { |
| | | const statusMap = { |
| | | 'å¾
åè´§': 'warning', |
| | | 'å·²åè´§': 'primary', |
| | | 'å·²ç¾æ¶': 'success' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const handleSearch = () => { |
| | | // æç´¢é»è¾å·²å¨computedä¸å¤ç |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.orderNo = '' |
| | | searchForm.paymentStatus = '' |
| | | searchForm.shippingStatus = '' |
| | | } |
| | | |
| | | const handleAdd = () => { |
| | | dialogTitle.value = 'æ°å¢è®°å½' |
| | | isEdit.value = false |
| | | form.orderNo = '' |
| | | form.customer = '' |
| | | form.orderAmount = 0 |
| | | form.paymentMethod = '' |
| | | form.paymentStatus = 'æªä»æ¬¾' |
| | | form.shippingStatus = 'å¾
åè´§' |
| | | form.shippingDate = '' |
| | | form.trackingNo = '' |
| | | form.remark = '' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleView = (row) => { |
| | | // æ¥çè®°å½è¯¦æ
|
| | | ElMessage.info('æ¥çè®°å½è¯¦æ
åè½å¾
å®ç°') |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = 'ç¼è¾è®°å½' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | Object.assign(form, row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handlePayment = (row) => { |
| | | currentRecord.value = row |
| | | paymentAmount.value = row.orderAmount |
| | | paymentMethod.value = '' |
| | | paymentRemark.value = '' |
| | | paymentDialogVisible.value = true |
| | | } |
| | | |
| | | const handleShipping = (row) => { |
| | | currentRecord.value = row |
| | | shippingDate.value = '' |
| | | logisticsCompany.value = '' |
| | | trackingNo.value = '' |
| | | shippingRemark.value = '' |
| | | shippingDialogVisible.value = true |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥è®°å½åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = recordList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | recordList.value.splice(index, 1) |
| | | pagination.total-- |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const savePayment = () => { |
| | | if (!paymentMethod.value) { |
| | | ElMessage.warning('è¯·éæ©ä»æ¬¾æ¹å¼') |
| | | return |
| | | } |
| | | |
| | | const index = recordList.value.findIndex(item => item.id === currentRecord.value.id) |
| | | if (index > -1) { |
| | | if (paymentAmount.value >= currentRecord.value.orderAmount) { |
| | | recordList.value[index].paymentStatus = '已仿¬¾' |
| | | } else if (paymentAmount.value > 0) { |
| | | recordList.value[index].paymentStatus = 'é¨å仿¬¾' |
| | | } |
| | | ElMessage.success('仿¬¾è®°å½å·²ä¿å') |
| | | paymentDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const saveShipping = () => { |
| | | if (!shippingDate.value || !logisticsCompany.value || !trackingNo.value) { |
| | | ElMessage.warning('请填å宿´çåè´§ä¿¡æ¯') |
| | | return |
| | | } |
| | | |
| | | const index = recordList.value.findIndex(item => item.id === currentRecord.value.id) |
| | | if (index > -1) { |
| | | recordList.value[index].shippingStatus = 'å·²åè´§' |
| | | recordList.value[index].shippingDate = shippingDate.value |
| | | recordList.value[index].trackingNo = trackingNo.value |
| | | ElMessage.success('åè´§ä¿¡æ¯å·²ä¿å') |
| | | shippingDialogVisible.value = false |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = recordList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | recordList.value[index] = { ...form, id: editId.value } |
| | | ElMessage.success('ç¼è¾æå') |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | const newId = Math.max(...recordList.value.map(item => item.id)) + 1 |
| | | recordList.value.push({ |
| | | ...form, |
| | | id: newId |
| | | }) |
| | | pagination.total++ |
| | | ElMessage.success('æ°å¢æå') |
| | | } |
| | | dialogVisible.value = false |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val.page |
| | | pagination.pageSize = val.limit |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- æç´¢åºå --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="6"> |
| | | <el-input |
| | | v-model="searchForm.name" |
| | | placeholder="请è¾å
¥ä¸å¡åå§å" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.department" placeholder="è¯·éæ©é¨é¨" clearable> |
| | | <el-option label="éå®é¨" value="éå®é¨"></el-option> |
| | | <el-option label="å¸åºé¨" value="å¸åºé¨"></el-option> |
| | | <el-option label="客æé¨" value="客æé¨"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©ç¶æ" clearable> |
| | | <el-option label="å¨è" value="å¨è"></el-option> |
| | | <el-option label="离è" value="离è"></el-option> |
| | | <el-option label="è¯ç¨æ" value="è¯ç¨æ"></el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | <el-button type="primary" style="float: right;" @click="handleAdd">æ°å¢ä¸å¡å</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- ä¸å¡åå表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column prop="id" label="ID" width="80" align="center"/> |
| | | <el-table-column prop="name" label="å§å" width="120" /> |
| | | <el-table-column prop="phone" label="èç³»çµè¯" width="140" /> |
| | | <el-table-column prop="email" label="é®ç®±" width="200" /> |
| | | <el-table-column prop="department" label="é¨é¨" width="100" /> |
| | | <el-table-column prop="position" label="èä½" width="100" /> |
| | | <el-table-column prop="hireDate" label="å
¥èæ¥æ" width="120" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="80"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="permissions" label="æé"> |
| | | <template #default="scope"> |
| | | <el-tag v-for="perm in scope.row.permissions" :key="perm" size="small" style="margin-right: 5px;"> |
| | | {{ perm }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)">ç¼è¾</el-button> |
| | | <el-button link type="primary" @click="handlePermissions(scope.row)">æé</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å§å" prop="name"> |
| | | <el-input v-model="form.name" placeholder="请è¾å
¥å§å"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯" prop="phone"> |
| | | <el-input v-model="form.phone" placeholder="请è¾å
¥èç³»çµè¯"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é®ç®±" prop="email"> |
| | | <el-input v-model="form.email" placeholder="请è¾å
¥é®ç®±"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¨é¨" prop="department"> |
| | | <el-select v-model="form.department" placeholder="è¯·éæ©é¨é¨" style="width: 100%"> |
| | | <el-option label="éå®é¨" value="éå®é¨"></el-option> |
| | | <el-option label="å¸åºé¨" value="å¸åºé¨"></el-option> |
| | | <el-option label="客æé¨" value="客æé¨"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èä½" prop="position"> |
| | | <el-input v-model="form.position" placeholder="请è¾å
¥èä½"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¥èæ¥æ" prop="hireDate"> |
| | | <el-date-picker |
| | | v-model="form.hireDate" |
| | | type="date" |
| | | placeholder="éæ©å
¥èæ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="å¨è" value="å¨è"></el-option> |
| | | <el-option label="离è" value="离è"></el-option> |
| | | <el-option label="è¯ç¨æ" value="è¯ç¨æ"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æéè®¾ç½®å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="permissionDialogVisible" title="æé设置" width="500px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="ä¸å¡åå§å"> |
| | | <span>{{ currentSalesperson.name }}</span> |
| | | </el-form-item> |
| | | <el-form-item label="æé设置"> |
| | | <el-checkbox-group v-model="currentPermissions"> |
| | | <el-checkbox label="订å管ç">订å管ç</el-checkbox> |
| | | <el-checkbox label="客æ·ç®¡ç">客æ·ç®¡ç</el-checkbox> |
| | | <el-checkbox label="è´¢å¡ç®¡ç">è´¢å¡ç®¡ç</el-checkbox> |
| | | <el-checkbox label="å货管ç">å货管ç</el-checkbox> |
| | | <el-checkbox label="æ¥è¡¨æ¥ç">æ¥è¡¨æ¥ç</el-checkbox> |
| | | <el-checkbox label="ç³»ç»è®¾ç½®">ç³»ç»è®¾ç½®</el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="permissionDialogVisible = false">å æ¶</el-button> |
| | | <el-button type="primary" @click="savePermissions">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, nextTick } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Search } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const searchForm = reactive({ |
| | | name: '', |
| | | department: '', |
| | | status: '' |
| | | }) |
| | | |
| | | const salespersonList = ref([ |
| | | { |
| | | id: 1, |
| | | name: 'éå¿å¼º', |
| | | phone: '13800138001', |
| | | email: '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> |
| | |
| | | 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 {
|
| | | // é¨ç½²ç产ç¯å¢åå¼åç¯å¢ä¸çURLã
|