Quellcode durchsuchen

fix: 统一接口错误提示并消除重复消息

yangyi vor 2 Wochen
Ursprung
Commit
1eecae7f33

+ 4 - 3
README.md

@@ -79,10 +79,11 @@ VITE_API_BASE_URL=http://localhost:8080
 `src/util/axios-instance.ts` 集中处理鉴权与提示:
 
 - 请求拦截器从 `localStorage.token` 读取令牌并注入 `Authorization: Bearer <token>`。
-- 响应拦截器按业务状态码判断:`code` 为 `0` 或 `200` 视为成功;成功且带 `message` 时弹出 `ElMessage.success`,失败时弹出后端返回的 `message` 并 reject。
-- HTTP `401` 统一跳转登录页。
+- 响应拦截器按业务状态码判断:`code` 为 `0` 或 `200` 视为成功并直接返回 `response.data`;失败时统一弹出后端返回的 `message` 并 reject。
+- HTTP/网络异常同样由拦截器统一提示(相同文案 1.5 秒内去重);HTTP `401` 统一提示并跳转登录页。
+- 后端成功响应的 `message` 固定为 `success`,因此拦截器不再弹成功提示;操作成功文案与表单校验提示保留在视图层。
 
-视图层因此不需要各自处理错误提示,只需 `try/catch` 处理业务失败分支。
+视图层不再处理接口错误提示,只需 `try/catch` 保留必要的状态重置、页面跳转等控制流。
 
 ## 接口代码生成
 

+ 43 - 11
src/util/axios-instance.ts

@@ -31,26 +31,58 @@ axiosInstance.interceptors.request.use(
     }
 );
 
+const ERROR_MESSAGE_INTERVAL = 1500;
+let lastErrorMessage = '';
+let lastErrorTime = 0;
+
+const showError = (message: string) => {
+    const now = Date.now();
+    if (message === lastErrorMessage && now - lastErrorTime < ERROR_MESSAGE_INTERVAL) {
+        return;
+    }
+    lastErrorMessage = message;
+    lastErrorTime = now;
+    ElMessage.error(message);
+};
+
+const getErrorMessage = (error: any): string => {
+    if (error.response?.data?.message) {
+        return error.response.data.message;
+    }
+    if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
+        return '请求超时,请稍后重试';
+    }
+    if (!error.response) {
+        return '网络异常,请检查网络连接';
+    }
+    return `请求失败(${error.response.status})`;
+};
+
 // 响应拦截器
 axiosInstance.interceptors.response.use(
     (response: AxiosResponse) => {
         // 根据后端返回格式调整
-        if (response.data.code !== 0 && response.data.code !== 200) {
-            if (response.data.message) {
-                ElMessage.error(response.data.message);
-            }
-            return Promise.reject(new Error(response.data.message || '请求失败'));
-        }
-        if (response.data.message) {
-            ElMessage.success(response.data.message);
+        const data = response.data;
+        if (data?.code !== 0 && data?.code !== 200) {
+            const message = data?.message || '请求失败';
+            showError(message);
+            const error = new Error(message) as Error & { code?: number; data?: unknown };
+            error.code = data?.code;
+            error.data = data?.data;
+            return Promise.reject(error);
         }
-        return response.data;
+        return data;
     },
     (error) => {
         // 统一错误处理
-        if (error.response?.status === 401) {
+        const status = error.response?.status;
+        const message = status === 401 ? '登录已过期,请重新登录' : getErrorMessage(error);
+        showError(message);
+        if (status === 401) {
             // 未授权,跳转登录
-            window.location.href = '/login';
+            window.setTimeout(() => {
+                window.location.href = '/login';
+            }, 500);
         }
         return Promise.reject(error);
     }

+ 2 - 8
src/view/LoginView.vue

@@ -213,11 +213,8 @@ const handleLogin = async () => {
       })
       ElMessage.success('登录成功')
       router.push((route.query.redirect as string) || '/')
-    } else {
-      ElMessage.error(res.message || '登录失败')
     }
-  } catch (err: any) {
-    ElMessage.error(err.message || '登录失败,请检查网络')
+  } catch {
   } finally {
     loginLoading.value = false
   }
@@ -243,11 +240,8 @@ const handleRegister = async () => {
       registerForm.username = ''
       registerForm.password = ''
       registerForm.confirmPassword = ''
-    } else {
-      ElMessage.error(res.message || '注册失败')
     }
-  } catch (err: any) {
-    ElMessage.error(err.message || '注册失败,请检查网络')
+  } catch {
   } finally {
     registerLoading.value = false
   }

+ 0 - 9
src/view/admin/CategoryManageView.vue

