## 公告:一个 schema 兼两种形态,兼出两处谎
前端 utils/types.ts 只手抄了**后台那份**公告形状,oj 侧也拿它当类型用。可
apps/api/src/routes/content.ts 的 `/announcements` 列表既不下发 content 也不
下发 visible —— 于是类型声称列表行有这两个键、运行时都是 undefined。今天没炸
只是因为组件恰好没读(正文是点开后另拉一次详情)。
契约那边则是另一头:announcementSchema 把 content 写成 `.optional()`,让一个
schema 同时兼列表和详情。代价是**详情**拿到的 content 类型也成了
`string | undefined`,组件只能 ?? 兜底。
现在两边都按后台侧早就在用的套路拆开:content 必填,列表用
`.omit({ content: true })` 派生。四个类型各归各位:
oj 列表 AnnouncementListItem 无 content、无 visible
oj 详情 Announcement 有 content、无 visible
后台列表 AdminAnnouncementListItem 无 content、有 visible
后台详情 AdminAnnouncement 有 content、有 visible
前端手抄的 Announcement / AnnouncementEdit / AnnouncementListItem 全部删掉,
AnnouncementEdit 改成从请求体派生(`CreateAnnouncementRequest & { id: number }`)。
后端 content.ts 的列表端点跟着换成 announcementListItemSchema —— 它本来就没
传 content,输出一字不变。
## SUBMISSION_RESULT 不再手抄
`-2 | -1 | 0 | ... | 10` 这 11 个码是从后端抄的,改成 `JudgeStatus | 9`:
后端那部分从契约派生,9 是前端本地的「正在提交」伪状态(后端永远不下发,
所以契约里没有,见 constants.ts 的 SubmissionStatus.submitting)。
这样 CLAUDE.md 说的「三处同步」才真的有人守:**实测过**,往
judgeStatusSchema 加一个 `z.literal(11)`,constants.ts 的 JUDGE_STATUS 立刻
报 TS2741 缺 '11' 的映射,加不上标签就编译不过。
顺带 useSubmissionMonitor.ts 里两处裸 `9`(各带一句 `// 9 = submitting`)
换成 SubmissionStatus.submitting,注释就不用写了。
## 顺手
TestcaseUploadedReturns 这个改名 re-export 去掉,直接用契约的
UploadTestCaseResponse(全仓 2 处引用)。
**没动 transforms.ts。** 之前把它记成「旧前端字段名的化石」,看下来判断有误:
filterResult 里 difficulty 要查 DIFFICULTY 映射表转中文、rate 要 getACRate 算、
status 要把 myStatus 翻成 passed/failed/not_test —— 是实打实的视图模型,不是
单纯改名。改它只会把计算逻辑挪个地方。
## 验证
tsc(apps/api) 0 error、check:routes 168 条无遮蔽、vue-tsc 0 error、vite build
通过。因为动了后端响应 schema,起服务实跑了公告的四条路径:
- curl 三个端点逐个核对键集:oj 列表无 content/visible 且只出可见的那条、
oj 详情有 content、后台列表有 visible 无 content 且两条都在。
- 浏览器里 oj 公告列表渲染正常、点开正文能出来;后台列表两行齐全;
编辑页表单载入正确;改标题保存 PUT 200,库里 title 变了、visible/top 没被
带歪,保存后跳回列表并重新拉取。
(agent-browser 的 `find text 保存 click` 打不到 naive-ui 的按钮 handler ——
不发请求也不报错,一开始误判成保存坏了。改用 DOM 上直接 .click() 就正常,
是自动化的坑,不是应用的问题。)
冒烟用的两条公告已删干净。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,8 @@ import type {
|
||||
Grade,
|
||||
ProblemDetail,
|
||||
ProblemDifficulty,
|
||||
JudgeStatus,
|
||||
CreateAnnouncementRequest,
|
||||
} from "@oj2/contract"
|
||||
|
||||
/**
|
||||
@@ -64,8 +66,15 @@ export type {
|
||||
SqlDisplayColumn,
|
||||
} from "@oj2/contract"
|
||||
|
||||
export type SUBMISSION_RESULT =
|
||||
-2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
|
||||
/**
|
||||
* 判题状态码 + 前端本地的「正在提交」(9)。
|
||||
*
|
||||
* 后端那 11 个码**从契约派生**,不再手抄 —— judgeStatusSchema 加一个码,
|
||||
* constants.ts 的 JUDGE_STATUS 会因为缺映射当场编译不过,这正是
|
||||
* CLAUDE.md 说的「三处同步」想要的效果。9 是后端永远不会下发的伪状态,
|
||||
* 见 constants.ts 的 SubmissionStatus.submitting。
|
||||
*/
|
||||
export type SUBMISSION_RESULT = JudgeStatus | 9
|
||||
|
||||
export type ProblemStatus = "passed" | "failed" | "not_test"
|
||||
|
||||
@@ -89,7 +98,7 @@ export type {
|
||||
* (stripped_output_md5 / input_size / output_size)。这套键名保持 snake_case
|
||||
* 是因为它会原样落进 problem.test_case_score 和判题沙箱读的 info 文件。
|
||||
*/
|
||||
export type { UploadTestCaseResponse as TestcaseUploadedReturns } from "@oj2/contract"
|
||||
export type { UploadTestCaseResponse } from "@oj2/contract"
|
||||
|
||||
/**
|
||||
* 题目表单里的测试点:上传返回的条目 + 前端本地算出的分值。
|
||||
@@ -408,23 +417,21 @@ export type {
|
||||
AcTrend,
|
||||
} from "@oj2/contract"
|
||||
|
||||
export interface AnnouncementEdit {
|
||||
id: number
|
||||
title: string
|
||||
tag: string
|
||||
content: string
|
||||
visible: boolean
|
||||
top: boolean
|
||||
}
|
||||
/**
|
||||
* 公告。两侧形状**不同**,原来这里只手抄了后台那份、oj 侧也拿它当类型用:
|
||||
* oj 的 `/announcements` 列表既不下发 `content` 也不下发 `visible`
|
||||
* (见 apps/api/src/routes/content.ts),于是类型声称有、实际是 undefined。
|
||||
* 现在各取各的契约类型。
|
||||
*/
|
||||
export type {
|
||||
Announcement,
|
||||
AnnouncementListItem,
|
||||
AdminAnnouncement,
|
||||
AdminAnnouncementListItem,
|
||||
} from "@oj2/contract"
|
||||
|
||||
export interface Announcement extends AnnouncementEdit {
|
||||
createdBy: SampleUser
|
||||
createTime: string
|
||||
lastUpdateTime: string
|
||||
}
|
||||
|
||||
/** 列表不下发正文:公告是 8MB 上限的富文本,列表页只显示标题 */
|
||||
export type AnnouncementListItem = Omit<Announcement, "content">
|
||||
/** 后台编辑表单:请求体 + id(新建时填 0,提交前由 api 层剥掉) */
|
||||
export type AnnouncementEdit = CreateAnnouncementRequest & { id: number }
|
||||
|
||||
/**
|
||||
* 站内信。取契约的形状,只把 `submission` 换成前端窄化过的那个
|
||||
@@ -445,7 +452,7 @@ export type {
|
||||
ReactionCounts,
|
||||
ReactionState,
|
||||
} from "@oj2/contract"
|
||||
import type { ReactionKey, SampleUser } from "@oj2/contract"
|
||||
import type { ReactionKey } from "@oj2/contract"
|
||||
|
||||
/**
|
||||
* 教程。直接取契约 —— 手抄的那份把 `createdBy` 写成了可选的 `User`(后端下发的是
|
||||
|
||||
Reference in New Issue
Block a user