feat(mileage): add daily mileage columns to main export sheet
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- '车辆汇总' 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
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ln-bi",
|
"name": "ln-bi",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.8",
|
"version": "1.1.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently -n server,client -c blue,green \"npm run dev:server\" \"npm run dev:client\"",
|
"dev": "concurrently -n server,client -c blue,green \"npm run dev:server\" \"npm run dev:client\"",
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ interface ExportContext {
|
|||||||
sortBy: 'today' | 'total';
|
sortBy: 'today' | 'total';
|
||||||
}
|
}
|
||||||
|
|
||||||
const HEADERS = [
|
const BASE_HEADERS = [
|
||||||
'状态', '车牌号', '客户', '业务部门', '项目', '租赁状态',
|
'状态', '车牌号', '客户', '业务部门', '项目', '租赁状态',
|
||||||
'运营区域', '区间里程(km)', '累计里程(km)',
|
'运营区域',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
function statusLabel(v: MonitoringVehicle): string {
|
function statusLabel(v: MonitoringVehicle): string {
|
||||||
@@ -20,7 +20,6 @@ function statusLabel(v: MonitoringVehicle): string {
|
|||||||
|
|
||||||
function mileageCell(v: MonitoringVehicle, kind: 'today' | 'total'): string | number {
|
function mileageCell(v: MonitoringVehicle, kind: 'today' | 'total'): string | number {
|
||||||
if (kind === 'today') {
|
if (kind === 'today') {
|
||||||
// 区间内未对接但有历史累计,视作区间 0;只有完全无数据才标「未对接」。
|
|
||||||
if (!v.isDataSynced && v.totalKm == null) return '未对接';
|
if (!v.isDataSynced && v.totalKm == null) return '未对接';
|
||||||
return Math.max(0, v.dailyKm || 0);
|
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 start = ctx.startDate || ctx.date || '';
|
||||||
const end = ctx.endDate || ctx.date || '';
|
const end = ctx.endDate || ctx.date || '';
|
||||||
const isRange = !!start && !!end && start !== end;
|
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)[][] = [
|
const summaryData: (string | number)[][] = [
|
||||||
[...HEADERS],
|
summaryHeaders,
|
||||||
...vehicles.map(v => [
|
...vehicles.map(v => {
|
||||||
statusLabel(v),
|
const baseRow = [
|
||||||
v.plate,
|
statusLabel(v),
|
||||||
v.customer || '',
|
v.plate,
|
||||||
v.department || '',
|
v.customer || '',
|
||||||
v.project || '',
|
v.department || '',
|
||||||
v.rentStatus || '',
|
v.project || '',
|
||||||
v.region || '',
|
v.rentStatus || '',
|
||||||
mileageCell(v, 'today'),
|
v.region || '',
|
||||||
mileageCell(v, 'total'),
|
];
|
||||||
]),
|
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 ws = XLSX.utils.aoa_to_sheet(summaryData);
|
||||||
|
|
||||||
ws['!cols'] = [
|
const numFixedCols = BASE_HEADERS.length;
|
||||||
|
const wsCols: { wch: number }[] = [
|
||||||
{ wch: 8 }, // 状态
|
{ wch: 8 }, // 状态
|
||||||
{ wch: 12 }, // 车牌号
|
{ wch: 12 }, // 车牌号
|
||||||
{ wch: 28 }, // 客户
|
{ wch: 28 }, // 客户
|
||||||
@@ -56,21 +77,24 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont
|
|||||||
{ wch: 16 }, // 项目
|
{ wch: 16 }, // 项目
|
||||||
{ wch: 10 }, // 租赁状态
|
{ wch: 10 }, // 租赁状态
|
||||||
{ wch: 12 }, // 运营区域
|
{ 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 (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 });
|
const ref = XLSX.utils.encode_cell({ r, c });
|
||||||
if (ws[ref]?.t === 'n') ws[ref].z = '0.##########';
|
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 });
|
const ref = XLSX.utils.encode_cell({ r: 0, c });
|
||||||
if (ws[ref]) {
|
if (ws[ref]) {
|
||||||
(ws[ref] as { s?: unknown }).s = {
|
(ws[ref] as { s?: unknown }).s = {
|
||||||
@@ -84,9 +108,7 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont
|
|||||||
const wb = XLSX.utils.book_new();
|
const wb = XLSX.utils.book_new();
|
||||||
XLSX.utils.book_append_sheet(wb, ws, '车辆汇总');
|
XLSX.utils.book_append_sheet(wb, ws, '车辆汇总');
|
||||||
|
|
||||||
const dayKeys = Array.from(
|
// 每日明细 sheet:保留原有格式
|
||||||
new Set(vehicles.flatMap(v => Object.keys(v.dailyMileage || {})))
|
|
||||||
).sort();
|
|
||||||
if (dayKeys.length > 0) {
|
if (dayKeys.length > 0) {
|
||||||
const detailHeaders = [
|
const detailHeaders = [
|
||||||
'车牌号', '客户', '业务部门', '项目', '租赁状态', '运营区域',
|
'车牌号', '客户', '业务部门', '项目', '租赁状态', '运营区域',
|
||||||
|
|||||||
Reference in New Issue
Block a user