| | |
| | | <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: '2024-01-15 09:30:00', |
| | | updateTime: '2024-01-20 14:20:00' |
| | | }, |
| | | { |
| | | id: 'SP002', |
| | | name: 'æ ·åB-002', |
| | | type: '塿å¶å', |
| | | status: '已宿', |
| | | progress: 100, |
| | | createTime: '2024-01-10 10:15:00', |
| | | updateTime: '2024-01-18 16:45:00' |
| | | }, |
| | | { |
| | | id: 'SP003', |
| | | name: 'æ ·åC-003', |
| | | type: 'çµåå
ä»¶', |
| | | status: 'å¾
æ£æµ', |
| | | progress: 0, |
| | | createTime: '2024-01-22 08:45:00', |
| | | updateTime: '2024-01-22 08:45:00' |
| | | }, |
| | | { |
| | | id: 'EQ001', |
| | | name: 'æ£æµè®¾å¤A', |
| | | type: 'å
谱仪', |
| | | status: '使ç¨ä¸', |
| | | progress: 60, |
| | | createTime: '2024-01-05 14:20:00', |
| | | updateTime: '2024-01-20 11:30:00' |
| | | }, |
| | | { |
| | | id: 'EQ002', |
| | | name: 'æ£æµè®¾å¤B', |
| | | type: 'æ¾å¾®é', |
| | | status: '空é²', |
| | | progress: 0, |
| | | createTime: '2024-01-08 16:10:00', |
| | | updateTime: '2024-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: '2024-01-15 09:30:00', |
| | | updateTime: '2024-01-20 14:20:00' |
| | | }, |
| | | { |
| | | id: 'SP002', |
| | | name: 'æ ·åB-002', |
| | | type: '塿å¶å', |
| | | status: '已宿', |
| | | progress: 100, |
| | | createTime: '2024-01-10 10:15:00', |
| | | updateTime: '2024-01-18 16:45:00' |
| | | }, |
| | | { |
| | | id: 'SP003', |
| | | name: 'æ ·åC-003', |
| | | type: 'çµåå
ä»¶', |
| | | status: 'å¾
æ£æµ', |
| | | progress: 0, |
| | | createTime: '2024-01-22 08:45:00', |
| | | updateTime: '2024-01-22 08:45:00' |
| | | }, |
| | | { |
| | | id: 'EQ001', |
| | | name: 'æ£æµè®¾å¤A', |
| | | type: 'å
谱仪', |
| | | status: '使ç¨ä¸', |
| | | progress: 60, |
| | | createTime: '2024-01-05 14:20:00', |
| | | updateTime: '2024-01-20 11:30:00' |
| | | }, |
| | | { |
| | | id: 'EQ002', |
| | | name: 'æ£æµè®¾å¤B', |
| | | type: 'æ¾å¾®é', |
| | | status: '空é²', |
| | | progress: 0, |
| | | createTime: '2024-01-08 16:10:00', |
| | | updateTime: '2024-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> |