feat(web): 区域发病统计图表与蚕房区域字段(#22)

This commit is contained in:
weijuesen
2026-08-13 08:54:08 +08:00
parent 7949f90c0c
commit 9c5af92619
4 changed files with 61 additions and 1 deletions
+1
View File
@@ -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;
+9
View File
@@ -36,3 +36,12 @@ export const getTraceChecklist = (id: string) =>
get<ChecklistItem[]>(`/trace-records/${id}/checklist`);
export const submitTraceChecklist = (id: string, answers: { key: string; answer: string }[]) =>
post<TraceRecord>(`/trace-records/${id}/checklist`, { answers });
export interface RegionStat {
region: string;
total: number;
diseases: Record<string, number>;
}
export const getRegionStats = (days = 90, disease?: string) =>
get<RegionStat[]>('/trace-records/region-stats', { params: { days, disease } });
+4
View File
@@ -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() {
<Form.Item label="位置" name="location" rules={[{ required: true, message: '请输入位置' }]}>
<Input placeholder="如:1号厂房东侧" />
</Form.Item>
<Form.Item label="区域" name="region">
<Input placeholder="如:XX镇 / XX村(用于区域发病统计)" />
</Form.Item>
<Form.Item label="容量(盒)" name="capacity" rules={[{ required: true, message: '请输入容量' }]}>
<Input type="number" min={0} placeholder="蚕盒数量" />
</Form.Item>
+47 -1
View File
@@ -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<string, { color: string; text: string }> = {
const canWrite = () => authService.hasPermission('trace:write');
export default function TracesPage() {
const [regionStats, setRegionStats] = useState<RegionStat[]>([]);
const actionRef = useRef<ActionType>();
const [createOpen, setCreateOpen] = useState(false);
const [createForm] = Form.useForm<Partial<TraceRecord>>();
@@ -35,6 +39,38 @@ export default function TracesPage() {
const [checklist, setChecklist] = useState<ChecklistItem[]>([]);
const [answers, setAnswers] = useState<Record<string, string>>({});
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<string, number> = {};
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 ? (
<div style={{ display: 'flex', gap: 16, marginBottom: 16 }}>
<div style={{ flex: 2, background: '#fff', borderRadius: 8, padding: 8 }}>
<ReactECharts option={barOption} style={{ height: 260 }} />
</div>
<div style={{ flex: 1, background: '#fff', borderRadius: 8, padding: 8 }}>
<ReactECharts option={pieOption} style={{ height: 260 }} />
</div>
</div>
) : null}
<ProTable<TraceRecord>
actionRef={actionRef}
rowKey="id"