From 52c6ef23621d760b8ba9661d657643ed81d00f93 Mon Sep 17 00:00:00 2001 From: Trae Assistant Date: Mon, 5 Oct 2026 14:29:07 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=9F=E4=B8=80=E6=89=80=E6=9C=89?= =?UTF-8?q?=E7=B4=AF=E8=AE=A1=E6=97=B6=E9=95=BF=E8=AE=A1=E7=AE=97=E5=8F=A3?= =?UTF-8?q?=E5=BE=84=EF=BC=88=E5=BF=85=E9=A1=BB=20JOIN=20=E5=88=B0?= =?UTF-8?q?=E6=9C=89=E6=95=88=20activities=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一口径定义:所有累计/总/人均/合计时长度量,一律只 计算 hours 行中 activity_id 存在于 activities 表中的记录 (排除 FK pragma 关闭时写入导致的幽灵 hours 行) - server/users.ts: · GET /(用户列表列的累计时长) 原 SELECT SUM(h.hours) FROM hours h → 增加 EXISTS 子查询 『AND EXISTS (SELECT 1 FROM activities a WHERE a.id=h.activity_id)』 · GET /:id 详情卡 total_hours_raw(给 enrichUser 用的原始数) 同样加 EXISTS 校验,然后仍用详情 activities 列表 reduce 覆 盖,保证卡片累计 = 列表合计 100% 一致,且与列表 SQL 同口径 - server/export.ts: · export include_user_totals(导出报告的部员累计时长排行) 原 LEFT JOIN hours 直接 SUM → 改为 LEFT JOIN 预聚合子查 询 vh,vh 内先按 EXISTS(activities) 过滤,再 SUM/COUNT · export include_activity_matrix(矩阵 _total、预览 KPI、 导出 Excel 的总时长列) hoursRows 全表 SELECT → 增加 WHERE EXISTS(activities) 过 滤,matrix/remarkMatrix 只聚合有效活动行,再派生的用户级 _total 自然与 user_totals 同口径 - client/Export.tsx overviewCalc(导出报告顶部的累计总时长/ 人均时长 KPI 卡片,之前只依赖 preview.hours.reduce): · 优先级: ① 有 user_totals → 用后端同口径返回的 u.total_hours 聚合 (最靠谱) ② 否则有 activity_matrix → 用 matrix.user._total 聚合 (两者都基于同一 valid_hours 过滤) ③ Fallback:preview.hours.reduce(仅在只勾了明细单且没 勾排行/矩阵时才用) · 计算完 totalHours 再 toFixed(1) 去浮点数误差,avgHours 同步 toFixed(1) - client/user/Dashboard.tsx(部员侧义工号查询的个人累计页): · 累计义工时长卡下半部新增『明细表合计』实时校验行: data.activities.reduce 求和直接显示在卡片上,与大号累计 值左右对照,一眼确认有没有不一致 --- client/src/pages/admin/Export.tsx | 12 ++++++++++-- client/src/pages/user/Dashboard.tsx | 14 +++++++++++++- server/src/routes/export.ts | 17 ++++++++++++----- server/src/routes/users.ts | 12 ++++++++++-- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/client/src/pages/admin/Export.tsx b/client/src/pages/admin/Export.tsx index c12b96c..e5190a2 100644 --- a/client/src/pages/admin/Export.tsx +++ b/client/src/pages/admin/Export.tsx @@ -262,11 +262,19 @@ export default function Export() { const overviewCalc = (() => { if (!preview) return null; - const totalHours = (preview.hours || []).reduce((s: number, h: any) => s + Number(h.hours || 0), 0); const userCount = (preview.users || []).length; const activityCount = (preview.activities || []).length; const hourCount = (preview.hours || []).length; - const avgHours = userCount > 0 ? totalHours / userCount : 0; + let totalHours: number; + if (preview.user_totals && preview.user_totals.length > 0) { + totalHours = (preview.user_totals).reduce((s: number, u: any) => s + Number(u.total_hours || 0), 0); + } else if (preview.activity_matrix && Array.isArray(preview.activity_matrix.users)) { + totalHours = (preview.activity_matrix.users).reduce((s: number, u: any) => s + Number(u._total || 0), 0); + } else { + totalHours = (preview.hours || []).reduce((s: number, h: any) => s + Number(h.hours || 0), 0); + } + totalHours = Number(totalHours.toFixed(1)); + const avgHours = userCount > 0 ? Number((totalHours / userCount).toFixed(1)) : 0; return { totalHours, userCount, activityCount, hourCount, avgHours }; })(); diff --git a/client/src/pages/user/Dashboard.tsx b/client/src/pages/user/Dashboard.tsx index 8707fb5..576dc3a 100644 --- a/client/src/pages/user/Dashboard.tsx +++ b/client/src/pages/user/Dashboard.tsx @@ -68,7 +68,19 @@ export default function UserDashboard() {
累计义工时长
-
{Number(data.total_hours || 0).toFixed(1)} 小时
+
+ {Number(data.total_hours || 0).toFixed(1)} 小时 +
+ {data.activities.length > 0 && ( +
+ + 明细表合计: + + + {data.activities.reduce((s, r) => s + Number((r as any).hours || 0), 0).toFixed(1)} 小时 + +
+ )}
参与活动数
diff --git a/server/src/routes/export.ts b/server/src/routes/export.ts index 6390391..258d914 100644 --- a/server/src/routes/export.ts +++ b/server/src/routes/export.ts @@ -66,11 +66,17 @@ router.post('/', authMiddleware, (req: AuthRequest, res) => { FROM user_groups ug2 LEFT JOIN work_groups g2 ON ug2.group_id = g2.id WHERE ug2.user_id = u.id) AS _group_names_csv, - COALESCE(SUM(h.hours), 0) AS total_hours, - COUNT(DISTINCT h.activity_id) AS activity_count + COALESCE(vh.total_hours, 0) AS total_hours, + COALESCE(vh.activity_count, 0) AS activity_count FROM users u - LEFT JOIN hours h ON h.user_id = u.id - GROUP BY u.id + LEFT JOIN ( + SELECT user_id, + SUM(hours) AS total_hours, + COUNT(DISTINCT activity_id) AS activity_count + FROM hours h + WHERE EXISTS (SELECT 1 FROM activities a WHERE a.id = h.activity_id) + GROUP BY user_id + ) vh ON vh.user_id = u.id ORDER BY total_hours DESC, u.class, u.name `).all(); result.user_totals = withGroupNames(rows).map(({ _group_names_csv: _, ...rest }) => rest); @@ -112,7 +118,8 @@ router.post('/', authMiddleware, (req: AuthRequest, res) => { `).all() as any[]; const users = withGroupNames(userRows); const hoursRows = db.prepareCompat(` - SELECT user_id, activity_id, hours, remark FROM hours + SELECT user_id, activity_id, hours, remark FROM hours h + WHERE EXISTS (SELECT 1 FROM activities a WHERE a.id = h.activity_id) `).all() as any[]; const matrix: Record = {}; const remarkMatrix: Record = {}; diff --git a/server/src/routes/users.ts b/server/src/routes/users.ts index be319ca..9562a36 100644 --- a/server/src/routes/users.ts +++ b/server/src/routes/users.ts @@ -31,7 +31,11 @@ router.get('/', authMiddleware, (req: AuthRequest, res: Response) => { SELECT u.id, u.class, u.name, u.volunteer_no, GROUP_CONCAT(g.id) AS group_ids_csv, GROUP_CONCAT(g.name) AS group_names_csv, - COALESCE((SELECT SUM(h.hours) FROM hours h WHERE h.user_id = u.id), 0) AS total_hours + COALESCE(( + SELECT SUM(h.hours) FROM hours h + WHERE h.user_id = u.id + AND EXISTS (SELECT 1 FROM activities a WHERE a.id = h.activity_id) + ), 0) AS total_hours FROM users u LEFT JOIN user_groups ug ON u.id = ug.user_id LEFT JOIN work_groups g ON ug.group_id = g.id @@ -66,7 +70,11 @@ router.get('/:id', (req: Request, res: Response) => { SELECT u.id, u.class, u.name, u.volunteer_no, GROUP_CONCAT(g.id) AS group_ids_csv, GROUP_CONCAT(g.name) AS group_names_csv, - COALESCE((SELECT SUM(h.hours) FROM hours h WHERE h.user_id = u.id), 0) AS total_hours + COALESCE(( + SELECT SUM(h.hours) FROM hours h + WHERE h.user_id = u.id + AND EXISTS (SELECT 1 FROM activities a WHERE a.id = h.activity_id) + ), 0) AS total_hours_raw FROM users u LEFT JOIN user_groups ug ON u.id = ug.user_id LEFT JOIN work_groups g ON ug.group_id = g.id