From 9c5af926198c9b0671a6e680bd5dfa8e43f666a1 Mon Sep 17 00:00:00 2001 From: weijuesen Date: Thu, 13 Aug 2026 08:54:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E5=8C=BA=E5=9F=9F=E5=8F=91?= =?UTF-8?q?=E7=97=85=E7=BB=9F=E8=AE=A1=E5=9B=BE=E8=A1=A8=E4=B8=8E=E8=9A=95?= =?UTF-8?q?=E6=88=BF=E5=8C=BA=E5=9F=9F=E5=AD=97=E6=AE=B5=EF=BC=88#22?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/dal/silkworm.ts | 1 + web/src/dal/trace.ts | 9 +++++++ web/src/pages/Silkworm.tsx | 4 ++++ web/src/pages/Traces.tsx | 48 +++++++++++++++++++++++++++++++++++++- 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/web/src/dal/silkworm.ts b/web/src/dal/silkworm.ts index 4d9dde2..397d961 100644 --- a/web/src/dal/silkworm.ts +++ b/web/src/dal/silkworm.ts @@ -5,6 +5,7 @@ export interface SilkwormHouse { name: string; code?: string; location?: string; + region?: string; description?: string; status?: 'active' | 'inactive' | 'running' | 'idle' | 'alarm'; capacity?: number; diff --git a/web/src/dal/trace.ts b/web/src/dal/trace.ts index 9237223..3bd0848 100644 --- a/web/src/dal/trace.ts +++ b/web/src/dal/trace.ts @@ -36,3 +36,12 @@ export const getTraceChecklist = (id: string) => get(`/trace-records/${id}/checklist`); export const submitTraceChecklist = (id: string, answers: { key: string; answer: string }[]) => post(`/trace-records/${id}/checklist`, { answers }); + +export interface RegionStat { + region: string; + total: number; + diseases: Record; +} + +export const getRegionStats = (days = 90, disease?: string) => + get('/trace-records/region-stats', { params: { days, disease } }); diff --git a/web/src/pages/Silkworm.tsx b/web/src/pages/Silkworm.tsx index 605d335..69b49d1 100644 --- a/web/src/pages/Silkworm.tsx +++ b/web/src/pages/Silkworm.tsx @@ -13,6 +13,7 @@ export default function SilkwormPage() { { title: '序号', valueType: 'indexBorder', search: false, width: 60 }, { title: '名称', dataIndex: 'name' }, { title: '位置', dataIndex: 'location', search: false, render: (_, r) => r.location || '-' }, + { title: '区域', dataIndex: 'region', search: false, render: (_, r) => r.region || '-' }, { title: '容量(盒)', dataIndex: 'capacity', search: false, render: (_, r) => r.capacity ?? '-' }, { title: '阶段', @@ -111,6 +112,9 @@ export default function SilkwormPage() { + + + diff --git a/web/src/pages/Traces.tsx b/web/src/pages/Traces.tsx index b326925..4d6f680 100644 --- a/web/src/pages/Traces.tsx +++ b/web/src/pages/Traces.tsx @@ -1,9 +1,10 @@ -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Button, Drawer, Form, Input, Modal, Popconfirm, Radio, Select, Space, Tag, Typography, message, } from 'antd'; import { PlusOutlined } from '@ant-design/icons'; import { ProTable, type ActionType, type ProColumns } from '@ant-design/pro-components'; +import ReactECharts from 'echarts-for-react'; import dayjs from 'dayjs'; import { listTraceRecords, @@ -13,8 +14,10 @@ import { autoTrace, getTraceChecklist, submitTraceChecklist, + getRegionStats, type TraceRecord, type ChecklistItem, + type RegionStat, } from '../dal/trace'; import { authService } from '../services/auth'; @@ -28,6 +31,7 @@ const STATUS_LABELS: Record = { const canWrite = () => authService.hasPermission('trace:write'); export default function TracesPage() { + const [regionStats, setRegionStats] = useState([]); const actionRef = useRef(); const [createOpen, setCreateOpen] = useState(false); const [createForm] = Form.useForm>(); @@ -35,6 +39,38 @@ export default function TracesPage() { const [checklist, setChecklist] = useState([]); const [answers, setAnswers] = useState>({}); + useEffect(() => { + getRegionStats(90) + .then(setRegionStats) + .catch(() => {}); + }, []); + + const barOption = { + title: { text: '区域发病统计(近 90 天)' }, + tooltip: {}, + xAxis: { type: 'category', data: regionStats.map((s) => s.region) }, + yAxis: { type: 'value', minInterval: 1 }, + series: [{ type: 'bar', data: regionStats.map((s) => s.total), itemStyle: { color: '#fa8c16' } }], + }; + + const diseaseMap: Record = {}; + regionStats.forEach((s) => { + Object.entries(s.diseases).forEach(([d, n]) => { + diseaseMap[d] = (diseaseMap[d] || 0) + n; + }); + }); + const pieOption = { + title: { text: '病种分布' }, + tooltip: { trigger: 'item' }, + series: [ + { + type: 'pie', + radius: ['35%', '65%'], + data: Object.entries(diseaseMap).map(([name, value]) => ({ name, value })), + }, + ], + }; + const openDetail = async (r: TraceRecord) => { setDetail(r); if (r.status !== 'archived') { @@ -116,6 +152,16 @@ export default function TracesPage() { return ( <> + {regionStats.length > 0 ? ( +
+
+ +
+
+ +
+
+ ) : null} actionRef={actionRef} rowKey="id"