Files
ln-bi/src/modules/assets/analysis-export.ts
T

55 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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[], inventory = false): 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,
...(inventory ? { 运维负责人: v.opsManagers?.trim() || '-' } : { 客户: 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),
} : {}),
}));
}
export async function createAnalysisWorkbook(statistics: ExportRow[], details: ExportRow[]) {
const XLSX = await import('xlsx');
const workbook = XLSX.utils.book_new();
for (const [name, rows] of [['统计表', statistics], ['详情表', details]] as const) {
const sheet = XLSX.utils.json_to_sheet(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'] };
XLSX.utils.book_append_sheet(workbook, sheet, name);
}
return workbook;
}
export async function downloadAnalysisWorkbook(statistics: ExportRow[], details: ExportRow[], title: string) {
const workbook = await createAnalysisWorkbook(statistics, details);
const XLSX = await import('xlsx');
const safeTitle = title.replace(/[\\/:*?"<>|\x00-\x1f]/g, '-').slice(0, 120);
XLSX.writeFile(workbook, `${safeTitle}.xlsx`, { compression: true });
}