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:
+1
-1
@@ -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
@@ -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/);
|
|
||||||
});
|
|
||||||
+7
-7
@@ -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';
|
||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
* 本原型独立嵌入 bi-next,禁止引入 OneOS V2 组件;
|
* 本原型独立嵌入 bi-next,禁止引入 OneOS V2 组件;
|
||||||
* 字体例外:汉字/UI 与数字等宽对齐 V2 Token(DESIGN §2.2)。
|
* 字体例外:汉字/UI 与数字等宽对齐 V2 Token(DESIGN §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 {
|
||||||
+2
-2
@@ -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), "环比 —");
|
||||||
+4
-4
@@ -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", {
|
||||||
+5
-5
@@ -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
-1
@@ -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 "环比 —";
|
||||||
+3
-3
@@ -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';
|
||||||
+7
-7
@@ -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,
|
||||||
-33
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-21
@@ -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
@@ -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(), '');
|
||||||
|
|||||||
Reference in New Issue
Block a user