| | |
| | | <script setup lang="ts"> |
| | | import { computed, ref } from 'vue' |
| | | import { computed, onMounted, ref } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { use } from 'echarts/core' |
| | | import { CanvasRenderer } from 'echarts/renderers' |
| | |
| | | import { GridComponent, TooltipComponent } from 'echarts/components' |
| | | import VChart from 'vue-echarts' |
| | | import { useTests, type TestRecord } from '@/stores/tests' |
| | | import { useTodos } from '@/stores/todos' |
| | | import { getTestList } from '@/api/tests' |
| | | import { getBatteryGroupList } from '@/api/batteryGroups' |
| | | |
| | | use([CanvasRenderer, BarChart, GridComponent, TooltipComponent]) |
| | | |
| | |
| | | const chartPeriod = ref('week') |
| | | const router = useRouter() |
| | | const { records } = useTests() |
| | | const { records: todoRecords } = useTodos() |
| | | onMounted(async () => { |
| | | try { |
| | | const [tests, groups] = await Promise.all([ |
| | | getTestList({ pageNum: 1, pageSize: 1000 }), |
| | | getBatteryGroupList({ pageNum: 1, pageSize: 1000 }), |
| | | ]) |
| | | records.value = tests.rows |
| | | // 首页的电池组总数来自后端列表,避免用页面种子推断业务事实。 |
| | | batteryGroupCount.value = groups.total |
| | | } catch { |
| | | // 页面保留当前已加载数据,错误提示由全局请求层处理。 |
| | | } |
| | | }) |
| | | const batteryGroupCount = ref(0) |
| | | const testFilterVisible = ref(false) |
| | | const testType = ref('全部') |
| | | const testTypeDraft = ref('全部') |
| | |
| | | const runningTestCount = computed(() => records.value.filter(item => item.status === '检测中').length) |
| | | const pendingTestCount = computed(() => records.value.filter(item => item.status === '待测试').length) |
| | | |
| | | const cabinetStatusSummary = [ |
| | | { label: '在线', value: 18, color: '#23865e' }, |
| | | { label: '离线', value: 18, color: '#8795a3' }, |
| | | { label: '待测试', value: 8, color: '#b77a24' }, |
| | | { label: '进行中', value: 12, color: '#2188f5' }, |
| | | { label: '暂停', value: 0, color: '#b34f4f' }, |
| | | ] |
| | | const cabinetStatusSummary = computed(() => [ |
| | | { label: '在线', value: cabinets.filter(item => item.status === '在线').length, color: '#23865e' }, |
| | | { label: '离线', value: cabinets.filter(item => item.status === '离线').length, color: '#8795a3' }, |
| | | { label: '待测试', value: records.value.filter(item => item.status === '待测试').length, color: '#b77a24' }, |
| | | { label: '进行中', value: records.value.filter(item => item.status === '检测中').length, color: '#2188f5' }, |
| | | { label: '暂停', value: records.value.filter(item => item.status === '暂停').length, color: '#b34f4f' }, |
| | | ]) |
| | | |
| | | const todoList = [ |
| | | { title: '高温循环测试', time: '09:14 · 待处理', priority: 'P1' }, |
| | | { title: '电芯容量测试', time: '10:18 · 待处理', priority: 'P2' }, |
| | | { title: '低温充放电测试', time: '11:06 · 待处理', priority: 'P1' }, |
| | | { title: 'A-03 设备校准', time: '12:00 · 待处理', priority: 'P2' }, |
| | | { title: '数据修订复核', time: '14:05 · 待处理', priority: 'P1' }, |
| | | { title: '异常记录确认', time: '14:28 · 待处理', priority: 'P1' }, |
| | | ] |
| | | const todoList = computed(() => todoRecords.value.filter(item => item.status === '待处理').slice(0, 6)) |
| | | |
| | | const periodData = { |
| | | week: { labels: ['07/04', '07/05', '07/06', '07/07', '07/08', '07/09', '07/10'], values: [312, 348, 320, 401, 376, 428, 392] }, |
| | | month: { labels: ['第 1 周', '第 2 周', '第 3 周', '第 4 周'], values: [1460, 1580, 1720, 1640] }, |
| | | year: { labels: ['1月', '3月', '5月', '7月', '9月', '11月'], values: [5200, 6480, 7320, 8874, 9120, 9840] }, |
| | | } |
| | | const periodData = computed(() => { |
| | | const now = new Date() |
| | | const dayKey = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` |
| | | const count = (key: string) => records.value.filter(item => item.createdAt.slice(0, key.length) === key).length |
| | | const weekLabels: string[] = []; const weekValues: number[] = [] |
| | | for (let index = 6; index >= 0; index -= 1) { const date = new Date(now); date.setDate(now.getDate() - index); const key = dayKey(date); weekLabels.push(`${date.getMonth() + 1}/${date.getDate()}`); weekValues.push(count(key)) } |
| | | const monthKey = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}` |
| | | const monthDays = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate(); const monthLabels: string[] = []; const monthValues: number[] = [] |
| | | for (let day = 1; day <= Math.min(now.getDate(), monthDays); day += 1) { const key = `${monthKey}-${String(day).padStart(2, '0')}`; monthLabels.push(`${day}日`); monthValues.push(count(key)) } |
| | | const yearLabels: string[] = []; const yearValues: number[] = [] |
| | | for (let month = 1; month <= now.getMonth() + 1; month += 1) { const key = `${now.getFullYear()}-${String(month).padStart(2, '0')}`; yearLabels.push(`${month}月`); yearValues.push(count(key)) } |
| | | return { week: { labels: weekLabels, values: weekValues }, month: { labels: monthLabels, values: monthValues }, year: { labels: yearLabels, values: yearValues } } |
| | | }) |
| | | |
| | | const chartOption = computed(() => { |
| | | const data = periodData[chartPeriod.value as keyof typeof periodData] |
| | | const data = periodData.value[chartPeriod.value as keyof typeof periodData.value] |
| | | const values = data.values |
| | | return { |
| | | tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, |
| | | grid: { left: 8, right: 10, bottom: 4, top: 22, containLabel: true }, |
| | |
| | | }, |
| | | series: [{ |
| | | type: 'bar', barMaxWidth: 28, |
| | | data: data.values.map((value, index) => ({ value, itemStyle: { color: index === data.values.length - 1 ? '#2188f5' : '#b9d8f8' } })), |
| | | data: values.map((value, index) => ({ value, itemStyle: { color: index === values.length - 1 ? '#2188f5' : '#b9d8f8' } })), |
| | | itemStyle: { borderRadius: 0 }, |
| | | }], |
| | | } |
| | |
| | | </section> |
| | | |
| | | <div class="metrics"> |
| | | <article><span>总测试数</span><small>+12.4%</small><strong>373</strong></article> |
| | | <article><span>总电池组数</span><small>+4.2%</small><strong>368</strong></article> |
| | | <article><span>日新增测试</span><small>+18.7%</small><strong>8,874</strong></article> |
| | | <article><span>月新增测试</span><small>+9.6%</small><strong>8,874</strong></article> |
| | | <article><span>总测试数</span><strong>{{ records.length }}</strong></article> |
| | | <article><span>总电池组数</span><strong>{{ batteryGroupCount }}</strong></article> |
| | | <article><span>日新增测试</span><strong>{{ records.filter(item => item.createdAt.slice(0, 10) === new Date().toISOString().slice(0, 10)).length }}</strong></article> |
| | | <article><span>月新增测试</span><strong>{{ records.filter(item => item.createdAt.slice(0, 7) === new Date().toISOString().slice(0, 7)).length }}</strong></article> |
| | | </div> |
| | | |
| | | <section class="panel chart-panel"> |
| | |
| | | </section> |
| | | |
| | | <section class="panel todo-panel"> |
| | | <header class="panel-header"><div class="panel-title"><i></i><h3>待办 (18)</h3></div><button class="text-action" type="button" @click="router.push('/todo-list')">全部</button></header> |
| | | <div class="todo-list"><div v-for="item in todoList" :key="item.title"><span><strong>{{ item.title }}</strong><small>{{ item.time }}</small></span><b :class="item.priority.toLowerCase()">{{ item.priority }}</b></div></div> |
| | | <header class="panel-header"><div class="panel-title"><i></i><h3>待办 ({{ todoRecords.filter(item => item.status === '待处理').length }})</h3></div><button class="text-action" type="button" @click="router.push('/todo-list')">全部</button></header> |
| | | <div class="todo-list"><div v-for="item in todoList" :key="item.id"><span><strong>{{ item.content }}</strong><small>{{ item.operatedAt }} · 待处理</small></span><b class="pending">待处理</b></div></div> |
| | | </section> |
| | | </aside> |
| | | </div> |