refactor(stage5): 合并重复实现,移除失效的演示能力
Excel 导出(原先 4 份各自拼装 workbook) - 新增 src/shared/xlsx.ts 作为唯一实现:文件名统一 .xlsx、sheet 名截断到 31 字符, 对外提供 buildAoaSheet / buildJsonSheet / writeWorkbook / exportAoaSheet / exportJsonSheet。 只收敛"组装与写出"这一层,各调用方仍自行决定列宽、冻结与数字格式,导出样式不变。 - 迁移 assets(内联 json_to_sheet + writeFile)、mileage/xlsx-export、 hydrogen 的两份 helper(prototype-download.ts 与 download-xls.js,均已删除)。 高德地图(原先 2 份近乎逐行复制) - 新增 src/shared/amap.ts:SDK 版本、插件列表、安全码注入、底图参数、 控件位置与热力图色带只在此处定义;两个画布只保留各自的半径/透明度。 - 图例渐变条原先把同一组色值又写了一遍,改为复用 shared 的常量,图例与地图不会漂移。 数值格式化 - 两个下钻视图各自复制了同样的 formatNumber/format(共 42 处调用), 统一到 hydrogen/model/display-format.ts 的 formatFixed;默认路径与既有行为逐字一致。 - 原 display-format.ts 里的 finiteNumber/formatNumber/formatScaled 无任何生产调用方, 只被自己的测试引用;改为 formatFixed + blankForMissing 显式选项, 既保留了"真零 vs 不可用"的区分能力,也不再留无人使用的导出。测试同步重写。 失效的演示能力 - Blur / DemoModeProvider 恒为 enabled=false,等于永久 no-op: 移除 13 个文件里 50 处 <Blur> 包裹(渲染结果不变)、删除 components/Blur.tsx 与 Shell 中的 Provider,调用方直接渲染原表达式。 未做(刻意) - src/lib/cn.ts 不引入 tailwind-merge:全仓只有 2 处调用,不值得新增传递依赖。已在文档说明。 架构守护新增 2 条:只有 shared/amap.ts 可加载高德 SDK; modules/** 不得再出现 book_new / book_append_sheet / writeFile(。 lint / test(137) / build 全绿,可达性仍为 0 未引用文件。
This commit is contained in:
@@ -6,7 +6,6 @@ import {
|
||||
} from 'recharts';
|
||||
import type { MileageSourceGroup, MonitoringVehicle } from './types';
|
||||
import { fetchVehicleRecent, type VehicleRecentDay } from './api';
|
||||
import Blur from '../../components/Blur';
|
||||
|
||||
interface Props {
|
||||
vehicle: MonitoringVehicle | null;
|
||||
@@ -160,7 +159,7 @@ export default function VehicleDetailModal({ vehicle, onClose, sourcePriority }:
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-sm font-black text-slate-900 font-mono truncate"><Blur>{vehicle.plate}</Blur></span>
|
||||
<span className="text-sm font-black text-slate-900 font-mono truncate">{vehicle.plate}</span>
|
||||
<span className={`text-[8px] px-1 rounded font-bold ${vehicle.isOnline ? 'bg-green-50 text-green-600' : 'bg-slate-100 text-slate-400'}`}>
|
||||
{vehicle.isOnline ? '在线' : '离线'}
|
||||
</span>
|
||||
@@ -169,7 +168,7 @@ export default function VehicleDetailModal({ vehicle, onClose, sourcePriority }:
|
||||
{vehicle.rentStatus || ''}
|
||||
{vehicle.department ? ` · ${vehicle.department.replace('业务', '')}` : ''}
|
||||
{vehicle.customer ? ` · ` : ''}
|
||||
{vehicle.customer && <Blur>{vehicle.customer}</Blur>}
|
||||
{vehicle.customer}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from 'recharts';
|
||||
import Blur from '../../../components/Blur';
|
||||
import type { MileageReportGroup } from '../api';
|
||||
import {
|
||||
filterAndSortVehicles,
|
||||
@@ -167,7 +166,7 @@ export default function VehicleTable({ group }: { group: MileageReportGroup }) {
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 text-xs font-black text-slate-900">
|
||||
<BarChart3 size={15} className="text-blue-600" />
|
||||
<Blur>{trendVehicle.plate}</Blur>
|
||||
{trendVehicle.plate}
|
||||
<span className="font-bold text-slate-400">近7日里程</span>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap gap-x-5 gap-y-1 text-[10px] font-bold text-slate-500">
|
||||
@@ -249,12 +248,12 @@ export default function VehicleTable({ group }: { group: MileageReportGroup }) {
|
||||
className={`grid cursor-pointer grid-cols-[112px_88px_minmax(140px,1fr)_82px_90px_80px_28px] items-center gap-3 px-3 py-2.5 text-[11px] font-bold outline-none transition-colors hover:bg-slate-50 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500 md:grid-cols-[110px_72px_100px_minmax(160px,1fr)_90px_100px_88px_32px] ${selected ? 'bg-blue-50/60' : ''}`}
|
||||
>
|
||||
<span className={`sticky left-3 z-10 -my-2.5 flex self-stretch flex-col justify-center font-mono font-black text-slate-800 shadow-[8px_0_12px_-12px_rgba(15,23,42,0.45)] md:flex-row md:items-center md:shadow-none ${frozenCellClass}`}>
|
||||
<Blur>{vehicle.plate}</Blur>
|
||||
{vehicle.plate}
|
||||
<span className={`mt-0.5 font-sans text-[9px] md:hidden ${vehicle.mileageBand === 'INVENTORY' ? 'text-amber-600' : 'text-emerald-600'}`}>{vehicle.status}</span>
|
||||
</span>
|
||||
<span className={`sticky left-[110px] z-10 -my-2.5 hidden self-stretch items-center shadow-[8px_0_12px_-12px_rgba(15,23,42,0.45)] md:flex ${frozenCellClass} ${vehicle.mileageBand === 'INVENTORY' ? 'text-amber-600' : 'text-emerald-600'}`}>{vehicle.status}</span>
|
||||
<span className="truncate text-slate-500">{vehicle.department || vehicle.inventoryRegion || '未标注'}</span>
|
||||
<span className="truncate text-slate-500"><Blur>{vehicle.customer || '未绑定客户'}</Blur></span>
|
||||
<span className="truncate text-slate-500">{vehicle.customer || '未绑定客户'}</span>
|
||||
<span className={`text-right tabular-nums ${vehicle.mileageBand === 'HIGH' ? 'text-blue-600' : vehicle.mileageBand === 'INVENTORY' && vehicle.dailyMileage > 0 ? 'text-rose-600' : 'text-slate-700'}`}>
|
||||
{fmtKm(vehicle.dailyMileage)} km
|
||||
</span>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { ArrowDown, ArrowUp, Minimize2, RotateCcw } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
import Blur from '../../../../components/Blur';
|
||||
import type { MileageSourceGroup, MonitoringFilters, MonitoringStats, MonitoringVehicle } from '../../types';
|
||||
import { vehicleStatisticTime } from '../oneos-time';
|
||||
import { MILEAGE_SOURCE_META, vehicleSourceDisplay } from '../source-display';
|
||||
@@ -292,7 +291,7 @@ export default function FullscreenMonitoring({
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="text-xs font-bold text-white"><Blur>{v.plate}</Blur></div>
|
||||
<div className="text-xs font-bold text-white">{v.plate}</div>
|
||||
<span
|
||||
className={`inline-flex shrink-0 rounded px-1.5 py-0.5 text-[7px] font-black ${sourceDisplay.className}`}
|
||||
title={sourceDisplay.title}
|
||||
@@ -304,7 +303,7 @@ export default function FullscreenMonitoring({
|
||||
{statisticTime.label}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-[11px] text-slate-400"><Blur>{v.customer || '-'}</Blur></td>
|
||||
<td className="px-3 py-2 text-[11px] text-slate-400">{v.customer || '-'}</td>
|
||||
<td className="px-3 py-2 text-[11px] text-slate-400">{v.brand || '-'}</td>
|
||||
<td className="px-3 py-2 text-[11px] text-slate-400">{v.rentStatus || '-'}</td>
|
||||
<td className="px-3 py-2 text-[11px] text-slate-400">{v.department || '-'}</td>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { RefObject } from 'react';
|
||||
import { Truck } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
import Blur from '../../../../components/Blur';
|
||||
import type { MonitoringVehicle } from '../../types';
|
||||
import { vehicleStatisticTime } from '../oneos-time';
|
||||
import { vehicleSourceDisplay } from '../source-display';
|
||||
@@ -73,7 +72,7 @@ export default function VehicleList({
|
||||
</div>
|
||||
<div className="overflow-hidden flex-1">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<span className="text-xs font-black text-slate-900 font-mono"><Blur>{v.plate}</Blur></span>
|
||||
<span className="text-xs font-black text-slate-900 font-mono">{v.plate}</span>
|
||||
<span className={`text-[8px] px-1 rounded ${v.isOnline ? 'bg-green-50 text-green-600' : 'bg-slate-100 text-slate-400'} font-bold`}>
|
||||
{v.isOnline ? '在线' : '离线'}
|
||||
</span>
|
||||
@@ -89,12 +88,12 @@ export default function VehicleList({
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 md:hidden">
|
||||
<span className="text-[8px] text-slate-300 font-bold">{v.rentStatus || ''}{v.department ? ` · ${v.department.replace('业务', '')}` : ''}</span>
|
||||
<span className="text-[9px] font-bold text-slate-600 truncate"><Blur>{v.customer || '-'}</Blur></span>
|
||||
<span className="text-[9px] font-bold text-slate-600 truncate">{v.customer || '-'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden min-w-0 md:block">
|
||||
<div className="truncate text-xs font-bold text-slate-700"><Blur>{v.customer || '-'}</Blur></div>
|
||||
<div className="truncate text-xs font-bold text-slate-700">{v.customer || '-'}</div>
|
||||
<div className="mt-1 truncate text-[9px] font-medium text-slate-400">
|
||||
{[v.rentStatus, v.department?.replace('业务', ''), v.project].filter(Boolean).join(' · ') || '暂无归属信息'}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import { ArrowUpDown, Calendar, Search, Truck, X } from 'lucide-react';
|
||||
import Blur from '../../../components/Blur';
|
||||
import type { TargetVehicle } from '../types';
|
||||
import {
|
||||
filterAndSortTargetVehicles,
|
||||
@@ -116,7 +115,7 @@ export default function AllVehiclesPanel({
|
||||
</div>
|
||||
<div className="overflow-hidden flex-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-xs font-black text-slate-900 font-mono"><Blur>{vehicle.plateNumber}</Blur></span>
|
||||
<span className="text-xs font-black text-slate-900 font-mono">{vehicle.plateNumber}</span>
|
||||
<span className={`text-[8px] px-1 rounded ${vehicle.isOnline ? 'bg-green-50 text-green-600' : 'bg-slate-100 text-slate-400'} font-bold`}>
|
||||
{vehicle.isOnline ? '在线' : '离线'}
|
||||
</span>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import { ChevronDown, Maximize2, Truck } from 'lucide-react';
|
||||
import Blur from '../../../components/Blur';
|
||||
import type { TargetSummary, TargetVehicle } from '../types';
|
||||
import { fmtDateLabel, fmtKm, fmtPercent, getTargetAssessment } from './model';
|
||||
|
||||
@@ -212,7 +211,7 @@ export default function TargetDetailPanel({
|
||||
{vehicles.slice(0, 5).map(vehicle => (
|
||||
<div key={vehicle.plateNumber} className="bg-slate-50/50/50 px-2 py-1.5 rounded-lg flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[10px] font-mono font-bold text-slate-700"><Blur>{vehicle.plateNumber}</Blur></span>
|
||||
<span className="text-[10px] font-mono font-bold text-slate-700">{vehicle.plateNumber}</span>
|
||||
<span className="text-[7px] px-1 rounded bg-green-100 text-green-600 font-bold">
|
||||
在线
|
||||
</span>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as XLSX from 'xlsx';
|
||||
import { buildAoaSheet, writeWorkbook } from '../../shared/xlsx';
|
||||
import type { MonitoringVehicle } from './types';
|
||||
|
||||
interface ExportContext {
|
||||
@@ -74,7 +75,7 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont
|
||||
}),
|
||||
];
|
||||
|
||||
const ws = XLSX.utils.aoa_to_sheet(summaryData);
|
||||
const ws = buildAoaSheet(summaryData);
|
||||
|
||||
const numFixedCols = BASE_HEADERS.length;
|
||||
const wsCols: { wch: number }[] = [
|
||||
@@ -114,10 +115,8 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont
|
||||
}
|
||||
}
|
||||
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, '车辆汇总');
|
||||
|
||||
// 每日明细 sheet:保留原有格式
|
||||
const sheets: Array<{ name: string; sheet: XLSX.WorkSheet }> = [{ name: '车辆汇总', sheet: ws }];
|
||||
if (dayKeys.length > 0) {
|
||||
const detailHeaders = [
|
||||
'车牌号', '数据来源', '客户', '业务部门', '项目', '租赁状态', '运营区域',
|
||||
@@ -140,7 +139,7 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont
|
||||
v.totalKm != null ? v.totalKm : '',
|
||||
]),
|
||||
];
|
||||
const detailWs = XLSX.utils.aoa_to_sheet(detailData);
|
||||
const detailWs = buildAoaSheet(detailData);
|
||||
detailWs['!cols'] = [
|
||||
{ wch: 12 },
|
||||
{ wch: 16 },
|
||||
@@ -160,7 +159,7 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont
|
||||
if (detailWs[ref]?.t === 'n') detailWs[ref].z = '0.##########';
|
||||
}
|
||||
}
|
||||
XLSX.utils.book_append_sheet(wb, detailWs, '每日明细');
|
||||
sheets.push({ name: '每日明细', sheet: detailWs });
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
@@ -178,5 +177,5 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont
|
||||
? '统计时间'
|
||||
: isRange ? '区间' : '今日';
|
||||
const filename = `里程看板_${dateTag}_${hh}${mm}_${sortLabel}.xlsx`;
|
||||
XLSX.writeFile(wb, filename);
|
||||
writeWorkbook(sheets, filename);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user