refactor(时段): 两份手抄的选项列表与五份 "weeks:1" 解析收成一处
Deploy / deploy (push) Has been cancelled

时段选项的 value 是 `<date-fns 单位>:<数量>`,把它解成 Duration 的那段 `split(":")`
在**五个组件里各写了一遍**(提交统计、流程图统计、AI 分析页、榜单页、班级对比页),
每份的兜底还都不一样;选项列表也抄了好几份:

- 两个统计面板逐字相同地拼「10/20/30 分钟 + DURATION_OPTIONS + 全部时段」;
- rank/list.vue 和 class/pk.vue 各手写了同样的五条长时段,pk.vue 里还留着一句
  「与 rank/list.vue 保持一致」的注释 —— 靠注释同步的东西迟早不同步。

现在:`PANEL_DURATION_OPTIONS`(面板用,含分钟级和 all)、`LONG_DURATION_OPTIONS`
(榜单/班级对比用,从 DURATION_OPTIONS 派生)、`durationFromValue()`(唯一解析)。
各站点自己的兜底保留在原处,那部分本来就该各不相同。

## 行为零变化,逐条比对过

把改动前各处手写的列表原样取出来和新的比:面板 12 条、榜单 5 条、班级对比 6 条,
标签与取值逐条一致;11 个时段值的解析结果与旧的内联写法逐个相同。

唯一的差异在 `all`:旧写法产出 `{all: NaN}`,新写法回 null。**两边都到不了** ——
三处用到 subOptions 的地方全在 `query.duration === "all" ? … : …` 的 else 分支里,
三元短路,all 时根本不求值。逐处确认过。

## 实跑

- 榜单页:下拉 5 条顺序正确;切一周内,请求从 start=2026-08-10(months:1)
  变成 start=2026-09-03(weeks:1),正好差 7 天;
- 班级对比页:下拉 6 条含「全部时间」;两个班 PK,全部时间和一个月内都正常出结果;
- 提交统计面板:默认 minutes:10,统计请求的 start 正好比 end 早 10 分钟。

另:ChartJS.register 那 15 处**没有动**。逐个列出来看,它们注册的是各自需要的那一套,
不是同一份重复(真正逐字相同的只有 4 个 Bar 图和 2 个 Line 图),而且组件各自声明依赖
正是 chart.js 该用的方式 —— 没用到的控制器不会进包,忘了注册会当场抛
`"bar" is not a registered controller`,是响的不是静默的。集中注册只会把懒加载的图表
代码推进首屏,为省 6 处重复不值得。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012j1vgeDqay8wKCh8dPgPcH
This commit is contained in:
2026-09-10 20:28:33 -06:00
co-authored by Claude Opus 5
parent 8eae4bea7b
commit 2b07040aee
7 changed files with 82 additions and 72 deletions
+32
View File
@@ -322,6 +322,10 @@ export function sortFlowchartCriteria<T>(
return Object.entries(details).sort(([a], [b]) => rank(a) - rank(b))
}
/**
* 时段选项。`value` 是 `<date-fns 的单位>:<数量>`,由 `durationFromValue()` 解析 ——
* 别在组件里再手写一遍 `split(":")`,那套解析原来散在五个文件里。
*/
export const DURATION_OPTIONS = [
{ label: "本节课内", value: "hours:1" },
{ label: "两节课内", value: "hours:2" },
@@ -333,6 +337,34 @@ export const DURATION_OPTIONS = [
{ label: "一年内", value: "years:1" },
] as const
/**
* 两个统计面板(提交统计、流程图统计)的时段下拉。
*
* 比通用的那份多了头尾:前面三档分钟级是给**上课当场**用的 —— 老师布置完一道题,
* 想看的就是「刚才这十分钟谁交了」;末尾的 `all` 不是一个时长,`query.duration === "all"`
* 会走各自的分支不带时间条件,所以它永远不会进 durationFromValue()。
*
* 原来这份列表在两个面板里逐字抄了两遍。
*/
export const PANEL_DURATION_OPTIONS = [
{ label: "10分钟内", value: "minutes:10" },
{ label: "20分钟内", value: "minutes:20" },
{ label: "30分钟内", value: "minutes:30" },
...DURATION_OPTIONS,
{ label: "全部时段", value: "all" },
] as const
/**
* 榜单和班级对比用的长时段,一周起步。
*
* 这两个页面看的是长期趋势,「本节课内」这种窗口在那儿没有意义 —— 全班一小时内的
* AC 数拉出来比不出什么。原来 rank/list.vue 和 class/pk.vue 各手抄了一份同样的五条,
* pk.vue 里还留着「与 rank/list.vue 保持一致」的注释,现在从上面那份派生。
*/
export const LONG_DURATION_OPTIONS = DURATION_OPTIONS.filter(
(option) => !["hours:1", "hours:2", "days:1"].includes(option.value),
)
// 班级号的位数范围。学生用户名形如 ks<班级号><姓名>,班级号还要跟
// 网站配置里的班级列表对得上。
// 后端 OnlineJudge/utils/shortcuts.py 的 CLASS_NAME_MIN/MAX_DIGITS