refactor: expose electric data freshness

This commit is contained in:
kkfluous
2026-08-07 15:22:04 +08:00
parent 21f5e5388a
commit 0d849fcbb1
6 changed files with 46 additions and 18 deletions
+20 -11
View File
@@ -12,6 +12,11 @@ function fmtYuan(yuan: number) {
function fmtKwh(kwh: number) {
return `${kwh.toLocaleString('zh-CN', { maximumFractionDigits: 2 })} 度`;
}
function fmtMonth(month: string | undefined) {
if (!month) return '当前月';
const [year, monthNumber] = month.split('-');
return `${year}${Number(monthNumber)}`;
}
export default function ElectricOverview() {
const [data, setData] = useState<ElectricOverviewResponse | null>(null);
@@ -35,16 +40,20 @@ export default function ElectricOverview() {
const trendData = data.trend;
// 当电能数据滞后(本月无数据走 fallback)时,柱图标题显示实际月份
const trendMonthLabel = trendData[0]?.date.slice(0, 7);
const currentMonth = new Date().toISOString().slice(0, 7);
const trendMonthText = fmtMonth(trendMonthLabel);
const now = new Date();
const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
const chartTitle = trendMonthLabel && trendMonthLabel !== currentMonth
? `${trendMonthLabel} 每日充电`
: '本月每日充电';
const activeDays = trendData.filter(item => item.kwh > 0).length;
const avgDailyKwh = activeDays > 0 ? trendData.reduce((sum, item) => sum + item.kwh, 0) / activeDays : 0;
const avgDailyFee = activeDays > 0 ? trendData.reduce((sum, item) => sum + item.fee, 0) / activeDays : 0;
const trendKwh = trendData.reduce((sum, item) => sum + item.kwh, 0);
const trendFee = trendData.reduce((sum, item) => sum + item.fee, 0);
const avgDailyKwh = activeDays > 0 ? trendKwh / activeDays : 0;
const avgDailyFee = activeDays > 0 ? trendFee / activeDays : 0;
const peakDay = trendData.reduce<typeof trendData[number] | null>((best, item) => (!best || item.kwh > best.kwh ? item : best), null);
const avgPrice = k.totalKwh > 0 ? k.totalFee / k.totalKwh : 0;
const monthPrice = k.monthKwh > 0 ? k.monthFee / k.monthKwh : 0;
const trendPrice = trendKwh > 0 ? trendFee / trendKwh : 0;
const openDay = (date: string) => {
const url = buildElectricDrillUrl(
{ pathname: window.location.pathname, search: window.location.search, hash: '#electric' },
@@ -62,19 +71,19 @@ export default function ElectricOverview() {
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
{data.latestRecordAt ?? '暂无记录'} · ·
</div>
{/* 横向 mini KPI 头 */}
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
<MetricTile icon={Wallet} label="累计充电费" value={fmtYuan(k.totalFee)} helper={fmtKwh(k.totalKwh)} />
<MetricTile icon={CalendarClock} label="本月充电费" value={fmtYuan(k.monthFee)} helper={fmtKwh(k.monthKwh)} tone="emerald" />
<MetricTile icon={Gauge} label="综合费用强度" value={avgPrice.toFixed(2)} unit="元/度" helper={`本月 ${monthPrice.toFixed(2)} 元/度 · 含零费用订单`} tone="amber" />
<MetricTile icon={CalendarClock} label={`${trendMonthText}充电费`} value={fmtYuan(trendFee)} helper={fmtKwh(trendKwh)} tone="emerald" />
<MetricTile icon={Gauge} label="综合费用强度" value={avgPrice.toFixed(2)} unit="元/度" helper={`${trendMonthText} ${trendPrice.toFixed(2)} 元/度 · 含零费用订单`} tone="amber" />
<MetricTile icon={BatteryCharging} label="今日充电" value={k.todayKwh.toLocaleString('zh-CN', { maximumFractionDigits: 1 })} unit="度" helper={`${fmtYuan(k.todayFee)} · ${k.todayChainPct >= 0 ? '+' : ''}${(k.todayChainPct * 100).toFixed(1)}%`} tone={Math.abs(k.todayChainPct) >= 0.3 ? 'rose' : 'slate'} />
</div>
<div className="grid grid-cols-1 gap-3 md:grid-cols-3">
<SurfaceCard className="p-3">
<div className="text-[11px] font-black text-slate-400"></div>
<div className="text-[11px] font-black text-slate-400">{trendMonthText}</div>
<div className="mt-1 text-xl font-black text-slate-900">{activeDays}<span className="ml-1 text-[11px] text-slate-400"></span></div>
<div className="mt-1 text-[11px] font-bold text-slate-500"> {avgDailyKwh.toLocaleString('zh-CN', { maximumFractionDigits: 1 })} </div>
</SurfaceCard>
@@ -85,12 +94,12 @@ export default function ElectricOverview() {
</SurfaceCard>
<SurfaceCard className="p-3">
<div className="text-[11px] font-black text-slate-400"></div>
<div className="mt-1 text-xl font-black text-emerald-600">{k.totalFee > 0 ? (k.monthFee / k.totalFee * 100).toFixed(1) : '0.0'}%</div>
<div className="mt-1 text-[11px] font-bold text-slate-500"> / </div>
<div className="mt-1 text-xl font-black text-emerald-600">{k.totalFee > 0 ? (trendFee / k.totalFee * 100).toFixed(1) : '0.0'}%</div>
<div className="mt-1 text-[11px] font-bold text-slate-500">{trendMonthText} / </div>
</SurfaceCard>
</div>
{/* 本月每日充电柱图 */}
{/* 当前月无数据时展示最近一个有数据的自然月 */}
<SurfaceCard className="p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-bold text-slate-700">{chartTitle}</span>
+1
View File
@@ -50,6 +50,7 @@ export function fetchHydrogenDaily(query: HydrogenDailyQuery, customer: Customer
export interface ElectricOverviewResponse {
kpi: ElectricKpi;
trend: ElectricDailyRow[];
latestRecordAt: string | null;
}
export function fetchElectricOverview(): Promise<ElectricOverviewResponse> {