From 4d523cbce01b44874c1b2c2648c4e6f10150f07c Mon Sep 17 00:00:00 2001 From: kkfluous Date: Wed, 15 Jul 2026 14:30:16 +0800 Subject: [PATCH] feat(mileage): add daily mileage columns to main export sheet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - '车辆汇总' sheet now includes per-day mileage columns for date ranges - Daily columns shown as 'MM-DD里程(km)' between base info and summary cols - Freeze pane fixed at 7 base columns for horizontal scrolling - '每日明细' sheet retained as detailed backup - Bump version to 1.1.9 --- package.json | 2 +- src/modules/mileage/xlsx-export.ts | 72 +++++++++++++++++++----------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index ee3c5f7..b8912fe 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ln-bi", "private": true, - "version": "1.1.8", + "version": "1.1.9", "type": "module", "scripts": { "dev": "concurrently -n server,client -c blue,green \"npm run dev:server\" \"npm run dev:client\"", diff --git a/src/modules/mileage/xlsx-export.ts b/src/modules/mileage/xlsx-export.ts index a274059..f33f2b4 100644 --- a/src/modules/mileage/xlsx-export.ts +++ b/src/modules/mileage/xlsx-export.ts @@ -8,9 +8,9 @@ interface ExportContext { sortBy: 'today' | 'total'; } -const HEADERS = [ +const BASE_HEADERS = [ '状态', '车牌号', '客户', '业务部门', '项目', '租赁状态', - '运营区域', '区间里程(km)', '累计里程(km)', + '运营区域', ] as const; function statusLabel(v: MonitoringVehicle): string { @@ -20,7 +20,6 @@ function statusLabel(v: MonitoringVehicle): string { function mileageCell(v: MonitoringVehicle, kind: 'today' | 'total'): string | number { if (kind === 'today') { - // 区间内未对接但有历史累计,视作区间 0;只有完全无数据才标「未对接」。 if (!v.isDataSynced && v.totalKm == null) return '未对接'; return Math.max(0, v.dailyKm || 0); } @@ -31,24 +30,46 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont 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)[][] = [ - [...HEADERS], - ...vehicles.map(v => [ - statusLabel(v), - v.plate, - v.customer || '', - v.department || '', - v.project || '', - v.rentStatus || '', - v.region || '', - mileageCell(v, 'today'), - mileageCell(v, 'total'), - ]), + summaryHeaders, + ...vehicles.map(v => { + const baseRow = [ + statusLabel(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); - ws['!cols'] = [ + const numFixedCols = BASE_HEADERS.length; + const wsCols: { wch: number }[] = [ { wch: 8 }, // 状态 { wch: 12 }, // 车牌号 { wch: 28 }, // 客户 @@ -56,21 +77,24 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont { wch: 16 }, // 项目 { wch: 10 }, // 租赁状态 { wch: 12 }, // 运营区域 - { wch: 14 }, // 区间里程 - { wch: 14 }, // 累计里程 ]; + 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: 0, ySplit: 1 } as never; + // 冻结首行 + 固定列 + ws['!freeze'] = { xSplit: numFixedCols, ySplit: 1 } as never; for (let r = 1; r < summaryData.length; r++) { - for (const c of [7, 8]) { + 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.##########'; } } - // 表头样式(在客户端 SheetJS 社区版仅基本样式生效) - for (let c = 0; c < HEADERS.length; c++) { + // 表头样式 + 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 = { @@ -84,9 +108,7 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, '车辆汇总'); - const dayKeys = Array.from( - new Set(vehicles.flatMap(v => Object.keys(v.dailyMileage || {}))) - ).sort(); + // 每日明细 sheet:保留原有格式 if (dayKeys.length > 0) { const detailHeaders = [ '车牌号', '客户', '业务部门', '项目', '租赁状态', '运营区域',