183 lines
5.6 KiB
TypeScript
183 lines
5.6 KiB
TypeScript
import * as XLSX from 'xlsx';
|
|
import type { MonitoringVehicle } from './types';
|
|
|
|
interface ExportContext {
|
|
date?: string;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
sortBy: 'today' | 'total' | 'statisticTime';
|
|
}
|
|
|
|
const BASE_HEADERS = [
|
|
'状态', '数据来源', '车牌号', '客户', '业务部门', '项目', '租赁状态',
|
|
'运营区域',
|
|
] as const;
|
|
|
|
function sourceLabel(v: MonitoringVehicle): string {
|
|
if (v.sourceCategory === 'INSTRUMENT') return '仪表数据';
|
|
if (v.sourceCategory === 'GPS') return 'GPS数据';
|
|
if (v.sourceCategory === 'MIXED') return '仪表数据+GPS数据';
|
|
return v.isDataSynced ? '来源待接口' : '无数据';
|
|
}
|
|
|
|
function statusLabel(v: MonitoringVehicle): string {
|
|
if (!v.isDataSynced) return '未对接';
|
|
return v.isOnline ? '在线' : '离线';
|
|
}
|
|
|
|
function mileageCell(v: MonitoringVehicle, kind: 'today' | 'total'): string | number {
|
|
if (kind === 'today') {
|
|
if (!v.isDataSynced && v.totalKm == null) return '未对接';
|
|
return Math.max(0, v.dailyKm || 0);
|
|
}
|
|
return v.totalKm != null ? v.totalKm : '未对接';
|
|
}
|
|
|
|
export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportContext): void {
|
|
const start = ctx.startDate || ctx.date || '';
|
|
const end = ctx.endDate || ctx.date || '';
|
|
const isRange = !!start && !!end && start !== end;
|
|
|
|
const dayKeys = Array.from(
|
|
new Set(vehicles.flatMap(v => Object.keys(v.dailyMileage || {})))
|
|
).sort();
|
|
|
|
// 车辆汇总 sheet:基础列 + 每日里程列 + 区间合计 + 累计里程
|
|
const dailyHeaders = dayKeys.map(day => `${day.slice(5)}里程(km)`);
|
|
const summaryHeaders = [
|
|
...BASE_HEADERS,
|
|
...(dayKeys.length > 0 ? dailyHeaders : []),
|
|
isRange ? '区间合计(km)' : '当日里程(km)',
|
|
'累计里程(km)',
|
|
];
|
|
|
|
const summaryData: (string | number)[][] = [
|
|
summaryHeaders,
|
|
...vehicles.map(v => {
|
|
const baseRow = [
|
|
statusLabel(v),
|
|
sourceLabel(v),
|
|
v.plate,
|
|
v.customer || '',
|
|
v.department || '',
|
|
v.project || '',
|
|
v.rentStatus || '',
|
|
v.region || '',
|
|
];
|
|
const dailyCols = dayKeys.map(day => v.dailyMileage?.[day] ?? 0);
|
|
return [
|
|
...baseRow,
|
|
...dailyCols,
|
|
mileageCell(v, 'today'),
|
|
mileageCell(v, 'total'),
|
|
];
|
|
}),
|
|
];
|
|
|
|
const ws = XLSX.utils.aoa_to_sheet(summaryData);
|
|
|
|
const numFixedCols = BASE_HEADERS.length;
|
|
const wsCols: { wch: number }[] = [
|
|
{ wch: 8 }, // 状态
|
|
{ wch: 16 }, // 数据来源
|
|
{ wch: 12 }, // 车牌号
|
|
{ wch: 28 }, // 客户
|
|
{ wch: 14 }, // 业务部门
|
|
{ wch: 16 }, // 项目
|
|
{ wch: 10 }, // 租赁状态
|
|
{ wch: 12 }, // 运营区域
|
|
];
|
|
for (let i = 0; i < dayKeys.length; i++) wsCols.push({ wch: 12 });
|
|
wsCols.push({ wch: 14 }); // 区间合计
|
|
wsCols.push({ wch: 14 }); // 累计里程
|
|
ws['!cols'] = wsCols;
|
|
|
|
// 冻结首行 + 固定列
|
|
ws['!freeze'] = { xSplit: numFixedCols, ySplit: 1 } as never;
|
|
|
|
for (let r = 1; r < summaryData.length; r++) {
|
|
for (let c = numFixedCols; c < summaryHeaders.length; c++) {
|
|
const ref = XLSX.utils.encode_cell({ r, c });
|
|
if (ws[ref]?.t === 'n') ws[ref].z = '0.##########';
|
|
}
|
|
}
|
|
|
|
// 表头样式
|
|
for (let c = 0; c < summaryHeaders.length; c++) {
|
|
const ref = XLSX.utils.encode_cell({ r: 0, c });
|
|
if (ws[ref]) {
|
|
(ws[ref] as { s?: unknown }).s = {
|
|
font: { bold: true, color: { rgb: 'FFFFFF' } },
|
|
fill: { fgColor: { rgb: '2563EB' } },
|
|
alignment: { horizontal: 'center', vertical: 'center' },
|
|
};
|
|
}
|
|
}
|
|
|
|
const wb = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(wb, ws, '车辆汇总');
|
|
|
|
// 每日明细 sheet:保留原有格式
|
|
if (dayKeys.length > 0) {
|
|
const detailHeaders = [
|
|
'车牌号', '数据来源', '客户', '业务部门', '项目', '租赁状态', '运营区域',
|
|
...dayKeys.map(day => `${day}里程(km)`),
|
|
'区间合计(km)',
|
|
'累计里程(km)',
|
|
];
|
|
const detailData: (string | number)[][] = [
|
|
detailHeaders,
|
|
...vehicles.map(v => [
|
|
v.plate,
|
|
sourceLabel(v),
|
|
v.customer || '',
|
|
v.department || '',
|
|
v.project || '',
|
|
v.rentStatus || '',
|
|
v.region || '',
|
|
...dayKeys.map(day => v.dailyMileage?.[day] || 0),
|
|
Math.max(0, v.dailyKm || 0),
|
|
v.totalKm != null ? v.totalKm : '',
|
|
]),
|
|
];
|
|
const detailWs = XLSX.utils.aoa_to_sheet(detailData);
|
|
detailWs['!cols'] = [
|
|
{ wch: 12 },
|
|
{ wch: 16 },
|
|
{ wch: 28 },
|
|
{ wch: 14 },
|
|
{ wch: 16 },
|
|
{ wch: 10 },
|
|
{ wch: 12 },
|
|
...dayKeys.map(() => ({ wch: 14 })),
|
|
{ wch: 14 },
|
|
{ wch: 14 },
|
|
];
|
|
detailWs['!freeze'] = { xSplit: 7, ySplit: 1 } as never;
|
|
for (let r = 1; r < detailData.length; r++) {
|
|
for (let c = 7; c < detailHeaders.length; c++) {
|
|
const ref = XLSX.utils.encode_cell({ r, c });
|
|
if (detailWs[ref]?.t === 'n') detailWs[ref].z = '0.##########';
|
|
}
|
|
}
|
|
XLSX.utils.book_append_sheet(wb, detailWs, '每日明细');
|
|
}
|
|
|
|
const now = new Date();
|
|
const y = now.getFullYear();
|
|
const m = String(now.getMonth() + 1).padStart(2, '0');
|
|
const d = String(now.getDate()).padStart(2, '0');
|
|
const hh = String(now.getHours()).padStart(2, '0');
|
|
const mm = String(now.getMinutes()).padStart(2, '0');
|
|
const dateTag = start && end
|
|
? `${start.replace(/-/g, '')}-${end.replace(/-/g, '')}`
|
|
: `${y}${m}${d}`;
|
|
const sortLabel = ctx.sortBy === 'total'
|
|
? '累计'
|
|
: ctx.sortBy === 'statisticTime'
|
|
? '统计时间'
|
|
: isRange ? '区间' : '今日';
|
|
const filename = `里程看板_${dateTag}_${hh}${mm}_${sortLabel}.xlsx`;
|
|
XLSX.writeFile(wb, filename);
|
|
}
|