57 lines
3.4 KiB
TypeScript
57 lines
3.4 KiB
TypeScript
import type { VehicleListItem } from './types';
|
||
|
||
export type ExportRow = Record<string, string | number | null | undefined>;
|
||
|
||
/** Use complete filtered groups, never the progressively rendered slice. Repeat parent labels for Excel filtering. */
|
||
export function statisticsExportRows(labels: string[], groups: { values: string[]; count: number; extra?: ExportRow }[], total: number, unit = '台'): ExportRow[] {
|
||
return groups.map(group => ({
|
||
...Object.fromEntries(labels.length ? labels.map((label, index) => [label, group.values[index]]) : [['统计范围', '当前筛选结果']]),
|
||
[`数量(${unit})`]: group.count,
|
||
...group.extra,
|
||
'占比(%)': total ? Number((group.count / total * 100).toFixed(1)) : 0,
|
||
}));
|
||
}
|
||
|
||
export function vehicleExportRows(vehicles: VehicleListItem[]): ExportRow[] {
|
||
const status: Record<string, string> = { Abnormal: '异动', Pending: '待交车', Inventory: '在库', Operating: '运营中' };
|
||
const operation: Record<string, string> = { '1': '租赁', '2': '自营', '3': '可运营', '4': '待运营' };
|
||
const hasPending = vehicles.some(v => v.pendingDelivery);
|
||
const hasMovement = vehicles.some(v => v.abnormalMove);
|
||
return vehicles.map(v => ({
|
||
车牌: v.plateNumber, VIN: v.vin, 品牌: v.brandLabel, 车型: v.type, 型号: v.model,
|
||
省份: v.province, 城市: v.city || v.location,
|
||
状态: status[v.status] || v.status, 运营状态: operation[v.operationStatus || ''] || v.operationStatus,
|
||
客户: v.customerName, 签约公司: v.orgName, 部门: v.departmentName, 业务负责人: v.customerManager,
|
||
所属公司: v.subjectOrg, 合同: v.contractNo,
|
||
...(hasPending ? { 待交车合同: v.pendingDelivery?.contractNo, 待交车客户: v.pendingDelivery?.customerName } : {}),
|
||
...(hasMovement ? {
|
||
异动类型: v.abnormalMove?.type, 审批通过时间: v.abnormalMove?.approvalPassedAt,
|
||
'已异动时长(天)': v.abnormalMove?.elapsedDays == null ? null : Number(v.abnormalMove.elapsedDays.toFixed(1)),
|
||
具体工作: v.abnormalMove?.work, 申请人: v.abnormalMove?.applicant,
|
||
预计完成时间: v.abnormalMove?.targetCompletionTime,
|
||
实际完成时间: v.abnormalMove?.actualCompletionTime || (v.abnormalMove?.status === '待结束' ? '未完成' : null),
|
||
} : {}),
|
||
}));
|
||
}
|
||
|
||
async function analysisSheets(statistics: ExportRow[], details: ExportRow[]) {
|
||
const { buildJsonSheet } = await import('../../shared/xlsx');
|
||
return ([['统计表', statistics], ['详情表', details]] as const).map(([name, rows]) => {
|
||
const sheet = buildJsonSheet(rows.map(row => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, value ?? '—']))));
|
||
sheet['!cols'] = Object.keys(rows[0] || {}).map(label => ({ wch: /时间|合同|项目|工作/.test(label) ? 28 : 18 }));
|
||
if (sheet['!ref']) sheet['!autofilter'] = { ref: sheet['!ref'] };
|
||
return { name, sheet };
|
||
});
|
||
}
|
||
|
||
export async function createAnalysisWorkbook(statistics: ExportRow[], details: ExportRow[]) {
|
||
const { createWorkbook } = await import('../../shared/xlsx');
|
||
return createWorkbook(await analysisSheets(statistics, details));
|
||
}
|
||
|
||
export async function downloadAnalysisWorkbook(statistics: ExportRow[], details: ExportRow[], title: string) {
|
||
const { writeWorkbook } = await import('../../shared/xlsx');
|
||
const safeTitle = title.replace(/[\\/:*?"<>|\x00-\x1f]/g, '-').slice(0, 120);
|
||
writeWorkbook(await analysisSheets(statistics, details), `${safeTitle}.xlsx`, { compression: true });
|
||
}
|