添加会话模型和API Key配置功能
- 在模型模块中新增会话开关状态字段,支持会话模型的管理。 - 更新模型选择器,增加系统模型的API Key配置弹窗,提升用户体验。 - 优化错误处理逻辑,确保接口错误由全局拦截器处理,减少冗余提示。 - 更新相关样式以增强界面可读性和美观性。
This commit is contained in:
@@ -7,10 +7,10 @@ import { getChangedFields } from '/@/utils/diffUtils';
|
||||
import { handleModuleNotEnabled } from '/@/utils/assetSubscribe';
|
||||
|
||||
/**
|
||||
* 控制一次请求的错误提示归属:
|
||||
* - global: 交给 request.ts 统一弹错,适合绝大多数接口
|
||||
* - page: 页面自己在 catch 中决定提示文案,避免与全局重复
|
||||
* - silent: 完全静默,适合轮询、后台刷新等不希望打扰用户的请求
|
||||
* 控制一次请求的错误提示归属(默认 global):
|
||||
* - global: 由拦截器统一弹出后端返回的 message(含 HTTP 与业务 JSON)
|
||||
* - page: 不自动弹窗,仅 reject;请在页面 catch 内自行处理(应与全局择一,避免重复)
|
||||
* - silent: 完全静默(轮询等)
|
||||
*/
|
||||
export interface RequestOptions {
|
||||
errorMode?: 'global' | 'page' | 'silent';
|
||||
@@ -39,6 +39,26 @@ const ERROR_MESSAGE_INTERVAL = 2000;
|
||||
const getErrorMode = (config?: InternalAxiosRequestConfig) => config?.requestOptions?.errorMode ?? 'global';
|
||||
const shouldShowGlobalError = (config?: InternalAxiosRequestConfig) => getErrorMode(config) === 'global';
|
||||
|
||||
/**
|
||||
* 从接口响应体解析可读错误文案(JSON API、Spring 风格等)
|
||||
*/
|
||||
export function extractBackendMessage(data: unknown): string | undefined {
|
||||
if (data == null) return undefined;
|
||||
if (typeof data === 'string') {
|
||||
const t = data.trim();
|
||||
return t.length > 0 && t.length < 2000 ? t : undefined;
|
||||
}
|
||||
if (typeof data !== 'object') return undefined;
|
||||
const o = data as Record<string, unknown>;
|
||||
const pick = (v: unknown) => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
|
||||
return (
|
||||
pick(o.message) ||
|
||||
pick(o.msg) ||
|
||||
pick(o.error) ||
|
||||
(typeof o.detail === 'string' ? pick(o.detail) : undefined)
|
||||
);
|
||||
}
|
||||
|
||||
const closeActiveErrorMessage = () => {
|
||||
activeErrorMessage?.close();
|
||||
activeErrorMessage = null;
|
||||
@@ -174,7 +194,7 @@ const responseInterceptor = (response: AxiosResponse) => {
|
||||
const res = response.data;
|
||||
const httpStatus = response.status;
|
||||
const code = res?.code;
|
||||
const message = res?.message;
|
||||
const message = extractBackendMessage(res);
|
||||
const config = response.config;
|
||||
|
||||
if (isTokenExpiredError(httpStatus, code, message)) {
|
||||
@@ -207,8 +227,8 @@ const responseInterceptor = (response: AxiosResponse) => {
|
||||
if (knownErrorCodes.includes(code)) {
|
||||
errorMsg = message || `请求失败(${code})`;
|
||||
} else {
|
||||
// 未知的 code,统一提示后端异常
|
||||
errorMsg = '后端异常,请联系管理员';
|
||||
// 未知的 code:优先使用后端 message,便于排查业务含义
|
||||
errorMsg = message || '后端异常,请联系管理员';
|
||||
}
|
||||
|
||||
showErrorMessage(errorMsg, config);
|
||||
@@ -221,7 +241,8 @@ const responseInterceptor = (response: AxiosResponse) => {
|
||||
const responseErrorHandler = (error: any) => {
|
||||
const config = error.config as InternalAxiosRequestConfig | undefined;
|
||||
const httpStatus = error.response?.status;
|
||||
const responseMessage = error.response?.data?.message;
|
||||
const responseData = error.response?.data;
|
||||
const responseMessage = extractBackendMessage(responseData);
|
||||
|
||||
if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
|
||||
showErrorMessage('请求超时,请检查网络连接', config);
|
||||
@@ -232,7 +253,7 @@ const responseErrorHandler = (error: any) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (isTokenExpiredError(httpStatus, error.response?.data?.code, responseMessage)) {
|
||||
if (isTokenExpiredError(httpStatus, error.response?.data?.code as number | undefined, responseMessage)) {
|
||||
handleTokenExpired();
|
||||
return Promise.reject(new Error('登录状态已过期'));
|
||||
}
|
||||
@@ -245,7 +266,7 @@ const responseErrorHandler = (error: any) => {
|
||||
const lastSubscribeTime = sessionStorage.getItem('lastSubscribeTime');
|
||||
const now = Date.now();
|
||||
if (lastSubscribeTime && now - parseInt(lastSubscribeTime) < 5000) {
|
||||
showErrorMessage(responseMessage || '服务开通中,请稍后刷新页面', config);
|
||||
showErrorMessage(responseMessage ?? '服务开通中,请稍后刷新页面', config);
|
||||
return Promise.reject(new Error('模块开通中'));
|
||||
}
|
||||
|
||||
@@ -253,30 +274,30 @@ const responseErrorHandler = (error: any) => {
|
||||
handleModuleNotEnabled(currentPath);
|
||||
return Promise.reject(new Error('模块未开通'));
|
||||
}
|
||||
showErrorMessage(responseMessage || '服务未开通', config);
|
||||
showErrorMessage(responseMessage ?? '服务未开通', config);
|
||||
break;
|
||||
case 403:
|
||||
showErrorMessage(responseMessage || '没有权限访问该资源', config);
|
||||
showErrorMessage(responseMessage ?? '没有权限访问该资源', config);
|
||||
break;
|
||||
case 404:
|
||||
showErrorMessage(responseMessage || '请求的资源不存在', config);
|
||||
showErrorMessage(responseMessage ?? '请求的资源不存在', config);
|
||||
break;
|
||||
case 429:
|
||||
// 429 是限流,不等于登录过期,这里只保留频率提示。
|
||||
showErrorMessage(responseMessage || '请求过于频繁,请稍后再试', config);
|
||||
showErrorMessage(responseMessage ?? '请求过于频繁,请稍后再试', config);
|
||||
break;
|
||||
case 500:
|
||||
showErrorMessage(responseMessage || '服务器内部错误', config);
|
||||
showErrorMessage(responseMessage ?? '服务器内部错误', config);
|
||||
break;
|
||||
case 502:
|
||||
showErrorMessage(responseMessage || '网关错误', config);
|
||||
showErrorMessage(responseMessage ?? '网关错误', config);
|
||||
break;
|
||||
case 503:
|
||||
showErrorMessage(responseMessage || '服务不可用', config);
|
||||
showErrorMessage(responseMessage ?? '服务不可用', config);
|
||||
break;
|
||||
default:
|
||||
if (httpStatus >= 400) {
|
||||
showErrorMessage(responseMessage || `请求失败(${httpStatus})`, config);
|
||||
showErrorMessage(responseMessage ?? `请求失败(${httpStatus})`, config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,5 +307,27 @@ const responseErrorHandler = (error: any) => {
|
||||
service.interceptors.request.use(requestInterceptor, requestErrorHandler);
|
||||
service.interceptors.response.use(responseInterceptor, responseErrorHandler);
|
||||
|
||||
/**
|
||||
* 从 axios / 业务 reject 中取出后端返回的提示文案(与全局拦截器同源逻辑;silent / 特殊场景下可在页面使用)。
|
||||
*/
|
||||
export function getApiErrorMessage(error: unknown, fallback = '操作失败'): string {
|
||||
const e = error as any;
|
||||
const fromBody = extractBackendMessage(e?.response?.data);
|
||||
if (fromBody != null && fromBody !== '') {
|
||||
return fromBody;
|
||||
}
|
||||
const msg = e?.message;
|
||||
if (typeof msg === 'string' && msg.trim() !== '') {
|
||||
if (/^Request failed with status code \d+$/i.test(msg)) {
|
||||
return extractBackendMessage(e?.response?.data) ?? fallback;
|
||||
}
|
||||
if (msg === 'Network Error') {
|
||||
return '网络异常,请检查网络连接';
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export default service;
|
||||
export { closeActiveErrorMessage, showErrorMessage };
|
||||
|
||||
Reference in New Issue
Block a user