Files
s2f/frontend/lib/api/client.ts
T
freedakgmail 33b4c734aa feat: 完善前后端核心功能模块
后端:
- 新增认证(auth)、任务(tasks)、映射(mappings)、对账(reconciliation)、异常(exceptions)、导出(exports) API
- 新增核心模块: database, security, permissions, tenant, exceptions, error_handlers
- 新增数据模型: user, company, reconciliation_task, field_mapping, uploaded_file 等
- 新增服务层: ai_recognizer, file_parser, file_storage, mapping, reconciliation 等
- 添加数据库迁移脚本

前端:
- 新增登录页面和仪表盘页面
- 新增任务列表、任务详情、字段映射页面
- 新增异常处理页面和规则设置页面
- 新增 API 代理路由 /api/[...path]
- 新增 UI 组件库 (button, card, dialog, input, table 等)
- 新增 auth 组件 (ProtectedRoute, PermissionGate)
- 新增 layout 组件 (Header, Sidebar)
- 新增 mapping 组件 (FieldMappingTable, AISuggestionPanel)
- 新增 API 客户端和 hooks (useAsync, useToast, usePermission 等)
- 新增状态管理 (auth-store, company-store, ui-store)
- 集成 Tailwind CSS 和 shadcn/ui 组件库

其他:
- 添加 Alembic 数据库迁移配置
- 添加初始化示例数据脚本
- 更新项目文档
2026-07-07 09:04:47 +08:00

122 lines
3.7 KiB
TypeScript

import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import type { ApiError, ApiResponse } from './types';
// 创建 Axios 实例
const createApiClient = (): AxiosInstance => {
const baseURL = process.env.NEXT_PUBLIC_API_URL || '';
const instance = axios.create({
baseURL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器
instance.interceptors.request.use(
(config) => {
// 自动添加 JWT Token
if (typeof window !== 'undefined') {
const token = localStorage.getItem('auth_token');
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
// 添加企业 ID
const companyId = localStorage.getItem('company_id');
if (companyId && config.headers) {
config.headers['X-Company-ID'] = companyId;
}
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
instance.interceptors.response.use(
(response: AxiosResponse) => {
return response;
},
(error: AxiosError<ApiError>) => {
// 统一错误处理
const apiError: ApiError = {
code: 'UNKNOWN_ERROR',
message: '未知错误',
};
if (error.response) {
// 服务器返回错误
const { data, status } = error.response;
if (data && typeof data === 'object' && 'error' in data) {
const errorData = data as { error: { code: string; message: string; details?: unknown } };
apiError.code = errorData.error.code;
apiError.message = errorData.error.message;
apiError.details = errorData.error.details;
} else {
apiError.code = `HTTP_${status}`;
apiError.message = error.message;
}
// 401 未授权 - 跳转登录
if (status === 401) {
if (typeof window !== 'undefined') {
localStorage.removeItem('auth_token');
localStorage.removeItem('company_id');
window.location.href = '/login';
}
}
} else if (error.request) {
// 请求发送但无响应
apiError.code = 'NETWORK_ERROR';
apiError.message = '网络错误,请检查连接';
} else {
// 其他错误
apiError.message = error.message;
}
return Promise.reject(apiError);
}
);
return instance;
};
// API 客户端单例
export const apiClient = createApiClient();
// 通用请求方法封装
export const api = {
get: async <T>(url: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> => {
const response = await apiClient.get<T>(url, config);
return { data: response.data, status: response.status };
},
post: async <T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> => {
const response = await apiClient.post<T>(url, data, config);
return { data: response.data, status: response.status };
},
put: async <T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> => {
const response = await apiClient.put<T>(url, data, config);
return { data: response.data, status: response.status };
},
patch: async <T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> => {
const response = await apiClient.patch<T>(url, data, config);
return { data: response.data, status: response.status };
},
delete: async <T>(url: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> => {
const response = await apiClient.delete<T>(url, config);
return { data: response.data, status: response.status };
},
};
export default apiClient;