feat: sync admin UI updates

This commit is contained in:
2026-04-23 18:41:45 +08:00
parent 24e517dfec
commit d628dfdd72
12 changed files with 199 additions and 136 deletions

View File

@@ -1,4 +1,4 @@
import request from '/@/utils/request'; import request, { type RequestOptions } from '/@/utils/request';
export interface CreationListParams { export interface CreationListParams {
pageNum: number; pageNum: number;
@@ -61,28 +61,32 @@ export interface DownloadToFileParams {
fileURL: string; fileURL: string;
} }
export function getCreationList(params: CreationListParams) { // requestOptions 用来声明“这个接口的错误提示由谁负责”。
export function getCreationList(params: CreationListParams, requestOptions?: RequestOptions) {
return request({ return request({
url: '/black-deacon/creation/info/list', url: '/black-deacon/creation/info/list',
method: 'get', method: 'get',
params, params,
requestOptions,
}) as Promise<CreationListResponse>; }) as Promise<CreationListResponse>;
} }
export function createCreation(data: CreationSubmitParams) { export function createCreation(data: CreationSubmitParams, requestOptions?: RequestOptions) {
return request({ return request({
url: '/black-deacon/creation/info/creation', url: '/black-deacon/creation/info/creation',
method: 'post', method: 'post',
data, data,
timeout: 0, timeout: 0,
requestOptions,
}); });
} }
export function downloadToFile(data: DownloadToFileParams) { export function downloadToFile(data: DownloadToFileParams, requestOptions?: RequestOptions) {
return request({ return request({
url: '/oss/file/downloadToBrowser', url: '/oss/file/downloadToBrowser',
method: 'post', method: 'post',
data, data,
responseType: 'blob', responseType: 'blob',
requestOptions,
}); });
} }

View File

@@ -1,4 +1,4 @@
import request from '/@/utils/request'; import request, { type RequestOptions } from '/@/utils/request';
export interface LiveAccountParams { export interface LiveAccountParams {
pageNum: number; pageNum: number;
@@ -47,42 +47,47 @@ export interface LiveAccountDetailResponse {
data: LiveAccount; data: LiveAccount;
} }
export function getLiveAccountList(params: LiveAccountParams): Promise<LiveAccountListResponse> { export function getLiveAccountList(params: LiveAccountParams, requestOptions?: RequestOptions): Promise<LiveAccountListResponse> {
return request({ return request({
url: '/erp/live/account/controller/listLiveAccounts', url: '/erp/live/account/controller/listLiveAccounts',
method: 'get', method: 'get',
params, params,
requestOptions,
}) as Promise<LiveAccountListResponse>; }) as Promise<LiveAccountListResponse>;
} }
export function getLiveAccountDetail(params: { id: string }): Promise<LiveAccountDetailResponse> { export function getLiveAccountDetail(params: { id: string }, requestOptions?: RequestOptions): Promise<LiveAccountDetailResponse> {
return request({ return request({
url: '/erp/live/account/controller/getLiveAccount', url: '/erp/live/account/controller/getLiveAccount',
method: 'get', method: 'get',
params, params,
requestOptions,
}) as Promise<LiveAccountDetailResponse>; }) as Promise<LiveAccountDetailResponse>;
} }
export function createLiveAccount(data: LiveAccountSaveParams) { export function createLiveAccount(data: LiveAccountSaveParams, requestOptions?: RequestOptions) {
return request({ return request({
url: '/erp/live/account/controller/createLiveAccount', url: '/erp/live/account/controller/createLiveAccount',
method: 'post', method: 'post',
data, data,
requestOptions,
}); });
} }
export function updateLiveAccount(data: LiveAccountSaveParams) { export function updateLiveAccount(data: LiveAccountSaveParams, requestOptions?: RequestOptions) {
return request({ return request({
url: '/erp/live/account/controller/updateLiveAccount', url: '/erp/live/account/controller/updateLiveAccount',
method: 'put', method: 'put',
data, data,
requestOptions,
}); });
} }
export function deleteLiveAccount(params: { id: string }) { export function deleteLiveAccount(params: { id: string }, requestOptions?: RequestOptions) {
return request({ return request({
url: '/erp/live/account/controller/deleteLiveAccount', url: '/erp/live/account/controller/deleteLiveAccount',
method: 'delete', method: 'delete',
params, params,
requestOptions,
}); });
} }

View File

@@ -1,4 +1,4 @@
import request from '/@/utils/request'; import request, { type RequestOptions } from '/@/utils/request';
export interface ScheduleListParams { export interface ScheduleListParams {
pageNum: number; pageNum: number;
@@ -73,42 +73,47 @@ export interface ScheduleDetailResponse {
data: ScheduleDetail; data: ScheduleDetail;
} }
export function getScheduleList(params: ScheduleListParams): Promise<ScheduleListResponse> { export function getScheduleList(params: ScheduleListParams, requestOptions?: RequestOptions): Promise<ScheduleListResponse> {
return request({ return request({
url: '/erp/schedule/controller/listSchedules', url: '/erp/schedule/controller/listSchedules',
method: 'get', method: 'get',
params, params,
requestOptions,
}) as Promise<ScheduleListResponse>; }) as Promise<ScheduleListResponse>;
} }
export function getScheduleDetail(params: { id: string }): Promise<ScheduleDetailResponse> { export function getScheduleDetail(params: { id: string }, requestOptions?: RequestOptions): Promise<ScheduleDetailResponse> {
return request({ return request({
url: '/erp/schedule/controller/getSchedule', url: '/erp/schedule/controller/getSchedule',
method: 'get', method: 'get',
params, params,
requestOptions,
}) as Promise<ScheduleDetailResponse>; }) as Promise<ScheduleDetailResponse>;
} }
export function createSchedule(data: ScheduleSaveParams) { export function createSchedule(data: ScheduleSaveParams, requestOptions?: RequestOptions) {
return request({ return request({
url: '/erp/schedule/controller/createSchedule', url: '/erp/schedule/controller/createSchedule',
method: 'post', method: 'post',
data, data,
requestOptions,
}); });
} }
export function updateSchedule(data: ScheduleSaveParams) { export function updateSchedule(data: ScheduleSaveParams, requestOptions?: RequestOptions) {
return request({ return request({
url: '/erp/schedule/controller/updateSchedule', url: '/erp/schedule/controller/updateSchedule',
method: 'put', method: 'put',
data, data,
requestOptions,
}); });
} }
export function deleteSchedule(params: { id: string }) { export function deleteSchedule(params: { id: string }, requestOptions?: RequestOptions) {
return request({ return request({
url: '/erp/schedule/controller/deleteSchedule', url: '/erp/schedule/controller/deleteSchedule',
method: 'delete', method: 'delete',
params, params,
requestOptions,
}); });
} }

View File

@@ -178,8 +178,8 @@ export default defineComponent({
}, },
}) })
.then(async () => { .then(async () => {
// 清除缓存/token等 // 手动退出登录也只清理登录态缓存,保留主题、语言等本地配置。
Session.clear(); Session.clearAuth();
// 显式回到登录页,避免保留之前受保护页面的重定向参数 // 显式回到登录页,避免保留之前受保护页面的重定向参数
await router.replace('/login'); await router.replace('/login');
}) })

