From 462b2ecff3ce38b8d2778c09bbd1e7b7fd40ffd5 Mon Sep 17 00:00:00 2001 From: shishengliang Date: Sat, 24 Oct 2026 13:37:09 +0800 Subject: [PATCH] feat(energy): add vehicle scope and station search to hydrogen daily --- src/modules/energy/HydrogenDaily.tsx | 95 +++++++++++++++++-- .../energy/hydrogen-daily/model.test.ts | 51 ++++++++++ src/modules/energy/hydrogen-daily/model.ts | 22 +++++ src/modules/energy/types.ts | 2 +- 4 files changed, 159 insertions(+), 11 deletions(-) diff --git a/src/modules/energy/HydrogenDaily.tsx b/src/modules/energy/HydrogenDaily.tsx index ec0fe46..07dfb17 100644 --- a/src/modules/energy/HydrogenDaily.tsx +++ b/src/modules/energy/HydrogenDaily.tsx @@ -1,11 +1,12 @@ import { useEffect, useMemo, useState } from 'react'; -import { ChevronRight, Fuel, Plug, TrendingUp, Truck } from 'lucide-react'; +import { Check, ChevronRight, Fuel, Layers, Plug, Search, TrendingUp, Truck, X } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer, Cell, Tooltip, ReferenceLine } from 'recharts'; import TrendBadge from './TrendBadge'; import { fetchHydrogenDaily } from './api'; import type { CustomerType, DateQuickPick, HydrogenDailyRow } from './types'; import { + filterHydrogenRows, getQuickRange, getRangeModeLabel, normalizeRange, @@ -13,12 +14,14 @@ import { type RangeMode, } from './hydrogen-daily/model'; import DailyRangeControls from './daily-range/DailyRangeControls'; +import { DateRangePicker } from '../../components/ui/DateRangePicker'; import { EmptyState, ErrorState, LoadingState, MetricTile, SurfaceCard } from '../../components/ui/surface'; export default function HydrogenDaily() { const [pick, setPick] = useState('last15'); const [dateRange, setDateRange] = useState(() => getQuickRange('last15')); - const [customer, setCustomer] = useState('lingniu'); + const [customer, setCustomer] = useState('all'); + const [stationSearch, setStationSearch] = useState(''); const [expanded, setExpanded] = useState>(new Set()); const [rows, setRows] = useState(null); const [error, setError] = useState(null); @@ -38,6 +41,8 @@ export default function HydrogenDaily() { return () => { cancelled = true; }; }, [pick, customer, effectiveRange.start, effectiveRange.end]); + const filteredRows = useMemo(() => filterHydrogenRows(rows, stationSearch), [rows, stationSearch]); + const { trendData, totalKg, @@ -47,7 +52,7 @@ export default function HydrogenDaily() { peakDay, lowDay, zeroDays, - } = useMemo(() => summarizeHydrogenRows(rows), [rows]); + } = useMemo(() => summarizeHydrogenRows(filteredRows), [filteredRows]); const scopeLabel = getRangeModeLabel(pick); const rangeText = `${effectiveRange.start} 至 ${effectiveRange.end}`; @@ -78,13 +83,83 @@ export default function HydrogenDaily() { onCustomPick={() => setPick('custom')} onDateRangeChange={updateDateRange} onCustomerChange={setCustomer} - /> + dateControl={ + ({ label: option.label, range: getQuickRange(option.id) }))} + onChange={range => { + setDateRange(range); + const matching = (['thisWeek', 'thisMonth', 'last15'] as const).find(option => { + const preset = getQuickRange(option); + return preset.start === range.start && preset.end === range.end; + }); + setPick(matching ?? 'custom'); + }} + /> + } + > +
+
+ 车辆归属 +
+ {([ + { value: 'all', label: '全部车辆', description: '内外部汇总', icon: Layers }, + { value: 'lingniu', label: '内部车辆', description: '羚牛车辆', icon: Truck }, + { value: 'external', label: '外部车辆', description: '佛山站外部订单', icon: Truck }, + ] as const).map(option => { + const Icon = option.icon; + return ( + + ); + })} +
+
+
+ +
+
+
+
+
- + - +
{/* 时段加氢量柱图 */} @@ -183,11 +258,11 @@ export default function HydrogenDaily() { {/* 主行 + 子行 */} {error ? (
- ) : rows === null ? ( + ) : filteredRows === null ? (
- ) : rows.length === 0 || totalKg === 0 ? ( -
- ) : rows.map(r => { + ) : filteredRows.length === 0 || totalKg === 0 ? ( +
+ ) : filteredRows.map(r => { const open = expanded.has(r.date); const isAbnormal = Math.abs(r.chainPct) >= 0.3; const abnormalBg = isAbnormal diff --git a/src/modules/energy/hydrogen-daily/model.test.ts b/src/modules/energy/hydrogen-daily/model.test.ts index 651ed1d..e4c45bb 100644 --- a/src/modules/energy/hydrogen-daily/model.test.ts +++ b/src/modules/energy/hydrogen-daily/model.test.ts @@ -2,6 +2,7 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import type { HydrogenDailyRow } from '../types.js'; import { + filterHydrogenRows, getQuickRange, getRangeModeLabel, normalizeRange, @@ -82,3 +83,53 @@ test('空数据保持零值且没有峰谷日', () => { zeroDays: 0, }); }); + +const searchRows: HydrogenDailyRow[] = [ + { date: '2026-09-17', totalKg: 50, chainPct: -0.75, customerType: 'all', stations: [ + { name: '嘉兴站', kg: 50, pricePerKg: 30, chainPct: 0 }, + ] }, + { date: '2026-09-16', totalKg: 200, chainPct: 1, customerType: 'all', stations: [ + { name: '佛山南海羚牛加氢站', kg: 30, pricePerKg: 38, chainPct: 2 }, + { name: '佛山豪汇石油加氢站', kg: 10, pricePerKg: 35, chainPct: 0 }, + { name: '嘉兴站', kg: 160, pricePerKg: 30, chainPct: 1 }, + ] }, + { date: '2026-09-15', totalKg: 100, chainPct: 0, customerType: 'all', stations: [ + { name: '佛山南海羚牛加氢站', kg: 10, pricePerKg: 38, chainPct: 0 }, + { name: '嘉兴站', kg: 90, pricePerKg: 30, chainPct: 0 }, + ] }, +]; + +test('站名搜索同步更新日明细、总量、趋势和环比,保留完整日期及原始数据', () => { + const before = structuredClone(searchRows); + const filtered = filterHydrogenRows(searchRows, ' 佛山 ')!; + assert.deepEqual(filtered.map(row => row.date), searchRows.map(row => row.date)); + assert.deepEqual(filtered.map(row => row.totalKg), [0, 40, 10]); + assert.deepEqual(filtered.map(row => row.chainPct), [-1, 3, 0]); + assert.equal(filtered[1].stations.length, 2); + assert.equal(filtered[1].stations[0].pricePerKg, 38); + assert.equal(filtered[1].stations[0].chainPct, 2); + const summary = summarizeHydrogenRows(filtered); + assert.equal(summary.totalKg, 50); + assert.equal(summary.stationCount, 2); + assert.equal(summary.activeDays, 2); + assert.equal(summary.avgKg, 25); + assert.equal(summary.zeroDays, 1); + assert.deepEqual(summary.trendData.map(row => row.totalKg), [10, 40, 0]); + assert.deepEqual(searchRows, before); +}); + +test('无匹配关键词显示零结果,清除或空白搜索恢复全量,加载中保持加载状态', () => { + const missing = filterHydrogenRows(searchRows, '不存在的站点')!; + assert.equal(missing.length, 3); + assert.ok(missing.every(row => row.totalKg === 0 && row.chainPct === 0 && row.stations.length === 0)); + assert.strictEqual(filterHydrogenRows(searchRows, ''), searchRows); + assert.strictEqual(filterHydrogenRows(searchRows, ' '), searchRows); + assert.equal(summarizeHydrogenRows(filterHydrogenRows(searchRows, '')).totalKg, 350); + assert.equal(filterHydrogenRows(null, '佛山'), null); + assert.deepEqual(filterHydrogenRows([], '佛山'), []); +}); + +test('完整站名可定位单站,搜索将特殊字符按普通文字匹配', () => { + assert.equal(summarizeHydrogenRows(filterHydrogenRows(searchRows, '佛山南海羚牛加氢站')).totalKg, 40); + assert.equal(summarizeHydrogenRows(filterHydrogenRows(searchRows, '%')).totalKg, 0); +}); diff --git a/src/modules/energy/hydrogen-daily/model.ts b/src/modules/energy/hydrogen-daily/model.ts index e2403e8..d22b23b 100644 --- a/src/modules/energy/hydrogen-daily/model.ts +++ b/src/modules/energy/hydrogen-daily/model.ts @@ -9,6 +9,28 @@ export { type RangeMode, } from '../daily-range/model'; +// 接口已返回所选车辆范围内的全部日/站点数据;搜索后统一重算日合计与环比。 +export function filterHydrogenRows(rows: HydrogenDailyRow[] | null, stationSearch: string): HydrogenDailyRow[] | null { + const keyword = stationSearch.trim().toLocaleLowerCase(); + if (!rows || !keyword) return rows; + + const filtered = rows.map(row => { + const stations = row.stations.filter(station => station.name.toLocaleLowerCase().includes(keyword)); + return { + ...row, + stations, + totalKg: Math.round(stations.reduce((sum, station) => sum + station.kg, 0) * 100) / 100, + chainPct: 0, + }; + }); + let previousKg = 0; + for (const row of [...filtered].sort((a, b) => a.date.localeCompare(b.date))) { + row.chainPct = previousKg > 0 ? (row.totalKg - previousKg) / previousKg : 0; + previousKg = row.totalKg; + } + return filtered; +} + export function summarizeHydrogenRows(rows: HydrogenDailyRow[] | null) { const source = rows ?? []; // 图表固定按日期升序;复制数组避免改变接口返回及表格原始顺序。 diff --git a/src/modules/energy/types.ts b/src/modules/energy/types.ts index a1c27c2..9825975 100644 --- a/src/modules/energy/types.ts +++ b/src/modules/energy/types.ts @@ -1,4 +1,4 @@ -export type CustomerType = 'external' | 'lingniu'; +export type CustomerType = 'external' | 'lingniu' | 'all'; export type DateQuickPick = 'thisWeek' | 'thisMonth' | 'last15'; export interface HydrogenKpi {