@@ -32,11 +32,8 @@ const fetchData = async () => {
     const res = await api.list()
     if (res?.code === 200) {
       tableData.value = res.data || []
-    } else {
-      ElMessage.error(res?.message || '获取分类失败')
     }
   } catch {
-    ElMessage.error('获取分类失败')
     tableData.value = []
   } finally {
     loading.value = false
@@ -73,11 +70,8 @@ const handleSave = async () => {
       ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
       dialogVisible.value = false
       fetchData()
-    } else {
-      ElMessage.error(res?.message || '保存失败')
     }
   } catch {
-    ElMessage.error('保存失败')
   }
 }
 
@@ -90,11 +84,8 @@ const handleDelete = (row: CategoryVo) => {
         if (res?.code === 200) {
           ElMessage.success('删除成功')
           fetchData()
-        } else {
-          ElMessage.error(res?.message || '删除失败')
         }
       } catch {
-        ElMessage.error('删除失败')
       }
     })
     .catch(() => {})

+ 4 - 29
src/view/admin/ContestManageView.vue

@@ -76,7 +76,6 @@ const fetchData = async () => {
     tableData.value = pageData?.data || []
     pagination.total = pageData?.total || 0
   } catch {
-    ElMessage.error('获取比赛列表失败')
     tableData.value = []
   } finally {
     loading.value = false
@@ -186,11 +185,8 @@ const handleSave = async () => {
       ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
       dialogVisible.value = false
       fetchData()
-    } else {
-      ElMessage.error(res?.message || '保存失败')
     }
   } catch {
-    ElMessage.error('保存失败')
   }
 }
 
@@ -203,11 +199,8 @@ const handleDelete = (row: ContestVo) => {
         if (res?.code === 200) {
           ElMessage.success('删除成功')
           fetchData()
-        } else {
-          ElMessage.error(res?.message || '删除失败')
         }
       } catch {
-        ElMessage.error('删除失败')
       }
     })
     .catch(() => {})
@@ -281,11 +274,8 @@ const loadConfig = async () => {
       existingProblems.value = (d.problems || []).map((p) => ({ ...p }))
       configProblems.value = (d.problems || []).map((p) => ({ ...p }))
       configLoaded.value = true
-    } else {
-      ElMessage.error(res?.message || '获取比赛详情失败')
     }
   } catch {
-    ElMessage.error('获取比赛详情失败')
   }
 }
 
@@ -332,34 +322,21 @@ const saveConfig = async () => {
       const old = existingMap.get(cp.problemId)
       const dto: ContestProblemDto = { problemId: cp.problemId!, score: cp.score ?? 0, displayOrder: cp.displayOrder ?? 1 }
       if (!old) {
-        const res = await api.addProblem(id, dto)
-        if (res?.code !== 200) {
-          ElMessage.error(res?.message || `添加题目 ${cp.title} 失败`)
-          return
-        }
+        await api.addProblem(id, dto)
       } else if (old.score !== cp.score || old.displayOrder !== cp.displayOrder) {
-        const res = await api.updateProblem(id, dto)
-        if (res?.code !== 200) {
-          ElMessage.error(res?.message || `更新题目 ${cp.title} 失败`)
-          return
-        }
+        await api.updateProblem(id, dto)
       }
     }
 
     for (const ex of existing) {
       if (!currentMap.has(ex.problemId)) {
-        const res = await api.removeProblem(id, { problemId: ex.problemId! })
-        if (res?.code !== 200) {
-          ElMessage.error(res?.message || `移除题目 ${ex.title} 失败`)
-          return
-        }
+        await api.removeProblem(id, { problemId: ex.problemId! })
       }
     }
 
     ElMessage.success('题目配置保存成功')
     configDialogVisible.value = false
   } catch {
-    ElMessage.error('保存题目配置失败')
   } finally {
     configSaving.value = false
   }
@@ -395,11 +372,9 @@ const openRegistrations = (row: ContestVo) => {
     .then((res) => {
       if (res?.code === 200) {
         regTable.value = res.data || []
-      } else {
-        ElMessage.error(res?.message || '获取报名名单失败')
       }
     })
-    .catch(() => ElMessage.error('获取报名名单失败'))
+    .catch(() => {})
     .finally(() => {
       regLoading.value = false
     })

+ 1 - 27
src/view/admin/ProblemManageView.vue

@@ -127,7 +127,6 @@ const fetchData = async () => {
     tableData.value = pageData?.data || []
     pagination.total = pageData?.total || 0
   } catch {
-    ElMessage.error('获取题目列表失败')
     tableData.value = []
   } finally {
     loading.value = false
@@ -191,7 +190,7 @@ const handleEdit = (row: ProblemVo) => {
       problemForm.categoryIds = d.categories?.map((c) => c.id).filter((c): c is number => c !== undefined) || []
       dialogVisible.value = true
     })
-    .catch(() => ElMessage.error('获取题目信息失败'))
+    .catch(() => {})
 }
 
 const handleSave = async () => {
@@ -213,11 +212,8 @@ const handleSave = async () => {
       ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
       dialogVisible.value = false
       fetchData()
-    } else {
-      ElMessage.error(res?.message || '保存失败')
     }
   } catch {
-    ElMessage.error('保存失败')
   }
 }
 
