Initial commit

This commit is contained in:
2025-09-22 11:51:16 +08:00
commit c32381f8ed
1191 changed files with 130140 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
import { http } from './http';
export const amazonApi = {
// 上传Excel文件解析ASIN列表
importAsinFromExcel(file: File) {
const formData = new FormData();
formData.append('file', file);
return http.upload<{ code: number, data: { asinList: string[], total: number }, msg: string | null }>('/api/amazon/import/asin', formData);
},
getProductsBatch(asinList: string[], batchId: string) {
return http.post<{ code: number, data: { products: any[] }, msg: string | null }>('/api/amazon/products/batch', { asinList, batchId });
},
getLatestProducts() {
return http.get<{ code: number, data: { products: any[] }, msg: string | null }>('/api/amazon/products/latest');
},
getProductsByBatch(batchId: string) {
return http.get<{ products: any[] }>(`/api/amazon/products/batch/${batchId}`);
},
updateProduct(productData: unknown) {
return http.post('/api/amazon/products/update', productData);
},
deleteProduct(productId: string) {
return http.post('/api/amazon/products/delete', { id: productId });
},
exportToExcel(products: unknown[], options: Record<string, unknown> = {}) {
return http.post('/api/amazon/export', { products, ...options });
},
getProductStats() {
return http.get('/api/amazon/stats');
},
searchProducts(searchParams: Record<string, unknown>) {
return http.get('/api/amazon/products/search', searchParams);
},
openGenmaiSpirit() {
return http.post('/api/genmai/open');
},
};

View File

@@ -0,0 +1,90 @@
import { http } from './http';
// 统一响应处理函数 - 适配ERP客户端格式
function unwrap<T>(res: any): T {
if (res && typeof res.success === 'boolean') {
if (!res.success) {
const message: string = res.message || res.msg || '请求失败';
throw new Error(message);
}
return res as T;
}
// 兼容标准格式
if (res && typeof res.code === 'number') {
if (res.code !== 0) {
const message: string = res.msg || '请求失败';
throw new Error(message);
}
return (res.data as T) ?? ({} as T);
}
return res as T;
}
// 认证相关类型定义
interface LoginRequest {
username: string;
password: string;
}
interface RegisterRequest {
username: string;
password: string;
}
interface LoginResponse {
success: boolean;
token: string;
permissions: string[];
username: string;
message?: string;
}
interface RegisterResponse {
success: boolean;
message?: string;
}
interface CheckUsernameResponse {
available: boolean;
}
export const authApi = {
// 用户登录
login(params: LoginRequest) {
return http
.post('/api/login', params)
.then(res => unwrap<LoginResponse>(res));
},
// 用户注册
register(params: RegisterRequest) {
return http
.post('/api/register', params)
.then(res => unwrap<RegisterResponse>(res));
},
// 检查用户名可用性
checkUsername(username: string) {
return http
.get('/api/check-username', { username })
.then(res => {
// checkUsername 使用标准格式 {code: 200, data: boolean}
if (res && res.code === 200) {
return { available: res.data };
}
throw new Error(res?.msg || '检查用户名失败');
});
},
// 验证token有效性
verifyToken(token: string) {
return http
.post('/api/verify', { token })
.then(res => unwrap<{ success: boolean }>(res));
},
// 用户登出
logout(token: string) {
return http.postVoid('/api/logout', { token });
},
};

View File

@@ -0,0 +1,48 @@
import { http } from './http'
// 与老版保持相同的接口路径与参数
const base = '/api/device'
export interface DeviceQuota {
limit: number
used: number
}
export interface DeviceItem {
deviceId: string
name?: string
status?: 'online' | 'offline'
lastActiveAt?: string
}
export const deviceApi = {
getQuota(username: string) {
return http.get<DeviceQuota | any>(`${base}/quota`, { username }).then((res: any) => {
if (res && typeof res.limit !== 'undefined') return res as DeviceQuota
if (res && typeof res.code === 'number') return (res.data as DeviceQuota) || { limit: 0, used: 0 }
return (res?.data as DeviceQuota) || { limit: 0, used: 0 }
})
},
list(username: string) {
return http.get<DeviceItem[] | any>(`${base}/list`, { username }).then((res: any) => {
if (Array.isArray(res)) return res as DeviceItem[]
if (res && typeof res.code === 'number') return (res.data as DeviceItem[]) || []
return (res?.data as DeviceItem[]) || []
})
},
register(payload: { username: string }) {
return http.post(`${base}/register`, payload)
},
remove(payload: { deviceId: string }) {
return http.postVoid(`${base}/remove`, payload)
},
heartbeat(payload: { username: string; deviceId: string; version?: string }) {
return http.postVoid(`${base}/heartbeat`, payload)
},
}

View File

