refactor(dialysis+health): 透析统计从 erp-health 迁移到 erp-dialysis,消除跨 crate 残留
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- erp-dialysis: 新建 dialysis_stats_dto/handler/service,注册 /health/admin/statistics/dialysis 路由
- erp-health: 删除 get_dialysis_statistics 及 helper、DialysisStatisticsResp、
  DialysisRecordNotFound/DialysisPrescriptionNotFound、validate_dialysis_status* 及 9 个测试、
  DoctorDashboard.pending_dialysis_review、module 路由
- Web: HealthDataStats 移除 dialysis 字段,新增 getDialysisStats() 独立 API,
  useStatsData 并行 fetch,HealthDataCenter 接受独立 dialysisData prop
- 小程序: DoctorDashboard 移除 pending_dialysis_review,医护工作台移除"待审透析"卡片
This commit is contained in:
iven
2026-04-29 07:56:21 +08:00
parent cb6f5cc651
commit facc8b0d24
20 changed files with 234 additions and 265 deletions

View File

@@ -21,7 +21,6 @@ const CARDS: CardConfig[] = [
];
const HEALTH_CARDS: CardConfig[] = [
{ key: 'pending_dialysis_review', label: '待审透析', initial: '透', route: '/pages/doctor/patients/index' },
{ key: 'pending_lab_review', label: '待审化验', initial: '化', route: '/pages/doctor/report/index' },
{ key: 'today_appointments', label: '今日预约', initial: '约', route: '/pages/doctor/patients/index' },
];

View File

@@ -8,7 +8,6 @@ export interface DoctorDashboard {
unread_messages: number;
pending_follow_ups: number;
today_consultations: number;
pending_dialysis_review: number;
pending_lab_review: number;
today_appointments: number;
}

View File