@@ -230,11 +226,8 @@ const handleDelete = (row: ProblemVo) => {
         if (res?.code === 200) {
           ElMessage.success('删除成功')
           fetchData()
-        } else {
-          ElMessage.error(res?.message || '删除失败')
         }
       } catch {
-        ElMessage.error('删除失败')
       }
     })
     .catch(() => {})
@@ -254,11 +247,8 @@ const handleBatchDelete = () => {
           ElMessage.success('删除成功')
           selectedRows.value = []
           fetchData()
-        } else {
-          ElMessage.error(res?.message || '删除失败')
         }
       } catch {
-        ElMessage.error('删除失败')
       }
     })
     .catch(() => {})
@@ -318,11 +308,8 @@ const saveCase = async () => {
       ElMessage.success('保存成功')
       caseDialogVisible.value = false
       loadDetail(currentProblemId.value!)
-    } else {
-      ElMessage.error(res?.message || '保存失败')
     }
   } catch {
-    ElMessage.error('保存失败')
   }
 }
 
@@ -335,11 +322,8 @@ const deleteCase = (row: ProblemCaseVo) => {
         if (res?.code === 200) {
           ElMessage.success('删除成功')
           loadDetail(currentProblemId.value!)
-        } else {
-          ElMessage.error(res?.message || '删除失败')
         }
       } catch {
-        ElMessage.error('删除失败')
       }
     })
     .catch(() => {})
@@ -383,11 +367,8 @@ const saveTest = async () => {
       ElMessage.success('保存成功')
       testDialogVisible.value = false
       loadDetail(currentProblemId.value!)
-    } else {
-      ElMessage.error(res?.message || '保存失败')
     }
   } catch {
-    ElMessage.error('保存失败')
   }
 }
 
@@ -400,11 +381,8 @@ const deleteTest = (row: ProblemTestVo) => {
         if (res?.code === 200) {
           ElMessage.success('删除成功')
           loadDetail(currentProblemId.value!)
-        } else {
-          ElMessage.error(res?.message || '删除失败')
         }
       } catch {
-        ElMessage.error('删除失败')
       }
     })
     .catch(() => {})
@@ -425,13 +403,9 @@ const toggleTestHidden = async (row: ProblemTestVo, val: number) => {
     const res = await api.updateTest(data)
     if (res?.code === 200) {
       ElMessage.success('更新成功')
-    } else {
-      row.isHidden = old
-      ElMessage.error(res?.message || '更新失败')
     }
   } catch {
     row.isHidden = old
-    ElMessage.error('更新失败')
   }
 }
 

+ 1 - 18
src/view/admin/UserManageView.vue

@@ -96,8 +96,7 @@ const fetchData = async () => {
     const pageData = res?.data
     tableData.value = pageData?.data || []
     pagination.total = pageData?.total || 0
-  } catch (err) {
-    ElMessage.error('获取数据失败')
+  } catch {
     tableData.value = []
   } finally {
     loading.value = false
@@ -141,7 +140,6 @@ const handleEdit = async (row: UserVo) => {
       dialogVisible.value = true
     }
   } catch {
-    ElMessage.error('获取用户信息失败')
   }
 }
 
@@ -167,8 +165,6 @@ const handleSave = async () => {
           ElMessage.success('修改成功')
           dialogVisible.value = false
           fetchData()
-        } else {
-          ElMessage.error(res?.message || '修改失败')
         }
       } else {
         if (!userForm.password) {
@@ -184,12 +180,9 @@ const handleSave = async () => {
           ElMessage.success('新增成功')
           dialogVisible.value = false
           fetchData()
-        } else {
-          ElMessage.error(res?.message || '新增失败')
         }
       }
     } catch {
-      ElMessage.error(isEdit.value ? '修改失败' : '新增失败')
     }
   })
 }
@@ -210,11 +203,8 @@ const handleDelete = (row: UserVo) => {
       if (res?.code === 0 || res?.code === 200) {
         ElMessage.success('删除成功')
         fetchData()
-      } else {
-        ElMessage.error(res?.message || '删除失败')
       }
     } catch {
-      ElMessage.error('删除失败')
     }
   }).catch(() => {})
 }