@@ -0,0 +1,78 @@
// 极简 HTTP 工具:仅封装 GET/POST默认指向本地 8081
export type HttpMethod = 'GET' | 'POST';
const BASE_URL = 'http://localhost:8081';
// 将对象转为查询字符串
function buildQuery(params?: Record<string, unknown>): string {
if (!params) return '';
const usp = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value === undefined || value === null) return;
usp.append(key, String(value));
});
const queryString = usp.toString();
return queryString ? `?${queryString}` : '';
}
// 统一请求入口:自动加上 BASE_URL、JSON 头与错误处理
async function request<T>(path: string, options: RequestInit): Promise<T> {
const res = await fetch(`${BASE_URL}${path}`, {
credentials: 'omit',
cache: 'no-store',
...options,
headers: {
'Content-Type': 'application/json',
...(options.headers || {}),
},
});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(text || `HTTP ${res.status}`);
}
const contentType = res.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
return (await res.json()) as T;
}
return (await res.text()) as unknown as T;
}
export const http = {
get<T>(path: string, params?: Record<string, unknown>) {
return request<T>(`${path}${buildQuery(params)}`, { method: 'GET' });
},
post<T>(path: string, body?: unknown) {
return request<T>(path, { method: 'POST', body: body ? JSON.stringify(body) : undefined });
},
// 用于无需读取响应体的 POST如删除/心跳等),从根源避免读取中断
postVoid(path: string, body?: unknown) {
return fetch(`${BASE_URL}${path}`, {
method: 'POST',
body: body ? JSON.stringify(body) : undefined,
credentials: 'omit',
cache: 'no-store',
headers: { 'Content-Type': 'application/json' },
}).then(res => {
if (!res.ok) return res.text().then(t => Promise.reject(new Error(t || `HTTP ${res.status}`)));
return undefined as unknown as void;
});
},
// 文件上传:透传 FormData不设置 Content-Type 让浏览器自动处理
upload<T>(path: string, form: FormData) {
const res = fetch(`${BASE_URL}${path}`, {
method: 'POST',
body: form,
credentials: 'omit',
cache: 'no-store',
});
return res.then(async response => {
if (!response.ok) {
const text = await response.text().catch(() => '');
throw new Error(text || `HTTP ${response.status}`);
}
return response.json() as Promise<T>;
});
},
};

View File

@@ -0,0 +1,38 @@
import { http } from './http';
function unwrap<T>(res: any): T {
if (res && typeof res.code === 'number') {
if (res.code !== 0) {
const message: string = res.msg || '请求失败';
throw new Error(message);
}
return (res.data as T) ?? ({} as T);
}
return res as T;
}
export const rakutenApi = {
// 上传 Excel 或按店铺名查询
getProducts(params: { file?: File; shopName?: string; batchId?: string }) {
const formData = new FormData();
if (params.file) formData.append('file', params.file);
if (params.batchId) formData.append('batchId', params.batchId);
if (params.shopName) formData.append('shopName', params.shopName);
return http
.upload('/api/rakuten/products', formData)
.then(res => unwrap<{ products: any[]; total?: number; sessionId?: string }>(res));
},
search1688(imageUrl: string, sessionId?: string) {
const payload: Record<string, unknown> = { imageUrl };
if (sessionId) payload.sessionId = sessionId;
return http.post('/api/rakuten/search1688', payload).then(res => unwrap<any>(res));
},
getLatestProducts() {
return http.get('/api/rakuten/products/latest').then(res => unwrap<{ products: any[] }>(res));
},
exportAndSave(exportData: unknown) {
return http
.post('/api/rakuten/export-and-save', exportData)
.then(res => unwrap<{ filePath: string; fileName?: string; recordCount?: number; hasImages?: boolean }>(res));
},
};

View File

@@ -0,0 +1,15 @@
import { http } from './http';
export const shopeeApi = {
getAdHosting(params: Record<string, unknown> = {}) {
return http.get('/api/shopee/ad-hosting', params);
},
getReviews(params: Record<string, unknown> = {}) {
return http.get('/api/shopee/reviews', params);
},
exportData(exportParams: Record<string, unknown> = {}) {
return http.post('/api/shopee/export', exportParams);
},
};

View File

@@ -0,0 +1,56 @@
// 斑马订单模型(根据页面所需字段精简定义)
export interface ZebraOrder {
orderedAt?: string;
productImage?: string;
productTitle?: string;
shopOrderNumber?: string;
timeSinceOrder?: string;
priceJpy?: number;
productQuantity?: number;
shippingFeeJpy?: number;
serviceFee?: string;
productNumber?: string;
poNumber?: string;
shippingFeeCny?: number;
internationalShippingFee?: number;
poLogisticsCompany?: string;
poTrackingNumber?: string;
internationalTrackingNumber?: string;
trackInfo?: string;
}
export interface ZebraOrdersResp {
orders: ZebraOrder[];
total?: number;
totalPages?: number;
}
import { http } from './http';
// 斑马 API与原 zebra-api.js 对齐的接口封装
export const zebraApi = {
getOrders(params: Record<string, unknown>) {
return http.get<ZebraOrdersResp>('/api/banma/orders', params);
},
getOrdersByBatch(batchId: string) {
return http.get<ZebraOrdersResp>(`/api/banma/orders/batch/${batchId}`);
},
getLatestOrders() {
return http.get<ZebraOrdersResp>('/api/banma/orders/latest');
},
getShops() {
return http.get<{ data?: { list?: Array<{ id: string; shopName: string }> } }>('/api/banma/shops');
},
refreshToken() {
return http.post('/api/banma/refresh-token');
},
exportAndSaveOrders(exportData: unknown) {
return http.post<{ filePath: string }>('/api/banma/export-and-save', exportData);
},
getOrderStats() {
return http.get('/api/banma/orders/stats');
},
searchOrders(searchParams: Record<string, unknown>) {
return http.get('/api/banma/orders/search', searchParams);
},
};