From d3b05b86299d425450ceffb859e87a8abe79f77d Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Tue, 25 Aug 2026 18:07:01 -0600 Subject: [PATCH] =?UTF-8?q?refactor(=E5=A5=91=E7=BA=A6):=20=E8=A1=A5?= =?UTF-8?q?=E9=BD=90=2080=20=E4=B8=AA=E7=B1=BB=E5=9E=8B=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=EF=BC=8C=E5=89=8D=E7=AB=AF=E4=B8=8D=E5=86=8D=E6=89=8B=E6=8A=84?= =?UTF-8?q?=E5=BD=A2=E7=8A=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 契约原来有 184 个 schema 但只导出了 104 个类型,缺的那 80 个前端只能照着 手抄一遍 —— 这是「表格列静默空白」那一类 bug 的根因(d3348f9、2edb8cf, 以及上一个 commit 修的 Top100 两列)。现在 186 个 schema 对 186 个类型, 一一对应,下次要用直接 import。 补导出是机械的(fooSchema → Foo),零命名冲突。真正有价值的是换的过程中 契约逼出来的 5 处分歧 —— 手抄那份在说谎,而 vue-tsc 拦不住,因为类型说它是对的: - Tutorial.createdBy 手抄成了可选的 `User`(即 AdminUser,带 email、 rawPassword),后端下发的是必有的 SampleUser。读 createdBy.email 会拿到 undefined。列表页因此被迫写 `row.createdBy?.username` 和 `row.createdAt!`, 换成契约类型后两处断言都不需要了。 - TutorialListItem 手抄成 `Omit`,但后端列表接口连 code 一起省了 —— 类型声称 code 在。 - Testcase 手抄成 `{input_name, output_name, score}`:响应实际有 5 个字段, 且**没有 score**。score 是上传完成后前端按测试点数量平分补上去的,手抄那份 把本地字段说成了响应字段。现在写成 `TestCaseEntry & { score: string }`。 - Tag 手抄成 `{id, name}`,契约是 `{id, name, problemCount}` —— shared/api.ts 只好用 `Tag & { problemCount: number }` 把丢掉的补回来。 - CreateMessage 是旧后端按名字投递的形状(sender/recipient/submission), 契约要的是 recipientId/submissionId。全仓零引用,删掉。 同时删掉另外两个零引用的手写类型:LANGUAGE_SHOW_LABEL、UserAdminType; 本地重复的 SampleUser 换成契约的;oj/problem/list.vue 里本地第三份 Tag 改成 `ContractTag & { checked: boolean }`。 需要收窄的一律**从契约派生再收窄**,字段名跟着契约走,只有真正本地的那一两个 键是自己的: export type Exercise = Omit & { data: 七种题型的联合 } export type SubmitCodePayload = Omit & { language: LANGUAGE } 保留不动的窄化:StatisticInfo / SubmissionInfo(判题 JSONB 原文,snake_case)、 SQLDisplay*、ProblemFiltered(视图模型)、Exercise*Data(契约里 data 就是 Record,七种题型结构不同,后端本来也不校验)。 types.ts 的手写 interface 从 31 个降到 22 个。 顺带修的代码:admin/tutorial/detail.vue 新建教程的表单对象缺三个后端产出的 字段,加了 TutorialEdit(对齐 BlankProblem / BlankContest 的写法);三处测试点 上传原来是拿响应对象原地塞 score,改成 map 出新对象,分数算法一字未改。 验证:apps/api tsc(7.0.2) 0 error、check:routes 168 条无遮蔽、 apps/web vue-tsc 0 error、vite build 通过。改动绝大部分在类型层,运行时只有 测试点上传那三处(等价替换)—— **那条路径要传 zip 才能实跑,没有实打**, 只做了代码等价性核对。 Co-Authored-By: Claude Opus 5 --- .../problem/components/SQLTestcaseEditor.vue | 18 ++- .../problem/components/TestcaseGenerator.vue | 18 ++- apps/web/src/admin/problem/detail.vue | 10 +- apps/web/src/admin/tutorial/detail.vue | 4 +- apps/web/src/oj/problem/list.vue | 9 +- apps/web/src/shared/api.ts | 2 +- apps/web/src/utils/types.ts | 146 +++++++++--------- packages/contract/src/account.ts | 2 + packages/contract/src/achievement.ts | 2 + packages/contract/src/admin.ts | 55 +++++++ packages/contract/src/ai.ts | 5 + packages/contract/src/classroom.ts | 3 + packages/contract/src/common.ts | 2 + packages/contract/src/content.ts | 9 ++ packages/contract/src/contest.ts | 4 + packages/contract/src/flowchart.ts | 2 + packages/contract/src/problemset.ts | 4 + packages/contract/src/submission.ts | 3 + 18 files changed, 193 insertions(+), 105 deletions(-) diff --git a/apps/web/src/admin/problem/components/SQLTestcaseEditor.vue b/apps/web/src/admin/problem/components/SQLTestcaseEditor.vue index 7717ecd..7263f43 100644 --- a/apps/web/src/admin/problem/components/SQLTestcaseEditor.vue +++ b/apps/web/src/admin/problem/components/SQLTestcaseEditor.vue @@ -181,14 +181,16 @@ async function upload() { const file = new File([blob], "testcase.zip", { type: "application/zip" }) const res = await uploadTestcases(file, { sql: true }) - const testcases: Testcase[] = res.data.info - const baseScore = Math.floor(100 / testcases.length) - const remainder = 100 - baseScore * testcases.length - testcases.forEach((tc, i) => { - tc.score = String( - i === testcases.length - 1 ? baseScore + remainder : baseScore, - ) - }) + // score 不在上传响应里,是这里按测试点数量平分补上的(余数给最后一个) + const entries = res.data.info + const baseScore = Math.floor(100 / entries.length) + const remainder = 100 - baseScore * entries.length + const testcases: Testcase[] = entries.map((entry, i) => ({ + ...entry, + score: String( + i === entries.length - 1 ? baseScore + remainder : baseScore, + ), + })) emit("uploaded", res.data.id, testcases) message.success("上传成功") diff --git a/apps/web/src/admin/problem/components/TestcaseGenerator.vue b/apps/web/src/admin/problem/components/TestcaseGenerator.vue index 589ce29..3eb79a1 100644 --- a/apps/web/src/admin/problem/components/TestcaseGenerator.vue +++ b/apps/web/src/admin/problem/components/TestcaseGenerator.vue @@ -169,14 +169,16 @@ async function upload() { const file = new File([blob], "testcase.zip", { type: "application/zip" }) const res = await uploadTestcases(file) - const testcases: Testcase[] = res.data.info - const baseScore = Math.floor(100 / testcases.length) - const remainder = 100 - baseScore * testcases.length - testcases.forEach((tc, i) => { - tc.score = String( - i === testcases.length - 1 ? baseScore + remainder : baseScore, - ) - }) + // score 不在上传响应里,是这里按测试点数量平分补上的(余数给最后一个) + const entries = res.data.info + const baseScore = Math.floor(100 / entries.length) + const remainder = 100 - baseScore * entries.length + const testcases: Testcase[] = entries.map((entry, i) => ({ + ...entry, + score: String( + i === entries.length - 1 ? baseScore + remainder : baseScore, + ), + })) emit("uploaded", res.data.id, testcases) message.success("上传成功") diff --git a/apps/web/src/admin/problem/detail.vue b/apps/web/src/admin/problem/detail.vue index 53a09fa..af71d82 100644 --- a/apps/web/src/admin/problem/detail.vue +++ b/apps/web/src/admin/problem/detail.vue @@ -294,10 +294,12 @@ async function handleUploadTestcases({ file }: UploadCustomRequestOptions) { message.error("上传测试用例失败") return } - const testcases = res.data.info - for (let file of testcases) { - file.score = (100 / testcases.length).toFixed(0) - } + // score 不在上传响应里,前端按测试点数量平分补上 + const entries = res.data.info + const testcases: Testcase[] = entries.map((entry) => ({ + ...entry, + score: (100 / entries.length).toFixed(0), + })) problem.value.testCaseScore = testcases problem.value.testCaseId = res.data.id } catch (err) { diff --git a/apps/web/src/admin/tutorial/detail.vue b/apps/web/src/admin/tutorial/detail.vue index 500b385..dd6893f 100644 --- a/apps/web/src/admin/tutorial/detail.vue +++ b/apps/web/src/admin/tutorial/detail.vue @@ -1,7 +1,7 @@