diff --git a/web/src/dal/health.ts b/web/src/dal/health.ts new file mode 100644 index 0000000..4f5c697 --- /dev/null +++ b/web/src/dal/health.ts @@ -0,0 +1,21 @@ +import { get } from '../api/http'; + +export interface HealthProfile { + roomId: string; + roomName: string; + score: number; + grade: string; + riskDistribution: { green: number; yellow: number; orange: number; red: number }; + lampResults: { total: number; positive: number }; + events: { consultations: number; traces: number }; + batch?: any; +} + +export const getHealthProfile = (roomId: string) => + get(`/rooms/${roomId}/health-profile`); + +export const getMonthlyStats = (year?: number) => + get<{ year: number; stats: { month: string; total: number; diseases: Record }[] }>( + '/trace-records/monthly-stats', + { params: { year } }, + ); diff --git a/web/src/pages/Silkworm.tsx b/web/src/pages/Silkworm.tsx index 69b49d1..abb7ab7 100644 --- a/web/src/pages/Silkworm.tsx +++ b/web/src/pages/Silkworm.tsx @@ -1,13 +1,17 @@ import { useState } from 'react'; -import { Button, Form, Input, Modal, Popconfirm, Select, Tag, message } from 'antd'; +import { Button, Descriptions, Drawer, Form, Input, Modal, Popconfirm, Select, Tag, message, Progress } from 'antd'; import { PlusOutlined } from '@ant-design/icons'; import { ProTable, type ProColumns } from '@ant-design/pro-components'; import { listHouses, createHouse, updateHouse, deleteHouse, type SilkwormHouse } from '../dal/silkworm'; +import { getHealthProfile, type HealthProfile } from '../dal/health'; export default function SilkwormPage() { const [modalOpen, setModalOpen] = useState(false); const [form] = Form.useForm>(); const [editing, setEditing] = useState(null); + const [profileOpen, setProfileOpen] = useState(false); + const [profile, setProfile] = useState(null); + const [profileLoading, setProfileLoading] = useState(false); const columns: ProColumns[] = [ { title: '序号', valueType: 'indexBorder', search: false, width: 60 }, @@ -44,6 +48,20 @@ export default function SilkwormPage() { title: '操作', valueType: 'option', render: (_, r) => [ + { + setProfileOpen(true); + setProfileLoading(true); + try { + setProfile(await getHealthProfile(r.id)); + } finally { + setProfileLoading(false); + } + }} + > + 健康画像 + , { @@ -131,6 +149,42 @@ export default function SilkwormPage() { + setProfileOpen(false)}> + {profileLoading ?
加载中...
: null} + {profile ? ( +
+ `${Math.round(profile.score)}分`} + strokeColor={profile.score >= 85 ? '#52c41a' : profile.score >= 70 ? '#10b981' : profile.score >= 55 ? '#faad14' : '#f5222d'} + /> + + + + {profile.grade} + + + + 绿 {profile.riskDistribution.green} / 黄 {profile.riskDistribution.yellow} / 橙{' '} + {profile.riskDistribution.orange} / 红 {profile.riskDistribution.red} + + + 共 {profile.lampResults.total} 项,阳性 {profile.lampResults.positive} 项 + + + 会诊 {profile.events.consultations} 次 / 溯源 {profile.events.traces} 次 + + {profile.batch ? ( + + {profile.batch.name} + {profile.batch.instar ? `(${profile.batch.instar}龄)` : ''} + + ) : null} + +
+ ) : null} +
); } diff --git a/web/src/pages/Traces.tsx b/web/src/pages/Traces.tsx index 4d6f680..26814e7 100644 --- a/web/src/pages/Traces.tsx +++ b/web/src/pages/Traces.tsx @@ -19,6 +19,7 @@ import { type ChecklistItem, type RegionStat, } from '../dal/trace'; +import { getMonthlyStats } from '../dal/health'; import { authService } from '../services/auth'; const STATUS_LABELS: Record = { @@ -32,6 +33,7 @@ const canWrite = () => authService.hasPermission('trace:write'); export default function TracesPage() { const [regionStats, setRegionStats] = useState([]); + const [monthlyStats, setMonthlyStats] = useState<{ month: string; total: number }[]>([]); const actionRef = useRef(); const [createOpen, setCreateOpen] = useState(false); const [createForm] = Form.useForm>(); @@ -43,6 +45,9 @@ export default function TracesPage() { getRegionStats(90) .then(setRegionStats) .catch(() => {}); + getMonthlyStats() + .then((res) => setMonthlyStats(res.stats.map((s) => ({ month: s.month, total: s.total })))) + .catch(() => {}); }, []); const barOption = { @@ -71,6 +76,14 @@ export default function TracesPage() { ], }; + const monthlyOption = { + title: { text: '年度发病规律' }, + tooltip: {}, + xAxis: { type: 'category', data: monthlyStats.map((s) => s.month) }, + yAxis: { type: 'value', minInterval: 1 }, + series: [{ type: 'line', data: monthlyStats.map((s) => s.total), smooth: true, areaStyle: {} }], + }; + const openDetail = async (r: TraceRecord) => { setDetail(r); if (r.status !== 'archived') { @@ -162,6 +175,11 @@ export default function TracesPage() { ) : null} + {monthlyStats.length > 0 ? ( +
+ +
+ ) : null} actionRef={actionRef} rowKey="id"