@@ -235,11 +225,8 @@ const handleBatchDelete = () => {
         ElMessage.success('删除成功')
         selectedRows.value = []
         fetchData()
-      } else {
-        ElMessage.error(res?.message || '删除失败')
       }
     } catch {
-      ElMessage.error('删除失败')
     }
   }).catch(() => {})
 }
@@ -253,13 +240,9 @@ const handleToggleEnable = async (row: UserVo, newEnable: number) => {
     const res = await api.updateUserStatus({ id: row.id!, enable: newEnable as UpdateUserStatusDto['enable'] })
     if (res?.code === 0 || res?.code === 200) {
       ElMessage.success('状态更新成功')
-    } else {
-      row.enable = oldEnable
-      ElMessage.error(res?.message || '状态更新失败')
     }
   } catch {
     row.enable = oldEnable
-    ElMessage.error('状态更新失败')
   }
 }
 

+ 1 - 5
src/view/contest/ContestDetailView.vue

@@ -97,11 +97,10 @@ const fetchDetail = async (password?: string) => {
         unlocked.value = true
       }
     } else {
-      ElMessage.error(res?.message || '获取比赛失败')
       router.push('/contests')
     }
   } catch {
-    ElMessage.error('获取比赛失败')
+    router.push('/contests')
   } finally {
     loading.value = false
   }
@@ -135,11 +134,8 @@ const doRegister = async (password?: string) => {
       ElMessage.success('报名成功')
       if (isPasswordType.value && password) validPassword.value = password
       await fetchDetail(validPassword.value)
-    } else {
-      ElMessage.error(res?.message || '报名失败')
     }
   } catch {
-    ElMessage.error('报名失败')
   }
 }
 

+ 0 - 2
src/view/contest/ContestListView.vue

@@ -1,7 +1,6 @@
 <script setup lang="ts">
 import { getContestController } from '@/api/contest-controller'
 import type { ContestVo, QueryByPage2Params } from '@/api/models'
-import { ElMessage } from 'element-plus'
 import { RefreshRight, Search } from '@element-plus/icons-vue'
 import { onMounted, reactive, ref } from 'vue'
 import { useRouter } from 'vue-router'
@@ -67,7 +66,6 @@ const fetchData = async () => {
     tableData.value = pageData?.data || []
     pagination.total = pageData?.total || 0
   } catch {
-    ElMessage.error('获取比赛列表失败')
     tableData.value = []
   } finally {
     loading.value = false

+ 0 - 3
src/view/contest/ContestRankingView.vue

@@ -24,11 +24,8 @@ const fetchRanking = async () => {
     const res = await api.getRanking(contestId)
     if (res?.code === 200) {
       tableData.value = res.data || []
-    } else {
-      ElMessage.error(res?.message || '获取排行榜失败')
     }
   } catch {
-    ElMessage.error('获取排行榜失败')
   } finally {
     loading.value = false
   }

+ 0 - 6
src/view/problem/ProblemDetailView.vue

@@ -45,11 +45,8 @@ const fetchDetail = async () => {
     const res = await api.getDetail1(problemId.value)
     if (res?.code === 200 && res.data) {
       detail.value = res.data
-    } else {
-      ElMessage.error(res?.message || '获取题目失败')
     }
   } catch {
-    ElMessage.error('获取题目失败')
   } finally {
     loading.value = false
   }
@@ -109,11 +106,8 @@ const handleSubmit = async () => {
     if (res?.code === 200 && res.data) {
       result.value = res.data
       ElMessage.success('提交成功')
-    } else {
-      ElMessage.error(res?.message || '提交失败')
     }
   } catch {
-    ElMessage.error('提交失败')
   } finally {
     submitting.value = false
   }

+ 0 - 2
src/view/problem/ProblemListView.vue

@@ -2,7 +2,6 @@
 import { getProblemController } from '@/api/problem-controller'
 import { getCategoryController } from '@/api/category-controller'
 import type { CategoryVo, ProblemVo, QueryByPage1Params } from '@/api/models'
-import { ElMessage } from 'element-plus'
 import { RefreshRight, Search } from '@element-plus/icons-vue'
 import { onMounted, reactive, ref } from 'vue'
 import { useRouter } from 'vue-router'
@@ -66,7 +65,6 @@ const fetchData = async () => {
     tableData.value = pageData?.data || []
     pagination.total = pageData?.total || 0
   } catch {
-    ElMessage.error('获取题目列表失败')
     tableData.value = []
   } finally {
     loading.value = false

+ 0 - 1
src/view/submission/SubmissionListView.vue

@@ -25,7 +25,6 @@ const fetchData = async () => {
     tableData.value = pageData?.data || []
     pagination.total = pageData?.total || 0
   } catch {
-    ElMessage.error('获取提交记录失败')
     tableData.value = []
   } finally {
     loading.value = false