🐛 修复导入错误提示重复弹出并丢失业务文案
- axios 拦截器:blob 请求(导入/导出)在业务校验失败时后端返回的是 JSON(Content-Type: application/json),而 axios 在 responseType='blob' 下把响应体解析成 Blob,直接读 res.data.msg 取不到任何字段, status 退回 res.status、文案落到「系统错误」兜底,用户误以为系统崩溃。 现按 Content-Type 判断:JSON 才解析回对象,真·文件流保持原样不动。 - 统一提示出口 notifiedError():弹窗后给 Error 打 _notified 标记, 业务层据此避免同一条错误被弹两次。 - import-excel:拦截器已提示过的错误不再重复 $message.error; >500/网络异常等仍由 resolveErrorMessage 从 Blob 中取 msg 兜底。
This commit is contained in:
+52
-11
@@ -70,11 +70,44 @@ const redirectToLogin = () => {
|
|||||||
store.dispatch('FedLogOut').then(() => router.push({ path: '/login' }));
|
store.dispatch('FedLogOut').then(() => router.push({ path: '/login' }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一弹出错误提示并返回带标记的 Error。
|
||||||
|
* `_notified` 标记用于让业务层(批量导入等)识别「拦截器已经提示过」,
|
||||||
|
* 避免同一条错误被弹两次。
|
||||||
|
*/
|
||||||
|
const notifiedError = message => {
|
||||||
|
ElMessage({
|
||||||
|
message: message,
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
const error = new Error(message);
|
||||||
|
error._notified = true;
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
axios.defaults.timeout = 10000;
|
axios.defaults.timeout = 10000;
|
||||||
//返回其他状态码
|
//返回其他状态码
|
||||||
axios.defaults.validateStatus = function (status) {
|
axios.defaults.validateStatus = function (status) {
|
||||||
return status >= 200 && status <= 500; // 默认的
|
return status >= 200 && status <= 500; // 默认的
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断响应是否为 JSON。
|
||||||
|
* 用于 blob 请求(导入/导出)的兜底:后端在业务校验失败时返回的是 JSON
|
||||||
|
* (Content-Type: application/json),而不是文件流,需要解析后才能取到 msg。
|
||||||
|
*/
|
||||||
|
const isJsonContentType = res => {
|
||||||
|
const headers = res.headers || {};
|
||||||
|
const contentType = String(headers['content-type'] || res.data?.type || '').toLowerCase();
|
||||||
|
return contentType.includes('application/json') || contentType.includes('text/json');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断响应体是否已是解析好的对象(无需再解析)。
|
||||||
|
* 注意 Blob 也是 object,必须排除,否则会把待解析的文件流误判为已完成。
|
||||||
|
*/
|
||||||
|
const isPlainObject = value =>
|
||||||
|
value !== null && typeof value === 'object' && !(value instanceof Blob);
|
||||||
//跨域请求,允许保存cookie
|
//跨域请求,允许保存cookie
|
||||||
axios.defaults.withCredentials = true;
|
axios.defaults.withCredentials = true;
|
||||||
// NProgress Configuration
|
// NProgress Configuration
|
||||||
@@ -140,7 +173,7 @@ axios.interceptors.request.use(
|
|||||||
|
|
||||||
//http response拦截
|
//http response拦截
|
||||||
axios.interceptors.response.use(
|
axios.interceptors.response.use(
|
||||||
res => {
|
async res => {
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
//获取配置信息
|
//获取配置信息
|
||||||
const config = res.config;
|
const config = res.config;
|
||||||
@@ -149,6 +182,22 @@ axios.interceptors.response.use(
|
|||||||
if (cryptoData) {
|
if (cryptoData) {
|
||||||
res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey));
|
res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey));
|
||||||
}
|
}
|
||||||
|
// blob 请求(文件下载、导入)的响应体是 Blob,直接读 res.data.code / res.data.msg
|
||||||
|
// 拿不到任何字段:状态码会退回 res.status、提示文案会落到「系统错误」兜底,
|
||||||
|
// 用户就会以为系统崩了——而后端其实返回了明确的业务提示(如「未读取到数据行…」)。
|
||||||
|
// 这里按 Content-Type 判断:JSON 才解析回对象;真·文件流(Excel 等)保持原样不动。
|
||||||
|
// 兼容 Blob(浏览器 responseType='blob')与 string(非浏览器环境)两种形态。
|
||||||
|
if (isJsonContentType(res) && !isPlainObject(res.data)) {
|
||||||
|
try {
|
||||||
|
const text = res.data instanceof Blob ? await res.data.text() : res.data;
|
||||||
|
const parsed = typeof text === 'string' && text ? JSON.parse(text) : null;
|
||||||
|
if (parsed && typeof parsed === 'object') {
|
||||||
|
res.data = parsed;
|
||||||
|
}
|
||||||
|
} catch (ignored) {
|
||||||
|
// 声明是 JSON 却解析失败:保持原样,交给下游按文件处理
|
||||||
|
}
|
||||||
|
}
|
||||||
//获取状态信息
|
//获取状态信息
|
||||||
const status = res.data.error_code || res.data.code || res.status;
|
const status = res.data.error_code || res.data.code || res.status;
|
||||||
const statusWhiteList = website.statusWhiteList || [];
|
const statusWhiteList = website.statusWhiteList || [];
|
||||||
@@ -204,20 +253,12 @@ axios.interceptors.response.use(
|
|||||||
|
|
||||||
// OAuth2 业务错误码提示
|
// OAuth2 业务错误码提示
|
||||||
if (status > 2000 && !validatenull(res.data.error_description)) {
|
if (status > 2000 && !validatenull(res.data.error_description)) {
|
||||||
ElMessage({
|
return Promise.reject(notifiedError(message));
|
||||||
message: message,
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
return Promise.reject(new Error(message));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其余非 200 响应统一提示
|
// 其余非 200 响应统一提示
|
||||||
if (status !== 200) {
|
if (status !== 200) {
|
||||||
ElMessage({
|
return Promise.reject(notifiedError(message));
|
||||||
message: message,
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
return Promise.reject(new Error(message));
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -153,7 +153,11 @@ export const handleImportExcel = async (vm, option, column, businessName, onSucc
|
|||||||
option.onSuccess(res);
|
option.onSuccess(res);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vm.$message.error(await resolveErrorMessage(error));
|
// 拦截器已提示过的错误(如后端 400 返回的业务提示)不再重复弹一次,
|
||||||
|
// 只保留一次清晰的中文提示。
|
||||||
|
if (!error?._notified) {
|
||||||
|
vm.$message.error(await resolveErrorMessage(error));
|
||||||
|
}
|
||||||
if (typeof option.onError === 'function') {
|
if (typeof option.onError === 'function') {
|
||||||
option.onError(error);
|
option.onError(error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user