style(专题): 「题单」改名「专题」,顶栏、列表、详情、后台、成就说法统一
Deploy / deploy (push) Canceled after 0s

只改界面文字和注释:网址 /problemset、接口、表名、字段名都不动,老链接照常能打开。
设计稿页名「题单重设计」和已执行的迁移文件保留原样。
线上 3 个成就(题单收藏家 / 入门 / 通关)已同步改名。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-11-03 00:12:18 +08:00
1 parent 959c326bc9
commit 1b703d13ae
68 files changed
+306 -306

No files matched your search

+1 -1
View File
@@ -62,7 +62,7 @@ export const pendingAchievementSchema = z.object({
/**
* WebSocket 推来的解锁通知。比 `/achievements/pending` 多一个 `kind` ——
* 题单奖章和成就来自两张表、id 会重叠,前端靠它区分(见 events.ts 的 publishAchievementNotification)。
* 专题奖章和成就来自两张表、id 会重叠,前端靠它区分(见 events.ts 的 publishAchievementNotification)。
*/
export const achievementNotificationSchema = pendingAchievementSchema.extend({
kind: z.enum(['achievement', 'badge']),
+5 -5
View File
@@ -462,7 +462,7 @@ export const updateAcmHelperRequestSchema = z.object({
checked: z.boolean(),
})
// ---------------------------------------------------------------- 题单管理
// ---------------------------------------------------------------- 专题管理
export const adminProblemSetSchema = z.object({
id: z.number().int(),
@@ -474,13 +474,13 @@ export const adminProblemSetSchema = z.object({
assignedAt: z.string().nullable(),
/** 现在在不在布置期内(服务端的钟) */
assigning: z.boolean(),
/** 公开:学生能在题单列表里看到、能进 */
/** 公开:学生能在专题列表里看到、能进 */
visible: z.boolean(),
createdBy: sampleUserSchema,
createTime: z.string(),
lastUpdateTime: z.string(),
problemsCount: z.number().int(),
/** 加入这份题单的人数,后台用来判断改动会影响多少人 */
/** 加入这个专题的人数,后台用来判断改动会影响多少人 */
participantCount: z.number().int(),
/** 做完(必做题全对)的人数 */
completedCount: z.number().int(),
@@ -519,7 +519,7 @@ export const addProblemsToSetResultSchema = z.object({
added: z.array(z.string()),
/** 题号不存在、不可见或是比赛题 */
missing: z.array(z.string()),
/** 本来就在题单里 */
/** 本来就在专题里 */
duplicate: z.array(z.string()),
})
@@ -527,7 +527,7 @@ export const updateProblemInSetRequestSchema = z.object({
isRequired: z.boolean(),
})
/** 拖动排序:题单里题目的链接 id,按新顺序给全 */
/** 拖动排序:专题里题目的链接 id,按新顺序给全 */
export const reorderProblemSetProblemsRequestSchema = z.object({
ids: z.array(z.number().int()).min(1),
})
+2 -2
View File
@@ -66,7 +66,7 @@ export const createFlowchartResponseSchema = z.object({
status: z.literal('pending'),
})
/**
* 流程图画到这两档算这道题做完:题单进度、课堂条、课堂任务、「你画的流程图」、结果页签的 ✓
* 流程图画到这两档算这道题做完:专题进度、课堂条、课堂任务、「你画的流程图」、结果页签的 ✓
* 都按它(设计文档 2026-09-28-problem-page-redesign 第 3 节决定 4、6)。前后端只有这一份
*/
export const FLOWCHART_PASS_GRADES = ['S', 'A'] as const
@@ -75,7 +75,7 @@ export function isFlowchartPass(grade: string | null | undefined) {
return (FLOWCHART_PASS_GRADES as readonly string[]).includes(grade ?? '')
}
/** 自己在一道题上评完的历次分数,早的在前;hidden = 被题单闸门藏起来的次数 */
/** 自己在一道题上评完的历次分数,早的在前;hidden = 被专题闸门藏起来的次数 */
export const flowchartScoresSchema = z.object({
scores: z.array(
z.object({
+1 -1
View File
@@ -472,7 +472,7 @@ export const problemListItemSchema = z.object({
hasAstRules: z.boolean(),
/** SQL 题(可选语言里有 SQL):写 SQL 语句查数据库,和写 C / Python 是两回事,列表里单独标出来 */
isSql: z.boolean(),
/** 0 = 做对了。画流程图的题,流程图评到 A / S 也给 0(和题单、课堂条一个口径) */
/** 0 = 做对了。画流程图的题,流程图评到 A / S 也给 0(和专题、课堂条一个口径) */
myStatus: z.number().int().nullable(),
/**
* 做对的人 / 交过的人,和题目页「统计」页签同一个口径(代码提交、不算还在判的)。
+12 -12
View File
@@ -4,7 +4,7 @@ import { paginatedSchema, sampleUserSchema } from './common'
import { maskedProblemDifficultySchema } from './problem'
/**
* 我在这份题单里的进度。
* 我在这个专题里的进度。
*
* 两个计数口径不同,别混用:
* - completedCount / totalCount 只数**必做题**,进度条和「做完了」看它;
@@ -60,9 +60,9 @@ export const problemSetSchema = z.object({
export const problemSetListSchema = paginatedSchema(problemSetSchema)
/**
* 题单页的题目行只渲染题号、标题,所以只下发这几样(外加题目主键)。
* 专题页的题目行只渲染题号、标题,所以只下发这几样(外加题目主键)。
* 以前复用 problemListItemSchema,服务端每次要多 join 一次 user / 标签表,
* 而那些字段题单页一个都不渲染。
* 而那些字段专题页一个都不渲染。
*/
export const problemSetProblemItemSchema = z.object({
id: z.number().int(),
@@ -77,11 +77,11 @@ export const problemSetProblemSchema = z.object({
problem: problemSetProblemItemSchema,
order: z.number().int(),
isRequired: z.boolean(),
/** 在这份题单里做对了没有(代码通过,或流程图拿到 A / S) */
/** 在这个专题里做对了没有(代码通过,或流程图拿到 A / S) */
isCompleted: z.boolean(),
/** 在这份题单里做对的时刻 */
/** 在这个专题里做对的时刻 */
solvedTime: z.string().nullable(),
/** 在这份题单里交错了几次(还没做对时显示「交过 n 次」) */
/** 在这个专题里交错了几次(还没做对时显示「交过 n 次」) */
wrongCount: z.number().int(),
/**
* 布置期内、以前做对过、旧代码正被藏着 —— 行上标「以前做对过 · 旧代码先藏着」。
@@ -94,11 +94,11 @@ export const joinProblemSetRequestSchema = z.object({
problemSetId: z.number().int().positive(),
})
/** 老师看某个班在这份题单里的情况:真名 × 每道题 */
/** 老师看某个班在这个专题里的情况:真名 × 每道题 */
export const problemSetClassCellSchema = z.object({
/** 在题单里做对的时刻 */
/** 在专题里做对的时刻 */
solvedTime: z.string().nullable(),
/** 在题单里交错的次数 */
/** 在专题里交错的次数 */
wrongCount: z.number().int(),
/** 加入之前就做对过这道题(任何入口) */
solvedBefore: z.boolean(),
@@ -116,7 +116,7 @@ export const problemSetClassStudentSchema = z.object({
})
export const problemSetClassViewSchema = z.object({
/** 加入过这份题单的学生分属哪些班:三分之一以上的人加入了的班在前(按最近一次加入),零星几个人的在后 */
/** 加入过这个专题的学生分属哪些班:三分之一以上的人加入了的班在前(按最近一次加入),零星几个人的在后 */
classes: z.array(z.object({ className: z.string(), joined: z.number().int(), size: z.number().int() })),
/** 这次看的是哪个班;谁都没加入时为 null */
className: z.string().nullable(),
@@ -126,7 +126,7 @@ export const problemSetClassViewSchema = z.object({
_id: z.string(),
title: z.string(),
isRequired: z.boolean(),
/** 这个班在题单里做对的人数 */
/** 这个班在专题里做对的人数 */
solved: z.number().int(),
}),
),
@@ -136,7 +136,7 @@ export const problemSetClassViewSchema = z.object({
students: z.array(problemSetClassStudentSchema),
})
/** 某道题为什么藏着旧代码:落在哪些正在布置的题单里 */
/** 某道题为什么藏着旧代码:落在哪些正在布置的专题里 */
export const problemSetLockSchema = z.object({
problemSetId: z.number().int(),
title: z.string(),
+9 -9
View File
@@ -185,11 +185,11 @@ export const createSubmissionRequestSchema = z.object({
.max(1024 * 1024),
contestId: z.number().int().positive().optional(),
/**
* 来源题单。学生从 `/problemset/:id/problem/:pid` 那个入口提交时前端带上,
* 后端落进 `submission.problemset_id`,提交列表据此标出「这条是刷题单刷出来的」。
* 来源专题。学生从 `/problemset/:id/problem/:pid` 那个入口提交时前端带上,
* 后端落进 `submission.problemset_id`,提交列表据此标出「这条是刷专题刷出来的」。
*
* 只是**来源标记**,不参与判题、也不参与题单进度记账 —— 进度由判完之后的
* `recordSolvedProblem` 记进所有已加入且含这道题的题单,和从哪个入口进来无关。
* 只是**来源标记**,不参与判题、也不参与专题进度记账 —— 进度由判完之后的
* `recordSolvedProblem` 记进所有已加入且含这道题的专题,和从哪个入口进来无关。
* 所以这里带错了顶多是标记不准,不会影响成绩。
*/
problemSetId: z.number().int().positive().optional(),
@@ -249,10 +249,10 @@ export const submissionDetailSchema = z.object({
problemTitle: z.string(),
/** 提交者**现在**的班级(和提交列表同口径);已删号的为 null */
className: z.string().nullable(),
/** 从哪个题单交的,同列表的 problemSet */
/** 从哪个专题交的,同列表的 problemSet */
problemSet: z.object({ id: z.number().int(), title: z.string() }).nullable(),
/**
* false = 自己的这份代码在题单布置期内先藏着(见后端 problemSetLockCutoffs):code 为空串、
* false = 自己的这份代码在专题布置期内先藏着(见后端 problemSetLockCutoffs):code 为空串、
* statisticInfo / info 为空对象,其余照给,页面照样摆出是谁、哪道题、什么结果。
* 别人的代码看不到时不走这里,直接 403
*/
@@ -262,7 +262,7 @@ export const submissionDetailSchema = z.object({
/**
* 同一个人在同一道题、同一场比赛(或都不在比赛里)交过的每一次,按时间先后。只有带
* `?attempts=1` 时才给(独立的提交详情页用),否则为 null —— 题目页每判完一次都要拉一遍
* 详情,用不着这一串。showLink 的口径同上,题单藏着的那几次点不进去
* 详情,用不着这一串。showLink 的口径同上,专题藏着的那几次点不进去
*/
attempts: z
.array(
@@ -310,7 +310,7 @@ export const submissionListItemSchema = z.object({
language: problemLanguageSchema,
statisticInfo: statisticInfoSchema,
/**
* 来源题单,非题单入口提交的为 null。比赛提交恒为 null(比赛题不会进题单)。
* 来源专题,非专题入口提交的为 null。比赛提交恒为 null(比赛题不会进专题)。
* 历史提交里只有「当年首次 AC 那一条」有值 —— 迁移 0007 从 problemset_submission
* 回填的就是这些,其余老提交无从判断入口,一律留空。
*/
@@ -488,7 +488,7 @@ export const submissionLessonsSchema = z.object({
* 只认那个语言,流程图评到 A / S 也算 —— 和任务页同一个口径)。推断的课为 null
*/
doneCount: z.number().int().nullable(),
/** 多半是从题单里交的 —— 「题单 7 道」 */
/** 多半是从专题里交的 —— 「专题 7 道」 */
fromProblemSet: z.boolean(),
/** 这节课的题(上面 problems)交了几次 —— 和点进去看到的对得上 */
total: z.number().int(),