import * as XLSX from 'xlsx'; import type { MonitoringVehicle } from './types'; interface ExportContext { date?: string; startDate?: string; endDate?: string; sortBy: 'today' | 'total'; } 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 filename = `里程看板_${dateTag}_${hh}${mm}_${ctx.sortBy === 'today' ? (isRange ? '区间' : '今日') : '累计'}.xlsx`; XLSX.writeFile(wb, filename); }