This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
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/, '')}月`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user