33b4c734aa
后端: - 新增认证(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 数据库迁移配置 - 添加初始化示例数据脚本 - 更新项目文档
122 lines
3.7 KiB
TypeScript
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; |