2026-10-13 16:59:12 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 统计时间快捷范围(Phase 3 Task 4 仪表盘;Task 8 统计分析复用)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 口径事实源:app/platform/routes/stats_routes.py 的 from/to 解析
|
|
|
|
|
|
* (data_routes._parse_dt)——前端只传本地日期串 YYYY-MM-DD:
|
|
|
|
|
|
* - 日期-only 的 to 后端视为含当日全天(23:59:59.999999,边界含);
|
|
|
|
|
|
* - 边界是 naive UTC(与库内 created_at 同一对比空间),前端不做时区换算;
|
|
|
|
|
|
* - "近 N 天" = 含今天往回共 N 天(今天 / 近7天 / 近30天)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export type StatsQuickRange = 'today' | 'last7' | 'last30'
|
|
|
|
|
|
|
|
|
|
|
|
/** 快捷范围清单(顺序即切换器展示序) */
|
|
|
|
|
|
export const STATS_QUICK_RANGES: Array<{
|
|
|
|
|
|
value: StatsQuickRange
|
|
|
|
|
|
label: string
|
|
|
|
|
|
/** 窗口天数(含今天) */
|
|
|
|
|
|
days: number
|
|
|
|
|
|
}> = [
|
|
|
|
|
|
{ value: 'today', label: '今天', days: 1 },
|
|
|
|
|
|
{ value: 'last7', label: '近7天', days: 7 },
|
|
|
|
|
|
{ value: 'last30', label: '近30天', days: 30 }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
/** Date → 本地日期串 YYYY-MM-DD(月/日两位补零) */
|
|
|
|
|
|
function toDateKey(date: Date): string {
|
|
|
|
|
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
|
|
|
const day = String(date.getDate()).padStart(2, '0')
|
|
|
|
|
|
return `${date.getFullYear()}-${month}-${day}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 快捷范围 → {from, to} 查询参数(本地日期;now 可注入便于测试) */
|
|
|
|
|
|
export function rangeToParams(
|
|
|
|
|
|
range: StatsQuickRange,
|
|
|
|
|
|
now: Date = new Date()
|
|
|
|
|
|
): { from: string; to: string } {
|
|
|
|
|
|
const days = STATS_QUICK_RANGES.find((item) => item.value === range)?.days ?? 1
|
|
|
|
|
|
return {
|
|
|
|
|
|
from: toDateKey(new Date(now.getFullYear(), now.getMonth(), now.getDate() - (days - 1))),
|
|
|
|
|
|
to: toDateKey(now)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-10-13 22:33:55 +08:00
|
|
|
|
|
|
|
|
|
|
// ---- 自定义区间扩展(Phase 3 Task 8 统计分析页) ----------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
/** 时间筛选状态:快捷档 + 自定义(统计分析页;仪表盘只用快捷档) */
|
|
|
|
|
|
export type StatsRangeFilter = StatsQuickRange | 'custom'
|
|
|
|
|
|
|
|
|
|
|
|
/** 筛选档位清单(顺序即切换器展示序;自定义档配合日期区间选择器) */
|
|
|
|
|
|
export const STATS_RANGE_OPTIONS: Array<{ value: StatsRangeFilter; label: string }> = [
|
|
|
|
|
|
...STATS_QUICK_RANGES.map(({ value, label }) => ({ value, label })),
|
|
|
|
|
|
{ value: 'custom', label: '自定义' }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 筛选状态 → {from, to} 查询参数(custom 需完整且起≤止的区间)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 返回 null = 区间不完整/非法(自定义只选了一半、起>止)——调用方不发请求,
|
|
|
|
|
|
* 等 ElDatePicker 补全;快速档永不为 null。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function resolveStatsRange(
|
|
|
|
|
|
filter: StatsRangeFilter,
|
|
|
|
|
|
custom: [string, string] | null | undefined,
|
|
|
|
|
|
now: Date = new Date()
|
|
|
|
|
|
): { from: string; to: string } | null {
|
|
|
|
|
|
if (filter !== 'custom') return rangeToParams(filter, now)
|
|
|
|
|
|
if (!custom || !custom[0] || !custom[1]) return null
|
|
|
|
|
|
const [from, to] = custom
|
|
|
|
|
|
if (from > to) return null // ISO 日期串字典序即时间序
|
|
|
|
|
|
return { from, to }
|
|
|
|
|
|
}
|