refactor(前端): vue-tsc 清零,type-check 可以当 CI 门禁了
接上 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>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { userProfileSchema } from "@oj2/contract"
|
||||
import { userProfileSchema, type Quote } from "@oj2/contract"
|
||||
import api2 from "utils/api2"
|
||||
import type { ApiResponse } from "utils/api2"
|
||||
import type { Profile, Tag } from "utils/types"
|
||||
@@ -38,9 +38,11 @@ export function getProblemTagList() {
|
||||
}
|
||||
|
||||
export function getHitokoto() {
|
||||
return api2.get("quotes/random")
|
||||
return api2.get<Quote>("quotes/random")
|
||||
}
|
||||
|
||||
export function getClassUsernames(classroom: string) {
|
||||
return api2.get(`classes/${encodeURIComponent(classroom)}/usernames`)
|
||||
return api2.get<string[]>(
|
||||
`classes/${encodeURIComponent(classroom)}/usernames`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onUnmounted, nextTick, computed, watch } from "vue"
|
||||
import { ref, onUnmounted, nextTick, computed } from "vue"
|
||||
import { getNodeTypeConfig } from "./useNodeStyles"
|
||||
import NodeHandles from "./NodeHandles.vue"
|
||||
import NodeActions from "./NodeActions.vue"
|
||||
|
||||
@@ -32,8 +32,10 @@ const { height = "calc(100vh - 133px)" } = defineProps<Props>()
|
||||
const { addEdges, removeNodes, removeEdges } = useVueFlow()
|
||||
|
||||
// 节点和边的响应式数据
|
||||
const nodes = ref<Node[]>([])
|
||||
const edges = ref<Edge[]>([])
|
||||
// 显式标注成 Ref<Node[]>,不走 ref<T>() 的 UnwrapRef 推导 —— vue-flow 的
|
||||
// Node/Edge 嵌套很深,让 TS 去调和两种形态会直接撞上「实例化层级过深」
|
||||
const nodes = ref([]) as Ref<Node[]>
|
||||
const edges = ref([]) as Ref<Edge[]>
|
||||
|
||||
// 历史记录管理
|
||||
const { canUndo, canRedo, saveState, undo, redo } = useHistory()
|
||||
|
||||
@@ -9,8 +9,15 @@ const hitokoto = reactive({
|
||||
async function receive() {
|
||||
try {
|
||||
const res = await getHitokoto()
|
||||
hitokoto.sentence = res.data.hitokoto
|
||||
hitokoto.from = res.data.from
|
||||
// 契约是 string | Record —— 一言数据集不在仓库里,形状留了余地
|
||||
const quote = res.data
|
||||
if (typeof quote === "string") {
|
||||
hitokoto.sentence = quote
|
||||
hitokoto.from = ""
|
||||
} else {
|
||||
hitokoto.sentence = String(quote.hitokoto ?? "")
|
||||
hitokoto.from = String(quote.from ?? "")
|
||||
}
|
||||
} catch (error) {
|
||||
hitokoto.sentence = "获取一言失败,请点击重试"
|
||||
hitokoto.from = "DEV"
|
||||
|
||||
@@ -39,7 +39,7 @@ const rules: FormRules = {
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
loginRef.value!.validate(async (errors: FormRules | undefined) => {
|
||||
loginRef.value!.validate(async (errors?: unknown) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
authStore.clearLoginError()
|
||||
|
||||
@@ -37,7 +37,7 @@ function goLogin() {
|
||||
}
|
||||
|
||||
function submit() {
|
||||
signupRef.value!.validate(async (errors: FormRules | undefined) => {
|
||||
signupRef.value!.validate(async (errors?: unknown) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
authStore.clearSignupError()
|
||||
|
||||
@@ -258,14 +258,14 @@ export class BaseWebSocket<T extends WebSocketMessage = WebSocketMessage> {
|
||||
/**
|
||||
* 断开连接钩子(子类可重写)
|
||||
*/
|
||||
protected onDisconnected(event: CloseEvent) {
|
||||
protected onDisconnected(_event: CloseEvent) {
|
||||
// 子类实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误钩子(子类可重写)
|
||||
*/
|
||||
protected onError(error: Event) {
|
||||
protected onError(_error: Event) {
|
||||
// 子类实现
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user