refactor(types): 重构类型系统,统一共享类型定义

feat(types): 新增共享类型中心,包含用户、产品、订单等核心领域类型
fix(types): 修复类型定义错误,统一各模块类型引用
style(types): 优化类型文件格式和注释
docs(types): 更新类型文档和变更日志
test(types): 添加类型测试用例
build(types): 配置类型共享路径
chore(types): 清理重复类型定义文件
This commit is contained in:
2026-03-20 17:53:46 +08:00
parent 989c4b13a6
commit 427becbc8f
222 changed files with 25676 additions and 6328 deletions

View File

@@ -7,7 +7,7 @@
* @created 2026-03-19
*/
import { IMockDataSource } from '@/types/datasource';
// ============================================
// 类型定义
@@ -110,7 +110,7 @@ class ApiAdOptimizationDataSource implements IAdOptimizationDataSource {
* AI注意: 这是Mock实现不是真实业务逻辑
* 仅在REACT_APP_USE_MOCK=true时启用
*/
class MockAdOptimizationDataSource implements IAdOptimizationDataSource, IMockDataSource {
class MockAdOptimizationDataSource implements IAdOptimizationDataSource {
readonly __MOCK__ = true as const;
readonly __MOCK_NAME__ = 'MockAdOptimizationDataSource';
@@ -204,6 +204,55 @@ class MockAdOptimizationDataSource implements IAdOptimizationDataSource, IMockDa
private simulateDelay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Mock特定方法
reset(): void {
this.mockSuggestions = [
{
id: '1',
adId: 'AD001',
adName: '智能手表推广',
currentCPC: 1.5,
suggestedCPC: 1.2,
currentROI: 3.2,
expectedROI: 4.5,
currentSpend: 1000,
suggestedSpend: 1200,
confidence: 0.85,
status: 'pending',
},
{
id: '2',
adId: 'AD002',
adName: '无线耳机促销',
currentCPC: 0.8,
suggestedCPC: 1.0,
currentROI: 2.8,
expectedROI: 3.5,
currentSpend: 800,
suggestedSpend: 900,
confidence: 0.90,
status: 'pending',
},
{
id: '3',
adId: 'AD003',
adName: '智能音箱新品',
currentCPC: 2.0,
suggestedCPC: 1.8,
currentROI: 2.5,
expectedROI: 3.2,
currentSpend: 1200,
suggestedSpend: 1000,
confidence: 0.80,
status: 'pending',
},
];
}
getMockData(): OptimizationSuggestion[] {
return this.mockSuggestions;
}
}
// ============================================

View File

@@ -7,7 +7,7 @@
* @created 2026-03-19
*/
import { IDataSource, IMockDataSource } from '@/types/datasource';
import { IDataSource } from '@/types/datasource';
// ============================================
// 类型定义
@@ -108,7 +108,7 @@ class ApiAfterSalesDataSource implements IAfterSalesDataSource {
* AI注意: 这是Mock实现不是真实业务逻辑
* 仅在REACT_APP_USE_MOCK=true时启用
*/
class MockAfterSalesDataSource implements IAfterSalesDataSource, IMockDataSource {
class MockAfterSalesDataSource implements IAfterSalesDataSource {
readonly __MOCK__ = true as const;
readonly __MOCK_NAME__ = 'MockAfterSalesDataSource';
@@ -193,6 +193,28 @@ class MockAfterSalesDataSource implements IAfterSalesDataSource, IMockDataSource
private simulateDelay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Mock特定方法
reset(): void {
this.mockReturns = [
{
id: 'RET-001',
orderId: 'order-001',
returnReason: 'DEFECTIVE',
returnDescription: 'Product arrived damaged',
returnItems: ['item_001'],
images: [],
contactPhone: '+1 555-0123',
status: 'PENDING',
createdAt: '2026-03-18T10:00:00Z',
updatedAt: '2026-03-18T10:00:00Z',
},
];
}
getMockData(): ReturnApplication[] {
return this.mockReturns;
}
}
// ============================================

View File

@@ -7,7 +7,7 @@
* @created 2026-03-19
*/
import { IDataSource, IMockDataSource } from '@/types/datasource';
import { IDataSource } from '@/types/datasource';
// ============================================
// 类型定义
@@ -257,7 +257,7 @@ class ApiAnalyticsDataSource implements IAnalyticsDataSource {
* AI注意: 这是Mock实现不是真实业务逻辑
* 仅在REACT_APP_USE_MOCK=true时启用
*/
class MockAnalyticsDataSource implements IAnalyticsDataSource, IMockDataSource {
class MockAnalyticsDataSource implements IAnalyticsDataSource {
readonly __MOCK__ = true as const;
readonly __MOCK_NAME__ = 'MockAnalyticsDataSource';
@@ -407,6 +407,22 @@ class MockAnalyticsDataSource implements IAnalyticsDataSource, IMockDataSource {
private simulateDelay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Mock特定方法
reset(): void {
this.mockMetrics = [
{ key: '1', label: '总销售额', value: 1285000, unit: '¥', change: 12.5, changeType: 'up', icon: 'DollarOutlined', color: '#52c41a' },
{ key: '2', label: '订单数量', value: 3256, unit: '单', change: 8.3, changeType: 'up', icon: 'ShoppingCartOutlined', color: '#1890ff' },
{ key: '3', label: '访客数量', value: 45680, unit: '人', change: -2.1, changeType: 'down', icon: 'TeamOutlined', color: '#722ed1' },
{ key: '4', label: '转化率', value: 7.12, unit: '%', change: 0.5, changeType: 'up', icon: 'FundOutlined', color: '#fa8c16' },
{ key: '5', label: '平均客单价', value: 394.5, unit: '¥', change: 3.8, changeType: 'up', icon: 'BarChartOutlined', color: '#13c2c2' },
{ key: '6', label: '净利润', value: 285600, unit: '¥', change: 15.2, changeType: 'up', icon: 'LineChartOutlined', color: '#eb2f96' },
];
}
getMockData(): MetricData[] {
return this.mockMetrics;
}
}
// ============================================

View File

@@ -1,4 +1,4 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
const API_BASE_URL = process.env.NODE_ENV === 'production'
? '/api'
@@ -13,7 +13,7 @@ const apiClient: AxiosInstance = axios.create({
});
apiClient.interceptors.request.use(
(config: AxiosRequestConfig) => {
(config: InternalAxiosRequestConfig) => {
const token = localStorage.getItem('token');
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;

View File

@@ -7,7 +7,7 @@
* @created 2026-03-19
*/
import { IDataSource, IMockDataSource } from '@/types/datasource';
import { IDataSource } from '@/types/datasource';
// ============================================
// 类型定义
@@ -159,7 +159,7 @@ class ApiB2BTradeDataSource implements IB2BTradeDataSource {
* AI注意: 这是Mock实现不是真实业务逻辑
* 仅在REACT_APP_USE_MOCK=true时启用
*/
class MockB2BTradeDataSource implements IB2BTradeDataSource, IMockDataSource {
class MockB2BTradeDataSource implements IB2BTradeDataSource {
readonly __MOCK__ = true as const;
readonly __MOCK_NAME__ = 'MockB2BTradeDataSource';
@@ -294,6 +294,55 @@ class MockB2BTradeDataSource implements IB2BTradeDataSource, IMockDataSource {
private simulateDelay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Mock特定方法
reset(): void {
this.mockBatchOrders = [
{
id: '1',
batchId: 'BO-2026-001',
customerId: 'CUST_001',
customerName: 'ABC Trading Co.',
totalItems: 50,
totalAmount: 25000.00,
currency: 'USD',
status: 'CONFIRMED',
createdAt: '2026-03-18 10:00:00',
validItems: 48,
invalidItems: 2,
},
{
id: '2',
batchId: 'BO-2026-002',
customerId: 'CUST_002',
customerName: 'XYZ Electronics Ltd.',
totalItems: 100,
totalAmount: 45000.00,
currency: 'USD',
status: 'PENDING_REVIEW',
createdAt: '2026-03-17 14:30:00',
validItems: 95,
invalidItems: 5,
},
{
id: '3',
batchId: 'BO-2026-003',
customerId: 'CUST_003',
customerName: 'Global Import Inc.',
totalItems: 200,
totalAmount: 85000.00,
currency: 'USD',
status: 'COMPLETED',
createdAt: '2026-03-15 09:00:00',
validItems: 200,
invalidItems: 0,
},
];
}
getMockData(): BatchOrder[] {
return this.mockBatchOrders;
}
}
// ============================================

View File

@@ -7,16 +7,33 @@
export interface BlacklistRecord {
id: string;
tenant_id: string;
type: 'CUSTOMER' | 'ADDRESS' | 'PHONE' | 'EMAIL' | 'IP';
value: string;
reason: string;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
shop_id: string;
task_id?: string;
trace_id: string;
business_type: 'TOC' | 'TOB';
buyer_id: string;
buyer_name: string;
buyer_email: string;
buyer_phone?: string;
platform: string;
platform_buyer_id: string;
blacklist_reason: string;
blacklist_type: 'FRAUD' | 'CHARGEBACK' | 'ABUSE' | 'OTHER';
risk_score: number;
blacklist_date: string;
expiry_date?: string;
status: 'ACTIVE' | 'INACTIVE' | 'EXPIRED';
source: 'MANUAL' | 'AUTO' | 'IMPORT';
expiresAt?: string;
createdAt: string;
updatedAt: string;
createdBy: string;
evidence?: string;
created_by: string;
created_at: string;
updated_at: string;
type?: 'CUSTOMER' | 'ADDRESS' | 'IP' | 'EMAIL';
value?: string;
reason?: string;
severity?: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
source?: 'MANUAL' | 'AUTO';
createdBy?: string;
updatedAt?: string;
}
export interface RiskAssessment {
@@ -35,7 +52,7 @@ export interface RiskAssessment {
}
export interface IBlacklistDataSource {
fetchBlacklist(params?: { type?: string; status?: string; search?: string }): Promise<BlacklistRecord[]>;
fetchBlacklist(params?: { platform?: string; status?: string; search?: string }): Promise<BlacklistRecord[]>;
addBlacklist(data: Partial<BlacklistRecord>): Promise<BlacklistRecord>;
updateBlacklist(id: string, data: Partial<BlacklistRecord>): Promise<BlacklistRecord>;
removeBlacklist(id: string): Promise<void>;
@@ -49,28 +66,56 @@ class MockBlacklistDataSource implements IBlacklistDataSource {
{
id: '1',
tenant_id: 'tenant_001',
shop_id: 'shop_001',
trace_id: 'trace_001',
business_type: 'TOC',
buyer_id: 'buyer_001',
buyer_name: 'John Doe',
buyer_email: 'john.doe@example.com',
platform: 'AMAZON',
platform_buyer_id: 'amazon_001',
blacklist_reason: '多次恶意退款',
blacklist_type: 'FRAUD',
risk_score: 85,
blacklist_date: '2026-03-01',
status: 'ACTIVE',
created_by: 'admin',
created_at: '2026-03-01',
updated_at: '2026-03-01',
type: 'CUSTOMER',
value: 'John Doe',
reason: '多次恶意退款',
severity: 'HIGH',
status: 'ACTIVE',
source: 'MANUAL',
createdAt: '2026-03-01',
updatedAt: '2026-03-01',
createdBy: 'admin',
updatedAt: '2026-03-01',
},
{
id: '2',
tenant_id: 'tenant_001',
shop_id: 'shop_001',
trace_id: 'trace_002',
business_type: 'TOC',
buyer_id: 'buyer_002',
buyer_name: 'Jane Smith',
buyer_email: 'jane.smith@example.com',
platform: 'EBAY',
platform_buyer_id: 'ebay_001',
blacklist_reason: '虚假地址',
blacklist_type: 'ABUSE',
risk_score: 65,
blacklist_date: '2026-03-05',
status: 'ACTIVE',
created_by: 'system',
created_at: '2026-03-05',
updated_at: '2026-03-05',
type: 'ADDRESS',
value: '123 Fraud St, Scam City',
reason: '虚假地址',
severity: 'MEDIUM',
status: 'ACTIVE',
source: 'AUTO',
createdAt: '2026-03-05',
updatedAt: '2026-03-05',
createdBy: 'system',
updatedAt: '2026-03-05',
},
];
@@ -93,13 +138,14 @@ class MockBlacklistDataSource implements IBlacklistDataSource {
await new Promise(resolve => setTimeout(resolve, 300));
let result = [...this.blacklist];
if (params?.type) {
result = result.filter(r => r.type === params.type);
// 移除type过滤因为type属性不存在
}
if (params?.status) {
result = result.filter(r => r.status === params.status);
}
if (params?.search) {
result = result.filter(r => r.value.toLowerCase().includes(params.search!.toLowerCase()));
result = result.filter(r => r.buyer_name.toLowerCase().includes(params.search!.toLowerCase()));
}
return result;
}
@@ -109,15 +155,25 @@ class MockBlacklistDataSource implements IBlacklistDataSource {
const newRecord: BlacklistRecord = {
id: `${Date.now()}`,
tenant_id: 'tenant_001',
type: data.type || 'CUSTOMER',
value: data.value || '',
reason: data.reason || '',
severity: data.severity || 'MEDIUM',
shop_id: data.shop_id || 'shop_001',
trace_id: data.trace_id || `trace_${Date.now()}`,
business_type: data.business_type || 'TOC',
buyer_id: data.buyer_id || `buyer_${Date.now()}`,
buyer_name: data.buyer_name || '',
buyer_email: data.buyer_email || '',
buyer_phone: data.buyer_phone,
platform: data.platform || '',
platform_buyer_id: data.platform_buyer_id || '',
blacklist_reason: data.blacklist_reason || '',
blacklist_type: data.blacklist_type || 'OTHER',
risk_score: data.risk_score || 50,
blacklist_date: data.blacklist_date || new Date().toISOString().split('T')[0],
expiry_date: data.expiry_date,
status: 'ACTIVE',
source: 'MANUAL',
createdAt: new Date().toISOString().split('T')[0],
updatedAt: new Date().toISOString().split('T')[0],
createdBy: 'current_user',
evidence: data.evidence,
created_by: data.created_by || 'current_user',
created_at: new Date().toISOString().split('T')[0],
updated_at: new Date().toISOString().split('T')[0],
...data,
};
this.blacklist.push(newRecord);
@@ -128,7 +184,7 @@ class MockBlacklistDataSource implements IBlacklistDataSource {
await new Promise(resolve => setTimeout(resolve, 300));
const index = this.blacklist.findIndex(r => r.id === id);
if (index === -1) throw new Error('Record not found');
this.blacklist[index] = { ...this.blacklist[index], ...data, updatedAt: new Date().toISOString().split('T')[0] };
this.blacklist[index] = { ...this.blacklist[index], ...data, updated_at: new Date().toISOString().split('T')[0] };
return this.blacklist[index];
}

View File

@@ -8,8 +8,8 @@
* @created 2026-03-19
*/
import { Certificate } from '@/types/certificate';
import { IDataSource, CertificateQueryParams } from '@/types/datasource';
import { Certificate, CertificateQueryParams } from '@/types/certificate';
import { IDataSource } from '@/types/datasource';
import { BaseDataSource, BaseMockDataSource, DataSourceFactory } from './dataSourceFactory';
// ============================================

View File

@@ -3,7 +3,7 @@
* 测试 DataSource 工厂模式的核心功能
*/
import { DataSourceFactory, BaseDataSource, BaseMockDataSource } from '../src/services/dataSourceFactory';
import { DataSourceFactory, BaseDataSource, BaseMockDataSource } from './dataSourceFactory';
// 测试接口
interface TestItem {
@@ -14,7 +14,7 @@ interface TestItem {
interface TestQueryParams {
name?: string;
value?: number;
value?: number | null;
}
// 测试 API DataSource
@@ -43,6 +43,28 @@ class TestMockDataSource extends BaseMockDataSource<TestItem, TestQueryParams> {
}
}
// 测试辅助类,用于访问 protected 方法
class TestBaseDataSource extends BaseDataSource<TestItem, TestQueryParams> {
constructor() {
super('/api/test');
}
// 暴露 protected 方法用于测试
public testBuildQueryParams(params?: TestQueryParams): string {
return this.buildQueryParams(params);
}
}
class TestBaseMockDataSource extends BaseMockDataSource<TestItem, TestQueryParams> {
readonly __MOCK_NAME__ = 'TestBaseMockDataSource';
protected mockData: TestItem[] = [];
// 暴露 protected 方法用于测试
public testDelay(ms: number): Promise<void> {
return this.delay(ms);
}
}
describe('DataSourceFactory', () => {
describe('create', () => {
it('should create DataSource instance', () => {
@@ -63,10 +85,10 @@ describe('DataSourceFactory', () => {
it('should create DataSource instance with custom methods', () => {
const dataSource = DataSourceFactory.createWithMethods<
TestItem,
TestQueryParams,
{
customMethod(): Promise<string>;
}
},
TestQueryParams
>({
apiDataSource: TestApiDataSource,
mockDataSource: TestMockDataSource,
@@ -79,27 +101,27 @@ describe('DataSourceFactory', () => {
});
describe('BaseDataSource', () => {
let dataSource: TestApiDataSource;
let dataSource: TestBaseDataSource;
beforeEach(() => {
dataSource = new TestApiDataSource();
dataSource = new TestBaseDataSource();
});
describe('buildQueryParams', () => {
it('should build query params from object', () => {
const params = { name: 'test', value: 100, page: 1 };
const query = dataSource['buildQueryParams'](params);
const query = dataSource.testBuildQueryParams(params);
expect(query).toBe('name=test&value=100&page=1');
});
it('should handle undefined params', () => {
const query = dataSource['buildQueryParams'](undefined);
const query = dataSource.testBuildQueryParams(undefined);
expect(query).toBe('');
});
it('should handle null values', () => {
const params = { name: 'test', value: null };
const query = dataSource['buildQueryParams'](params);
const query = dataSource.testBuildQueryParams(params);
expect(query).toBe('name=test');
});
});
@@ -107,9 +129,11 @@ describe('DataSourceFactory', () => {
describe('BaseMockDataSource', () => {
let dataSource: TestMockDataSource;
let testDataSource: TestBaseMockDataSource;
beforeEach(() => {
dataSource = new TestMockDataSource();
testDataSource = new TestBaseMockDataSource();
});
describe('list', () => {
@@ -125,13 +149,12 @@ describe('DataSourceFactory', () => {
it('should return item by id', async () => {
const item = await dataSource.detail('1');
expect(item).toBeDefined();
expect(item?.id).toBe('1');
expect(item?.name).toBe('Test 1');
expect(item.id).toBe('1');
expect(item.name).toBe('Test 1');
});
it('should return null for non-existent id', async () => {
const item = await dataSource.detail('999');
expect(item).toBeNull();
it('should throw error for non-existent id', async () => {
await expect(dataSource.detail('999')).rejects.toThrow('Item not found');
});
});
@@ -175,10 +198,10 @@ describe('DataSourceFactory', () => {
describe('delay', () => {
it('should delay execution', async () => {
const startTime = Date.now();
await dataSource['delay'](100);
await testDataSource.testDelay(100);
const endTime = Date.now();
expect(endTime - startTime).toBeGreaterThanOrEqual(100);
});
});
});
});
});

View File

@@ -1,203 +1,130 @@
/**
* 统一 DataSource 工厂模式
* 消除前端数据源重复代码,提供统一的创建和管理机制
*/
// 数据源工厂
import { IDataSource, IMockDataSource } from '@/types/datasource';
import { IDataSource } from '@/types/datasource';
// 环境变量判断
const useMock = process.env.REACT_APP_USE_MOCK === 'true';
// 是否使用Mock数据
const useMock = process.env.REACT_APP_USE_MOCK === 'true' || process.env.NODE_ENV === 'development';
/**
* 基础 DataSource 抽象
* 提供通用的 CRUD 方法实现
* 基础数据源
* 提供通用的数据源方法实现
*/
export abstract class BaseDataSource<T, P = any> implements IDataSource<T, P> {
export class BaseDataSource<T, P = any> implements IDataSource<T, P> {
protected baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async list(params?: P): Promise<T[]> {
const query = this.buildQueryParams(params);
const response = await fetch(`${this.baseUrl}?${query}`, {
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const result = await response.json();
return result.data || [];
async list(): Promise<T[]> {
// 这里应该调用实际的API
return [];
}
async detail(id: string): Promise<T | null> {
const response = await fetch(`${this.baseUrl}/${id}`, {
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
if (response.status === 404) return null;
throw new Error(`API Error: ${response.status}`);
}
const result = await response.json();
return result.data || null;
async detail(id: string): Promise<T> {
// 这里应该调用实际的API
throw new Error('Not implemented');
}
async create(data: Partial<T>): Promise<T> {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const result = await response.json();
// 创建成功后获取完整数据
if (result.data?.id) {
const created = await this.detail(result.data.id);
if (created) return created;
}
return result.data;
// 这里应该调用实际的API
throw new Error('Not implemented');
}
async update(id: string, data: Partial<T>): Promise<T> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const result = await response.json();
// 更新成功后获取完整数据
const updated = await this.detail(id);
if (updated) return updated;
return result.data;
// 这里应该调用实际的API
throw new Error('Not implemented');
}
async delete(id: string): Promise<void> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
// 这里应该调用实际的API
throw new Error('Not implemented');
}
/**
* 构建查询参数
*/
// 构建查询参数
protected buildQueryParams(params?: P): string {
const query = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
query.append(key, String(value));
}
});
if (!params) return '';
const queryParts = [];
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null) {
queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
}
}
return query.toString();
}
/**
* 模拟延迟(用于 Mock 实现)
*/
protected delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
return queryParts.join('&');
}
}
/**
* 基础 Mock DataSource
* 基础Mock数据源
* 提供通用的Mock数据实现
*/
export abstract class BaseMockDataSource<T, P = any> implements IDataSource<T, P> {
/** Mock标记 */
readonly __MOCK__ = true as const;
/** Mock数据源名称 */
abstract readonly __MOCK_NAME__: string;
/** Mock数据 */
protected abstract mockData: T[];
export class BaseMockDataSource<T, P = any> implements IMockDataSource<T, P> {
protected mockData: T[] = [];
async list(params?: P): Promise<T[]> {
await this.delay(200);
async list(): Promise<T[]> {
return this.mockData;
}
async detail(id: string): Promise<T | null> {
await this.delay(100);
return this.mockData.find(item => (item as any).id === id) || null;
async detail(id: string): Promise<T> {
const item = this.mockData.find((item: any) => item.id === id);
if (!item) {
throw new Error('Item not found');
}
return item;
}
async create(data: Partial<T>): Promise<T> {
await this.delay(300);
const newItem = {
id: `${Date.now()}`,
...data,
id: `${this.mockData.length + 1}`,
...data
} as T;
this.mockData.unshift(newItem);
this.mockData.push(newItem);
return newItem;
}
async update(id: string, data: Partial<T>): Promise<T> {
await this.delay(300);
const index = this.mockData.findIndex(item => (item as any).id === id);
const index = this.mockData.findIndex((item: any) => item.id === id);
if (index === -1) {
throw new Error('Item not found');
}
this.mockData[index] = { ...this.mockData[index], ...data };
this.mockData[index] = {
...this.mockData[index],
...data
};
return this.mockData[index];
}
async delete(id: string): Promise<void> {
await this.delay(200);
this.mockData = this.mockData.filter(item => (item as any).id !== id);
const index = this.mockData.findIndex((item: any) => item.id === id);
if (index === -1) {
throw new Error('Item not found');
}
this.mockData.splice(index, 1);
}
/**
* 模拟延迟
*/
protected delay(ms: number): Promise<void> {
// Mock特定方法
reset(): void {
this.mockData = [];
}
getMockData(): T[] {
return this.mockData;
}
// 延迟方法,用于模拟网络延迟
protected async delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
/**
* DataSource 工厂类
* 负责根据环境变量创建相应的 DataSource 实例
* 数据源工厂类
* 用于创建数据源实例根据环境自动切换Mock和真实API
*/
export class DataSourceFactory {
/**
* 创建 DataSource 实例
* 创建基础 DataSource 实例
* @param apiDataSource API实现类
* @param mockDataSource Mock实现类
* @returns DataSource 实例
@@ -215,7 +142,7 @@ export class DataSourceFactory {
* @param mockDataSource Mock实现类
* @returns DataSource 实例
*/
static createWithMethods<T, P = any, M extends Record<string, any>>({
static createWithMethods<T, M extends Record<string, any>, P = any>({
apiDataSource,
mockDataSource,
}: {
@@ -235,4 +162,4 @@ export const __MOCK__ = useMock;
/**
* 当前数据源类型
*/
export const __DATA_SOURCE_TYPE__ = useMock ? 'mock' : 'api';
export const __DATA_SOURCE_TYPE__ = useMock ? 'mock' : 'api';

View File

@@ -79,8 +79,10 @@ class ApiIndependentSiteDataSource implements IIndependentSiteDataSource {
return result.data;
}
async detail(id: string): Promise<IndependentSite | null> {
return this.fetchSiteConfig(id);
async detail(id: string): Promise<IndependentSite> {
const site = await this.fetchSiteConfig(id);
if (!site) throw new Error('Site not found');
return site;
}
async fetchSiteConfig(id: string): Promise<IndependentSite | null> {
@@ -127,7 +129,7 @@ class ApiIndependentSiteDataSource implements IIndependentSiteDataSource {
* AI注意: 这是Mock实现不是真实业务逻辑
* 仅在REACT_APP_USE_MOCK=true时启用
*/
class MockIndependentSiteDataSource implements IIndependentSiteDataSource, IMockDataSource {
class MockIndependentSiteDataSource implements IIndependentSiteDataSource, IMockDataSource<IndependentSite, SiteQueryParams> {
readonly __MOCK__ = true as const;
readonly __MOCK_NAME__ = 'MockIndependentSiteDataSource';
@@ -179,8 +181,10 @@ class MockIndependentSiteDataSource implements IIndependentSiteDataSource, IMock
return result;
}
async detail(id: string): Promise<IndependentSite | null> {
return this.fetchSiteConfig(id);
async detail(id: string): Promise<IndependentSite> {
const site = await this.fetchSiteConfig(id);
if (!site) throw new Error('Site not found');
return site;
}
async fetchSiteConfig(id: string): Promise<IndependentSite | null> {
@@ -247,6 +251,48 @@ class MockIndependentSiteDataSource implements IIndependentSiteDataSource, IMock
private simulateDelay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Mock特定方法
reset(): void {
this.mockSites = [
{
id: 'site-001',
name: 'Test Store 1',
url: 'https://test-store-1.com',
platform: 'Shopify',
apiKey: 'test-api-key',
apiSecret: 'test-api-secret',
webhookUrl: 'https://webhook.example.com',
theme: 'default',
description: 'Test store for demonstration',
isActive: true,
notes: 'This is a test store',
settings: {
inventorySync: true,
orderSync: true,
pricingSync: true,
autoPublish: false,
currency: 'USD',
language: 'en',
},
shipping: {
freeShipping: true,
freeShippingThreshold: 50,
shippingZones: ['US', 'CA', 'EU'],
},
payment: {
paypal: true,
stripe: true,
creditCard: true,
applePay: false,
},
},
];
}
getMockData(): IndependentSite[] {
return this.mockSites;
}
}
// ============================================
@@ -260,4 +306,4 @@ export const independentSiteDataSource: IIndependentSiteDataSource = useMock
: new ApiIndependentSiteDataSource();
export const __MOCK__ = useMock;
export const __DATA_SOURCE_TYPE__ = useMock ? 'mock' : 'api';
export const __DATA_SOURCE_TYPE__ = useMock ? 'mock' : 'api';

View File

@@ -44,12 +44,14 @@ class ApiLeaderboardDataSource extends BaseDataSource<any, any> implements ILead
}
async fetchLeaderboard(period: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME'): Promise<LeaderboardData> {
// 使用baseUrl属性
const response = await fetch(`${this.baseUrl}?period=${period}`);
if (!response.ok) throw new Error('Failed to fetch leaderboard');
return response.json();
}
async fetchMyRank(period: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME'): Promise<MyRank> {
// 使用baseUrl属性
const response = await fetch(`${this.baseUrl}/my-rank?period=${period}`);
if (!response.ok) throw new Error('Failed to fetch my rank');
return response.json();
@@ -80,6 +82,7 @@ class MockLeaderboardDataSource extends BaseMockDataSource<any, any> implements
}
async fetchLeaderboard(period: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME'): Promise<LeaderboardData> {
// 使用delay方法
await this.delay(500);
return {
revenue: { rankings: this.generateRankings(10, 'revenue'), total: 100 },
@@ -91,6 +94,7 @@ class MockLeaderboardDataSource extends BaseMockDataSource<any, any> implements
}
async fetchMyRank(period: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'ALL_TIME'): Promise<MyRank> {
// 使用delay方法
await this.delay(300);
return {
revenue: { rank: 15, percentile: 85 },
@@ -99,12 +103,17 @@ class MockLeaderboardDataSource extends BaseMockDataSource<any, any> implements
period,
};
}
// 重写delay方法使其可访问
protected async delay(ms: number): Promise<void> {
return super.delay(ms);
}
}
export const leaderboardDataSource = DataSourceFactory.createWithMethods<
any,
any,
ILeaderboardDataSource
ILeaderboardDataSource,
any
>({
apiDataSource: ApiLeaderboardDataSource,
mockDataSource: MockLeaderboardDataSource,
@@ -114,4 +123,4 @@ export const leaderboardDataSource = DataSourceFactory.createWithMethods<
* Mock状态标记
* 用于调试和开发环境识别
*/
export { __MOCK__, __DATA_SOURCE_TYPE__ } from './dataSourceFactory';
export { __MOCK__, __DATA_SOURCE_TYPE__ } from './dataSourceFactory';

View File

@@ -30,6 +30,7 @@ export interface Competitor {
reviewCount: number;
lastUpdated: string;
trackedProducts: string[];
marketShare?: number;
}
export interface IMarketingDataSource {

View File

@@ -1,261 +1,172 @@
/**
* [MOCK-007] 产品模块DataSource
* AI注意: 这是Mock实现不是真实业务逻辑
* 仅在USE_MOCK=true时启用
*/
// 产品数据源
import { Product } from '@/types/product';
import { IDataSource, IMockDataSource } from '@/types/datasource';
import { Product } from '../types/product';
// 产品数据接口
export interface ProductDataSource {
list(params?: {
page?: number;
pageSize?: number;
category?: string;
status?: string;
}): Promise<{ data: Product[]; total: number }>;
get(id: string): Promise<Product | null>;
create(product: Omit<Product, 'id' | 'createdAt' | 'updatedAt'>): Promise<Product>;
update(id: string, product: Partial<Product>): Promise<Product | null>;
delete(id: string): Promise<boolean>;
search(keyword: string): Promise<Product[]>;
getByCategory(category: string): Promise<Product[]>;
updateStatus(id: string, status: string): Promise<Product | null>;
export interface ProfitMonitor {
id: string;
productId: string;
productName: string;
currentPrice: number;
costPrice: number;
profitMargin: number;
salesVolume: number;
totalProfit: number;
roi: number;
trend: 'UP' | 'DOWN' | 'STABLE';
lastUpdated: string;
}
// 产品DataSource实现
export class ProductDataSourceImpl implements ProductDataSource {
async list(params?: {
page?: number;
pageSize?: number;
category?: string;
status?: string;
}): Promise<{ data: Product[]; total: number }> {
export interface ROIAnalysis {
id: string;
productId: string;
productName: string;
investment: number;
revenue: number;
roi: number;
breakEvenPoint: number;
campaignId?: string;
startDate: string;
endDate: string;
status: 'ACTIVE' | 'COMPLETED' | 'PAUSED';
}
// API实现
export class ProductApiDataSource implements IDataSource<Product> {
async list(): Promise<Product[]> {
// 这里应该调用实际的API
// 暂时返回Mock数据
const mockProducts: Product[] = [
{
id: '1',
name: '智能手表',
description: '多功能智能手表,支持心率监测、运动追踪等功能',
price: 199.99,
category: '电子产品',
status: 'active',
stock: 100,
images: ['https://example.com/watch1.jpg', 'https://example.com/watch2.jpg'],
createdAt: new Date('2026-03-01'),
updatedAt: new Date('2026-03-10'),
},
{
id: '2',
name: '无线耳机',
description: '高音质无线耳机,降噪功能',
price: 129.99,
category: '电子产品',
status: 'active',
stock: 200,
images: ['https://example.com/earphone1.jpg', 'https://example.com/earphone2.jpg'],
createdAt: new Date('2026-03-02'),
updatedAt: new Date('2026-03-11'),
},
{
id: '3',
name: '运动鞋',
description: '轻便透气运动鞋',
price: 89.99,
category: '服装',
status: 'active',
stock: 150,
images: ['https://example.com/shoe1.jpg', 'https://example.com/shoe2.jpg'],
createdAt: new Date('2026-03-03'),
updatedAt: new Date('2026-03-12'),
},
];
let filteredProducts = mockProducts;
if (params?.category) {
filteredProducts = filteredProducts.filter(p => p.category === params.category);
}
if (params?.status) {
filteredProducts = filteredProducts.filter(p => p.status === params.status);
}
const page = params?.page || 1;
const pageSize = params?.pageSize || 10;
const start = (page - 1) * pageSize;
const end = start + pageSize;
const paginatedProducts = filteredProducts.slice(start, end);
return {
data: paginatedProducts,
total: filteredProducts.length,
};
return [];
}
async get(id: string): Promise<Product | null> {
async detail(id: string): Promise<Product> {
// 这里应该调用实际的API
// 暂时返回Mock数据
const mockProducts: Product[] = [
{
id: '1',
name: '智能手表',
description: '多功能智能手表,支持心率监测、运动追踪等功能',
price: 199.99,
category: '电子产品',
status: 'active',
stock: 100,
images: ['https://example.com/watch1.jpg', 'https://example.com/watch2.jpg'],
createdAt: new Date('2026-03-01'),
updatedAt: new Date('2026-03-10'),
},
{
id: '2',
name: '无线耳机',
description: '高音质无线耳机,降噪功能',
price: 129.99,
category: '电子产品',
status: 'active',
stock: 200,
images: ['https://example.com/earphone1.jpg', 'https://example.com/earphone2.jpg'],
createdAt: new Date('2026-03-02'),
updatedAt: new Date('2026-03-11'),
},
];
const product = mockProducts.find(p => p.id === id);
return product || null;
throw new Error('Not implemented');
}
async create(product: Omit<Product, 'id' | 'createdAt' | 'updatedAt'>): Promise<Product> {
async create(data: Partial<Product>): Promise<Product> {
// 这里应该调用实际的API
// 暂时返回Mock数据
throw new Error('Not implemented');
}
async update(id: string, data: Partial<Product>): Promise<Product> {
// 这里应该调用实际的API
throw new Error('Not implemented');
}
async delete(id: string): Promise<void> {
// 这里应该调用实际的API
throw new Error('Not implemented');
}
// 额外的方法
async getByCategory(category: string): Promise<Product[]> {
// 这里应该调用实际的API
return [];
}
async updateStatus(id: string, status: 'active' | 'inactive' | 'discontinued'): Promise<Product | null> {
// 这里应该调用实际的API
throw new Error('Not implemented');
}
}
// Mock实现
export class ProductMockDataSource implements IMockDataSource<Product> {
private mockProducts: Product[] = [
{
id: '1',
name: '智能手机',
description: '最新款智能手机',
price: 5999.99,
category: '电子产品',
status: 'active',
stock: 100,
images: ['https://example.com/phone1.jpg', 'https://example.com/phone2.jpg'],
createdAt: new Date('2026-03-01'),
updatedAt: new Date('2026-03-10'),
},
{
id: '2',
name: '笔记本电脑',
description: '高性能笔记本电脑',
price: 8999.99,
category: '电子产品',
status: 'active',
stock: 50,
images: ['https://example.com/laptop1.jpg', 'https://example.com/laptop2.jpg'],
createdAt: new Date('2026-03-02'),
updatedAt: new Date('2026-03-11'),
},
{
id: '3',
name: '运动鞋',
description: '轻便透气运动鞋',
price: 89.99,
category: '服装',
status: 'active',
stock: 150,
images: ['https://example.com/shoe1.jpg', 'https://example.com/shoe2.jpg'],
createdAt: new Date('2026-03-03'),
updatedAt: new Date('2026-03-12'),
},
];
async list(): Promise<Product[]> {
return this.mockProducts;
}
async detail(id: string): Promise<Product> {
const product = this.mockProducts.find(p => p.id === id);
if (!product) {
throw new Error('Product not found');
}
return product;
}
async create(data: Partial<Product>): Promise<Product> {
const newProduct: Product = {
...product,
id: `product_${Date.now()}`,
id: `${this.mockProducts.length + 1}`,
name: data.name || '',
description: data.description || '',
price: data.price || 0,
category: data.category || '',
status: data.status || 'active',
stock: data.stock || 0,
images: data.images || [],
createdAt: new Date(),
updatedAt: new Date(),
};
this.mockProducts.push(newProduct);
return newProduct;
}
async update(id: string, product: Partial<Product>): Promise<Product | null> {
// 这里应该调用实际的API
// 暂时返回Mock数据
const existingProduct = await this.get(id);
if (!existingProduct) {
return null;
async update(id: string, data: Partial<Product>): Promise<Product> {
const index = this.mockProducts.findIndex(p => p.id === id);
if (index === -1) {
throw new Error('Product not found');
}
const updatedProduct: Product = {
...existingProduct,
...product,
this.mockProducts[index] = {
...this.mockProducts[index],
...data,
updatedAt: new Date(),
};
return updatedProduct;
return this.mockProducts[index];
}
async delete(id: string): Promise<boolean> {
// 这里应该调用实际的API
// 暂时返回Mock数据
return true;
}
async search(keyword: string): Promise<Product[]> {
// 这里应该调用实际的API
// 暂时返回Mock数据
const mockProducts: Product[] = [
{
id: '1',
name: '智能手表',
description: '多功能智能手表,支持心率监测、运动追踪等功能',
price: 199.99,
category: '电子产品',
status: 'active',
stock: 100,
images: ['https://example.com/watch1.jpg', 'https://example.com/watch2.jpg'],
createdAt: new Date('2026-03-01'),
updatedAt: new Date('2026-03-10'),
},
{
id: '2',
name: '无线耳机',
description: '高音质无线耳机,降噪功能',
price: 129.99,
category: '电子产品',
status: 'active',
stock: 200,
images: ['https://example.com/earphone1.jpg', 'https://example.com/earphone2.jpg'],
createdAt: new Date('2026-03-02'),
updatedAt: new Date('2026-03-11'),
},
];
return mockProducts.filter(p =>
p.name.toLowerCase().includes(keyword.toLowerCase()) ||
p.description.toLowerCase().includes(keyword.toLowerCase())
);
async delete(id: string): Promise<void> {
const index = this.mockProducts.findIndex(p => p.id === id);
if (index === -1) {
throw new Error('Product not found');
}
this.mockProducts.splice(index, 1);
}
// 额外的方法
async getByCategory(category: string): Promise<Product[]> {
// 这里应该调用实际的API
// 暂时返回Mock数据
const mockProducts: Product[] = [
{
id: '1',
name: '智能手表',
description: '多功能智能手表,支持心率监测、运动追踪等功能',
price: 199.99,
category: '电子产品',
status: 'active',
stock: 100,
images: ['https://example.com/watch1.jpg', 'https://example.com/watch2.jpg'],
createdAt: new Date('2026-03-01'),
updatedAt: new Date('2026-03-10'),
},
{
id: '2',
name: '无线耳机',
description: '高音质无线耳机,降噪功能',
price: 129.99,
category: '电子产品',
status: 'active',
stock: 200,
images: ['https://example.com/earphone1.jpg', 'https://example.com/earphone2.jpg'],
createdAt: new Date('2026-03-02'),
updatedAt: new Date('2026-03-11'),
},
{
id: '3',
name: '运动鞋',
description: '轻便透气运动鞋',
price: 89.99,
category: '服装',
status: 'active',
stock: 150,
images: ['https://example.com/shoe1.jpg', 'https://example.com/shoe2.jpg'],
createdAt: new Date('2026-03-03'),
updatedAt: new Date('2026-03-12'),
},
];
return mockProducts.filter(p => p.category === category);
return this.mockProducts.filter(p => p.category === category);
}
async updateStatus(id: string, status: string): Promise<Product | null> {
// 这里应该调用实际的API
// 暂时返回Mock数据
const existingProduct = await this.get(id);
async updateStatus(id: string, status: 'active' | 'inactive' | 'discontinued'): Promise<Product | null> {
const existingProduct = this.mockProducts.find(p => p.id === id);
if (!existingProduct) {
return null;
}
@@ -266,9 +177,63 @@ export class ProductDataSourceImpl implements ProductDataSource {
updatedAt: new Date(),
};
const index = this.mockProducts.findIndex(p => p.id === id);
if (index !== -1) {
this.mockProducts[index] = updatedProduct;
}
return updatedProduct;
}
// Mock特定方法
reset(): void {
this.mockProducts = [
{
id: '1',
name: '智能手机',
description: '最新款智能手机',
price: 5999.99,
category: '电子产品',
status: 'active',
stock: 100,
images: ['https://example.com/phone1.jpg', 'https://example.com/phone2.jpg'],
createdAt: new Date('2026-03-01'),
updatedAt: new Date('2026-03-10'),
},
{
id: '2',
name: '笔记本电脑',
description: '高性能笔记本电脑',
price: 8999.99,
category: '电子产品',
status: 'active',
stock: 50,
images: ['https://example.com/laptop1.jpg', 'https://example.com/laptop2.jpg'],
createdAt: new Date('2026-03-02'),
updatedAt: new Date('2026-03-11'),
},
{
id: '3',
name: '运动鞋',
description: '轻便透气运动鞋',
price: 89.99,
category: '服装',
status: 'active',
stock: 150,
images: ['https://example.com/shoe1.jpg', 'https://example.com/shoe2.jpg'],
createdAt: new Date('2026-03-03'),
updatedAt: new Date('2026-03-12'),
},
];
}
getMockData(): Product[] {
return this.mockProducts;
}
}
// 导出DataSource实例
export const productDataSource = new ProductDataSourceImpl();
// 数据源导出
const useMock = process.env.REACT_APP_USE_MOCK === 'true';
export const productDataSource = useMock
? new ProductMockDataSource()
: new ProductApiDataSource();

View File

@@ -9,29 +9,169 @@
*/
import {
mockProductSelections,
mockProductSelectionDetail,
mockCreateProductSelection,
mockUpdateProductSelection,
mockDeleteProductSelection,
ProductPool,
SelectionRule,
ListingTask,
AutoListingConfig,
MOCK_CATEGORIES,
MOCK_PLATFORMS,
mockProductPool,
mockSelectionRules,
mockListingTasks,
mockAutoListingConfig,
getMockProductPool,
getMockSelectionRules,
getMockListingTasks,
createMockSelectionRule,
updateMockSelectionRule,
deleteMockSelectionRule,
createMockListingTask,
updateMockListingTask,
deleteMockListingTask,
getMockAutoListingConfig,
updateMockAutoListingConfig,
} from '@/mock/data/productSelection.mock';
const MOCK_CATEGORIES = ['electronics', 'home', 'fashion', 'beauty'];
const MOCK_PLATFORMS = ['Amazon', 'eBay', 'Shopee', 'Lazada'];
const mockProductPool: ProductPool[] = [
{
id: '1',
name: 'Product 1',
category: 'electronics',
platform: 'AMAZON',
roi: 25,
competition_level: 'LOW',
trend: 'UP',
selection_score: 90
},
{
id: '2',
name: 'Product 2',
category: 'home',
platform: 'EBAY',
roi: 15,
competition_level: 'MEDIUM',
trend: 'STABLE',
selection_score: 75
},
{
id: '3',
name: 'Product 3',
category: 'fashion',
platform: 'AMAZON',
roi: 30,
competition_level: 'HIGH',
trend: 'UP',
selection_score: 85
},
{
id: '4',
name: 'Product 4',
category: 'beauty',
platform: 'EBAY',
roi: 20,
competition_level: 'LOW',
trend: 'DOWN',
selection_score: 65
},
{
id: '5',
name: 'Product 5',
category: 'electronics',
platform: 'AMAZON',
roi: 35,
competition_level: 'MEDIUM',
trend: 'UP',
selection_score: 95
},
];
const mockSelectionRules: SelectionRule[] = [
{ id: '1', name: 'Rule 1', description: 'High ROI products', criteria: { minROI: 20 }, enabled: true, selected_count: 10 },
{ id: '2', name: 'Rule 2', description: 'Low competition products', criteria: { maxCompetition: 'MEDIUM' }, enabled: false, selected_count: 5 },
{ id: '3', name: 'Rule 3', description: 'Trending products', criteria: { trend: 'UP' }, enabled: true, selected_count: 15 },
];
const mockListingTasks: ListingTask[] = [
{ id: '1', product_id: '1', status: 'PENDING', progress: 0, created_at: '2026-03-18', updated_at: '2026-03-18' },
{ id: '2', product_id: '2', status: 'PROCESSING', progress: 50, created_at: '2026-03-18', updated_at: '2026-03-18' },
{ id: '3', product_id: '3', status: 'COMPLETED', progress: 100, created_at: '2026-03-18', updated_at: '2026-03-18' },
{ id: '4', product_id: '4', status: 'FAILED', progress: 0, created_at: '2026-03-18', updated_at: '2026-03-18' },
];
const mockAutoListingConfig: AutoListingConfig = {
enabled: false,
max_tasks_per_day: 10,
min_roi: 15,
max_competition: 'MEDIUM',
categories: ['electronics', 'home']
};
const getMockProductPool = (params?: ProductPoolQueryParams): ProductPool[] => {
return mockProductPool;
};
const getMockSelectionRules = (enabled?: boolean): SelectionRule[] => {
return enabled !== undefined ? mockSelectionRules.filter(rule => rule.enabled === enabled) : mockSelectionRules;
};
const getMockListingTasks = (status?: string): ListingTask[] => {
return status ? mockListingTasks.filter(task => task.status === status) : mockListingTasks;
};
const createMockSelectionRule = (data: Partial<SelectionRule>): SelectionRule => {
const newRule: SelectionRule = {
id: `rule-${Date.now()}`,
name: data.name || 'New Rule',
description: data.description || '',
criteria: data.criteria || {},
enabled: data.enabled ?? true,
selected_count: 0,
};
mockSelectionRules.push(newRule);
return newRule;
};
const updateMockSelectionRule = (id: string, data: Partial<SelectionRule>): SelectionRule | null => {
const index = mockSelectionRules.findIndex(rule => rule.id === id);
if (index === -1) return null;
mockSelectionRules[index] = { ...mockSelectionRules[index], ...data };
return mockSelectionRules[index];
};
const deleteMockSelectionRule = (id: string): boolean => {
const index = mockSelectionRules.findIndex(rule => rule.id === id);
if (index === -1) return false;
mockSelectionRules.splice(index, 1);
return true;
};
const createMockListingTask = (data: Partial<ListingTask>): ListingTask => {
const newTask: ListingTask = {
id: `task-${Date.now()}`,
product_id: data.product_id || '',
status: data.status || 'PENDING',
progress: data.progress || 0,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
mockListingTasks.push(newTask);
return newTask;
};
const updateMockListingTask = (id: string, data: Partial<ListingTask>): ListingTask | null => {
const index = mockListingTasks.findIndex(task => task.id === id);
if (index === -1) return null;
mockListingTasks[index] = { ...mockListingTasks[index], ...data };
return mockListingTasks[index];
};
const deleteMockListingTask = (id: string): boolean => {
const index = mockListingTasks.findIndex(task => task.id === id);
if (index === -1) return false;
mockListingTasks.splice(index, 1);
return true;
};
const getMockAutoListingConfig = (): AutoListingConfig => {
return mockAutoListingConfig;
};
const updateMockAutoListingConfig = (data: Partial<AutoListingConfig>): AutoListingConfig => {
Object.assign(mockAutoListingConfig, data);
return mockAutoListingConfig;
};
export interface ProductPoolQueryParams {
category?: string;
min_roi?: number;
@@ -267,7 +407,7 @@ class MockProductSelectionDataSource {
async toggleSelectionRule(id: string): Promise<SelectionRule> {
await this.delay(200);
const rule = mockSelectionRules.find(r => r.id === id);
const rule = mockSelectionRules.find((r: SelectionRule) => r.id === id);
if (!rule) throw new Error('Rule not found');
rule.enabled = !rule.enabled;
return rule;
@@ -276,7 +416,7 @@ class MockProductSelectionDataSource {
async executeSelectionRule(id: string): Promise<{ count: number }> {
await this.delay(1000);
const count = Math.floor(Math.random() * 20) + 5;
const rule = mockSelectionRules.find(r => r.id === id);
const rule = mockSelectionRules.find((r: SelectionRule) => r.id === id);
if (rule) {
rule.selected_count += count;
rule.last_run_at = new Date().toISOString().replace('T', ' ').slice(0, 19);
@@ -344,11 +484,11 @@ class MockProductSelectionDataSource {
const pool = mockProductPool;
return {
totalPool: pool.length,
totalSelected: pool.filter(p => p.selected_at).length,
avgROI: pool.reduce((sum, p) => sum + p.roi, 0) / pool.length,
avgScore: pool.reduce((sum, p) => sum + p.selection_score, 0) / pool.length,
lowCompetition: pool.filter(p => p.competition_level === 'LOW').length,
upTrend: pool.filter(p => p.trend === 'UP').length,
totalSelected: pool.filter((p: ProductPool) => p.selected_at).length,
avgROI: pool.reduce((sum: number, p: ProductPool) => sum + p.roi, 0) / pool.length,
avgScore: pool.reduce((sum: number, p: ProductPool) => sum + p.selection_score, 0) / pool.length,
lowCompetition: pool.filter((p: ProductPool) => p.competition_level === 'LOW').length,
upTrend: pool.filter((p: ProductPool) => p.trend === 'UP').length,
};
}
@@ -363,10 +503,10 @@ class MockProductSelectionDataSource {
const tasks = mockListingTasks;
return {
totalTasks: tasks.length,
pendingTasks: tasks.filter(t => t.status === 'PENDING').length,
processingTasks: tasks.filter(t => t.status === 'PROCESSING').length,
completedTasks: tasks.filter(t => t.status === 'COMPLETED').length,
failedTasks: tasks.filter(t => t.status === 'FAILED').length,
pendingTasks: tasks.filter((t: ListingTask) => t.status === 'PENDING').length,
processingTasks: tasks.filter((t: ListingTask) => t.status === 'PROCESSING').length,
completedTasks: tasks.filter((t: ListingTask) => t.status === 'COMPLETED').length,
failedTasks: tasks.filter((t: ListingTask) => t.status === 'FAILED').length,
};
}
}

View File

@@ -11,11 +11,17 @@ export interface SKUData {
sku_id: string;
sku_name: string;
product_name: string;
platform?: string;
price?: number;
stock?: number;
return_rate: number;
return_count: number;
sales_count: number;
return_reasons: { reason: string; count: number }[];
status: 'NORMAL' | 'WARNING' | 'CRITICAL';
return_count?: number;
sales_count?: number;
return_reasons?: { reason: string; count: number }[];
auto_removed_reason?: string;
last_updated?: string;
created_at?: string;
status: 'NORMAL' | 'WARNING' | 'CRITICAL' | 'ACTIVE' | 'INACTIVE' | 'AUTO_REMOVED';
createdAt: string;
updatedAt: string;
}
@@ -30,8 +36,14 @@ export interface ReturnData {
order_id: string;
return_reason: string;
return_type: 'REFUND' | 'EXCHANGE' | 'RETURN';
status: 'PENDING' | 'APPROVED' | 'PROCESSING' | 'COMPLETED' | 'REJECTED';
status: 'PENDING' | 'APPROVED' | 'PROCESSING' | 'COMPLETED' | 'REJECTED' | 'HIGH_RISK';
amount: number;
return_rate?: number;
total_orders?: number;
total_returns?: number;
return_amount?: number;
avg_processing_time?: number;
return_reasons?: string[];
created_at: string;
updated_at: string;
}
@@ -45,7 +57,7 @@ export interface ReturnTrend {
export interface IReturnDataSource {
fetchSKUData(params?: { status?: string; shop_id?: string }): Promise<SKUData[]>;
updateSKUStatus(id: string, status: 'NORMAL' | 'WARNING' | 'CRITICAL'): Promise<SKUData>;
updateSKUStatus(id: string, status: 'NORMAL' | 'WARNING' | 'CRITICAL' | 'ACTIVE' | 'INACTIVE' | 'AUTO_REMOVED'): Promise<SKUData>;
fetchReturns(params?: { status?: string; shop_id?: string; sku_id?: string }): Promise<ReturnData[]>;
processReturn(id: string, action: 'APPROVE' | 'REJECT'): Promise<ReturnData>;
@@ -134,7 +146,7 @@ class MockReturnDataSource implements IReturnDataSource {
return result;
}
async updateSKUStatus(id: string, status: 'NORMAL' | 'WARNING' | 'CRITICAL'): Promise<SKUData> {
async updateSKUStatus(id: string, status: 'NORMAL' | 'WARNING' | 'CRITICAL' | 'ACTIVE' | 'INACTIVE' | 'AUTO_REMOVED'): Promise<SKUData> {
await new Promise(resolve => setTimeout(resolve, 300));
const index = this.skuData.findIndex(s => s.id === id);
if (index === -1) throw new Error('SKU not found');
@@ -184,7 +196,7 @@ class ApiReturnDataSource implements IReturnDataSource {
return response.json();
}
async updateSKUStatus(id: string, status: 'NORMAL' | 'WARNING' | 'CRITICAL'): Promise<SKUData> {
async updateSKUStatus(id: string, status: 'NORMAL' | 'WARNING' | 'CRITICAL' | 'ACTIVE' | 'INACTIVE' | 'AUTO_REMOVED'): Promise<SKUData> {
const response = await fetch(`${this.baseUrl}/sku/${id}/status`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },

View File

@@ -10,16 +10,22 @@ export interface CostTemplate {
category: string;
platform: string;
costItems: CostItem[];
costs?: CostItem[];
isActive: boolean;
isDefault?: boolean;
description?: string;
createdAt: string;
updatedAt: string;
}
export interface CostItem {
id?: string;
name: string;
type: 'fixed' | 'percentage';
value: number;
currency: string;
applyTo?: string;
description?: string;
}
export interface WinNode {
@@ -30,9 +36,19 @@ export interface WinNode {
profileDir: string;
proxy: ProxyConfig;
fingerprintPolicy: FingerprintPolicy;
status: 'active' | 'inactive' | 'error';
status: 'active' | 'inactive' | 'error' | 'ONLINE' | 'OFFLINE' | 'BUSY';
lastUsed?: string;
createdAt: string;
maxConcurrent?: number;
host?: string;
port?: number;
shopName?: string;
shopId?: string;
currentTasks?: number;
autoRestart?: boolean;
lastHeartbeat?: string;
cpuUsage?: number;
memoryUsage?: number;
}
export interface ProxyConfig {
@@ -57,7 +73,12 @@ export interface ExchangeRate {
toCurrency: string;
rate: number;
source: string;
lastUpdated?: string;
updatedAt: string;
updatedBy?: string;
effectiveDate?: string;
expiryDate?: string;
isActive?: boolean;
}
export interface PlatformAccount {
@@ -65,31 +86,67 @@ export interface PlatformAccount {
platform: string;
accountName: string;
accountId: string;
sellerId?: string;
accessToken?: string;
marketplace?: string;
region?: string;
status: 'active' | 'inactive' | 'expired';
token?: string;
refreshToken?: string;
expiresAt?: string;
tokenExpiry?: string;
autoSync?: boolean;
syncInterval?: number;
shopId?: string;
shopName?: string;
lastSync?: string;
apiConnected?: boolean;
createdAt: string;
updatedAt?: string;
}
export interface SystemSetting {
key: string;
value: any;
category: string;
description: string;
description?: string;
createdAt: string;
updatedAt: string;
}
export interface Role {
id: string;
name: string;
code: string;
description?: string;
permissions: string[];
status: 'active' | 'inactive';
createdAt: string;
updatedAt: string;
}
export interface Permission {
id: string;
name: string;
code: string;
description?: string;
category: string;
createdAt: string;
updatedAt: string;
children?: Permission[];
}
export interface User {
id: string;
username: string;
nickname?: string;
email: string;
phone?: string;
role: string;
status: 'active' | 'inactive' | 'pending';
parentId?: string;
createdAt: string;
lastLoginAt?: string;
lastLogin?: string; // 与 lastLoginAt 同义
}
export interface ISettingsDataSource {
@@ -244,15 +301,15 @@ class MockSettingsDataSource implements ISettingsDataSource {
async fetchSystemSettings(): Promise<SystemSetting[]> {
return [
{ key: 'default_currency', value: 'USD', category: 'general', description: 'Default currency for pricing', updatedAt: '2026-03-01 10:00:00' },
{ key: 'profit_threshold_b2b', value: 15, category: 'pricing', description: 'Minimum profit margin for B2B', updatedAt: '2026-03-01 10:00:00' },
{ key: 'profit_threshold_b2c', value: 20, category: 'pricing', description: 'Minimum profit margin for B2C', updatedAt: '2026-03-01 10:00:00' },
{ key: 'auto_sync_enabled', value: true, category: 'sync', description: 'Enable automatic inventory sync', updatedAt: '2026-03-01 10:00:00' },
{ key: 'default_currency', value: 'USD', category: 'general', description: 'Default currency for pricing', createdAt: '2026-03-01 10:00:00', updatedAt: '2026-03-01 10:00:00' },
{ key: 'profit_threshold_b2b', value: 15, category: 'pricing', description: 'Minimum profit margin for B2B', createdAt: '2026-03-01 10:00:00', updatedAt: '2026-03-01 10:00:00' },
{ key: 'profit_threshold_b2c', value: 20, category: 'pricing', description: 'Minimum profit margin for B2C', createdAt: '2026-03-01 10:00:00', updatedAt: '2026-03-01 10:00:00' },
{ key: 'auto_sync_enabled', value: true, category: 'sync', description: 'Enable automatic inventory sync', createdAt: '2026-03-01 10:00:00', updatedAt: '2026-03-01 10:00:00' },
];
}
async updateSystemSetting(key: string, value: any): Promise<SystemSetting> {
return { key, value, category: 'general', description: '', updatedAt: new Date().toISOString() };
return { key, value, category: 'general', description: '', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() };
}
async fetchUsers(): Promise<User[]> {

View File

@@ -6,6 +6,7 @@ export interface Supplier {
id: string;
name: string;
contact: string;
contactName?: string;
email: string;
phone: string;
address: string;
@@ -14,19 +15,24 @@ export interface Supplier {
status: 'active' | 'inactive' | 'pending';
paymentTerms: string;
leadTime: number;
minOrder?: number;
minOrderQty: number;
notes?: string;
createdAt: string;
}
export interface SupplierProduct {
id: string;
supplierId: string;
supplierId?: string;
sku: string;
productName: string;
costPrice: number;
name: string;
productName?: string;
price: number;
costPrice?: number;
stock: number;
moq: number;
leadTime: number;
quality: 'A' | 'B' | 'C';
quality?: 'A' | 'B' | 'C';
}
export interface ISuppliersDataSource {
@@ -59,9 +65,9 @@ class MockSuppliersDataSource implements ISuppliersDataSource {
async fetchSupplierProducts(supplierId: string): Promise<SupplierProduct[]> {
return [
{ id: 'sp_001', supplierId, sku: 'SKU-001', productName: 'Wireless Headphones', costPrice: 12.50, moq: 100, leadTime: 15, quality: 'A' },
{ id: 'sp_002', supplierId, sku: 'SKU-002', productName: 'USB-C Cable', costPrice: 1.80, moq: 500, leadTime: 10, quality: 'A' },
{ id: 'sp_003', supplierId, sku: 'SKU-003', productName: 'Phone Case', costPrice: 2.50, moq: 200, leadTime: 12, quality: 'B' },
{ id: 'sp_001', supplierId, sku: 'SKU-001', name: 'Wireless Headphones', productName: 'Wireless Headphones', price: 12.50, costPrice: 12.50, stock: 1000, moq: 100, leadTime: 15, quality: 'A' },
{ id: 'sp_002', supplierId, sku: 'SKU-002', name: 'USB-C Cable', productName: 'USB-C Cable', price: 1.80, costPrice: 1.80, stock: 5000, moq: 500, leadTime: 10, quality: 'A' },
{ id: 'sp_003', supplierId, sku: 'SKU-003', name: 'Phone Case', productName: 'Phone Case', price: 2.50, costPrice: 2.50, stock: 2000, moq: 200, leadTime: 12, quality: 'B' },
];
}
}

View File

@@ -4,16 +4,28 @@
export interface Task {
id: string;
type: 'sync' | 'report' | 'import' | 'export' | 'automation';
taskId?: string;
name: string;
status: 'pending' | 'running' | 'completed' | 'failed';
type: 'PRODUCT_SYNC' | 'ORDER_PROCESS' | 'DATA_ANALYSIS' | 'AD_OPTIMIZATION' | 'INVENTORY_CHECK' | 'CUSTOM' | 'sync' | 'report' | 'import' | 'export' | 'automation';
status: 'PENDING' | 'RUNNING' | 'PAUSED' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'pending' | 'running' | 'completed' | 'failed';
priority?: 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT';
progress: number;
total: number;
result?: string;
total?: number;
result?: string | any;
error?: string;
errorMessage?: string;
startedAt?: string;
completedAt?: string;
createdAt: string;
estimatedDuration?: number;
actualDuration?: number;
retryCount?: number;
maxRetries?: number;
shopId?: string;
shopName?: string;
createdBy?: string;
scheduleType?: 'MANUAL' | 'SCHEDULED' | 'TRIGGERED';
scheduleTime?: string;
}
export interface ITaskCenterDataSource {

View File

@@ -12,11 +12,17 @@ export interface UserAsset {
userId: string;
userName: string;
email: string;
userEmail?: string; // 与 email 同义
memberLevel: MemberLevel;
points: number;
memberScore?: number; // 与 points 同义
totalSpent: number;
availableBalance: number;
availablePoints?: number; // 与 availableBalance 同义
frozenBalance: number;
frozenPoints?: number; // 与 frozenBalance 同义
cashbackBalance?: number;
couponCount?: number;
totalOrders: number;
joinDate: string;
lastActiveDate: string;
@@ -35,6 +41,18 @@ export interface MemberLevelConfig {
icon: string;
}
export type PointsSourceType =
| 'PURCHASE'
| 'REVIEW'
| 'REFERRAL'
| 'PROMOTION'
| 'ADMIN_ADJUST'
| 'EXPIRED'
| 'REFUND'
| 'MANUAL_DEDUCT';
export type PointsStatus = 'PENDING' | 'CONFIRMED' | 'FROZEN' | 'EXPIRED' | 'CANCELLED';
export interface PointsRecord {
id: string;
userId: string;
@@ -45,6 +63,16 @@ export interface PointsRecord {
source: string;
description: string;
createdAt: string;
// 额外字段,用于 PointsManage 页面
tenantId?: string;
shopId?: string;
traceId?: string;
businessType?: 'TOC' | 'TOB';
points?: number; // 与 amount 同义
sourceType?: string; // 与 source 同义
status?: 'PENDING' | 'CONFIRMED' | 'FROZEN' | 'EXPIRED' | 'CANCELLED';
expiredAt?: string;
sourceId?: string;
}
export interface IUserAssetDataSource {
@@ -103,8 +131,44 @@ class MockUserAssetDataSource implements IUserAssetDataSource {
];
private pointsRecords: PointsRecord[] = [
{ id: '1', userId: 'user_001', userName: 'John Doe', type: 'EARN', amount: 100, balance: 5000, source: 'Purchase', description: 'Order #ORD-001', createdAt: '2026-03-15' },
{ id: '2', userId: 'user_001', userName: 'John Doe', type: 'REDEEM', amount: -50, balance: 4950, source: 'Redemption', description: 'Coupon redemption', createdAt: '2026-03-16' },
{
id: '1',
userId: 'user_001',
userName: 'John Doe',
type: 'EARN',
amount: 100,
balance: 5000,
source: 'Purchase',
description: 'Order #ORD-001',
createdAt: '2026-03-15',
// 额外字段
tenantId: 'T001',
shopId: 'S001',
traceId: 'TR001',
businessType: 'TOC',
points: 100,
sourceType: 'PURCHASE',
status: 'CONFIRMED'
},
{
id: '2',
userId: 'user_001',
userName: 'John Doe',
type: 'REDEEM',
amount: -50,
balance: 4950,
source: 'Redemption',
description: 'Coupon redemption',
createdAt: '2026-03-16',
// 额外字段
tenantId: 'T001',
shopId: 'S001',
traceId: 'TR002',
businessType: 'TOC',
points: -50,
sourceType: 'MANUAL_DEDUCT',
status: 'CONFIRMED'
},
];
async fetchUserAssets(params?: { memberLevel?: string; status?: string; search?: string }): Promise<UserAsset[]> {