- 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 边框完美贴合无缝
This commit is contained in:
@@ -749,49 +749,61 @@ export default function Users() {
|
|||||||
<h3 className="font-semibold mb-2 text-gray-800">
|
<h3 className="font-semibold mb-2 text-gray-800">
|
||||||
参与的活动({detail.activities.length} 次)<span className="ml-2 text-xs font-normal muted">按登记时间倒序,最新补录/登记在最上方</span>
|
参与的活动({detail.activities.length} 次)<span className="ml-2 text-xs font-normal muted">按登记时间倒序,最新补录/登记在最上方</span>
|
||||||
</h3>
|
</h3>
|
||||||
<DataTable
|
<div className="overflow-hidden">
|
||||||
columns={[
|
<DataTable
|
||||||
{
|
columns={[
|
||||||
key: 'title',
|
{
|
||||||
title: '活动名称',
|
key: 'title',
|
||||||
width: '30%',
|
title: '活动名称',
|
||||||
render: (r: any) => (
|
width: '30%',
|
||||||
<div className="font-medium leading-snug break-words max-w-[280px]">{r.title}</div>
|
render: (r: any) => (
|
||||||
),
|
<div className="font-medium leading-snug break-words max-w-[280px]">{r.title}</div>
|
||||||
},
|
),
|
||||||
{
|
},
|
||||||
key: 'time',
|
{
|
||||||
title: '活动时间',
|
key: 'time',
|
||||||
width: '240px',
|
title: '活动时间',
|
||||||
render: (r: any) => (
|
width: '240px',
|
||||||
<div className="text-xs font-mono tracking-wide leading-snug whitespace-nowrap pl-3 pr-2 text-gray-700 text-left">
|
render: (r: any) => (
|
||||||
{formatDateRange(r.start_time, r.end_time)}
|
<div className="text-xs font-mono tracking-wide leading-snug whitespace-nowrap pl-3 pr-2 text-gray-700 text-left">
|
||||||
</div>
|
{formatDateRange(r.start_time, r.end_time)}
|
||||||
),
|
</div>
|
||||||
},
|
),
|
||||||
{
|
},
|
||||||
key: 'hours',
|
{
|
||||||
title: '时长',
|
key: 'hours',
|
||||||
width: '100px',
|
title: '时长',
|
||||||
render: (r: any) => (
|
width: '100px',
|
||||||
<span className="font-semibold text-primary-600 text-center block w-full text-sm">
|
render: (r: any) => (
|
||||||
{Number(r.hours || 0).toFixed(1)}h
|
<span className="font-semibold text-primary-600 text-center block w-full text-sm">
|
||||||
</span>
|
{Number(r.hours || 0).toFixed(1)}h
|
||||||
),
|
</span>
|
||||||
},
|
),
|
||||||
{
|
},
|
||||||
key: 'remark',
|
{
|
||||||
title: '备注',
|
key: 'remark',
|
||||||
render: (r: any) => (
|
title: '备注',
|
||||||
<div className="leading-relaxed break-words text-gray-700 pr-2">
|
render: (r: any) => (
|
||||||
{r.remark || <span className="muted">—</span>}
|
<div className="leading-relaxed break-words text-gray-700 pr-2">
|
||||||
</div>
|
{r.remark || <span className="muted">—</span>}
|
||||||
),
|
</div>
|
||||||
},
|
),
|
||||||
]}
|
},
|
||||||
data={detail.activities}
|
]}
|
||||||
rowKey={(r: any) => `${r.id}-${r.reg_at || r.hours}-${r.remark || ''}`}
|
data={detail.activities}
|
||||||
/>
|
rowKey={(r: any) => `${r.id}-${r.reg_at || r.hours}-${r.remark || ''}`}
|
||||||
|
/>
|
||||||
|
{detail.activities.length > 0 && (
|
||||||
|
<div className="mt-1 px-3 py-2.5 rounded-b-md border-t border-gray-200 bg-primary-50/60 flex items-center justify-between">
|
||||||
|
<div className="text-sm font-medium text-gray-700">
|
||||||
|
本页列表合计 <span className="text-gray-500 text-xs ml-1">(应等于上方卡片的累计时长)</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-lg font-bold text-primary-700">
|
||||||
|
{detail.activities.reduce((s: number, r: any) => s + Number(r.hours || 0), 0).toFixed(1)}h
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ router.get('/:id', (req: Request, res: Response) => {
|
|||||||
WHERE h.user_id = ?
|
WHERE h.user_id = ?
|
||||||
ORDER BY h.created_at DESC, a.start_time DESC
|
ORDER BY h.created_at DESC, a.start_time DESC
|
||||||
`).all(id);
|
`).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 });
|
res.json({ ...user, activities });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user