From 09b9862f1fba8bab87c9290ff8819ccf8b989a13 Mon Sep 17 00:00:00 2001 From: kkfluous Date: Tue, 28 Apr 2026 11:15:55 +0800 Subject: [PATCH] feat(energy): add module shell, register in nav Co-Authored-By: Claude Opus 4.7 (1M context) --- src/App.tsx | 6 +++-- src/components/Shell.tsx | 1 + src/modules/energy/ElectricView.tsx | 7 +++++ src/modules/energy/EnergyModule.tsx | 40 +++++++++++++++++++++++++++++ src/modules/energy/HydrogenView.tsx | 7 +++++ src/modules/energy/TrendBadge.tsx | 24 +++++++++++++++++ 6 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/modules/energy/ElectricView.tsx create mode 100644 src/modules/energy/EnergyModule.tsx create mode 100644 src/modules/energy/HydrogenView.tsx create mode 100644 src/modules/energy/TrendBadge.tsx diff --git a/src/App.tsx b/src/App.tsx index de6f9a5..769dda2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,19 @@ import { useMemo } from 'react'; -import { Truck, Route, Activity } from 'lucide-react'; +import { Truck, Route, Activity, Zap } from 'lucide-react'; import { Shell, type ModuleConfig } from './components/Shell'; import AssetsModule from './modules/assets/AssetsModule'; import MileageModule from './modules/mileage/MileageModule'; import SchedulingModule from './modules/scheduling/SchedulingModule'; +import EnergyModule from './modules/energy/EnergyModule'; import AuthProvider from './auth/AuthProvider'; import { useAuth } from './auth/useAuth'; import UnauthorizedPage from './auth/UnauthorizedPage'; import { canAccessScheduling } from './shared/auth/roles'; const BASE_MODULES: ModuleConfig[] = [ - { id: 'assets', label: '资产管理', icon: Truck, component: AssetsModule }, + { id: 'assets', label: '资产管理', icon: Truck, component: AssetsModule }, { id: 'mileage', label: '里程管理', icon: Route, component: MileageModule }, + { id: 'energy', label: '能源管理', icon: Zap, component: EnergyModule }, ]; const SCHEDULING_MODULE: ModuleConfig = { diff --git a/src/components/Shell.tsx b/src/components/Shell.tsx index c56b931..71782bc 100644 --- a/src/components/Shell.tsx +++ b/src/components/Shell.tsx @@ -15,6 +15,7 @@ const PATH_MAP: Record = { '/assets': 'assets', '/mileage': 'mileage', '/scheduling': 'scheduling', + '/energy': 'energy', }; function getInitialModule(modules: ModuleConfig[]): string { diff --git a/src/modules/energy/ElectricView.tsx b/src/modules/energy/ElectricView.tsx new file mode 100644 index 0000000..bc99a69 --- /dev/null +++ b/src/modules/energy/ElectricView.tsx @@ -0,0 +1,7 @@ +export default function ElectricView() { + return ( +
+ 电能视图占位 — 将在 Task 6 实现 +
+ ); +} diff --git a/src/modules/energy/EnergyModule.tsx b/src/modules/energy/EnergyModule.tsx new file mode 100644 index 0000000..3aa10dc --- /dev/null +++ b/src/modules/energy/EnergyModule.tsx @@ -0,0 +1,40 @@ +import { useState } from 'react'; +import { Fuel, BatteryCharging } from 'lucide-react'; +import { motion } from 'motion/react'; +import HydrogenView from './HydrogenView'; +import ElectricView from './ElectricView'; + +type TopTab = 'hydrogen' | 'electric'; + +export default function EnergyModule() { + const [activeTab, setActiveTab] = useState('hydrogen'); + return ( +
+
+
+ + +
+ {activeTab === 'hydrogen' ? : } +
+
+ ); +} diff --git a/src/modules/energy/HydrogenView.tsx b/src/modules/energy/HydrogenView.tsx new file mode 100644 index 0000000..7397ce7 --- /dev/null +++ b/src/modules/energy/HydrogenView.tsx @@ -0,0 +1,7 @@ +export default function HydrogenView() { + return ( +
+ 氢能视图占位 — 将在 Task 3-5 实现 +
+ ); +} diff --git a/src/modules/energy/TrendBadge.tsx b/src/modules/energy/TrendBadge.tsx new file mode 100644 index 0000000..e2b52d7 --- /dev/null +++ b/src/modules/energy/TrendBadge.tsx @@ -0,0 +1,24 @@ +import { ArrowUp, ArrowDown, Minus } from 'lucide-react'; + +export interface TrendBadgeProps { + value: number; // -1..+1, 0 表示持平 + className?: string; +} + +export default function TrendBadge({ value, className = '' }: TrendBadgeProps) { + const isUp = value > 0.0001; + const isDown = value < -0.0001; + const cls = isUp + ? 'bg-emerald-50 text-emerald-600' + : isDown + ? 'bg-red-50 text-red-600' + : 'bg-slate-100 text-slate-500'; + const Icon = isUp ? ArrowUp : isDown ? ArrowDown : Minus; + const sign = isUp ? '+' : ''; + return ( + + + {sign}{(value * 100).toFixed(2)}% + + ); +}