feat(班级下拉): 全站的班级下拉按年级分组,新年级在上,组里按班号排
Deploy / deploy (push) Canceled after 0s

登录框、提交列表、数据统计、排名、课堂看板、题单班级视图、班级 PK 七处统一走
groupClassOptions。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-10 12:54:26 +08:00
1 parent 1e441411a5
commit 367cfc9d5d
6 files changed
+71 -59

No files matched your search

+40 -4
View File
@@ -40,14 +40,50 @@ export function submissionDayText(time: string) {
*/
export function classLabel(className: string | null) {
if (className === null || className === NO_CLASS) return "没填班级"
if (!/^\d{3,}$/.test(className)) return className
return `${className.slice(0, 2)}计算机${className.slice(2)}班`
}
type ClassOption = { label: string; value: string }
/**
* 班级下拉的选项:配置里的班,最后一项「没填班级」(这批号也要能和普通班一样圈出来看)。
* 带进来的班不在配置里(老班、手打的)也要显示得出来,不然框里是空的却在筛
* 全站的班级下拉都按年级分组:「25 级」底下列 251、252……,新的年级在上,组里按班号排
* (11 班排在 2 班后面)。年级是班号前两位,和排名的「本年级」同一个口径。
* `first` 给了就把那个年级提到最前(班级 PK 加班时先给同年级的)。
* `toOption` 换掉选项长相(题单的「n/m 人加入」、登录框的 ks 前缀);不是数字班号的
* 名字不分组,按原顺序跟在最后。
*/
export function groupClassOptions<T extends ClassOption = ClassOption>(
names: string[],
toOption: (name: string) => T = (name) => ({ label: classLabel(name), value: name }) as T,
first?: string,
) {
const groups = new Map<string, string[]>()
const rest: string[] = []
for (const name of new Set(names)) {
if (!/^\d{3,}$/.test(name)) {
rest.push(name)
continue
}
const grade = name.slice(0, 2)
groups.set(grade, [...(groups.get(grade) ?? []), name])
}
const grouped = [...groups]
.sort((a, b) => (a[0] === first ? -1 : b[0] === first ? 1 : b[0].localeCompare(a[0])))
.map(([grade, list]) => ({
type: "group" as const,
label: `${grade} 级`,
key: `grade-${grade}`,
children: list.sort((a, b) => a.localeCompare(b, "zh", { numeric: true })).map(toOption),
}))
return [...grouped, ...rest.map(toOption)]
}
/**
* 班级下拉的选项:配置里的班按年级分组,最后一项「没填班级」(这批号也要能和普通班一样
* 圈出来看)。带进来的班不在配置里(老班、手打的)也要显示得出来,不然框里是空的却在筛
*/
export function classSelectOptions(list: string[], current: string) {
const all = current && current !== NO_CLASS && !list.includes(current) ? [current, ...list] : list
return [...all, NO_CLASS].map((item) => ({ label: classLabel(item), value: item }))
const all = current && current !== NO_CLASS ? [current, ...list] : list
return [...groupClassOptions(all), { label: classLabel(NO_CLASS), value: NO_CLASS }]
}