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 / 截图留档 → 确认核销。