refactor(stage2): 生产代码移出 src/vendor,氢能收敛为分层 feature

src/vendor 语义是'第三方/参考',却装着线上氢能看板并反向 import
src/modules,依赖方向倒挂。本次把生产代码归位并建立氢能分层:

  src/modules/energy/hydrogen/
    index.tsx        导航入口(原 HydrogenModule.tsx)
    api.ts types.ts  HTTP 与 DTO
    dev-mock-api.ts  仅 dev 的 vite 插件(仍由 vite.config.ts 引用)
    model/           纯逻辑(format / bearing-labels / daily-detail-format)
    board/           经营看板 UI(原 vendor energy-h2-bi-board)
    station-daily/   单站日报 UI(原 vendor energy-h2-station-daily)
    drill/           下钻弹层 UI(原 hydrogen-bi-v2 的 UI 部分)
    common/          工具与共享组件(原 vendor common + prototype-download)
    fonts/           JetBrains Mono(原 vendor resources/design-system)

- UI 与 CSS 逐字节保留,仅移动位置与改写相对 import。
- 删除自挂载原型入口后遗留的 2 个 annotation-source.json。
- vite.config.ts 与 tsconfig 的路径/exclude 同步清理。
- independent-entry.test.ts 不再硬编码路径,改为断言'两个入口解析到同一文件'。

