2026-03-19 01:39:34 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* [MOCK-AFTER-001] AfterSales数据源抽象层
|
|
|
|
|
|
* AI注意: 这是数据源抽象层,业务组件通过此层获取数据
|
|
|
|
|
|
* 仅在REACT_APP_USE_MOCK=true时启用Mock
|
|
|
|
|
|
*
|
|
|
|
|
|
* @module services/afterSalesDataSource
|
|
|
|
|
|
* @created 2026-03-19
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2026-03-20 17:53:46 +08:00
|
|
|
|
import { IDataSource } from '@/types/datasource';
|
2026-03-19 01:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 类型定义
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
export interface OrderItem {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
skuId: string;
|
|
|
|
|
|
productName: string;
|
|
|
|
|
|
quantity: number;
|
|
|
|
|
|
unitPrice: number;
|
|
|
|
|
|
totalPrice: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReturnApplication {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
orderId: string;
|
|
|
|
|
|
returnReason: string;
|
|
|
|
|
|
returnDescription: string;
|
|
|
|
|
|
returnItems: string[];
|
|
|
|
|
|
images: string[];
|
|
|
|
|
|
contactPhone: string;
|
|
|
|
|
|
status: 'PENDING' | 'APPROVED' | 'REJECTED' | 'PROCESSING' | 'COMPLETED' | 'CANCELLED';
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AfterSalesQueryParams {
|
|
|
|
|
|
orderId?: string;
|
|
|
|
|
|
status?: string;
|
|
|
|
|
|
startDate?: string;
|
|
|
|
|
|
endDate?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// AfterSales专用接口
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
export interface IAfterSalesDataSource {
|
|
|
|
|
|
fetchOrderItems(orderId: string): Promise<OrderItem[]>;
|
|
|
|
|
|
submitReturn(data: Partial<ReturnApplication>): Promise<ReturnApplication>;
|
|
|
|
|
|
fetchReturnApplications(params?: AfterSalesQueryParams): Promise<ReturnApplication[]>;
|
|
|
|
|
|
fetchReturnDetail(id: string): Promise<ReturnApplication | null>;
|
|
|
|
|
|
updateReturnStatus(id: string, status: string): Promise<ReturnApplication>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// API实现
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
class ApiAfterSalesDataSource implements IAfterSalesDataSource {
|
|
|
|
|
|
async fetchOrderItems(orderId: string): Promise<OrderItem[]> {
|
|
|
|
|
|
const response = await fetch(`/api/v1/orders/${orderId}/items`);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async submitReturn(data: Partial<ReturnApplication>): Promise<ReturnApplication> {
|
|
|
|
|
|
const response = await fetch('/api/v1/returns', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify(data),
|
|
|
|
|
|
});
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fetchReturnApplications(params?: AfterSalesQueryParams): Promise<ReturnApplication[]> {
|
|
|
|
|
|
const query = new URLSearchParams(params as any).toString();
|
|
|
|
|
|
const response = await fetch(`/api/v1/returns?${query}`);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fetchReturnDetail(id: string): Promise<ReturnApplication | null> {
|
|
|
|
|
|
const response = await fetch(`/api/v1/returns/${id}`);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateReturnStatus(id: string, status: string): Promise<ReturnApplication> {
|
|
|
|
|
|
const response = await fetch(`/api/v1/returns/${id}/status`, {
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify({ status }),
|
|
|
|
|
|
});
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// Mock实现
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* [MOCK] AfterSales Mock数据源
|
|
|
|
|
|
* AI注意: 这是Mock实现,不是真实业务逻辑
|
|
|
|
|
|
* 仅在REACT_APP_USE_MOCK=true时启用
|
|
|
|
|
|
*/
|
2026-03-20 17:53:46 +08:00
|
|
|
|
class MockAfterSalesDataSource implements IAfterSalesDataSource {
|
2026-03-19 01:39:34 +08:00
|
|
|
|
readonly __MOCK__ = true as const;
|
|
|
|
|
|
readonly __MOCK_NAME__ = 'MockAfterSalesDataSource';
|
|
|
|
|
|
|
|
|
|
|
|
private mockOrderItems: Record<string, OrderItem[]> = {
|
|
|
|
|
|
'order-001': [
|
|
|
|
|
|
{ id: 'item_001', skuId: 'SKU_001', productName: 'Wireless Bluetooth Headphones', quantity: 2, unitPrice: 29.99, totalPrice: 59.98 },
|
|
|
|
|
|
{ id: 'item_002', skuId: 'SKU_002', productName: 'USB-C Charging Cable', quantity: 5, unitPrice: 4.99, totalPrice: 24.95 },
|
|
|
|
|
|
],
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private mockReturns: ReturnApplication[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
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',
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
async fetchOrderItems(orderId: string): Promise<OrderItem[]> {
|
|
|
|
|
|
await this.simulateDelay(500);
|
|
|
|
|
|
return this.mockOrderItems[orderId] || [
|
|
|
|
|
|
{ id: 'item_001', skuId: 'SKU_001', productName: 'Wireless Bluetooth Headphones', quantity: 2, unitPrice: 29.99, totalPrice: 59.98 },
|
|
|
|
|
|
{ id: 'item_002', skuId: 'SKU_002', productName: 'USB-C Charging Cable', quantity: 5, unitPrice: 4.99, totalPrice: 24.95 },
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async submitReturn(data: Partial<ReturnApplication>): Promise<ReturnApplication> {
|
|
|
|
|
|
await this.simulateDelay(1000);
|
|
|
|
|
|
const newReturn: ReturnApplication = {
|
|
|
|
|
|
id: `RET-${Date.now()}`,
|
|
|
|
|
|
orderId: data.orderId || '',
|
|
|
|
|
|
returnReason: data.returnReason || '',
|
|
|
|
|
|
returnDescription: data.returnDescription || '',
|
|
|
|
|
|
returnItems: data.returnItems || [],
|
|
|
|
|
|
images: data.images || [],
|
|
|
|
|
|
contactPhone: data.contactPhone || '',
|
|
|
|
|
|
status: 'PENDING',
|
|
|
|
|
|
createdAt: new Date().toISOString(),
|
|
|
|
|
|
updatedAt: new Date().toISOString(),
|
|
|
|
|
|
};
|
|
|
|
|
|
this.mockReturns.push(newReturn);
|
|
|
|
|
|
return newReturn;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fetchReturnApplications(params?: AfterSalesQueryParams): Promise<ReturnApplication[]> {
|
|
|
|
|
|
await this.simulateDelay(300);
|
|
|
|
|
|
let result = [...this.mockReturns];
|
|
|
|
|
|
if (params?.orderId) {
|
|
|
|
|
|
result = result.filter(r => r.orderId === params.orderId);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (params?.status) {
|
|
|
|
|
|
result = result.filter(r => r.status === params.status);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fetchReturnDetail(id: string): Promise<ReturnApplication | null> {
|
|
|
|
|
|
await this.simulateDelay(200);
|
|
|
|
|
|
const ret = this.mockReturns.find(r => r.id === id);
|
|
|
|
|
|
return ret ? { ...ret } : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateReturnStatus(id: string, status: string): Promise<ReturnApplication> {
|
|
|
|
|
|
await this.simulateDelay(300);
|
|
|
|
|
|
const index = this.mockReturns.findIndex(r => r.id === id);
|
|
|
|
|
|
if (index === -1) throw new Error('Return application not found');
|
|
|
|
|
|
this.mockReturns[index] = {
|
|
|
|
|
|
...this.mockReturns[index],
|
|
|
|
|
|
status: status as ReturnApplication['status'],
|
|
|
|
|
|
updatedAt: new Date().toISOString(),
|
|
|
|
|
|
};
|
|
|
|
|
|
return this.mockReturns[index];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private simulateDelay(ms: number): Promise<void> {
|
|
|
|
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
|
|
}
|
2026-03-20 17:53:46 +08:00
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 导出数据源实例
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
const useMock = process.env.REACT_APP_USE_MOCK === 'true';
|
|
|
|
|
|
|
|
|
|
|
|
export const afterSalesDataSource: IAfterSalesDataSource = useMock
|
|
|
|
|
|
? new MockAfterSalesDataSource()
|
|
|
|
|
|
: new ApiAfterSalesDataSource();
|
|
|
|
|
|
|
|
|
|
|
|
export const __MOCK__ = useMock;
|
|
|
|
|
|
export const __DATA_SOURCE_TYPE__ = useMock ? 'mock' : 'api';
|