type等
This commit is contained in:
298
urbanLifelineWeb/packages/shared/src/api/index.ts
Normal file
298
urbanLifelineWeb/packages/shared/src/api/index.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
import axios, { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from "axios";
|
||||
import { ElLoading, ElMessage } from "element-plus";
|
||||
import type { ResultDomain } from "@/types";
|
||||
import { API_BASE_URL } from "@/config";
|
||||
|
||||
/**
|
||||
* 扩展AxiosRequestConfig以支持自定义配置
|
||||
*/
|
||||
interface CustomAxiosRequestConfig extends Partial<InternalAxiosRequestConfig> {
|
||||
/** 是否显示加载动画 */
|
||||
showLoading?: boolean;
|
||||
/** 是否显示错误提示 */
|
||||
showError?: boolean;
|
||||
/** 是否需要token */
|
||||
requiresAuth?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token管理
|
||||
*/
|
||||
export const TokenManager = {
|
||||
/** 获取token(从localStorage获取并检查过期) */
|
||||
getToken(): string | null {
|
||||
const itemStr = localStorage.getItem('token');
|
||||
if (!itemStr) return null;
|
||||
|
||||
try {
|
||||
const item = JSON.parse(itemStr);
|
||||
const now = Date.now();
|
||||
|
||||
// 检查是否过期
|
||||
if (item.timestamp && item.expiresIn) {
|
||||
if (now - item.timestamp > item.expiresIn) {
|
||||
// 已过期,删除
|
||||
localStorage.removeItem('token');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return item.value || itemStr; // 兼容旧数据
|
||||
} catch {
|
||||
// 如果不是JSON格式,直接返回(兼容旧数据)
|
||||
return itemStr;
|
||||
}
|
||||
},
|
||||
|
||||
/** 设置token(始终使用localStorage,根据rememberMe设置过期时间) */
|
||||
setToken(token: string, rememberMe = false): void {
|
||||
const data = {
|
||||
value: token,
|
||||
timestamp: Date.now(),
|
||||
// 如果不勾选"记住我",设置1天过期时间;勾选则7天
|
||||
expiresIn: rememberMe ? 7 * 24 * 60 * 60 * 1000 : 1 * 24 * 60 * 60 * 1000
|
||||
};
|
||||
localStorage.setItem('token', JSON.stringify(data));
|
||||
},
|
||||
|
||||
/** 移除token */
|
||||
removeToken(): void {
|
||||
localStorage.removeItem('token');
|
||||
},
|
||||
|
||||
/** 检查是否有token */
|
||||
hasToken(): boolean {
|
||||
return !!this.getToken();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建axios实例
|
||||
*
|
||||
* 说明:
|
||||
* - 统一使用配置模块提供的 API_BASE_URL 作为基础路径
|
||||
* - API_BASE_URL 在开发环境来自 devConfig,在生产环境来自 window.APP_RUNTIME_CONFIG
|
||||
*/
|
||||
const request = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
}
|
||||
});
|
||||
|
||||
let loadingInstance: ReturnType<typeof ElLoading.service> | null = null;
|
||||
|
||||
/**
|
||||
* 请求拦截器
|
||||
*/
|
||||
request.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
const customConfig = config as CustomAxiosRequestConfig;
|
||||
|
||||
// 默认不显示加载动画,只有 showLoading 为 true 时才展示
|
||||
if (customConfig.showLoading === true) {
|
||||
loadingInstance = ElLoading.service({
|
||||
lock: true,
|
||||
text: "加载中...",
|
||||
background: "rgba(0, 0, 0, 0.7)",
|
||||
});
|
||||
}
|
||||
|
||||
// 添加token
|
||||
const token = TokenManager.getToken();
|
||||
if (token && config.headers) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// 自动处理 FormData:删除 Content-Type,让浏览器自动设置(包含 boundary)
|
||||
if (config.data instanceof FormData && config.headers) {
|
||||
delete config.headers['Content-Type'];
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
if (loadingInstance) {
|
||||
loadingInstance.close();
|
||||
loadingInstance = null;
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 响应拦截器
|
||||
*/
|
||||
request.interceptors.response.use(
|
||||
(response: AxiosResponse<ResultDomain<any>>) => {
|
||||
// 关闭加载动画
|
||||
if (loadingInstance) {
|
||||
loadingInstance.close();
|
||||
loadingInstance = null;
|
||||
}
|
||||
|
||||
const result = response.data;
|
||||
|
||||
// 检查是否为ResultDomain格式
|
||||
if (result && typeof result === 'object' && 'code' in result) {
|
||||
// 检查登录状态
|
||||
if (result.login === false) {
|
||||
ElMessage.error(result.message || '请先登录');
|
||||
TokenManager.removeToken();
|
||||
// 跳转到登录页
|
||||
window.location.href = '/login';
|
||||
return Promise.reject(new Error('未登录'));
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
if (result.auth === false) {
|
||||
ElMessage.error(result.message || '没有权限访问');
|
||||
return Promise.reject(new Error('无权限'));
|
||||
}
|
||||
|
||||
// // 检查业务逻辑是否成功 业务逻辑不应该提前判断
|
||||
// if (!result.success) {
|
||||
// const config = response.config as CustomAxiosRequestConfig;
|
||||
// if (config.showError !== false) {
|
||||
// ElMessage.error(result.message || '操作失败');
|
||||
// }
|
||||
// return Promise.reject(new Error(result.message || '操作失败'));
|
||||
// }
|
||||
|
||||
// 返回成功的数据
|
||||
return response;
|
||||
}
|
||||
|
||||
// 非ResultDomain格式,直接返回
|
||||
return response;
|
||||
},
|
||||
(error: AxiosError<ResultDomain<any>>) => {
|
||||
// 关闭加载动画
|
||||
if (loadingInstance) {
|
||||
loadingInstance.close();
|
||||
loadingInstance = null;
|
||||
}
|
||||
|
||||
const config = error.config as CustomAxiosRequestConfig;
|
||||
|
||||
// 处理HTTP错误
|
||||
if (error.response) {
|
||||
const { status, data } = error.response;
|
||||
|
||||
switch (status) {
|
||||
case 401:
|
||||
ElMessage.error('认证失败,请重新登录');
|
||||
TokenManager.removeToken();
|
||||
window.location.href = '/login';
|
||||
break;
|
||||
case 403:
|
||||
ElMessage.error('没有权限访问该资源');
|
||||
break;
|
||||
case 404:
|
||||
ElMessage.error('请求的资源不存在');
|
||||
break;
|
||||
case 500:
|
||||
ElMessage.error(data?.message || '服务器内部错误');
|
||||
break;
|
||||
default:
|
||||
if (config?.showError !== false) {
|
||||
ElMessage.error(data?.message || '请求失败');
|
||||
}
|
||||
}
|
||||
} else if (error.request) {
|
||||
// 请求已发送但没有收到响应
|
||||
ElMessage.error('网络错误,请检查网络连接');
|
||||
} else {
|
||||
// 其他错误
|
||||
if (config?.showError !== false) {
|
||||
ElMessage.error(error.message || '请求失败');
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* API封装
|
||||
*/
|
||||
export const api = {
|
||||
/**
|
||||
* GET请求 - data参数会作为URL查询参数追加
|
||||
*/
|
||||
get<T = any>(url: string, data?: any, config?: CustomAxiosRequestConfig): Promise<AxiosResponse<ResultDomain<T>>> {
|
||||
// 如果有data参数,将其转换为URL查询参数
|
||||
if (data) {
|
||||
const params = new URLSearchParams();
|
||||
Object.keys(data).forEach(key => {
|
||||
if (data[key] !== undefined && data[key] !== null) {
|
||||
params.append(key, String(data[key]));
|
||||
}
|
||||
});
|
||||
const queryString = params.toString();
|
||||
url += (url.includes('?') ? '&' : '?') + queryString;
|
||||
}
|
||||
return request.get<ResultDomain<T>>(url, config);
|
||||
},
|
||||
|
||||
/**
|
||||
* POST请求 - data参数放到请求体
|
||||
*/
|
||||
post<T = any>(url: string, data?: any, config?: CustomAxiosRequestConfig): Promise<AxiosResponse<ResultDomain<T>>> {
|
||||
return request.post<ResultDomain<T>>(url, data, config);
|
||||
},
|
||||
|
||||
/**
|
||||
* PUT请求 - data参数放到请求体
|
||||
*/
|
||||
put<T = any>(url: string, data?: any, config?: CustomAxiosRequestConfig): Promise<AxiosResponse<ResultDomain<T>>> {
|
||||
return request.put<ResultDomain<T>>(url, data, config);
|
||||
},
|
||||
|
||||
/**
|
||||
* DELETE请求 - data参数放到请求体
|
||||
*/
|
||||
delete<T = any>(url: string, data?: any, config?: CustomAxiosRequestConfig): Promise<AxiosResponse<ResultDomain<T>>> {
|
||||
return request.delete<ResultDomain<T>>(url, { ...config, data });
|
||||
},
|
||||
|
||||
/**
|
||||
* PATCH请求 - data参数放到请求体
|
||||
*/
|
||||
patch<T = any>(url: string, data?: any, config?: CustomAxiosRequestConfig): Promise<AxiosResponse<ResultDomain<T>>> {
|
||||
return request.patch<ResultDomain<T>>(url, data, config);
|
||||
},
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
*/
|
||||
upload<T = any>(url: string, formData: FormData, config?: CustomAxiosRequestConfig): Promise<AxiosResponse<ResultDomain<T>>> {
|
||||
return request.post<ResultDomain<T>>(url, formData, {
|
||||
...config,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
...(config?.headers as Record<string, string>)
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 文件下载
|
||||
*/
|
||||
download(url: string, filename?: string, config?: CustomAxiosRequestConfig): Promise<void> {
|
||||
return request.get(url, {
|
||||
...config,
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data]);
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = filename || 'download';
|
||||
link.click();
|
||||
window.URL.revokeObjectURL(link.href);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default request;
|
||||
Reference in New Issue
Block a user