From d628dfdd728d4e51ffd5a99cc7f4c5dbb80f4f6d Mon Sep 17 00:00:00 2001 From: 2910410219 <2910410219@qq.com> Date: Thu, 23 Apr 2026 18:41:45 +0800 Subject: [PATCH] feat: sync admin UI updates --- src/api/digitalHuman/creation/index.ts | 12 +- .../operation/setting/live-account/index.ts | 17 +- .../operation/setting/scheduling/index.ts | 17 +- src/layout/navBars/breadcrumb/user.vue | 4 +- src/router/index.ts | 3 +- src/utils/request.ts | 211 ++++++++++-------- src/utils/storage.ts | 13 ++ src/views/error/401.vue | 4 +- .../component/editLiveAccount.vue | 8 +- .../operation/setting/live-account/index.vue | 20 +- .../scheduling/component/editSchedule.vue | 8 +- .../operation/setting/scheduling/index.vue | 18 +- 12 files changed, 199 insertions(+), 136 deletions(-) diff --git a/src/api/digitalHuman/creation/index.ts b/src/api/digitalHuman/creation/index.ts index 610e6fd..6602e8c 100644 --- a/src/api/digitalHuman/creation/index.ts +++ b/src/api/digitalHuman/creation/index.ts @@ -1,4 +1,4 @@ -import request from '/@/utils/request'; +import request, { type RequestOptions } from '/@/utils/request'; export interface CreationListParams { pageNum: number; @@ -61,28 +61,32 @@ export interface DownloadToFileParams { fileURL: string; } -export function getCreationList(params: CreationListParams) { +// requestOptions 用来声明“这个接口的错误提示由谁负责”。 +export function getCreationList(params: CreationListParams, requestOptions?: RequestOptions) { return request({ url: '/black-deacon/creation/info/list', method: 'get', params, + requestOptions, }) as Promise; } -export function createCreation(data: CreationSubmitParams) { +export function createCreation(data: CreationSubmitParams, requestOptions?: RequestOptions) { return request({ url: '/black-deacon/creation/info/creation', method: 'post', data, timeout: 0, + requestOptions, }); } -export function downloadToFile(data: DownloadToFileParams) { +export function downloadToFile(data: DownloadToFileParams, requestOptions?: RequestOptions) { return request({ url: '/oss/file/downloadToBrowser', method: 'post', data, responseType: 'blob', + requestOptions, }); } diff --git a/src/api/trade/operation/setting/live-account/index.ts b/src/api/trade/operation/setting/live-account/index.ts index 467c3cf..e471e79 100644 --- a/src/api/trade/operation/setting/live-account/index.ts +++ b/src/api/trade/operation/setting/live-account/index.ts @@ -1,4 +1,4 @@ -import request from '/@/utils/request'; +import request, { type RequestOptions } from '/@/utils/request'; export interface LiveAccountParams { pageNum: number; @@ -47,42 +47,47 @@ export interface LiveAccountDetailResponse { data: LiveAccount; } -export function getLiveAccountList(params: LiveAccountParams): Promise { +export function getLiveAccountList(params: LiveAccountParams, requestOptions?: RequestOptions): Promise { return request({ url: '/erp/live/account/controller/listLiveAccounts', method: 'get', params, + requestOptions, }) as Promise; } -export function getLiveAccountDetail(params: { id: string }): Promise { +export function getLiveAccountDetail(params: { id: string }, requestOptions?: RequestOptions): Promise { return request({ url: '/erp/live/account/controller/getLiveAccount', method: 'get', params, + requestOptions, }) as Promise; } -export function createLiveAccount(data: LiveAccountSaveParams) { +export function createLiveAccount(data: LiveAccountSaveParams, requestOptions?: RequestOptions) { return request({ url: '/erp/live/account/controller/createLiveAccount', method: 'post', data, + requestOptions, }); } -export function updateLiveAccount(data: LiveAccountSaveParams) { +export function updateLiveAccount(data: LiveAccountSaveParams, requestOptions?: RequestOptions) { return request({ url: '/erp/live/account/controller/updateLiveAccount', method: 'put', data, + requestOptions, }); } -export function deleteLiveAccount(params: { id: string }) { +export function deleteLiveAccount(params: { id: string }, requestOptions?: RequestOptions) { return request({ url: '/erp/live/account/controller/deleteLiveAccount', method: 'delete', params, + requestOptions, }); } diff --git a/src/api/trade/operation/setting/scheduling/index.ts b/src/api/trade/operation/setting/scheduling/index.ts index 130fa1b..64344ce 100644 --- a/src/api/trade/operation/setting/scheduling/index.ts +++ b/src/api/trade/operation/setting/scheduling/index.ts @@ -1,4 +1,4 @@ -import request from '/@/utils/request'; +import request, { type RequestOptions } from '/@/utils/request'; export interface ScheduleListParams { pageNum: number; @@ -73,42 +73,47 @@ export interface ScheduleDetailResponse { data: ScheduleDetail; } -export function getScheduleList(params: ScheduleListParams): Promise { +export function getScheduleList(params: ScheduleListParams, requestOptions?: RequestOptions): Promise { return request({ url: '/erp/schedule/controller/listSchedules', method: 'get', params, + requestOptions, }) as Promise; } -export function getScheduleDetail(params: { id: string }): Promise { +export function getScheduleDetail(params: { id: string }, requestOptions?: RequestOptions): Promise { return request({ url: '/erp/schedule/controller/getSchedule', method: 'get', params, + requestOptions, }) as Promise; } -export function createSchedule(data: ScheduleSaveParams) { +export function createSchedule(data: ScheduleSaveParams, requestOptions?: RequestOptions) { return request({ url: '/erp/schedule/controller/createSchedule', method: 'post', data, + requestOptions, }); } -export function updateSchedule(data: ScheduleSaveParams) { +export function updateSchedule(data: ScheduleSaveParams, requestOptions?: RequestOptions) { return request({ url: '/erp/schedule/controller/updateSchedule', method: 'put', data, + requestOptions, }); } -export function deleteSchedule(params: { id: string }) { +export function deleteSchedule(params: { id: string }, requestOptions?: RequestOptions) { return request({ url: '/erp/schedule/controller/deleteSchedule', method: 'delete', params, + requestOptions, }); } diff --git a/src/layout/navBars/breadcrumb/user.vue b/src/layout/navBars/breadcrumb/user.vue index ff02220..9f2f4e8 100644 --- a/src/layout/navBars/breadcrumb/user.vue +++ b/src/layout/navBars/breadcrumb/user.vue @@ -178,8 +178,8 @@ export default defineComponent({ }, }) .then(async () => { - // 清除缓存/token等 - Session.clear(); + // 手动退出登录也只清理登录态缓存,保留主题、语言等本地配置。 + Session.clearAuth(); // 显式回到登录页,避免保留之前受保护页面的重定向参数 await router.replace('/login'); }) diff --git a/src/router/index.ts b/src/router/index.ts index da607b1..b03fade 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -95,7 +95,8 @@ router.beforeEach(async (to, from, next) => { } else { if (!token) { next(`/login?redirect=${to.path}¶ms=${JSON.stringify(to.query ? to.query : to.params)}`); - Session.clear(); + // 进入受保护页面但本地已没有 token 时,只清理登录态缓存即可。 + Session.clearAuth(); NProgress.done(); } else if (token && to.path === '/login') { next('/home'); diff --git a/src/utils/request.ts b/src/utils/request.ts index c163cb3..68c72ca 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -1,35 +1,92 @@ import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import { ElMessage, ElMessageBox } from 'element-plus'; +import type { MessageHandler } from 'element-plus'; import { Session } from '/@/utils/storage'; import qs from 'qs'; import { getChangedFields } from '/@/utils/diffUtils'; import { handleModuleNotEnabled } from '/@/utils/assetSubscribe'; -// 标记是否正在处理 token 过期,避免重复弹窗 -let isHandlingTokenExpired = false; +/** + * 控制一次请求的错误提示归属: + * - global: 交给 request.ts 统一弹错,适合绝大多数接口 + * - page: 页面自己在 catch 中决定提示文案,避免与全局重复 + * - silent: 完全静默,适合轮询、后台刷新等不希望打扰用户的请求 + */ +export interface RequestOptions { + errorMode?: 'global' | 'page' | 'silent'; +} -// 错误消息防抖:防止短时间内显示多个错误消息 -let lastErrorTime = 0; -const ERROR_MESSAGE_INTERVAL = 2000; // 2秒内只显示一个错误 - -const showErrorMessage = (message: string) => { - const now = Date.now(); - - // 2秒内只显示一个错误消息(不管内容是否相同) - if (now - lastErrorTime < ERROR_MESSAGE_INTERVAL) { - return; // 跳过 +declare module 'axios' { + interface AxiosRequestConfig { + requestOptions?: RequestOptions; } - lastErrorTime = now; - ElMessage.error(message); + interface InternalAxiosRequestConfig { + requestOptions?: RequestOptions; + } +} + +// 标记是否正在处理 token 过期,避免出现多个登录过期弹窗。 +let isHandlingTokenExpired = false; + +// 始终只保留一个错误消息实例,新的错误会先关闭旧的提示。 +let activeErrorMessage: MessageHandler | null = null; + +// 同类错误提示做一个短时间节流,避免接口连发时刷屏。 +let lastErrorTime = 0; +const ERROR_MESSAGE_INTERVAL = 2000; + +const getErrorMode = (config?: InternalAxiosRequestConfig) => config?.requestOptions?.errorMode ?? 'global'; +const shouldShowGlobalError = (config?: InternalAxiosRequestConfig) => getErrorMode(config) === 'global'; + +const closeActiveErrorMessage = () => { + activeErrorMessage?.close(); + activeErrorMessage = null; }; -// ============================================================ -// Axios 实例配置 -// 地址配置见 .env.development 文件 -// ============================================================ +/** + * token 过期只接受明确的后端信号: + * - HTTP 401 + * - 业务 code = 401 + * - 已知的固定错误文案 + * 不再使用模糊的 includes('token'),避免把普通业务错误误判成登录过期。 + */ +const isTokenExpiredError = (httpStatus?: number, code?: number, message?: string) => { + const normalizedMessage = String(message || '') + .trim() + .toLowerCase(); + const tokenExpiredMessages = ['token is invalid', 'token 解析失败', 'decrypt error', 'jwt expired', 'invalid token']; + + return httpStatus === 401 || code === 401 || tokenExpiredMessages.includes(normalizedMessage); +}; + +/** + * 全局错误提示统一从这里走: + * 1. 先判断当前请求是否允许全局弹错 + * 2. 再做节流,防止短时间重复提示 + * 3. 最后保证页面上同一时刻只有一个错误弹窗 + */ +const showErrorMessage = (message: string, config?: InternalAxiosRequestConfig) => { + if (!shouldShowGlobalError(config)) return; + + const now = Date.now(); + if (now - lastErrorTime < ERROR_MESSAGE_INTERVAL) return; + + lastErrorTime = now; + closeActiveErrorMessage(); + + let currentMessage: MessageHandler | null = null; + currentMessage = ElMessage.error({ + message, + onClose: () => { + if (activeErrorMessage === currentMessage) { + activeErrorMessage = null; + } + }, + }); + activeErrorMessage = currentMessage; +}; -// 统一服务实例(端口8000)- 全部模块共用 const service: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_URL, timeout: 50000, @@ -41,23 +98,21 @@ const service: AxiosInstance = axios.create({ }, }); -// token 过期处理函数 +/** + * 登录过期时优先关闭普通错误消息,再弹出唯一的登录过期确认框。 + * 这样用户不会先看到一个普通报错,再叠一个登录过期弹窗。 + */ const handleTokenExpired = () => { if (isHandlingTokenExpired) return; isHandlingTokenExpired = true; + closeActiveErrorMessage(); ElMessageBox.alert('登录状态已过期,请重新登录', '提示', { confirmButtonText: '确定', showClose: false, closeOnClickModal: false, closeOnPressEscape: false, - beforeClose: (action, _instance, done) => { - if (action === 'confirm') { - done(); - performLogout(); - } - }, }) .then(() => { performLogout(); @@ -67,60 +122,47 @@ const handleTokenExpired = () => { }); }; -// 执行退出登录操作 +/** + * 统一退出动作: + * - 只清理登录态相关缓存 + * - 重置 token 过期处理标记 + * - 最后回到登录页 + */ const performLogout = () => { - Session.clear(); - localStorage.clear(); + Session.clearAuth(); isHandlingTokenExpired = false; - // Hash 路由统一回登录页,避免跳到错误地址 setTimeout(() => { window.location.href = '/#/login'; }, 500); }; -// 请求拦截器 const requestInterceptor = (config: InternalAxiosRequestConfig) => { - // 检查 token 是否有效 const token = Session.get('token'); if (token) { - // 可以在这里添加 token 有效性检查(如果需要) config.headers!['Authorization'] = `Bearer ${token}`; } - // PUT 请求最小化传参处理 - // 如果请求数据中包含 _originalData,则自动计算差异,只传递修改过的字段 + // PUT 请求只传变更字段,避免把未修改的数据整包提交给后端。 if (config.method?.toLowerCase() === 'put' && config.data && typeof config.data === 'object') { const { _originalData, ...currentData } = config.data; if (_originalData && typeof _originalData === 'object') { - // 获取 id 字段(必须保留) const idField = currentData.id || currentData.Id || currentData.ID; - - // 计算差异 const changedFields = getChangedFields(_originalData, currentData, { exclude: ['_originalData', 'id', 'Id', 'ID'], }); - // 如果有变化,只传递 id + 变化的字段 - if (Object.keys(changedFields).length > 0) { - config.data = { id: idField, ...changedFields }; - } else { - // 没有变化,只传递 id - config.data = { id: idField }; - } + config.data = Object.keys(changedFields).length > 0 ? { id: idField, ...changedFields } : { id: idField }; } } return config; }; -const requestErrorHandler = (error: any) => { - return Promise.reject(error); -}; +const requestErrorHandler = (error: any) => Promise.reject(error); -// 响应拦截器 const responseInterceptor = (response: AxiosResponse) => { - // 文件流响应直接返回 + // 文件流直接返回原始响应,调用方需要自行处理 Blob。 if ( response.config.responseType === 'blob' || response.headers['content-type']?.includes('application/zip') || @@ -133,75 +175,58 @@ const responseInterceptor = (response: AxiosResponse) => { const httpStatus = response.status; const code = res?.code; const message = res?.message; + const config = response.config; - // 检查 token 相关错误 - if ( - httpStatus === 401 || - code === 401 || - message?.includes('token') || - message === 'token is invalid' || - message === 'token 解析失败' || - message?.includes('decrypt error') - ) { + if (isTokenExpiredError(httpStatus, code, message)) { handleTokenExpired(); return Promise.reject(new Error('登录状态已过期')); } - // 处理模块未开通错误 (403) - // 跳过资产SKU查询接口,避免弹窗内部请求触发循环 const requestUrl = response.config.url || ''; if (code === 402 && !requestUrl.includes('/assets/asset/sku/')) { - // 获取当前路由路径 const currentPath = window.location.hash.replace('#', '') || window.location.pathname; handleModuleNotEnabled(currentPath); - // 直接返回,不再显示错误消息 return Promise.reject(new Error('模块未开通')); } - // 业务逻辑错误处理(排除403,因为上面已处理) + // 业务失败默认走全局提示;如果页面声明自己处理,这里只抛错不弹窗。 if (code !== undefined && code !== 0 && code !== 200 && code !== 403) { const errorMsg = message || `请求失败(${code})`; - showErrorMessage(errorMsg); + showErrorMessage(errorMsg, config); return Promise.reject(new Error(errorMsg)); } return res; }; -// 响应错误拦截器 const responseErrorHandler = (error: any) => { + const config = error.config as InternalAxiosRequestConfig | undefined; + const httpStatus = error.response?.status; + const responseMessage = error.response?.data?.message; + if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) { - showErrorMessage('请求超时,请检查网络连接'); + showErrorMessage('请求超时,请检查网络连接', config); return Promise.reject(new Error('请求超时')); } if (!error.response) { - if (error.message === 'Network Error') { - // ElMessage.error('网络连接错误,请检查网络设置'); - } else { - // ElMessage.error('网络异常,请检查连接'); - } return Promise.reject(error); } - const httpStatus = error.response.status; - // 优先使用返回数据中的 message 字段 - const responseMessage = error.response.data?.message; + if (isTokenExpiredError(httpStatus, error.response?.data?.code, responseMessage)) { + handleTokenExpired(); + return Promise.reject(new Error('登录状态已过期')); + } - // 处理 HTTP 错误状态 const requestUrl = error.response.config?.url || ''; + switch (httpStatus) { - case 401: - handleTokenExpired(); - break; case 402: - // 模块未开通处理,跳过SKU相关接口避免循环 if (!requestUrl.includes('/assets/asset/sku/') && !requestUrl.includes('getAssetAndSku')) { - // 检查是否刚从开通页面返回(5秒内不再跳转) const lastSubscribeTime = sessionStorage.getItem('lastSubscribeTime'); const now = Date.now(); if (lastSubscribeTime && now - parseInt(lastSubscribeTime) < 5000) { - showErrorMessage(responseMessage || '服务开通中,请稍后刷新页面'); + showErrorMessage(responseMessage || '服务开通中,请稍后刷新页面', config); return Promise.reject(new Error('模块开通中')); } @@ -209,40 +234,38 @@ const responseErrorHandler = (error: any) => { handleModuleNotEnabled(currentPath); return Promise.reject(new Error('模块未开通')); } - showErrorMessage(responseMessage || '服务未开通'); + showErrorMessage(responseMessage || '服务未开通', config); break; case 403: - showErrorMessage(responseMessage || '没有权限访问该资源'); + showErrorMessage(responseMessage || '没有权限访问该资源', config); break; case 404: - showErrorMessage(responseMessage || '请求的资源不存在'); + showErrorMessage(responseMessage || '请求的资源不存在', config); break; case 429: - showErrorMessage(responseMessage || '请求过于频繁,请稍后再试'); - handleTokenExpired(); + // 429 是限流,不等于登录过期,这里只保留频率提示。 + showErrorMessage(responseMessage || '请求过于频繁,请稍后再试', config); break; case 500: - showErrorMessage(responseMessage || '服务器内部错误'); + showErrorMessage(responseMessage || '服务器内部错误', config); break; case 502: - showErrorMessage(responseMessage || '网关错误'); + showErrorMessage(responseMessage || '网关错误', config); break; case 503: - showErrorMessage(responseMessage || '服务不可用'); + showErrorMessage(responseMessage || '服务不可用', config); break; default: if (httpStatus >= 400) { - showErrorMessage(responseMessage || `请求失败(${httpStatus})`); + showErrorMessage(responseMessage || `请求失败(${httpStatus})`, config); } } return Promise.reject(error); }; -// 为实例添加拦截器 service.interceptors.request.use(requestInterceptor, requestErrorHandler); service.interceptors.response.use(responseInterceptor, responseErrorHandler); -// 导出 export default service; -export { showErrorMessage }; +export { closeActiveErrorMessage, showErrorMessage }; diff --git a/src/utils/storage.ts b/src/utils/storage.ts index a983f80..1859cd9 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,5 +1,11 @@ import Cookies from 'js-cookie'; +/** + * 这些 key 属于登录态或用户会话上下文。 + * 退出登录时只清理这部分数据,避免误删主题、语言、布局等本地个性化配置。 + */ +const SESSION_AUTH_KEYS = ['token', 'userInfo', 'userMenu', 'permissions']; + /** * window.localStorage 浏览器永久缓存 * @method set 设置永久缓存 @@ -33,6 +39,7 @@ export const Local = { * @method get 获取临时缓存 * @method remove 移除临时缓存 * @method clear 移除全部临时缓存 + * @method clearAuth 移除登录态相关缓存 */ export const Session = { // 设置临时缓存 @@ -56,4 +63,10 @@ export const Session = { Cookies.remove('token'); window.sessionStorage.clear(); }, + // 只清理登录态相关缓存,保留非登录相关的页面状态与本地配置 + clearAuth() { + SESSION_AUTH_KEYS.forEach((key) => this.remove(key)); + }, }; + +export { SESSION_AUTH_KEYS }; diff --git a/src/views/error/401.vue b/src/views/error/401.vue index 711fa25..f9d0379 100644 --- a/src/views/error/401.vue +++ b/src/views/error/401.vue @@ -36,8 +36,8 @@ export default defineComponent({ const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes); const onSetAuth = () => { // https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS - // 清除缓存/token等 - Session.clear(); + // 401 页面回登录时只清理登录态相关缓存,保留本地个性化配置。 + Session.clearAuth(); // 使用 reload 时,不需要调用 resetRoute() 重置路由 window.location.reload(); }; diff --git a/src/views/trade/operation/setting/live-account/component/editLiveAccount.vue b/src/views/trade/operation/setting/live-account/component/editLiveAccount.vue index 9702ddc..a530e57 100644 --- a/src/views/trade/operation/setting/live-account/component/editLiveAccount.vue +++ b/src/views/trade/operation/setting/live-account/component/editLiveAccount.vue @@ -101,7 +101,8 @@ const openDialog = async (row?: { id?: string }) => { try { loading.value = true; - const res = await getLiveAccountDetail({ id: String(row.id) }); + // 详情加载失败时由当前弹窗给出更易懂的业务提示。 + const res = await getLiveAccountDetail({ id: String(row.id) }, { errorMode: 'page' }); if (res?.data) { fillForm(res.data); } @@ -129,11 +130,12 @@ const handleSubmit = async () => { remark: formData.remark, }; + // 提交失败提示交给当前弹窗自己处理,避免和 request.ts 的统一报错重复。 if (isEdit.value) { - await updateLiveAccount(payload); + await updateLiveAccount(payload, { errorMode: 'page' }); ElMessage.success('修改成功'); } else { - await createLiveAccount(payload); + await createLiveAccount(payload, { errorMode: 'page' }); ElMessage.success('新增成功'); } diff --git a/src/views/trade/operation/setting/live-account/index.vue b/src/views/trade/operation/setting/live-account/index.vue index d99f1d6..196416f 100644 --- a/src/views/trade/operation/setting/live-account/index.vue +++ b/src/views/trade/operation/setting/live-account/index.vue @@ -131,13 +131,17 @@ const tableData = reactive({ const getList = async () => { try { tableData.loading = true; - const res = await getLiveAccountList({ - ...tableData.param, - platform: searchForm.platform || undefined, - accountName: searchForm.accountName || undefined, - accountId: searchForm.accountId || undefined, - status: searchForm.status, - }); + // 列表失败文案由当前页面决定,避免和全局请求报错同时出现。 + const res = await getLiveAccountList( + { + ...tableData.param, + platform: searchForm.platform || undefined, + accountName: searchForm.accountName || undefined, + accountId: searchForm.accountId || undefined, + status: searchForm.status, + }, + { errorMode: 'page' } + ); if (res && res.data) { tableData.data = (res.data.list || []).map((item: any) => ({ ...item, @@ -191,7 +195,7 @@ const handleDelete = async (row: LiveAccountItem) => { cancelButtonText: '取消', type: 'warning', }); - await deleteLiveAccount({ id: row.id }); + await deleteLiveAccount({ id: row.id }, { errorMode: 'page' }); ElMessage.success('删除成功'); getList(); } catch (error) { diff --git a/src/views/trade/operation/setting/scheduling/component/editSchedule.vue b/src/views/trade/operation/setting/scheduling/component/editSchedule.vue index c512b5a..e47316d 100644 --- a/src/views/trade/operation/setting/scheduling/component/editSchedule.vue +++ b/src/views/trade/operation/setting/scheduling/component/editSchedule.vue @@ -153,7 +153,8 @@ const openDialog = async (row?: { id?: string }) => { await loadOptions(); if (row?.id) { - const res = await getScheduleDetail({ id: String(row.id) }); + // 详情请求失败时,这个弹窗希望给出更明确的页面语义提示。 + const res = await getScheduleDetail({ id: String(row.id) }, { errorMode: 'page' }); const detail = res?.data; if (detail) { formData.id = String(detail.id); @@ -195,11 +196,12 @@ const handleSubmit = async () => { remark: formData.remark, }; + // 提交失败文案由弹窗自己控制,避免接口层和弹窗层重复报错。 if (isEdit.value) { - await updateSchedule(payload); + await updateSchedule(payload, { errorMode: 'page' }); ElMessage.success('修改排班成功'); } else { - await createSchedule(payload); + await createSchedule(payload, { errorMode: 'page' }); ElMessage.success('新增排班成功'); } diff --git a/src/views/trade/operation/setting/scheduling/index.vue b/src/views/trade/operation/setting/scheduling/index.vue index 7b7d04e..b3c2b21 100644 --- a/src/views/trade/operation/setting/scheduling/index.vue +++ b/src/views/trade/operation/setting/scheduling/index.vue @@ -144,12 +144,16 @@ const getStatusTagType = (status: number): 'success' | 'info' | 'warning' => { const getList = async () => { try { tableData.loading = true; - const res = await getScheduleList({ - ...tableData.param, - anchorName: searchForm.anchorName || undefined, - accountName: searchForm.accountName || undefined, - status: searchForm.status, - } as any); + // 列表失败文案由当前页面决定,避免和 request.ts 的全局错误提示重复。 + const res = await getScheduleList( + { + ...tableData.param, + anchorName: searchForm.anchorName || undefined, + accountName: searchForm.accountName || undefined, + status: searchForm.status, + } as any, + { errorMode: 'page' } + ); const scheduleData = res?.data; if (scheduleData) { tableData.data = (scheduleData.list || []).map((item: any) => ({ @@ -207,7 +211,7 @@ const handleDelete = async (row: ScheduleItem) => { cancelButtonText: '取消', type: 'warning', }); - await deleteSchedule({ id: row.id }); + await deleteSchedule({ id: row.id }, { errorMode: 'page' }); ElMessage.success('删除成功'); getList(); } catch (error) {