diff --git a/client/src/pages/admin/FourHourExport.tsx b/client/src/pages/admin/FourHourExport.tsx index bbec85c..78e2f4f 100644 --- a/client/src/pages/admin/FourHourExport.tsx +++ b/client/src/pages/admin/FourHourExport.tsx @@ -194,67 +194,42 @@ export default function FourHourExport() { const doExcel = () => { if (!preview) { alert('请先生成预览'); return; } + if (!preview.activity_matrix?.activities?.length || !preview.activity_matrix?.users?.length) { + alert('当前预览中无可导出的活动矩阵数据'); + return; + } const sheets: Parameters[0] = []; - - const flatRows = (preview.hours || []).map((h: any, i: number) => { - const hoursTxt = Number(h.hours || 0).toFixed(1) + (h.truncated ? '(截断)' : ''); - return { - '序号': i + 1, - '班级': h.class, - '姓名': h.name, - '义工号': h.volunteer_no || '-', - '工作组': h.group_name || '-', - '活动名称': h.activity_title, - '活动时间': String(h.activity_start_time || '').replace('T', ' ').slice(0, 16), - '本次计时长(小时)': hoursTxt, - '备注': h.remark || '-', + const { activities, users } = preview.activity_matrix; + const data = users.map((u: any) => { + const row: any = { + '部员': u.name, + '班级': u.class || '-', + '义工号': u.volunteer_no || '-', + '工作组': u.group_name || '-', }; + for (const a of activities) { + const h = Number(u[String(a.id)] || 0); + const rmk = (u as any)?._remarks?.[String(a.id)] as string | undefined; + const tr = (u as any)?._truncated?.[String(a.id)] as boolean | undefined; + let cell = h > 0 ? h.toFixed(1) : '—'; + if (tr) cell = cell + '(截断)'; + if (rmk) cell = cell + '\n备注:' + rmk; + const colTitle = `${a.title}(${String(a.start_time || '').slice(0,10)})`; + row[colTitle] = cell; + } + row['本次核销合计(h)'] = Number(u._total || 0).toFixed(1); + return row; }); - sheets.push({ name: '总合明细(4h核销)', data: flatRows }); - - if (preview.user_totals?.length) { - sheets.push({ - name: '部员核销汇总', - data: preview.user_totals.map((u: any) => ({ - '排名': u.rank, - '班级': u.class, - '姓名': u.name, - '义工号': u.volunteer_no || '-', - '工作组': u.group_name || '-', - '本次核销(h)': Number(u.exported_total || 0).toFixed(1), - '核销后剩余未导出(h)': Number(u.unexported_remaining || 0).toFixed(1), - })), - }); - } - - if (preview.activity_matrix?.activities?.length && preview.activity_matrix?.users?.length) { - const { activities, users } = preview.activity_matrix; - const data = users.map((u: any) => { - const row: any = { '部员': u.name, '义工号': u.volunteer_no || '-' }; - for (const a of activities) { - const h = Number(u[String(a.id)] || 0); - const rmk = (u as any)?._remarks?.[String(a.id)] as string | undefined; - const tr = (u as any)?._truncated?.[String(a.id)] as boolean | undefined; - let cell = h > 0 ? h.toFixed(1) : '—'; - if (tr) cell = cell + '(截断)'; - if (rmk) cell = cell + '\n备注:' + rmk; - row[a.title] = cell; - } - row['本次核销合计'] = Number(u._total || 0).toFixed(1); - return row; - }); - const firstAct = preview.activity_matrix.activities[0]; - sheets.push({ - name: '活动矩阵(4h核销)', - data, - hint: firstAct ? { - matrixColStart: String(firstAct.title), - matrixColEnd: '本次核销合计', - } : undefined, - }); - } - - exportToExcel(sheets, `义工时长-四小时导出-${utc8DateStamp()}.xlsx`); + const firstColTitle = activities[0] ? `${activities[0].title}(${String(activities[0].start_time || '').slice(0,10)})` : ''; + sheets.push({ + name: '活动矩阵总表(四小时核销)', + data, + hint: firstColTitle ? { + matrixColStart: firstColTitle, + matrixColEnd: '本次核销合计(h)', + } : undefined, + }); + exportToExcel(sheets, `义工时长-四小时导出-活动矩阵总表-${utc8DateStamp()}.xlsx`); }; const doImage = async () => { @@ -275,6 +250,8 @@ export default function FourHourExport() { 按部员维度:每位部员「尚未核销的累计义工时」≥ 4h 即列入当次导出。 单次活动若剩余时长超过 4h,显示时自动截断为 4.0(并标注「截断」),但原始登记时长不变。 点击「确认核销」后,本次合计小时数将写入核销记录,下次自动扣除、不会重复统计。 +
+ 导出 Excel 仅包含 活动矩阵总表 单一工作表(部员×活动,末尾含本次核销合计列)。

@@ -326,7 +303,7 @@ export default function FourHourExport() {
- 建议流程:生成预览 → 核对数据 → 导出 Excel/图片留档 → 确认核销。 + 建议流程:生成预览 → 核对活动矩阵 → 导出【活动矩阵总表】Excel / 截图留档 → 确认核销。