| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼
|
| | | VITE_APP_TITLE = ä¸å¼ºæå
´ç®¡çç³»ç»
|
| | |
|
| | | # å¼åç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'development'
|
| | |
|
| | | # è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼/å¼åç¯å¢
|
| | | # ä¸å¼ºæå
´ç®¡çç³»ç»/å¼åç¯å¢
|
| | | VITE_APP_BASE_API = '/dev-api'
|
| | |
| | | # 页颿 é¢
|
| | | VITE_APP_TITLE = è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼
|
| | | VITE_APP_TITLE = ä¸å¼ºæå
´ç®¡çç³»ç»
|
| | |
|
| | | # ç产ç¯å¢é
ç½®
|
| | | VITE_APP_ENV = 'production'
|
| | |
|
| | | # è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼/ç产ç¯å¢
|
| | | # ä¸å¼ºæå
´ç®¡çç³»ç»/ç产ç¯å¢
|
| | | VITE_APP_BASE_API = '/prod-api'
|
| | |
|
| | | # æ¯å¦å¨æå
æ¶å¼å¯åç¼©ï¼æ¯æ gzip å brotli
|
| | |
| | | # 页颿 é¢
|
| | | 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>è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼</title>
|
| | | <link rel="icon" href="/ZQHXico.ico">
|
| | | <title>ä¸å¼ºæå
´ç®¡çç³»ç»</title>
|
| | | <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
| | | <style>
|
| | | html,
|
| | |
| | | { |
| | | "name": "ruoyi", |
| | | "version": "3.8.9", |
| | | "description": "è¯å¯¼äºï¼ç®¡çä¿¡æ¯ç³»ç»ï¼", |
| | | "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: 'å 餿å' }); |
| | | } |
| | | |
| | | |
| | | |
| | |
| | | data: data
|
| | | })
|
| | | }
|
| | |
|
| | | export function tideLogin(data) {
|
| | | return request({
|
| | | url: '/tide/tideLogin',
|
| | | headers: {
|
| | | isToken: false,
|
| | | repeatSubmit: false
|
| | | },
|
| | | method: 'post',
|
| | | data: data
|
| | | })
|
| | | }
|
| | |
| | | <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/logo/logo.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://114.132.189.42:7004";
|
| | | app.config.globalProperties.javaApi = "http://114.132.189.42:8099";
|
| | | app.config.globalProperties.HaveJson = (val) => {
|
| | | return JSON.parse(JSON.stringify(val));
|
| | | };
|
| | |
| | | import router from "./router";
|
| | | import { ElMessage } from "element-plus";
|
| | | import NProgress from "nprogress";
|
| | | import "nprogress/nprogress.css";
|
| | | import { getToken } from "@/utils/auth";
|
| | | import { isHttp, isPathMatch } from "@/utils/validate";
|
| | | import { isRelogin } from "@/utils/request";
|
| | | import useUserStore from "@/store/modules/user";
|
| | | import useSettingsStore from "@/store/modules/settings";
|
| | | import usePermissionStore from "@/store/modules/permission";
|
| | | import router from './router'
|
| | | import { ElMessage } from 'element-plus'
|
| | | import NProgress from 'nprogress'
|
| | | import 'nprogress/nprogress.css'
|
| | | import { getToken } from '@/utils/auth'
|
| | | import { isHttp, isPathMatch } from '@/utils/validate'
|
| | | import { isRelogin } from '@/utils/request'
|
| | | import useUserStore from '@/store/modules/user'
|
| | | import useSettingsStore from '@/store/modules/settings'
|
| | | import usePermissionStore from '@/store/modules/permission'
|
| | |
|
| | | NProgress.configure({ showSpinner: false });
|
| | | NProgress.configure({ showSpinner: false })
|
| | |
|
| | | const whiteList = ["/login", "/register", "/device-info"];
|
| | | const whiteList = ['/login', '/register', '/callbacklccpn']
|
| | |
|
| | | const isWhiteList = (path) => {
|
| | | return whiteList.some((pattern) => isPathMatch(pattern, path));
|
| | | };
|
| | | return whiteList.some(pattern => isPathMatch(pattern, path))
|
| | | }
|
| | |
|
| | | router.beforeEach((to, from, next) => {
|
| | | NProgress.start();
|
| | | NProgress.start()
|
| | | if (getToken()) {
|
| | | to.meta.title && useSettingsStore().setTitle(to.meta.title);
|
| | | to.meta.title && useSettingsStore().setTitle(to.meta.title)
|
| | | /* has token*/
|
| | | if (to.path === "/login") {
|
| | | next({ path: "/" });
|
| | | NProgress.done();
|
| | | if (to.path === '/login') {
|
| | | next({ path: '/' })
|
| | | NProgress.done()
|
| | | } else if (isWhiteList(to.path)) {
|
| | | next();
|
| | | next()
|
| | | } else {
|
| | | if (useUserStore().roles.length === 0) {
|
| | | isRelogin.show = true;
|
| | | isRelogin.show = true
|
| | | // 夿å½åç¨æ·æ¯å¦å·²æåå®user_infoä¿¡æ¯
|
| | | useUserStore()
|
| | | .getInfo()
|
| | | .then(() => {
|
| | | isRelogin.show = false;
|
| | | usePermissionStore()
|
| | | .generateRoutes()
|
| | | .then((accessRoutes) => {
|
| | | // æ ¹æ®rolesæéçæå¯è®¿é®çè·¯ç±è¡¨
|
| | | accessRoutes.forEach((route) => {
|
| | | if (!isHttp(route.path)) {
|
| | | router.addRoute(route); // å¨ææ·»å å¯è®¿é®è·¯ç±è¡¨
|
| | | }
|
| | | });
|
| | | next({ ...to, replace: true }); // hackæ¹æ³ ç¡®ä¿addRoutes已宿
|
| | | });
|
| | | useUserStore().getInfo().then(() => {
|
| | | isRelogin.show = false
|
| | | usePermissionStore().generateRoutes().then(accessRoutes => {
|
| | | // æ ¹æ®rolesæéçæå¯è®¿é®çè·¯ç±è¡¨
|
| | | accessRoutes.forEach(route => {
|
| | | if (!isHttp(route.path)) {
|
| | | router.addRoute(route) // å¨ææ·»å å¯è®¿é®è·¯ç±è¡¨
|
| | | }
|
| | | })
|
| | | next({ ...to, replace: true }) // hackæ¹æ³ ç¡®ä¿addRoutes已宿
|
| | | })
|
| | | .catch((err) => {
|
| | | useUserStore()
|
| | | .logOut()
|
| | | .then(() => {
|
| | | ElMessage.error(err);
|
| | | next({ path: "/" });
|
| | | });
|
| | | });
|
| | | }).catch(err => {
|
| | | useUserStore().logOut().then(() => {
|
| | | ElMessage.error(err)
|
| | | next({ path: '/' })
|
| | | })
|
| | | })
|
| | | } else {
|
| | | next();
|
| | | next()
|
| | | }
|
| | | }
|
| | | } else {
|
| | | // 没ætoken
|
| | | if (isWhiteList(to.path)) {
|
| | | // å¨å
ç»å½ç½ååï¼ç´æ¥è¿å
¥
|
| | | next();
|
| | | next()
|
| | | } else {
|
| | | next(`/login?redirect=${to.fullPath}`); // å¦åå
¨é¨éå®åå°ç»å½é¡µ
|
| | | NProgress.done();
|
| | | next(`/login?redirect=${to.fullPath}`) // å¦åå
¨é¨éå®åå°ç»å½é¡µ
|
| | | NProgress.done()
|
| | | }
|
| | | }
|
| | | });
|
| | | })
|
| | |
|
| | | router.afterEach(() => {
|
| | | NProgress.done();
|
| | | });
|
| | | NProgress.done()
|
| | | })
|
| | |
| | | hidden: true |
| | | }, |
| | | { |
| | | path: "/callbacklccpn", |
| | | component: () => import("@/views/tideLogin.vue"), |
| | | hidden: true, |
| | | }, |
| | | { |
| | | path: '/register', |
| | | component: () => import('@/views/register'), |
| | | hidden: true |
| | |
| | | component: () => import('@/views/index'), |
| | | name: 'Index', |
| | | meta: { title: 'é¦é¡µ', icon: 'dashboard', affix: true } |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | path: '/main/MobileChat', |
| | | component: Layout, |
| | | redirect: '', |
| | | hidden: true, |
| | | children: [ |
| | | { |
| | | path: '', |
| | | component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'), |
| | | name: 'MobileChat', |
| | | meta: { title: 'AI对è¯', icon: 'dashboard', affix: true} |
| | | } |
| | | ] |
| | | }, |
| | |
| | | component: () => import('@/views/system/user/authRole'), |
| | | name: 'AuthRole', |
| | | meta: { title: 'åé
è§è²', activeMenu: '/system/user' } |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | path: '/main/MobileChat', |
| | | component: Layout, |
| | | redirect: '', |
| | | hidden: true, |
| | | permissions: ['MobileChat:edit'], |
| | | children: [ |
| | | { |
| | | path: '', |
| | | component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'), |
| | | name: 'MobileChat', |
| | | meta: { title: 'AI对è¯', activeMenu: '/chatHome/chatHomeIndex'} |
| | | } |
| | | ] |
| | | }, |
| | |
| | | import {login, logout, getInfo, loginCheck, loginCheckFactory} from '@/api/login'
|
| | | import {login, logout, getInfo, loginCheck, loginCheckFactory,tideLogin} from '@/api/login'
|
| | | import { getToken, setToken, removeToken } from '@/utils/auth'
|
| | | import { isHttp, isEmpty } from "@/utils/validate"
|
| | | import defAva from '@/assets/images/profile.jpg'
|
| | |
| | | })
|
| | | })
|
| | | },
|
| | | TideLogin(code) {
|
| | | return new Promise((resolve, reject) => {
|
| | | tideLogin(code)
|
| | | .then((res) => {
|
| | | setToken(res.token);
|
| | | this.token = res.token
|
| | | Vue.prototype.uploadHeader = {
|
| | | Authorization: "Bearer " + res.token,
|
| | | };
|
| | | resolve();
|
| | | })
|
| | | .catch((error) => {
|
| | | reject(error);
|
| | | });
|
| | | });
|
| | | },
|
| | | }
|
| | | })
|
| | |
|
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30" v-for="(contact, index) in formYYs.contactList" :key="index"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è系人ï¼" prop="contactPerson"> |
| | | <el-input v-model="contact.contactPerson" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯ï¼" prop="contactPhone"> |
| | | <div style="display: flex; align-items: center;width: 100%;"> |
| | | <el-input v-model="contact.contactPhone" placeholder="请è¾å
¥" clearable /> |
| | | <el-button @click="removeContact(index)" type="danger" circle style="margin-left: 5px;"> |
| | | <el-icon><Close /></el-icon> |
| | | </el-button> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-button @click="addNewContact" style="margin-bottom: 10px;">+ æ°å¢è系人</el-button> |
| | | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¶è¡åºæ¬æ·ï¼" prop="basicBankAccount"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30" v-for="(contact, index) in formYYs.contactList" :key="index"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è系人ï¼" prop="contactPerson"> |
| | | <el-input v-model="contact.contactPerson" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯ï¼" prop="contactPhone"> |
| | | <div style="display: flex; align-items: center;width: 100%;"> |
| | | <el-input v-model="contact.contactPhone" placeholder="请è¾å
¥" clearable /> |
| | | <el-button @click="removeContact(index)" type="danger" circle style="margin-left: 5px;"> |
| | | <el-icon><Close /></el-icon> |
| | | </el-button> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-button @click="addNewContact" style="margin-bottom: 10px;">+ æ°å¢è系人</el-button> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»´æ¤äººï¼" prop="maintainer"> |
| | |
| | | </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> |
| | |
| | | |
| | | // æ¨¡ææ°æ® |
| | | const holidayData = ref([ |
| | | { id: '1', name: 'æ¥è', type: 'legal', startDate: '2024-02-10', endDate: '2024-02-17', days: 8, status: 'active' }, |
| | | { id: '2', name: 'æ¸
æè', type: 'legal', startDate: '2024-04-05', endDate: '2024-04-05', days: 1, status: 'active' }, |
| | | { id: '3', name: 'å³å¨è', type: 'legal', startDate: '2024-05-01', endDate: '2024-05-05', days: 5, status: 'active' } |
| | | { id: '1', name: 'æ¥è', type: 'legal', startDate: '2025-02-10', endDate: '2025-02-17', days: 8, status: 'active' }, |
| | | { id: '2', name: 'æ¸
æè', type: 'legal', startDate: '2025-04-05', endDate: '2025-04-05', days: 1, status: 'active' }, |
| | | { id: '3', name: 'å³å¨è', type: 'legal', startDate: '2025-05-01', endDate: '2025-05-05', days: 5, status: 'active' } |
| | | ]) |
| | | |
| | | const annualData = ref([ |
| | |
| | | problem: "大é¢ååå®¡æ¹æµç¨å¤æï¼å®¡æ¹æ¶é´é¿ï¼å½±åä¸å¡è¿å±", |
| | | solution: "建ç«ç»¿è²ééï¼å¯¹ç¬¦åæ¡ä»¶çååéç¨ç®åå®¡æ¹æµç¨ï¼ç±é¨é¨è´è´£äººç´æ¥å®¡æ¹ï¼å¹³åå®¡æ¹æ¶é´ä»3天缩çè³1天", |
| | | keyPoints: "绿è²é鿡件,ç®åæµç¨,å®¡æ¹æé,æ¶é´æ§å¶", |
| | | creator: "å¼ ç»ç", |
| | | creator: "éå¿å¼º", |
| | | usageCount: 15, |
| | | createTime: "2024-01-15 10:30:00" |
| | | createTime: "2025-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | |
| | | keyPoints: "项ç®åè°,宿ä¼è®®,ç»ä¸æè§,è´è´£äººå¶åº¦", |
| | | creator: "æä¸»ç®¡", |
| | | usageCount: 8, |
| | | createTime: "2024-01-14 15:20:00" |
| | | createTime: "2025-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | |
| | | keyPoints: "ç´§æ¥å级,æ åå¶å®,æµç¨ç®å,åæ¶å¤ç", |
| | | creator: "çä¸å", |
| | | usageCount: 12, |
| | | createTime: "2024-01-13 09:15:00" |
| | | createTime: "2025-01-13 09:15: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() |
| | | }; |
| | |
| | | id: 1, |
| | | title: '产åå¼åå¨ä¼', |
| | | status: 'ongoing', |
| | | startTime: '2024-01-15 09:00:00', |
| | | endTime: '2024-01-15 10:30:00', |
| | | 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' }, |
| | |
| | | id: 2, |
| | | title: '客æ·éæ±è¯å®¡ä¼', |
| | | status: 'upcoming', |
| | | startTime: '2024-01-15 14:00:00', |
| | | endTime: '2024-01-15 15:00:00', |
| | | 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' }, |
| | |
| | | id: 3, |
| | | title: 'å¢é建设活å¨', |
| | | status: 'completed', |
| | | startTime: '2024-01-14 16:00:00', |
| | | endTime: '2024-01-14 18:00:00', |
| | | startTime: '2025-01-14 16:00:00', |
| | | endTime: '2025-01-14 18:00:00', |
| | | location: 'å
¬å¸å¤§å
', |
| | | host: '人äºé¨', |
| | | participants: ['å
¨ä½åå·¥'], |
| | |
| | | <div class="app-container"> |
| | | <!-- æç´¢è¡¨å --> |
| | | <div class="search_form"> |
| | | <!-- <div>--> |
| | | <!-- <span class="search_title">å
¬åæ é¢ï¼</span>--> |
| | | <!-- <el-input--> |
| | | <!-- v-model="searchForm.title"--> |
| | | <!-- style="width: 240px"--> |
| | | <!-- placeholder="请è¾å
¥å
¬åæ é¢æç´¢"--> |
| | | <!-- @change="handleQuery"--> |
| | | <!-- clearable--> |
| | | <!-- :prefix-icon="Search"--> |
| | | <!-- />--> |
| | | <!-- <span class="search_title ml10">å
¬åç±»åï¼</span>--> |
| | | <!-- <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px">--> |
| | | <!-- <el-option label="æ¾åéç¥" :value="1"/>--> |
| | | <!-- <el-option label="设å¤ç»´ä¿®éç¥" :value="2"/>--> |
| | | <!-- </el-select>--> |
| | | <!-- <span class="search_title ml10">ç¶æï¼</span>--> |
| | | <!-- <el-select v-model="searchForm.status" clearable @change="handleQuery" style="width: 240px">--> |
| | | <!-- <el-option label="è稿" :value="0"/>--> |
| | | <!-- <el-option label="å·²åå¸" :value="1"/>--> |
| | | <!-- <el-option label="å·²ä¸çº¿" :value="2"/>--> |
| | | <!-- </el-select>--> |
| | | <!-- <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button>--> |
| | | <!-- <el-button @click="resetQuery" style="margin-left: 10px">éç½®</el-button>--> |
| | | <!-- </div>--> |
| | | <div> |
| | | <span class="search_title">å
¬åæ é¢ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.noticeTitle" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å
¬åæ é¢æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <span class="search_title ml10">å
¬åç±»åï¼</span> |
| | | <el-select v-model="searchForm.noticeType" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="æ¾åéç¥" value="1" /> |
| | | <el-option label="设å¤ç»´ä¿®éç¥" value="2" /> |
| | | </el-select> |
| | | <span class="search_title ml10">ç¶æï¼</span> |
| | | <el-select v-model="searchForm.status" clearable @change="handleQuery" style="width: 240px"> |
| | | <el-option label="è稿" value="0" /> |
| | | <el-option label="å·²åå¸" value="1" /> |
| | | <el-option label="å·²ä¸çº¿" value="2" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | <el-button @click="resetQuery" style="margin-left: 10px">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢å
Œ</el-button> |
| | | <el-button type="danger" plain @click="handleDelete" :disabled="!selectedIds.length">å é¤</el-button> |
| | |
| | | <!-- éç¥å
¬åæ¿ --> |
| | | <div class="notice-board"> |
| | | <!-- æ¾åéç¥åºå --> |
| | | <div class="notice-section" v-if="holidayNoticeCount > 0"> |
| | | <div class="notice-section" v-if="holidayNotices.length > 0"> |
| | | <div class="section-header"> |
| | | <h3>ð
æ¾åéç¥</h3> |
| | | <span class="section-count">{{ holidayNoticeCount }}æ¡</span> |
| | | <span class="section-count">{{ holidayNotices.length }}æ¡</span> |
| | | </div> |
| | | <div class="notice-cards"> |
| | | <div |
| | | v-for="notice in holidayNotices" |
| | | :key="notice.id" |
| | | class="notice-card holiday-card" |
| | | :class="{ 'urgent': notice.priority === '3' }" |
| | | <div |
| | | v-for="notice in holidayNotices" |
| | | :key="notice.id" |
| | | class="notice-card holiday-card" |
| | | :class="{ 'urgent': notice.priority === '3' }" |
| | | > |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <el-icon class="holiday-icon"> |
| | | <Calendar/> |
| | | </el-icon> |
| | | {{ notice.title }} |
| | | <el-icon class="holiday-icon"><Calendar /></el-icon> |
| | | {{ notice.noticeTitle }} |
| | | </div> |
| | | <div class="card-actions"> |
| | | <el-button link type="primary" @click="handleEdit(notice)">ç¼è¾</el-button> |
| | |
| | | </div> |
| | | </div> |
| | | <div class="card-content"> |
| | | <p>{{ notice.content }}</p> |
| | | <p>{{ notice.noticeContent }}</p> |
| | | </div> |
| | | <div class="card-footer"> |
| | | <div class="card-meta"> |
| | |
| | | </span> |
| | | </div> |
| | | <div class="card-info"> |
| | | <span class="creator">{{ notice.createUserName }}</span> |
| | | <span class="creator">{{ notice.createBy }}</span> |
| | | <span class="time">{{ notice.createTime }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="card-remark" v-if="notice.remark"> |
| | | <el-icon> |
| | | <InfoFilled/> |
| | | </el-icon> |
| | | <el-icon><InfoFilled /></el-icon> |
| | | <span>{{ notice.remark }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <pagination |
| | | v-if="holidayNoticePage.total > 0" |
| | | :total="holidayNoticePage.total" |
| | | :page="holidayNoticePage.current" |
| | | :limit="holidayNoticePage.size" |
| | | @pagination="handleHolidayNoticeCurrentChange" |
| | | /> |
| | | |
| | | <!-- 设å¤ç»´ä¿®éç¥åºå --> |
| | | <div class="notice-section" v-if="maintenanceNoticeCount > 0"> |
| | | <div class="notice-section" v-if="maintenanceNotices.length > 0"> |
| | | <div class="section-header"> |
| | | <h3>ð§ 设å¤ç»´ä¿®éç¥</h3> |
| | | <span class="section-count">{{ maintenanceNoticeCount }}æ¡</span> |
| | | <span class="section-count">{{ maintenanceNotices.length }}æ¡</span> |
| | | </div> |
| | | <div class="notice-cards"> |
| | | <div |
| | | v-for="notice in maintenanceNotices" |
| | | :key="notice.id" |
| | | class="notice-card maintenance-card" |
| | | :class="{ 'urgent': notice.priority === '3' }" |
| | | <div |
| | | v-for="notice in maintenanceNotices" |
| | | :key="notice.id" |
| | | class="notice-card maintenance-card" |
| | | :class="{ 'urgent': notice.priority === '3' }" |
| | | > |
| | | <div class="card-header"> |
| | | <div class="card-title"> |
| | | <el-icon class="maintenance-icon"> |
| | | <Tools/> |
| | | </el-icon> |
| | | {{ notice.title }} |
| | | <el-icon class="maintenance-icon"><Tools /></el-icon> |
| | | {{ notice.noticeTitle }} |
| | | </div> |
| | | <div class="card-actions"> |
| | | <el-button link type="primary" @click="handleEdit(notice)">ç¼è¾</el-button> |
| | |
| | | </div> |
| | | </div> |
| | | <div class="card-content"> |
| | | <p>{{ notice.content }}</p> |
| | | <p>{{ notice.noticeContent }}</p> |
| | | </div> |
| | | <div class="card-footer"> |
| | | <div class="card-meta"> |
| | |
| | | </span> |
| | | </div> |
| | | <div class="card-info"> |
| | | <span class="creator">{{ notice.createUserName }}</span> |
| | | <span class="creator">{{ notice.createBy }}</span> |
| | | <span class="time">{{ notice.createTime }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="card-remark" v-if="notice.remark"> |
| | | <el-icon> |
| | | <InfoFilled/> |
| | | </el-icon> |
| | | <el-icon><InfoFilled /></el-icon> |
| | | <span>{{ notice.remark }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <pagination |
| | | v-if="maintenanceNoticePage.total > 0" |
| | | :total="maintenanceNoticePage.total" |
| | | :page="maintenanceNoticePage.current" |
| | | :limit="maintenanceNoticePage.size" |
| | | @pagination="handleMaintenanceNoticeCurrentChange" |
| | | /> |
| | | |
| | | <!-- ç©ºç¶æ --> |
| | | <div class="empty-state" v-if="holidayNotices.length === 0 && maintenanceNotices.length === 0"> |
| | | <el-empty description="ææ éç¥å
Œ"/> |
| | | <div class="empty-state" v-if="filteredNotices.length === 0"> |
| | | <el-empty description="ææ éç¥å
Œ" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="800px" |
| | | append-to-body |
| | | @close="resetForm" |
| | | <el-dialog |
| | | :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="800px" |
| | | append-to-body |
| | | @close="resetForm" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬åæ é¢" prop="title"> |
| | | <el-input v-model="form.title" placeholder="请è¾å
¥å
¬åæ é¢"/> |
| | | <el-form-item label="å
¬åæ é¢" prop="noticeTitle"> |
| | | <el-input v-model="form.noticeTitle" placeholder="请è¾å
¥å
¬åæ é¢" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬åç±»å" prop="type"> |
| | | <el-select v-model="form.type" placeholder="è¯·éæ©å
¬åç±»å" style="width: 100%"> |
| | | <el-option label="æ¾åéç¥" :value="1"/> |
| | | <el-option label="设å¤ç»´ä¿®éç¥" :value="2"/> |
| | | <el-form-item label="å
¬åç±»å" prop="noticeType"> |
| | | <el-select v-model="form.noticeType" placeholder="è¯·éæ©å
¬åç±»å" style="width: 100%"> |
| | | <el-option label="æ¾åéç¥" value="1" /> |
| | | <el-option label="设å¤ç»´ä¿®éç¥" value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio :value="0">è稿</el-radio> |
| | | <el-radio :value="1">å·²åå¸</el-radio> |
| | | <el-radio :value="2">å·²ä¸çº¿</el-radio> |
| | | <el-radio value="0">è稿</el-radio> |
| | | <el-radio value="1">å·²åå¸</el-radio> |
| | | <el-radio value="2">å·²ä¸çº¿</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼å
级"> |
| | | <el-select v-model="form.priority" placeholder="è¯·éæ©ä¼å
级" style="width: 100%"> |
| | | <el-option label="æ®é" :value="1"/> |
| | | <el-option label="éè¦" :value="2"/> |
| | | <el-option label="ç´§æ¥" :value="3"/> |
| | | <el-option label="æ®é" value="1" /> |
| | | <el-option label="éè¦" value="2" /> |
| | | <el-option label="ç´§æ¥" value="3" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-col :span="24"> |
| | | <el-form-item label="å
¬åå
容" prop="noticeContent"> |
| | | <el-input |
| | | v-model="form.content" |
| | | type="textarea" |
| | | :rows="6" |
| | | placeholder="请è¾å
¥å
¬åå
容" |
| | | maxlength="500" |
| | | show-word-limit |
| | | v-model="form.noticeContent" |
| | | type="textarea" |
| | | :rows="6" |
| | | placeholder="请è¾å
¥å
¬åå
容" |
| | | maxlength="500" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input |
| | | v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | maxlength="200" |
| | | show-word-limit |
| | | v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | maxlength="200" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {Search, Calendar, Tools, InfoFilled} from "@element-plus/icons-vue"; |
| | | import {onMounted, ref, reactive, toRefs, computed} from "vue"; |
| | | import {ElMessage, ElMessageBox} from "element-plus"; |
| | | import { Search, Calendar, Tools, InfoFilled } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { |
| | | addNotice, |
| | | delNotice, |
| | | getCount, |
| | | listNotice, |
| | | updateNotice |
| | | } from "../../../api/collaborativeApproval/noticeManagement.js"; |
| | | import pagination from "../../../components/PIMTable/Pagination.vue"; |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | // ååºå¼æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | title: "", |
| | | type: undefined, |
| | | status: undefined, |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | status: "", |
| | | }, |
| | | form: { |
| | | id: undefined, |
| | | title: "", |
| | | type: null, |
| | | content: "", |
| | | status: 0, |
| | | priority: 1, |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | noticeContent: "", |
| | | status: "0", |
| | | priority: "1", |
| | | remark: "", |
| | | createBy: "", |
| | | createTime: "", |
| | | }, |
| | | rules: { |
| | | title: [ |
| | | {required: true, message: "å
¬åæ é¢ä¸è½ä¸ºç©º", trigger: "blur"} |
| | | noticeTitle: [ |
| | | { required: true, message: "å
¬åæ é¢ä¸è½ä¸ºç©º", trigger: "blur" } |
| | | ], |
| | | type: [ |
| | | {required: true, message: "è¯·éæ©å
¬åç±»å", trigger: "change"} |
| | | noticeType: [ |
| | | { required: true, message: "è¯·éæ©å
¬åç±»å", trigger: "change" } |
| | | ], |
| | | content: [ |
| | | {required: true, message: "å
¬åå
容ä¸è½ä¸ºç©º", trigger: "blur"} |
| | | noticeContent: [ |
| | | { required: true, message: "å
¬åå
容ä¸è½ä¸ºç©º", trigger: "blur" } |
| | | ] |
| | | } |
| | | }); |
| | | |
| | | const {searchForm, form, rules} = toRefs(data); |
| | | const { searchForm, form, rules } = toRefs(data); |
| | | |
| | | // 页é¢ç¶æ |
| | | const dialogVisible = ref(false); |
| | |
| | | const selectedIds = ref([]); |
| | | const formRef = ref(); |
| | | |
| | | // æ¨¡ææ°æ® - æ ¹æ®æ³å®èåæ¥è®¾è®¡ |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | noticeTitle: "2024å¹´æ¥èæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "2", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´æ¥èæ¾å宿å¦ä¸ï¼2æ10æ¥ï¼åä¸ï¼è³2æ17æ¥ï¼åå
«ï¼æ¾åè°ä¼ï¼å
±8天ã2æ4æ¥ï¼æææ¥ï¼ã2æ18æ¥ï¼æææ¥ï¼ä¸çã请åé¨é¨æåå好工ä½å®æã", |
| | | remark: "æ¾åæé´è¯·ä¿æææºç
éï¼å¦æç´§æ¥äºå¡åæ¶èç³»", |
| | | createBy: "人äºé¨", |
| | | createTime: "2025-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: 2, |
| | | noticeTitle: "2024å¹´æ¸
æèæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´æ¸
æèæ¾å宿å¦ä¸ï¼4æ4æ¥ï¼ææåï¼è³4æ6æ¥ï¼ææå
ï¼æ¾åè°ä¼ï¼å
±3天ã4æ7æ¥ï¼æææ¥ï¼ä¸çã", |
| | | remark: "请åé¨é¨å好å¼ç宿ï¼ç¡®ä¿èæ¥æé´å项工使£å¸¸è¿è½¬", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2025-01-14 14:20:00" |
| | | }, |
| | | { |
| | | id: 3, |
| | | noticeTitle: "2024å¹´å³å¨èæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´å³å¨èæ¾å宿å¦ä¸ï¼5æ1æ¥ï¼ææä¸ï¼è³5æ5æ¥ï¼æææ¥ï¼æ¾åè°ä¼ï¼å
±5天ã4æ28æ¥ï¼æææ¥ï¼ã5æ11æ¥ï¼ææå
ï¼ä¸çã", |
| | | remark: "æ¾åå请å
³éçµæºï¼é好é¨çªï¼æ³¨æå®å
¨", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2025-01-13 09:15:00" |
| | | }, |
| | | { |
| | | id: 4, |
| | | noticeTitle: "2024年端åèæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024年端åèæ¾å宿å¦ä¸ï¼6æ8æ¥ï¼ææå
ï¼è³6æ10æ¥ï¼ææä¸ï¼æ¾åè°ä¼ï¼å
±3天ã6æ11æ¥ï¼ææäºï¼ä¸çã", |
| | | remark: "ç¥å¤§å®¶ç«¯åèå¿«ä¹ï¼é家幸ç¦ï¼", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2025-01-12 16:30:00" |
| | | }, |
| | | { |
| | | id: 5, |
| | | noticeTitle: "2024å¹´ä¸ç§èæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´ä¸ç§èæ¾å宿å¦ä¸ï¼9æ15æ¥ï¼æææ¥ï¼è³9æ17æ¥ï¼ææäºï¼æ¾åè°ä¼ï¼å
±3天ã9æ14æ¥ï¼ææå
ï¼ä¸çã", |
| | | remark: "ä¸ç§ä½³èï¼ç¥å¤§å®¶å¢åç¾æ»¡ï¼å¹¸ç¦å®åº·ï¼", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2025-01-11 11:20:00" |
| | | }, |
| | | { |
| | | id: 6, |
| | | noticeTitle: "2024å¹´å½åºèæ¾åéç¥", |
| | | noticeType: "1", |
| | | priority: "2", |
| | | status: "1", |
| | | noticeContent: "æ ¹æ®å½å¡é¢åå
Œ
éç¥ï¼2024å¹´å½åºèæ¾å宿å¦ä¸ï¼10æ1æ¥ï¼ææäºï¼è³10æ7æ¥ï¼ææä¸ï¼æ¾åè°ä¼ï¼å
±7天ã9æ29æ¥ï¼æææ¥ï¼ã10æ12æ¥ï¼ææå
ï¼ä¸çã", |
| | | remark: "å½åºæé´è¯·åé¨é¨å好å¼ç宿ï¼ç¡®ä¿å®å
¨ç¨³å®", |
| | | createBy: "è¡æ¿é¨", |
| | | createTime: "2025-01-10 15:45:00" |
| | | }, |
| | | { |
| | | id: 7, |
| | | noticeTitle: "A车é´ç产线年度æ£ä¿®éç¥", |
| | | noticeType: "2", |
| | | priority: "2", |
| | | status: "1", |
| | | noticeContent: "A车é´ç产线å°äº2024å¹´1æ20æ¥ï¼å¨å
ï¼è¿è¡å¹´åº¦æ£ä¿®ç»´æ¤ï¼é¢è®¡åå·¥8å°æ¶ãæ£ä¿®å
容å
æ¬ï¼è®¾å¤æ¸
æ´ã润æ»ä¿å
»ãå®å
¨è£
ç½®æ£æ¥çã请ç产é¨é¨æåè°æ´ç产计åã", |
| | | remark: "ç»´ä¿®æé´è¯·ç¸å
³äººåé
åï¼ç¡®ä¿æ£ä¿®å·¥ä½å®å
¨é¡ºå©è¿è¡", |
| | | createBy: "设å¤é¨", |
| | | createTime: "2025-01-14 14:20:00" |
| | | }, |
| | | { |
| | | id: 8, |
| | | noticeTitle: "B车é´è®¾å¤é¢é²æ§ç»´æ¤éç¥", |
| | | noticeType: "2", |
| | | priority: "1", |
| | | status: "1", |
| | | noticeContent: "B车é´å
³é®è®¾å¤å°äº2024å¹´1æ25æ¥è¿è¡é¢é²æ§ç»´æ¤ï¼é¢è®¡åå·¥4å°æ¶ãç»´æ¤å
容å
æ¬ï¼è®¾å¤æ£æ¥ãé¶ä»¶æ´æ¢ãæ§è½æµè¯çã请ç¸å
³é¨é¨é
åã", |
| | | remark: "ç»´æ¤å®æåå°è¿è¡è¯è¿è¡ï¼ç¡®ä¿è®¾å¤æ£å¸¸è¿è¡", |
| | | createBy: "设å¤é¨", |
| | | createTime: "2025-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredNotices = computed(() => { |
| | | let filtered = [...mockData]; |
| | | |
| | | |
| | | if (searchForm.value.noticeTitle) { |
| | | filtered = filtered.filter(item => |
| | | item.noticeTitle.includes(searchForm.value.noticeTitle) |
| | | filtered = filtered.filter(item => |
| | | item.noticeTitle.includes(searchForm.value.noticeTitle) |
| | | ); |
| | | } |
| | | if (searchForm.value.noticeType) { |
| | | filtered = filtered.filter(item => |
| | | item.noticeType === searchForm.value.noticeType |
| | | filtered = filtered.filter(item => |
| | | item.noticeType === searchForm.value.noticeType |
| | | ); |
| | | } |
| | | if (searchForm.value.status !== "") { |
| | | filtered = filtered.filter(item => |
| | | item.status === searchForm.value.status |
| | | filtered = filtered.filter(item => |
| | | item.status === searchForm.value.status |
| | | ); |
| | | } |
| | | |
| | | |
| | | return filtered; |
| | | }); |
| | | |
| | | const holidayNotices = computed(() => { |
| | | return filteredNotices.value.filter(notice => notice.noticeType === "1"); |
| | | }); |
| | | |
| | | const maintenanceNotices = computed(() => { |
| | | return filteredNotices.value.filter(notice => notice.noticeType === "2"); |
| | | }); |
| | | |
| | | // æ¹æ³å®ä¹ |
| | |
| | | |
| | | const resetQuery = () => { |
| | | searchForm.value = { |
| | | title: "", |
| | | type: "", |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | status: "" |
| | | }; |
| | | }; |
| | | |
| | | const getPriorityText = (priority) => { |
| | | const priorityMap = {"1": "æ®é", "2": "éè¦", "3": "ç´§æ¥"}; |
| | | const priorityMap = { "1": "æ®é", "2": "éè¦", "3": "ç´§æ¥" }; |
| | | return priorityMap[priority] || "æ®é"; |
| | | }; |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = {"0": "è稿", "1": "å·²åå¸", "2": "å·²ä¸çº¿"}; |
| | | const statusMap = { "0": "è稿", "1": "å·²åå¸", "2": "å·²ä¸çº¿" }; |
| | | return statusMap[status] || "æªç¥"; |
| | | }; |
| | | |
| | |
| | | dialogTitle.value = "æ°å¢å
Œ"; |
| | | form.value = { |
| | | id: undefined, |
| | | title: "", |
| | | type: undefined, |
| | | content: "", |
| | | status: 0, |
| | | priority: 1, |
| | | noticeTitle: "", |
| | | noticeType: "", |
| | | noticeContent: "", |
| | | status: "0", |
| | | priority: "1", |
| | | remark: "", |
| | | createBy: userStore.name || "å½åç¨æ·", |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | } |
| | | dialogVisible.value = true; |
| | |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = "ç¼è¾å
Œ"; |
| | | form.value = {...row}; |
| | | form.value = { ...row }; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedIds.value = selection.map(item => item.id); |
| | | }; |
| | | |
| | | const handleDelete = (id) => { |
| | | ElMessageBox.confirm( |
| | | "确认å é¤è¿æ¡å
¬ååï¼", |
| | | "æç¤º", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning" |
| | | } |
| | | "确认å é¤è¿æ¡å
¬ååï¼", |
| | | "æç¤º", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning" |
| | | } |
| | | ).then(() => { |
| | | delNotice(id).then(res => { |
| | | const index = mockData.findIndex(item => item.id === id); |
| | | if (index > -1) { |
| | | mockData.splice(index, 1); |
| | | ElMessage.success("å 餿å"); |
| | | resetTable() |
| | | }) |
| | | } |
| | | }); |
| | | }; |
| | | |
| | |
| | | if (valid) { |
| | | if (form.value.id) { |
| | | // ç¼è¾æ¨¡å¼ |
| | | updateNotice(form.value).then(res => { |
| | | ElMessage.success("ä¿®æ¹æå"); |
| | | resetTable() |
| | | }) |
| | | const index = mockData.findIndex(item => item.id === form.value.id); |
| | | if (index > -1) { |
| | | mockData[index] = { ...form.value }; |
| | | } |
| | | ElMessage.success("ä¿®æ¹æå"); |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | addNotice(form.value).then(res => { |
| | | ElMessage.success("æ°å¢æå"); |
| | | resetTable() |
| | | }) |
| | | const newId = Math.max(...mockData.map(item => item.id)) + 1; |
| | | const newNotice = { |
| | | ...form.value, |
| | | id: newId, |
| | | createTime: new Date().toLocaleString() |
| | | }; |
| | | mockData.unshift(newNotice); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const holidayNoticeCount = ref() |
| | | const maintenanceNoticeCount = ref() |
| | | const fetchCount = () => { |
| | | getCount().then(res => { |
| | | holidayNoticeCount.value = res.data.filter(item => { |
| | | return item.type === 1 |
| | | })[0].count; |
| | | maintenanceNoticeCount.value = res.data.filter(item => { |
| | | return item.type === 2 |
| | | })[0].count; |
| | | }); |
| | | } |
| | | |
| | | const holidayNotices = ref([]) |
| | | const maintenanceNotices = ref([]) |
| | | const holidayNoticePage = ref({ |
| | | total: 0, |
| | | current: 1, |
| | | size: 6 |
| | | }) |
| | | |
| | | const maintenanceNoticePage = ref({ |
| | | total: 0, |
| | | current: 1, |
| | | size: 6 |
| | | }) |
| | | |
| | | const fetchHolidayNotices = () => { |
| | | listNotice({...holidayNoticePage.value, type: 1}).then(res => { |
| | | holidayNotices.value = res.data.records |
| | | holidayNoticePage.value.total = res.data.total |
| | | }); |
| | | }; |
| | | |
| | | const fetchMaintenanceNotices = () => { |
| | | listNotice({...holidayNoticePage.value, type: 2}).then(res => { |
| | | maintenanceNotices.value = res.data.records |
| | | maintenanceNoticePage.value.total = res.data.total |
| | | }); |
| | | }; |
| | | |
| | | const handleHolidayNoticeCurrentChange = (val) => { |
| | | holidayNoticePage.value.size = val.limit |
| | | holidayNoticePage.value.current = val.page |
| | | fetchHolidayNotices() |
| | | }; |
| | | |
| | | const handleMaintenanceNoticeCurrentChange = (val) => { |
| | | maintenanceNoticePage.value.size = val.limit |
| | | maintenanceNoticePage.value.current = val.page |
| | | fetchMaintenanceNotices() |
| | | }; |
| | | |
| | | const resetTable = () => { |
| | | holidayNoticePage.value.current = 1 |
| | | holidayNoticePage.value.size = 6 |
| | | maintenanceNoticePage.value.current = 1 |
| | | maintenanceNoticePage.value.size = 6 |
| | | fetchHolidayNotices() |
| | | fetchMaintenanceNotices() |
| | | fetchCount() |
| | | }; |
| | | |
| | | const resetForm = () => { |
| | |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | fetchCount() |
| | | fetchHolidayNotices() |
| | | fetchMaintenanceNotices() |
| | | // 页é¢å è½½å®æ |
| | | }); |
| | | </script> |
| | | |
| | |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .priority-1 { |
| | | background: #f0f9ff; |
| | | color: #0369a1; |
| | | } |
| | | .priority-1 { background: #f0f9ff; color: #0369a1; } |
| | | .priority-2 { background: #fef3c7; color: #d97706; } |
| | | .priority-3 { background: #fef2f2; color: #dc2626; } |
| | | |
| | | .priority-2 { |
| | | background: #fef3c7; |
| | | color: #d97706; |
| | | } |
| | | |
| | | .priority-3 { |
| | | background: #fef2f2; |
| | | color: #dc2626; |
| | | } |
| | | |
| | | .status-0 { |
| | | background: #f3f4f6; |
| | | color: #6b7280; |
| | | } |
| | | |
| | | .status-1 { |
| | | background: #d1fae5; |
| | | color: #059669; |
| | | } |
| | | |
| | | .status-2 { |
| | | background: #fef3c7; |
| | | color: #d97706; |
| | | } |
| | | .status-0 { background: #f3f4f6; color: #6b7280; } |
| | | .status-1 { background: #d1fae5; color: #059669; } |
| | | .status-2 { background: #fef3c7; color: #d97706; } |
| | | |
| | | .card-info { |
| | | display: flex; |
| | |
| | | .notice-cards { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | |
| | | |
| | | .search_form { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | } |
| | | |
| | | |
| | | .search_form > div { |
| | | width: 100%; |
| | | } |
| | |
| | | status: "published", |
| | | content: "æ ¹æ®å½å®¶è§å®ï¼ç»åå
¬å¸å®é
æ
åµï¼ç°å°2024å¹´æ¥èæ¾å宿éç¥å¦ä¸...", |
| | | departments: ["ææ¯é¨", "éå®é¨", "人äºé¨", "è´¢å¡é¨", "è¿è¥é¨", "å¸åºé¨", "客æé¨"], |
| | | expireDate: "2024-02-15", |
| | | expireDate: "2025-02-15", |
| | | syncMethods: ["wechat", "dingtalk", "email"], |
| | | createTime: "2024-01-15 10:30:00" |
| | | createTime: "2025-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | |
| | | status: "published", |
| | | content: "ææ¯é¨å®äºæ¯å¨äºä¸å2ç¹å¬å¼å¨ä¾ä¼ï¼è¯·åä½åäºåæ¶åå ...", |
| | | departments: ["ææ¯é¨"], |
| | | expireDate: "2024-01-20", |
| | | expireDate: "2025-01-20", |
| | | syncMethods: ["wechat", "dingtalk"], |
| | | createTime: "2024-01-14 15:20:00" |
| | | createTime: "2025-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | |
| | | status: "draft", |
| | | content: "为维æ¤å
¬å¸æ£å¸¸ç§©åºï¼è§èåå·¥è¡ä¸ºï¼ç°å¯¹è¿åå
¬å¸è§å®çè¡ä¸ºè¿è¡å¤ç½...", |
| | | departments: ["人äºé¨", "ææ¯é¨", "éå®é¨"], |
| | | expireDate: "2024-02-13", |
| | | expireDate: "2025-02-13", |
| | | syncMethods: ["wechat", "email"], |
| | | createTime: "2024-01-13 09:15:00" |
| | | createTime: "2025-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
| | |
| | | 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> |
| | |
| | | programName: "订åå¤çRPA", |
| | | status: "running", |
| | | description: "èªå¨å¤ç客æ·è®¢åï¼å
æ¬éªè¯ãåé
å确认", |
| | | createTime: "2024-01-15 10:30:00" |
| | | createTime: "2025-01-15 10:30:00" |
| | | }, |
| | | { |
| | | id: "2", |
| | | programName: "åºå忥RPA", |
| | | status: "stopped", |
| | | description: "忥å¤ä¸ªä»åºçåºåæ°æ®ï¼ç¡®ä¿æ°æ®ä¸è´æ§", |
| | | createTime: "2024-01-14 15:20:00" |
| | | createTime: "2025-01-14 15:20:00" |
| | | }, |
| | | { |
| | | id: "3", |
| | | programName: "æ¥è¡¨çæRPA", |
| | | status: "error", |
| | | description: "èªå¨çææ¯æ¥é宿¥è¡¨ååºåæ¥è¡¨", |
| | | createTime: "2024-01-13 09:15:00" |
| | | createTime: "2025-01-13 09:15:00" |
| | | } |
| | | ]; |
| | | |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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: 'ç«å³å¬åé®é¢äº§åï¼åæåå ï¼å¶å®æ¹è¿æªæ½' |
| | |
| | | version: 'v2.1.0', |
| | | status: 'active', |
| | | accuracy: '94.2%', |
| | | lastUpdate: '2024-01-15 14:30:00' |
| | | lastUpdate: '2025-01-15 14:30:00' |
| | | }, |
| | | { |
| | | modelName: 'å°å±åå颿µæ¨¡å', |
| | | version: 'v1.8.5', |
| | | status: 'active', |
| | | accuracy: '91.7%', |
| | | lastUpdate: '2024-01-14 09:15:00' |
| | | lastUpdate: '2025-01-14 09:15:00' |
| | | }, |
| | | { |
| | | modelName: 'è½èåææ¨¡å', |
| | | version: 'v2.0.3', |
| | | status: 'standby', |
| | | accuracy: '89.3%', |
| | | lastUpdate: '2024-01-13 16:45:00' |
| | | lastUpdate: '2025-01-13 16:45:00' |
| | | } |
| | | ]) |
| | | |
| | |
| | | power: '75.5', |
| | | powerFactor: '0.85', |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: '2024-01-15 10:30:00' |
| | | lastUpdateTime: '2025-01-15 10:30:00' |
| | | }, |
| | | { |
| | | id: 2, |
| | |
| | | power: '45.2', |
| | | powerFactor: '0.92', |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: '2024-01-15 10:25:00' |
| | | lastUpdateTime: '2025-01-15 10:25:00' |
| | | } |
| | | ] |
| | | this.pagination.total = this.meterList.length |
| | |
| | | power: '50.0', |
| | | powerFactor: '0.85', |
| | | status: 'æ£å¸¸', |
| | | lastUpdateTime: '2024-01-15 12:00:00' |
| | | lastUpdateTime: '2025-01-15 12:00:00' |
| | | } |
| | | this.detailDialogVisible = true |
| | | }, |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | | |
| | | |
| | |
| | | const maintenanceRecords = ref([ |
| | | { |
| | | id: 1, |
| | | date: '2024-01-15', |
| | | date: '2025-01-15', |
| | | type: 'inspection', |
| | | title: '年度æ£éª', |
| | | description: 'æç
§TSG 21-2016æ åè¿è¡å¹´åº¦æ£éªï¼è®¾å¤ç¶æè¯å¥½', |
| | |
| | | }, |
| | | { |
| | | id: 2, |
| | | date: '2024-02-20', |
| | | date: '2025-02-20', |
| | | type: 'maintenance', |
| | | title: 'å®å
¨éç»´æ¤', |
| | | description: 'æ´æ¢å®å
¨éå¯å°åï¼æ ¡ååå设å®å¼', |
| | |
| | | }, |
| | | { |
| | | id: 3, |
| | | date: '2024-03-10', |
| | | date: '2025-03-10', |
| | | type: 'inspection', |
| | | title: 'ååæµè¯', |
| | | description: 'è¿è¡åå容卿°´åè¯éªï¼ç¬¦åè®¾è®¡è¦æ±', |
| | |
| | | </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", |
| | |
| | | modalOptions, |
| | | handleConfirm, |
| | | closeModal, |
| | | } = useModal({ title: "设å¤ç»´ä¿®" }); |
| | | } = useModal({ title: "设å¤ä¿å
»" }); |
| | | |
| | | /** |
| | | * @desc ä¿åä¿å
» |
| | |
| | | <!-- <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> --> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | <el-button type="primary" plain @click="handlePrint">æå°</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column |
| | | label="åºåºç¼å·" |
| | | prop="code" |
| | | min-width="250" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åºåºæ¥æ" |
| | | prop="createTime" |
| | | min-width="250" |
| | | min-width="130" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | |
| | | <el-table-column |
| | | label="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | width="100" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | width="100" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- æå°é¢è§å¼¹çª --> |
| | | <el-dialog |
| | | v-model="printPreviewVisible" |
| | | title="æå°é¢è§" |
| | | width="90%" |
| | | :close-on-click-modal="false" |
| | | class="print-preview-dialog" |
| | | > |
| | | <div class="print-preview-container"> |
| | | <div class="print-preview-header"> |
| | | <el-button type="primary" @click="executePrint">æ§è¡æå°</el-button> |
| | | <el-button @click="printPreviewVisible = false">å
³éé¢è§</el-button> |
| | | </div> |
| | | <div class="print-preview-content"> |
| | | <div v-if="printData.length === 0" style="text-align: center; padding: 50px; color: #999;"> |
| | | ææ æå°æ°æ® |
| | | </div> |
| | | <div v-else style="text-align: center; padding: 10px; color: #666; font-size: 14px; background: #e8f4fd; margin-bottom: 10px;"> |
| | | å
± {{ printData.length }} æ¡æ°æ®å¾
æå° |
| | | </div> |
| | | <div v-for="(item, index) in printData" :key="index" class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="company-name">é¼è¯çå®ä¸æé责任å
¬å¸</div> |
| | | <div class="document-title">é¶å®åè´§å</div> |
| | | </div> |
| | | |
| | | <div class="info-section"> |
| | | <div class="info-row"> |
| | | <div> |
| | | <span class="label">åè´§æ¥æï¼</span> |
| | | <span class="value">{{ formatDate(item.createTime) }}</span> |
| | | </div> |
| | | <div> |
| | | |
| | | <span class="label">客æ·åç§°ï¼</span> |
| | | <span class="value">{{ item.supplierName || 'å¼ ç±æ' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <span class="label">åå·ï¼</span> |
| | | <span class="value">{{ item.code }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table-section"> |
| | | <table class="product-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼åå·</th> |
| | | <th>åä½</th> |
| | | <th>åä»·</th> |
| | | <th>é¶å®æ°é</th> |
| | | <th>é¶å®éé¢</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr> |
| | | <td>{{ item.productCategory || 'ç ç°ç ' }}</td> |
| | | <td>{{ item.specificationModel || 'æ å' }}</td> |
| | | <td>{{ item.unit || 'å' }}</td> |
| | | <td>{{ item.taxInclusiveUnitPrice || '0' }}</td> |
| | | <td>{{ item.inboundNum || '2000' }}</td> |
| | | <td>{{ item.taxInclusiveTotalPrice || '0' }}</td> |
| | | </tr> |
| | | </tbody> |
| | | <tfoot> |
| | | <tr> |
| | | <td class="label">å计</td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value">{{ item.inboundNum || '2000' }}</td> |
| | | <td class="total-value">{{ item.taxInclusiveTotalPrice || '0' }}</td> |
| | | </tr> |
| | | </tfoot> |
| | | </table> |
| | | </div> |
| | | |
| | | <div class="footer-section"> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§çµè¯ï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§äººï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item address-item"> |
| | | <span class="label">æ¶è´§å°åï¼</span> |
| | | <span class="value address-value"></span> |
| | | </div> |
| | | </div> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æä½åï¼</span> |
| | | <span class="value">{{ userStore.nickname || 'æå¼å' }}</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æå°æ¥æï¼</span> |
| | | <span class="value">{{ formatDateTime(new Date()) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { ref } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { |
| | | getStockOutPage, |
| | | delStockOut, |
| | |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const userList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const productList = ref([]) |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const fileList = ref([]); |
| | | |
| | | // æå°ç¸å
³ |
| | | const printPreviewVisible = ref(false); |
| | | const printData = ref([]); |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", |
| | |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // æå°åè½ |
| | | const handlePrint = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦æå°çæ°æ®"); |
| | | return; |
| | | } |
| | | printData.value = [...selectedRows.value]; |
| | | console.log('æå°æ°æ®:', printData.value); |
| | | printPreviewVisible.value = true; |
| | | }; |
| | | |
| | | // æ§è¡æå° |
| | | const executePrint = () => { |
| | | console.log('å¼å§æ§è¡æå°ï¼æ°æ®æ¡æ°:', printData.value.length); |
| | | console.log('æå°æ°æ®:', printData.value); |
| | | |
| | | // å建ä¸ä¸ªæ°çæå°çªå£ |
| | | const printWindow = window.open('', '_blank', 'width=800,height=600'); |
| | | |
| | | // æå»ºæå°å
容 |
| | | let printContent = ` |
| | | <!DOCTYPE html> |
| | | <html> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <title>æå°é¢è§</title> |
| | | <style> |
| | | body { |
| | | margin: 0; |
| | | padding: 0; |
| | | font-family: "SimSun", serif; |
| | | background: white; |
| | | } |
| | | .print-page { |
| | | width: 200mm; |
| | | height: 75mm; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | background: white; |
| | | box-sizing: border-box; |
| | | page-break-after: always; |
| | | page-break-inside: avoid; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-size: 12px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | color: #000; |
| | | } |
| | | .header { |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .info-row { |
| | | line-height: 20px; |
| | | } |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 12px; |
| | | } |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .table-section { |
| | | margin-bottom: 40px; |
| | | // flex: 0.6; |
| | | } |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | } |
| | | .product-table th, .product-table td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 12px; |
| | | line-height: 1.4; |
| | | } |
| | | .product-table th { |
| | | font-weight: bold; |
| | | } |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | .footer-section { |
| | | margin-top: auto; |
| | | } |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 22px; |
| | | justify-content: space-between; |
| | | } |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | } |
| | | .footer-item .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .footer-item .value { |
| | | min-width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .address-item .address-value { |
| | | min-width: 200px; |
| | | } |
| | | @media print { |
| | | body { |
| | | margin: 0; |
| | | padding: 0; |
| | | } |
| | | .print-page { |
| | | margin: 0; |
| | | padding: 10mm; |
| | | /* padding-left: 20mm; */ |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | `; |
| | | |
| | | // ä¸ºæ¯æ¡æ°æ®çææå°é¡µé¢ |
| | | printData.value.forEach((item, index) => { |
| | | printContent += ` |
| | | <div class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="company-name">é¼è¯çå®ä¸æé责任å
¬å¸</div> |
| | | <div class="document-title">é¶å®åè´§å</div> |
| | | </div> |
| | | |
| | | <div class="info-section"> |
| | | <div class="info-row"> |
| | | <div> |
| | | <span class="label">åè´§æ¥æï¼</span> |
| | | <span class="value">${formatDate(item.createTime)}</span> |
| | | </div> |
| | | <div> |
| | | <span class="label">客æ·åç§°ï¼</span> |
| | | <span class="value">${item.supplierName || 'å¼ ç±æ'}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <span class="label">åå·ï¼</span> |
| | | <span class="value">${item.code || ''}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table-section"> |
| | | <table class="product-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼åå·</th> |
| | | <th>åä½</th> |
| | | <th>åä»·</th> |
| | | <th>é¶å®æ°é</th> |
| | | <th>é¶å®éé¢</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr> |
| | | <td>${item.productCategory || 'ç ç°ç '}</td> |
| | | <td>${item.specificationModel || 'æ å'}</td> |
| | | <td>${item.unit || 'å'}</td> |
| | | <td>${item.taxInclusiveUnitPrice || '0'}</td> |
| | | <td>${item.inboundNum || '2000'}</td> |
| | | <td>${item.taxInclusiveTotalPrice || '0'}</td> |
| | | </tr> |
| | | </tbody> |
| | | <tfoot> |
| | | <tr> |
| | | <td class="label">å计</td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value">${item.inboundNum || '2000'}</td> |
| | | <td class="total-value">${item.taxInclusiveTotalPrice || '0'}</td> |
| | | </tr> |
| | | </tfoot> |
| | | </table> |
| | | </div> |
| | | |
| | | <div class="footer-section"> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§çµè¯ï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§äººï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item address-item"> |
| | | <span class="label">æ¶è´§å°åï¼</span> |
| | | <span class="value address-value"></span> |
| | | </div> |
| | | </div> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æä½åï¼</span> |
| | | <span class="value">${userStore.nickname || 'æå¼å'}</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æå°æ¥æï¼</span> |
| | | <span class="value">${formatDateTime(new Date())}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | `; |
| | | }); |
| | | |
| | | printContent += ` |
| | | </body> |
| | | </html> |
| | | `; |
| | | |
| | | // åå
¥å
容尿°çªå£ |
| | | printWindow.document.write(printContent); |
| | | printWindow.document.close(); |
| | | |
| | | // çå¾
å
容å è½½å®æåæå° |
| | | printWindow.onload = () => { |
| | | setTimeout(() => { |
| | | printWindow.print(); |
| | | printWindow.close(); |
| | | printPreviewVisible.value = false; |
| | | }, 500); |
| | | }; |
| | | }; |
| | | |
| | | |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = (dateString) => { |
| | | if (!dateString) return getCurrentDate(); |
| | | const date = new Date(dateString); |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | return `${year}/${month}/${day}`; |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (date) => { |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | const hours = String(date.getHours()).padStart(2, "0"); |
| | | const minutes = String(date.getMinutes()).padStart(2, "0"); |
| | | const seconds = String(date.getSeconds()).padStart(2, "0"); |
| | | return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; |
| | | }; |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| | | <style scoped lang="scss"> |
| | | .print-preview-dialog { |
| | | .el-dialog__body { |
| | | padding: 0; |
| | | max-height: 80vh; |
| | | overflow-y: auto; |
| | | } |
| | | } |
| | | |
| | | .print-preview-container { |
| | | .print-preview-header { |
| | | padding: 15px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | text-align: center; |
| | | |
| | | .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | } |
| | | |
| | | .print-preview-content { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 400px; |
| | | } |
| | | } |
| | | |
| | | .print-page { |
| | | width: 220mm; |
| | | height: 90mm; |
| | | padding: 10mm; |
| | | margin: 0 auto; |
| | | background: white; |
| | | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| | | margin-bottom: 10px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-family: "SimSun", serif; |
| | | font-size: 10px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .header { |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .info-row { |
| | | line-height: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .table-section { |
| | | margin-bottom: 4px; |
| | | flex: 1; |
| | | |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | |
| | | th, td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 14px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | th { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-label { |
| | | text-align: right; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .footer-section { |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 20px; |
| | | justify-content: space-between; |
| | | |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | &.address-item { |
| | | .address-value { |
| | | min-width: 200px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @media print { |
| | | .app-container { |
| | | display: none; |
| | | } |
| | | |
| | | .print-page { |
| | | box-shadow: none; |
| | | margin: 0; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <pagination :total="total" layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" :limit="page.size" @pagination="paginationChange" /> |
| | | </div> |
| | | <Modal ref="modalRef" @success="handleQuery"></Modal> |
| | | <files-dia ref="filesDia"></files-dia> |
| | | <!-- <Modal ref="modalRef" @success="handleQuery"></Modal> --> |
| | | <!-- <files-dia ref="filesDia"></files-dia> --> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, reactive, toRefs, nextTick, getCurrentInstance } from 'vue' |
| | | import dayjs from "dayjs"; |
| | | import Modal from "./Modal.vue"; |
| | | import FilesDia from "./filesDia.vue"; |
| | | // import Modal from "./Modal.vue"; |
| | | // import FilesDia from "./filesDia.vue"; |
| | | import Pagination from "@/components/Pagination/index.vue"; |
| | | import {lavorIssueListPage, deleteLedger, update} from "@/api/lavorissce/ledger.js"; |
| | | import {listPage, deleteLedger, update} from "@/api/lavorissce/ledger.js"; |
| | | import {ElMessageBox, ElMessage} from "element-plus"; |
| | | const { proxy } = getCurrentInstance(); |
| | | import { getCurrentMonth } from "@/utils/util" |
| | |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const modalRef = ref(); |
| | | const filesDia = ref(); |
| | | // const filesDia = ref(); |
| | | const multipleList = ref([]); |
| | | const jidu = ref([ |
| | | { |
| | |
| | | const getList = async () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page.value }; |
| | | lavorIssueListPage(params).then(res => { |
| | | listPage(params).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | |
| | | }) |
| | | } |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (row) => { |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog( row,'æ¶å
¥') |
| | | }) |
| | | }; |
| | | // const openFilesFormDia = (row) => { |
| | | // nextTick(() => { |
| | | // filesDia.value?.openDialog( row,'æ¶å
¥') |
| | | // }) |
| | | // }; |
| | | // äºä»¶å¤ç彿° |
| | | const handleSelectionChange = (selection) => { |
| | | multipleList.value = selection; |
| | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | let num = Number(form.value.inboundQuantity) |
| | | if(num < 1 || num > currentRowNum.value){ |
| | | if(num <= 0 || num > currentRowNum.value){ |
| | | return proxy.$modal.msgWarning("请填å
¥æææ°å") |
| | | } |
| | | proxy.$refs["formRef"].validate(valid => { |
| | |
| | | warningLevel: 'ç´§æ¥', |
| | | warningThreshold: 20, |
| | | isEnabled: true, |
| | | warningTime: '2024-01-15 08:30:00', |
| | | warningTime: '2025-01-15 08:30:00', |
| | | warningDuration: 3, |
| | | lastUpdateTime: '2024-01-15 10:00:00', |
| | | expectedRefillTime: '2024-01-16 14:00:00', |
| | | expectedShortageTime: '2024-01-15 18:30:00', // ä»å¤©ä¸å6:30ç¼ºæ° |
| | | lastUpdateTime: '2025-01-15 10:00:00', |
| | | expectedRefillTime: '2025-01-16 14:00:00', |
| | | expectedShortageTime: '2025-01-15 18:30:00', // ä»å¤©ä¸å6:30ç¼ºæ° |
| | | warningRule: '彿°ä½éä½äº20%æ¶è§¦åé¢è¦' |
| | | }, |
| | | { |
| | |
| | | warningLevel: 'éè¦', |
| | | warningThreshold: 10, |
| | | isEnabled: true, |
| | | warningTime: '2024-01-14 16:20:00', |
| | | warningTime: '2025-01-14 16:20:00', |
| | | warningDuration: 2, |
| | | lastUpdateTime: '2024-01-15 09:15:00', |
| | | expectedRefillTime: '2024-01-17 09:00:00', |
| | | expectedShortageTime: '2024-01-18 12:00:00', // 3天åç¼ºæ° |
| | | lastUpdateTime: '2025-01-15 09:15:00', |
| | | expectedRefillTime: '2025-01-17 09:00:00', |
| | | expectedShortageTime: '2025-01-18 12:00:00', // 3天åç¼ºæ° |
| | | warningRule: 'å½ååè¶
è¿8MPaæ¶è§¦åé¢è¦' |
| | | }, |
| | | { |
| | |
| | | warningLevel: 'ä¸è¬', |
| | | warningThreshold: 5, |
| | | isEnabled: true, |
| | | warningTime: '2024-01-13 11:45:00', |
| | | warningTime: '2025-01-13 11:45:00', |
| | | warningDuration: 1, |
| | | lastUpdateTime: '2024-01-15 08:45:00', |
| | | expectedRefillTime: '2024-01-20 10:00:00', |
| | | expectedShortageTime: '2024-01-22 15:30:00', // 7天åç¼ºæ° |
| | | lastUpdateTime: '2025-01-15 08:45:00', |
| | | expectedRefillTime: '2025-01-20 10:00:00', |
| | | expectedShortageTime: '2025-01-22 15:30:00', // 7天åç¼ºæ° |
| | | warningRule: '彿¸©åº¦è¶
è¿60°Cæ¶è§¦åé¢è¦' |
| | | }, |
| | | { |
| | |
| | | warningLevel: 'ç´§æ¥', |
| | | warningThreshold: 15, |
| | | isEnabled: true, |
| | | warningTime: '2024-01-15 07:15:00', |
| | | warningTime: '2025-01-15 07:15:00', |
| | | warningDuration: 4, |
| | | lastUpdateTime: '2024-01-15 11:30:00', |
| | | expectedRefillTime: '2024-01-15 16:00:00', |
| | | expectedShortageTime: '2024-01-15 14:00:00', // ä»å¤©ä¸å2ç¹ç¼ºæ° |
| | | lastUpdateTime: '2025-01-15 11:30:00', |
| | | expectedRefillTime: '2025-01-15 16:00:00', |
| | | expectedShortageTime: '2025-01-15 14:00:00', // ä»å¤©ä¸å2ç¹ç¼ºæ° |
| | | warningRule: '彿£æµå°æ°ä½æ³æ¼æ¶è§¦åé¢è¦' |
| | | }, |
| | | { |
| | |
| | | warningLevel: 'éè¦', |
| | | warningThreshold: 20, |
| | | isEnabled: false, |
| | | warningTime: '2024-01-14 14:30:00', |
| | | warningTime: '2025-01-14 14:30:00', |
| | | warningDuration: 2, |
| | | lastUpdateTime: '2024-01-15 09:00:00', |
| | | expectedRefillTime: '2024-01-19 08:00:00', |
| | | expectedShortageTime: '2024-01-21 10:00:00', // 6天åç¼ºæ° |
| | | lastUpdateTime: '2025-01-15 09:00:00', |
| | | expectedRefillTime: '2025-01-19 08:00:00', |
| | | expectedShortageTime: '2025-01-21 10:00:00', // 6天åç¼ºæ° |
| | | warningRule: '彿°ä½éä½äº20%æ¶è§¦åé¢è¦' |
| | | }, |
| | | { |
| | |
| | | warningLevel: 'ä¸è¬', |
| | | warningThreshold: 8, |
| | | isEnabled: true, |
| | | warningTime: '2024-01-12 09:20:00', |
| | | warningTime: '2025-01-12 09:20:00', |
| | | warningDuration: 1, |
| | | lastUpdateTime: '2024-01-15 08:30:00', |
| | | expectedRefillTime: '2024-01-25 14:00:00', |
| | | expectedShortageTime: '2024-01-28 16:00:00', // 13天åç¼ºæ° |
| | | lastUpdateTime: '2025-01-15 08:30:00', |
| | | expectedRefillTime: '2025-01-25 14:00:00', |
| | | expectedShortageTime: '2025-01-28 16:00:00', // 13天åç¼ºæ° |
| | | warningRule: 'å½ååè¶
è¿8MPaæ¶è§¦åé¢è¦' |
| | | }, |
| | | { |
| | |
| | | warningLevel: 'éè¦', |
| | | warningThreshold: 6, |
| | | isEnabled: true, |
| | | warningTime: '2024-01-11 16:45:00', |
| | | warningTime: '2025-01-11 16:45:00', |
| | | warningDuration: 1, |
| | | lastUpdateTime: '2024-01-15 07:45:00', |
| | | expectedRefillTime: '2024-01-30 09:00:00', |
| | | expectedShortageTime: '2024-02-05 12:00:00', // 21天åç¼ºæ° |
| | | lastUpdateTime: '2025-01-15 07:45:00', |
| | | expectedRefillTime: '2025-01-30 09:00:00', |
| | | expectedShortageTime: '2025-02-05 12:00:00', // 21天åç¼ºæ° |
| | | warningRule: '彿¸©åº¦è¶
è¿60°Cæ¶è§¦åé¢è¦' |
| | | }, |
| | | { |
| | |
| | | warningLevel: 'ç´§æ¥', |
| | | warningThreshold: 12, |
| | | isEnabled: true, |
| | | warningTime: '2024-01-15 06:30:00', |
| | | warningTime: '2025-01-15 06:30:00', |
| | | warningDuration: 5, |
| | | lastUpdateTime: '2024-01-15 12:15:00', |
| | | expectedRefillTime: '2024-01-15 20:00:00', |
| | | expectedShortageTime: '2024-01-15 17:30:00', // ä»å¤©ä¸å5:30ç¼ºæ° |
| | | lastUpdateTime: '2025-01-15 12:15:00', |
| | | expectedRefillTime: '2025-01-15 20:00:00', |
| | | expectedShortageTime: '2025-01-15 17:30:00', // ä»å¤©ä¸å5:30ç¼ºæ° |
| | | warningRule: '彿£æµå°æ°ä½æ³æ¼æ¶è§¦åé¢è¦' |
| | | } |
| | | ] |
| | |
| | | <style lang='scss' scoped>
|
| | | .login {
|
| | | height: 100%;
|
| | | background-image: url("../assets/images/login-background.png");
|
| | | background-image: url("../assets/indexViews/JZYJView.png");
|
| | | background-size: cover;
|
| | | position: relative;
|
| | | }
|
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container analytics-container"> |
| | | |
| | | <!-- å
³é®ææ å¡ç --> |
| | | <el-row :gutter="20" class="metrics-cards"> |
| | | <el-col :span="6" v-for="(item, index) in keyMetrics" :key="index"> |
| | | <el-card class="metric-card" :class="item.type"> |
| | | <div class="card-content"> |
| | | <div class="card-icon"> |
| | | <el-icon :size="32"> |
| | | <component :is="item.icon" /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="card-info"> |
| | | <div class="card-number"> |
| | | <el-skeleton-item v-if="loading" variant="text" style="width: 60px; height: 32px;" /> |
| | | <span v-else>{{ item.value }}{{ item.unit }}</span> |
| | | </div> |
| | | <div class="card-label">{{ item.label }}</div> |
| | | <div class="card-trend" :class="item.trend > 0 ? 'positive' : 'negative'"> |
| | | <el-icon> |
| | | <component :is="item.trend > 0 ? 'ArrowUp' : 'ArrowDown'" /> |
| | | </el-icon> |
| | | {{ Math.abs(item.trend) }}% |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- å¾è¡¨åºå --> |
| | | <el-row :gutter="20" class="charts-section"> |
| | | <!-- åå·¥æµå¨çè¶å¿å¾ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åå·¥æµå¨çè¶å¿</span> |
| | | <el-tag type="info">è¿12个æ</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="turnoverChartRef" class="chart"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- é¨é¨äººååå¸ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>é¨é¨äººååå¸</span> |
| | | <el-tag type="success">å½åç¶æ</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="departmentChartRef" class="chart"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 第äºè¡å¾è¡¨ --> |
| | | <el-row :gutter="20" class="charts-section"> |
| | | <!-- ç¼å¶è¾¾æç --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ç¼å¶è¾¾æç</span> |
| | | <el-tag type="warning">åé¨é¨å¯¹æ¯</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="staffingChartRef" class="chart"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <!-- åå·¥æµå¤±åå åæ --> |
| | | <el-col :span="12"> |
| | | <el-card class="chart-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åå·¥æµå¤±åå åæ</span> |
| | | <el-tag type="danger">年度ç»è®¡</el-tag> |
| | | </div> |
| | | </template> |
| | | <div class="chart-container"> |
| | | <div ref="attritionChartRef" class="chart"></div> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, onUnmounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { |
| | | Refresh, |
| | | User, |
| | | TrendCharts, |
| | | DataAnalysis, |
| | | PieChart, |
| | | ArrowUp, |
| | | ArrowDown |
| | | } from '@element-plus/icons-vue' |
| | | import * as echarts from 'echarts' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | | const autoRefreshEnabled = ref(true) |
| | | const autoRefreshInterval = ref(null) |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const turnoverChartRef = ref(null) |
| | | const departmentChartRef = ref(null) |
| | | const staffingChartRef = ref(null) |
| | | const attritionChartRef = ref(null) |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | let turnoverChart = null |
| | | let departmentChart = null |
| | | let staffingChart = null |
| | | let attritionChart = null |
| | | |
| | | // èªå¨æ´æ°é´éï¼10åéï¼ |
| | | const AUTO_REFRESH_INTERVAL = 10 * 60 * 1000 |
| | | |
| | | // å
³é®ææ æ°æ® |
| | | const keyMetrics = ref([ |
| | | { |
| | | label: 'åå·¥æµå¨ç', |
| | | value: 0, |
| | | unit: '%', |
| | | icon: 'TrendCharts', |
| | | type: 'primary', |
| | | trend: 0 |
| | | }, |
| | | { |
| | | label: 'åå·¥æµå¤±ç', |
| | | value: 0, |
| | | unit: '%', |
| | | icon: 'User', |
| | | type: 'danger', |
| | | trend: 0 |
| | | }, |
| | | { |
| | | label: 'ç¼å¶è¾¾æç', |
| | | value: 0, |
| | | unit: '%', |
| | | icon: 'DataAnalysis', |
| | | type: 'success', |
| | | trend: 0 |
| | | }, |
| | | { |
| | | label: 'å¨èåå·¥æ°', |
| | | value: 0, |
| | | unit: '人', |
| | | icon: 'PieChart', |
| | | type: 'warning', |
| | | trend: 0 |
| | | } |
| | | ]) |
| | | |
| | | // é¨é¨æ°æ® |
| | | const departmentData = ref([]) |
| | | |
| | | // å¯å¨èªå¨å·æ° |
| | | const startAutoRefresh = () => { |
| | | if (autoRefreshInterval.value) { |
| | | clearInterval(autoRefreshInterval.value) |
| | | } |
| | | if (autoRefreshEnabled.value) { |
| | | autoRefreshInterval.value = setInterval(() => { |
| | | refreshData() |
| | | }, AUTO_REFRESH_INTERVAL) |
| | | } |
| | | } |
| | | |
| | | // 忢èªå¨å·æ° |
| | | const stopAutoRefresh = () => { |
| | | if (autoRefreshInterval.value) { |
| | | clearInterval(autoRefreshInterval.value) |
| | | autoRefreshInterval.value = null |
| | | } |
| | | } |
| | | |
| | | // 忢èªå¨å·æ°ç¶æ |
| | | const toggleAutoRefresh = (value) => { |
| | | if (value) { |
| | | startAutoRefresh() |
| | | } else { |
| | | stopAutoRefresh() |
| | | } |
| | | } |
| | | |
| | | // çææ¨¡ææ°æ® |
| | | const generateMockData = () => { |
| | | // çæå
³é®ææ æ°æ® |
| | | keyMetrics.value[0].value = (Math.random() * 5 + 2).toFixed(1) |
| | | keyMetrics.value[0].trend = (Math.random() * 3 - 1.5).toFixed(1) |
| | | |
| | | keyMetrics.value[1].value = (Math.random() * 3 + 1).toFixed(1) |
| | | keyMetrics.value[1].trend = (Math.random() * 2 - 1).toFixed(1) |
| | | |
| | | keyMetrics.value[2].value = (Math.random() * 15 + 85).toFixed(1) |
| | | keyMetrics.value[2].trend = (Math.random() * 3 - 1.5).toFixed(1) |
| | | |
| | | keyMetrics.value[3].value = Math.floor(Math.random() * 50 + 200) |
| | | keyMetrics.value[3].trend = (Math.random() * 2 - 1).toFixed(1) |
| | | |
| | | // çæé¨é¨æ°æ® |
| | | const departments = ['ææ¯é¨', 'éå®é¨', '人äºé¨', 'è´¢å¡é¨', 'ç产é¨', 'å¸åºé¨'] |
| | | departmentData.value = departments.map(dept => ({ |
| | | department: dept, |
| | | currentStaff: Math.floor(Math.random() * 30 + 20), |
| | | plannedStaff: Math.floor(Math.random() * 10 + 35), |
| | | staffingRate: Math.floor(Math.random() * 20 + 80), |
| | | turnoverRate: (Math.random() * 4 + 1).toFixed(1), |
| | | attritionRate: (Math.random() * 2 + 0.5).toFixed(1), |
| | | newHires: Math.floor(Math.random() * 5 + 1), |
| | | resignations: Math.floor(Math.random() * 3 + 1), |
| | | status: Math.random() > 0.7 ? 'å¼å¸¸' : 'æ£å¸¸' |
| | | })) |
| | | } |
| | | |
| | | // å·æ°æ°æ® |
| | | const refreshData = async () => { |
| | | loading.value = true |
| | | try { |
| | | // 模æAPIè°ç¨å»¶è¿ |
| | | await new Promise(resolve => setTimeout(resolve, 500)) |
| | | |
| | | generateMockData() |
| | | renderAllCharts() |
| | | |
| | | if (!autoRefreshEnabled.value) { |
| | | ElMessage.success('æ°æ®å·æ°æå') |
| | | } |
| | | } catch (error) { |
| | | console.error('å·æ°æ°æ®å¤±è´¥:', error) |
| | | ElMessage.error('å·æ°æ°æ®å¤±è´¥') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // åå§åå¾è¡¨ |
| | | const initCharts = () => { |
| | | setTimeout(() => { |
| | | if (turnoverChartRef.value) { |
| | | turnoverChart = echarts.init(turnoverChartRef.value) |
| | | } |
| | | if (departmentChartRef.value) { |
| | | departmentChart = echarts.init(departmentChartRef.value) |
| | | } |
| | | if (staffingChartRef.value) { |
| | | staffingChart = echarts.init(staffingChartRef.value) |
| | | } |
| | | if (attritionChartRef.value) { |
| | | attritionChart = echarts.init(attritionChartRef.value) |
| | | } |
| | | |
| | | renderAllCharts() |
| | | }, 300) |
| | | } |
| | | |
| | | // æ¸²æææå¾è¡¨ |
| | | const renderAllCharts = () => { |
| | | renderTurnoverChart() |
| | | renderDepartmentChart() |
| | | renderStaffingChart() |
| | | renderAttritionChart() |
| | | } |
| | | |
| | | // 渲æåå·¥æµå¨çè¶å¿å¾ |
| | | const renderTurnoverChart = () => { |
| | | if (!turnoverChart) return |
| | | |
| | | const months = ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'] |
| | | const turnoverData = months.map(() => (Math.random() * 5 + 2).toFixed(1)) |
| | | const attritionData = months.map(() => (Math.random() * 3 + 1).toFixed(1)) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'åå·¥æµå¨çè¶å¿', |
| | | left: 'center', |
| | | textStyle: { fontSize: 16, fontWeight: 'normal' } |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'cross' } |
| | | }, |
| | | legend: { |
| | | data: ['æµå¨ç', 'æµå¤±ç'], |
| | | bottom: 10 |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '15%', |
| | | top: '15%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: months, |
| | | boundaryGap: false |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { formatter: '{value}%' } |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'æµå¨ç', |
| | | type: 'line', |
| | | data: turnoverData, |
| | | smooth: true, |
| | | lineStyle: { color: '#409EFF' }, |
| | | itemStyle: { color: '#409EFF' } |
| | | }, |
| | | { |
| | | name: 'æµå¤±ç', |
| | | type: 'line', |
| | | data: attritionData, |
| | | smooth: true, |
| | | lineStyle: { color: '#F56C6C' }, |
| | | itemStyle: { color: '#F56C6C' } |
| | | } |
| | | ] |
| | | } |
| | | |
| | | turnoverChart.setOption(option) |
| | | } |
| | | |
| | | // 渲æé¨é¨äººååå¸å¾ |
| | | const renderDepartmentChart = () => { |
| | | if (!departmentChart) return |
| | | |
| | | const data = departmentData.value.map(item => ({ |
| | | name: item.department, |
| | | value: item.currentStaff |
| | | })) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'é¨é¨äººååå¸', |
| | | left: 'center', |
| | | textStyle: { fontSize: 16, fontWeight: 'normal' } |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b}: {c}人 ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left', |
| | | top: 'middle' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'äººåæ°é', |
| | | type: 'pie', |
| | | radius: ['40%', '70%'], |
| | | center: ['60%', '50%'], |
| | | data: data, |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: 'rgba(0, 0, 0, 0.5)' |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | |
| | | departmentChart.setOption(option) |
| | | } |
| | | |
| | | // 渲æç¼å¶è¾¾æçå¾ |
| | | const renderStaffingChart = () => { |
| | | if (!staffingChart) return |
| | | |
| | | const departments = departmentData.value.map(item => item.department) |
| | | const rates = departmentData.value.map(item => item.staffingRate) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'ç¼å¶è¾¾æç', |
| | | left: 'center', |
| | | textStyle: { fontSize: 16, fontWeight: 'normal' } |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'shadow' } |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '15%', |
| | | top: '15%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: departments, |
| | | axisLabel: { rotate: 45 } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { formatter: '{value}%' }, |
| | | max: 100 |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'è¾¾æç', |
| | | type: 'bar', |
| | | data: rates, |
| | | itemStyle: { |
| | | color: function(params) { |
| | | const value = params.value |
| | | if (value >= 90) return '#67C23A' |
| | | if (value >= 80) return '#E6A23C' |
| | | return '#F56C6C' |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | |
| | | staffingChart.setOption(option) |
| | | } |
| | | |
| | | // 渲æåå·¥æµå¤±åå åæå¾ |
| | | const renderAttritionChart = () => { |
| | | if (!attritionChart) return |
| | | |
| | | const reasons = ['èªèµå¾
é', 'èä¸åå±', 'å·¥ä½ç¯å¢', '个人åå ', 'å
¶ä»'] |
| | | const data = reasons.map(() => Math.floor(Math.random() * 20 + 5)) |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'åå·¥æµå¤±åå åæ', |
| | | left: 'center', |
| | | textStyle: { fontSize: 16, fontWeight: 'normal' } |
| | | }, |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b}: {c}人 ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | left: 'left', |
| | | top: 'middle' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'æµå¤±äººæ°', |
| | | type: 'pie', |
| | | radius: '50%', |
| | | center: ['60%', '50%'], |
| | | data: reasons.map((reason, index) => ({ |
| | | name: reason, |
| | | value: data[index] |
| | | })), |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: 'rgba(0, 0, 0, 0.5)' |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | |
| | | attritionChart.setOption(option) |
| | | } |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | generateMockData() |
| | | initCharts() |
| | | startAutoRefresh() |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | stopAutoRefresh() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .analytics-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: 12px; |
| | | color: white; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | color: white; |
| | | margin-bottom: 10px; |
| | | font-size: 28px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .page-header p { |
| | | color: rgba(255, 255, 255, 0.9); |
| | | font-size: 14px; |
| | | margin: 0 0 15px 0; |
| | | } |
| | | |
| | | .header-controls { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .refresh-btn { |
| | | margin-left: 20px; |
| | | } |
| | | |
| | | .metrics-cards { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .metric-card { |
| | | border-radius: 12px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | transition: all 0.3s ease; |
| | | border: none; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .metric-card:hover { |
| | | transform: translateY(-5px); |
| | | box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .metric-card.primary { |
| | | border-left: 4px solid #409EFF; |
| | | background: linear-gradient(135deg, #409EFF 0%, #36a3f7 100%); |
| | | } |
| | | |
| | | .metric-card.danger { |
| | | border-left: 4px solid #F56C6C; |
| | | background: linear-gradient(135deg, #F56C6C 0%, #f78989 100%); |
| | | } |
| | | |
| | | .metric-card.success { |
| | | border-left: 4px solid #67C23A; |
| | | background: linear-gradient(135deg, #67C23A 0%, #85ce61 100%); |
| | | } |
| | | |
| | | .metric-card.warning { |
| | | border-left: 4px solid #E6A23C; |
| | | background: linear-gradient(135deg, #E6A23C 0%, #ebb563 100%); |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .card-icon { |
| | | margin-right: 20px; |
| | | color: white; |
| | | } |
| | | |
| | | .card-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-number { |
| | | font-size: 32px; |
| | | font-weight: 600; |
| | | color: white; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .card-label { |
| | | font-size: 14px; |
| | | color: rgba(255, 255, 255, 0.9); |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .card-trend { |
| | | font-size: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .card-trend.positive { |
| | | color: #67C23A; |
| | | } |
| | | |
| | | .card-trend.negative { |
| | | color: #F56C6C; |
| | | } |
| | | |
| | | .charts-section { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .chart-card { |
| | | border-radius: 12px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | border: none; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | padding: 15px 20px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | .chart-container { |
| | | height: 350px; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .chart { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 768px) { |
| | | .analytics-container { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .page-header { |
| | | padding: 15px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .header-controls { |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .refresh-btn { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .metrics-cards .el-col { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .charts-section .el-col { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-container { |
| | | height: 300px; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 480px) { |
| | | .page-header h2 { |
| | | font-size: 20px; |
| | | } |
| | | |
| | | .card-number { |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .chart-container { |
| | | height: 250px; |
| | | } |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container scheduling-container"> |
| | | <!-- çéåºå --> |
| | | <div class="filter-section"> |
| | | <el-form :inline="true" :model="filterForm" class="filter-form"> |
| | | <el-form-item label="åå·¥å§åï¼"> |
| | | <el-input |
| | | v-model="filterForm.employeeName" |
| | | placeholder="请è¾å
¥åå·¥å§å" |
| | | clearable |
| | | style="width: 150px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="çæ¬¡ç±»åï¼"> |
| | | <el-select v-model="filterForm.shiftType" placeholder="è¯·éæ©çæ¬¡" clearable style="width: 120px"> |
| | | <el-option label="æ©ç" value="æ©ç" /> |
| | | <el-option label="ä¸ç" value="ä¸ç" /> |
| | | <el-option label="æç" value="æç" /> |
| | | <el-option label="å¤ç" value="å¤ç" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <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" |
| | | style="width: 250px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleFilter"> |
| | | <el-icon><Search /></el-icon> |
| | | çé |
| | | </el-button> |
| | | <el-button @click="resetFilter"> |
| | | <el-icon><Refresh /></el-icon> |
| | | éç½® |
| | | </el-button> |
| | | <el-button type="primary" @click="openScheduleDialog('add')"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ°å¢æç |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <!-- æçè¡¨æ ¼ --> |
| | | <div class="table-section"> |
| | | <el-table |
| | | :data="filteredScheduleList" |
| | | border |
| | | stripe |
| | | style="width: 100%" |
| | | height="calc(100vh - 18.5em)" |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" /> |
| | | <el-table-column prop="employeeName" label="åå·¥å§å" width="120" /> |
| | | <el-table-column prop="employeeId" label="å工工å·" width="100" /> |
| | | <el-table-column prop="department" label="é¨é¨" width="120" /> |
| | | <el-table-column prop="shiftType" label="çæ¬¡ç±»å" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getShiftTagType(scope.row.shiftType)"> |
| | | {{ scope.row.shiftType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="workDate" label="工使¥æ" width="120" /> |
| | | <el-table-column prop="startTime" label="å¼å§æ¶é´" width="100" /> |
| | | <el-table-column prop="endTime" label="ç»ææ¶é´" width="100" /> |
| | | <el-table-column prop="workHours" label="工使¶é¿" width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.workHours }}å°æ¶ |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStatusTagType(scope.row.status)"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" min-width="150" /> |
| | | <el-table-column label="æä½" width="200" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | type="primary" |
| | | size="small" |
| | | @click="openScheduleDialog('edit', scope.row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | @click="handleDelete(scope.row)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æ¹éæä½ --> |
| | | <div class="batch-actions" v-if="selectedRows.length > 0"> |
| | | <el-button |
| | | type="danger" |
| | | @click="handleBatchDelete" |
| | | :disabled="selectedRows.length === 0" |
| | | > |
| | | æ¹éå é¤ ({{ selectedRows.length }}) |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- æçæ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | v-model="scheduleDialog" |
| | | :title="dialogType === 'add' ? 'æ°å¢æç' : 'ç¼è¾æç'" |
| | | width="700px" |
| | | @close="closeScheduleDialog" |
| | | > |
| | | <el-form |
| | | :model="scheduleForm" |
| | | :rules="scheduleRules" |
| | | ref="scheduleFormRef" |
| | | label-width="120px" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åå·¥å§åï¼" prop="employeeName"> |
| | | <el-input v-model="scheduleForm.employeeName" placeholder="请è¾å
¥åå·¥å§å" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å工工å·ï¼" prop="employeeId"> |
| | | <el-input v-model="scheduleForm.employeeId" placeholder="请è¾å
¥å工工å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¨é¨ï¼" prop="department"> |
| | | <el-select v-model="scheduleForm.department" placeholder="è¯·éæ©é¨é¨" style="width: 100%"> |
| | | <el-option label="ææ¯é¨" value="ææ¯é¨" /> |
| | | <el-option label="éå®é¨" value="éå®é¨" /> |
| | | <el-option label="人äºé¨" value="人äºé¨" /> |
| | | <el-option label="è´¢å¡é¨" value="è´¢å¡é¨" /> |
| | | <el-option label="ç产é¨" value="ç产é¨" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="çæ¬¡ç±»åï¼" prop="shiftType"> |
| | | <el-select v-model="scheduleForm.shiftType" placeholder="è¯·éæ©çæ¬¡" style="width: 100%"> |
| | | <el-option label="æ©ç" value="æ©ç" /> |
| | | <el-option label="ä¸ç" value="ä¸ç" /> |
| | | <el-option label="æç" value="æç" /> |
| | | <el-option label="å¤ç" value="å¤ç" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="工使¥æï¼" prop="workDate"> |
| | | <el-date-picker |
| | | v-model="scheduleForm.workDate" |
| | | 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="status"> |
| | | <el-select v-model="scheduleForm.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%"> |
| | | <el-option label="已宿" value="已宿" /> |
| | | <el-option label="已确认" value="已确认" /> |
| | | <el-option label="已宿" value="已宿" /> |
| | | <el-option label="已忶" value="已忶" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼å§æ¶é´ï¼" prop="startTime"> |
| | | <el-time-picker |
| | | v-model="scheduleForm.startTime" |
| | | placeholder="éæ©å¼å§æ¶é´" |
| | | style="width: 100%" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»ææ¶é´ï¼" prop="endTime"> |
| | | <el-time-picker |
| | | v-model="scheduleForm.endTime" |
| | | placeholder="éæ©ç»ææ¶é´" |
| | | style="width: 100%" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨ï¼" prop="remark"> |
| | | <el-input |
| | | v-model="scheduleForm.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitScheduleForm">确认</el-button> |
| | | <el-button @click="closeScheduleDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, Download, Search, Refresh } from '@element-plus/icons-vue' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const scheduleDialog = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | const scheduleFormRef = ref() |
| | | |
| | | // çé表å |
| | | const filterForm = reactive({ |
| | | employeeName: '', |
| | | shiftType: '', |
| | | dateRange: [] |
| | | }) |
| | | |
| | | // æç表å |
| | | const scheduleForm = reactive({ |
| | | id: '', |
| | | employeeName: '', |
| | | employeeId: '', |
| | | department: '', |
| | | shiftType: '', |
| | | workDate: '', |
| | | startTime: '', |
| | | endTime: '', |
| | | workHours: 0, |
| | | status: '已宿', |
| | | remark: '' |
| | | }) |
| | | |
| | | // 表åéªè¯è§å |
| | | const scheduleRules = reactive({ |
| | | employeeName: [{ required: true, message: '请è¾å
¥åå·¥å§å', trigger: 'blur' }], |
| | | employeeId: [{ required: true, message: '请è¾å
¥å工工å·', trigger: 'blur' }], |
| | | department: [{ required: true, message: 'è¯·éæ©é¨é¨', trigger: 'change' }], |
| | | shiftType: [{ required: true, message: 'è¯·éæ©çæ¬¡ç±»å', trigger: 'change' }], |
| | | workDate: [{ required: true, message: 'è¯·éæ©å·¥ä½æ¥æ', trigger: 'change' }], |
| | | startTime: [{ required: true, message: 'è¯·éæ©å¼å§æ¶é´', trigger: 'change' }], |
| | | endTime: [{ required: true, message: 'è¯·éæ©ç»ææ¶é´', trigger: 'change' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | }) |
| | | |
| | | // 模ææçæ°æ® |
| | | const scheduleList = ref([ |
| | | { |
| | | id: 1, |
| | | employeeName: 'å¼ æµ·æ´', |
| | | employeeId: 'EMP001', |
| | | department: 'ææ¯é¨', |
| | | shiftType: 'æ©ç', |
| | | workDate: '2024-01-15', |
| | | startTime: '08:00', |
| | | endTime: '17:00', |
| | | workHours: 9, |
| | | status: '已宿', |
| | | remark: 'æ£å¸¸æç' |
| | | }, |
| | | { |
| | | id: 2, |
| | | employeeName: 'æè¶
', |
| | | employeeId: 'EMP002', |
| | | department: 'éå®é¨', |
| | | shiftType: 'ä¸ç', |
| | | workDate: '2024-01-15', |
| | | startTime: '14:00', |
| | | endTime: '22:00', |
| | | workHours: 8, |
| | | status: '已确认', |
| | | remark: '客æ·ä¼è®®' |
| | | }, |
| | | { |
| | | id: 3, |
| | | employeeName: 'çæ°', |
| | | employeeId: 'EMP003', |
| | | department: 'ç产é¨', |
| | | shiftType: 'æç', |
| | | workDate: '2024-01-15', |
| | | startTime: '22:00', |
| | | endTime: '06:00', |
| | | workHours: 8, |
| | | status: '已宿', |
| | | remark: 'å¤çç产' |
| | | } |
| | | ]) |
| | | |
| | | // 计ç®å±æ§ï¼çéåçæçå表 |
| | | const filteredScheduleList = computed(() => { |
| | | let result = scheduleList.value |
| | | |
| | | if (filterForm.employeeName) { |
| | | result = result.filter(item => |
| | | item.employeeName.includes(filterForm.employeeName) |
| | | ) |
| | | } |
| | | |
| | | if (filterForm.shiftType) { |
| | | result = result.filter(item => item.shiftType === filterForm.shiftType) |
| | | } |
| | | |
| | | if (filterForm.dateRange && filterForm.dateRange.length === 2) { |
| | | result = result.filter(item => { |
| | | const workDate = new Date(item.workDate) |
| | | const startDate = new Date(filterForm.dateRange[0]) |
| | | const endDate = new Date(filterForm.dateRange[1]) |
| | | return workDate >= startDate && workDate <= endDate |
| | | }) |
| | | } |
| | | |
| | | return result |
| | | }) |
| | | |
| | | // è·åçæ¬¡æ ç¾ç±»å |
| | | const getShiftTagType = (shiftType) => { |
| | | const typeMap = { |
| | | 'æ©ç': 'success', |
| | | 'ä¸ç': 'warning', |
| | | 'æç': 'info', |
| | | 'å¤ç': 'danger' |
| | | } |
| | | return typeMap[shiftType] || 'info' |
| | | } |
| | | |
| | | // è·åç¶ææ ç¾ç±»å |
| | | const getStatusTagType = (status) => { |
| | | const typeMap = { |
| | | '已宿': 'info', |
| | | '已确认': 'warning', |
| | | '已宿': 'success', |
| | | '已忶': 'danger' |
| | | } |
| | | return typeMap[status] || 'info' |
| | | } |
| | | |
| | | // çé |
| | | const handleFilter = () => { |
| | | // çéé»è¾å·²å¨è®¡ç®å±æ§ä¸å®ç° |
| | | } |
| | | |
| | | // éç½®çé |
| | | const resetFilter = () => { |
| | | filterForm.employeeName = '' |
| | | filterForm.shiftType = '' |
| | | filterForm.dateRange = [] |
| | | } |
| | | |
| | | // æå¼æçå¯¹è¯æ¡ |
| | | const openScheduleDialog = (type, data) => { |
| | | dialogType.value = type |
| | | scheduleDialog.value = true |
| | | |
| | | if (type === 'edit' && data) { |
| | | // ç¼è¾æ¨¡å¼ï¼å¤å¶æ°æ® |
| | | Object.assign(scheduleForm, { ...data }) |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ï¼é置表å |
| | | Object.keys(scheduleForm).forEach(key => { |
| | | scheduleForm[key] = '' |
| | | }) |
| | | scheduleForm.status = '已宿' |
| | | scheduleForm.workDate = new Date().toISOString().split('T')[0] |
| | | } |
| | | } |
| | | |
| | | // å
³éæçå¯¹è¯æ¡ |
| | | const closeScheduleDialog = () => { |
| | | scheduleFormRef.value?.resetFields() |
| | | scheduleDialog.value = false |
| | | } |
| | | |
| | | // 计ç®å·¥ä½æ¶é¿ |
| | | const calculateWorkHours = () => { |
| | | if (scheduleForm.startTime && scheduleForm.endTime) { |
| | | const start = new Date(`2000-01-01 ${scheduleForm.startTime}`) |
| | | const end = new Date(`2000-01-01 ${scheduleForm.endTime}`) |
| | | |
| | | if (end < start) { |
| | | // 跨天çæ
åµ |
| | | end.setDate(end.getDate() + 1) |
| | | } |
| | | |
| | | const diffMs = end - start |
| | | const diffHours = diffMs / (1000 * 60 * 60) |
| | | scheduleForm.workHours = Math.round(diffHours * 100) / 100 |
| | | } |
| | | } |
| | | |
| | | // æäº¤æç表å |
| | | const submitScheduleForm = () => { |
| | | scheduleFormRef.value.validate((valid) => { |
| | | if (valid) { |
| | | // 计ç®å·¥ä½æ¶é¿ |
| | | calculateWorkHours() |
| | | |
| | | if (dialogType.value === 'add') { |
| | | // æ°å¢ |
| | | const newSchedule = { |
| | | ...scheduleForm, |
| | | id: Date.now() // ä½¿ç¨æ¶é´æ³ä½ä¸ºä¸´æ¶ID |
| | | } |
| | | scheduleList.value.push(newSchedule) |
| | | ElMessage.success('æ°å¢æçæå') |
| | | } else { |
| | | // ç¼è¾ |
| | | const index = scheduleList.value.findIndex(item => item.id === scheduleForm.id) |
| | | if (index !== -1) { |
| | | scheduleList.value[index] = { ...scheduleForm } |
| | | ElMessage.success('ç¼è¾æçæå') |
| | | } |
| | | } |
| | | |
| | | closeScheduleDialog() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // å 餿ç |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm( |
| | | `ç¡®å®è¦å é¤ ${row.employeeName} çæçè®°å½åï¼`, |
| | | 'å é¤æç¤º', |
| | | { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | const index = scheduleList.value.findIndex(item => item.id === row.id) |
| | | if (index !== -1) { |
| | | scheduleList.value.splice(index, 1) |
| | | ElMessage.success('å 餿å') |
| | | } |
| | | }).catch(() => { |
| | | ElMessage.info('已忶å é¤') |
| | | }) |
| | | } |
| | | |
| | | // æ¹éå é¤ |
| | | const handleBatchDelete = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | ElMessage.warning('è¯·éæ©è¦å é¤çè®°å½') |
| | | return |
| | | } |
| | | |
| | | ElMessageBox.confirm( |
| | | `ç¡®å®è¦å é¤éä¸ç ${selectedRows.value.length} æ¡æçè®°å½åï¼`, |
| | | 'æ¹éå é¤æç¤º', |
| | | { |
| | | confirmButtonText: '确认', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | } |
| | | ).then(() => { |
| | | const selectedIds = selectedRows.value.map(row => row.id) |
| | | scheduleList.value = scheduleList.value.filter(item => !selectedIds.includes(item.id)) |
| | | selectedRows.value = [] |
| | | ElMessage.success('æ¹éå 餿å') |
| | | }).catch(() => { |
| | | ElMessage.info('已忶å é¤') |
| | | }) |
| | | } |
| | | |
| | | // éæ©ååäºä»¶ |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection |
| | | } |
| | | |
| | | // ç嬿¶é´ååï¼èªå¨è®¡ç®å·¥ä½æ¶é¿ |
| | | const watchTimeChange = () => { |
| | | if (scheduleForm.startTime && scheduleForm.endTime) { |
| | | calculateWorkHours() |
| | | } |
| | | } |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | // 页é¢åå§å |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .scheduling-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: 12px; |
| | | color: white; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | color: white; |
| | | margin-bottom: 10px; |
| | | font-size: 28px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .page-header p { |
| | | color: rgba(255, 255, 255, 0.9); |
| | | font-size: 14px; |
| | | margin: 0 0 15px 0; |
| | | } |
| | | |
| | | .header-controls { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .filter-section { |
| | | background: white; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .filter-form { |
| | | margin: 0; |
| | | } |
| | | |
| | | .table-section { |
| | | background: white; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .batch-actions { |
| | | background: white; |
| | | padding: 15px 20px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.el-form-item__label) { |
| | | font-weight: 500; |
| | | color: #303133; |
| | | } |
| | | |
| | | :deep(.el-input__wrapper) { |
| | | box-shadow: 0 0 0 1px #dcdfe6 inset; |
| | | } |
| | | |
| | | :deep(.el-input__wrapper:hover) { |
| | | box-shadow: 0 0 0 1px #c0c4cc inset; |
| | | } |
| | | |
| | | :deep(.el-input__wrapper.is-focus) { |
| | | box-shadow: 0 0 0 1px #409eff inset; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 768px) { |
| | | .scheduling-container { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .page-header { |
| | | padding: 15px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | font-size: 24px; |
| | | } |
| | | |
| | | .header-controls { |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .filter-form .el-form-item { |
| | | margin-bottom: 10px; |
| | | } |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container self-service-container"> |
| | | |
| | | <!-- åè½å¯¼èªå¡ç --> |
| | | <el-row :gutter="20" class="nav-cards"> |
| | | <el-col :span="6" v-for="(item, index) in navItems" :key="index"> |
| | | <el-card class="nav-card" @click="handleNavClick(item.type)"> |
| | | <div class="nav-content"> |
| | | <el-icon :size="40" class="nav-icon"> |
| | | <component :is="item.icon" /> |
| | | </el-icon> |
| | | <h3>{{ item.title }}</h3> |
| | | <p>{{ item.desc }}</p> |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | | <div class="main-content"> |
| | | <!-- èå¤è®°å½ --> |
| | | <el-card v-if="currentView === 'attendance'" class="content-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>个人èå¤è®°å½</span> |
| | | <el-button type="primary" @click="addAttendanceRecord">æ°å¢è®°å½</el-button> |
| | | </div> |
| | | </template> |
| | | <el-table :data="attendanceData" style="width: 100%"> |
| | | <el-table-column prop="date" label="æ¥æ" /> |
| | | <el-table-column prop="checkIn" label="ç¾å°æ¶é´" /> |
| | | <el-table-column prop="checkOut" label="ç¾éæ¶é´" /> |
| | | <el-table-column prop="workHours" label="工使¶é¿" width="100" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'æ£å¸¸' ? 'success' : 'danger'"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="150"> |
| | | <template #default="scope"> |
| | | <el-button size="small" @click="editAttendanceRecord(scope.row)">ç¼è¾</el-button> |
| | | <el-button size="small" type="danger" @click="deleteAttendanceRecord(scope.$index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- èªèµå --> |
| | | <el-card v-if="currentView === 'salary'" class="content-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>èªèµåæ¥è¯¢</span> |
| | | <el-date-picker v-model="salaryMonth" type="month" placeholder="éæ©æä»½" /> |
| | | </div> |
| | | </template> |
| | | <el-table :data="salaryData" style="width: 100%"> |
| | | <el-table-column prop="month" label="æä»½" /> |
| | | <el-table-column prop="basicSalary" label="åºæ¬å·¥èµ" /> |
| | | <el-table-column prop="bonus" label="å¥é" /> |
| | | <el-table-column prop="deduction" label="æ£æ¬¾" /> |
| | | <el-table-column prop="total" label="å®åå·¥èµ" /> |
| | | <el-table-column prop="status" label="ç¶æ" > |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === '已忾' ? 'success' : 'warning'"> |
| | | {{ scope.row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- åæç³è¯· --> |
| | | <el-card v-if="currentView === 'leave'" class="content-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>åæç³è¯·ç®¡ç</span> |
| | | <el-button type="primary" @click="showLeaveDialog = true">ç³è¯·åæ</el-button> |
| | | </div> |
| | | </template> |
| | | <el-table :data="leaveData" style="width: 100%"> |
| | | <el-table-column prop="type" label="åæç±»å" /> |
| | | <el-table-column prop="startDate" label="å¼å§æ¥æ" /> |
| | | <el-table-column prop="endDate" label="ç»ææ¥æ" /> |
| | | <el-table-column prop="days" label="天æ°" width="80" /> |
| | | <el-table-column prop="reason" 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 label="æä½" width="150"> |
| | | <template #default="scope"> |
| | | <el-button size="small" @click="editLeaveRecord(scope.row)">ç¼è¾</el-button> |
| | | <el-button size="small" type="danger" @click="deleteLeaveRecord(scope.$index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- ä¸ªäººä¿¡æ¯ --> |
| | | <el-card v-if="currentView === 'profile'" class="content-card"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>个人信æ¯ç»´æ¤</span> |
| | | <el-button type="primary" @click="editProfile = true">ç¼è¾ä¿¡æ¯</el-button> |
| | | </div> |
| | | </template> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="å§å">{{ profile.name }}</el-descriptions-item> |
| | | <el-descriptions-item label="å·¥å·">{{ profile.employeeId }}</el-descriptions-item> |
| | | <el-descriptions-item label="é¨é¨">{{ profile.department }}</el-descriptions-item> |
| | | <el-descriptions-item label="èä½">{{ profile.position }}</el-descriptions-item> |
| | | <el-descriptions-item label="å
¥èæ¥æ">{{ profile.hireDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="èç³»çµè¯">{{ profile.phone }}</el-descriptions-item> |
| | | <el-descriptions-item label="é®ç®±">{{ profile.email }}</el-descriptions-item> |
| | | <el-descriptions-item label="å°å">{{ profile.address }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- åæç³è¯·å¼¹çª --> |
| | | <el-dialog v-model="showLeaveDialog" title="ç³è¯·åæ" width="500px"> |
| | | <el-form :model="leaveForm" label-width="100px"> |
| | | <el-form-item label="åæç±»å"> |
| | | <el-select v-model="leaveForm.type" placeholder="è¯·éæ©åæç±»å"> |
| | | <el-option label="å¹´å" value="å¹´å" /> |
| | | <el-option label="ç
å" value="ç
å" /> |
| | | <el-option label="è°ä¼" value="è°ä¼" /> |
| | | <el-option label="äºå" value="äºå" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="å¼å§æ¥æ"> |
| | | <el-date-picker v-model="leaveForm.startDate" type="date" placeholder="éæ©å¼å§æ¥æ" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç»ææ¥æ"> |
| | | <el-date-picker v-model="leaveForm.endDate" type="date" placeholder="éæ©ç»ææ¥æ" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·åå "> |
| | | <el-input v-model="leaveForm.reason" type="textarea" rows="3" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="showLeaveDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitLeaveApplication">æäº¤ç³è¯·</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ°å¢èå¤è®°å½å¼¹çª --> |
| | | <el-dialog v-model="showAttendanceDialog" title="æ°å¢èå¤è®°å½" width="500px"> |
| | | <el-form :model="attendanceForm" :rules="attendanceRules" ref="attendanceFormRef" label-width="100px"> |
| | | <el-form-item label="æ¥æ" prop="date"> |
| | | <el-date-picker v-model="attendanceForm.date" type="date" placeholder="éæ©æ¥æ" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¾å°æ¶é´" prop="checkIn"> |
| | | <el-time-picker v-model="attendanceForm.checkIn" placeholder="éæ©ç¾å°æ¶é´" format="HH:mm" value-format="HH:mm" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¾éæ¶é´" prop="checkOut"> |
| | | <el-time-picker v-model="attendanceForm.checkOut" placeholder="éæ©ç¾éæ¶é´" format="HH:mm" value-format="HH:mm" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ" prop="status"> |
| | | <el-select v-model="attendanceForm.status" placeholder="è¯·éæ©ç¶æ"> |
| | | <el-option label="æ£å¸¸" value="æ£å¸¸" /> |
| | | <el-option label="è¿å°" value="è¿å°" /> |
| | | <el-option label="æ©é" value="æ©é" /> |
| | | <el-option label="缺å¤" value="缺å¤" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="showAttendanceDialog = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitAttendance">æäº¤</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 个人信æ¯ç¼è¾å¼¹çª --> |
| | | <el-dialog v-model="editProfile" title="ç¼è¾ä¸ªäººä¿¡æ¯" width="500px"> |
| | | <el-form :model="profileForm" label-width="100px"> |
| | | <el-form-item label="å§å"> |
| | | <el-input v-model="profileForm.name" /> |
| | | </el-form-item> |
| | | <el-form-item label="èç³»çµè¯"> |
| | | <el-input v-model="profileForm.phone" /> |
| | | </el-form-item> |
| | | <el-form-item label="é®ç®±"> |
| | | <el-input v-model="profileForm.email" /> |
| | | </el-form-item> |
| | | <el-form-item label="å°å"> |
| | | <el-input v-model="profileForm.address" type="textarea" rows="2" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="editProfile = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="saveProfile">ä¿å</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, watch } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { |
| | | Calendar, |
| | | Money, |
| | | Clock, |
| | | User |
| | | } from '@element-plus/icons-vue' |
| | | |
| | | // å½åè§å¾ |
| | | const currentView = ref('attendance') |
| | | |
| | | // 导èªé¡¹ |
| | | const navItems = [ |
| | | { type: 'attendance', title: 'èå¤è®°å½', desc: 'æ¥è¯¢ä¸ªäººèå¤ä¿¡æ¯', icon: 'Calendar' }, |
| | | { type: 'salary', title: 'èªèµå', desc: 'æ¥çèªèµåæ¾è®°å½', icon: 'Money' }, |
| | | { type: 'leave', title: 'åæç³è¯·', desc: 'å¨çº¿ç³è¯·åç±»åæ', icon: 'Clock' }, |
| | | { type: 'profile', title: '个人信æ¯', desc: 'ç»´æ¤ä¸ªäººåºæ¬ä¿¡æ¯', icon: 'User' } |
| | | ] |
| | | |
| | | // è夿°æ® |
| | | const attendanceData = ref([ |
| | | { date: '2024-01-15', checkIn: '09:00', checkOut: '18:00', workHours: '9å°æ¶', status: 'æ£å¸¸' }, |
| | | { date: '2024-01-16', checkIn: '08:55', checkOut: '18:05', workHours: '9å°æ¶10å', status: 'æ£å¸¸' }, |
| | | { date: '2024-01-17', checkIn: '09:15', checkOut: '18:00', workHours: '8å°æ¶45å', status: 'è¿å°' } |
| | | ]) |
| | | |
| | | // èªèµæ°æ® |
| | | const salaryData = ref([ |
| | | { month: '2024-01', basicSalary: 8000, bonus: 1000, deduction: 200, total: 8800, status: '已忾' }, |
| | | { month: '2023-12', basicSalary: 8000, bonus: 800, deduction: 150, total: 8650, status: '已忾' } |
| | | ]) |
| | | |
| | | // åææ°æ® |
| | | const leaveData = ref([ |
| | | { type: 'å¹´å', startDate: '2024-02-01', endDate: '2024-02-03', days: 3, reason: 'æ¥èåå®¶', status: 'å·²éè¿' }, |
| | | { type: 'ç
å', startDate: '2024-01-20', endDate: '2024-01-21', days: 2, reason: 'æååç§', status: '审æ¹ä¸' } |
| | | ]) |
| | | |
| | | // ä¸ªäººä¿¡æ¯ |
| | | const profile = ref({ |
| | | name: 'å¼ æµ·æ´', |
| | | employeeId: 'EMP001', |
| | | department: 'ææ¯é¨', |
| | | position: '软件工ç¨å¸', |
| | | hireDate: '2023-03-01', |
| | | phone: '13800138000', |
| | | email: 'zhangsan@company.com', |
| | | address: 'åäº¬å¸æé³åºxxxè¡éxxxå·' |
| | | }) |
| | | |
| | | // å¼¹çªæ§å¶ |
| | | const showLeaveDialog = ref(false) |
| | | const editProfile = ref(false) |
| | | const salaryMonth = ref('') |
| | | |
| | | // è¡¨åæ°æ® |
| | | const leaveForm = reactive({ |
| | | type: '', |
| | | startDate: '', |
| | | endDate: '', |
| | | reason: '' |
| | | }) |
| | | |
| | | const profileForm = reactive({ |
| | | name: '', |
| | | phone: '', |
| | | email: '', |
| | | address: '' |
| | | }) |
| | | |
| | | // æ°å¢èå¤è®°å½ï¼å¼¹çªä¸è¡¨å |
| | | const showAttendanceDialog = ref(false) |
| | | const attendanceFormRef = ref(null) |
| | | const attendanceForm = reactive({ |
| | | date: '', |
| | | checkIn: '', |
| | | checkOut: '', |
| | | status: 'æ£å¸¸' |
| | | }) |
| | | const attendanceRules = { |
| | | date: [{ required: true, message: 'è¯·éæ©æ¥æ', trigger: 'change' }], |
| | | checkIn: [{ required: true, message: 'è¯·éæ©ç¾å°æ¶é´', trigger: 'change' }], |
| | | checkOut: [{ required: true, message: 'è¯·éæ©ç¾éæ¶é´', trigger: 'change' }], |
| | | status: [{ required: true, message: 'è¯·éæ©ç¶æ', trigger: 'change' }] |
| | | } |
| | | |
| | | // å¤ç导èªç¹å» |
| | | const handleNavClick = (type) => { |
| | | currentView.value = type |
| | | } |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = (status) => { |
| | | const types = { |
| | | 'å·²éè¿': 'success', |
| | | '审æ¹ä¸': 'warning', |
| | | 'å·²æç»': 'danger' |
| | | } |
| | | return types[status] || 'info' |
| | | } |
| | | |
| | | // æ°å¢èå¤è®°å½ï¼æå¼å¼¹çªå¹¶é¢å¡«é»è®¤å¼ï¼ |
| | | const addAttendanceRecord = () => { |
| | | attendanceForm.date = new Date().toISOString().split('T')[0] |
| | | attendanceForm.checkIn = '09:00' |
| | | attendanceForm.checkOut = '18:00' |
| | | attendanceForm.status = 'æ£å¸¸' |
| | | showAttendanceDialog.value = true |
| | | } |
| | | |
| | | // 计ç®å·¥æ¶ |
| | | const computeWorkHours = (inStr, outStr) => { |
| | | const [inH, inM] = inStr.split(':').map(n => parseInt(n, 10)) |
| | | const [outH, outM] = outStr.split(':').map(n => parseInt(n, 10)) |
| | | const inMin = inH * 60 + inM |
| | | const outMin = outH * 60 + outM |
| | | const diff = Math.max(0, outMin - inMin) |
| | | const h = Math.floor(diff / 60) |
| | | const m = diff % 60 |
| | | return m === 0 ? `${h}å°æ¶` : `${h}å°æ¶${m}å` |
| | | } |
| | | |
| | | // æäº¤æ°å¢èå¤è®°å½ |
| | | const submitAttendance = () => { |
| | | if (!attendanceFormRef.value) return |
| | | attendanceFormRef.value.validate((valid) => { |
| | | if (!valid) return |
| | | const workHours = computeWorkHours(attendanceForm.checkIn, attendanceForm.checkOut) |
| | | const newRecord = { |
| | | date: attendanceForm.date, |
| | | checkIn: attendanceForm.checkIn, |
| | | checkOut: attendanceForm.checkOut, |
| | | workHours, |
| | | status: attendanceForm.status |
| | | } |
| | | attendanceData.value.unshift(newRecord) |
| | | showAttendanceDialog.value = false |
| | | // é置表å |
| | | attendanceForm.date = '' |
| | | attendanceForm.checkIn = '' |
| | | attendanceForm.checkOut = '' |
| | | attendanceForm.status = 'æ£å¸¸' |
| | | ElMessage.success('èå¤è®°å½æ·»å æå') |
| | | }) |
| | | } |
| | | |
| | | // ç¼è¾èå¤è®°å½ |
| | | const editAttendanceRecord = (row) => { |
| | | ElMessage.info('ç¼è¾åè½å¼åä¸...') |
| | | } |
| | | |
| | | // å é¤èå¤è®°å½ |
| | | const deleteAttendanceRecord = (index) => { |
| | | attendanceData.value.splice(index, 1) |
| | | ElMessage.success('èå¤è®°å½å 餿å') |
| | | } |
| | | |
| | | // ç¼è¾åæè®°å½ |
| | | const editLeaveRecord = (row) => { |
| | | ElMessage.info('ç¼è¾åè½å¼åä¸...') |
| | | } |
| | | |
| | | // å é¤åæè®°å½ |
| | | const deleteLeaveRecord = (index) => { |
| | | leaveData.value.splice(index, 1) |
| | | ElMessage.success('åæè®°å½å 餿å') |
| | | } |
| | | |
| | | // æäº¤åæç³è¯· |
| | | const submitLeaveApplication = () => { |
| | | if (!leaveForm.type || !leaveForm.startDate || !leaveForm.endDate || !leaveForm.reason) { |
| | | ElMessage.warning('请填å宿´ä¿¡æ¯') |
| | | return |
| | | } |
| | | |
| | | const newLeave = { |
| | | type: leaveForm.type, |
| | | startDate: leaveForm.startDate, |
| | | endDate: leaveForm.endDate, |
| | | days: 3, // ç®åè®¡ç® |
| | | reason: leaveForm.reason, |
| | | status: '审æ¹ä¸' |
| | | } |
| | | |
| | | leaveData.value.unshift(newLeave) |
| | | showLeaveDialog.value = false |
| | | |
| | | // é置表å |
| | | Object.keys(leaveForm).forEach(key => { |
| | | leaveForm[key] = '' |
| | | }) |
| | | |
| | | ElMessage.success('åæç³è¯·æäº¤æå') |
| | | } |
| | | |
| | | // ä¿åä¸ªäººä¿¡æ¯ |
| | | const saveProfile = () => { |
| | | Object.assign(profile.value, profileForm) |
| | | editProfile.value = false |
| | | ElMessage.success('个人信æ¯ä¿åæå') |
| | | } |
| | | |
| | | // åå§å个人信æ¯è¡¨å |
| | | const initProfileForm = () => { |
| | | Object.assign(profileForm, { |
| | | name: profile.value.name, |
| | | phone: profile.value.phone, |
| | | email: profile.value.email, |
| | | address: profile.value.address |
| | | }) |
| | | } |
| | | |
| | | // çå¬ç¼è¾ä¸ªäººä¿¡æ¯å¼¹çª |
| | | watch(editProfile, (val) => { |
| | | if (val) { |
| | | initProfileForm() |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .self-service-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: 12px; |
| | | color: white; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | color: white; |
| | | margin-bottom: 10px; |
| | | font-size: 28px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .page-header p { |
| | | color: rgba(255, 255, 255, 0.9); |
| | | font-size: 14px; |
| | | margin: 0; |
| | | } |
| | | |
| | | .nav-cards { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .nav-card { |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | border-radius: 12px; |
| | | border: none; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .nav-card:hover { |
| | | transform: translateY(-5px); |
| | | box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .nav-content { |
| | | text-align: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .nav-icon { |
| | | color: #409EFF; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .nav-content h3 { |
| | | margin: 0 0 10px 0; |
| | | color: #303133; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .nav-content p { |
| | | margin: 0; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .main-content { |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .content-card { |
| | | border-radius: 12px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | border: none; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | /* ååºå¼è®¾è®¡ */ |
| | | @media (max-width: 768px) { |
| | | .self-service-container { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .nav-cards .el-col { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | font-size: 24px; |
| | | } |
| | | } |
| | | </style> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="éè´è®¢åå·ï¼"> |
| | | <el-input v-model="searchForm.orderNo" placeholder="请è¾å
¥è®¢åå·" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="searchForm.supplierName" placeholder="请è¾å
¥ä¾åºååç§°" clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <el-card class="table-card" shadow="never"> |
| | | <div class="table-header"> |
| | | <el-button type="primary" @click="openDialog('add')">æ°å¢å°è´§</el-button> |
| | | <el-button type="success" @click="handleBatchReceive">æ¹éæ¶è´§</el-button> |
| | | <el-button type="danger" @click="handleBatchDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | |
| | | <el-table :data="tableData" border v-loading="loading" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="å°è´§åå·" prop="arrivalNo" width="180" /> |
| | | <el-table-column label="éè´è®¢åå·" prop="orderNo" width="180" /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column label="å°è´§ç¶æ" prop="status" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å°è´§æ°é" prop="arrivalQuantity" width="100" /> |
| | | <el-table-column label="å°è´§æ¶é´" prop="arrivalTime" width="180" /> |
| | | <el-table-column label="æä½" width="200" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openDialog('edit', row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleReceive(row)">æ¶è´§</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? 'æ°å¢å°è´§' : 'ç¼è¾å°è´§'" width="600px"> |
| | | <el-form :model="formData" label-width="120px"> |
| | | <el-form-item label="éè´è®¢åå·"> |
| | | <el-select v-model="formData.orderNo" placeholder="è¯·éæ©éè´è®¢å" style="width: 100%"> |
| | | <el-option label="PO20241201001" value="PO20241201001" /> |
| | | <el-option label="PO20241201002" value="PO20241201002" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºååç§°"> |
| | | <el-input v-model="formData.supplierName" placeholder="ä¾åºååç§°" readonly /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | |
| | | const loading = ref(false) |
| | | const dialogVisible = ref(false) |
| | | const dialogType = ref('add') |
| | | const selectedRows = ref([]) |
| | | |
| | | const searchForm = reactive({ |
| | | orderNo: '', |
| | | supplierName: '' |
| | | }) |
| | | |
| | | const formData = reactive({ |
| | | orderNo: '', |
| | | supplierName: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | arrivalNo: 'AR20241201001', |
| | | orderNo: 'PO20241201001', |
| | | supplierName: 'ä¾åºåA', |
| | | status: 'received', |
| | | arrivalQuantity: 250, |
| | | arrivalTime: '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> |
| | |
| | | }, |
| | | }, |
| | | { |
| | | label: "å·²å¼ç¥¨éé¢(å
)", |
| | | label: "å·²æ¥ç¥¨éé¢(å
)", |
| | | prop: "receiptPaymentAmount", |
| | | width:200, |
| | | formatData: (val) => { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | label: "å¾
å¼ç¥¨éé¢(å
)", |
| | | label: "å¾
æ¥ç¥¨éé¢(å
)", |
| | | prop: "unReceiptPaymentAmount", |
| | | width:200, |
| | | formatData: (val) => { |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»è®°äººï¼" prop="registrant"> |
| | | <el-input |
| | | v-model="form.registrant" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¥æï¼" prop="paymentDate"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.paymentDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¥æï¼" prop="paymentDate"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.paymentDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»è®°äººï¼" prop="registrant"> |
| | | <el-input |
| | | v-model="form.registrant" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»è®°æ¥æï¼" prop="registrationtDate"> |
| | | <el-input |
| | |
| | | const isShowSummarySon = ref(true); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "éè´ååå·", |
| | | prop: "purchaseContractNumber", |
| | | }, |
| | | { |
| | | label: "仿¬¾æ¥æ", |
| | | prop: "paymentDate", |
| | | }, |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | defineOptions({ |
| | | name: "æ¥ç¥¨å°è´¦è¡¨å", |
| | | }); |
| | | |
| | | const temFutureTickets = ref(0) |
| | | const { form, resetForm } = useFormData({ |
| | | id: undefined, |
| | | purchaseContractNumber: undefined, // éè´ååå· |
| | |
| | | form.ticketsAmount = data.ticketsAmount.toFixed(2); |
| | | form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice; |
| | | form.futureTickets = data.futureTickets; |
| | | temFutureTickets.value = data.futureTickets; |
| | | } |
| | | }; |
| | | |
| | |
| | | proxy.$modal.msgWarning("å«ç¨åä»·ä¸è½ä¸ºé¶ææªå®ä¹"); |
| | | return; |
| | | } |
| | | |
| | | if (Number(form.ticketsNum) > Number(form.futureTickets)) { |
| | | if (Number(form.ticketsNum) > Number(temFutureTickets.value)) { |
| | | proxy.$modal.msgWarning("å¼ç¥¨æ°ä¸å¾å¤§äºæªå¼ç¥¨æ°"); |
| | | form.ticketsNum = form.futureTickets |
| | | return; |
| | | form.ticketsNum = temFutureTickets.value |
| | | } |
| | | |
| | | // ç¡®ä¿æææ°å¼é½è½¬æ¢ä¸ºæ°åç±»åè¿è¡è®¡ç® |
| | | const ticketsAmount = Number(val) * Number(form.taxInclusiveUnitPrice); |
| | | const futureTickets = Number(form.futureTickets) - Number(val); |
| | | const ticketsAmount = Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice); |
| | | const futureTickets = Number(temFutureTickets.value) - Number(form.ticketsNum); |
| | | form.futureTickets = Number(futureTickets.toFixed(2)); |
| | | form.ticketsAmount = Number(ticketsAmount.toFixed(2)); |
| | | }; |
| | |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "客æ·åç§°", |
| | | prop: "customerName", |
| | | label: "项ç®åç§°", |
| | | prop: "projectName", |
| | | width: 240, |
| | | }, |
| | | { |
| | | label: "ä¾åºååç§°", |
| | | prop: "supplierName", |
| | | width: 240, |
| | | }, |
| | | { |
| | | label: "产å大类", |
| | | prop: "productCategory", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | |
| | | }, |
| | | }, |
| | | { |
| | | label: "å¼ç¥¨æ¥æ", |
| | | label: "æ¬æ¬¡æ¥ç¥¨æ°", |
| | | prop: "ticketsNum", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: "æ¥ç¥¨æ¥æ", |
| | | prop: "createdAt", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: "å¼ç¥¨éé¢", |
| | | label: "æ¥ç¥¨éé¢(å
)", |
| | | prop: "ticketsAmount", |
| | | width: 200, |
| | | formatData: (cell) => { |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | emergencyRecords: [ |
| | | { |
| | | id: 'EM001', |
| | | time: '2024-01-15 14:35:12', |
| | | time: '2025-01-15 14:35:12', |
| | | location: 'å¨ç½T-003', |
| | | type: 'ç²ç·è¶
æ ', |
| | | status: 'resolved', |
| | |
| | | }, |
| | | { |
| | | id: 'EM002', |
| | | time: '2024-01-15 14:35:15', |
| | | time: '2025-01-15 14:35:15', |
| | | location: 'å缩æºC-002', |
| | | type: 'ç¡«åæ°¢è¶
æ ', |
| | | status: 'processing', |
| | |
| | | <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> |
¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div></div> |
| | | </template> |
| | | <script setup> |
| | | import useUserStore from '@/store/modules/user' |
| | | const userStore = useUserStore() |
| | | let { proxy } = getCurrentInstance() |
| | | function goLogin() { |
| | | userStore.TideLogin({code : proxy.$route.query.code}).then(() => { |
| | | proxy.$router.push({ path: redirect || "/" }).catch(() => { }); |
| | | }) |
| | | } |
| | | goLogin() |
| | | </script> |
| | | <style scoped></style> |
| | |
| | | const baseUrl =
|
| | | VITE_APP_ENV == "development"
|
| | | ? "http://114.132.189.42:8089" // å¼åç¯å¢å端æ¥å£
|
| | | : "http://114.132.189.42:7003"; // ç产ç¯å¢å端æ¥å£
|
| | | : "http://114.132.189.42:1234"; // ç产ç¯å¢å端æ¥å£
|
| | |
|
| | | return {
|
| | | // é¨ç½²ç产ç¯å¢åå¼åç¯å¢ä¸çURLã
|