@@ -214,7 +214,6 @@ export interface VitalSignsReportRate {
}
export interface HealthDataStats {
dialysis: DialysisStatistics;
lab_reports: LabReportStatistics;
appointments: AppointmentStatistics;
vital_signs_report_rate: VitalSignsReportRate;
@@ -376,6 +375,14 @@ export const pointsApi = {
return data.data;
},
getDialysisStats: async (): Promise<DialysisStatistics> => {
const { data } = await client.get<{
success: boolean;
data: DialysisStatistics;
}>('/health/admin/statistics/dialysis');
return data.data;
},
getPersonalStats: async (): Promise<PersonalStats> => {
const { data } = await client.get<{
success: boolean;

View File

@@ -11,7 +11,7 @@ import { useCountUp } from '../../../hooks/useCountUp';
import HealthDataCenter from './HealthDataCenter';
export function AdminDashboard() {
const { patientStats, followUpStats, healthDataStats, loading } = useStatsData();
const { patientStats, followUpStats, healthDataStats, dialysisStats, loading } = useStatsData();
const patientCount = useCountUp(patientStats?.total_patients ?? 0);
const appointmentCount = useCountUp(healthDataStats?.appointments?.this_month ?? 0);
const doctorCount = useCountUp(0);
@@ -72,7 +72,7 @@ export function AdminDashboard() {
<Tabs
defaultActiveKey="dialysis"
items={[
{ key: 'dialysis', label: '透析管理', children: <HealthDataCenter data={healthDataStats} tab="dialysis" /> },
{ key: 'dialysis', label: '透析管理', children: <HealthDataCenter data={healthDataStats} dialysisData={dialysisStats} tab="dialysis" /> },
{ key: 'lab', label: '化验报告', children: <HealthDataCenter data={healthDataStats} tab="lab" /> },
{ key: 'appointments', label: '预约分析', children: <HealthDataCenter data={healthDataStats} tab="appointments" /> },
{ key: 'vital-signs', label: '体征数据', children: <HealthDataCenter data={healthDataStats} tab="vital-signs" /> },

View File

@@ -1,30 +1,31 @@
import { Row, Col, Card, Statistic, Tag, Typography, Empty } from 'antd';
import type { HealthDataStats } from '../../../api/health/points';
import type { HealthDataStats, DialysisStatistics } from '../../../api/health/points';
const { Text } = Typography;
interface HealthDataCenterProps {
data: HealthDataStats | null;
dialysisData?: DialysisStatistics | null;
tab?: string;
}
function DialysisPanel({ data }: { data: HealthDataStats | null }) {
function DialysisPanel({ data }: { data: DialysisStatistics | null | undefined }) {
return (
<Card type="inner" title={<span style={{ fontSize: 14, fontWeight: 600 }}></span>} style={{ borderRadius: 8 }}>
<Row gutter={[12, 12]}>
<Col span={8}><Statistic title="总记录" value={data?.dialysis.total_records ?? 0} valueStyle={{ fontSize: 20 }} /></Col>
<Col span={8}><Statistic title="本月新增" value={data?.dialysis.this_month ?? 0} valueStyle={{ fontSize: 20, color: '#2563eb' }} /></Col>
<Col span={8}><Statistic title="待审核" value={data?.dialysis.pending_review ?? 0} valueStyle={{ fontSize: 20, color: '#d97706' }} /></Col>
<Col span={8}><Statistic title="总记录" value={data?.total_records ?? 0} valueStyle={{ fontSize: 20 }} /></Col>
<Col span={8}><Statistic title="本月新增" value={data?.this_month ?? 0} valueStyle={{ fontSize: 20, color: '#2563eb' }} /></Col>
<Col span={8}><Statistic title="待审核" value={data?.pending_review ?? 0} valueStyle={{ fontSize: 20, color: '#d97706' }} /></Col>
</Row>
<Row gutter={[12, 12]} style={{ marginTop: 12 }}>
<Col span={8}><Statistic title="并发症率" value={data?.dialysis.complication_rate ?? 0} suffix="%" precision={1} valueStyle={{ fontSize: 18 }} /></Col>
<Col span={8}><Statistic title="平均超滤(ml)" value={data?.dialysis.avg_ultrafiltration ?? 0} precision={0} valueStyle={{ fontSize: 18 }} /></Col>
<Col span={8}><Statistic title="平均时长(分)" value={data?.dialysis.avg_duration ?? 0} precision={0} valueStyle={{ fontSize: 18 }} /></Col>
<Col span={8}><Statistic title="并发症率" value={data?.complication_rate ?? 0} suffix="%" precision={1} valueStyle={{ fontSize: 18 }} /></Col>
<Col span={8}><Statistic title="平均超滤(ml)" value={data?.avg_ultrafiltration ?? 0} precision={0} valueStyle={{ fontSize: 18 }} /></Col>
<Col span={8}><Statistic title="平均时长(分)" value={data?.avg_duration ?? 0} precision={0} valueStyle={{ fontSize: 18 }} /></Col>
</Row>
{(data?.dialysis.type_distribution ?? []).length > 0 && (
{(data?.type_distribution ?? []).length > 0 && (
<div style={{ marginTop: 12 }}>
<Text type="secondary" style={{ fontSize: 12 }}>: </Text>
{data!.dialysis.type_distribution.map((item) => (
{data!.type_distribution.map((item) => (
<Tag key={item.name} color="blue" style={{ marginTop: 4 }}>{item.name}: {item.value}</Tag>
))}
</div>
@@ -101,15 +102,24 @@ function VitalSignsPanel({ data }: { data: HealthDataStats | null }) {
);
}
const TAB_PANELS: Record<string, React.FC<{ data: HealthDataStats | null }>> = {
dialysis: DialysisPanel,
lab: LabPanel,
appointments: AppointmentsPanel,
'vital-signs': VitalSignsPanel,
};
export default function HealthDataCenter({ data, dialysisData, tab = 'dialysis' }: HealthDataCenterProps) {
if (tab === 'dialysis') {
return (
<Row gutter={[16, 16]}>
<Col span={24}>
<DialysisPanel data={dialysisData} />
</Col>
</Row>
);
}
export default function HealthDataCenter({ data, tab = 'dialysis' }: HealthDataCenterProps) {
const Panel = TAB_PANELS[tab];
const PANELS: Record<string, React.FC<{ data: HealthDataStats | null }>> = {
lab: LabPanel,
appointments: AppointmentsPanel,
'vital-signs': VitalSignsPanel,
};
const Panel = PANELS[tab];
if (!Panel) {
return <Empty description="未知数据面板" />;

View File

@@ -6,6 +6,7 @@ import {
type FollowUpStatistics,
type PointsStatistics,
type HealthDataStats,
type DialysisStatistics,
} from '../../../api/health/points';
export interface StatsData {
@@ -14,6 +15,7 @@ export interface StatsData {
followUpStats: FollowUpStatistics | null;
pointsStats: PointsStatistics | null;
healthDataStats: HealthDataStats | null;
dialysisStats: DialysisStatistics | null;
loading: boolean;
error: string | null;
refresh: () => void;
@@ -28,6 +30,7 @@ export function useStatsData(): StatsData {
const [followUpStats, setFollowUpStats] = useState<FollowUpStatistics | null>(null);
const [pointsStats, setPointsStats] = useState<PointsStatistics | null>(null);
const [healthDataStats, setHealthDataStats] = useState<HealthDataStats | null>(null);
const [dialysisStats, setDialysisStats] = useState<DialysisStatistics | null>(null);
const fetchAllStats = useCallback(async () => {
setLoading(true);
@@ -52,9 +55,10 @@ export function useStatsData(): StatsData {
tryFetch(pointsApi.getFollowUpStats, setFollowUpStats, '随访'),
tryFetch(pointsApi.getStatistics, setPointsStats, '积分'),
tryFetch(pointsApi.getHealthDataStats, setHealthDataStats, '健康数据'),
tryFetch(pointsApi.getDialysisStats, setDialysisStats, '透析'),
]);
if (hasAnyError && errors.length === 5) {
if (hasAnyError && errors.length === 6) {
setError('加载统计数据失败');
}
@@ -66,7 +70,7 @@ export function useStatsData(): StatsData {
}, [fetchAllStats]);
return {
patientStats, consultationStats, followUpStats, pointsStats, healthDataStats,
patientStats, consultationStats, followUpStats, pointsStats, healthDataStats, dialysisStats,
loading, error, refresh: fetchAllStats,
};
}