From ac919731a9fdb32c763e9c19f6f3c652ac20743a Mon Sep 17 00:00:00 2001 From: iven Date: Sun, 26 Apr 2026 23:48:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20QA=20=E5=85=A8=E9=87=8F=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E5=8F=91=E7=8E=B0=205=20=E4=B8=AA=20bug=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - [P0] 登录失败无反馈: client.ts 响应拦截器跳过 /auth/login 的 401 处理,让错误传播到 Login 组件 - [P0] 统计仪表盘 400: 前端用独立 try/catch 替代 Promise.all 提高容错性;后端 stats_service 白名单补充 ultrafiltration_volume/dialysis_duration - [P1] 随访负责人显示 UUID: 批量解析 assigned_to 用户名 - [P2] 消息中心时间未格式化: 添加 formatDateTime 函数 - [P2] 首页显示 login_failed: 过滤审计日志中的 login_failed 动作 --- apps/web/src/api/client.ts | 2 +- apps/web/src/pages/Home.tsx | 2 +- .../web/src/pages/health/FollowUpTaskList.tsx | 16 +++++++ .../src/pages/health/StatisticsDashboard.tsx | 44 +++++++++++-------- .../src/pages/messages/NotificationList.tsx | 14 +++++- .../erp-health/src/service/stats_service.rs | 2 + 6 files changed, 58 insertions(+), 22 deletions(-) diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts index 3554193..b4f1260 100644 --- a/apps/web/src/api/client.ts +++ b/apps/web/src/api/client.ts @@ -104,7 +104,7 @@ client.interceptors.response.use( }, async (error) => { const originalRequest = error.config; - if (error.response?.status === 401 && !originalRequest._retry) { + if (error.response?.status === 401 && !originalRequest._retry && !originalRequest.url?.includes('/auth/login')) { if (isRefreshing) { return new Promise((resolve, reject) => { failedQueue.push({ resolve, reject }); diff --git a/apps/web/src/pages/Home.tsx b/apps/web/src/pages/Home.tsx index eae9d1b..ad73694 100644 --- a/apps/web/src/pages/Home.tsx +++ b/apps/web/src/pages/Home.tsx @@ -186,7 +186,7 @@ export default function Home() { setActivitiesLoading(true); try { const result = await listAuditLogs({ page: 1, page_size: 5 }); - if (!cancelled) setRecentActivities(result.data); + if (!cancelled) setRecentActivities(result.data.filter(a => a.action !== 'login_failed')); } catch { // 静默处理 } finally { diff --git a/apps/web/src/pages/health/FollowUpTaskList.tsx b/apps/web/src/pages/health/FollowUpTaskList.tsx index bb17439..ce6e779 100644 --- a/apps/web/src/pages/health/FollowUpTaskList.tsx +++ b/apps/web/src/pages/health/FollowUpTaskList.tsx @@ -16,6 +16,7 @@ import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'; import dayjs from 'dayjs'; import { followUpApi, type FollowUpTask, type CreateFollowUpTaskReq, type UpdateFollowUpTaskReq } from '../../api/health/followUp'; import { patientApi } from '../../api/health/patients'; +import { getUser } from '../../api/users'; import { StatusTag } from './components/StatusTag'; import { PatientSelect } from './components/PatientSelect'; import { DoctorSelect } from './components/DoctorSelect'; @@ -120,6 +121,21 @@ export default function FollowUpTaskList() { if (Object.keys(newLabels).length > 0) { setPatientLabels((prev) => ({ ...prev, ...newLabels })); } + + // Batch resolve assignee names + const assigneeIds = [...new Set(result.data.map((t: FollowUpTask) => t.assigned_to).filter(Boolean))]; + const newDoctorLabels: Record = {}; + await Promise.allSettled( + assigneeIds.map(async (id: string) => { + try { + const u = await getUser(id); + newDoctorLabels[id] = u.display_name || u.username; + } catch { /* skip */ } + }), + ); + if (Object.keys(newDoctorLabels).length > 0) { + setDoctorLabels((prev) => ({ ...prev, ...newDoctorLabels })); + } } catch { message.error('加载随访任务失败'); } finally { diff --git a/apps/web/src/pages/health/StatisticsDashboard.tsx b/apps/web/src/pages/health/StatisticsDashboard.tsx index 94856af..f75e8d2 100644 --- a/apps/web/src/pages/health/StatisticsDashboard.tsx +++ b/apps/web/src/pages/health/StatisticsDashboard.tsx @@ -92,25 +92,33 @@ export default function StatisticsDashboard() { const fetchAllStats = useCallback(async () => { setLoading(true); setError(null); - try { - const [patients, consultations, followUps, points, healthData] = await Promise.all([ - pointsApi.getPatientStats(), - pointsApi.getConsultationStats(), - pointsApi.getFollowUpStats(), - pointsApi.getStatistics(), - pointsApi.getHealthDataStats(), - ]); - setPatientStats(patients); - setConsultationStats(consultations); - setFollowUpStats(followUps); - setPointsStats(points); - setHealthDataStats(healthData); - } catch (err: unknown) { - const message = err instanceof Error ? err.message : '加载统计数据失败'; - setError(message); - } finally { - setLoading(false); + + let hasAnyError = false; + const errors: string[] = []; + + const tryFetch = async (fn: () => Promise, setter: (v: T) => void, label: string) => { + try { + const data = await fn(); + setter(data); + } catch { + hasAnyError = true; + errors.push(label); + } + }; + + await Promise.all([ + tryFetch(pointsApi.getPatientStats, setPatientStats, '患者'), + tryFetch(pointsApi.getConsultationStats, setConsultationStats, '咨询'), + tryFetch(pointsApi.getFollowUpStats, setFollowUpStats, '随访'), + tryFetch(pointsApi.getStatistics, setPointsStats, '积分'), + tryFetch(pointsApi.getHealthDataStats, setHealthDataStats, '健康数据'), + ]); + + if (hasAnyError && errors.length === 5) { + setError('加载统计数据失败'); } + + setLoading(false); }, []); useEffect(() => { diff --git a/apps/web/src/pages/messages/NotificationList.tsx b/apps/web/src/pages/messages/NotificationList.tsx index cf28687..49681a7 100644 --- a/apps/web/src/pages/messages/NotificationList.tsx +++ b/apps/web/src/pages/messages/NotificationList.tsx @@ -7,6 +7,16 @@ import { useThemeMode } from '../../hooks/useThemeMode'; const { Paragraph } = Typography; +function formatDateTime(value: string): string { + return new Date(value).toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }); +} + interface Props { queryFilter?: MessageQuery; } @@ -84,7 +94,7 @@ export default function NotificationList({ queryFilter }: Props) {
{record.body}
- {record.created_at} + {formatDateTime(record.created_at)}
), @@ -170,7 +180,7 @@ export default function NotificationList({ queryFilter }: Props) { key: 'created_at', width: 180, render: (v: string) => ( - {v} + {formatDateTime(v)} ), }, { diff --git a/crates/erp-health/src/service/stats_service.rs b/crates/erp-health/src/service/stats_service.rs index eae3ea6..f6460a8 100644 --- a/crates/erp-health/src/service/stats_service.rs +++ b/crates/erp-health/src/service/stats_service.rs @@ -426,6 +426,8 @@ async fn compute_avg_field( field: &str, ) -> AppResult> { const ALLOWED_FIELDS: &[&str] = &[ + "ultrafiltration_volume", + "dialysis_duration", "uf_volume", "uf_rate", "blood_flow_rate",