refactor(前端): 接口错误改成 ApiError 类,catch 不再写 any
拦截器原来 reject 一个裸对象 `{ error, data }`,没有类型也没有类型守卫,于是 46 处
`catch (err: any)` 直接读 `err.data` / `err.error` —— 代码 bug 抛出的 TypeError 也按
接口错误读,弹出来的是 undefined 或英文。
- utils/api.ts:reject 改成 `ApiError extends Error`,字段名沿用 error(错误码)/
data(文案),存量读法语义不变;加 isApiError / errorCode / errorMessage。
errorMessage 对非接口错误一律用 fallback,不把 TypeError 的英文甩给学生
- 46 处 catch 全部换掉 any(45 处接口错误 + pythonSyntaxCheck 里 Skulpt 的错误对象)
- 断网等没有响应体的情况,文案从英文 "Request failed" 换成「网络异常,请检查网络后重试」
浏览器实跑核过:登录密码错误仍显示「用户名或密码不正确」(错误码分支)、
接口错误 reject 出来是 ApiError、网络不通时拿到中文文案、非接口错误走 fallback。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6406794971
commit
7a390323fe
29 files changed
+167
-106
No files matched your search
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { errorMessage } from "utils/api"
|
||||
import TextEditor from "shared/components/TextEditor.vue"
|
||||
import type { AnnouncementEdit } from "utils/types"
|
||||
import { createAnnouncement, editAnnouncement, getAnnouncement } from "../api"
|
||||
@@ -63,8 +64,8 @@ async function submit() {
|
||||
message.success("修改已保存")
|
||||
}
|
||||
router.push({ name: "admin announcement list" })
|
||||
} catch (err: any) {
|
||||
message.error(err.data)
|
||||
} catch (err) {
|
||||
message.error(errorMessage(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorMessage } from "utils/api"
|
||||
import { formatISO } from "date-fns"
|
||||
import TextEditor from "shared/components/TextEditor.vue"
|
||||
import { fromPickerValue, parseTime, toPickerValue } from "utils/functions"
|
||||
@@ -102,8 +103,8 @@ async function submit() {
|
||||
message.success("修改已保存")
|
||||
}
|
||||
router.push({ name: "admin contest list" })
|
||||
} catch (err: any) {
|
||||
message.error(err.data)
|
||||
} catch (err) {
|
||||
message.error(errorMessage(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorMessage } from "utils/api"
|
||||
import type { AcmHelperItem, SubmissionInfo } from "utils/types"
|
||||
import { NButton, NCheckbox, NSelect, NTag } from "naive-ui"
|
||||
import { parseTime } from "utils/functions"
|
||||
@@ -70,8 +71,8 @@ async function toggleChecked(item: HelperItem) {
|
||||
submissions.value = [...submissions.value]
|
||||
|
||||
message.success(newChecked ? "已标记为已检查" : "已取消标记")
|
||||
} catch (err: any) {
|
||||
message.error(err.data || "操作失败")
|
||||
} catch (err) {
|
||||
message.error(errorMessage(err, "操作失败"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +102,9 @@ async function markAllAsChecked() {
|
||||
|
||||
loadingMsg.destroy()
|
||||
message.success(`已标记 ${unchecked.length} 个提交为已检查`)
|
||||
} catch (err: any) {
|
||||
} catch (err) {
|
||||
loadingMsg.destroy()
|
||||
message.error(err.data || "批量操作失败")
|
||||
message.error(errorMessage(err, "批量操作失败"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +160,8 @@ async function viewSubmission(item: HelperItem) {
|
||||
}
|
||||
|
||||
toggleCodePanel(true)
|
||||
} catch (err: any) {
|
||||
message.error(err.data || "加载提交失败")
|
||||
} catch (err) {
|
||||
message.error(errorMessage(err, "加载提交失败"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,8 +175,8 @@ async function loadData() {
|
||||
// 再获取 AC 提交列表
|
||||
const data = await getACMHelperList(Number(props.contestID))
|
||||
submissions.value = data
|
||||
} catch (err: any) {
|
||||
message.error(err.data || "加载失败")
|
||||
} catch (err) {
|
||||
message.error(errorMessage(err, "加载失败"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { errorCode, errorMessage } from "utils/api"
|
||||
import {
|
||||
deleteContestProblem,
|
||||
deleteProblem,
|
||||
@@ -33,8 +34,8 @@ async function handleDeleteProblem() {
|
||||
}
|
||||
message.success("删除成功")
|
||||
emit("updated")
|
||||
} catch (err: any) {
|
||||
if (err.error === "problem-has-submissions") {
|
||||
} catch (err) {
|
||||
if (errorCode(err) === "problem-has-submissions") {
|
||||
message.error("这道题有提交之后,就不能被删除")
|
||||
} else {
|
||||
message.error("删除失败")
|
||||
@@ -81,13 +82,13 @@ async function handleMakePublic() {
|
||||
message.success("已成功转为公开题目(需要手动设置可见)")
|
||||
showMakePublicModal.value = false
|
||||
emit("updated") // 刷新列表
|
||||
} catch (err: any) {
|
||||
if (err.error === "display-id-exists") {
|
||||
} catch (err) {
|
||||
if (errorCode(err) === "display-id-exists") {
|
||||
message.error("该题目编号已存在,请使用其他编号")
|
||||
} else if (err.error === "already-public") {
|
||||
} else if (errorCode(err) === "already-public") {
|
||||
message.error("该题目已经是公开题目")
|
||||
} else {
|
||||
message.error("转换失败:" + (err.data || "未知错误"))
|
||||
message.error("转换失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorCode, errorMessage } from "utils/api"
|
||||
import { addProblemForContest } from "admin/api"
|
||||
|
||||
interface Props {
|
||||
@@ -22,13 +23,13 @@ async function addProblem() {
|
||||
displayID.value,
|
||||
)
|
||||
emit("added")
|
||||
} catch (err: any) {
|
||||
if (err.error === "display-id-exists") {
|
||||
} catch (err) {
|
||||
if (errorCode(err) === "display-id-exists") {
|
||||
message.error("显示编号重复了,请重新写一个")
|
||||
} else if (err.error === "contest-ended") {
|
||||
} else if (errorCode(err) === "contest-ended") {
|
||||
message.error("这场比赛已经结束了,不能添加题目")
|
||||
} else {
|
||||
message.error(err.data)
|
||||
message.error(errorMessage(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorCode, isApiError } from "utils/api"
|
||||
import type { LANGUAGE, SqlDisplay, Testcase } from "utils/types"
|
||||
import { createZipBlob } from "utils/functions"
|
||||
import SQLDataTable from "oj/problem/components/SQLDataTable.vue"
|
||||
@@ -81,15 +82,15 @@ onMounted(async () => {
|
||||
if (res.length) {
|
||||
scripts.value = res.map((f) => ({ ...blankEntry(), sql: f.content }))
|
||||
}
|
||||
} catch (err: any) {
|
||||
} catch (err) {
|
||||
// 新题、以及旧格式(非 SQL)测试点,后端回 404/409,保持空白就是对的,不该报错。
|
||||
// 但**其它**失败必须说出来:读不出来时编辑器长得和「这题本来就没测试点」一模一样,
|
||||
// 教师看不出区别,会以为要自己重新填。保存本身是安全的(后端读不到测试点信息会拒绝
|
||||
// 整个保存),所以这里只提示、不阻塞。
|
||||
const code = err?.error
|
||||
const code = errorCode(err)
|
||||
if (code && code !== "problem-not-found" && code !== "not-sql-test-case") {
|
||||
// 后端那句话(如"测试点信息读取失败")只说了现象,教师需要的是「下面是空的、别存」
|
||||
const detail = err?.data ? `(${err.data})` : ""
|
||||
const detail = isApiError(err) && err.data ? `(${err.data})` : ""
|
||||
loadError.value =
|
||||
`已有测试点脚本没能读出来${detail}。下方是空白模板,不是本题真实的测试点 —— ` +
|
||||
`直接保存不会丢数据(后端会拒绝),但也不会生效。请先排查测试点文件,或重新上传。`
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorCode, errorMessage } from "utils/api"
|
||||
import { PROBLEM_TAG_MAX_LENGTH } from "@oj2/contract"
|
||||
import { getProblemTagList } from "shared/api"
|
||||
import TextEditor from "shared/components/TextEditor.vue"
|
||||
@@ -484,11 +485,11 @@ async function submit() {
|
||||
params: { contestID: props.contestID },
|
||||
})
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.error === "display-id-exists") {
|
||||
} catch (err) {
|
||||
if (errorCode(err) === "display-id-exists") {
|
||||
message.error("显示编号重复了,请换一个显示编号")
|
||||
} else {
|
||||
message.error(err.data)
|
||||
message.error(errorMessage(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,9 +516,9 @@ async function generateMermaid() {
|
||||
)
|
||||
problem.value.mermaidCode = res.flowchart
|
||||
message.warning("如果渲染不成功,请复制到外部 AI 网站检查语法")
|
||||
} catch (err: any) {
|
||||
} catch (err) {
|
||||
// 没有 finally 的话,AI 接口回 502 时按钮永远卡在 loading,只能刷页面
|
||||
message.error(err.data || "生成失败,请稍后再试")
|
||||
message.error(errorMessage(err, "生成失败,请稍后再试"))
|
||||
} finally {
|
||||
isAIGenerating.value = false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { errorMessage } from "utils/api"
|
||||
import { deleteProblemSet, updateProblemSetStatus } from "admin/api"
|
||||
|
||||
interface Props {
|
||||
@@ -24,8 +25,8 @@ async function handleDeleteProblemSet() {
|
||||
await deleteProblemSet(props.problemSetId)
|
||||
message.success("删除成功")
|
||||
emit("updated")
|
||||
} catch (err: any) {
|
||||
message.error("删除失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("删除失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +54,8 @@ async function handleUpdateStatus() {
|
||||
message.success("状态更新成功")
|
||||
showStatusModal.value = false
|
||||
emit("updated")
|
||||
} catch (err: any) {
|
||||
message.error("状态更新失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("状态更新失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorMessage } from "utils/api"
|
||||
import { NTabPane, NTabs, NButton, NFlex } from "naive-ui"
|
||||
import type {
|
||||
AdminProblemSet,
|
||||
@@ -61,8 +62,8 @@ async function loadProblemSetDetail() {
|
||||
try {
|
||||
const res = await getProblemSetDetail(problemSetId.value)
|
||||
problemSet.value = res
|
||||
} catch (err: any) {
|
||||
message.error("加载题单详情失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("加载题单详情失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +71,8 @@ async function loadProblems() {
|
||||
try {
|
||||
const res = await getProblemSetProblems(problemSetId.value)
|
||||
problems.value = res
|
||||
} catch (err: any) {
|
||||
message.error("加载题目列表失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("加载题目列表失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +80,8 @@ async function loadBadges() {
|
||||
try {
|
||||
const res = await getProblemSetBadges(problemSetId.value)
|
||||
badges.value = res
|
||||
} catch (err: any) {
|
||||
message.error("加载奖章列表失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("加载奖章列表失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +89,8 @@ async function loadProgress() {
|
||||
try {
|
||||
const res = await getProblemSetProgress(problemSetId.value)
|
||||
progress.value = res
|
||||
} catch (err: any) {
|
||||
message.error("加载进度列表失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("加载进度列表失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,8 +101,8 @@ async function handleAddProblem(data: AddProblemToSetRequest) {
|
||||
showAddProblemModal.value = false
|
||||
loadProblems()
|
||||
loadProblemSetDetail() // 刷新题目数量
|
||||
} catch (err: any) {
|
||||
message.error("添加题目失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("添加题目失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,8 +112,8 @@ async function handleRemoveProblem(problemSetProblemId: number) {
|
||||
message.success("题目移除成功")
|
||||
loadProblems()
|
||||
loadProblemSetDetail() // 刷新题目数量
|
||||
} catch (err: any) {
|
||||
message.error("移除题目失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("移除题目失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +125,8 @@ async function handleEditProblem(data: UpdateProblemInSetRequest) {
|
||||
message.success("题目编辑成功")
|
||||
showEditProblemModal.value = false
|
||||
loadProblems()
|
||||
} catch (err: any) {
|
||||
message.error("编辑题目失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("编辑题目失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,8 +136,8 @@ async function handleAddBadge(data: BadgeFormData) {
|
||||
message.success("奖章创建成功")
|
||||
showAddBadgeModal.value = false
|
||||
loadBadges()
|
||||
} catch (err: any) {
|
||||
message.error("创建奖章失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("创建奖章失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,8 +146,8 @@ async function handleDeleteBadge(badgeId: number) {
|
||||
await deleteProblemSetBadge(problemSetId.value, badgeId)
|
||||
message.success("奖章删除成功")
|
||||
loadBadges()
|
||||
} catch (err: any) {
|
||||
message.error("删除奖章失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("删除奖章失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,8 +159,8 @@ async function handleEditBadge(data: BadgeFormData) {
|
||||
message.success("奖章编辑成功")
|
||||
showEditBadgeModal.value = false
|
||||
loadBadges()
|
||||
} catch (err: any) {
|
||||
message.error("编辑奖章失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("编辑奖章失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,8 +169,8 @@ async function handleRemoveUser(userId: number) {
|
||||
await removeUserFromProblemSet(problemSetId.value, userId)
|
||||
message.success("用户移除成功")
|
||||
loadProgress()
|
||||
} catch (err: any) {
|
||||
message.error("移除用户失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("移除用户失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorMessage } from "utils/api"
|
||||
import type { CreateProblemSetData, EditProblemSetData } from "utils/types"
|
||||
import { fromPickerValue, toPickerValue } from "utils/functions"
|
||||
import { getProblemSetDetail, createProblemSet, editProblemSet } from "../api"
|
||||
@@ -60,8 +61,8 @@ async function loadProblemSetDetail() {
|
||||
visible: data.visible,
|
||||
endTime: data.endTime ? new Date(data.endTime) : null,
|
||||
}
|
||||
} catch (err: any) {
|
||||
message.error("加载题单详情失败:" + (err.data || "未知错误"))
|
||||
} catch (err) {
|
||||
message.error("加载题单详情失败:" + errorMessage(err, "未知错误"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,11 +88,11 @@ async function handleSubmit() {
|
||||
message.success("题单创建成功")
|
||||
}
|
||||
router.push({ name: "admin problemset list" })
|
||||
} catch (err: any) {
|
||||
} catch (err) {
|
||||
message.error(
|
||||
(isEdit.value ? "更新" : "创建") +
|
||||
"题单失败:" +
|
||||
(err.data || "未知错误"),
|
||||
errorMessage(err, "未知错误"),
|
||||
)
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorMessage } from "utils/api"
|
||||
import { NButton, NTag } from "naive-ui"
|
||||
import {
|
||||
CLASS_NAME_MAX_DIGITS,
|
||||
@@ -210,8 +211,8 @@ async function saveWebsiteConfig() {
|
||||
}
|
||||
try {
|
||||
await editWebsite(websiteConfig)
|
||||
} catch (err: any) {
|
||||
message.error("保存失败:" + err.data)
|
||||
} catch (err) {
|
||||
message.error("保存失败:" + errorMessage(err))
|
||||
return
|
||||
}
|
||||
message.success("网站配置保存成功")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { errorMessage } from "utils/api"
|
||||
import { deleteTutorial } from "admin/api"
|
||||
|
||||
interface Props {
|
||||
@@ -22,8 +23,8 @@ async function handleDelete() {
|
||||
await deleteTutorial(props.tutorialID)
|
||||
message.success("删除成功")
|
||||
emit("deleted")
|
||||
} catch (err: any) {
|
||||
message.error(err.data)
|
||||
} catch (err) {
|
||||
message.error(errorMessage(err))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorMessage } from "utils/api"
|
||||
import type {
|
||||
Exercise,
|
||||
ExerciseType,
|
||||
@@ -317,8 +318,8 @@ async function save() {
|
||||
}
|
||||
showForm.value = false
|
||||
await load()
|
||||
} catch (e: any) {
|
||||
message.error(e.data ?? "保存失败")
|
||||
} catch (e) {
|
||||
message.error(errorMessage(e, "保存失败"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { errorMessage } from "utils/api"
|
||||
import CodeEditor from "shared/components/CodeEditor.vue"
|
||||
import MarkdownEditor from "shared/components/MarkdownEditor.vue"
|
||||
import type { TutorialEdit } from "utils/types"
|
||||
@@ -63,8 +64,8 @@ async function submit() {
|
||||
await updateTutorial(tutorial)
|
||||
message.success("修改已保存")
|
||||
}
|
||||
} catch (err: any) {
|
||||
message.error(err.data)
|
||||
} catch (err) {
|
||||
message.error(errorMessage(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorMessage } from "utils/api"
|
||||
import { NSwitch } from "naive-ui"
|
||||
import { parseTime } from "utils/functions"
|
||||
import type { TutorialListItem } from "utils/types"
|
||||
@@ -63,8 +64,8 @@ async function toggleVisible(tutorial: TutorialListItem) {
|
||||
try {
|
||||
await setTutorialVisibility(tutorial.id, tutorial.isPublic)
|
||||
message.success("更新成功")
|
||||
} catch (err: any) {
|
||||
message.error(err.data)
|
||||
} catch (err) {
|
||||
message.error(errorMessage(err))
|
||||
tutorial.isPublic = !tutorial.isPublic
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorMessage } from "utils/api"
|
||||
import {
|
||||
CLASS_NAME_MAX_DIGITS,
|
||||
CLASS_NAME_MAX_VALUE,
|
||||
@@ -66,8 +67,8 @@ async function uploadUsers() {
|
||||
hiddenElement.download = prefix.value + ".csv"
|
||||
hiddenElement.click()
|
||||
hiddenElement.remove()
|
||||
} catch (err: any) {
|
||||
message.error("上传失败:" + err.data)
|
||||
} catch (err) {
|
||||
message.error("上传失败:" + errorMessage(err))
|
||||
} finally {
|
||||
toggleLoading(false)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { errorMessage } from "utils/api"
|
||||
import { DataTableRowKey, NFlex, NTag, SelectOption } from "naive-ui"
|
||||
import Pagination from "shared/components/Pagination.vue"
|
||||
import { usePagination } from "shared/composables/pagination"
|
||||
@@ -250,8 +251,8 @@ async function handleEditUser() {
|
||||
})
|
||||
await editUser(user)
|
||||
}
|
||||
} catch (err: any) {
|
||||
message.error("保存失败:" + err.data)
|
||||
} catch (err) {
|
||||
message.error("保存失败:" + errorMessage(err))
|
||||
return
|
||||
}
|
||||
userEditing.value = null
|
||||
|
||||
Reference in new issue
Block a user