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 数据库迁移配置 - 添加初始化示例数据脚本 - 更新项目文档
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user