/** * [MOCK] 多店铺报表数据源 * AI注意: 这是Mock实现,不是真实业务逻辑 * 仅在USE_MOCK=true时启用 */ export type ReportType = 'SALES' | 'PROFIT' | 'INVENTORY' | 'ORDER' | 'AD' | 'REFUND'; export type TimeDimension = 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'QUARTERLY' | 'YEARLY'; export interface ShopInfo { id: string; tenant_id: string; name: string; platform: string; status: 'ACTIVE' | 'INACTIVE' | 'SUSPENDED'; region: string; currency: string; } export interface AggregatedReport { id: string; tenant_id: string; report_type: ReportType; time_dimension: TimeDimension; start_date: string; end_date: string; summary: { total_shops: number; active_shops: number; total_revenue: number; total_cost: number; total_profit: number; total_orders: number; total_quantity: number; avg_order_value: number; profit_margin: number; }; shop_breakdown: Array<{ shop_id: string; shop_name: string; platform: string; revenue: number; cost: number; profit: number; orders: number; quantity: number; avg_order_value: number; profit_margin: number; contribution_rate: number; }>; time_series: Array<{ date: string; revenue: number; profit: number; orders: number; shop_count: number; }>; platform_breakdown: Array<{ platform: string; revenue: number; profit: number; orders: number; shop_count: number; }>; rankings: { top_revenue_shops: Array<{ shop_id: string; shop_name: string; value: number }>; top_profit_shops: Array<{ shop_id: string; shop_name: string; value: number }>; top_growth_shops: Array<{ shop_id: string; shop_name: string; growth_rate: number }>; }; comparison: { revenue_growth: number; profit_growth: number; order_growth: number; shop_growth: number; }; created_at: string; updated_at: string; } export interface ReportSubscription { id: string; tenant_id: string; user_id: string; report_type: ReportType; time_dimension: TimeDimension; frequency: 'DAILY' | 'WEEKLY' | 'MONTHLY'; recipients: string[]; enabled: boolean; last_sent_at?: string; next_send_at?: string; created_at: string; updated_at: string; } export interface IShopReportDataSource { generateReport(params: { tenant_id: string; report_type: ReportType; time_dimension: TimeDimension; start_date: string; end_date: string; shop_ids?: string[]; platforms?: string[]; }): Promise; fetchHistoricalReports(tenantId: string, reportType?: ReportType, limit?: number): Promise; fetchReportById(reportId: string): Promise; createSubscription(params: { tenant_id: string; report_type: ReportType; time_dimension: TimeDimension; frequency: 'DAILY' | 'WEEKLY' | 'MONTHLY'; recipients: string[]; }): Promise; fetchUserSubscriptions(tenantId: string): Promise; } const REPORT_TYPE_NAMES: Record = { SALES: '销售报表', PROFIT: '利润报表', INVENTORY: '库存报表', ORDER: '订单报表', AD: '广告报表', REFUND: '退款报表', }; const PLATFORM_NAMES: Record = { AMAZON: '亚马逊', EBAY: 'eBay', SHOPEE: 'Shopee', TIKTOK: 'TikTok', TEMU: 'Temu', LAZADA: 'Lazada', }; class MockShopReportDataSource implements IShopReportDataSource { private reports: AggregatedReport[] = []; private subscriptions: ReportSubscription[] = []; constructor() { this.initMockData(); } private initMockData() { const shops = [ { id: 'shop-001', name: '店铺A', platform: 'AMAZON' }, { id: 'shop-002', name: '店铺B', platform: 'EBAY' }, { id: 'shop-003', name: '店铺C', platform: 'SHOPEE' }, { id: 'shop-004', name: '店铺D', platform: 'AMAZON' }, { id: 'shop-005', name: '店铺E', platform: 'TIKTOK' }, ]; // 生成模拟报表 const reportTypes: ReportType[] = ['SALES', 'PROFIT', 'ORDER']; const timeDimensions: TimeDimension[] = ['DAILY', 'WEEKLY', 'MONTHLY']; for (let i = 0; i < 5; i++) { const reportType = reportTypes[i % reportTypes.length]; const timeDimension = timeDimensions[i % timeDimensions.length]; const totalRevenue = 50000 + Math.random() * 100000; const totalProfit = totalRevenue * (0.15 + Math.random() * 0.15); const shopBreakdown = shops.map((shop, index) => { const shopRevenue = totalRevenue / shops.length * (0.8 + Math.random() * 0.4); const shopProfit = shopRevenue * (0.1 + Math.random() * 0.2); return { shop_id: shop.id, shop_name: shop.name, platform: shop.platform, revenue: shopRevenue, cost: shopRevenue - shopProfit, profit: shopProfit, orders: Math.floor(100 + Math.random() * 500), quantity: Math.floor(500 + Math.random() * 2000), avg_order_value: shopRevenue / (100 + Math.random() * 500), profit_margin: (shopProfit / shopRevenue) * 100, contribution_rate: (shopRevenue / totalRevenue) * 100, }; }).sort((a, b) => b.revenue - a.revenue); const timeSeries = Array.from({ length: 7 }, (_, day) => ({ date: new Date(Date.now() - (6 - day) * 86400000).toISOString().split('T')[0], revenue: totalRevenue / 7 * (0.8 + Math.random() * 0.4), profit: totalProfit / 7 * (0.8 + Math.random() * 0.4), orders: Math.floor((100 + Math.random() * 500) / 7), shop_count: shops.length, })); const platformBreakdown = Object.entries( shops.reduce((acc, shop) => { if (!acc[shop.platform]) { acc[shop.platform] = { revenue: 0, profit: 0, orders: 0, shop_count: 0 }; } const shopData = shopBreakdown.find(s => s.shop_id === shop.id); if (shopData) { acc[shop.platform].revenue += shopData.revenue; acc[shop.platform].profit += shopData.profit; acc[shop.platform].orders += shopData.orders; acc[shop.platform].shop_count += 1; } return acc; }, {} as Record) ).map(([platform, data]) => ({ platform, ...data })); this.reports.push({ id: `RPT-${i + 1}`, tenant_id: 'tenant-001', report_type: reportType, time_dimension: timeDimension, start_date: new Date(Date.now() - 7 * 86400000).toISOString().split('T')[0], end_date: new Date().toISOString().split('T')[0], summary: { total_shops: shops.length, active_shops: shops.length, total_revenue: totalRevenue, total_cost: totalRevenue - totalProfit, total_profit: totalProfit, total_orders: shopBreakdown.reduce((sum, s) => sum + s.orders, 0), total_quantity: shopBreakdown.reduce((sum, s) => sum + s.quantity, 0), avg_order_value: totalRevenue / shopBreakdown.reduce((sum, s) => sum + s.orders, 0), profit_margin: (totalProfit / totalRevenue) * 100, }, shop_breakdown: shopBreakdown, time_series: timeSeries, platform_breakdown: platformBreakdown, rankings: { top_revenue_shops: shopBreakdown.slice(0, 3).map(s => ({ shop_id: s.shop_id, shop_name: s.shop_name, value: s.revenue, })), top_profit_shops: shopBreakdown.slice(0, 3).map(s => ({ shop_id: s.shop_id, shop_name: s.shop_name, value: s.profit, })), top_growth_shops: shopBreakdown.slice(0, 3).map((s, i) => ({ shop_id: s.shop_id, shop_name: s.shop_name, growth_rate: 15 + Math.random() * 20 - i * 5, })), }, comparison: { revenue_growth: 12.5 + Math.random() * 10, profit_growth: 8.3 + Math.random() * 8, order_growth: 5.7 + Math.random() * 5, shop_growth: 2.1 + Math.random() * 3, }, created_at: new Date(Date.now() - i * 86400000).toISOString(), updated_at: new Date(Date.now() - i * 86400000).toISOString(), }); } } async generateReport(params: { tenant_id: string; report_type: ReportType; time_dimension: TimeDimension; start_date: string; end_date: string; shop_ids?: string[]; platforms?: string[]; }): Promise { await this.delay(1000); const report = this.reports[0]; return { ...report, id: `RPT-${Date.now()}`, report_type: params.report_type, time_dimension: params.time_dimension, start_date: params.start_date, end_date: params.end_date, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }; } async fetchHistoricalReports(tenantId: string, reportType?: ReportType, limit: number = 10): Promise { await this.delay(300); let reports = this.reports.filter(r => r.tenant_id === tenantId); if (reportType) { reports = reports.filter(r => r.report_type === reportType); } return reports.slice(0, limit); } async fetchReportById(reportId: string): Promise { await this.delay(200); return this.reports.find(r => r.id === reportId) || null; } async createSubscription(params: { tenant_id: string; report_type: ReportType; time_dimension: TimeDimension; frequency: 'DAILY' | 'WEEKLY' | 'MONTHLY'; recipients: string[]; }): Promise { await this.delay(300); const subscription: ReportSubscription = { id: `SUB-${Date.now()}`, tenant_id: params.tenant_id, user_id: 'user-001', report_type: params.report_type, time_dimension: params.time_dimension, frequency: params.frequency, recipients: params.recipients, enabled: true, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }; this.subscriptions.push(subscription); return subscription; } async fetchUserSubscriptions(tenantId: string): Promise { await this.delay(200); return this.subscriptions.filter(s => s.tenant_id === tenantId); } private delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } } class ApiShopReportDataSource implements IShopReportDataSource { private baseUrl = '/api/shop-reports'; async generateReport(params: { tenant_id: string; report_type: ReportType; time_dimension: TimeDimension; start_date: string; end_date: string; shop_ids?: string[]; platforms?: string[]; }): Promise { const response = await fetch(`${this.baseUrl}/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); if (!response.ok) throw new Error('Failed to generate report'); const data = await response.json(); return data.data; } async fetchHistoricalReports(tenantId: string, reportType?: ReportType, limit?: number): Promise { const params = new URLSearchParams({ tenant_id: tenantId }); if (reportType) params.append('report_type', reportType); if (limit) params.append('limit', limit.toString()); const response = await fetch(`${this.baseUrl}/history?${params}`); if (!response.ok) throw new Error('Failed to fetch historical reports'); const data = await response.json(); return data.data; } async fetchReportById(reportId: string): Promise { const response = await fetch(`${this.baseUrl}/${reportId}`); if (!response.ok) throw new Error('Failed to fetch report'); const data = await response.json(); return data.data; } async createSubscription(params: { tenant_id: string; report_type: ReportType; time_dimension: TimeDimension; frequency: 'DAILY' | 'WEEKLY' | 'MONTHLY'; recipients: string[]; }): Promise { const response = await fetch(`${this.baseUrl}/subscriptions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); if (!response.ok) throw new Error('Failed to create subscription'); const data = await response.json(); return data.data; } async fetchUserSubscriptions(tenantId: string): Promise { const response = await fetch(`${this.baseUrl}/subscriptions/my?tenant_id=${tenantId}`); if (!response.ok) throw new Error('Failed to fetch subscriptions'); const data = await response.json(); return data.data; } } const useMock = process.env.REACT_APP_USE_MOCK === 'true'; export const shopReportDataSource: IShopReportDataSource = useMock ? new MockShopReportDataSource() : new ApiShopReportDataSource(); export { REPORT_TYPE_NAMES, PLATFORM_NAMES };