导入改写用一次性 codemod 完成(按旧位置解析、按新位置重写),
lint / test(131) / build 全绿,可达性分析仍为 0 未引用文件。
This commit is contained in:
dsh-agent
2026-09-11 10:14:37 +08:00
parent 3daa9808ce
commit 7556dbcebc
64 changed files with 71 additions and 100 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ const EleImportPage = lazy(() => import("./modules/ele/EleImportPage"));
const FeedbackAdminPage = lazy(() => import("./modules/admin/FeedbackAdminPage")); const FeedbackAdminPage = lazy(() => import("./modules/admin/FeedbackAdminPage"));
const HydrogenPrototypeBoard = lazy( const HydrogenPrototypeBoard = lazy(
() => () =>
import("./vendor/lnbi-8113-exact/prototypes/energy-h2-bi-board/EnergyBiBoardApp").then( import("./modules/energy/hydrogen/board/EnergyBiBoardApp").then(
({ EnergyBiBoardApp }) => ({ default: EnergyBiBoardApp }), ({ EnergyBiBoardApp }) => ({ default: EnergyBiBoardApp }),
), ),
); );
+1 -1
View File
@@ -15,7 +15,7 @@ import type { PathSet } from './routing';
const AssetsModule = lazy(() => import('../modules/assets/AssetsModule')); const AssetsModule = lazy(() => import('../modules/assets/AssetsModule'));
const MileageModule = lazy(() => import('../modules/mileage/MileageModule')); const MileageModule = lazy(() => import('../modules/mileage/MileageModule'));
const SchedulingModule = lazy(() => import('../modules/scheduling/SchedulingModule')); const SchedulingModule = lazy(() => import('../modules/scheduling/SchedulingModule'));
const HydrogenModule = lazy(() => import('../modules/energy/HydrogenModule')); const HydrogenModule = lazy(() => import('../modules/energy/hydrogen/index'));
const ElectricModule = lazy(() => import('../modules/energy/ElectricModule')); const ElectricModule = lazy(() => import('../modules/energy/ElectricModule'));
const EtcModule = lazy(() => import('../modules/energy/EtcModule')); const EtcModule = lazy(() => import('../modules/energy/EtcModule'));
const VehicleHeatmapModule = lazy(() => import('../modules/vehicle-heatmap/VehicleHeatmapModule')); const VehicleHeatmapModule = lazy(() => import('../modules/vehicle-heatmap/VehicleHeatmapModule'));
@@ -1,12 +0,0 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
test("能源氢费 BI 入口与独立验收地址复用同一看板", () => {
const entry = readFileSync(new URL("../HydrogenModule.tsx", import.meta.url), "utf8");
const app = readFileSync(new URL("../../../App.tsx", import.meta.url), "utf8");
assert.match(entry, /vendor\/lnbi-8113-exact\/prototypes\/energy-h2-bi-board\/EnergyBiBoardApp/);
assert.match(entry, /return <EnergyBiBoardApp \/>/);
assert.match(app, /vendor\/lnbi-8113-exact\/prototypes\/energy-h2-bi-board\/EnergyBiBoardApp/);
});
@@ -20,8 +20,8 @@ import {
X, X,
Zap, Zap,
} from 'lucide-react'; } from 'lucide-react';
import { downloadExcelAoa } from '../../common/download-xls'; import { downloadExcelAoa } from '../common/download-xls';
import { MobileListFullscreenButton } from '../../common/MobileListFullscreenButton'; import { MobileListFullscreenButton } from '../common/MobileListFullscreenButton';
import { import {
SOURCE_LABEL, SOURCE_LABEL,
companyRowsForStats, companyRowsForStats,
@@ -56,12 +56,12 @@ import {
type HostView, type HostView,
type H2OrderRow, type H2OrderRow,
} from './types'; } from './types';
import { StationDailyApp } from '../energy-h2-station-daily/StationDailyApp'; import { StationDailyApp } from '../station-daily/StationDailyApp';
import '../energy-h2-station-daily/styles.css'; import '../station-daily/styles.css';
import './styles/energy-bi-board.css'; import './styles/energy-bi-board.css';
import { fetchH2BiDaily, fetchH2BiDailyTree, fetchH2BiMeta, fetchH2BiOverview } from '../../../../modules/energy/hydrogen-bi-v2/api'; import { fetchH2BiDaily, fetchH2BiDailyTree, fetchH2BiMeta, fetchH2BiOverview } from '../api';
import { PrototypeRealDailyView } from '../../../../modules/energy/hydrogen-bi-v2/prototype-real-daily'; import { PrototypeRealDailyView } from '../drill/prototype-real-daily';
import { PrototypeDrillModal, prototypeFleetScope } from '../../../../modules/energy/hydrogen-bi-v2/prototype-real-drills'; import { PrototypeDrillModal, prototypeFleetScope } from '../drill/prototype-real-drills';
type BoardScope = 'global' | 'station'; type BoardScope = 'global' | 'station';
type StatsTab = 'siteMonth' | 'customer'; type StatsTab = 'siteMonth' | 'customer';
@@ -3,7 +3,7 @@
* 本原型独立嵌入 bi-next禁止引入 OneOS V2 组件 * 本原型独立嵌入 bi-next禁止引入 OneOS V2 组件
* 字体例外汉字/UI 与数字等宽对齐 V2 TokenDESIGN §2.2 * 字体例外汉字/UI 与数字等宽对齐 V2 TokenDESIGN §2.2
*/ */
@import '../../../resources/design-system/fonts/jetbrains-mono/jetbrains-mono.css'; @import '../../fonts/jetbrains-mono/jetbrains-mono.css';
/* —— 访问口令门(轻门禁 · 对齐宿主蓝系,非 V2 组件) —— */ /* —— 访问口令门(轻门禁 · 对齐宿主蓝系,非 V2 组件) —— */
.ehb-gate { .ehb-gate {
@@ -3,8 +3,8 @@ import test from "node:test";
import { createElement } from "react"; import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server"; import { renderToStaticMarkup } from "react-dom/server";
import { DailyBranchState, DailyTreeButton } from "./daily-detail-controls"; import { DailyBranchState, DailyTreeButton } from "./daily-detail-controls";
import { dailySummaryRows, formatDailyChange } from "./daily-detail-format"; import { dailySummaryRows, formatDailyChange } from "../model/daily-detail-format";
import type { H2BiDailyResponse } from "./types"; import type { H2BiDailyResponse } from "../types";
test("每日环比区分真实零值、缺失值和非数值", () => { test("每日环比区分真实零值、缺失值和非数值", () => {
for (const value of [null, undefined, NaN, Infinity, "0"]) assert.equal(formatDailyChange(value), "环比 —"); for (const value of [null, undefined, NaN, Infinity, "0"]) assert.equal(formatDailyChange(value), "环比 —");
@@ -1,9 +1,9 @@
import { Fragment, useEffect, useMemo, useRef, useState } from "react"; import { Fragment, useEffect, useMemo, useRef, useState } from "react";
import { Download, RefreshCw, Truck } from "lucide-react"; import { Download, RefreshCw, Truck } from "lucide-react";
import { DailyTreeButton, DailyBranchState } from "./daily-detail-controls"; import { DailyTreeButton, DailyBranchState } from "./daily-detail-controls";
import { dailySummaryRows, formatDailyChange } from "./daily-detail-format"; import { dailySummaryRows, formatDailyChange } from "../model/daily-detail-format";
import { fetchH2BiDaily, fetchH2BiDailyTree, fetchH2BiDrill } from "./api"; import { fetchH2BiDaily, fetchH2BiDailyTree, fetchH2BiDrill } from "../api";
import { downloadExcelAoa } from "./prototype-download"; import { downloadExcelAoa } from "../common/prototype-download";
import "./real-daily-mobile.css"; import "./real-daily-mobile.css";
import type { import type {
H2BiDailyResponse, H2BiDailyResponse,
@@ -11,7 +11,7 @@ import type {
H2BiDrillResponse, H2BiDrillResponse,
H2BiQuery, H2BiQuery,
H2BiVehicleScope, H2BiVehicleScope,
} from "./types"; } from "../types";
const format = (value: number, digits = 2) => const format = (value: number, digits = 2) =>
value.toLocaleString("zh-CN", { value.toLocaleString("zh-CN", {
@@ -2,10 +2,10 @@ import { Component, Fragment, useEffect, useMemo, useRef, useState } from "react
import type { ErrorInfo, ReactNode } from "react"; import type { ErrorInfo, ReactNode } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { ChevronDown, ChevronLeft, Download, Search, SlidersHorizontal, Truck, X } from "lucide-react"; import { ChevronDown, ChevronLeft, Download, Search, SlidersHorizontal, Truck, X } from "lucide-react";
import { MobileListFullscreenButton } from "../../../vendor/lnbi-8113-exact/common/MobileListFullscreenButton"; import { MobileListFullscreenButton } from "../common/MobileListFullscreenButton";
import { fetchAllH2BiDrill, fetchH2BiDrill, fetchH2BiMeta } from "./api"; import { fetchAllH2BiDrill, fetchH2BiDrill, fetchH2BiMeta } from "../api";
import { downloadExcelAoa } from "./prototype-download"; import { downloadExcelAoa } from "../common/prototype-download";
import { bearingLabels } from "./bearing-labels"; import { bearingLabels } from "../model/bearing-labels";
import type { import type {
H2BiDrillGroupBy, H2BiDrillGroupBy,
H2BiDrillGroupRow, H2BiDrillGroupRow,
@@ -14,7 +14,7 @@ import type {
H2BiMetaResponse, H2BiMetaResponse,
H2BiQuery, H2BiQuery,
H2BiVehicleScope, H2BiVehicleScope,
} from "./types"; } from "../types";
import "./drill-prototype-parity.css"; import "./drill-prototype-parity.css";
type DrillKind = "kpi" | "customer" | "station" | "records"; type DrillKind = "kpi" | "customer" | "station" | "records";
@@ -0,0 +1,37 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
import test from "node:test";
const here = path.dirname(fileURLToPath(import.meta.url));
/** 抽出文件里指向某个模块的 import 说明符(支持静态 import 与动态 import())。 */
function importedSpecifier(source: string, moduleName: string): string | null {
const patterns = [
new RegExp(`from\\s*["']([^"']*${moduleName})["']`),
new RegExp(`import\\(\\s*["']([^"']*${moduleName})["']\\s*\\)`),
];
for (const re of patterns) {
const match = source.match(re);
if (match) return match[1];
}
return null;
}
test("能源氢费 BI 入口与独立验收地址复用同一看板", () => {
const entryPath = path.join(here, "index.tsx");
const appPath = path.join(here, "..", "..", "..", "App.tsx");
const entry = readFileSync(entryPath, "utf8");
const app = readFileSync(appPath, "utf8");
const entrySpec = importedSpecifier(entry, "EnergyBiBoardApp");
const appSpec = importedSpecifier(app, "EnergyBiBoardApp");
assert.ok(entrySpec, "氢费 BI 入口必须引用 EnergyBiBoardApp");
assert.ok(appSpec, "App.tsx 的独立验收路由必须引用 EnergyBiBoardApp");
// 不锁定具体路径,只锁定"两个入口解析到同一个文件",避免目录调整造成无意义漂移。
const resolve = (spec: string, from: string) => path.resolve(path.dirname(from), spec);
assert.equal(resolve(entrySpec!, entryPath), resolve(appSpec!, appPath));
assert.match(entry, /return <EnergyBiBoardApp \/>/);
});
@@ -1,4 +1,4 @@
import { EnergyBiBoardApp } from '../../vendor/lnbi-8113-exact/prototypes/energy-h2-bi-board/EnergyBiBoardApp'; import { EnergyBiBoardApp } from './board/EnergyBiBoardApp';
/** /**
* The accepted hydrogen fee BI surface. It is shared with the independent * The accepted hydrogen fee BI surface. It is shared with the independent
@@ -1,4 +1,4 @@
import type { H2BiDailyResponse } from "./types"; import type { H2BiDailyResponse } from "../types";
export function formatDailyChange(value: unknown): string { export function formatDailyChange(value: unknown): string {
if (typeof value !== "number" || !Number.isFinite(value)) return "环比 —"; if (typeof value !== "number" || !Number.isFinite(value)) return "环比 —";
@@ -5,9 +5,9 @@
*/ */
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { ArrowUpRight, Fuel, MapPin, RefreshCw, X } from 'lucide-react'; import { ArrowUpRight, Fuel, MapPin, RefreshCw, X } from 'lucide-react';
import { MobileListFullscreenButton } from '../../common/MobileListFullscreenButton'; import { MobileListFullscreenButton } from '../common/MobileListFullscreenButton';
import { fetchHydrogenStationBoard } from '../../../../modules/energy/api'; import { fetchHydrogenStationBoard } from '../../api';
import type { HydrogenStationBoardResponse } from '../../../../modules/energy/types'; import type { HydrogenStationBoardResponse } from '../../types';
import type { StationDailyVolumeRow } from './data/mockStationDaily'; import type { StationDailyVolumeRow } from './data/mockStationDaily';
import { SdDateRangePicker } from './SdDateRangePicker'; import { SdDateRangePicker } from './SdDateRangePicker';
import { StationDailyDetailView } from './StationDailyDetailView'; import { StationDailyDetailView } from './StationDailyDetailView';
@@ -5,17 +5,17 @@
*/ */
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { ArrowLeft, ChevronDown, ChevronUp, Download, RefreshCw } from 'lucide-react'; import { ArrowLeft, ChevronDown, ChevronUp, Download, RefreshCw } from 'lucide-react';
import { downloadExcelAoa } from '../../common/download-xls'; import { downloadExcelAoa } from '../common/download-xls';
import { MobileListFullscreenButton } from '../../common/MobileListFullscreenButton'; import { MobileListFullscreenButton } from '../common/MobileListFullscreenButton';
import { import {
SPOT_PAY_METHOD_LABEL, SPOT_PAY_METHOD_LABEL,
type StationCashIntakeDay, type StationCashIntakeDay,
} from '../../common/energy-spot-cash-intake'; } from '../common/energy-spot-cash-intake';
import { fetchHydrogenStationBoard } from '../../../../modules/energy/api'; import { fetchHydrogenStationBoard } from '../../api';
import { fetchAllH2BiDrillRecords } from '../../../../modules/energy/hydrogen-bi-v2/api'; import { fetchAllH2BiDrillRecords } from '../api';
import { PrototypeDrillModal } from '../../../../modules/energy/hydrogen-bi-v2/prototype-real-drills'; import { PrototypeDrillModal } from '../drill/prototype-real-drills';
import { MobileDailyList, MobileCustomerMonthList } from './StationMobileLists'; import { MobileDailyList, MobileCustomerMonthList } from './StationMobileLists';
import type { HydrogenStationBoardResponse } from '../../../../modules/energy/types'; import type { HydrogenStationBoardResponse } from '../../types';
import { import {
monthTotals, monthTotals,
stationTrendDateLabel, stationTrendDateLabel,
@@ -1,33 +0,0 @@
{
"directory": {
"title": "能源氢费经营看板",
"nodes": [
{
"id": "overview",
"title": "产品需求说明(PRD",
"type": "markdown",
"path": ".spec/requirements-prd.md",
"description": "口令 lingniu · 顶栏全局/单站 · 无关联入口卡 · 默认全部车辆·KPI/图表/钻取跟随筛选 · 无车牌归外部车辆 · 外部主数据见 energy-h2-external-* · 分流 own→氢费明细 external→仅 BI"
},
{
"id": "board-app",
"title": "氢能经营看板",
"type": "route",
"path": "/prototypes/energy-h2-bi-board",
"description": "顶栏全局/单站;责任主体统一为我司承担、客户承担、待核准三类,自行并入客户承担;PC 展示五项 KPI,移动端五项均可见可点;KPI 穿透支持按加氢站/按客户切换并追到车辆和订单;所有页面、全屏明细和钻取弹窗明确展示开始与结束时间,经营汇总到天、当日实时到分钟、订单和加氢流水到秒"
},
{
"id": "energy-board-plan",
"title": "能源 BI 看板方案",
"type": "markdown",
"path": "../../resources/prd/energy-board-plan-20260806.md"
},
{
"id": "host-ref",
"title": "宿主 overview 视觉参考",
"type": "markdown",
"path": "../../resources/prd/energy-bi-host-ref/content.md"
}
]
}
}
@@ -1,21 +0,0 @@
{
"directory": {
"title": "加氢站日报",
"nodes": [
{
"id": "overview",
"title": "产品需求说明(PRD",
"type": "markdown",
"path": ".spec/requirements-prd.md",
"description": "口令 lingniu · 起止查询日期 · KPI钻取 · 各站行内加氢量占比 · 按量降序"
},
{
"id": "app",
"title": "加氢站日报",
"type": "route",
"path": "/prototypes/energy-h2-station-daily",
"description": "起止日期+KPI钻取+占比+各站单卡;钻取页可导出xlsx"
}
]
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { defineConfig, loadEnv } from 'vite'; import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite'; import tailwindcss from '@tailwindcss/vite';
import { energyDevMockApi } from './src/modules/energy/hydrogen-bi-v2/dev-mock-api'; import { energyDevMockApi } from './src/modules/energy/hydrogen/dev-mock-api';
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), ''); const env = loadEnv(mode, process.cwd(), '');