feat: 统计分析(卡片/漏斗/趋势/分布/推送健康,ECharts)
- admin 统计分析页(views/stats/overview): 指标卡四张(口径文案沿用 仪表盘)/ 漏斗横向条形(有序单色明度阶梯,标注 数量·占上一级%)/ 按日趋势双线(窗口内无数据日期前端补零)/ 意向环形(分类三槽固定 指派,色随实体不随筛选)/ 来源·站点·国家 Top10 横向条形(名义类目 统一单色)/ 推送健康表(四状态计数 + duplicated 子集行) - 筛选一行作用全部图表: 时间档(今天/近7天/近30天/自定义区间,自定义 不完整不发请求并提示)+ 租户切换(超管含"全部"合并选项,停用租户 标注;租户管理员固定自己) - charts.ts 纯函数层(选项构建 + 双模式主题档),配色经 validate_palette.js 明暗双模式校验(分类/漏斗 ordinal 全过; 意向饼语义红绿组 CVD 硬伤弃用改分类槽) - statsRange.ts 扩展自定义区间解析(既有仪表盘导出不动); 新增 charts/resolveStatsRange 单测 19 例(vitest 120→139) - 修复 merge_stats distributions 尾随逗号 1 元组 bug(合并端点曾把 distributions 序列化成 JSON 数组,单租户路径不受累)+ 回归钉子; 后端 595 测试全过 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,18 @@
|
||||
/**
|
||||
* 统计时间快捷范围单测(Phase 3 Task 4)
|
||||
* 统计时间快捷范围单测(Phase 3 Task 4;Task 8 补自定义区间 resolveStatsRange)
|
||||
*
|
||||
* 钉住口径:from/to 为本地日期串 YYYY-MM-DD;"近 N 天"含今天往回共 N 天
|
||||
* (今天→from==to;近7天→from=今天-6;近30天→今天-29)——后端把日期-only
|
||||
* 的 to 视为含当日全天(stats_routes 的 _parse_dt),前端不传时间部分。
|
||||
* 自定义档:完整且起≤止 → 原样透传;缺半/起>止 → null(调用方不发请求)。
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { STATS_QUICK_RANGES, rangeToParams } from '@/utils/statsRange'
|
||||
import {
|
||||
STATS_QUICK_RANGES,
|
||||
STATS_RANGE_OPTIONS,
|
||||
rangeToParams,
|
||||
resolveStatsRange
|
||||
} from '@/utils/statsRange'
|
||||
|
||||
/** 固定"现在":2026-10-13 09:30(本地时间)——跨月/跨年边界另测 */
|
||||
const NOW = new Date(2026, 9, 13, 9, 30)
|
||||
@@ -55,3 +61,43 @@ describe('统计时间快捷范围(src/utils/statsRange.ts)', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('时间筛选档位解析(resolveStatsRange——Task 8 自定义区间)', () => {
|
||||
it('快捷档等价 rangeToParams(custom 入参被忽略)', () => {
|
||||
expect(resolveStatsRange('last7', null, NOW)).toEqual({ from: '2026-10-07', to: '2026-10-13' })
|
||||
})
|
||||
|
||||
it('自定义完整区间:原样透传(不做时区/时间部分加工)', () => {
|
||||
expect(resolveStatsRange('custom', ['2026-09-01', '2026-10-13'], NOW)).toEqual({
|
||||
from: '2026-09-01',
|
||||
to: '2026-10-13'
|
||||
})
|
||||
})
|
||||
|
||||
it('自定义起=止:单日闭区间合法', () => {
|
||||
expect(resolveStatsRange('custom', ['2026-10-13', '2026-10-13'], NOW)).toEqual({
|
||||
from: '2026-10-13',
|
||||
to: '2026-10-13'
|
||||
})
|
||||
})
|
||||
|
||||
it('自定义缺半(null/空串)→ null(不发请求)', () => {
|
||||
expect(resolveStatsRange('custom', null, NOW)).toBeNull()
|
||||
expect(resolveStatsRange('custom', undefined, NOW)).toBeNull()
|
||||
expect(resolveStatsRange('custom', ['', '2026-10-13'], NOW)).toBeNull()
|
||||
expect(resolveStatsRange('custom', ['2026-10-13', ''], NOW)).toBeNull()
|
||||
})
|
||||
|
||||
it('自定义起>止 → null(ISO 日期串字典序即时间序)', () => {
|
||||
expect(resolveStatsRange('custom', ['2026-10-14', '2026-10-13'], NOW)).toBeNull()
|
||||
})
|
||||
|
||||
it('筛选档位清单:三快捷档 + 自定义殿后(切换器展示序)', () => {
|
||||
expect(STATS_RANGE_OPTIONS.map((item) => item.value)).toEqual([
|
||||
'today',
|
||||
'last7',
|
||||
'last30',
|
||||
'custom'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -39,3 +39,32 @@ export function rangeToParams(
|
||||
to: toDateKey(now)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 自定义区间扩展(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 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user