76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import type { HydrogenOverviewResponse } from '../api';
|
|
|
|
export function formatKg(kg: number): { value: string; unit: string } {
|
|
if (kg >= 1000) return { value: (kg / 1000).toFixed(2), unit: 'T' };
|
|
return { value: kg.toFixed(2), unit: 'Kg' };
|
|
}
|
|
|
|
export function formatYuan(yuan: number): { value: string; unit: string } {
|
|
const absolute = Math.abs(yuan);
|
|
if (absolute >= 100_000_000) {
|
|
return { value: (yuan / 100_000_000).toFixed(2), unit: '亿元' };
|
|
}
|
|
if (absolute >= 10_000) {
|
|
return {
|
|
value: (yuan / 10_000).toLocaleString('zh-CN', { maximumFractionDigits: 2 }),
|
|
unit: '万元',
|
|
};
|
|
}
|
|
return {
|
|
value: yuan.toLocaleString('zh-CN', { maximumFractionDigits: 0 }),
|
|
unit: '元',
|
|
};
|
|
}
|
|
|
|
export function formatRelative(timestamp: number, now = Date.now()): string {
|
|
const seconds = Math.max(0, Math.floor((now - timestamp) / 1000));
|
|
if (seconds < 5) return '刚刚';
|
|
if (seconds < 60) return `${seconds} 秒前`;
|
|
const minutes = Math.floor(seconds / 60);
|
|
if (minutes < 60) return `${minutes} 分钟前`;
|
|
const hours = Math.floor(minutes / 60);
|
|
if (hours < 24) return `${hours} 小时前`;
|
|
return new Date(timestamp).toLocaleString('zh-CN', { hour12: false });
|
|
}
|
|
|
|
export function formatRefreshTime(timestamp: number, now = Date.now()): string {
|
|
const exactTime = new Date(timestamp).toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: false,
|
|
});
|
|
return `${formatRelative(timestamp, now)} · ${exactTime.replace(/\//g, '-')}`;
|
|
}
|
|
|
|
export function deriveOverviewMetrics(data: HydrogenOverviewResponse) {
|
|
const { kpi, monthly, stations, top5 } = data;
|
|
const monthAvgKg = monthly.length > 0
|
|
? monthly.reduce((sum, month) => sum + month.kg, 0) / monthly.length
|
|
: 0;
|
|
const bestMonth = monthly.reduce<typeof monthly[number] | null>(
|
|
(best, item) => (!best || item.kg > best.kg ? item : best),
|
|
null,
|
|
);
|
|
const latestMonth = monthly[monthly.length - 1];
|
|
const previousMonth = monthly[monthly.length - 2];
|
|
|
|
return {
|
|
monthAvgKg,
|
|
bestMonth,
|
|
latestMonth,
|
|
monthMomentum: latestMonth && previousMonth && previousMonth.kg > 0
|
|
? ((latestMonth.kg - previousMonth.kg) / previousMonth.kg) * 100
|
|
: null,
|
|
top5Share: (top5.reduce((sum, item) => sum + item.kg, 0) / Math.max(1, kpi.yearKg)) * 100,
|
|
profitYield: kpi.yearRevenue > 0 ? (kpi.yearProfit / kpi.yearRevenue) * 100 : 0,
|
|
stationAvgKg: stations.length > 0 ? kpi.yearKg / stations.length : 0,
|
|
monthlyDual: monthly.map(month => ({
|
|
...month,
|
|
monthLabel: `${month.month.slice(5).replace(/^0/, '')}月`,
|
|
})),
|
|
};
|
|
}
|