This commit is contained in:
@@ -194,43 +194,19 @@ export default function FourHourExport() {
|
||||
|
||||
const doExcel = () => {
|
||||
if (!preview) { alert('请先生成预览'); return; }
|
||||
const sheets: Parameters<typeof exportToExcel>[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 || '-',
|
||||
};
|
||||
});
|
||||
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) {
|
||||
alert('当前预览中无可导出的活动矩阵数据');
|
||||
return;
|
||||
}
|
||||
|
||||
if (preview.activity_matrix?.activities?.length && preview.activity_matrix?.users?.length) {
|
||||
const sheets: Parameters<typeof exportToExcel>[0] = [];
|
||||
const { activities, users } = preview.activity_matrix;
|
||||
const data = users.map((u: any) => {
|
||||
const row: any = { '部员': u.name, '义工号': u.volunteer_no || '-' };
|
||||
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;
|
||||
@@ -238,23 +214,22 @@ export default function FourHourExport() {
|
||||
let cell = h > 0 ? h.toFixed(1) : '—';
|
||||
if (tr) cell = cell + '(截断)';
|
||||
if (rmk) cell = cell + '\n备注:' + rmk;
|
||||
row[a.title] = cell;
|
||||
const colTitle = `${a.title}(${String(a.start_time || '').slice(0,10)})`;
|
||||
row[colTitle] = cell;
|
||||
}
|
||||
row['本次核销合计'] = Number(u._total || 0).toFixed(1);
|
||||
row['本次核销合计(h)'] = Number(u._total || 0).toFixed(1);
|
||||
return row;
|
||||
});
|
||||
const firstAct = preview.activity_matrix.activities[0];
|
||||
const firstColTitle = activities[0] ? `${activities[0].title}(${String(activities[0].start_time || '').slice(0,10)})` : '';
|
||||
sheets.push({
|
||||
name: '活动矩阵(4h核销)',
|
||||
name: '活动矩阵总表(四小时核销)',
|
||||
data,
|
||||
hint: firstAct ? {
|
||||
matrixColStart: String(firstAct.title),
|
||||
matrixColEnd: '本次核销合计',
|
||||
hint: firstColTitle ? {
|
||||
matrixColStart: firstColTitle,
|
||||
matrixColEnd: '本次核销合计(h)',
|
||||
} : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
exportToExcel(sheets, `义工时长-四小时导出-${utc8DateStamp()}.xlsx`);
|
||||
exportToExcel(sheets, `义工时长-四小时导出-活动矩阵总表-${utc8DateStamp()}.xlsx`);
|
||||
};
|
||||
|
||||
const doImage = async () => {
|
||||
@@ -275,6 +250,8 @@ export default function FourHourExport() {
|
||||
按部员维度:每位部员「尚未核销的累计义工时」≥ 4h 即列入当次导出。
|
||||
单次活动若剩余时长超过 4h,显示时自动截断为 4.0(并标注「截断」),但原始登记时长不变。
|
||||
点击「确认核销」后,本次合计小时数将写入核销记录,下次自动扣除、不会重复统计。
|
||||
<br />
|
||||
<span className="text-gray-600">导出 Excel 仅包含 <b className="text-primary-700">活动矩阵总表</b> 单一工作表(部员×活动,末尾含本次核销合计列)。</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -326,7 +303,7 @@ export default function FourHourExport() {
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-3 mt-5 pt-4 border-t border-primary-100/70">
|
||||
<div className="muted text-sm">
|
||||
建议流程:<span className="font-semibold text-gray-700">生成预览 → 核对数据 → 导出 Excel/图片留档 → 确认核销</span>。
|
||||
建议流程:<span className="font-semibold text-gray-700">生成预览 → 核对活动矩阵 → 导出【活动矩阵总表】Excel / 截图留档 → 确认核销</span>。
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap justify-end">
|
||||
<Button variant="secondary" onClick={loadPreview} disabled={loading}>
|
||||
|
||||
@@ -408,8 +408,10 @@ router.post('/four-hour/confirm', authMiddleware, (req: AuthRequest, res) => {
|
||||
insItem.run(exportId, it.hour_id, it.delta);
|
||||
}
|
||||
}
|
||||
try { db.execCompat('COMMIT'); } catch (err: any) {
|
||||
if (!/no transaction is active/i.test(String(err?.message || ''))) throw err;
|
||||
}
|
||||
db.save();
|
||||
db.execCompat('COMMIT');
|
||||
res.json({ ok: true, updated, skipped, total_processed: plan.length, export_id: exportId, total_applied: Number(totalApplied.toFixed(1)) });
|
||||
} catch (e: any) {
|
||||
try { db.execCompat('ROLLBACK'); } catch {}
|
||||
@@ -487,8 +489,10 @@ router.post('/four-hour/void', authMiddleware, (req: AuthRequest, res) => {
|
||||
`\n[已撤回 ${new Date().toISOString().slice(0,19).replace('T',' ')}${adminUsername ? ' by '+adminUsername : ''}]`,
|
||||
exportId
|
||||
);
|
||||
try { db.execCompat('COMMIT'); } catch (err: any) {
|
||||
if (!/no transaction is active/i.test(String(err?.message || ''))) throw err;
|
||||
}
|
||||
db.save();
|
||||
db.execCompat('COMMIT');
|
||||
res.json({ ok: true, export_id: exportId, rolled, skipped, rolled_hours: Number(rolledHours.toFixed(1)) });
|
||||
} catch (e: any) {
|
||||
try { db.execCompat('ROLLBACK'); } catch {}
|
||||
|
||||
Reference in New Issue
Block a user