client.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. // Axios客户端实例(仅注入JWT Token,factory_id由后端从Token提取)
  2. import axios from 'axios';
  3. import type { AxiosInstance } from 'axios';
  4. import { getSessionStorage, removeSessionStorage, setSessionStorage, STORAGE_KEYS } from '@smartcut/shared-utils';
  5. export interface ApiClientConfig {
  6. baseURL: string;
  7. tokenKey: string; // sessionStorage中的token键名
  8. }
  9. /**
  10. * 创建API客户端实例
  11. * factory_id 不再由前端传递,后端通过 JWT Token 自动提取
  12. */
  13. export function createApiClient(config: ApiClientConfig): AxiosInstance {
  14. const instance = axios.create({
  15. baseURL: config.baseURL,
  16. timeout: 30000,
  17. headers: {
  18. 'Content-Type': 'application/json'
  19. }
  20. });
  21. // 请求拦截器:仅注入JWT Token
  22. instance.interceptors.request.use(
  23. (requestConfig) => {
  24. const token = getSessionStorage<string>(config.tokenKey);
  25. if (token) {
  26. requestConfig.headers.Authorization = `Bearer ${token}`;
  27. }
  28. return requestConfig;
  29. },
  30. (error) => Promise.reject(error)
  31. );
  32. // 响应拦截器:Token续期 + 业务错误码处理 + 401处理
  33. instance.interceptors.response.use(
  34. (response) => {
  35. const newToken = response.headers['x-new-token'];
  36. if (newToken) {
  37. setSessionStorage(config.tokenKey, newToken);
  38. }
  39. // 后端对业务错误返回 HTTP 200 + code!=0,此处统一转为 rejected,
  40. // 让各视图的 catch 块能正确显示错误信息
  41. const body = response.data;
  42. if (body && typeof body === 'object' && typeof body.code === 'number' && body.code !== 0) {
  43. const err = new Error(body.msg || '请求失败');
  44. (err as any).code = body.code;
  45. (err as any).response = response;
  46. return Promise.reject(err);
  47. }
  48. return response;
  49. },
  50. (error) => {
  51. if (error.response?.status === 401) {
  52. // 401时仅清除Token,保留factory_id便于重新登录同一工厂
  53. removeSessionStorage(config.tokenKey);
  54. const factoryId = getSessionStorage<string>(STORAGE_KEYS.CURRENT_FACTORY_ID);
  55. // P2: 保存当前路径,登录后返回原页面;redirect 仅允许相对路径,防开放重定向
  56. const currentPath = window.location.pathname + window.location.search;
  57. const isSafeRedirect = currentPath.startsWith('/') && !currentPath.startsWith('//') && !currentPath.startsWith('/login');
  58. const base = isSafeRedirect
  59. ? '/login?redirect=' + encodeURIComponent(currentPath)
  60. : '/login';
  61. const factoryParam = factoryId ? '&factory=' + encodeURIComponent(factoryId) : '';
  62. window.location.href = base + factoryParam;
  63. }
  64. return Promise.reject(error);
  65. }
  66. );
  67. return instance;
  68. }