feat(mileage): 外部三选筛选 + 车牌多选粘贴 + 运营区域 + xlsx 下载
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- 外部行改为 批次型号 / 运营区域 / 车牌多选;按部门、按客户移入详情面板 - 车牌多选支持从 Excel 粘贴(换行/逗号/空格分隔),未匹配项显示警告 - 新增运营区域筛选:基于 136 批次区域映射(华东/华南/西南/西北) - 新增 xlsx 数据下载,导出当前筛选结果(带表头样式与列宽) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
81
src/modules/mileage/xlsx-export.ts
Normal file
81
src/modules/mileage/xlsx-export.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import * as XLSX from 'xlsx';
|
||||
import type { MonitoringVehicle } from './types';
|
||||
|
||||
interface ExportContext {
|
||||
date: string;
|
||||
sortBy: 'today' | 'total';
|
||||
}
|
||||
|
||||
const HEADERS = [
|
||||
'状态', '车牌号', '客户', '业务部门', '项目', '租赁状态',
|
||||
'运营区域', '今日里程(km)', '累计里程(km)',
|
||||
] as const;
|
||||
|
||||
function statusLabel(v: MonitoringVehicle): string {
|
||||
if (!v.isDataSynced) return '未对接';
|
||||
return v.isOnline ? '在线' : '离线';
|
||||
}
|
||||
|
||||
function mileageCell(v: MonitoringVehicle, kind: 'today' | 'total'): string | number {
|
||||
if (!v.isDataSynced) return '未对接';
|
||||
if (kind === 'today') return Math.max(0, Math.round(v.dailyKm || 0));
|
||||
return v.totalKm != null ? Math.round(v.totalKm) : '未对接';
|
||||
}
|
||||
|
||||
export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportContext): void {
|
||||
const data: (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'),
|
||||
]),
|
||||
];
|
||||
|
||||
const ws = XLSX.utils.aoa_to_sheet(data);
|
||||
|
||||
ws['!cols'] = [
|
||||
{ wch: 8 }, // 状态
|
||||
{ wch: 12 }, // 车牌号
|
||||
{ wch: 28 }, // 客户
|
||||
{ wch: 14 }, // 业务部门
|
||||
{ wch: 16 }, // 项目
|
||||
{ wch: 10 }, // 租赁状态
|
||||
{ wch: 12 }, // 运营区域
|
||||
{ wch: 14 }, // 今日里程
|
||||
{ wch: 14 }, // 累计里程
|
||||
];
|
||||
|
||||
ws['!freeze'] = { xSplit: 0, ySplit: 1 } as never;
|
||||
|
||||
// 表头样式(在客户端 SheetJS 社区版仅基本样式生效)
|
||||
for (let c = 0; c < HEADERS.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, '里程明细');
|
||||
|
||||
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 = ctx.date ? ctx.date.replace(/-/g, '') : `${y}${m}${d}`;
|
||||
const filename = `里程看板_${dateTag}_${hh}${mm}_${ctx.sortBy === 'today' ? '今日' : '累计'}.xlsx`;
|
||||
XLSX.writeFile(wb, filename);
|
||||
}
|
||||
Reference in New Issue
Block a user