接上 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<string, unknown>(JSONB 原文)。
- FlowchartEditor 的 TS2589:vue-flow 的 Node 嵌套太深,ref<Node[]>([]) 的
UnwrapRef 推导撞上实例化层级上限,改成 ref([]) as Ref<Node[]>。
- 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 <noreply@anthropic.com>
93 lines
2.1 KiB
TypeScript
93 lines
2.1 KiB
TypeScript
import { addAPIProvider } from "@iconify/vue"
|
|
import { createPinia } from "pinia"
|
|
import { createRouter, createWebHistory } from "vue-router"
|
|
|
|
import { STORAGE_KEY } from "utils/constants"
|
|
import storage from "utils/storage"
|
|
|
|
import App from "./App.vue"
|
|
import { admins, ojs } from "./routes"
|
|
|
|
const router = createRouter({
|
|
history: createWebHistory(),
|
|
routes: [ojs, admins],
|
|
})
|
|
|
|
const pinia = createPinia()
|
|
|
|
// 创建 app 并安装插件
|
|
const app = createApp(App)
|
|
app.use(pinia)
|
|
app.use(router)
|
|
|
|
// 现在可以安全地使用 Store
|
|
import { useAuthModalStore } from "./shared/store/authModal"
|
|
import { useUserStore } from "./shared/store/user"
|
|
|
|
const authStore = useAuthModalStore()
|
|
|
|
router.beforeEach(async (to, _from, next) => {
|
|
// 检查是否需要认证
|
|
if (to.matched.some((record) => record.meta.requiresAuth)) {
|
|
if (!storage.get(STORAGE_KEY.AUTHED)) {
|
|
authStore.openLoginModal()
|
|
next("/")
|
|
return
|
|
}
|
|
}
|
|
|
|
// 检查权限
|
|
if (
|
|
to.matched.some(
|
|
(record) =>
|
|
record.meta.requiresSuperAdmin ||
|
|
record.meta.requiresTeacherAdmin ||
|
|
record.meta.requiresProblemPermission,
|
|
)
|
|
) {
|
|
if (!storage.get(STORAGE_KEY.AUTHED)) {
|
|
authStore.openLoginModal()
|
|
next("/")
|
|
return
|
|
}
|
|
|
|
const userStore = useUserStore()
|
|
if (!userStore.user) {
|
|
try {
|
|
await userStore.getMyProfile()
|
|
} catch (error) {
|
|
next("/")
|
|
return
|
|
}
|
|
}
|
|
if (to.matched.some((record) => record.meta.requiresSuperAdmin)) {
|
|
if (!userStore.isSuperAdmin) {
|
|
next("/")
|
|
return
|
|
}
|
|
} else if (to.matched.some((record) => record.meta.requiresTeacherAdmin)) {
|
|
if (!userStore.isTeacherOrAbove) {
|
|
next("/")
|
|
return
|
|
}
|
|
} else if (
|
|
to.matched.some((record) => record.meta.requiresProblemPermission)
|
|
) {
|
|
if (!userStore.hasProblemPermission) {
|
|
next("/")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
next()
|
|
})
|
|
|
|
app.mount("#app")
|
|
|
|
if (!!import.meta.env.PUBLIC_ICONIFY_URL) {
|
|
addAPIProvider("", {
|
|
resources: [import.meta.env.PUBLIC_ICONIFY_URL],
|
|
})
|
|
}
|