refactor: unify BI vehicle scope controls

This commit is contained in:
kkfluous
2026-08-07 16:53:07 +08:00
parent ea9c9212e2
commit 86a23ad343
6 changed files with 120 additions and 70 deletions
+1
View File
@@ -153,6 +153,7 @@ SUM(fee) / NULLIF(SUM(kwh), 0)
- 已统一共享加载、空数据和故障组件的顶层卡片与明细内联边界,消除能源明细中的嵌套状态卡片。 - 已统一共享加载、空数据和故障组件的顶层卡片与明细内联边界,消除能源明细中的嵌套状态卡片。
- 已统一电能、氢能和 ETC 日期范围输入;电能与氢能共用快捷区间算法和单一输入事件路径。 - 已统一电能、氢能和 ETC 日期范围输入;电能与氢能共用快捷区间算法和单一输入事件路径。
- 电能与氢能可根据 URL 日期恢复快捷区间选中态;过期区间自动归为自定义,避免标签与统计日期不一致。 - 电能与氢能可根据 URL 日期恢复快捷区间选中态;过期区间自动归为自定义,避免标签与统计日期不一致。
- 已统一电能和氢能车辆范围 segmented control,并区分前端车辆归属状态与后端 `customer` 查询参数语义。
- 已完成并发同参 GET 去重。 - 已完成并发同参 GET 去重。
### 阶段 B:核心下钻闭环 ### 阶段 B:核心下钻闭环
@@ -0,0 +1,50 @@
import type { ComponentType } from 'react';
import { cn } from '../../lib/cn';
export interface AnalysisScopeOption<T extends string> {
id: T;
label: string;
}
export default function AnalysisScopeSwitch<T extends string>({
ariaLabel,
value,
options,
onChange,
icon: Icon,
className,
}: {
ariaLabel: string;
value: T;
options: readonly AnalysisScopeOption<T>[];
onChange: (value: T) => void;
icon?: ComponentType<{ size?: number; className?: string }>;
className?: string;
}) {
return (
<div
role="group"
aria-label={ariaLabel}
className={cn('grid gap-1 rounded-lg bg-slate-100 p-1', className)}
style={{ gridTemplateColumns: `repeat(${options.length}, minmax(0, 1fr))` }}
>
{options.map(option => (
<button
key={option.id}
type="button"
onClick={() => onChange(option.id)}
aria-pressed={value === option.id}
className={cn(
'flex min-h-9 min-w-0 items-center justify-center gap-1.5 rounded-lg px-2 text-[12px] font-black transition-colors',
value === option.id
? 'bg-white text-slate-900 shadow-sm'
: 'text-slate-500 hover:text-slate-700',
)}
>
{Icon ? <Icon size={14} className="shrink-0" /> : null}
<span className="min-w-0 truncate">{option.label}</span>
</button>
))}
</div>
);
}
+27 -27
View File
@@ -12,6 +12,7 @@ import {
type ElectricDrillContext, type ElectricDrillContext,
} from './electric-drill-context'; } from './electric-drill-context';
import AnalysisDateFilters from './AnalysisDateFilters'; import AnalysisDateFilters from './AnalysisDateFilters';
import AnalysisScopeSwitch, { type AnalysisScopeOption } from './AnalysisScopeSwitch';
import { import {
dateRangeModeLabel, dateRangeModeLabel,
getQuickDateRange, getQuickDateRange,
@@ -20,11 +21,17 @@ import {
type DateRangeMode, type DateRangeMode,
} from './date-range'; } from './date-range';
const VEHICLE_SCOPE_OPTIONS: readonly AnalysisScopeOption<ElectricVehicleScope>[] = [
{ id: 'all', label: '全部车辆' },
{ id: 'lingniu', label: '羚牛车辆' },
{ id: 'external', label: '外部车辆' },
];
export default function ElectricDaily() { export default function ElectricDaily() {
const [drillContext, setDrillContext] = useState<ElectricDrillContext>(() => ( const [drillContext, setDrillContext] = useState<ElectricDrillContext>(() => (
parseElectricDrillContext(window.location.search) parseElectricDrillContext(window.location.search)
)); ));
const [customer, setCustomer] = useState<ElectricVehicleScope>(drillContext.vehicleScope); const [vehicleScope, setVehicleScope] = useState<ElectricVehicleScope>(drillContext.vehicleScope);
const [pick, setPick] = useState<DateRangeMode>(() => { const [pick, setPick] = useState<DateRangeMode>(() => {
if (drillContext.startDate && drillContext.endDate) { if (drillContext.startDate && drillContext.endDate) {
return inferDateRangeMode(drillContext.startDate, drillContext.endDate); return inferDateRangeMode(drillContext.startDate, drillContext.endDate);
@@ -63,7 +70,7 @@ export default function ElectricDaily() {
const query = pick === 'custom' const query = pick === 'custom'
? { startDate: effectiveRange.start, endDate: effectiveRange.end } ? { startDate: effectiveRange.start, endDate: effectiveRange.end }
: { range: pick }; : { range: pick };
fetchElectricMonthly(customer, query) fetchElectricMonthly(vehicleScope, query)
.then(m => { .then(m => {
if (cancelled) return; if (cancelled) return;
setMonths(m); setMonths(m);
@@ -73,7 +80,7 @@ export default function ElectricDaily() {
}) })
.catch(e => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)); }); .catch(e => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)); });
return () => { cancelled = true; }; return () => { cancelled = true; };
}, [customer, pick, effectiveRange.start, effectiveRange.end]); }, [vehicleScope, pick, effectiveRange.start, effectiveRange.end]);
useEffect(() => { useEffect(() => {
if (!selectedDate) { if (!selectedDate) {
@@ -84,11 +91,11 @@ export default function ElectricDaily() {
let cancelled = false; let cancelled = false;
setOrders(null); setOrders(null);
setOrdersError(null); setOrdersError(null);
fetchElectricOrders(selectedDate, customer) fetchElectricOrders(selectedDate, vehicleScope)
.then(result => { if (!cancelled) setOrders(result); }) .then(result => { if (!cancelled) setOrders(result); })
.catch(e => { if (!cancelled) setOrdersError(e instanceof Error ? e.message : String(e)); }); .catch(e => { if (!cancelled) setOrdersError(e instanceof Error ? e.message : String(e)); });
return () => { cancelled = true; }; return () => { cancelled = true; };
}, [selectedDate, customer]); }, [selectedDate, vehicleScope]);
const toggleMonth = (m: string) => setOpenMonths(prev => { const toggleMonth = (m: string) => setOpenMonths(prev => {
const next = new Set(prev); const next = new Set(prev);
@@ -104,14 +111,14 @@ export default function ElectricDaily() {
const avgPrice = totalKwh > 0 ? totalFee / totalKwh : 0; const avgPrice = totalKwh > 0 ? totalFee / totalKwh : 0;
const scopeLabel = dateRangeModeLabel(pick); const scopeLabel = dateRangeModeLabel(pick);
const rangeText = `${effectiveRange.start}${effectiveRange.end}`; const rangeText = `${effectiveRange.start}${effectiveRange.end}`;
const showExternalEmpty = customer === 'external' && months !== null && totalKwh === 0; const showExternalEmpty = vehicleScope === 'external' && months !== null && totalKwh === 0;
const applyQuickPick = (nextPick: DateQuickPick) => { const applyQuickPick = (nextPick: DateQuickPick) => {
const nextRange = getQuickDateRange(nextPick); const nextRange = getQuickDateRange(nextPick);
setPick(nextPick); setPick(nextPick);
setDateRange(nextRange); setDateRange(nextRange);
commitDrillContext({ commitDrillContext({
vehicleScope: customer, vehicleScope,
startDate: nextRange.start, startDate: nextRange.start,
endDate: nextRange.end, endDate: nextRange.end,
}); });
@@ -124,14 +131,14 @@ export default function ElectricDaily() {
setPick('custom'); setPick('custom');
setDateRange(normalized); setDateRange(normalized);
commitDrillContext({ commitDrillContext({
vehicleScope: customer, vehicleScope,
startDate: normalized.start, startDate: normalized.start,
endDate: normalized.end, endDate: normalized.end,
}); });
}; };
const updateCustomer = (next: ElectricVehicleScope) => { const updateVehicleScope = (next: ElectricVehicleScope) => {
setCustomer(next); setVehicleScope(next);
commitDrillContext({ commitDrillContext({
...drillContext, ...drillContext,
vehicleScope: next, vehicleScope: next,
@@ -143,7 +150,7 @@ export default function ElectricDaily() {
const toggleDate = (date: string) => { const toggleDate = (date: string) => {
commitDrillContext({ commitDrillContext({
...drillContext, ...drillContext,
vehicleScope: customer, vehicleScope,
startDate: effectiveRange.start, startDate: effectiveRange.start,
endDate: effectiveRange.end, endDate: effectiveRange.end,
selectedDate: selectedDate === date ? undefined : date, selectedDate: selectedDate === date ? undefined : date,
@@ -154,7 +161,7 @@ export default function ElectricDaily() {
const handlePopState = () => { const handlePopState = () => {
const next = parseElectricDrillContext(window.location.search); const next = parseElectricDrillContext(window.location.search);
setDrillContext(next); setDrillContext(next);
setCustomer(next.vehicleScope); setVehicleScope(next.vehicleScope);
if (next.startDate && next.endDate) { if (next.startDate && next.endDate) {
const normalized = normalizeDateRange(next.startDate, next.endDate); const normalized = normalizeDateRange(next.startDate, next.endDate);
setPick(inferDateRangeMode(normalized.start, normalized.end)); setPick(inferDateRangeMode(normalized.start, normalized.end));
@@ -181,21 +188,14 @@ export default function ElectricDaily() {
onDateChange={updateDateRange} onDateChange={updateDateRange}
/> />
<div className="mt-2 grid grid-cols-3 gap-1 rounded-xl bg-slate-100 p-1"> <AnalysisScopeSwitch
{(['all', 'lingniu', 'external'] as const).map(c => ( ariaLabel="电能车辆范围"
<button value={vehicleScope}
key={c} options={VEHICLE_SCOPE_OPTIONS}
onClick={() => updateCustomer(c)} onChange={updateVehicleScope}
aria-pressed={customer === c} icon={Truck}
className={`flex min-h-9 items-center justify-center gap-1.5 rounded-lg text-[12px] font-black transition-all ${ className="mt-2"
customer === c ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500 hover:text-slate-700' />
}`}
>
<Truck size={14} />
{c === 'all' ? '全部车辆' : c === 'external' ? '外部车辆' : '羚牛车辆'}
</button>
))}
</div>
</SurfaceCard> </SurfaceCard>
<div className="grid grid-cols-2 gap-3 md:grid-cols-4"> <div className="grid grid-cols-2 gap-3 md:grid-cols-4">
+30 -31
View File
@@ -14,6 +14,7 @@ import {
} from './hydrogen-drill-context'; } from './hydrogen-drill-context';
import HydrogenOrders from './HydrogenOrders'; import HydrogenOrders from './HydrogenOrders';
import AnalysisDateFilters from './AnalysisDateFilters'; import AnalysisDateFilters from './AnalysisDateFilters';
import AnalysisScopeSwitch, { type AnalysisScopeOption } from './AnalysisScopeSwitch';
import { import {
dateRangeModeLabel, dateRangeModeLabel,
getQuickDateRange, getQuickDateRange,
@@ -22,6 +23,11 @@ import {
type DateRangeMode, type DateRangeMode,
} from './date-range'; } from './date-range';
const VEHICLE_SCOPE_OPTIONS: readonly AnalysisScopeOption<CustomerType>[] = [
{ id: 'lingniu', label: '羚牛车辆' },
{ id: 'external', label: '外部车辆' },
];
export default function HydrogenDaily() { export default function HydrogenDaily() {
const [drillContext, setDrillContext] = useState<HydrogenDrillContext>(() => ( const [drillContext, setDrillContext] = useState<HydrogenDrillContext>(() => (
parseHydrogenDrillContext(window.location.search) parseHydrogenDrillContext(window.location.search)
@@ -36,7 +42,7 @@ export default function HydrogenDaily() {
? normalizeDateRange(drillContext.startDate, drillContext.endDate) ? normalizeDateRange(drillContext.startDate, drillContext.endDate)
: getQuickDateRange('last15') : getQuickDateRange('last15')
)); ));
const [customer, setCustomer] = useState<CustomerType>(drillContext.vehicleScope); const [vehicleScope, setVehicleScope] = useState<CustomerType>(drillContext.vehicleScope);
const [expanded, setExpanded] = useState<Set<string>>(new Set()); const [expanded, setExpanded] = useState<Set<string>>(new Set());
const [rows, setRows] = useState<HydrogenDailyRow[] | null>(null); const [rows, setRows] = useState<HydrogenDailyRow[] | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -64,11 +70,11 @@ export default function HydrogenDaily() {
const query = pick === 'custom' const query = pick === 'custom'
? { startDate: effectiveRange.start, endDate: effectiveRange.end, stationId: selectedStationId, customerName: selectedCustomerName } ? { startDate: effectiveRange.start, endDate: effectiveRange.end, stationId: selectedStationId, customerName: selectedCustomerName }
: { range: pick, stationId: selectedStationId, customerName: selectedCustomerName }; : { range: pick, stationId: selectedStationId, customerName: selectedCustomerName };
fetchHydrogenDaily(query, customer) fetchHydrogenDaily(query, vehicleScope)
.then(r => { if (!cancelled) setRows(r); }) .then(r => { if (!cancelled) setRows(r); })
.catch(e => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)); }); .catch(e => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)); });
return () => { cancelled = true; }; return () => { cancelled = true; };
}, [pick, customer, effectiveRange.start, effectiveRange.end, selectedStationId, selectedCustomerName, retryKey]); }, [pick, vehicleScope, effectiveRange.start, effectiveRange.end, selectedStationId, selectedCustomerName, retryKey]);
// 柱图:按日期升序,用于"从左到右时间流" // 柱图:按日期升序,用于"从左到右时间流"
const trendData = useMemo(() => (rows ? [...rows].sort((a, b) => a.date.localeCompare(b.date)) : []), [rows]); const trendData = useMemo(() => (rows ? [...rows].sort((a, b) => a.date.localeCompare(b.date)) : []), [rows]);
@@ -100,7 +106,7 @@ export default function HydrogenDaily() {
setDateRange(nextRange); setDateRange(nextRange);
commitDrillContext({ commitDrillContext({
...drillContext, ...drillContext,
vehicleScope: customer, vehicleScope,
startDate: nextRange.start, startDate: nextRange.start,
endDate: nextRange.end, endDate: nextRange.end,
selectedDate: undefined, selectedDate: undefined,
@@ -115,15 +121,15 @@ export default function HydrogenDaily() {
setDateRange(normalized); setDateRange(normalized);
commitDrillContext({ commitDrillContext({
...drillContext, ...drillContext,
vehicleScope: customer, vehicleScope,
startDate: normalized.start, startDate: normalized.start,
endDate: normalized.end, endDate: normalized.end,
selectedDate: undefined, selectedDate: undefined,
}, 'replace'); }, 'replace');
}; };
const updateCustomer = (next: CustomerType) => { const updateVehicleScope = (next: CustomerType) => {
setCustomer(next); setVehicleScope(next);
commitDrillContext({ commitDrillContext({
...drillContext, ...drillContext,
vehicleScope: next, vehicleScope: next,
@@ -136,7 +142,7 @@ export default function HydrogenDaily() {
commitDrillContext({ commitDrillContext({
level: 'overview', level: 'overview',
year: drillContext.year, year: drillContext.year,
vehicleScope: customer, vehicleScope,
startDate: effectiveRange.start, startDate: effectiveRange.start,
endDate: effectiveRange.end, endDate: effectiveRange.end,
}, 'replace'); }, 'replace');
@@ -145,7 +151,7 @@ export default function HydrogenDaily() {
const openOrders = (date: string) => { const openOrders = (date: string) => {
commitDrillContext({ commitDrillContext({
...drillContext, ...drillContext,
vehicleScope: customer, vehicleScope,
startDate: effectiveRange.start, startDate: effectiveRange.start,
endDate: effectiveRange.end, endDate: effectiveRange.end,
selectedDate: date, selectedDate: date,
@@ -155,7 +161,7 @@ export default function HydrogenDaily() {
const closeOrders = () => { const closeOrders = () => {
commitDrillContext({ commitDrillContext({
...drillContext, ...drillContext,
vehicleScope: customer, vehicleScope,
startDate: effectiveRange.start, startDate: effectiveRange.start,
endDate: effectiveRange.end, endDate: effectiveRange.end,
selectedDate: undefined, selectedDate: undefined,
@@ -172,7 +178,7 @@ export default function HydrogenDaily() {
const handlePopState = () => { const handlePopState = () => {
const next = parseHydrogenDrillContext(window.location.search); const next = parseHydrogenDrillContext(window.location.search);
setDrillContext(next); setDrillContext(next);
setCustomer(next.vehicleScope); setVehicleScope(next.vehicleScope);
if (next.startDate && next.endDate) { if (next.startDate && next.endDate) {
const normalized = normalizeDateRange(next.startDate, next.endDate); const normalized = normalizeDateRange(next.startDate, next.endDate);
setPick(inferDateRangeMode(normalized.start, normalized.end)); setPick(inferDateRangeMode(normalized.start, normalized.end));
@@ -196,21 +202,14 @@ export default function HydrogenDaily() {
onDateChange={updateDateRange} onDateChange={updateDateRange}
/> />
<div className="mt-2 grid grid-cols-2 gap-1 rounded-xl bg-slate-100 p-1"> <AnalysisScopeSwitch
{(['lingniu', 'external'] as const).map(c => ( ariaLabel="氢能车辆范围"
<button value={vehicleScope}
key={c} options={VEHICLE_SCOPE_OPTIONS}
onClick={() => updateCustomer(c)} onChange={updateVehicleScope}
aria-pressed={customer === c} icon={Truck}
className={`flex min-h-9 items-center justify-center gap-1.5 rounded-lg text-[12px] font-black transition-all ${ className="mt-2"
customer === c ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500 hover:text-slate-700' />
}`}
>
<Truck size={14} />
{c === 'external' ? '外部车辆' : '羚牛车辆'}
</button>
))}
</div>
</SurfaceCard> </SurfaceCard>
{selectedStationName && ( {selectedStationName && (
@@ -261,13 +260,13 @@ export default function HydrogenDaily() {
{!error && rows !== null && <div className="grid grid-cols-2 gap-3 md:grid-cols-4"> {!error && rows !== null && <div className="grid grid-cols-2 gap-3 md:grid-cols-4">
<MetricTile icon={Fuel} label={`${scopeLabel}加氢量`} value={totalKg.toLocaleString('zh-CN', { maximumFractionDigits: 1 })} unit="Kg" helper={rangeText} /> <MetricTile icon={Fuel} label={`${scopeLabel}加氢量`} value={totalKg.toLocaleString('zh-CN', { maximumFractionDigits: 1 })} unit="Kg" helper={rangeText} />
<MetricTile icon={Truck} label="车辆归属" value={customer === 'external' ? '外部' : '羚牛'} helper="当前筛选口径" tone="emerald" /> <MetricTile icon={Truck} label="车辆归属" value={vehicleScope === 'external' ? '外部' : '羚牛'} helper="当前筛选口径" tone="emerald" />
<MetricTile icon={TrendingUp} label="有效天数" value={`${activeDays}/${rows?.length ?? 0}`} helper={`日均 ${avgKg.toLocaleString('zh-CN', { maximumFractionDigits: 1 })} Kg`} tone="amber" /> <MetricTile icon={TrendingUp} label="有效天数" value={`${activeDays}/${rows?.length ?? 0}`} helper={`日均 ${avgKg.toLocaleString('zh-CN', { maximumFractionDigits: 1 })} Kg`} tone="amber" />
<MetricTile icon={Plug} label="涉及加氢站" value={stationCount} unit="站" helper="按明细站点去重" tone="slate" /> <MetricTile icon={Plug} label="涉及加氢站" value={stationCount} unit="站" helper="按明细站点去重" tone="slate" />
</div>} </div>}
{/* 外部车辆:新系统数据还没准备好 */} {/* 外部车辆:新系统数据还没准备好 */}
{!error && customer === 'external' && rows !== null && totalKg === 0 && ( {!error && vehicleScope === 'external' && rows !== null && totalKg === 0 && (
<motion.div <motion.div
initial={{ opacity: 0, y: 8 }} initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
@@ -288,7 +287,7 @@ export default function HydrogenDaily() {
)} )}
{/* 时段加氢量柱图(外部车辆无数据时不渲染) */} {/* 时段加氢量柱图(外部车辆无数据时不渲染) */}
{!error && !(customer === 'external' && totalKg === 0) && trendData.length > 0 && ( {!error && !(vehicleScope === 'external' && totalKg === 0) && trendData.length > 0 && (
<SurfaceCard> <SurfaceCard>
<div className="flex items-center justify-between px-4 pt-4 mb-2"> <div className="flex items-center justify-between px-4 pt-4 mb-2">
<span className="text-sm font-bold text-slate-700"></span> <span className="text-sm font-bold text-slate-700"></span>
@@ -375,7 +374,7 @@ export default function HydrogenDaily() {
{selectedDate && ( {selectedDate && (
<HydrogenOrders <HydrogenOrders
date={selectedDate} date={selectedDate}
customer={customer} vehicleScope={vehicleScope}
stationId={selectedStationId} stationId={selectedStationId}
customerName={selectedCustomerName} customerName={selectedCustomerName}
onClose={closeOrders} onClose={closeOrders}
@@ -383,7 +382,7 @@ export default function HydrogenDaily() {
)} )}
{/* 表格(外部车辆 + 全 0 时不渲染,由上方友好空状态替代) */} {/* 表格(外部车辆 + 全 0 时不渲染,由上方友好空状态替代) */}
{!error && !(customer === 'external' && rows !== null && totalKg === 0) && ( {!error && !(vehicleScope === 'external' && rows !== null && totalKg === 0) && (
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm overflow-hidden"> <div className="bg-white rounded-2xl border border-slate-100 shadow-sm overflow-hidden">
{/* 表头 */} {/* 表头 */}
<div className="grid grid-cols-[minmax(0,1fr)_72px_64px] md:grid-cols-[minmax(0,1fr)_140px_120px_104px] gap-2 md:gap-3 px-3 py-2 bg-slate-50 text-[11px] font-bold text-slate-500"> <div className="grid grid-cols-[minmax(0,1fr)_72px_64px] md:grid-cols-[minmax(0,1fr)_140px_120px_104px] gap-2 md:gap-3 px-3 py-2 bg-slate-50 text-[11px] font-bold text-slate-500">
+6 -6
View File
@@ -11,13 +11,13 @@ function fmtMoney(value: number): string {
export default function HydrogenOrders({ export default function HydrogenOrders({
date, date,
customer, vehicleScope,
stationId, stationId,
customerName, customerName,
onClose, onClose,
}: { }: {
date: string; date: string;
customer: CustomerType; vehicleScope: CustomerType;
stationId?: number; stationId?: number;
customerName?: string; customerName?: string;
onClose: () => void; onClose: () => void;
@@ -30,28 +30,28 @@ export default function HydrogenOrders({
useEffect(() => { useEffect(() => {
setPage(1); setPage(1);
}, [customer, customerName, date, stationId]); }, [vehicleScope, customerName, date, stationId]);
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
setLoading(true); setLoading(true);
setError(null); setError(null);
setData(null); setData(null);
fetchHydrogenOrders({ date, customer, stationId, customerName, page, limit: 20 }) fetchHydrogenOrders({ date, customer: vehicleScope, stationId, customerName, page, limit: 20 })
.then(result => { if (!cancelled) setData(result); }) .then(result => { if (!cancelled) setData(result); })
.catch(loadError => { .catch(loadError => {
if (!cancelled) setError(loadError instanceof Error ? loadError.message : String(loadError)); if (!cancelled) setError(loadError instanceof Error ? loadError.message : String(loadError));
}) })
.finally(() => { if (!cancelled) setLoading(false); }); .finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; }; return () => { cancelled = true; };
}, [customer, customerName, date, page, retryKey, stationId]); }, [vehicleScope, customerName, date, page, retryKey, stationId]);
const totalPages = data?.totalPages ?? 1; const totalPages = data?.totalPages ?? 1;
const scope = customerName const scope = customerName
? `客户:${customerName}` ? `客户:${customerName}`
: stationId !== undefined : stationId !== undefined
? `站点 #${stationId}` ? `站点 #${stationId}`
: customer === 'external' ? '外部车辆' : '羚牛车辆'; : vehicleScope === 'external' ? '外部车辆' : '羚牛车辆';
return ( return (
<section className="overflow-hidden rounded-lg border border-blue-100 bg-white shadow-sm"> <section className="overflow-hidden rounded-lg border border-blue-100 bg-white shadow-sm">
+6 -6
View File
@@ -39,8 +39,8 @@ export interface HydrogenDailyQuery {
customerName?: string; customerName?: string;
} }
export function fetchHydrogenDaily(query: HydrogenDailyQuery, customer: CustomerType): Promise<HydrogenDailyRow[]> { export function fetchHydrogenDaily(query: HydrogenDailyQuery, vehicleScope: CustomerType): Promise<HydrogenDailyRow[]> {
const q = new URLSearchParams({ customer }); const q = new URLSearchParams({ customer: vehicleScope });
if (query.range) q.set('range', query.range); if (query.range) q.set('range', query.range);
if (query.startDate) q.set('startDate', query.startDate); if (query.startDate) q.set('startDate', query.startDate);
if (query.endDate) q.set('endDate', query.endDate); if (query.endDate) q.set('endDate', query.endDate);
@@ -80,16 +80,16 @@ export function fetchElectricOverview(): Promise<ElectricOverviewResponse> {
return fetchJson<ElectricOverviewResponse>(`${BASE}/electric/overview`); return fetchJson<ElectricOverviewResponse>(`${BASE}/electric/overview`);
} }
export function fetchElectricMonthly(customer: ElectricVehicleScope, query: HydrogenDailyQuery = { range: 'last15' }): Promise<ElectricMonthGroup[]> { export function fetchElectricMonthly(vehicleScope: ElectricVehicleScope, query: HydrogenDailyQuery = { range: 'last15' }): Promise<ElectricMonthGroup[]> {
const q = new URLSearchParams({ customer }); const q = new URLSearchParams({ customer: vehicleScope });
if (query.range) q.set('range', query.range); if (query.range) q.set('range', query.range);
if (query.startDate) q.set('startDate', query.startDate); if (query.startDate) q.set('startDate', query.startDate);
if (query.endDate) q.set('endDate', query.endDate); if (query.endDate) q.set('endDate', query.endDate);
return fetchJson<ElectricMonthGroup[]>(`${BASE}/electric/monthly?${q.toString()}`); return fetchJson<ElectricMonthGroup[]>(`${BASE}/electric/monthly?${q.toString()}`);
} }
export function fetchElectricOrders(date: string, customer: ElectricVehicleScope): Promise<ElectricChargeOrderResponse> { export function fetchElectricOrders(date: string, vehicleScope: ElectricVehicleScope): Promise<ElectricChargeOrderResponse> {
const q = new URLSearchParams({ date, customer }); const q = new URLSearchParams({ date, customer: vehicleScope });
return fetchJson<ElectricChargeOrderResponse>(`${BASE}/electric/orders?${q.toString()}`); return fetchJson<ElectricChargeOrderResponse>(`${BASE}/electric/orders?${q.toString()}`);
} }