View File

@@ -95,7 +95,8 @@ router.beforeEach(async (to, from, next) => {
} else { } else {
if (!token) { if (!token) {
next(`/login?redirect=${to.path}&params=${JSON.stringify(to.query ? to.query : to.params)}`); next(`/login?redirect=${to.path}&params=${JSON.stringify(to.query ? to.query : to.params)}`);
Session.clear(); // 进入受保护页面但本地已没有 token 时,只清理登录态缓存即可。
Session.clearAuth();
NProgress.done(); NProgress.done();
} else if (token && to.path === '/login') { } else if (token && to.path === '/login') {
next('/home'); next('/home');

View File

@@ -1,35 +1,92 @@
import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import type { MessageHandler } from 'element-plus';
import { Session } from '/@/utils/storage'; import { Session } from '/@/utils/storage';
import qs from 'qs'; import qs from 'qs';
import { getChangedFields } from '/@/utils/diffUtils'; import { getChangedFields } from '/@/utils/diffUtils';
import { handleModuleNotEnabled } from '/@/utils/assetSubscribe'; import { handleModuleNotEnabled } from '/@/utils/assetSubscribe';
// 标记是否正在处理 token 过期,避免重复弹窗 /**
let isHandlingTokenExpired = false; * 控制一次请求的错误提示归属:
* - global: 交给 request.ts 统一弹错,适合绝大多数接口
* - page: 页面自己在 catch 中决定提示文案,避免与全局重复
* - silent: 完全静默,适合轮询、后台刷新等不希望打扰用户的请求
*/
export interface RequestOptions {
errorMode?: 'global' | 'page' | 'silent';
}
// 错误消息防抖:防止短时间内显示多个错误消息 declare module 'axios' {
let lastErrorTime = 0; interface AxiosRequestConfig {
const ERROR_MESSAGE_INTERVAL = 2000; // 2秒内只显示一个错误 requestOptions?: RequestOptions;
const showErrorMessage = (message: string) => {
const now = Date.now();
// 2秒内只显示一个错误消息不管内容是否相同
if (now - lastErrorTime < ERROR_MESSAGE_INTERVAL) {
return; // 跳过
} }
lastErrorTime = now; interface InternalAxiosRequestConfig {
ElMessage.error(message); 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 实例配置 * token 过期只接受明确的后端信号:
// 地址配置见 .env.development 文件 * - 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({ const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_URL, baseURL: import.meta.env.VITE_API_URL,
timeout: 50000, timeout: 50000,
@@ -41,23 +98,21 @@ const service: AxiosInstance = axios.create({
}, },
}); });
// token 过期处理函数 /**
* 登录过期时优先关闭普通错误消息,再弹出唯一的登录过期确认框。
* 这样用户不会先看到一个普通报错,再叠一个登录过期弹窗。
*/
const handleTokenExpired = () => { const handleTokenExpired = () => {
if (isHandlingTokenExpired) return; if (isHandlingTokenExpired) return;
isHandlingTokenExpired = true; isHandlingTokenExpired = true;
closeActiveErrorMessage();
ElMessageBox.alert('登录状态已过期,请重新登录', '提示', { ElMessageBox.alert('登录状态已过期,请重新登录', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
showClose: false, showClose: false,
closeOnClickModal: false, closeOnClickModal: false,
closeOnPressEscape: false, closeOnPressEscape: false,
beforeClose: (action, _instance, done) => {
if (action === 'confirm') {
done();
performLogout();
}
},
}) })
.then(() => { .then(() => {
performLogout(); performLogout();
@@ -67,60 +122,47 @@ const handleTokenExpired = () => {
}); });
}; };
// 执行退出登录操作 /**
* 统一退出动作:
* - 只清理登录态相关缓存
* - 重置 token 过期处理标记
* - 最后回到登录页
*/
const performLogout = () => { const performLogout = () => {
Session.clear(); Session.clearAuth();
localStorage.clear();
isHandlingTokenExpired = false; isHandlingTokenExpired = false;
// Hash 路由统一回登录页,避免跳到错误地址
setTimeout(() => { setTimeout(() => {
window.location.href = '/#/login'; window.location.href = '/#/login';
}, 500); }, 500);
}; };
// 请求拦截器
const requestInterceptor = (config: InternalAxiosRequestConfig) => { const requestInterceptor = (config: InternalAxiosRequestConfig) => {
// 检查 token 是否有效
const token = Session.get('token'); const token = Session.get('token');
if (token) { if (token) {
// 可以在这里添加 token 有效性检查(如果需要)
config.headers!['Authorization'] = `Bearer ${token}`; config.headers!['Authorization'] = `Bearer ${token}`;
} }
// PUT 请求最小化传参处理 // PUT 请求只传变更字段,避免把未修改的数据整包提交给后端。
// 如果请求数据中包含 _originalData则自动计算差异只传递修改过的字段
if (config.method?.toLowerCase() === 'put' && config.data && typeof config.data === 'object') { if (config.method?.toLowerCase() === 'put' && config.data && typeof config.data === 'object') {
const { _originalData, ...currentData } = config.data; const { _originalData, ...currentData } = config.data;
if (_originalData && typeof _originalData === 'object') { if (_originalData && typeof _originalData === 'object') {
// 获取 id 字段(必须保留)
const idField = currentData.id || currentData.Id || currentData.ID; const idField = currentData.id || currentData.Id || currentData.ID;
// 计算差异
const changedFields = getChangedFields(_originalData, currentData, { const changedFields = getChangedFields(_originalData, currentData, {
exclude: ['_originalData', 'id', 'Id', 'ID'], exclude: ['_originalData', 'id', 'Id', 'ID'],
}); });
// 如果有变化,只传递 id + 变化的字段 config.data = Object.keys(changedFields).length > 0 ? { id: idField, ...changedFields } : { id: idField };
if (Object.keys(changedFields).length > 0) {
config.data = { id: idField, ...changedFields };
} else {
// 没有变化,只传递 id
config.data = { id: idField };
}
} }
} }
return config; return config;
}; };
const requestErrorHandler = (error: any) => { const requestErrorHandler = (error: any) => Promise.reject(error);
return Promise.reject(error);
};
// 响应拦截器
const responseInterceptor = (response: AxiosResponse) => { const responseInterceptor = (response: AxiosResponse) => {
// 文件流响应直接返回 // 文件流直接返回原始响应,调用方需要自行处理 Blob。
if ( if (
response.config.responseType === 'blob' || response.config.responseType === 'blob' ||
response.headers['content-type']?.includes('application/zip') || response.headers['content-type']?.includes('application/zip') ||
@@ -133,75 +175,58 @@ const responseInterceptor = (response: AxiosResponse) => {
const httpStatus = response.status; const httpStatus = response.status;
const code = res?.code; const code = res?.code;
const message = res?.message; const message = res?.message;
const config = response.config;
// 检查 token 相关错误 if (isTokenExpiredError(httpStatus, code, message)) {
if (
httpStatus === 401 ||
code === 401 ||
message?.includes('token') ||
message === 'token is invalid' ||
message === 'token 解析失败' ||
message?.includes('decrypt error')
) {
handleTokenExpired(); handleTokenExpired();
return Promise.reject(new Error('登录状态已过期')); return Promise.reject(new Error('登录状态已过期'));
} }
// 处理模块未开通错误 (403)
// 跳过资产SKU查询接口避免弹窗内部请求触发循环
const requestUrl = response.config.url || ''; const requestUrl = response.config.url || '';
if (code === 402 && !requestUrl.includes('/assets/asset/sku/')) { if (code === 402 && !requestUrl.includes('/assets/asset/sku/')) {
// 获取当前路由路径
const currentPath = window.location.hash.replace('#', '') || window.location.pathname; const currentPath = window.location.hash.replace('#', '') || window.location.pathname;
handleModuleNotEnabled(currentPath); handleModuleNotEnabled(currentPath);
// 直接返回,不再显示错误消息
return Promise.reject(new Error('模块未开通')); return Promise.reject(new Error('模块未开通'));
} }
// 业务逻辑错误处理排除403因为上面已处理 // 业务失败默认走全局提示;如果页面声明自己处理,这里只抛错不弹窗。
if (code !== undefined && code !== 0 && code !== 200 && code !== 403) { if (code !== undefined && code !== 0 && code !== 200 && code !== 403) {
const errorMsg = message || `请求失败(${code})`; const errorMsg = message || `请求失败(${code})`;
showErrorMessage(errorMsg); showErrorMessage(errorMsg, config);
return Promise.reject(new Error(errorMsg)); return Promise.reject(new Error(errorMsg));
} }
return res; return res;
}; };
// 响应错误拦截器
const responseErrorHandler = (error: any) => { 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')) { if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
showErrorMessage('请求超时,请检查网络连接'); showErrorMessage('请求超时,请检查网络连接', config);
return Promise.reject(new Error('请求超时')); return Promise.reject(new Error('请求超时'));
} }
if (!error.response) { if (!error.response) {
if (error.message === 'Network Error') {
// ElMessage.error('网络连接错误,请检查网络设置');
} else {
// ElMessage.error('网络异常,请检查连接');
}
return Promise.reject(error); return Promise.reject(error);
} }
const httpStatus = error.response.status; if (isTokenExpiredError(httpStatus, error.response?.data?.code, responseMessage)) {
// 优先使用返回数据中的 message 字段
const responseMessage = error.response.data?.message;
// 处理 HTTP 错误状态
const requestUrl = error.response.config?.url || '';
switch (httpStatus) {
case 401:
handleTokenExpired(); handleTokenExpired();
break; return Promise.reject(new Error('登录状态已过期'));
}
const requestUrl = error.response.config?.url || '';
switch (httpStatus) {
case 402: case 402:
// 模块未开通处理跳过SKU相关接口避免循环
if (!requestUrl.includes('/assets/asset/sku/') && !requestUrl.includes('getAssetAndSku')) { if (!requestUrl.includes('/assets/asset/sku/') && !requestUrl.includes('getAssetAndSku')) {
// 检查是否刚从开通页面返回5秒内不再跳转
const lastSubscribeTime = sessionStorage.getItem('lastSubscribeTime'); const lastSubscribeTime = sessionStorage.getItem('lastSubscribeTime');
const now = Date.now(); const now = Date.now();
if (lastSubscribeTime && now - parseInt(lastSubscribeTime) < 5000) { if (lastSubscribeTime && now - parseInt(lastSubscribeTime) < 5000) {
showErrorMessage(responseMessage || '服务开通中,请稍后刷新页面'); showErrorMessage(responseMessage || '服务开通中,请稍后刷新页面', config);
return Promise.reject(new Error('模块开通中')); return Promise.reject(new Error('模块开通中'));
} }
@@ -209,40 +234,38 @@ const responseErrorHandler = (error: any) => {
handleModuleNotEnabled(currentPath); handleModuleNotEnabled(currentPath);
return Promise.reject(new Error('模块未开通')); return Promise.reject(new Error('模块未开通'));
} }
showErrorMessage(responseMessage || '服务未开通'); showErrorMessage(responseMessage || '服务未开通', config);
break; break;
case 403: case 403:
showErrorMessage(responseMessage || '没有权限访问该资源'); showErrorMessage(responseMessage || '没有权限访问该资源', config);
break; break;
case 404: case 404:
showErrorMessage(responseMessage || '请求的资源不存在'); showErrorMessage(responseMessage || '请求的资源不存在', config);
break; break;
case 429: case 429:
showErrorMessage(responseMessage || '请求过于频繁,请稍后再试'); // 429 是限流,不等于登录过期,这里只保留频率提示。
handleTokenExpired(); showErrorMessage(responseMessage || '请求过于频繁,请稍后再试', config);
break; break;
case 500: case 500:
showErrorMessage(responseMessage || '服务器内部错误'); showErrorMessage(responseMessage || '服务器内部错误', config);
break; break;
case 502: case 502:
showErrorMessage(responseMessage || '网关错误'); showErrorMessage(responseMessage || '网关错误', config);
break; break;
case 503: case 503:
showErrorMessage(responseMessage || '服务不可用'); showErrorMessage(responseMessage || '服务不可用', config);
break; break;
default: default:
if (httpStatus >= 400) { if (httpStatus >= 400) {
showErrorMessage(responseMessage || `请求失败(${httpStatus})`); showErrorMessage(responseMessage || `请求失败(${httpStatus})`, config);
} }
} }
return Promise.reject(error); return Promise.reject(error);
}; };
// 为实例添加拦截器
service.interceptors.request.use(requestInterceptor, requestErrorHandler); service.interceptors.request.use(requestInterceptor, requestErrorHandler);
service.interceptors.response.use(responseInterceptor, responseErrorHandler); service.interceptors.response.use(responseInterceptor, responseErrorHandler);
// 导出
export default service; export default service;
export { showErrorMessage }; export { closeActiveErrorMessage, showErrorMessage };

View File

@@ -1,5 +1,11 @@
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
/**
* 这些 key 属于登录态或用户会话上下文。
* 退出登录时只清理这部分数据,避免误删主题、语言、布局等本地个性化配置。
*/
const SESSION_AUTH_KEYS = ['token', 'userInfo', 'userMenu', 'permissions'];
/** /**
* window.localStorage 浏览器永久缓存 * window.localStorage 浏览器永久缓存
* @method set 设置永久缓存 * @method set 设置永久缓存
@@ -33,6 +39,7 @@ export const Local = {
* @method get 获取临时缓存 * @method get 获取临时缓存
* @method remove 移除临时缓存 * @method remove 移除临时缓存
* @method clear 移除全部临时缓存 * @method clear 移除全部临时缓存
* @method clearAuth 移除登录态相关缓存
*/ */
export const Session = { export const Session = {
// 设置临时缓存 // 设置临时缓存
@@ -56,4 +63,10 @@ export const Session = {
Cookies.remove('token'); Cookies.remove('token');
window.sessionStorage.clear(); window.sessionStorage.clear();
}, },
// 只清理登录态相关缓存,保留非登录相关的页面状态与本地配置
clearAuth() {
SESSION_AUTH_KEYS.forEach((key) => this.remove(key));
},
}; };
export { SESSION_AUTH_KEYS };

View File

@@ -36,8 +36,8 @@ export default defineComponent({
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes); const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const onSetAuth = () => { const onSetAuth = () => {
// https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS // https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS
// 清除缓存/token等 // 401 页面回登录时只清理登录态相关缓存,保留本地个性化配置。
Session.clear(); Session.clearAuth();
// 使用 reload 时,不需要调用 resetRoute() 重置路由 // 使用 reload 时,不需要调用 resetRoute() 重置路由
window.location.reload(); window.location.reload();
}; };

View File

@@ -101,7 +101,8 @@ const openDialog = async (row?: { id?: string }) => {
try { try {
loading.value = true; loading.value = true;
const res = await getLiveAccountDetail({ id: String(row.id) }); // 详情加载失败时由当前弹窗给出更易懂的业务提示。
const res = await getLiveAccountDetail({ id: String(row.id) }, { errorMode: 'page' });
if (res?.data) { if (res?.data) {
fillForm(res.data); fillForm(res.data);
} }
@@ -129,11 +130,12 @@ const handleSubmit = async () => {
remark: formData.remark, remark: formData.remark,
}; };
// 提交失败提示交给当前弹窗自己处理,避免和 request.ts 的统一报错重复。
if (isEdit.value) { if (isEdit.value) {
await updateLiveAccount(payload); await updateLiveAccount(payload, { errorMode: 'page' });
ElMessage.success('修改成功'); ElMessage.success('修改成功');
} else { } else {
await createLiveAccount(payload); await createLiveAccount(payload, { errorMode: 'page' });
ElMessage.success('新增成功'); ElMessage.success('新增成功');
} }

View File

@@ -131,13 +131,17 @@ const tableData = reactive({
const getList = async () => { const getList = async () => {
try { try {
tableData.loading = true; tableData.loading = true;
const res = await getLiveAccountList({ // 列表失败文案由当前页面决定,避免和全局请求报错同时出现。
const res = await getLiveAccountList(
{
...tableData.param, ...tableData.param,
platform: searchForm.platform || undefined, platform: searchForm.platform || undefined,
accountName: searchForm.accountName || undefined, accountName: searchForm.accountName || undefined,
accountId: searchForm.accountId || undefined, accountId: searchForm.accountId || undefined,
status: searchForm.status, status: searchForm.status,
}); },
{ errorMode: 'page' }
);
if (res && res.data) { if (res && res.data) {
tableData.data = (res.data.list || []).map((item: any) => ({ tableData.data = (res.data.list || []).map((item: any) => ({
...item, ...item,
@@ -191,7 +195,7 @@ const handleDelete = async (row: LiveAccountItem) => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}); });
await deleteLiveAccount({ id: row.id }); await deleteLiveAccount({ id: row.id }, { errorMode: 'page' });
ElMessage.success('删除成功'); ElMessage.success('删除成功');
getList(); getList();
} catch (error) { } catch (error) {

View File

@@ -153,7 +153,8 @@ const openDialog = async (row?: { id?: string }) => {
await loadOptions(); await loadOptions();
if (row?.id) { 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; const detail = res?.data;
if (detail) { if (detail) {
formData.id = String(detail.id); formData.id = String(detail.id);
@@ -195,11 +196,12 @@ const handleSubmit = async () => {
remark: formData.remark, remark: formData.remark,
}; };
// 提交失败文案由弹窗自己控制,避免接口层和弹窗层重复报错。
if (isEdit.value) { if (isEdit.value) {
await updateSchedule(payload); await updateSchedule(payload, { errorMode: 'page' });
ElMessage.success('修改排班成功'); ElMessage.success('修改排班成功');
} else { } else {
await createSchedule(payload); await createSchedule(payload, { errorMode: 'page' });
ElMessage.success('新增排班成功'); ElMessage.success('新增排班成功');
} }

View File

@@ -144,12 +144,16 @@ const getStatusTagType = (status: number): 'success' | 'info' | 'warning' => {
const getList = async () => { const getList = async () => {
try { try {
tableData.loading = true; tableData.loading = true;
const res = await getScheduleList({ // 列表失败文案由当前页面决定,避免和 request.ts 的全局错误提示重复。
const res = await getScheduleList(
{
...tableData.param, ...tableData.param,
anchorName: searchForm.anchorName || undefined, anchorName: searchForm.anchorName || undefined,
accountName: searchForm.accountName || undefined, accountName: searchForm.accountName || undefined,
status: searchForm.status, status: searchForm.status,
} as any); } as any,
{ errorMode: 'page' }
);
const scheduleData = res?.data; const scheduleData = res?.data;
if (scheduleData) { if (scheduleData) {
tableData.data = (scheduleData.list || []).map((item: any) => ({ tableData.data = (scheduleData.list || []).map((item: any) => ({
@@ -207,7 +211,7 @@ const handleDelete = async (row: ScheduleItem) => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}); });
await deleteSchedule({ id: row.id }); await deleteSchedule({ id: row.id }, { errorMode: 'page' });
ElMessage.success('删除成功'); ElMessage.success('删除成功');
getList(); getList();
} catch (error) { } catch (error) {