feat(energy): add module shell, register in nav
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -15,6 +15,7 @@ const PATH_MAP: Record<string, string> = {
|
||||
'/assets': 'assets',
|
||||
'/mileage': 'mileage',
|
||||
'/scheduling': 'scheduling',
|
||||
'/energy': 'energy',
|
||||
};
|
||||
|
||||
function getInitialModule(modules: ModuleConfig[]): string {
|
||||
|
||||
7
src/modules/energy/ElectricView.tsx
Normal file
7
src/modules/energy/ElectricView.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function ElectricView() {
|
||||
return (
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-6 text-center text-slate-400 text-sm">
|
||||
电能视图占位 — 将在 Task 6 实现
|
||||
</div>
|
||||
);
|
||||
}
|
||||
40
src/modules/energy/EnergyModule.tsx
Normal file
40
src/modules/energy/EnergyModule.tsx
Normal file
@@ -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<TopTab>('hydrogen');
|
||||
return (
|
||||
<div className="min-h-screen bg-[#F8F9FB] text-gray-800 font-sans p-3 md:p-6 relative" style={{ overflowX: 'clip' }}>
|
||||
<div className="max-w-6xl mx-auto flex flex-col gap-3 pb-16 landscape:pb-0 landscape:h-full landscape:flex-1 landscape:overflow-hidden">
|
||||
<div className="bg-white px-4 py-2 rounded-2xl border border-slate-100 shadow-sm flex items-center gap-6 sticky top-0 z-30">
|
||||
<button
|
||||
onClick={() => setActiveTab('hydrogen')}
|
||||
className={`flex items-center gap-2 py-1 transition-all relative ${activeTab === 'hydrogen' ? 'text-blue-600' : 'text-slate-400'}`}
|
||||
>
|
||||
<Fuel size={14} />
|
||||
<span className="text-[11px] font-bold">氢能</span>
|
||||
{activeTab === 'hydrogen' && (
|
||||
<motion.div layoutId="activeEnergyTopTab" className="absolute -bottom-2 left-0 right-0 h-0.5 bg-blue-600 rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('electric')}
|
||||
className={`flex items-center gap-2 py-1 transition-all relative ${activeTab === 'electric' ? 'text-blue-600' : 'text-slate-400'}`}
|
||||
>
|
||||
<BatteryCharging size={14} />
|
||||
<span className="text-[11px] font-bold">电能</span>
|
||||
{activeTab === 'electric' && (
|
||||
<motion.div layoutId="activeEnergyTopTab" className="absolute -bottom-2 left-0 right-0 h-0.5 bg-blue-600 rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{activeTab === 'hydrogen' ? <HydrogenView /> : <ElectricView />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
src/modules/energy/HydrogenView.tsx
Normal file
7
src/modules/energy/HydrogenView.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function HydrogenView() {
|
||||
return (
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-6 text-center text-slate-400 text-sm">
|
||||
氢能视图占位 — 将在 Task 3-5 实现
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
src/modules/energy/TrendBadge.tsx
Normal file
24
src/modules/energy/TrendBadge.tsx
Normal file
@@ -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 (
|
||||
<span className={`inline-flex items-center gap-0.5 rounded-full px-2 py-0.5 text-[11px] font-bold ${cls} ${className}`}>
|
||||
<Icon size={11} />
|
||||
{sign}{(value * 100).toFixed(2)}%
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user