feat(energy): hydrogen overview KPI cards (4-card grid)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
7
src/modules/energy/HydrogenDaily.tsx
Normal file
7
src/modules/energy/HydrogenDaily.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default function HydrogenDaily() {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-6 text-center text-slate-400 text-sm">
|
||||||
|
每日氢能视图占位 — 将在 Task 5 实现
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
75
src/modules/energy/HydrogenOverview.tsx
Normal file
75
src/modules/energy/HydrogenOverview.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { Fuel, Wallet, Coins, CalendarClock } from 'lucide-react';
|
||||||
|
import { HYDROGEN_KPI } from './mock';
|
||||||
|
|
||||||
|
function fmtKg(kg: number) {
|
||||||
|
if (kg >= 1000) return `${(kg / 1000).toFixed(2)}T`;
|
||||||
|
return `${kg.toFixed(2)}Kg`;
|
||||||
|
}
|
||||||
|
function fmtYuanWan(yuan: number) {
|
||||||
|
return `¥${(yuan / 10_000).toFixed(2)}万`;
|
||||||
|
}
|
||||||
|
function fmtYuan(yuan: number) {
|
||||||
|
return `¥${yuan.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HydrogenOverview() {
|
||||||
|
const k = HYDROGEN_KPI;
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="bg-white rounded-xl border border-slate-100 px-3 py-1.5 text-[11px] text-slate-400">
|
||||||
|
数据自 2025-01-01 起,每 5 分钟更新
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
|
{/* 卡 1:年加氢量 */}
|
||||||
|
<div className="bg-gradient-to-br from-cyan-50 to-blue-50 rounded-2xl border border-slate-100 shadow-sm p-4 flex flex-col gap-2">
|
||||||
|
<div className="flex items-center justify-between text-[11px] text-slate-500">
|
||||||
|
<span className="flex items-center gap-1 font-bold"><Fuel size={12} className="text-cyan-600" />年加氢量</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl md:text-3xl font-bold text-slate-800 leading-tight">{fmtKg(k.yearKg)}</div>
|
||||||
|
<div className="text-[11px] text-slate-500 font-bold space-y-0.5">
|
||||||
|
<div>我方 <span className="text-slate-700">{fmtKg(k.ourYearKg)}</span></div>
|
||||||
|
<div>客户产生 <span className="text-slate-700">{fmtKg(k.customerYearKg)}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 卡 2:年加氢费 */}
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-violet-50 rounded-2xl border border-slate-100 shadow-sm p-4 flex flex-col gap-2">
|
||||||
|
<div className="flex items-center justify-between text-[11px] text-slate-500">
|
||||||
|
<span className="flex items-center gap-1 font-bold"><Wallet size={12} className="text-blue-600" />年加氢费</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl md:text-3xl font-bold text-slate-800 leading-tight">{fmtYuanWan(k.yearFee)}</div>
|
||||||
|
<div className="text-[11px] text-slate-500 font-bold">
|
||||||
|
<div>我方 <span className="text-slate-700">{fmtYuanWan(k.ourYearFee)}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 卡 3:累计羚牛承担 */}
|
||||||
|
<div className="bg-gradient-to-br from-amber-50 to-orange-50 rounded-2xl border border-slate-100 shadow-sm p-4 flex flex-col gap-2">
|
||||||
|
<div className="flex items-center justify-between text-[11px] text-slate-500">
|
||||||
|
<span className="flex items-center gap-1 font-bold"><Coins size={12} className="text-amber-600" />累计羚牛承担</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl md:text-3xl font-bold text-slate-800 leading-tight">{fmtYuanWan(k.lingniuBornFee)}</div>
|
||||||
|
<div className="text-[11px] text-slate-500 font-bold space-y-0.5">
|
||||||
|
<div>量 <span className="text-slate-700">{fmtKg(k.lingniuBornKg)}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 卡 4:本月 / 今日 */}
|
||||||
|
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-4 flex flex-col gap-2">
|
||||||
|
<div className="flex items-center justify-between text-[11px] text-slate-500">
|
||||||
|
<span className="flex items-center gap-1 font-bold"><CalendarClock size={12} className="text-slate-500" />本月 / 今日</span>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div>
|
||||||
|
<div className="text-[10px] text-slate-400 font-bold">本月</div>
|
||||||
|
<div className="text-base md:text-lg font-bold text-slate-800">{fmtKg(k.monthKg)}</div>
|
||||||
|
<div className="text-[11px] text-slate-500 font-bold">{fmtYuanWan(k.monthFee)}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-[10px] text-slate-400 font-bold">今日</div>
|
||||||
|
<div className="text-base md:text-lg font-bold text-slate-800">{fmtKg(k.todayKg)}</div>
|
||||||
|
<div className="text-[11px] text-slate-500 font-bold">{fmtYuan(k.todayFee)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,38 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { LayoutDashboard, CalendarDays } from 'lucide-react';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
import HydrogenOverview from './HydrogenOverview';
|
||||||
|
import HydrogenDaily from './HydrogenDaily';
|
||||||
|
|
||||||
|
type SubTab = 'overview' | 'daily';
|
||||||
|
|
||||||
export default function HydrogenView() {
|
export default function HydrogenView() {
|
||||||
|
const [sub, setSub] = useState<SubTab>('overview');
|
||||||
return (
|
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 className="bg-white px-4 py-2 rounded-2xl border border-slate-100 shadow-sm flex items-center gap-6 sticky top-[58px] z-20">
|
||||||
</div>
|
<button
|
||||||
|
onClick={() => setSub('overview')}
|
||||||
|
className={`flex items-center gap-2 py-1 transition-all relative ${sub === 'overview' ? 'text-blue-600' : 'text-slate-400'}`}
|
||||||
|
>
|
||||||
|
<LayoutDashboard size={14} />
|
||||||
|
<span className="text-[11px] font-bold">总览</span>
|
||||||
|
{sub === 'overview' && (
|
||||||
|
<motion.div layoutId="activeHydrogenSub" className="absolute -bottom-2 left-0 right-0 h-0.5 bg-blue-600 rounded-full" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setSub('daily')}
|
||||||
|
className={`flex items-center gap-2 py-1 transition-all relative ${sub === 'daily' ? 'text-blue-600' : 'text-slate-400'}`}
|
||||||
|
>
|
||||||
|
<CalendarDays size={14} />
|
||||||
|
<span className="text-[11px] font-bold">每日</span>
|
||||||
|
{sub === 'daily' && (
|
||||||
|
<motion.div layoutId="activeHydrogenSub" className="absolute -bottom-2 left-0 right-0 h-0.5 bg-blue-600 rounded-full" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{sub === 'overview' ? <HydrogenOverview /> : <HydrogenDaily />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user