diff --git a/docs/bi-refactor-roadmap.md b/docs/bi-refactor-roadmap.md index 3b1d54f..7827e06 100644 --- a/docs/bi-refactor-roadmap.md +++ b/docs/bi-refactor-roadmap.md @@ -152,6 +152,7 @@ SUM(fee) / NULLIF(SUM(kwh), 0) - 已完成 ETC 记录/账单视图、日期、搜索和分页 URL 状态,以及超范围页码自动纠正。 - 已统一共享加载、空数据和故障组件的顶层卡片与明细内联边界,消除能源明细中的嵌套状态卡片。 - 已统一电能、氢能和 ETC 日期范围输入;电能与氢能共用快捷区间算法和单一输入事件路径。 +- 电能与氢能可根据 URL 日期恢复快捷区间选中态;过期区间自动归为自定义,避免标签与统计日期不一致。 - 已完成并发同参 GET 去重。 ### 阶段 B:核心下钻闭环 diff --git a/src/modules/energy/ElectricDaily.tsx b/src/modules/energy/ElectricDaily.tsx index f71f632..248c1e4 100644 --- a/src/modules/energy/ElectricDaily.tsx +++ b/src/modules/energy/ElectricDaily.tsx @@ -15,6 +15,7 @@ import AnalysisDateFilters from './AnalysisDateFilters'; import { dateRangeModeLabel, getQuickDateRange, + inferDateRangeMode, normalizeDateRange, type DateRangeMode, } from './date-range'; @@ -24,12 +25,15 @@ export default function ElectricDaily() { parseElectricDrillContext(window.location.search) )); const [customer, setCustomer] = useState(drillContext.vehicleScope); - const [pick, setPick] = useState(() => ( - drillContext.startDate && drillContext.endDate || drillContext.selectedDate ? 'custom' : 'last15' - )); + const [pick, setPick] = useState(() => { + if (drillContext.startDate && drillContext.endDate) { + return inferDateRangeMode(drillContext.startDate, drillContext.endDate); + } + return 'last15'; + }); const [dateRange, setDateRange] = useState(() => { if (drillContext.startDate && drillContext.endDate) { - return { start: drillContext.startDate, end: drillContext.endDate }; + return normalizeDateRange(drillContext.startDate, drillContext.endDate); } if (drillContext.selectedDate) { return { start: drillContext.selectedDate, end: drillContext.selectedDate }; @@ -118,7 +122,7 @@ export default function ElectricDaily() { const nextRange = { ...dateRange, [field]: value }; const normalized = normalizeDateRange(nextRange.start, nextRange.end); setPick('custom'); - setDateRange(nextRange); + setDateRange(normalized); commitDrillContext({ vehicleScope: customer, startDate: normalized.start, @@ -152,8 +156,9 @@ export default function ElectricDaily() { setDrillContext(next); setCustomer(next.vehicleScope); if (next.startDate && next.endDate) { - setPick('custom'); - setDateRange({ start: next.startDate, end: next.endDate }); + const normalized = normalizeDateRange(next.startDate, next.endDate); + setPick(inferDateRangeMode(normalized.start, normalized.end)); + setDateRange(normalized); } else if (next.selectedDate) { setPick('custom'); setDateRange({ start: next.selectedDate, end: next.selectedDate }); diff --git a/src/modules/energy/HydrogenDaily.tsx b/src/modules/energy/HydrogenDaily.tsx index b01c84d..329c19b 100644 --- a/src/modules/energy/HydrogenDaily.tsx +++ b/src/modules/energy/HydrogenDaily.tsx @@ -17,6 +17,7 @@ import AnalysisDateFilters from './AnalysisDateFilters'; import { dateRangeModeLabel, getQuickDateRange, + inferDateRangeMode, normalizeDateRange, type DateRangeMode, } from './date-range'; @@ -26,11 +27,13 @@ export default function HydrogenDaily() { parseHydrogenDrillContext(window.location.search) )); const [pick, setPick] = useState(() => ( - drillContext.startDate && drillContext.endDate ? 'custom' : 'last15' + drillContext.startDate && drillContext.endDate + ? inferDateRangeMode(drillContext.startDate, drillContext.endDate) + : 'last15' )); const [dateRange, setDateRange] = useState(() => ( drillContext.startDate && drillContext.endDate - ? { start: drillContext.startDate, end: drillContext.endDate } + ? normalizeDateRange(drillContext.startDate, drillContext.endDate) : getQuickDateRange('last15') )); const [customer, setCustomer] = useState(drillContext.vehicleScope); @@ -108,8 +111,8 @@ export default function HydrogenDaily() { if (!value) return; const nextRange = { ...dateRange, [field]: value }; setPick('custom'); - setDateRange(nextRange); const normalized = normalizeDateRange(nextRange.start, nextRange.end); + setDateRange(normalized); commitDrillContext({ ...drillContext, vehicleScope: customer, @@ -171,8 +174,9 @@ export default function HydrogenDaily() { setDrillContext(next); setCustomer(next.vehicleScope); if (next.startDate && next.endDate) { - setPick('custom'); - setDateRange({ start: next.startDate, end: next.endDate }); + const normalized = normalizeDateRange(next.startDate, next.endDate); + setPick(inferDateRangeMode(normalized.start, normalized.end)); + setDateRange(normalized); } }; window.addEventListener('popstate', handlePopState); diff --git a/src/modules/energy/date-range.test.ts b/src/modules/energy/date-range.test.ts index 3d14392..b752c73 100644 --- a/src/modules/energy/date-range.test.ts +++ b/src/modules/energy/date-range.test.ts @@ -1,6 +1,11 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { dateRangeModeLabel, getQuickDateRange, normalizeDateRange } from './date-range.js'; +import { + dateRangeModeLabel, + getQuickDateRange, + inferDateRangeMode, + normalizeDateRange, +} from './date-range.js'; const FRIDAY = new Date(2026, 7, 7, 16, 30, 0); @@ -15,3 +20,14 @@ test('normalizes date order and publishes consistent labels', () => { assert.equal(dateRangeModeLabel('thisMonth'), '本月'); assert.equal(dateRangeModeLabel('custom'), '自定义区间'); }); + +test('restores a quick mode only while URL dates still match that calendar range', () => { + assert.equal(inferDateRangeMode('2026-08-03', '2026-08-07', FRIDAY), 'thisWeek'); + assert.equal(inferDateRangeMode('2026-08-01', '2026-08-07', FRIDAY), 'thisMonth'); + assert.equal(inferDateRangeMode('2026-08-07', '2026-07-24', FRIDAY), 'last15'); + assert.equal(inferDateRangeMode('2026-08-01', '2026-08-06', FRIDAY), 'custom'); + assert.equal( + inferDateRangeMode('2026-08-01', '2026-08-07', new Date(2026, 7, 8, 9, 0, 0)), + 'custom', + ); +}); diff --git a/src/modules/energy/date-range.ts b/src/modules/energy/date-range.ts index bba7440..ba66ed4 100644 --- a/src/modules/energy/date-range.ts +++ b/src/modules/energy/date-range.ts @@ -40,6 +40,15 @@ export function normalizeDateRange(start: string, end: string): DateRangeValue { return start <= end ? { start, end } : { start: end, end: start }; } +export function inferDateRangeMode(start: string, end: string, now = new Date()): DateRangeMode { + const range = normalizeDateRange(start, end); + const match = QUICK_DATE_OPTIONS.find(({ id }) => { + const quickRange = getQuickDateRange(id, now); + return quickRange.start === range.start && quickRange.end === range.end; + }); + return match?.id ?? 'custom'; +} + export function dateRangeModeLabel(mode: DateRangeMode): string { return mode === 'custom' ? '自定义区间' : LABELS[mode]; }