From 6e9d8578c95be24b861d9c1a86510f512748711d Mon Sep 17 00:00:00 2001 From: Trae Assistant Date: Mon, 5 Oct 2026 14:21:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=83=A8=E5=91=98=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E7=B4=AF=E8=AE=A1=E6=97=B6=E9=95=BF=E5=8D=A1=E4=B8=8E=E4=B8=8B?= =?UTF-8?q?=E6=96=B9=E6=B4=BB=E5=8A=A8=E5=88=97=E8=A1=A8=E5=90=88=E8=AE=A1?= =?UTF-8?q?=E4=B8=8D=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - server/users.ts GET /:id: · total_hours 原先是标量子查询(独立的 SELECT SUM FROM hours), 活动列表是 JOIN activities 后查出来的——两者在某些边界下可能 数据源不一致(FK pragma 状态、活动被删但 hours 残留时标量子 查询会多算 1 次,卡片显示 11h 而列表只有 7h 就是这个问题) · 改为直接用 activities 列表查询结果 reduce 求和后赋值给 user.total_hours,保证两个数字来自完全同一份数据 - client/Users.tsx 部员详情参与活动表: · 表底部新增一条粘性合计行:bg-primary-50/60 + 边框,左边小字 『本页列表合计(应等于上方卡片的累计时长)』,右边大号加粗 主题色数字实时显示 reduce 求和 · 溢出容器 overflow-hidden,合计行与 DataTable 边框完美贴合无缝 --- client/src/pages/admin/Users.tsx | 98 ++++++++++++++++++-------------- server/src/routes/users.ts | 2 + 2 files changed, 57 insertions(+), 43 deletions(-) diff --git a/client/src/pages/admin/Users.tsx b/client/src/pages/admin/Users.tsx index 025e2d9..f0b2ea5 100644 --- a/client/src/pages/admin/Users.tsx +++ b/client/src/pages/admin/Users.tsx @@ -749,49 +749,61 @@ export default function Users() {

参与的活动({detail.activities.length} 次)按登记时间倒序,最新补录/登记在最上方

- ( -
{r.title}
- ), - }, - { - key: 'time', - title: '活动时间', - width: '240px', - render: (r: any) => ( -
- {formatDateRange(r.start_time, r.end_time)} -
- ), - }, - { - key: 'hours', - title: '时长', - width: '100px', - render: (r: any) => ( - - {Number(r.hours || 0).toFixed(1)}h - - ), - }, - { - key: 'remark', - title: '备注', - render: (r: any) => ( -
- {r.remark || —} -
- ), - }, - ]} - data={detail.activities} - rowKey={(r: any) => `${r.id}-${r.reg_at || r.hours}-${r.remark || ''}`} - /> +
+ ( +
{r.title}
+ ), + }, + { + key: 'time', + title: '活动时间', + width: '240px', + render: (r: any) => ( +
+ {formatDateRange(r.start_time, r.end_time)} +
+ ), + }, + { + key: 'hours', + title: '时长', + width: '100px', + render: (r: any) => ( + + {Number(r.hours || 0).toFixed(1)}h + + ), + }, + { + key: 'remark', + title: '备注', + render: (r: any) => ( +
+ {r.remark || —} +
+ ), + }, + ]} + data={detail.activities} + rowKey={(r: any) => `${r.id}-${r.reg_at || r.hours}-${r.remark || ''}`} + /> + {detail.activities.length > 0 && ( +
+
+ 本页列表合计 (应等于上方卡片的累计时长) +
+
+ {detail.activities.reduce((s: number, r: any) => s + Number(r.hours || 0), 0).toFixed(1)}h +
+
+ )} +
)} diff --git a/server/src/routes/users.ts b/server/src/routes/users.ts index 551fc1b..be319ca 100644 --- a/server/src/routes/users.ts +++ b/server/src/routes/users.ts @@ -82,6 +82,8 @@ router.get('/:id', (req: Request, res: Response) => { WHERE h.user_id = ? ORDER BY h.created_at DESC, a.start_time DESC `).all(id); + const listSum = activities.reduce((s, r) => s + Number(r.hours || 0), 0); + user.total_hours = Number(listSum.toFixed(1)); res.json({ ...user, activities }); });