From 1198895b54a9f1bb97a27502ba663160808aeb6a Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Tue, 25 Aug 2026 14:04:10 -0600 Subject: [PATCH] =?UTF-8?q?refactor(=E5=89=8D=E7=AB=AF):=20vue-tsc=20?= =?UTF-8?q?=E6=B8=85=E9=9B=B6=EF=BC=8Ctype-check=20=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E5=BD=93=20CI=20=E9=97=A8=E7=A6=81=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接上 vue-tsc 时有 143 条既有错误,上一批迁移带走 89 条,这里把剩下的清完。 大部分是噪音(未使用的回调参数、死变量、少数 unknown),但里面躺着两个真问题: - 个人主页查不存在的用户会抛:getProfile 返回 null 时后面照样取 .acmProblemsStatus,靠 `!` 压着。加了早返回。 - getProblemSetProgress 我上一批标错了类型:后台这个接口返回的是裸数组, 不是分页信封(和 oj 侧的 /user-progress 不一样)。已改正并加注释区分。 其余处理: - 未使用的回调参数按 TS 约定加 `_` 前缀(v-for 的项、供子类重写的空钩子、 路由守卫的 from);确认无用的局部变量直接删(clickX/clickY 算了点击位置 但飞出方向用的是随机角度,hasToday 下面已经用 lastDateOnly 判过, prefix 算了周/月但标签里没用上)。 - App.vue 的 highlight.js 注册:Promise.all([...]).then(m => m.map(x => x.default)) 会把元组塌成 (HLJSApi | LanguageFn)[],hljs 上就找不到 registerLanguage。 改成逐个取 .default。 - 给一批 api 补上契约类型(getMetrics / getHitokoto / getTutorials / formatCode / getProblemBeatRate / getClassUsernames 等),契约相应补了 8 个 z.infer 导出。 - ContestRank.submissionInfo 和 AcmHelperItem.acInfo 在 api 边界窄化成 SubmissionInfo —— 契约里是 Record(JSONB 原文)。 - FlowchartEditor 的 TS2589:vue-flow 的 Node 嵌套太深,ref([]) 的 UnwrapRef 推导撞上实例化层级上限,改成 ref([]) as Ref。 - api2.ts 的响应拦截器**故意**不返回 AxiosResponse(要把 { data } 信封剥掉), 类型上确实说不通,没硬掰,写注释说明为什么用断言。 - 题目列表的「随机」按钮在模板里一直是注释状态,对应的 getRandom 和 getRandomProblemID 一并删除(后端 /problems/random 保留不动)。 验证:vue-tsc 0 条,apps/api tsc、check:routes、web build 全通过; 修过 key 的列都打接口确认过字段真实存在。 Co-Authored-By: Claude Opus 5 --- apps/web/src/App.vue | 21 +++++----- apps/web/src/admin/api.ts | 12 ++++-- apps/web/src/admin/problemset/detail.vue | 4 +- apps/web/src/admin/setting/config.vue | 10 ++--- .../tutorial/components/ExerciseManager.vue | 2 +- apps/web/src/admin/tutorial/detail.vue | 1 - apps/web/src/main.ts | 2 +- .../src/oj/ai/components/DurationChart.vue | 4 -- .../src/oj/ai/components/EfficiencyChart.vue | 2 +- apps/web/src/oj/ai/components/StreakStats.vue | 11 ------ apps/web/src/oj/api.ts | 39 ++++++++++++------- .../src/oj/learn/components/ExerciseDebug.vue | 2 +- .../src/oj/problem/components/ProblemInfo.vue | 19 ++++----- apps/web/src/oj/problem/list.vue | 10 +---- apps/web/src/oj/store/contest.ts | 9 +++-- apps/web/src/oj/user/index.vue | 8 ++-- apps/web/src/shared/api.ts | 8 ++-- .../components/FlowchartEditor/CustomNode.vue | 2 +- .../components/FlowchartEditor/index.vue | 6 ++- apps/web/src/shared/components/Hitokoto.vue | 11 +++++- apps/web/src/shared/components/Login.vue | 2 +- apps/web/src/shared/components/Signup.vue | 2 +- apps/web/src/shared/composables/websocket.ts | 4 +- apps/web/src/utils/api2.ts | 11 +++++- apps/web/src/utils/download.ts | 4 +- apps/web/src/utils/functions.ts | 5 --- apps/web/src/utils/types.ts | 1 + packages/contract/src/account.ts | 1 + packages/contract/src/admin.ts | 1 + packages/contract/src/content.ts | 1 + packages/contract/src/contest.ts | 1 + packages/contract/src/site.ts | 1 + packages/contract/src/submission.ts | 1 + 33 files changed, 119 insertions(+), 99 deletions(-) diff --git a/apps/web/src/App.vue b/apps/web/src/App.vue index 76fc1ab..3770664 100644 --- a/apps/web/src/App.vue +++ b/apps/web/src/App.vue @@ -28,7 +28,9 @@ const hljsInstance = ref(null) const loadHighlightJS = async () => { if (hljsInstance.value) return hljsInstance.value - const [hljs, c, cpp, python, java, javascript, go, sql] = await Promise.all([ + // 逐个取 .default,不要在 Promise.all 后面 map —— 那样元组会塌成 + // (HLJSApi | LanguageFn)[],每个元素都变成联合类型,hljs 上就找不到方法了 + const [core, c, cpp, python, java, javascript, go, sql] = await Promise.all([ import("highlight.js/lib/core"), import("highlight.js/lib/languages/c"), import("highlight.js/lib/languages/cpp"), @@ -37,15 +39,16 @@ const loadHighlightJS = async () => { import("highlight.js/lib/languages/javascript"), import("highlight.js/lib/languages/go"), import("highlight.js/lib/languages/sql"), - ]).then((modules) => modules.map((m) => m.default)) + ]) + const hljs = core.default - hljs.registerLanguage("c", c) - hljs.registerLanguage("python", python) - hljs.registerLanguage("cpp", cpp) - hljs.registerLanguage("java", java) - hljs.registerLanguage("javascript", javascript) - hljs.registerLanguage("go", go) - hljs.registerLanguage("sql", sql) + hljs.registerLanguage("c", c.default) + hljs.registerLanguage("python", python.default) + hljs.registerLanguage("cpp", cpp.default) + hljs.registerLanguage("java", java.default) + hljs.registerLanguage("javascript", javascript.default) + hljs.registerLanguage("go", go.default) + hljs.registerLanguage("sql", sql.default) hljsInstance.value = hljs return hljs diff --git a/apps/web/src/admin/api.ts b/apps/web/src/admin/api.ts index e9a6d70..bdc21e9 100644 --- a/apps/web/src/admin/api.ts +++ b/apps/web/src/admin/api.ts @@ -2,11 +2,13 @@ import api2 from "utils/api2" import { toProblemListItem } from "admin/transforms" import type { AcTrend, + AdminProblemSetProgress, BatchProblemTagResponse, GenerateSqlTestCaseResponse, RenameTagResponse, SqlTestCaseScript, AcmHelperItem, + SubmissionInfo, AdminAiReport, AdminAiReportList, StuckProblem, @@ -36,7 +38,6 @@ import type { ProblemSetBadge, ProblemSetList, ProblemSetProblem, - ProblemSetProgressList, TutorialListItem, } from "utils/types" @@ -476,7 +477,11 @@ export function makeProblemPublic(id: number, displayId: string) { // 比赛辅助检查 export function getACMHelperList(contestId: number) { - return api2.get(`admin/contests/${contestId}/acm-helper`) + // acInfo 在契约里是 Record(acm_contest_rank 的 JSONB 原文), + // 组件侧按 SubmissionInfo 读,收窄放在这里 + return api2.get< + Array & { acInfo: SubmissionInfo }> + >(`admin/contests/${contestId}/acm-helper`) } export function updateACMHelperChecked( @@ -652,8 +657,9 @@ export function deleteProblemSetBadge(problemSetId: number, badgeId: number) { } // 题单进度管理 API +// 注意:返回的是裸数组,不是分页信封 —— 和 oj 侧的 /user-progress 不同 export function getProblemSetProgress(problemSetId: number) { - return api2.get( + return api2.get( `admin/problem-sets/${problemSetId}/progress`, ) } diff --git a/apps/web/src/admin/problemset/detail.vue b/apps/web/src/admin/problemset/detail.vue index 7b2c883..9d393af 100644 --- a/apps/web/src/admin/problemset/detail.vue +++ b/apps/web/src/admin/problemset/detail.vue @@ -4,7 +4,7 @@ import type { ProblemSet, ProblemSetProblem, ProblemSetBadge, - ProblemSetProgress, + AdminProblemSetProgress, } from "utils/types" import { getProblemSetDetail, @@ -37,7 +37,7 @@ const problemSetId = computed(() => Number(route.params.problemSetId)) const problemSet = ref(null) const problems = ref([]) const badges = ref([]) -const progress = ref([]) +const progress = ref([]) // 模态框状态 const showAddProblemModal = ref(false) diff --git a/apps/web/src/admin/setting/config.vue b/apps/web/src/admin/setting/config.vue index 925d739..8387af1 100644 --- a/apps/web/src/admin/setting/config.vue +++ b/apps/web/src/admin/setting/config.vue @@ -6,7 +6,7 @@ import { CLASS_NAME_RE, } from "utils/constants" import { parseTime } from "utils/functions" -import type { Server } from "utils/types" +import type { OrphanTestCase, Server, WebsiteConfig } from "utils/types" import { useConfigStore } from "shared/store/config" import { useConfigWebSocket } from "shared/composables/websocket" import { @@ -98,8 +98,8 @@ const serverColumns: DataTableColumn[] = [ width: 100, }, { title: "IP", key: "ip", width: 140 }, - { title: "判题机版本", key: "judger_version", width: 100 }, - { title: "服务器 URL", key: "service_url", width: 200 }, + { title: "判题机版本", key: "judgerVersion", width: 100 }, + { title: "服务器 URL", key: "serviceUrl", width: 200 }, { title: "上一次心跳", key: "last_heartbeat", @@ -114,14 +114,14 @@ const serverColumns: DataTableColumn[] = [ }, ] -const testcases = ref([]) +const testcases = ref([]) const token = ref("") const servers = ref([]) const abnormalServers = computed(() => servers.value.filter((item) => item.status === "abnormal"), ) -const websiteConfig = reactive({ +const websiteConfig = reactive({ websiteBaseUrl: import.meta.env.PUBLIC_OJ_URL, websiteName: "判题狗", websiteNameShortcut: "判题狗", diff --git a/apps/web/src/admin/tutorial/components/ExerciseManager.vue b/apps/web/src/admin/tutorial/components/ExerciseManager.vue index 6197073..74aa1d9 100644 --- a/apps/web/src/admin/tutorial/components/ExerciseManager.vue +++ b/apps/web/src/admin/tutorial/components/ExerciseManager.vue @@ -427,7 +427,7 @@ function typeTagType(type: ExerciseType) { () diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index e84d4f2..200f371 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -26,7 +26,7 @@ import { useUserStore } from "./shared/store/user" const authStore = useAuthModalStore() -router.beforeEach(async (to, from, next) => { +router.beforeEach(async (to, _from, next) => { // 检查是否需要认证 if (to.matched.some((record) => record.meta.requiresAuth)) { if (!storage.get(STORAGE_KEY.AUTHED)) { diff --git a/apps/web/src/oj/ai/components/DurationChart.vue b/apps/web/src/oj/ai/components/DurationChart.vue index 7331939..2039f12 100644 --- a/apps/web/src/oj/ai/components/DurationChart.vue +++ b/apps/web/src/oj/ai/components/DurationChart.vue @@ -60,10 +60,6 @@ const title = computed(() => { const data = computed>(() => { return { labels: aiStore.durationData.map((duration) => { - let prefix = "周" - if (duration.unit === "months") { - prefix = "月" - } return [ parseTime(duration.start, "M月D日"), parseTime(duration.end, "M月D日"), diff --git a/apps/web/src/oj/ai/components/EfficiencyChart.vue b/apps/web/src/oj/ai/components/EfficiencyChart.vue index 1659a98..0e724e6 100644 --- a/apps/web/src/oj/ai/components/EfficiencyChart.vue +++ b/apps/web/src/oj/ai/components/EfficiencyChart.vue @@ -9,7 +9,7 @@