Initial commit

This commit is contained in:
2026-02-06 14:06:49 +08:00
commit 5fc7b33b3b
28 changed files with 5004 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
import axios from 'axios';
import type { ApiResponse, AuthResponse, User } from '@/types';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';
const apiClient = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('authToken');
localStorage.removeItem('currentUser');
window.location.href = '/login';
}
if (error.response?.status === 404) {
const url = error.config?.url || '';
if (url.includes('/query')) {
const customError = new Error('未找到该序列号,请检查输入是否正确');
customError.name = 'NotFoundError';
return Promise.reject(customError);
}
}
return Promise.reject(error);
}
);
export const authApi = {
login: async (username: string, password: string) => {
const response = await apiClient.post('/auth/login', { username, password });
// 后端返回的是 accessToken,前端期望的是 token
const token = response.data.accessToken || response.data.token;
const user = response.data.user;
if (token && user) {
localStorage.setItem('authToken', token);
localStorage.setItem('currentUser', JSON.stringify(user));
return user;
}
throw new Error('登录失败');
},
logout: async () => {
try {
await apiClient.post('/auth/logout');
} catch (error) {
console.error('Logout error:', error);
}
localStorage.removeItem('authToken');
localStorage.removeItem('currentUser');
},
getCurrentUser: (): User | null => {
const user = localStorage.getItem('currentUser');
return user ? JSON.parse(user) : null;
},
updateProfile: async (data: { name?: string; email?: string }) => {
const response = await apiClient.put('/auth/profile', data);
const user = response.data;
if (user) {
localStorage.setItem('currentUser', JSON.stringify(user));
return user;
}
throw new Error('更新资料失败');
},
changePassword: async (currentPassword: string, newPassword: string) => {
const response = await apiClient.post('/auth/change-password', { currentPassword, newPassword });
if (response.data.message) {
return true;
}
throw new Error(response.data.error || '修改密码失败');
},
};
export const serialApi = {
generate: async (data: {
companyName: string;
serialOption: 'auto' | 'custom';
serialPrefix?: string;
quantity: number;
validOption: 'days' | 'date';
validDays?: number;
validUntil?: string;
}) => {
// 根据后端接口调整参数
const payload = {
companyName: data.companyName,
quantity: data.quantity,
validDays: data.validOption === 'days' ? data.validDays : undefined,
serialPrefix: data.serialOption === 'custom' ? data.serialPrefix : undefined,
};
const response = await apiClient.post('/serials/generate', payload);
if (response.data.serials) {
return response.data;
}
throw new Error(response.data.error || '生成序列号失败');
},
query: async (serialNumber: string) => {
// 后端路径是正确的: /api/serials/:serialNumber/query
const response = await apiClient.get(`/serials/${encodeURIComponent(serialNumber)}/query`);
if (response.data.serial) {
return response.data.serial;
}
throw new Error(response.data.error || '查询序列号失败');
},
list: async (companyId?: number) => {
let url = '/serials';
if (companyId) url += `?companyId=${companyId}`;
const response = await apiClient.get(url);
if (response.data.data) {
return response.data.data;
}
throw new Error('获取序列号列表失败');
},
delete: async (id: number) => {
// 后端没有单个删除接口,需要使用企业接口下的删除
const response = await apiClient.delete(`/serials/${id}`);
if (response.data) {
return true;
}
throw new Error(response.data.error || '删除序列号失败');
},
};
export const companyApi = {
list: async (filter?: { search?: string; status?: 'all' | 'active' | 'expired' }) => {
let url = '/companies';
if (filter?.search || filter?.status) {
const params = new URLSearchParams();
if (filter.search) params.append('search', filter.search);
if (filter.status && filter.status !== 'all') params.append('status', filter.status);
url += `?${params.toString()}`;
}
const response = await apiClient.get(url);
if (response.data.data) {
return response.data.data;
}
throw new Error('获取企业列表失败');
},
get: async (companyName: string) => {
const response = await apiClient.get(`/companies/${encodeURIComponent(companyName)}`);
if (response.data.data) {
return response.data.data;
}
throw new Error('获取企业详情失败');
},
delete: async (companyName: string) => {
const response = await apiClient.delete(`/companies/${encodeURIComponent(companyName)}`);
if (response.data) {
return true;
}
throw new Error(response.data.error || '删除企业失败');
},
};
export const dashboardApi = {
getStats: async () => {
// 后端路径是 /api/companies/stats/overview
const response = await apiClient.get('/companies/stats/overview');
if (response.data.data) {
const data = response.data.data;
// 转换数据格式以匹配前端期望
return {
totalCompanies: data.overview?.totalCompanies || 0,
totalSerials: data.overview?.totalSerials || 0,
activeSerials: data.overview?.activeSerials || 0,
inactiveSerials: data.overview?.inactiveSerials || 0,
monthlyData: data.monthlyStats || [],
recentCompanies: data.recentCompanies?.map((c: any) => ({
id: c.companyName,
name: c.companyName,
status: 'active' as const,
createdAt: c.lastCreated,
})) || [],
recentSerials: data.recentSerials?.map((s: any) => ({
id: s.serialNumber,
serialNumber: s.serialNumber,
companyName: s.companyName,
status: s.isActive ? 'active' : 'inactive',
createdAt: s.createdAt,
})) || [],
};
}
throw new Error('获取统计数据失败');
},
};