From 0dac26d7818731ff11f28789048cdf98052ff0cb Mon Sep 17 00:00:00 2001 From: wurenzhi Date: Thu, 19 Mar 2026 01:39:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0MSW=E6=A8=A1=E6=8B=9F?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=92=8C=E6=95=B0=E6=8D=AE=E6=BA=90=E9=9B=86?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 重构页面组件移除冗余Layout组件 feat: 实现WebSocket和事件总线系统 feat: 添加队列和调度系统 docs: 更新架构文档和服务映射 style: 清理重复接口定义使用数据源 chore: 更新依赖项配置 feat: 添加运行时系统和领域引导 ci: 配置ESLint边界检查规则 build: 添加Redis和WebSocket依赖 test: 添加MSW浏览器环境入口 perf: 优化数据获取逻辑使用统一数据源 fix: 修复类型定义和状态管理问题 --- .trae/rules/project-specific-rules.md | 78 + agent4.code-workspace | 11 + client/src/pages/Billing/index.tsx | 277 + client/src/pages/EventLog/index.tsx | 176 + client/src/pages/TaskCenter/index.tsx | 233 + dashboard/.env.development | 23 + dashboard/.env.production | 23 + dashboard/package.json | 3 + dashboard/src/.umi/appData.json | 7339 ++++++++++++++--- dashboard/src/.umi/core/plugin.ts | 9 +- dashboard/src/.umi/core/route.tsx | 14 +- dashboard/src/app.ts | 40 + dashboard/src/layouts/index.tsx | 337 + dashboard/src/mock/README.md | 109 + dashboard/src/mock/browser.ts | 10 + dashboard/src/mock/data/certificate.mock.ts | 222 + .../src/mock/data/productSelection.mock.ts | 407 + dashboard/src/mock/msw.ts | 217 + dashboard/src/pages/AIDecisionLog/index.tsx | 759 ++ .../src/pages/Ad/AIOptimization/index.tsx | 134 +- .../src/pages/Ad/AutoAdjustment/index.tsx | 8 +- dashboard/src/pages/Ad/Performance/index.tsx | 8 +- dashboard/src/pages/Ad/index.tsx | 11 +- .../src/pages/AfterSales/CustomerService.tsx | 5 +- .../src/pages/AfterSales/RefundProcess.tsx | 1 + .../src/pages/AfterSales/ReturnApply.tsx | 44 +- dashboard/src/pages/AfterSales/index.tsx | 11 +- dashboard/src/pages/Analytics/index.tsx | 711 ++ .../src/pages/AutoProductSelection/index.tsx | 1170 +++ dashboard/src/pages/B2B/BatchOrder.tsx | 7 +- dashboard/src/pages/B2B/ContractManage.tsx | 10 +- dashboard/src/pages/B2B/index.tsx | 11 +- dashboard/src/pages/B2BTrade/BatchOrder.tsx | 143 +- .../src/pages/B2BTrade/ContractManage.tsx | 6 +- .../src/pages/B2BTrade/EnterpriseQuote.tsx | 15 +- .../src/pages/Blacklist/BlacklistManage.tsx | 5 +- dashboard/src/pages/Blacklist/RiskMonitor.tsx | 4 +- dashboard/src/pages/Blacklist/index.tsx | 11 +- .../pages/Compliance/CertificateManage.tsx | 464 +- dashboard/src/pages/Compliance/index.tsx | 1075 ++- dashboard/src/pages/DashboardPage.tsx | 720 ++ .../src/pages/Finance/Reconciliation.tsx | 33 +- dashboard/src/pages/Finance/Transactions.tsx | 33 +- .../IndependentSiteAnalytics.tsx | 1 + .../IndependentSite/IndependentSiteConfig.tsx | 64 +- .../IndependentSite/IndependentSiteList.tsx | 24 +- .../IndependentSite/IndependentSiteOrder.tsx | 1 + .../IndependentSiteProduct.tsx | 1 + .../src/pages/Inventory/InventoryForecast.tsx | 1 + dashboard/src/pages/Inventory/Warehouses.tsx | 40 +- dashboard/src/pages/Inventory/index.tsx | 44 +- dashboard/src/pages/Leaderboard/index.tsx | 353 + dashboard/src/pages/Logistics/FreightCalc.tsx | 16 +- .../src/pages/Logistics/LogisticsTrack.tsx | 9 +- dashboard/src/pages/Logistics/index.tsx | 11 +- dashboard/src/pages/Marketing/Ads.tsx | 35 +- dashboard/src/pages/Marketing/Competitors.tsx | 13 +- .../src/pages/Merchant/MerchantManage.tsx | 16 +- .../pages/Merchant/MerchantOrderManage.tsx | 14 +- .../Merchant/MerchantSettlementManage.tsx | 14 +- .../src/pages/Merchant/MerchantShopManage.tsx | 13 +- dashboard/src/pages/Merchant/index.tsx | 13 +- dashboard/src/pages/Orders/ExceptionOrder.tsx | 1 + .../src/pages/Orders/OrderAggregation.tsx | 1 + dashboard/src/pages/Orders/OrderDetail.tsx | 113 +- dashboard/src/pages/Orders/OrderList.tsx | 1335 ++- dashboard/src/pages/Orders/index.tsx | 11 +- .../src/pages/Product/AIPricing/index.tsx | 8 +- .../src/pages/Product/CrossPlatformManage.tsx | 807 ++ dashboard/src/pages/Product/ProductList.tsx | 893 ++ .../src/pages/Product/ProductPublishForm.tsx | 44 +- .../src/pages/Product/ProfitMonitor/index.tsx | 8 +- .../src/pages/Product/ROIAnalysis/index.tsx | 8 +- dashboard/src/pages/Product/index.tsx | 645 +- .../src/pages/Reports/PerformanceReport.tsx | 1 + dashboard/src/pages/Reports/ProfitReport.tsx | 1 + dashboard/src/pages/Reports/index.tsx | 864 +- dashboard/src/pages/Role/index.tsx | 25 +- .../src/pages/Settings/CostTemplateConfig.tsx | 486 ++ .../src/pages/Settings/ExchangeRateConfig.tsx | 385 + .../pages/Settings/PlatformAccountConfig.tsx | 446 + .../src/pages/Settings/SystemSettings.tsx | 1169 +++ .../src/pages/Settings/UserManagement.tsx | 44 +- .../src/pages/Settings/WinNodeConfig.tsx | 531 ++ dashboard/src/pages/Settings/index.tsx | 1254 ++- .../src/pages/StrategyMarketplace/index.tsx | 640 ++ .../src/pages/Suppliers/SupplierDetail.tsx | 27 +- dashboard/src/pages/Suppliers/index.tsx | 36 +- dashboard/src/pages/System/index.tsx | 7 +- dashboard/src/pages/TaskCenter/index.tsx | 937 +++ dashboard/src/pages/User/index.tsx | 20 +- dashboard/src/pages/index.tsx | 222 +- dashboard/src/services/abTestDataSource.ts | 252 + .../src/services/adOptimizationDataSource.ts | 220 + .../src/services/afterSalesDataSource.ts | 209 + dashboard/src/services/analyticsDataSource.ts | 423 + dashboard/src/services/b2bTradeDataSource.ts | 310 + dashboard/src/services/blacklistDataSource.ts | 224 + .../src/services/certificateDataSource.ts | 393 + dashboard/src/services/financeDataSource.ts | 101 + .../src/services/independentSiteDataSource.ts | 263 + dashboard/src/services/inventoryDataSource.ts | 131 + .../src/services/leaderboardDataSource.ts | 97 + dashboard/src/services/logisticsDataSource.ts | 251 + dashboard/src/services/marketingDataSource.ts | 112 + dashboard/src/services/merchantDataSource.ts | 371 + dashboard/src/services/orderDataSource.ts | 312 + dashboard/src/services/productDataSource.ts | 123 + .../services/productSelectionDataSource.ts | 385 + dashboard/src/services/reportsDataSource.ts | 104 + dashboard/src/services/settingsDataSource.ts | 369 + dashboard/src/services/suppliersDataSource.ts | 98 + .../src/services/taskCenterDataSource.ts | 78 + dashboard/src/types/datasource.ts | 204 + docs/00_Business/Business_ClosedLoops.md | 620 +- .../Business_ClosedLoops_Analysis.md | 218 - docs/00_Business/Governance_Standards.md | 100 + docs/00_Business/Task_Overview.md | 353 +- docs/00_Business/Upper_Level_ClosedLoops.md | 271 - docs/01_Architecture/Mock_Architecture.md | 708 ++ docs/01_Architecture/SERVICE_MAP.md | 28 +- docs/01_Architecture/System_Architecture.md | 153 + docs/05_AI/AI_RULES.md | 58 +- docs/Development_Progress.md | 138 +- docs/Module_Inventory.md | 685 -- docs/Runtime_Architecture.md | 519 ++ docs/临时修改建议 copy.MD | 182 +- package-lock.json | 6866 ++++++++------- package.json | 8 +- scripts/deploy-prep.js | 1231 +++ scripts/deployment-check.js | 421 + scripts/optimize-system.js | 466 ++ scripts/run-tests.js | 789 ++ server/.eslintrc.js | 52 + server/package.json | 6 + server/src/api/controllers/OrderController.ts | 91 +- server/src/api/routes/certificate.ts | 161 + server/src/api/routes/leaderboard.ts | 283 + server/src/api/routes/strategy.ts | 413 +- server/src/app.ts | 4 + server/src/bff/mock/certificateMock.ts | 426 + server/src/bff/services/certificateService.ts | 44 + server/src/core/guards/service.guard.ts | 80 + .../integration/SystemIntegrationService.ts | 764 ++ .../PerformanceOptimizationService.ts | 570 ++ server/src/core/runtime/DomainBootstrap.ts | 12 + .../core/security/SecurityHardeningService.ts | 964 +++ server/src/database/DatabaseSchema.ts | 127 +- server/src/repositories/BaseRepository.ts | 91 + server/src/repositories/billing/schema.sql | 50 + server/src/runtime/eventBus.ts | 28 + server/src/runtime/index.ts | 38 + server/src/runtime/jobQueue.ts | 64 + server/src/runtime/queue-core.ts | 65 + server/src/runtime/queue/index.ts | 36 + server/src/runtime/queue/priority.ts | 115 + server/src/runtime/queue/processor.ts | 71 + server/src/runtime/queue/worker.ts | 41 + server/src/runtime/scheduler.ts | 33 + server/src/runtime/worker.ts | 95 + server/src/runtime/ws/broadcast.ts | 66 + server/src/runtime/ws/index.ts | 29 + server/src/runtime/ws/server.ts | 108 + server/src/services/AIDecisionLogService.ts | 516 ++ server/src/services/AdAutoService.ts | 423 +- server/src/services/AutoListingService.ts | 653 ++ server/src/services/BillingService.ts | 405 +- server/src/services/LeaderboardService.ts | 316 + server/src/services/MerchantMetricsService.ts | 371 + server/src/services/OrderService.ts | 94 + .../src/services/ProductSelectionService.ts | 585 ++ .../services/StrategyRecommendationService.ts | 359 + server/src/services/StrategyService.ts | 374 + server/src/services/UsageService.ts | 126 + server/src/tests/comprehensive.test.ts | 584 ++ server/src/tests/system-integration.test.ts | 323 + 176 files changed, 47075 insertions(+), 8404 deletions(-) create mode 100644 agent4.code-workspace create mode 100644 client/src/pages/Billing/index.tsx create mode 100644 client/src/pages/EventLog/index.tsx create mode 100644 client/src/pages/TaskCenter/index.tsx create mode 100644 dashboard/.env.development create mode 100644 dashboard/.env.production create mode 100644 dashboard/src/app.ts create mode 100644 dashboard/src/layouts/index.tsx create mode 100644 dashboard/src/mock/README.md create mode 100644 dashboard/src/mock/browser.ts create mode 100644 dashboard/src/mock/data/certificate.mock.ts create mode 100644 dashboard/src/mock/data/productSelection.mock.ts create mode 100644 dashboard/src/mock/msw.ts create mode 100644 dashboard/src/pages/AIDecisionLog/index.tsx create mode 100644 dashboard/src/pages/Analytics/index.tsx create mode 100644 dashboard/src/pages/AutoProductSelection/index.tsx create mode 100644 dashboard/src/pages/DashboardPage.tsx create mode 100644 dashboard/src/pages/Leaderboard/index.tsx create mode 100644 dashboard/src/pages/Product/CrossPlatformManage.tsx create mode 100644 dashboard/src/pages/Product/ProductList.tsx create mode 100644 dashboard/src/pages/Settings/CostTemplateConfig.tsx create mode 100644 dashboard/src/pages/Settings/ExchangeRateConfig.tsx create mode 100644 dashboard/src/pages/Settings/PlatformAccountConfig.tsx create mode 100644 dashboard/src/pages/Settings/SystemSettings.tsx create mode 100644 dashboard/src/pages/Settings/WinNodeConfig.tsx create mode 100644 dashboard/src/pages/StrategyMarketplace/index.tsx create mode 100644 dashboard/src/pages/TaskCenter/index.tsx create mode 100644 dashboard/src/services/abTestDataSource.ts create mode 100644 dashboard/src/services/adOptimizationDataSource.ts create mode 100644 dashboard/src/services/afterSalesDataSource.ts create mode 100644 dashboard/src/services/analyticsDataSource.ts create mode 100644 dashboard/src/services/b2bTradeDataSource.ts create mode 100644 dashboard/src/services/blacklistDataSource.ts create mode 100644 dashboard/src/services/certificateDataSource.ts create mode 100644 dashboard/src/services/financeDataSource.ts create mode 100644 dashboard/src/services/independentSiteDataSource.ts create mode 100644 dashboard/src/services/inventoryDataSource.ts create mode 100644 dashboard/src/services/leaderboardDataSource.ts create mode 100644 dashboard/src/services/logisticsDataSource.ts create mode 100644 dashboard/src/services/marketingDataSource.ts create mode 100644 dashboard/src/services/merchantDataSource.ts create mode 100644 dashboard/src/services/orderDataSource.ts create mode 100644 dashboard/src/services/productDataSource.ts create mode 100644 dashboard/src/services/productSelectionDataSource.ts create mode 100644 dashboard/src/services/reportsDataSource.ts create mode 100644 dashboard/src/services/settingsDataSource.ts create mode 100644 dashboard/src/services/suppliersDataSource.ts create mode 100644 dashboard/src/services/taskCenterDataSource.ts create mode 100644 dashboard/src/types/datasource.ts delete mode 100644 docs/00_Business/Business_ClosedLoops_Analysis.md delete mode 100644 docs/00_Business/Upper_Level_ClosedLoops.md create mode 100644 docs/01_Architecture/Mock_Architecture.md delete mode 100644 docs/Module_Inventory.md create mode 100644 docs/Runtime_Architecture.md create mode 100644 scripts/deploy-prep.js create mode 100644 scripts/deployment-check.js create mode 100644 scripts/optimize-system.js create mode 100644 scripts/run-tests.js create mode 100644 server/.eslintrc.js create mode 100644 server/src/api/routes/certificate.ts create mode 100644 server/src/api/routes/leaderboard.ts create mode 100644 server/src/bff/mock/certificateMock.ts create mode 100644 server/src/bff/services/certificateService.ts create mode 100644 server/src/core/guards/service.guard.ts create mode 100644 server/src/core/integration/SystemIntegrationService.ts create mode 100644 server/src/core/performance/PerformanceOptimizationService.ts create mode 100644 server/src/core/security/SecurityHardeningService.ts create mode 100644 server/src/repositories/BaseRepository.ts create mode 100644 server/src/repositories/billing/schema.sql create mode 100644 server/src/runtime/eventBus.ts create mode 100644 server/src/runtime/index.ts create mode 100644 server/src/runtime/jobQueue.ts create mode 100644 server/src/runtime/queue-core.ts create mode 100644 server/src/runtime/queue/index.ts create mode 100644 server/src/runtime/queue/priority.ts create mode 100644 server/src/runtime/queue/processor.ts create mode 100644 server/src/runtime/queue/worker.ts create mode 100644 server/src/runtime/scheduler.ts create mode 100644 server/src/runtime/worker.ts create mode 100644 server/src/runtime/ws/broadcast.ts create mode 100644 server/src/runtime/ws/index.ts create mode 100644 server/src/runtime/ws/server.ts create mode 100644 server/src/services/AIDecisionLogService.ts create mode 100644 server/src/services/AutoListingService.ts create mode 100644 server/src/services/LeaderboardService.ts create mode 100644 server/src/services/MerchantMetricsService.ts create mode 100644 server/src/services/ProductSelectionService.ts create mode 100644 server/src/services/StrategyRecommendationService.ts create mode 100644 server/src/services/StrategyService.ts create mode 100644 server/src/services/UsageService.ts create mode 100644 server/src/tests/comprehensive.test.ts create mode 100644 server/src/tests/system-integration.test.ts diff --git a/.trae/rules/project-specific-rules.md b/.trae/rules/project-specific-rules.md index 696750e..4147585 100644 --- a/.trae/rules/project-specific-rules.md +++ b/.trae/rules/project-specific-rules.md @@ -321,6 +321,83 @@ Step 5: 完成后释放占用 --- +## 11. Mock数据规范(AI上下文安全) + +### 11.1 Mock数据原则 + +**核心目标**: 实现"不污染代码、AI上下文安全"的Mock方案 + +#### ❌ 禁止做法 +- **硬编码Mock数据**: 在业务组件中直接写死数据 + ```typescript + // 禁止! + const data = [{ id: 1, name: 'Mock商品' }] + ``` +- **if判断切换**: 在业务代码中通过if判断切换Mock/真实数据 + ```typescript + // 禁止! + if (isDev) return mockData + ``` + +#### ✅ 正确做法 +- **DataSource抽象层**: 通过数据源抽象层获取数据 + ```typescript + // 正确! + const data = await certificateDataSource.list() + ``` +- **环境变量控制**: 通过 `REACT_APP_USE_MOCK` 环境变量控制 +- **目录隔离**: 所有Mock文件必须放在 `/mock` 目录 + +### 11.2 Mock文件规范 + +#### 目录结构 +``` +/dashboard/src + /mock ← 所有Mock相关 + /data ← Mock数据定义 + msw.ts ← MSW配置 + browser.ts ← 浏览器入口 + README.md ← Mock使用说明 + /services ← DataSource抽象层 + *DataSource.ts +``` + +#### 文件标记 +所有Mock文件必须包含以下标记: +```typescript +/** + * [MOCK-XXX] 功能描述 + * AI注意: 这是Mock实现,不是真实业务逻辑 + * 仅在USE_MOCK=true时启用 + */ + +// 或者 + +// [MOCK] 此文件为Mock数据,AI请勿当作真实业务逻辑 +``` + +### 11.3 AI识别规则 + +AI在分析代码时: +- ✅ **忽略** `/mock` 目录下所有文件 +- ✅ **忽略** 包含 `[MOCK]` 标记的文件 +- ✅ **忽略** `__MOCK__` 变量为真的代码路径 +- ✅ **优先** 分析 `/services` 下的DataSource层 + +### 11.4 环境变量 + +| 变量名 | 开发环境 | 生产环境 | 说明 | +|--------|----------|----------|------| +| REACT_APP_USE_MOCK | `true` | `false` | 前端Mock开关 | +| USE_MOCK | `true` | `false` | 后端Mock开关 | + +### 11.5 参考文档 + +- [Mock架构设计](../../docs/01_Architecture/Mock_Architecture.md) +- [前端DataSource实现](../../dashboard/src/services/certificateDataSource.ts) + +--- + ## 快速参考 | 规则类别 | 关键约束 | 违反后果 | @@ -334,6 +411,7 @@ Step 5: 完成后释放占用 | **逻辑集中化** | **所有业务逻辑必须在Service层** | **AI维护困难,数据不一致** | | **任务领取** | **优先领任务包,最小2个任务** | **碎片化等待** | | **协作防撞** | **必须声明占用,先声明优先** | **重复开发** | +| **Mock规范** | **Mock数据必须隔离,禁止硬编码** | **AI上下文污染,维护困难** | --- diff --git a/agent4.code-workspace b/agent4.code-workspace new file mode 100644 index 0000000..99e9c06 --- /dev/null +++ b/agent4.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": "../../agent4" + }, + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/client/src/pages/Billing/index.tsx b/client/src/pages/Billing/index.tsx new file mode 100644 index 0000000..7cff872 --- /dev/null +++ b/client/src/pages/Billing/index.tsx @@ -0,0 +1,277 @@ +import React, { useState, useEffect } from 'react'; +import { Table, Tag, Spin, Alert, Typography, Card, Button, Modal, Descriptions } from 'antd'; +import { useRequest } from 'umi'; + +const { Title, Text } = Typography; + +interface BillingRecord { + id: string; + merchantId: string; + totalAmount: number; + status: string; + createdAt: Date; + paidAt?: Date; +} + +interface BillingItem { + id: string; + billingId: string; + feature: string; + amount: number; + quantity: number; + unitPrice: number; +} + +const statusColorMap: Record = { + pending: 'orange', + paid: 'green' +}; + +const statusTextMap: Record = { + pending: '待支付', + paid: '已支付' +}; + +const featureMap: Record = { + AI_OPTIMIZE: 'AI优化', + ADS_AUTO: '自动广告', + SYNC_INVENTORY: '库存同步', + CALCULATE_PROFIT: '利润计算' +}; + +export default function Billing() { + const [bills, setBills] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedBill, setSelectedBill] = useState(null); + const [billItems, setBillItems] = useState([]); + const [itemsLoading, setItemsLoading] = useState(false); + const [modalVisible, setModalVisible] = useState(false); + + // 获取账单列表 + const { run: fetchBills } = useRequest(async () => { + try { + // 从本地存储获取merchantId(实际应用中应该从认证信息中获取) + const merchantId = localStorage.getItem('merchantId') || 'anonymous'; + const response = await fetch(`/api/billing?merchantId=${merchantId}`); + const data = await response.json(); + setBills(data.list || []); + } catch (error) { + console.error('Error fetching bills:', error); + } finally { + setLoading(false); + } + }, { + manual: true + }); + + // 获取账单明细 + const { run: fetchBillItems } = useRequest(async (billingId: string) => { + try { + setItemsLoading(true); + const response = await fetch(`/api/billing/items?billingId=${billingId}`); + const data = await response.json(); + setBillItems(data.list || []); + } catch (error) { + console.error('Error fetching bill items:', error); + } finally { + setItemsLoading(false); + } + }, { + manual: true + }); + + // 初始加载账单列表 + useEffect(() => { + fetchBills(); + }, [fetchBills]); + + // 查看账单明细 + const handleViewDetails = async (bill: BillingRecord) => { + setSelectedBill(bill); + await fetchBillItems(bill.id); + setModalVisible(true); + }; + + // 标记账单为已支付 + const handleMarkAsPaid = async (bill: BillingRecord) => { + try { + const response = await fetch(`/api/billing/paid`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ billingId: bill.id }) + }); + + if (response.ok) { + // 刷新账单列表 + fetchBills(); + } + } catch (error) { + console.error('Error marking bill as paid:', error); + } + }; + + // 列定义 + const columns = [ + { + title: '账单ID', + dataIndex: 'id', + key: 'id', + ellipsis: true, + }, + { + title: '总金额', + dataIndex: 'totalAmount', + key: 'totalAmount', + render: (amount: number) => `$${amount.toFixed(2)}`, + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: (status: string) => ( + + {statusTextMap[status]} + + ), + }, + { + title: '创建时间', + dataIndex: 'createdAt', + key: 'createdAt', + render: (date: Date) => new Date(date).toLocaleString(), + }, + { + title: '支付时间', + dataIndex: 'paidAt', + key: 'paidAt', + render: (date: Date) => date ? new Date(date).toLocaleString() : '-', + }, + { + title: '操作', + key: 'action', + render: (_: any, record: BillingRecord) => ( +
+ + {record.status === 'pending' && ( + + )} +
+ ), + }, + ]; + + // 计算总金额 + const totalAmount = bills.reduce((sum, bill) => sum + bill.totalAmount, 0); + const pendingAmount = bills.filter(bill => bill.status === 'pending').reduce((sum, bill) => sum + bill.totalAmount, 0); + + return ( +
+ 账单管理 + + + + ${totalAmount.toFixed(2)} + ${pendingAmount.toFixed(2)} + ${(totalAmount - pendingAmount).toFixed(2)} + {bills.length} + + + + {loading ? ( +
+ +
+ ) : bills.length === 0 ? ( + + ) : ( + + )} + + {/* 账单明细 modal */} + setModalVisible(false)} + footer={[ + + ]} + width={800} + > + {selectedBill && ( +
+ + {selectedBill.id} + ${selectedBill.totalAmount.toFixed(2)} + + + {statusTextMap[selectedBill.status]} + + + {new Date(selectedBill.createdAt).toLocaleString()} + {selectedBill.paidAt ? new Date(selectedBill.paidAt).toLocaleString() : '-'} + + + 明细列表 + {itemsLoading ? ( + + ) : billItems.length === 0 ? ( + + ) : ( +
featureMap[feature] || feature, + }, + { + title: '数量', + dataIndex: 'quantity', + }, + { + title: '单价', + dataIndex: 'unitPrice', + render: (price: number) => `$${price.toFixed(2)}`, + }, + { + title: '金额', + dataIndex: 'amount', + render: (amount: number) => `$${amount.toFixed(2)}`, + }, + ]} + dataSource={billItems} + pagination={false} + /> + )} + + )} + + + ); +} diff --git a/client/src/pages/EventLog/index.tsx b/client/src/pages/EventLog/index.tsx new file mode 100644 index 0000000..0b6c631 --- /dev/null +++ b/client/src/pages/EventLog/index.tsx @@ -0,0 +1,176 @@ +import React, { useEffect, useState } from 'react'; +import { Table, Tag, Spin, Alert, Typography, Card } from 'antd'; +import { useRequest } from 'umi'; + +const { Title, Text } = Typography; + +interface Event { + id: string; + type: string; + payload: any; + timestamp: number; + source: string; + merchantId?: string; +} + +const eventTypeMap: Record = { + PRODUCT_CREATED: '商品创建', + PRODUCT_UPDATED: '商品更新', + ORDER_CREATED: '订单创建', + ORDER_PAID: '订单支付', + ORDER_COMPLETED: '订单完成', + INVENTORY_LOW: '库存不足', + INVENTORY_UPDATED: '库存更新', + FEATURE_ENABLED: '功能启用', + FEATURE_DISABLED: '功能禁用', + AI_TASK_CREATED: 'AI任务创建', + AI_TASK_COMPLETED: 'AI任务完成', + AD_STARTED: '广告开始', + AD_PERFORMANCE_UPDATED: '广告性能更新', + JOB_CREATED: '任务创建', + JOB_UPDATED: '任务更新', + JOB_COMPLETED: '任务完成', + JOB_FAILED: '任务失败', + BILLING_GENERATED: '账单生成' +}; + +export default function EventLog() { + const [events, setEvents] = useState([]); + const [loading, setLoading] = useState(true); + const [ws, setWs] = useState(null); + + // 获取事件日志 + const { run: fetchEvents } = useRequest(async () => { + try { + const response = await fetch('/api/events'); + const data = await response.json(); + setEvents(data.list || []); + } catch (error) { + console.error('Error fetching events:', error); + } finally { + setLoading(false); + } + }, { + manual: true + }); + + // 初始化WebSocket连接 + useEffect(() => { + // 从本地存储获取merchantId(实际应用中应该从认证信息中获取) + const merchantId = localStorage.getItem('merchantId') || 'anonymous'; + + // 建立WebSocket连接 + const socket = new WebSocket(`ws://localhost:8080?merchantId=${merchantId}`); + + socket.onopen = () => { + console.log('WebSocket connected'); + }; + + socket.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + + // 处理任何事件,刷新事件列表 + if (data.type !== 'CONNECTION_SUCCESS') { + fetchEvents(); + } + } catch (error) { + console.error('Error processing WebSocket message:', error); + } + }; + + socket.onclose = () => { + console.log('WebSocket disconnected'); + }; + + socket.onerror = (error) => { + console.error('WebSocket error:', error); + }; + + setWs(socket); + + // 组件卸载时关闭WebSocket连接 + return () => { + socket.close(); + }; + }, [fetchEvents]); + + // 初始加载事件列表 + useEffect(() => { + fetchEvents(); + }, [fetchEvents]); + + // 列定义 + const columns = [ + { + title: '事件类型', + dataIndex: 'type', + key: 'type', + render: (type: string) => eventTypeMap[type] || type, + }, + { + title: '来源', + dataIndex: 'source', + key: 'source', + }, + { + title: '时间', + dataIndex: 'timestamp', + key: 'timestamp', + render: (timestamp: number) => new Date(timestamp).toLocaleString(), + sorter: (a: Event, b: Event) => a.timestamp - b.timestamp, + defaultSortOrder: 'descend', + }, + { + title: '商户ID', + dataIndex: 'merchantId', + key: 'merchantId', + render: (merchantId: string) => merchantId || '-', + }, + { + title: '详情', + key: 'details', + render: (_: any, record: Event) => ( + { + // 查看事件详情 + console.log('Event details:', record); + }} + style={{ color: '#1890ff', cursor: 'pointer' }} + > + 查看详情 + + ), + }, + ]; + + return ( +
+ 事件日志 + + + 系统事件日志记录了系统的所有关键操作和状态变化,帮助您了解系统的运行情况。 + + + {loading ? ( +
+ +
+ ) : events.length === 0 ? ( + + ) : ( +
+ )} + + ); +} diff --git a/client/src/pages/TaskCenter/index.tsx b/client/src/pages/TaskCenter/index.tsx new file mode 100644 index 0000000..b46abf4 --- /dev/null +++ b/client/src/pages/TaskCenter/index.tsx @@ -0,0 +1,233 @@ +import React, { useEffect, useState } from 'react'; +import { Table, Tag, Spin, Alert, Typography, Card, Row, Col } from 'antd'; +import { useRequest } from 'umi'; + +const { Title, Text } = Typography; + +interface Job { + id: string; + type: string; + status: 'pending' | 'running' | 'success' | 'failed'; + payload: any; + result?: any; + retryCount: number; + merchantId?: string; + createdAt: number; + updatedAt: number; +} + +const statusColorMap: Record = { + pending: 'orange', + running: 'blue', + success: 'green', + failed: 'red' +}; + +const statusTextMap: Record = { + pending: '待处理', + running: '处理中', + success: '已完成', + failed: '失败' +}; + +const jobTypeMap: Record = { + AI_OPTIMIZE_PRODUCT: 'AI优化商品', + SYNC_INVENTORY: '同步库存', + RUN_ADS: '运行广告', + CALCULATE_PROFIT: '计算利润', + GENERATE_BILL: '生成账单', + UPDATE_AD_BUDGET: '更新广告预算', + STOP_AD: '停止广告' +}; + +export default function TaskCenter() { + const [jobs, setJobs] = useState([]); + const [loading, setLoading] = useState(true); + const [ws, setWs] = useState(null); + + // 获取任务列表 + const { run: fetchJobs } = useRequest(async () => { + try { + const response = await fetch('/api/jobs'); + const data = await response.json(); + setJobs(data.list || []); + } catch (error) { + console.error('Error fetching jobs:', error); + } finally { + setLoading(false); + } + }, { + manual: true + }); + + // 初始化WebSocket连接 + useEffect(() => { + // 从本地存储获取merchantId(实际应用中应该从认证信息中获取) + const merchantId = localStorage.getItem('merchantId') || 'anonymous'; + + // 建立WebSocket连接 + const socket = new WebSocket(`ws://localhost:8080?merchantId=${merchantId}`); + + socket.onopen = () => { + console.log('WebSocket connected'); + }; + + socket.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + + // 处理任务更新事件 + if (data.type === 'JOB_UPDATED' || data.type === 'JOB_COMPLETED' || data.type === 'JOB_FAILED') { + fetchJobs(); + } + + // 处理AI任务完成事件 + if (data.type === 'AI_TASK_COMPLETED') { + fetchJobs(); + } + } catch (error) { + console.error('Error processing WebSocket message:', error); + } + }; + + socket.onclose = () => { + console.log('WebSocket disconnected'); + }; + + socket.onerror = (error) => { + console.error('WebSocket error:', error); + }; + + setWs(socket); + + // 组件卸载时关闭WebSocket连接 + return () => { + socket.close(); + }; + }, [fetchJobs]); + + // 初始加载任务列表 + useEffect(() => { + fetchJobs(); + }, [fetchJobs]); + + // 列定义 + const columns = [ + { + title: '任务ID', + dataIndex: 'id', + key: 'id', + ellipsis: true, + }, + { + title: '任务类型', + dataIndex: 'type', + key: 'type', + render: (type: string) => jobTypeMap[type] || type, + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: (status: string) => ( + + {statusTextMap[status]} + + ), + }, + { + title: '重试次数', + dataIndex: 'retryCount', + key: 'retryCount', + }, + { + title: '创建时间', + dataIndex: 'createdAt', + key: 'createdAt', + render: (timestamp: number) => new Date(timestamp).toLocaleString(), + }, + { + title: '更新时间', + dataIndex: 'updatedAt', + key: 'updatedAt', + render: (timestamp: number) => new Date(timestamp).toLocaleString(), + }, + { + title: '操作', + key: 'action', + render: (_: any, record: Job) => ( + { + // 查看任务详情 + console.log('Job details:', record); + }} + style={{ color: '#1890ff', cursor: 'pointer' }} + > + 查看详情 + + ), + }, + ]; + + // 统计数据 + const stats = { + total: jobs.length, + pending: jobs.filter(job => job.status === 'pending').length, + running: jobs.filter(job => job.status === 'running').length, + success: jobs.filter(job => job.status === 'success').length, + failed: jobs.filter(job => job.status === 'failed').length, + }; + + return ( +
+ 任务中心 + + +
+ + 总任务数 +
{stats.total}
+
+ + + + 待处理 +
{stats.pending}
+
+ + + + 处理中 +
{stats.running}
+
+ + + + 已完成 +
{stats.success}
+
+ + + + {loading ? ( +
+ +
+ ) : jobs.length === 0 ? ( + + ) : ( +
+ )} + + ); +} diff --git a/dashboard/.env.development b/dashboard/.env.development new file mode 100644 index 0000000..2b20523 --- /dev/null +++ b/dashboard/.env.development @@ -0,0 +1,23 @@ +# 开发环境配置 +# 创建时间: 2026-03-19 (北京时间) + +# ============================================ +# Mock配置 +# ============================================ +# 启用Mock模式 (true=启用Mock, false=使用真实API) +REACT_APP_USE_MOCK=true + +# ============================================ +# API配置 +# ============================================ +# API基础URL (Mock模式下可不配置) +REACT_APP_API_BASE_URL=http://localhost:3000 + +# ============================================ +# 调试配置 +# ============================================ +# 启用Redux DevTools +REACT_APP_REDUX_DEVTOOLS=true + +# 启用MSW调试日志 +REACT_APP_MSW_DEBUG=true diff --git a/dashboard/.env.production b/dashboard/.env.production new file mode 100644 index 0000000..31f81a5 --- /dev/null +++ b/dashboard/.env.production @@ -0,0 +1,23 @@ +# 生产环境配置 +# 创建时间: 2026-03-19 (北京时间) + +# ============================================ +# Mock配置 (生产环境必须禁用) +# ============================================ +# 禁用Mock模式 +REACT_APP_USE_MOCK=false + +# ============================================ +# API配置 +# ============================================ +# API基础URL (生产环境配置) +REACT_APP_API_BASE_URL=/api + +# ============================================ +# 调试配置 (生产环境禁用) +# ============================================ +# 禁用Redux DevTools +REACT_APP_REDUX_DEVTOOLS=false + +# 禁用MSW调试日志 +REACT_APP_MSW_DEBUG=false diff --git a/dashboard/package.json b/dashboard/package.json index ad902ea..fc0c2ee 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -11,11 +11,13 @@ }, "dependencies": { "@ant-design/icons": "^5.2.6", + "@ant-design/plots": "^2.6.8", "@antv/g6": "^5.0.51", "@antv/l7": "^2.25.2", "@antv/l7-react": "^2.4.3", "antd": "^5.12.2", "axios": "^1.13.6", + "moment": "^2.30.1", "react": "^18.2.0", "react-dom": "^18.2.0", "recharts": "^3.8.0", @@ -24,6 +26,7 @@ "devDependencies": { "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", + "msw": "^2.12.13", "typescript": "^5.9.3" } } diff --git a/dashboard/src/.umi/appData.json b/dashboard/src/.umi/appData.json index d63e66f..85cdf17 100644 --- a/dashboard/src/.umi/appData.json +++ b/dashboard/src/.umi/appData.json @@ -13,11 +13,13 @@ }, "dependencies": { "@ant-design/icons": "^5.2.6", + "@ant-design/plots": "^2.6.8", "@antv/g6": "^5.0.51", "@antv/l7": "^2.25.2", "@antv/l7-react": "^2.4.3", "antd": "^5.12.2", "axios": "^1.13.6", + "moment": "^2.30.1", "react": "^18.2.0", "react-dom": "^18.2.0", "recharts": "^3.8.0", @@ -26,6 +28,7 @@ "devDependencies": { "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", + "msw": "^2.12.13", "typescript": "^5.9.3" } }, @@ -40,7 +43,7 @@ "config": {}, "time": { "hooks": {}, - "register": 2 + "register": 5 }, "enableBy": "register" }, @@ -53,7 +56,7 @@ "config": {}, "time": { "hooks": {}, - "register": 116 + "register": 37 }, "enableBy": "register" }, @@ -70,7 +73,7 @@ 0 ] }, - "register": 12 + "register": 11 }, "enableBy": "register" }, @@ -84,10 +87,10 @@ "time": { "hooks": { "onStart": [ - 3 + 6 ] }, - "register": 82 + "register": 8 }, "enableBy": "register" }, @@ -104,11 +107,7 @@ 0, 0, 0, - 0, - 0, - 0, - 0, - 0, + 1, 0, 0, 1, @@ -119,30 +118,12 @@ 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + 1, 0, 0 ] }, - "register": 4 + "register": 2 }, "enableBy": "register" }, @@ -156,35 +137,27 @@ "time": { "hooks": { "onDevCompileDone": [ - 32, - 6, - 4, - 5, - 10, - 4, - 4, + 33, 11, - 5, + 3, + 17, + 13, 8, 7, - 16, - 10, - 9, - 8, - 10, - 13, + 4, 6, - 8, - 10, 5, - 6, - 10, - 30, - 9, - 10, 5, + 4, + 22, 6, - 6 + 6, + 3, + 15, + 5, + 3, + 11, + 7 ] }, "register": 5 @@ -201,51 +174,29 @@ "time": { "hooks": { "modifyAppData": [ - 449 + 894 ], "onGenerateFiles": [ - 11, + 27, + 158, + 39, + 115, + 45, + 73, + 54, + 34, + 53, 36, - 28, - 26, - 18, - 41, - 21, - 21, - 18, - 28, - 28, - 26, - 25, - 32, - 27, - 25, - 25, - 25, - 26, - 28, - 20, - 26, - 29, - 22, - 38, - 38, - 39, - 38, - 26, - 27, - 26, - 28, - 25, - 22, - 23, - 39, 37, - 23, - 29 + 47, + 38, + 30, + 33, + 30, + 53 ] }, - "register": 79 + "register": 56 }, "enableBy": "register" }, @@ -268,7 +219,6 @@ 0, 0, 0, - 1, 0, 0, 0, @@ -276,31 +226,10 @@ 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, 0 ] }, - "register": 2 + "register": 1 }, "enableBy": "register" }, @@ -338,16 +267,6 @@ 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, 0 ], "onCheckCode": [ @@ -355,6 +274,7 @@ 0, 0, 0, + 0, 1, 0, 0, @@ -385,6 +305,38 @@ 0, 0, 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, 1, 0, 0, @@ -414,10 +366,55 @@ 0, 0, 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, 0 ] }, - "register": 3 + "register": 2 }, "enableBy": "register" }, @@ -434,7 +431,7 @@ 0 ] }, - "register": 7 + "register": 2 }, "enableBy": "register" }, @@ -447,7 +444,7 @@ "config": {}, "time": { "hooks": {}, - "register": 4 + "register": 2 }, "enableBy": "config" }, @@ -464,7 +461,7 @@ 0 ] }, - "register": 78 + "register": 19 }, "enableBy": "register" }, @@ -1316,7 +1313,7 @@ "config": {}, "time": { "hooks": {}, - "register": 2 + "register": 1 }, "enableBy": "config" }, @@ -1330,10 +1327,10 @@ "time": { "hooks": { "onStart": [ - 2 + 0 ] }, - "register": 4 + "register": 3 }, "enableBy": "register" }, @@ -1347,10 +1344,10 @@ "time": { "hooks": { "addBeforeMiddlewares": [ - 148 + 13 ] }, - "register": 4 + "register": 2 }, "enableBy": "register" }, @@ -1363,7 +1360,7 @@ "config": {}, "time": { "hooks": {}, - "register": 162 + "register": 210 }, "enableBy": "register" }, @@ -1376,7 +1373,7 @@ "config": {}, "time": { "hooks": {}, - "register": 84 + "register": 220 }, "enableBy": "config" }, @@ -1389,7 +1386,7 @@ "config": {}, "time": { "hooks": {}, - "register": 39 + "register": 129 }, "enableBy": "config" }, @@ -1403,71 +1400,12 @@ "time": { "hooks": { "modifyAppData": [ - 1 + 0 ], "addBeforeMiddlewares": [ 0 ], "modifyHTMLFavicon": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, 0, 0, 0, @@ -1542,45 +1480,23 @@ "time": { "hooks": { "onGenerateFiles": [ - 48, - 1, - 1, - 1, - 0, - 2, - 1, - 0, - 1, + 18, 3, 1, 1, + 5, + 4, 1, 1, 1, 1, 1, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 2, - 2, - 1, + 6, 1, 2, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 + 0 ], "addRuntimePlugin": [ 0, @@ -1588,43 +1504,21 @@ 0, 0, 0, + 1, + 0, + 0, + 1, 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + 1, 0, 0, 0, 0 ] }, - "register": 2 + "register": 3 }, "enableBy": "register" }, @@ -1637,7 +1531,7 @@ "config": {}, "time": { "hooks": {}, - "register": 4 + "register": 6 }, "enableBy": "config" }, @@ -1651,13 +1545,13 @@ "time": { "hooks": { "onStart": [ - 2 + 3 ], "addBeforeMiddlewares": [ 3 ] }, - "register": 71 + "register": 136 } }, "./node_modules/@umijs/preset-umi/dist/features/mpa/mpa": { @@ -1669,7 +1563,7 @@ "config": {}, "time": { "hooks": {}, - "register": 3 + "register": 4 }, "enableBy": "config" }, @@ -1682,7 +1576,7 @@ "config": {}, "time": { "hooks": {}, - "register": 2 + "register": 3 } }, "./node_modules/@umijs/preset-umi/dist/features/overrides/overrides": { @@ -1695,6 +1589,7 @@ "time": { "hooks": { "onGenerateFiles": [ + 2, 0, 0, 0, @@ -1703,30 +1598,7 @@ 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + 1, 0, 0, 0, @@ -1736,50 +1608,6 @@ 0 ], "addEntryImports": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, 0, 0, 0, @@ -1843,94 +1671,28 @@ "time": { "hooks": { "modifyConfig": [ - 1 + 2 ], "onGenerateFiles": [ - 147, - 69, - 48, - 90, - 24, - 98, - 53, - 37, - 33, - 54, - 32, - 39, - 39, - 38, - 44, - 37, - 35, - 35, - 50, - 44, - 29, - 36, - 40, - 36, - 62, - 64, + 300, 66, - 58, + 86, + 77, + 59, + 49, + 46, 33, - 39, - 43, - 39, 35, + 41, + 31, + 31, + 55, + 24, 35, - 35, - 63, - 65, - 34, - 129 + 23, + 51 ], "addPolyfillImports": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, 0, 0, 0, @@ -1980,6 +1742,11 @@ "time": { "hooks": { "addPolyfillImports": [ + 0, + 0, + 0, + 0, + 0, 0, 0, 1, @@ -2008,59 +1775,10 @@ 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, 0 ] }, - "register": 1 + "register": 2 }, "enableBy": "register" }, @@ -2074,29 +1792,7 @@ "time": { "hooks": { "onGenerateFiles": [ - 401, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + 388, 0, 0, 0, @@ -2115,7 +1811,7 @@ 0 ] }, - "register": 51 + "register": 6 }, "enableBy": "register" }, @@ -2128,7 +1824,7 @@ "config": {}, "time": { "hooks": {}, - "register": 0 + "register": 1 }, "enableBy": "config" }, @@ -2157,49 +1853,27 @@ "onGenerateFiles": [ 1, 1, - 1, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 0, - 0, - 0, - 1, - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 0, + 3, 1, 1, 1, 1, 0, 1, - 0, - 0, - 0, - 0, - 0, + 3, 1, 1, 0, - 0 + 1, + 0, + 1, + 1 ], "addBeforeMiddlewares": [ 0 ] }, - "register": 1 + "register": 2 }, "enableBy": "register" }, @@ -2213,87 +1887,43 @@ "time": { "hooks": { "onGenerateFiles": [ - 89, - 126, - 309, - 2, - 271, - 2, - 391, + 55, + 9, + 654, 4, - 243, + 625, + 1, + 542, 2, - 398, - 1, - 395, + 706, 2, - 346, + 514, + 1, + 1391, 2, - 311, - 1, - 473, - 1, - 430, - 1, - 305, - 1, - 357, - 1, - 280, - 1, - 306, + 815, 2, - 309, - 1, - 324, - 2, - 397, - 1, - 336, - 2, - 593, - 1, - 327, - 1, - 292, + 809, + 4, + 928, 3, - 457, + 567, 2, - 335, + 682, 1, - 614, - 2, - 528, + 532, 1, - 467, + 513, 1, - 398, + 431, + 3, + 543, 1, - 291, - 1, - 275, - 2, - 288, - 2, - 283, - 1, - 322, - 2, - 299, - 1, - 356, - 1, - 515, - 1, - 672, - 2, - 356, - 1, - 623, - 1 + 2028, + 2 ] }, - "register": 16 + "register": 27 }, "enableBy": "register" }, @@ -2306,7 +1936,7 @@ "config": {}, "time": { "hooks": {}, - "register": 4 + "register": 6 }, "enableBy": "config" }, @@ -2320,17 +1950,12 @@ "time": { "hooks": { "onGenerateFiles": [ + 18, 1, 0, - 0, 1, - 0, - 0, 1, - 0, - 0, - 0, - 0, + 1, 1, 0, 0, @@ -2339,32 +1964,15 @@ 0, 1, 0, - 0, - 0, 1, 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 1, - 0, - 0, - 0, 0 ], "onBeforeCompiler": [ - 94 + 17 ] }, - "register": 1 + "register": 3 } }, "./node_modules/@umijs/preset-umi/dist/features/ssr/ssr": { @@ -2376,7 +1984,7 @@ "config": {}, "time": { "hooks": {}, - "register": 26 + "register": 8 }, "enableBy": "config" }, @@ -2390,48 +1998,26 @@ "time": { "hooks": { "onGenerateFiles": [ - 802, - 17, - 13, - 28, + 1308, + 58, 10, - 15, - 11, + 12, + 14, + 10, + 18, + 12, 13, 10, 12, - 11, - 10, - 16, - 10, - 12, - 12, - 17, - 11, + 9, 9, 10, - 11, - 8, - 12, - 12, - 12, - 15, - 10, 9, - 10, - 11, - 11, - 9, - 12, - 11, 7, - 9, - 12, - 9, 12 ] }, - "register": 6 + "register": 20 }, "enableBy": "register" }, @@ -2448,7 +2034,7 @@ 0 ] }, - "register": 7 + "register": 10 }, "enableBy": "register" }, @@ -2461,7 +2047,7 @@ "config": {}, "time": { "hooks": {}, - "register": 5 + "register": 9 }, "enableBy": "config" }, @@ -2474,7 +2060,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 2 }, "enableBy": "config" }, @@ -2487,7 +2073,7 @@ "config": {}, "time": { "hooks": {}, - "register": 13 + "register": 23 } }, "./node_modules/@umijs/preset-umi/dist/features/monorepo/redirect": { @@ -2499,7 +2085,7 @@ "config": {}, "time": { "hooks": {}, - "register": 32 + "register": 43 }, "enableBy": "config" }, @@ -2513,27 +2099,9 @@ "time": { "hooks": { "onGenerateFiles": [ - 17, - 1, - 1, - 2, - 1, - 1, - 1, - 1, - 2, - 1, - 1, - 1, - 2, - 1, - 1, - 1, 3, + 7, 1, - 2, - 2, - 2, 1, 1, 1, @@ -2545,16 +2113,12 @@ 1, 1, 1, - 0, + 2, 1, - 1, - 1, - 1, - 1, - 1 + 2 ] }, - "register": 2 + "register": 6 } }, "./node_modules/@umijs/preset-umi/dist/features/clickToComponent/clickToComponent": { @@ -2566,7 +2130,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 2 }, "enableBy": "config" }, @@ -2579,7 +2143,7 @@ "config": {}, "time": { "hooks": {}, - "register": 2 + "register": 4 }, "enableBy": "config" }, @@ -2605,7 +2169,7 @@ "config": {}, "time": { "hooks": {}, - "register": 2 + "register": 4 } }, "./node_modules/@umijs/preset-umi/dist/features/swc/swc": { @@ -2634,7 +2198,7 @@ "config": {}, "time": { "hooks": {}, - "register": 22 + "register": 9 } }, "./node_modules/@umijs/preset-umi/dist/features/mako/mako": { @@ -2646,7 +2210,7 @@ "config": {}, "time": { "hooks": {}, - "register": 6 + "register": 4 }, "enableBy": "config" }, @@ -2659,7 +2223,7 @@ "config": {}, "time": { "hooks": {}, - "register": 12 + "register": 3 } }, "./node_modules/@umijs/preset-umi/dist/features/hmrGuardian/hmrGuardian": { @@ -2671,7 +2235,7 @@ "config": {}, "time": { "hooks": {}, - "register": 29 + "register": 16 } }, "./node_modules/@umijs/preset-umi/dist/features/routePreloadOnLoad/routePreloadOnLoad": { @@ -2684,71 +2248,8 @@ "time": { "hooks": { "addHTMLHeadScripts": [ - 24, - 4, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + 27, + 27, 0, 0, 0, @@ -2800,10 +2301,14 @@ 0, 0, 0, + 0, + 0, + 0, + 0, 0 ] }, - "register": 87 + "register": 102 } }, "./node_modules/@umijs/preset-umi/dist/features/forget/forget": { @@ -2815,7 +2320,7 @@ "config": {}, "time": { "hooks": {}, - "register": 33 + "register": 4 }, "enableBy": "config" }, @@ -2829,10 +2334,10 @@ "time": { "hooks": { "modifyUniBundler": [ - 1844 + 1970 ] }, - "register": 3 + "register": 8 }, "enableBy": "register" }, @@ -2845,7 +2350,7 @@ "config": {}, "time": { "hooks": {}, - "register": 7 + "register": 16 }, "enableBy": "register" }, @@ -2858,7 +2363,7 @@ "config": {}, "time": { "hooks": {}, - "register": 65 + "register": 84 }, "enableBy": "register" }, @@ -2872,10 +2377,10 @@ "time": { "hooks": { "modifyAppData": [ - 18 + 24 ] }, - "register": 67 + "register": 126 } }, "./node_modules/@umijs/preset-umi/dist/commands/help": { @@ -2887,7 +2392,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 2 }, "enableBy": "register" }, @@ -2900,7 +2405,7 @@ "config": {}, "time": { "hooks": {}, - "register": 0 + "register": 1 }, "enableBy": "register" }, @@ -2926,7 +2431,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 2 }, "enableBy": "register" }, @@ -2939,7 +2444,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 2 }, "enableBy": "register" }, @@ -2952,7 +2457,7 @@ "config": {}, "time": { "hooks": {}, - "register": 5 + "register": 8 }, "enableBy": "register" }, @@ -2978,7 +2483,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 2 }, "enableBy": "register" }, @@ -2991,7 +2496,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 3 }, "enableBy": "register" }, @@ -3017,7 +2522,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 3 }, "enableBy": "register" }, @@ -3043,7 +2548,7 @@ "config": {}, "time": { "hooks": {}, - "register": 2 + "register": 3 }, "enableBy": "register" }, @@ -3056,7 +2561,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 2 }, "enableBy": "register" }, @@ -3069,7 +2574,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 2 }, "enableBy": "register" }, @@ -3082,7 +2587,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 2 }, "enableBy": "register" }, @@ -3108,7 +2613,7 @@ "config": {}, "time": { "hooks": {}, - "register": 1 + "register": 3 }, "enableBy": "register" }, @@ -3121,7 +2626,7 @@ "config": {}, "time": { "hooks": {}, - "register": 35 + "register": 66 }, "enableBy": "register" }, @@ -3134,7 +2639,7 @@ "config": {}, "time": { "hooks": {}, - "register": 3 + "register": 4 }, "enableBy": "register" }, @@ -3147,7 +2652,7 @@ "config": {}, "time": { "hooks": {}, - "register": 5 + "register": 82 }, "enableBy": "register" }, @@ -3207,6 +2712,7 @@ "IndependentSite/IndependentSiteAnalytics": { "path": "IndependentSite/IndependentSiteAnalytics", "id": "IndependentSite/IndependentSiteAnalytics", + "parentId": "@@/global-layout", "file": "IndependentSite/IndependentSiteAnalytics.tsx", "absPath": "/IndependentSite/IndependentSiteAnalytics", "__content": "import React, { useState, useEffect } from 'react';\nimport { Card, DatePicker, Select, Spin, Statistic, Row, Col, Tabs, Button } from 'antd';\nimport { Area, AreaChart, Bar, BarChart, Line, LineChart, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis, CartesianGrid, Legend } from 'recharts';\nimport { useParams } from 'react-router-dom';\n\nconst { RangePicker } = DatePicker;\nconst { Option } = Select;\nconst { TabPane } = Tabs;\n\ninterface SalesData {\n date: string;\n sales: number;\n orders: number;\n customers: number;\n}\n\ninterface ProductData {\n name: string;\n sales: number;\n quantity: number;\n}\n\ninterface TrafficData {\n date: string;\n visitors: number;\n pageViews: number;\n bounceRate: number;\n}\n\ninterface ConversionData {\n name: string;\n value: number;\n}\n\nconst IndependentSiteAnalytics: React.FC = () => {\n const { id } = useParams<{ id: string }>();\n const [loading, setLoading] = useState(false);\n const [dateRange, setDateRange] = useState(null);\n const [timeRange, setTimeRange] = useState('30d');\n const [salesData, setSalesData] = useState([]);\n const [productData, setProductData] = useState([]);\n const [trafficData, setTrafficData] = useState([]);\n const [conversionData, setConversionData] = useState([]);\n const [summary, setSummary] = useState({\n totalSales: 0,\n totalOrders: 0,\n totalCustomers: 0,\n conversionRate: 0,\n });\n\n useEffect(() => {\n fetchAnalyticsData();\n }, [id, timeRange, dateRange]);\n\n const fetchAnalyticsData = async () => {\n setLoading(true);\n // 模拟API调用\n setTimeout(() => {\n // 模拟销售数据\n const mockSalesData: SalesData[] = Array.from({ length: 30 }, (_, i) => ({\n date: `2026-03-${String(i + 1).padStart(2, '0')}`,\n sales: Math.random() * 1000 + 500,\n orders: Math.random() * 50 + 10,\n customers: Math.random() * 30 + 5,\n }));\n\n // 模拟商品数据\n const mockProductData: ProductData[] = [\n { name: 'Product 1', sales: 1500, quantity: 15 },\n { name: 'Product 2', sales: 2000, quantity: 20 },\n { name: 'Product 3', sales: 1000, quantity: 10 },\n { name: 'Product 4', sales: 500, quantity: 5 },\n { name: 'Product 5', sales: 800, quantity: 8 },\n ];\n\n // 模拟流量数据\n const mockTrafficData: TrafficData[] = Array.from({ length: 30 }, (_, i) => ({\n date: `2026-03-${String(i + 1).padStart(2, '0')}`,\n visitors: Math.random() * 500 + 100,\n pageViews: Math.random() * 1000 + 500,\n bounceRate: Math.random() * 50 + 20,\n }));\n\n // 模拟转化数据\n const mockConversionData: ConversionData[] = [\n { name: '已转化', value: 20 },\n { name: '未转化', value: 80 },\n ];\n\n // 模拟汇总数据\n const mockSummary = {\n totalSales: 5800,\n totalOrders: 78,\n totalCustomers: 55,\n conversionRate: 20,\n };\n\n setSalesData(mockSalesData);\n setProductData(mockProductData);\n setTrafficData(mockTrafficData);\n setConversionData(mockConversionData);\n setSummary(mockSummary);\n setLoading(false);\n }, 1000);\n };\n\n const handleTimeRangeChange = (value: string) => {\n setTimeRange(value);\n };\n\n const handleDateRangeChange = (dates: any) => {\n setDateRange(dates);\n };\n\n return (\n
\n
\n

独立站数据分析

\n
\n \n \n \n \n \n \n \n \n
\n
\n\n {loading ? (\n
\n \n
\n ) : (\n <>\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `${name}: ${(percent * 100).toFixed(0)}%`}\n outerRadius={150}\n fill=\"#8884d8\"\n dataKey=\"value\"\n />\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n
\n

地域分布图表

\n
\n
\n \n \n \n
\n

购买行为图表

\n
\n
\n \n \n \n \n \n )}\n \n );\n};\n\nexport default IndependentSiteAnalytics;", @@ -3216,6 +2722,7 @@ "IndependentSite/IndependentSiteProduct": { "path": "IndependentSite/IndependentSiteProduct", "id": "IndependentSite/IndependentSiteProduct", + "parentId": "@@/global-layout", "file": "IndependentSite/IndependentSiteProduct.tsx", "absPath": "/IndependentSite/IndependentSiteProduct", "__content": "import React, { useState, useEffect } from 'react';\nimport { Table, Button, Input, Select, message, Upload, Modal, Form } from 'antd';\nimport { PlusOutlined, EditOutlined, DeleteOutlined, UploadOutlined } from '@ant-design/icons';\nimport { useParams } from 'react-router-dom';\n\nconst { Option } = Select;\nconst { Search } = Input;\n\ninterface Product {\n id: string;\n name: string;\n sku: string;\n price: number;\n stock: number;\n status: 'published' | 'draft' | 'unpublished';\n createdAt: string;\n lastUpdated: string;\n}\n\nconst IndependentSiteProduct: React.FC = () => {\n const { id } = useParams<{ id: string }>();\n const [products, setProducts] = useState([]);\n const [loading, setLoading] = useState(false);\n const [filters, setFilters] = useState({\n status: '',\n search: '',\n });\n const [modalVisible, setModalVisible] = useState(false);\n const [form] = Form.useForm();\n\n useEffect(() => {\n fetchProducts();\n }, [id, filters]);\n\n const fetchProducts = async () => {\n setLoading(true);\n // 模拟API调用\n setTimeout(() => {\n const mockProducts: Product[] = [\n {\n id: '1',\n name: 'Test Product 1',\n sku: 'TP-001',\n price: 99.99,\n stock: 100,\n status: 'published',\n createdAt: '2026-03-01',\n lastUpdated: '2026-03-15',\n },\n {\n id: '2',\n name: 'Test Product 2',\n sku: 'TP-002',\n price: 199.99,\n stock: 50,\n status: 'draft',\n createdAt: '2026-03-05',\n lastUpdated: '2026-03-10',\n },\n {\n id: '3',\n name: 'Test Product 3',\n sku: 'TP-003',\n price: 299.99,\n stock: 25,\n status: 'unpublished',\n createdAt: '2026-03-10',\n lastUpdated: '2026-03-18',\n },\n ];\n setProducts(mockProducts);\n setLoading(false);\n }, 500);\n };\n\n const handleAddProduct = () => {\n form.resetFields();\n setModalVisible(true);\n };\n\n const handleEditProduct = (record: Product) => {\n form.setFieldsValue(record);\n setModalVisible(true);\n };\n\n const handleDeleteProduct = (id: string) => {\n message.success('商品已删除');\n fetchProducts();\n };\n\n const handlePublishProduct = (id: string) => {\n message.success('商品已发布');\n fetchProducts();\n };\n\n const handleUnpublishProduct = (id: string) => {\n message.success('商品已下架');\n fetchProducts();\n };\n\n const handleSubmit = async (values: any) => {\n setLoading(true);\n // 模拟API调用\n setTimeout(() => {\n message.success('商品保存成功');\n setLoading(false);\n setModalVisible(false);\n fetchProducts();\n }, 1000);\n };\n\n const columns = [\n {\n title: '商品名称',\n dataIndex: 'name',\n key: 'name',\n },\n {\n title: 'SKU',\n dataIndex: 'sku',\n key: 'sku',\n },\n {\n title: '价格',\n dataIndex: 'price',\n key: 'price',\n render: (price: number) => `$${price.toFixed(2)}`,\n },\n {\n title: '库存',\n dataIndex: 'stock',\n key: 'stock',\n },\n {\n title: '状态',\n dataIndex: 'status',\n key: 'status',\n render: (status: string) => {\n const statusMap = {\n published: '已发布',\n draft: '草稿',\n unpublished: '未发布',\n };\n return statusMap[status] || status;\n },\n },\n {\n title: '创建时间',\n dataIndex: 'createdAt',\n key: 'createdAt',\n },\n {\n title: '最后更新',\n dataIndex: 'lastUpdated',\n key: 'lastUpdated',\n },\n {\n title: '操作',\n key: 'action',\n render: (_: any, record: Product) => (\n
\n }\n onClick={() => handleEditProduct(record)}\n >\n 编辑\n \n }\n onClick={() => handleDeleteProduct(record.id)}\n >\n 删除\n \n {record.status !== 'published' && (\n handlePublishProduct(record.id)}\n >\n 发布\n \n )}\n {record.status === 'published' && (\n handleUnpublishProduct(record.id)}\n >\n 下架\n \n )}\n
\n ),\n },\n ];\n\n return (\n
\n
\n

独立站商品管理

\n }\n onClick={handleAddProduct}\n >\n 添加商品\n \n
\n\n
\n setFilters({ ...filters, search: e.target.value })}\n />\n setFilters({ ...filters, status: value })}\n >\n \n \n \n \n \n
\n\n \n\n setModalVisible(false)}\n footer={null}\n >\n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n\n \n \n \n\n \n \n \n \n \n \n
\n );\n};\n\nexport default IndependentSiteProduct;", @@ -3225,6 +2732,7 @@ "IndependentSite/IndependentSiteConfig": { "path": "IndependentSite/IndependentSiteConfig", "id": "IndependentSite/IndependentSiteConfig", + "parentId": "@@/global-layout", "file": "IndependentSite/IndependentSiteConfig.tsx", "absPath": "/IndependentSite/IndependentSiteConfig", "__content": "import React, { useState, useEffect } from 'react';\nimport { Form, Input, Select, Button, message, Switch, Card, Tabs } from 'antd';\nimport { useNavigate, useParams } from 'react-router-dom';\n\nconst { Option } = Select;\nconst { TabPane } = Tabs;\n\nconst IndependentSiteConfig: React.FC = () => {\n const navigate = useNavigate();\n const { id } = useParams<{ id: string }>();\n const [form] = Form.useForm();\n const [loading, setLoading] = useState(false);\n const [siteData, setSiteData] = useState({});\n\n useEffect(() => {\n fetchSiteData();\n }, [id]);\n\n const fetchSiteData = async () => {\n setLoading(true);\n // 模拟API调用\n setTimeout(() => {\n const mockData = {\n id: id,\n name: 'Test Store 1',\n url: 'https://test-store-1.com',\n platform: 'Shopify',\n apiKey: 'test-api-key',\n apiSecret: 'test-api-secret',\n webhookUrl: 'https://webhook.example.com',\n theme: 'default',\n description: 'Test store for demonstration',\n isActive: true,\n notes: 'This is a test store',\n settings: {\n inventorySync: true,\n orderSync: true,\n pricingSync: true,\n autoPublish: false,\n currency: 'USD',\n language: 'en',\n },\n shipping: {\n freeShipping: true,\n freeShippingThreshold: 50,\n shippingZones: ['US', 'CA', 'EU'],\n },\n payment: {\n paypal: true,\n stripe: true,\n creditCard: true,\n applePay: false,\n },\n };\n setSiteData(mockData);\n form.setFieldsValue(mockData);\n setLoading(false);\n }, 500);\n };\n\n const onFinish = async (values: any) => {\n setLoading(true);\n // 模拟API调用\n setTimeout(() => {\n message.success('配置保存成功');\n setLoading(false);\n }, 1000);\n };\n\n const handleCancel = () => {\n navigate('/independent-site');\n };\n\n return (\n
\n
\n

独立站配置 - {siteData.name}

\n
\n\n \n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n
\n );\n};\n\nexport default IndependentSiteConfig;", @@ -3234,6 +2742,7 @@ "IndependentSite/IndependentSiteCreate": { "path": "IndependentSite/IndependentSiteCreate", "id": "IndependentSite/IndependentSiteCreate", + "parentId": "@@/global-layout", "file": "IndependentSite/IndependentSiteCreate.tsx", "absPath": "/IndependentSite/IndependentSiteCreate", "__content": "import React, { useState } from 'react';\nimport { Form, Input, Select, Button, message, Upload, Checkbox } from 'antd';\nimport { UploadOutlined } from '@ant-design/icons';\nimport { useNavigate } from 'react-router-dom';\n\nconst { Option } = Select;\n\nconst IndependentSiteCreate: React.FC = () => {\n const navigate = useNavigate();\n const [form] = Form.useForm();\n const [loading, setLoading] = useState(false);\n\n const onFinish = async (values: any) => {\n setLoading(true);\n // 模拟API调用\n setTimeout(() => {\n message.success('独立站创建成功');\n setLoading(false);\n navigate('/independent-site');\n }, 1000);\n };\n\n const handleCancel = () => {\n navigate('/independent-site');\n };\n\n return (\n
\n
\n

创建独立站

\n
\n\n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n\n \n \n \n\n \n 创建后立即启用\n \n\n \n \n \n\n \n \n \n \n \n
\n );\n};\n\nexport default IndependentSiteCreate;", @@ -3243,6 +2752,7 @@ "Compliance/CertificateExpiryReminder": { "path": "Compliance/CertificateExpiryReminder", "id": "Compliance/CertificateExpiryReminder", + "parentId": "@@/global-layout", "file": "Compliance/CertificateExpiryReminder.tsx", "absPath": "/Compliance/CertificateExpiryReminder", "__content": "import React, { useState, useEffect } from 'react';\nimport {\n Card,\n Table,\n Button,\n Space,\n Modal,\n Tag,\n Row,\n Col,\n Statistic,\n Alert,\n List,\n Badge,\n Tooltip,\n Switch,\n message,\n Divider,\n Timeline,\n} from 'antd';\nimport {\n BellOutlined,\n ClockCircleOutlined,\n WarningOutlined,\n CheckCircleOutlined,\n CloseCircleOutlined,\n SettingOutlined,\n MailOutlined,\n SyncOutlined,\n FileTextOutlined,\n} from '@ant-design/icons';\nimport dayjs from 'dayjs';\n\ninterface ExpiryReminder {\n id: string;\n certNo: string;\n certName: string;\n certType: string;\n productName: string;\n expiryDate: string;\n daysRemaining: number;\n status: 'CRITICAL' | 'WARNING' | 'NOTICE' | 'SAFE';\n reminderSent: boolean;\n lastReminderDate?: string;\n nextReminderDate?: string;\n actions: string[];\n}\n\nconst MOCK_REMINDERS: ExpiryReminder[] = [\n {\n id: '1',\n certNo: 'FCC-2025-045',\n certName: 'FCC Declaration',\n certType: 'FCC',\n productName: 'Control Module B',\n expiryDate: '2025-06-19',\n daysRemaining: 93,\n status: 'WARNING',\n reminderSent: true,\n lastReminderDate: '2025-03-15',\n nextReminderDate: '2025-04-15',\n actions: ['Initiate renewal process', 'Contact testing lab'],\n },\n {\n id: '2',\n certNo: 'RoHS-2024-112',\n certName: 'RoHS Compliance Certificate',\n certType: 'RoHS',\n productName: 'Power Supply Unit C',\n expiryDate: '2025-02-28',\n daysRemaining: -18,\n status: 'CRITICAL',\n reminderSent: true,\n lastReminderDate: '2025-03-01',\n actions: ['Immediate re-certification required', 'Suspend product listing'],\n },\n {\n id: '3',\n certNo: 'CE-2026-001',\n certName: 'CE Declaration of Conformity',\n certType: 'CE',\n productName: 'Industrial Sensor A',\n expiryDate: '2027-01-14',\n daysRemaining: 663,\n status: 'SAFE',\n reminderSent: false,\n actions: [],\n },\n {\n id: '4',\n certNo: 'UL-2026-008',\n certName: 'UL Safety Certification',\n certType: 'UL',\n productName: 'Communication Gateway D',\n expiryDate: '2027-01-31',\n daysRemaining: 680,\n status: 'SAFE',\n reminderSent: false,\n actions: [],\n },\n {\n id: '5',\n certNo: 'ISO-2025-022',\n certName: 'ISO 9001:2015',\n certType: 'ISO',\n productName: 'Company-wide',\n expiryDate: '2025-09-30',\n daysRemaining: 196,\n status: 'NOTICE',\n reminderSent: true,\n lastReminderDate: '2025-03-01',\n nextReminderDate: '2025-06-01',\n actions: ['Schedule surveillance audit', 'Update quality manual'],\n },\n];\n\nconst STATUS_CONFIG: Record = {\n CRITICAL: { color: 'error', text: 'Critical', icon: , bgColor: '#fff2f0' },\n WARNING: { color: 'warning', text: 'Warning', icon: , bgColor: '#fffbe6' },\n NOTICE: { color: 'processing', text: 'Notice', icon: , bgColor: '#e6f7ff' },\n SAFE: { color: 'success', text: 'Safe', icon: , bgColor: '#f6ffed' },\n};\n\nexport const CertificateExpiryReminder: React.FC = () => {\n const [loading, setLoading] = useState(false);\n const [reminders, setReminders] = useState(MOCK_REMINDERS);\n const [settingsModalVisible, setSettingsModalVisible] = useState(false);\n const [emailEnabled, setEmailEnabled] = useState(true);\n const [autoRenewal, setAutoRenewal] = useState(false);\n\n useEffect(() => {\n checkReminders();\n }, []);\n\n const checkReminders = () => {\n setLoading(true);\n setTimeout(() => {\n setLoading(false);\n message.success('Reminders checked');\n }, 1000);\n };\n\n const handleSendReminder = (reminder: ExpiryReminder) => {\n Modal.confirm({\n title: 'Send Reminder',\n content: `Send expiry reminder for ${reminder.certNo}?`,\n onOk: () => {\n setReminders(reminders.map(r => \n r.id === reminder.id ? { ...r, reminderSent: true, lastReminderDate: new Date().toISOString().split('T')[0] } : r\n ));\n message.success('Reminder sent successfully');\n },\n });\n };\n\n const handleInitiateRenewal = (reminder: ExpiryReminder) => {\n Modal.confirm({\n title: 'Initiate Renewal',\n content: `Start renewal process for ${reminder.certNo}?`,\n onOk: () => {\n message.success('Renewal process initiated');\n },\n });\n };\n\n const getStatusTag = (status: string, days: number) => {\n const config = STATUS_CONFIG[status];\n if (days < 0) {\n return }>EXPIRED;\n }\n return {config.text};\n };\n\n const criticalCount = reminders.filter(r => r.status === 'CRITICAL' || r.daysRemaining < 0).length;\n const warningCount = reminders.filter(r => r.status === 'WARNING').length;\n const noticeCount = reminders.filter(r => r.status === 'NOTICE').length;\n\n return (\n
\n \n
\n \n } \n />\n \n \n \n \n } \n />\n \n \n \n \n } \n />\n \n \n \n \n } \n />\n \n \n \n\n {criticalCount > 0 && (\n }\n style={{ marginBottom: 24 }}\n action={\n \n }\n />\n )}\n\n \n \n \n \n }\n >\n a.daysRemaining - b.daysRemaining)}\n renderItem={(item) => (\n \n ,\n ,\n ]}\n >\n
\n
\n
Email Notifications
\n \n
\n Receive email notifications for certificate expiry reminders\n
\n
\n\n
\n
Auto-initiate Renewal
\n \n
\n Automatically initiate renewal process when certificate enters warning status\n
\n
\n\n Reminder Schedule\n\n (\n \n \n \n )}\n />\n
\n \n \n );\n};\n\nexport default CertificateExpiryReminder;\n", @@ -3252,6 +2762,7 @@ "IndependentSite/IndependentSiteOrder": { "path": "IndependentSite/IndependentSiteOrder", "id": "IndependentSite/IndependentSiteOrder", + "parentId": "@@/global-layout", "file": "IndependentSite/IndependentSiteOrder.tsx", "absPath": "/IndependentSite/IndependentSiteOrder", "__content": "import React, { useState, useEffect } from 'react';\nimport { Table, Button, Input, Select, DatePicker, message, Modal, Form } from 'antd';\nimport { EyeOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';\nimport { useParams } from 'react-router-dom';\n\nconst { Option } = Select;\nconst { RangePicker } = DatePicker;\nconst { Search } = Input;\n\ninterface Order {\n id: string;\n orderNumber: string;\n customerName: string;\n total: number;\n status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'refunded';\n paymentMethod: string;\n createdAt: string;\n updatedAt: string;\n}\n\nconst IndependentSiteOrder: React.FC = () => {\n const { id } = useParams<{ id: string }>();\n const [orders, setOrders] = useState([]);\n const [loading, setLoading] = useState(false);\n const [filters, setFilters] = useState({\n status: '',\n paymentMethod: '',\n search: '',\n dateRange: null as any,\n });\n const [modalVisible, setModalVisible] = useState(false);\n const [selectedOrder, setSelectedOrder] = useState(null);\n const [form] = Form.useForm();\n\n useEffect(() => {\n fetchOrders();\n }, [id, filters]);\n\n const fetchOrders = async () => {\n setLoading(true);\n // 模拟API调用\n setTimeout(() => {\n const mockOrders: Order[] = [\n {\n id: '1',\n orderNumber: 'ORD-2026-001',\n customerName: 'John Doe',\n total: 199.99,\n status: 'processing',\n paymentMethod: 'PayPal',\n createdAt: '2026-03-15',\n updatedAt: '2026-03-16',\n },\n {\n id: '2',\n orderNumber: 'ORD-2026-002',\n customerName: 'Jane Smith',\n total: 299.99,\n status: 'shipped',\n paymentMethod: 'Stripe',\n createdAt: '2026-03-16',\n updatedAt: '2026-03-17',\n },\n {\n id: '3',\n orderNumber: 'ORD-2026-003',\n customerName: 'Bob Johnson',\n total: 99.99,\n status: 'delivered',\n paymentMethod: 'Credit Card',\n createdAt: '2026-03-17',\n updatedAt: '2026-03-18',\n },\n {\n id: '4',\n orderNumber: 'ORD-2026-004',\n customerName: 'Alice Brown',\n total: 499.99,\n status: 'cancelled',\n paymentMethod: 'PayPal',\n createdAt: '2026-03-18',\n updatedAt: '2026-03-18',\n },\n ];\n setOrders(mockOrders);\n setLoading(false);\n }, 500);\n };\n\n const handleViewOrder = (order: Order) => {\n setSelectedOrder(order);\n form.setFieldsValue(order);\n setModalVisible(true);\n };\n\n const handleUpdateOrder = async (values: any) => {\n setLoading(true);\n // 模拟API调用\n setTimeout(() => {\n message.success('订单状态更新成功');\n setLoading(false);\n setModalVisible(false);\n fetchOrders();\n }, 1000);\n };\n\n const handleCancelOrder = (id: string) => {\n message.success('订单已取消');\n fetchOrders();\n };\n\n const handleRefundOrder = (id: string) => {\n message.success('订单已退款');\n fetchOrders();\n };\n\n const columns = [\n {\n title: '订单号',\n dataIndex: 'orderNumber',\n key: 'orderNumber',\n },\n {\n title: '客户名称',\n dataIndex: 'customerName',\n key: 'customerName',\n },\n {\n title: '总金额',\n dataIndex: 'total',\n key: 'total',\n render: (total: number) => `$${total.toFixed(2)}`,\n },\n {\n title: '状态',\n dataIndex: 'status',\n key: 'status',\n render: (status: string) => {\n const statusMap = {\n pending: '待处理',\n processing: '处理中',\n shipped: '已发货',\n delivered: '已送达',\n cancelled: '已取消',\n refunded: '已退款',\n };\n return statusMap[status] || status;\n },\n },\n {\n title: '支付方式',\n dataIndex: 'paymentMethod',\n key: 'paymentMethod',\n },\n {\n title: '创建时间',\n dataIndex: 'createdAt',\n key: 'createdAt',\n },\n {\n title: '更新时间',\n dataIndex: 'updatedAt',\n key: 'updatedAt',\n },\n {\n title: '操作',\n key: 'action',\n render: (_: any, record: Order) => (\n
\n }\n onClick={() => handleViewOrder(record)}\n >\n 查看\n \n }\n onClick={() => handleViewOrder(record)}\n >\n 编辑\n \n {record.status !== 'cancelled' && record.status !== 'refunded' && (\n <>\n handleCancelOrder(record.id)}\n >\n 取消\n \n handleRefundOrder(record.id)}\n >\n 退款\n \n \n )}\n
\n ),\n },\n ];\n\n return (\n
\n
\n

独立站订单管理

\n
\n\n
\n setFilters({ ...filters, search: e.target.value })}\n />\n setFilters({ ...filters, status: value })}\n >\n \n \n \n \n \n \n \n \n setFilters({ ...filters, paymentMethod: value })}\n >\n \n \n \n \n \n \n setFilters({ ...filters, dateRange: dates })}\n />\n
\n\n \n\n {selectedOrder && (\n setModalVisible(false)}\n footer={null}\n width={800}\n >\n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n )}\n
\n );\n};\n\nexport default IndependentSiteOrder;", @@ -3261,6 +2772,7 @@ "IndependentSite/IndependentSiteList": { "path": "IndependentSite/IndependentSiteList", "id": "IndependentSite/IndependentSiteList", + "parentId": "@@/global-layout", "file": "IndependentSite/IndependentSiteList.tsx", "absPath": "/IndependentSite/IndependentSiteList", "__content": "import React, { useState, useEffect } from 'react';\nimport { Table, Button, Input, Select, DatePicker, message } from 'antd';\nimport { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';\nimport { useNavigate } from 'react-router-dom';\n\nconst { Option } = Select;\nconst { RangePicker } = DatePicker;\nconst { Search } = Input;\n\ninterface Site {\n id: string;\n name: string;\n url: string;\n status: 'active' | 'inactive' | 'building';\n platform: string;\n createdAt: string;\n lastUpdated: string;\n}\n\nconst IndependentSiteList: React.FC = () => {\n const navigate = useNavigate();\n const [sites, setSites] = useState([]);\n const [loading, setLoading] = useState(false);\n const [filters, setFilters] = useState({\n status: '',\n platform: '',\n search: '',\n dateRange: null as any,\n });\n\n useEffect(() => {\n fetchSites();\n }, [filters]);\n\n const fetchSites = async () => {\n setLoading(true);\n // 模拟API调用\n setTimeout(() => {\n const mockSites: Site[] = [\n {\n id: '1',\n name: 'Test Store 1',\n url: 'https://test-store-1.com',\n status: 'active',\n platform: 'Shopify',\n createdAt: '2026-03-01',\n lastUpdated: '2026-03-15',\n },\n {\n id: '2',\n name: 'Test Store 2',\n url: 'https://test-store-2.com',\n status: 'inactive',\n platform: 'WooCommerce',\n createdAt: '2026-03-05',\n lastUpdated: '2026-03-10',\n },\n {\n id: '3',\n name: 'Test Store 3',\n url: 'https://test-store-3.com',\n status: 'building',\n platform: 'Shopify',\n createdAt: '2026-03-10',\n lastUpdated: '2026-03-18',\n },\n ];\n setSites(mockSites);\n setLoading(false);\n }, 500);\n };\n\n const handleCreate = () => {\n navigate('/independent-site/create');\n };\n\n const handleEdit = (id: string) => {\n navigate(`/independent-site/config/${id}`);\n };\n\n const handleView = (id: string) => {\n navigate(`/independent-site/config/${id}`);\n };\n\n const handleDelete = (id: string) => {\n message.success('站点已删除');\n fetchSites();\n };\n\n const handleProductManage = (id: string) => {\n navigate(`/independent-site/product/${id}`);\n };\n\n const handleOrderManage = (id: string) => {\n navigate(`/independent-site/order/${id}`);\n };\n\n const handleAnalytics = (id: string) => {\n navigate(`/independent-site/analytics/${id}`);\n };\n\n const columns = [\n {\n title: '站点名称',\n dataIndex: 'name',\n key: 'name',\n },\n {\n title: 'URL',\n dataIndex: 'url',\n key: 'url',\n render: (url: string) => {url},\n },\n {\n title: '状态',\n dataIndex: 'status',\n key: 'status',\n render: (status: string) => {\n const statusMap = {\n active: '启用',\n inactive: '停用',\n building: '建设中',\n };\n return statusMap[status] || status;\n },\n },\n {\n title: '平台',\n dataIndex: 'platform',\n key: 'platform',\n },\n {\n title: '创建时间',\n dataIndex: 'createdAt',\n key: 'createdAt',\n },\n {\n title: '最后更新',\n dataIndex: 'lastUpdated',\n key: 'lastUpdated',\n },\n {\n title: '操作',\n key: 'action',\n render: (_: any, record: Site) => (\n
\n }\n onClick={() => handleView(record.id)}\n >\n 查看\n \n }\n onClick={() => handleEdit(record.id)}\n >\n 配置\n \n handleProductManage(record.id)}\n >\n 商品管理\n \n handleOrderManage(record.id)}\n >\n 订单管理\n \n handleAnalytics(record.id)}\n >\n 数据分析\n \n }\n onClick={() => handleDelete(record.id)}\n >\n 删除\n \n
\n ),\n },\n ];\n\n return (\n
\n
\n

独立站管理

\n }\n onClick={handleCreate}\n >\n 创建独立站\n \n
\n\n
\n setFilters({ ...filters, search: e.target.value })}\n />\n setFilters({ ...filters, status: value })}\n >\n \n \n \n \n \n setFilters({ ...filters, platform: value })}\n >\n \n \n \n \n \n setFilters({ ...filters, dateRange: dates })}\n />\n
\n\n \n
\n );\n};\n\nexport default IndependentSiteList;", @@ -3270,24 +2782,37 @@ "Merchant/MerchantSettlementManage": { "path": "Merchant/MerchantSettlementManage", "id": "Merchant/MerchantSettlementManage", + "parentId": "@@/global-layout", "file": "Merchant/MerchantSettlementManage.tsx", "absPath": "/Merchant/MerchantSettlementManage", "__content": "import React, { useState, useEffect } from 'react';\nimport { Table, Input, Button, Select, DatePicker, message, Card, Typography, Tag, Modal, Form } from 'antd';\nimport { SearchOutlined, EyeOutlined, DownloadOutlined, ReloadOutlined } from '@ant-design/icons';\nimport { getMerchantSettlements, processSettlement } from '../../services/merchantSettlementService';\n\nconst { Option } = Select;\nconst { Title, Text } = Typography;\nconst { RangePicker } = DatePicker;\nconst { Item } = Form;\n\ninterface Settlement {\n id: string;\n merchantId: string;\n merchantName: string;\n periodStart: string;\n periodEnd: string;\n totalAmount: number;\n status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED';\n createdAt: string;\n updatedAt: string;\n}\n\nconst MerchantSettlementManage: React.FC = () => {\n const [settlements, setSettlements] = useState([]);\n const [merchants, setMerchants] = useState<{ id: string; companyName: string }[]>([]);\n const [loading, setLoading] = useState(false);\n const [searchText, setSearchText] = useState('');\n const [merchantFilter, setMerchantFilter] = useState('');\n const [statusFilter, setStatusFilter] = useState('');\n const [dateRange, setDateRange] = useState<[string, string] | null>(null);\n const [isModalVisible, setIsModalVisible] = useState(false);\n const [selectedSettlement, setSelectedSettlement] = useState(null);\n const [form] = Form.useForm();\n\n const fetchSettlements = async () => {\n setLoading(true);\n try {\n const response = await getMerchantSettlements();\n setSettlements(response.data);\n } catch (error) {\n message.error('获取结算列表失败');\n console.error('获取结算列表失败:', error);\n } finally {\n setLoading(false);\n }\n };\n\n const fetchMerchants = async () => {\n try {\n // 实际项目中应该调用获取商户列表的API\n // 这里模拟数据\n setMerchants([\n { id: '1', companyName: '商户A' },\n { id: '2', companyName: '商户B' },\n { id: '3', companyName: '商户C' },\n ]);\n } catch (error) {\n console.error('获取商户列表失败:', error);\n }\n };\n\n useEffect(() => {\n fetchMerchants();\n fetchSettlements();\n }, []);\n\n const handleSearch = (value: string) => {\n setSearchText(value);\n };\n\n const handleMerchantFilter = (value: string) => {\n setMerchantFilter(value);\n };\n\n const handleStatusFilter = (value: string) => {\n setStatusFilter(value);\n };\n\n const handleDateRangeChange = (dates: any) => {\n if (dates) {\n setDateRange([dates[0].format('YYYY-MM-DD'), dates[1].format('YYYY-MM-DD')]);\n } else {\n setDateRange(null);\n }\n };\n\n const filteredSettlements = settlements.filter(settlement => {\n const matchesSearch = settlement.id.includes(searchText) ||\n settlement.merchantName.toLowerCase().includes(searchText.toLowerCase());\n const matchesMerchant = merchantFilter ? settlement.merchantId === merchantFilter : true;\n const matchesStatus = statusFilter ? settlement.status === statusFilter : true;\n return matchesSearch && matchesMerchant && matchesStatus;\n });\n\n const handleView = (settlement: Settlement) => {\n setSelectedSettlement(settlement);\n setIsModalVisible(true);\n };\n\n const handleProcess = (settlement: Settlement) => {\n // 调用API处理结算\n message.info(`处理结算: ${settlement.id}`);\n };\n\n const handleDownload = (settlement: Settlement) => {\n // 下载结算单\n message.info(`下载结算单: ${settlement.id}`);\n };\n\n const handleRefresh = () => {\n fetchSettlements();\n };\n\n const getStatusTag = (status: string) => {\n const statusConfig: Record = {\n PENDING: { color: 'blue', text: '待处理' },\n PROCESSING: { color: 'orange', text: '处理中' },\n COMPLETED: { color: 'green', text: '已完成' },\n FAILED: { color: 'red', text: '失败' },\n };\n const config = statusConfig[status] || { color: 'default', text: status };\n return {config.text};\n };\n\n const columns = [\n {\n title: '结算单ID',\n dataIndex: 'id',\n key: 'id',\n ellipsis: true,\n },\n {\n title: '商户',\n dataIndex: 'merchantName',\n key: 'merchantName',\n ellipsis: true,\n },\n {\n title: '结算周期',\n key: 'period',\n render: (_, settlement: Settlement) => (\n \n {settlement.periodStart} 至 {settlement.periodEnd}\n \n ),\n },\n {\n title: '结算金额',\n dataIndex: 'totalAmount',\n key: 'totalAmount',\n render: (amount: number) => ${amount.toFixed(2)},\n },\n {\n title: '状态',\n dataIndex: 'status',\n key: 'status',\n render: (status: string) => getStatusTag(status),\n },\n {\n title: '创建时间',\n dataIndex: 'createdAt',\n key: 'createdAt',\n ellipsis: true,\n },\n {\n title: '操作',\n key: 'action',\n render: (_: any, settlement: Settlement) => (\n
\n \n \n {settlement.status === 'PENDING' && (\n \n )}\n
\n ),\n },\n ];\n\n return (\n \n
\n 多商户结算管理\n \n
\n\n
\n }\n style={{ width: 300 }}\n onChange={(e) => handleSearch(e.target.value)}\n />\n \n {merchants.map(merchant => (\n \n ))}\n \n \n \n \n \n \n \n \n
\n\n \n\n setIsModalVisible(false)}\n footer={[\n ,\n ]}\n width={600}\n >\n {selectedSettlement && (\n
\n
\n 结算单ID: {selectedSettlement.id}\n
\n
\n 商户: {selectedSettlement.merchantName}\n
\n
\n 结算周期: {selectedSettlement.periodStart} 至 {selectedSettlement.periodEnd}\n
\n
\n 结算金额: ${selectedSettlement.totalAmount.toFixed(2)}\n
\n
\n 状态: {getStatusTag(selectedSettlement.status)}\n
\n
\n 创建时间: {selectedSettlement.createdAt}\n
\n
\n 更新时间: {selectedSettlement.updatedAt}\n
\n
\n 结算明细\n `$${amount.toFixed(2)}` },\n ]}\n dataSource={[\n { key: '1', item: '商品销售', amount: selectedSettlement.totalAmount * 0.9 },\n { key: '2', item: '平台服务费', amount: selectedSettlement.totalAmount * 0.1 },\n ]}\n pagination={false}\n />\n
\n
\n )}\n \n
\n );\n};\n\nexport default MerchantSettlementManage;", "__isJSFile": true, "__absFile": "D:/trae_projects/makemd/makemd/dashboard/src/pages/Merchant/MerchantSettlementManage.tsx" }, + "Settings/PlatformAccountConfig": { + "path": "Settings/PlatformAccountConfig", + "id": "Settings/PlatformAccountConfig", + "parentId": "@@/global-layout", + "file": "Settings/PlatformAccountConfig.tsx", + "absPath": "/Settings/PlatformAccountConfig", + "__content": "import React, { useState, useEffect } from 'react';\nimport {\n Card,\n Table,\n Button,\n Space,\n Modal,\n Form,\n Input,\n Select,\n Switch,\n Tag,\n message,\n Popconfirm,\n Tooltip,\n Row,\n Col,\n Statistic,\n Badge,\n} from 'antd';\nimport {\n PlusOutlined,\n EditOutlined,\n DeleteOutlined,\n CheckCircleOutlined,\n CloseCircleOutlined,\n SyncOutlined,\n ApiOutlined,\n ShopOutlined,\n LinkOutlined,\n} from '@ant-design/icons';\nimport type { ColumnsType } from 'antd/es/table';\n\nconst { Option } = Select;\nconst { Password } = Input;\n\ninterface PlatformAccount {\n id: string;\n platform: 'AMAZON' | 'EBAY' | 'SHOPIFY' | 'SHOPEE' | 'LAZADA' | 'ALIBABA';\n accountName: string;\n shopId: string;\n shopName: string;\n region: string;\n status: 'ACTIVE' | 'INACTIVE' | 'EXPIRED' | 'ERROR';\n apiConnected: boolean;\n lastSync: string;\n tokenExpiry: string;\n autoSync: boolean;\n syncInterval: number;\n}\n\nconst PLATFORM_CONFIG: Record = {\n AMAZON: { color: 'orange', text: 'Amazon', icon: },\n EBAY: { color: 'blue', text: 'eBay', icon: },\n SHOPIFY: { color: 'green', text: 'Shopify', icon: },\n SHOPEE: { color: 'red', text: 'Shopee', icon: },\n LAZADA: { color: 'purple', text: 'Lazada', icon: },\n ALIBABA: { color: 'orange', text: 'Alibaba', icon: },\n};\n\nconst STATUS_CONFIG: Record = {\n ACTIVE: { color: 'success', text: 'Active' },\n INACTIVE: { color: 'default', text: 'Inactive' },\n EXPIRED: { color: 'warning', text: 'Expired' },\n ERROR: { color: 'error', text: 'Error' },\n};\n\nconst PlatformAccountConfig: React.FC = () => {\n const [loading, setLoading] = useState(false);\n const [accounts, setAccounts] = useState([]);\n const [modalVisible, setModalVisible] = useState(false);\n const [editingAccount, setEditingAccount] = useState(null);\n const [testLoading, setTestLoading] = useState(false);\n const [form] = Form.useForm();\n\n useEffect(() => {\n fetchAccounts();\n }, []);\n\n const fetchAccounts = async () => {\n setLoading(true);\n try {\n const mockAccounts: PlatformAccount[] = [\n {\n id: '1',\n platform: 'AMAZON',\n accountName: 'US Store',\n shopId: 'AMZ-US-001',\n shopName: 'Amazon US Store',\n region: 'US',\n status: 'ACTIVE',\n apiConnected: true,\n lastSync: '2026-03-18 10:30:00',\n tokenExpiry: '2026-06-18',\n autoSync: true,\n syncInterval: 30,\n },\n {\n id: '2',\n platform: 'EBAY',\n accountName: 'eBay Outlet',\n shopId: 'EB-001',\n shopName: 'eBay Outlet Store',\n region: 'US',\n status: 'ACTIVE',\n apiConnected: true,\n lastSync: '2026-03-18 09:15:00',\n tokenExpiry: '2026-05-15',\n autoSync: true,\n syncInterval: 60,\n },\n {\n id: '3',\n platform: 'SHOPIFY',\n accountName: 'Brand Store',\n shopId: 'SH-BRAND-001',\n shopName: 'Shopify Brand Store',\n region: 'US',\n status: 'ACTIVE',\n apiConnected: true,\n lastSync: '2026-03-18 08:00:00',\n tokenExpiry: '2027-03-18',\n autoSync: false,\n syncInterval: 120,\n },\n {\n id: '4',\n platform: 'SHOPEE',\n accountName: 'Shopee Mall',\n shopId: 'SP-MALL-001',\n shopName: 'Shopee Mall Store',\n region: 'SG',\n status: 'EXPIRED',\n apiConnected: false,\n lastSync: '2026-03-10 14:00:00',\n tokenExpiry: '2026-03-15',\n autoSync: true,\n syncInterval: 30,\n },\n ];\n setAccounts(mockAccounts);\n } catch (error) {\n message.error('Failed to load platform accounts');\n } finally {\n setLoading(false);\n }\n };\n\n const handleAdd = () => {\n setEditingAccount(null);\n form.resetFields();\n setModalVisible(true);\n };\n\n const handleEdit = (record: PlatformAccount) => {\n setEditingAccount(record);\n form.setFieldsValue(record);\n setModalVisible(true);\n };\n\n const handleDelete = (id: string) => {\n setAccounts(accounts.filter(a => a.id !== id));\n message.success('Account deleted successfully');\n };\n\n const handleSubmit = async () => {\n try {\n const values = await form.validateFields();\n if (editingAccount) {\n setAccounts(accounts.map(a => \n a.id === editingAccount.id ? { ...a, ...values } : a\n ));\n message.success('Account updated successfully');\n } else {\n const newAccount: PlatformAccount = {\n id: `${Date.now()}`,\n ...values,\n status: 'INACTIVE',\n apiConnected: false,\n lastSync: '-',\n tokenExpiry: '-',\n };\n setAccounts([...accounts, newAccount]);\n message.success('Account added successfully');\n }\n setModalVisible(false);\n } catch (error) {\n console.error('Form validation failed:', error);\n }\n };\n\n const handleTestConnection = async () => {\n setTestLoading(true);\n try {\n await new Promise(resolve => setTimeout(resolve, 1500));\n message.success('API connection test successful');\n } catch (error) {\n message.error('API connection test failed');\n } finally {\n setTestLoading(false);\n }\n };\n\n const handleSync = async (id: string) => {\n message.loading('Starting sync...', 1);\n await new Promise(resolve => setTimeout(resolve, 1000));\n setAccounts(accounts.map(a => \n a.id === id ? { ...a, lastSync: new Date().toISOString().replace('T', ' ').slice(0, 19) } : a\n ));\n message.success('Sync completed');\n };\n\n const handleToggleAutoSync = (id: string, checked: boolean) => {\n setAccounts(accounts.map(a => \n a.id === id ? { ...a, autoSync: checked } : a\n ));\n message.success(`Auto sync ${checked ? 'enabled' : 'disabled'}`);\n };\n\n const stats = {\n total: accounts.length,\n active: accounts.filter(a => a.status === 'ACTIVE').length,\n expired: accounts.filter(a => a.status === 'EXPIRED').length,\n error: accounts.filter(a => a.status === 'ERROR').length,\n };\n\n const columns: ColumnsType = [\n {\n title: 'Platform',\n dataIndex: 'platform',\n key: 'platform',\n width: 120,\n render: (platform) => {\n const config = PLATFORM_CONFIG[platform];\n return (\n \n {config.text}\n \n );\n },\n },\n {\n title: 'Account Name',\n dataIndex: 'accountName',\n key: 'accountName',\n width: 150,\n },\n {\n title: 'Shop ID',\n dataIndex: 'shopId',\n key: 'shopId',\n width: 130,\n },\n {\n title: 'Region',\n dataIndex: 'region',\n key: 'region',\n width: 80,\n },\n {\n title: 'Status',\n dataIndex: 'status',\n key: 'status',\n width: 100,\n render: (status) => {\n const config = STATUS_CONFIG[status];\n return {config.text};\n },\n },\n {\n title: 'API',\n dataIndex: 'apiConnected',\n key: 'apiConnected',\n width: 80,\n render: (connected) => (\n : }>\n {connected ? 'Connected' : 'Disconnected'}\n \n ),\n },\n {\n title: 'Last Sync',\n dataIndex: 'lastSync',\n key: 'lastSync',\n width: 160,\n },\n {\n title: 'Token Expiry',\n dataIndex: 'tokenExpiry',\n key: 'tokenExpiry',\n width: 120,\n render: (date) => {\n const isExpiringSoon = date !== '-' && new Date(date) < new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);\n return {date};\n },\n },\n {\n title: 'Auto Sync',\n dataIndex: 'autoSync',\n key: 'autoSync',\n width: 100,\n render: (checked, record) => (\n handleToggleAutoSync(record.id, c)} size=\"small\" />\n ),\n },\n {\n title: 'Actions',\n key: 'action',\n width: 180,\n fixed: 'right',\n render: (_, record) => (\n \n \n
\n \n } />\n \n \n \n \n } />\n \n \n \n \n } />\n \n \n \n \n \n \n \n \n\n } onClick={handleAdd}>\n Add Account\n \n }\n >\n `Total ${total} accounts` }}\n />\n \n\n setModalVisible(false)}\n width={600}\n >\n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default PlatformAccountConfig;\n", + "__isJSFile": true, + "__absFile": "D:/trae_projects/makemd/makemd/dashboard/src/pages/Settings/PlatformAccountConfig.tsx" + }, "Compliance/CertificateManage": { "path": "Compliance/CertificateManage", "id": "Compliance/CertificateManage", + "parentId": "@@/global-layout", "file": "Compliance/CertificateManage.tsx", "absPath": "/Compliance/CertificateManage", - "__content": "import React, { useState, useEffect } from 'react';\nimport {\n Card,\n Table,\n Button,\n Space,\n Modal,\n Form,\n Input,\n Select,\n DatePicker,\n Upload,\n Descriptions,\n Divider,\n message,\n Tag,\n Tabs,\n Row,\n Col,\n Statistic,\n Alert,\n Tooltip,\n Badge,\n} from 'antd';\nimport {\n FileTextOutlined,\n PlusOutlined,\n EyeOutlined,\n EditOutlined,\n DeleteOutlined,\n UploadOutlined,\n DownloadOutlined,\n CheckCircleOutlined,\n ClockCircleOutlined,\n CloseCircleOutlined,\n WarningOutlined,\n BellOutlined,\n} from '@ant-design/icons';\nimport type { ColumnsType } from 'antd/es/table';\nimport dayjs from 'dayjs';\n\nconst { Option } = Select;\nconst { TabPane } = Tabs;\nconst { RangePicker } = DatePicker;\n\ninterface Certificate {\n id: string;\n certNo: string;\n certName: string;\n certType: 'CE' | 'FCC' | 'RoHS' | 'UL' | 'ISO' | 'FDA' | 'CCC' | 'OTHER';\n productId: string;\n productName: string;\n issuer: string;\n issueDate: string;\n expiryDate: string;\n status: 'VALID' | 'EXPIRED' | 'PENDING_RENEWAL' | 'REVOKED';\n attachments: string[];\n scope: string;\n remark?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nconst CERT_TYPES = [\n { value: 'CE', label: 'CE Certification', color: 'blue' },\n { value: 'FCC', label: 'FCC Certification', color: 'green' },\n { value: 'RoHS', label: 'RoHS Compliance', color: 'purple' },\n { value: 'UL', label: 'UL Certification', color: 'orange' },\n { value: 'ISO', label: 'ISO Certification', color: 'cyan' },\n { value: 'FDA', label: 'FDA Approval', color: 'red' },\n { value: 'CCC', label: 'CCC Certification', color: 'gold' },\n { value: 'OTHER', label: 'Other', color: 'default' },\n];\n\nconst STATUS_CONFIG: Record = {\n VALID: { color: 'success', text: 'Valid', icon: },\n EXPIRED: { color: 'error', text: 'Expired', icon: },\n PENDING_RENEWAL: { color: 'warning', text: 'Pending Renewal', icon: },\n REVOKED: { color: 'red', text: 'Revoked', icon: },\n};\n\nconst MOCK_PRODUCTS = [\n { id: 'P001', name: 'Industrial Sensor A' },\n { id: 'P002', name: 'Control Module B' },\n { id: 'P003', name: 'Power Supply Unit C' },\n { id: 'P004', name: 'Communication Gateway D' },\n];\n\nexport const CertificateManage: React.FC = () => {\n const [form] = Form.useForm();\n const [loading, setLoading] = useState(false);\n const [certificates, setCertificates] = useState([]);\n const [createModalVisible, setCreateModalVisible] = useState(false);\n const [detailModalVisible, setDetailModalVisible] = useState(false);\n const [selectedCert, setSelectedCert] = useState(null);\n const [activeTab, setActiveTab] = useState('all');\n\n useEffect(() => {\n fetchCertificates();\n }, []);\n\n const fetchCertificates = async () => {\n setLoading(true);\n try {\n const mockCerts: Certificate[] = [\n {\n id: '1',\n certNo: 'CE-2026-001',\n certName: 'CE Declaration of Conformity',\n certType: 'CE',\n productId: 'P001',\n productName: 'Industrial Sensor A',\n issuer: 'TÜV SÜD',\n issueDate: '2025-01-15',\n expiryDate: '2027-01-14',\n status: 'VALID',\n attachments: ['ce_cert_2026_001.pdf', 'test_report.pdf'],\n scope: 'Low Voltage Directive (LVD), EMC Directive',\n createdAt: '2025-01-15 10:00:00',\n updatedAt: '2025-01-15 10:00:00',\n },\n {\n id: '2',\n certNo: 'FCC-2025-045',\n certName: 'FCC Declaration',\n certType: 'FCC',\n productId: 'P002',\n productName: 'Control Module B',\n issuer: 'FCC',\n issueDate: '2024-06-20',\n expiryDate: '2025-06-19',\n status: 'PENDING_RENEWAL',\n attachments: ['fcc_cert.pdf'],\n scope: 'Part 15B Class B Digital Device',\n remark: 'Renewal application submitted',\n createdAt: '2024-06-20 14:30:00',\n updatedAt: '2025-03-10 09:00:00',\n },\n {\n id: '3',\n certNo: 'RoHS-2024-112',\n certName: 'RoHS Compliance Certificate',\n certType: 'RoHS',\n productId: 'P003',\n productName: 'Power Supply Unit C',\n issuer: 'SGS',\n issueDate: '2024-03-01',\n expiryDate: '2025-02-28',\n status: 'EXPIRED',\n attachments: ['rohs_cert.pdf'],\n scope: 'RoHS 2.0 (2011/65/EU)',\n remark: 'Needs re-certification',\n createdAt: '2024-03-01 09:00:00',\n updatedAt: '2025-03-01 00:00:00',\n },\n {\n id: '4',\n certNo: 'UL-2026-008',\n certName: 'UL Safety Certification',\n certType: 'UL',\n productId: 'P004',\n productName: 'Communication Gateway D',\n issuer: 'Underwriters Laboratories',\n issueDate: '2026-02-01',\n expiryDate: '2027-01-31',\n status: 'VALID',\n attachments: ['ul_cert.pdf', 'safety_test.pdf'],\n scope: 'UL 60950-1, Information Technology Equipment',\n createdAt: '2026-02-01 11:00:00',\n updatedAt: '2026-02-01 11:00:00',\n },\n ];\n setCertificates(mockCerts);\n } finally {\n setLoading(false);\n }\n };\n\n const handleCreateCert = async (values: any) => {\n setLoading(true);\n try {\n const product = MOCK_PRODUCTS.find(p => p.id === values.productId);\n const newCert: Certificate = {\n id: `${Date.now()}`,\n certNo: `${values.certType}-${new Date().getFullYear()}-${String(certificates.length + 1).padStart(3, '0')}`,\n certName: values.certName,\n certType: values.certType,\n productId: values.productId,\n productName: product?.name || '',\n issuer: values.issuer,\n issueDate: values.issueDate.format('YYYY-MM-DD'),\n expiryDate: values.expiryDate.format('YYYY-MM-DD'),\n status: 'VALID',\n attachments: [],\n scope: values.scope,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n setCertificates([newCert, ...certificates]);\n message.success('Certificate created successfully');\n setCreateModalVisible(false);\n form.resetFields();\n } finally {\n setLoading(false);\n }\n };\n\n const handleViewDetail = (cert: Certificate) => {\n setSelectedCert(cert);\n setDetailModalVisible(true);\n };\n\n const handleRenew = (cert: Certificate) => {\n Modal.confirm({\n title: 'Renew Certificate',\n content: `Are you sure you want to initiate renewal for ${cert.certNo}?`,\n onOk: () => {\n setCertificates(certificates.map(c => \n c.id === cert.id ? { ...c, status: 'PENDING_RENEWAL' as const, updatedAt: new Date().toISOString() } : c\n ));\n message.success('Renewal initiated');\n },\n });\n };\n\n const handleDelete = (certId: string) => {\n Modal.confirm({\n title: 'Delete Certificate',\n content: 'Are you sure you want to delete this certificate?',\n okType: 'danger',\n onOk: () => {\n setCertificates(certificates.filter(c => c.id !== certId));\n message.success('Certificate deleted');\n },\n });\n };\n\n const getFilteredCerts = () => {\n if (activeTab === 'all') return certificates;\n if (activeTab === 'expiring') {\n return certificates.filter(c => {\n const daysUntilExpiry = dayjs(c.expiryDate).diff(dayjs(), 'day');\n return daysUntilExpiry > 0 && daysUntilExpiry <= 90;\n });\n }\n return certificates.filter(c => c.status === activeTab.toUpperCase());\n };\n\n const columns: ColumnsType = [\n {\n title: 'Cert No',\n dataIndex: 'certNo',\n key: 'certNo',\n width: 130,\n render: (no: string, record: Certificate) => (\n handleViewDetail(record)}>{no}\n ),\n },\n {\n title: 'Certificate Name',\n dataIndex: 'certName',\n key: 'certName',\n width: 200,\n ellipsis: true,\n },\n {\n title: 'Type',\n dataIndex: 'certType',\n key: 'certType',\n width: 100,\n render: (type: string) => {\n const config = CERT_TYPES.find(t => t.value === type);\n return {type};\n },\n },\n {\n title: 'Product',\n dataIndex: 'productName',\n key: 'productName',\n width: 160,\n },\n {\n title: 'Issuer',\n dataIndex: 'issuer',\n key: 'issuer',\n width: 120,\n },\n {\n title: 'Issue Date',\n dataIndex: 'issueDate',\n key: 'issueDate',\n width: 100,\n },\n {\n title: 'Expiry Date',\n dataIndex: 'expiryDate',\n key: 'expiryDate',\n width: 100,\n render: (date: string, record: Certificate) => {\n const daysUntilExpiry = dayjs(date).diff(dayjs(), 'day');\n const isExpiringSoon = daysUntilExpiry > 0 && daysUntilExpiry <= 90;\n return (\n \n \n {date}\n \n \n );\n },\n },\n {\n title: 'Status',\n dataIndex: 'status',\n key: 'status',\n width: 120,\n render: (status: string) => {\n const config = STATUS_CONFIG[status];\n return {config.text};\n },\n },\n {\n title: 'Actions',\n key: 'actions',\n width: 150,\n render: (_, record: Certificate) => (\n \n \n \n \n } />\n \n \n \n \n } />\n \n \n \n \n } />\n \n \n \n \n } />\n \n \n \n \n }\n />\n \n \n \n\n } onClick={() => setCreateModalVisible(true)}>\n Add Certificate\n \n }>\n \n \n Expiring Soon} key=\"expiring\" />\n \n \n \n \n\n \n \n\n setCreateModalVisible(false)}\n footer={null}\n width={700}\n >\n
\n \n
\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n \n\n
\n \n \n
\n \n \n\n setDetailModalVisible(false)}\n footer={[\n ,\n selectedCert?.status === 'VALID' && (\n \n ),\n ]}\n width={700}\n >\n {selectedCert && (\n <>\n \n {selectedCert.certNo}\n \n \n {STATUS_CONFIG[selectedCert.status].text}\n \n \n {selectedCert.certName}\n \n t.value === selectedCert.certType)?.color}>\n {CERT_TYPES.find(t => t.value === selectedCert.certType)?.label}\n \n \n {selectedCert.productName}\n {selectedCert.issuer}\n {selectedCert.issueDate}\n {selectedCert.expiryDate}\n {selectedCert.scope}\n {selectedCert.remark && (\n {selectedCert.remark}\n )}\n \n\n {selectedCert.attachments.length > 0 && (\n <>\n Attachments\n \n {selectedCert.attachments.map((file, index) => (\n \n ))}\n \n \n )}\n \n )}\n \n \n );\n};\n\nexport default CertificateManage;\n", + "__content": "/**\n * 证书管理页面\n * 使用DataSource抽象层获取数据,支持Mock/真实API自动切换\n * \n * @module pages/Compliance/CertificateManage\n * @author AI-Frontend-Team\n * @created 2026-03-19\n */\n\nimport React, { useState, useEffect } from 'react';\nimport {\n Card,\n Table,\n Button,\n Space,\n Modal,\n Form,\n Input,\n Select,\n DatePicker,\n Upload,\n Descriptions,\n Divider,\n message,\n Tag,\n Tabs,\n Row,\n Col,\n Statistic,\n Alert,\n Tooltip,\n Badge,\n} from 'antd';\nimport {\n FileTextOutlined,\n PlusOutlined,\n EyeOutlined,\n EditOutlined,\n DeleteOutlined,\n UploadOutlined,\n DownloadOutlined,\n CheckCircleOutlined,\n ClockCircleOutlined,\n CloseCircleOutlined,\n WarningOutlined,\n BellOutlined,\n} from '@ant-design/icons';\nimport type { ColumnsType } from 'antd/es/table';\nimport dayjs from 'dayjs';\nimport { certificateDataSource, __DATA_SOURCE_TYPE__ } from '@/services/certificateDataSource';\nimport { Certificate } from '@/types/certificate';\n\nconst { Option } = Select;\nconst { TabPane } = Tabs;\nconst { RangePicker } = DatePicker;\n\n// 证书类型映射\nconst CERT_TYPE_MAP: Record = {\n PRODUCT_CERT: { label: '产品认证', color: 'blue' },\n BUSINESS_LICENSE: { label: '营业执照', color: 'green' },\n SAFETY_CERT: { label: '安全认证', color: 'red' },\n QUALITY_CERT: { label: '质量认证', color: 'purple' },\n OTHER: { label: '其他', color: 'default' },\n};\n\n// 状态映射\nconst STATUS_MAP: Record = {\n APPROVED: { text: '已批准', color: 'success', icon: },\n PENDING: { text: '待审核', color: 'warning', icon: },\n REJECTED: { text: '已拒绝', color: 'error', icon: },\n EXPIRED: { text: '已过期', color: 'red', icon: },\n};\n\n// 模拟产品列表(用于创建表单)\nconst MOCK_PRODUCTS = [\n { id: 'P001', name: '工业温度传感器' },\n { id: 'P002', name: '控制模块B' },\n { id: 'P003', name: '电源单元C' },\n { id: 'P004', name: '通信网关D' },\n];\n\nexport const CertificateManage: React.FC = () => {\n const [form] = Form.useForm();\n const [loading, setLoading] = useState(false);\n const [certificates, setCertificates] = useState([]);\n const [createModalVisible, setCreateModalVisible] = useState(false);\n const [detailModalVisible, setDetailModalVisible] = useState(false);\n const [selectedCert, setSelectedCert] = useState(null);\n const [activeTab, setActiveTab] = useState('all');\n\n // 组件挂载时加载数据\n useEffect(() => {\n fetchCertificates();\n }, []);\n\n /**\n * 获取证书列表\n * 通过DataSource抽象层获取数据\n */\n const fetchCertificates = async () => {\n setLoading(true);\n try {\n // 使用DataSource获取数据(自动切换Mock/真实API)\n const data = await certificateDataSource.list();\n setCertificates(data);\n \n if (__DATA_SOURCE_TYPE__ === 'mock') {\n console.log('[CertificateManage] Using mock data source');\n }\n } catch (error) {\n message.error('Failed to fetch certificates');\n console.error('[CertificateManage] Error fetching certificates:', error);\n } finally {\n setLoading(false);\n }\n };\n\n /**\n * 创建证书\n */\n const handleCreateCert = async (values: any) => {\n setLoading(true);\n try {\n const product = MOCK_PRODUCTS.find(p => p.id === values.productId);\n \n // 使用DataSource创建数据\n const newCert = await certificateDataSource.create({\n name: values.name,\n type: values.type,\n productId: values.productId,\n productName: product?.name,\n expiryDate: values.expiryDate?.format('YYYY-MM-DD'),\n notes: values.notes,\n });\n\n message.success('Certificate created successfully');\n setCreateModalVisible(false);\n form.resetFields();\n \n // 刷新列表\n await fetchCertificates();\n } catch (error) {\n message.error('Failed to create certificate');\n console.error('[CertificateManage] Error creating certificate:', error);\n } finally {\n setLoading(false);\n }\n };\n\n /**\n * 查看详情\n */\n const handleViewDetail = (cert: Certificate) => {\n setSelectedCert(cert);\n setDetailModalVisible(true);\n };\n\n /**\n * 更新证书状态(续期)\n */\n const handleRenew = async (cert: Certificate) => {\n Modal.confirm({\n title: '续期证书',\n content: `确定要为证书 ${cert.name} 发起续期申请吗?`,\n onOk: async () => {\n try {\n await certificateDataSource.update(cert.id, {\n status: 'PENDING',\n });\n message.success('续期申请已提交');\n await fetchCertificates();\n } catch (error) {\n message.error('Failed to renew certificate');\n console.error('[CertificateManage] Error renewing certificate:', error);\n }\n },\n });\n };\n\n /**\n * 删除证书\n */\n const handleDelete = async (certId: string) => {\n Modal.confirm({\n title: '删除证书',\n content: '确定要删除此证书吗?此操作不可恢复。',\n okType: 'danger',\n onOk: async () => {\n try {\n await certificateDataSource.delete(certId);\n message.success('证书已删除');\n await fetchCertificates();\n } catch (error) {\n message.error('Failed to delete certificate');\n console.error('[CertificateManage] Error deleting certificate:', error);\n }\n },\n });\n };\n\n /**\n * 获取过滤后的证书列表\n */\n const getFilteredCerts = () => {\n if (activeTab === 'all') return certificates;\n if (activeTab === 'expiring') {\n return certificates.filter(c => {\n if (!c.expiryDate) return false;\n const daysUntilExpiry = dayjs(c.expiryDate).diff(dayjs(), 'day');\n return daysUntilExpiry > 0 && daysUntilExpiry <= 90;\n });\n }\n return certificates.filter(c => c.status === activeTab.toUpperCase());\n };\n\n // 表格列定义\n const columns: ColumnsType = [\n {\n title: '证书名称',\n dataIndex: 'name',\n key: 'name',\n width: 200,\n ellipsis: true,\n render: (name: string, record: Certificate) => (\n handleViewDetail(record)}>{name}\n ),\n },\n {\n title: '类型',\n dataIndex: 'type',\n key: 'type',\n width: 120,\n render: (type: string) => {\n const config = CERT_TYPE_MAP[type] || { label: type, color: 'default' };\n return {config.label};\n },\n },\n {\n title: '关联产品',\n dataIndex: 'productName',\n key: 'productName',\n width: 160,\n render: (name: string) => name || '-',\n },\n {\n title: '到期日期',\n dataIndex: 'expiryDate',\n key: 'expiryDate',\n width: 120,\n render: (date: string) => {\n if (!date) return '-';\n const daysUntilExpiry = dayjs(date).diff(dayjs(), 'day');\n const isExpiringSoon = daysUntilExpiry > 0 && daysUntilExpiry <= 90;\n const isExpired = daysUntilExpiry <= 0;\n return (\n \n \n {date}\n \n \n );\n },\n },\n {\n title: '状态',\n dataIndex: 'status',\n key: 'status',\n width: 100,\n render: (status: string) => {\n const config = STATUS_MAP[status] || { text: status, color: 'default', icon: null };\n return {config.text};\n },\n },\n {\n title: '操作',\n key: 'actions',\n width: 150,\n render: (_, record: Certificate) => (\n \n \n \n \n } />\n \n \n \n \n } />\n \n \n \n \n } />\n \n \n \n \n } />\n \n \n \n \n }\n />\n \n \n \n\n {/* 证书列表 */}\n } onClick={() => setCreateModalVisible(true)}>\n 添加证书\n \n }\n >\n \n \n 即将到期} key=\"expiring\" />\n \n \n \n \n\n \n \n\n {/* 创建证书弹窗 */}\n setCreateModalVisible(false)}\n footer={null}\n width={700}\n >\n
\n \n
\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n \n\n
\n \n \n
\n \n \n\n {/* 详情弹窗 */}\n setDetailModalVisible(false)}\n footer={[\n ,\n selectedCert?.status === 'APPROVED' && (\n \n ),\n ]}\n width={700}\n >\n {selectedCert && (\n <>\n \n {selectedCert.id}\n \n \n {STATUS_MAP[selectedCert.status]?.text}\n \n \n {selectedCert.name}\n \n \n {CERT_TYPE_MAP[selectedCert.type]?.label}\n \n \n {selectedCert.productName || '-'}\n {selectedCert.expiryDate || '-'}\n {selectedCert.uploadDate || '-'}\n {selectedCert.approvedBy && (\n {selectedCert.approvedBy}\n )}\n {selectedCert.approvedDate && (\n {selectedCert.approvedDate}\n )}\n {selectedCert.notes && (\n {selectedCert.notes}\n )}\n \n\n {selectedCert.fileUrl && (\n <>\n 附件\n \n \n )}\n \n )}\n \n \n );\n};\n\nexport default CertificateManage;\n", "__isJSFile": true, "__absFile": "D:/trae_projects/makemd/makemd/dashboard/src/pages/Compliance/CertificateManage.tsx" }, "Merchant/MerchantOrderManage": { "path": "Merchant/MerchantOrderManage", "id": "Merchant/MerchantOrderManage", + "parentId": "@@/global-layout", "file": "Merchant/MerchantOrderManage.tsx", "absPath": "/Merchant/MerchantOrderManage", "__content": "import React, { useState, useEffect } from 'react';\nimport { Table, Input, Button, Select, DatePicker, message, Card, Typography, Tag } from 'antd';\nimport { SearchOutlined, EyeOutlined, ReloadOutlined } from '@ant-design/icons';\nimport { getMerchantOrders, updateOrderStatus } from '../../services/merchantOrderService';\n\nconst { Option } = Select;\nconst { Title, Text } = Typography;\nconst { RangePicker } = DatePicker;\n\ninterface Order {\n id: string;\n merchantId: string;\n merchantName: string;\n orderId: string;\n platform: string;\n totalAmount: number;\n status: 'PENDING' | 'PROCESSING' | 'SHIPPED' | 'DELIVERED' | 'CANCELLED' | 'REFUNDED';\n createdAt: string;\n updatedAt: string;\n}\n\nconst MerchantOrderManage: React.FC = () => {\n const [orders, setOrders] = useState([]);\n const [merchants, setMerchants] = useState<{ id: string; companyName: string }[]>([]);\n const [loading, setLoading] = useState(false);\n const [searchText, setSearchText] = useState('');\n const [merchantFilter, setMerchantFilter] = useState('');\n const [platformFilter, setPlatformFilter] = useState('');\n const [statusFilter, setStatusFilter] = useState('');\n const [dateRange, setDateRange] = useState<[string, string] | null>(null);\n\n const fetchOrders = async () => {\n setLoading(true);\n try {\n const response = await getMerchantOrders();\n setOrders(response.data);\n } catch (error) {\n message.error('获取订单列表失败');\n console.error('获取订单列表失败:', error);\n } finally {\n setLoading(false);\n }\n };\n\n const fetchMerchants = async () => {\n try {\n // 实际项目中应该调用获取商户列表的API\n // 这里模拟数据\n setMerchants([\n { id: '1', companyName: '商户A' },\n { id: '2', companyName: '商户B' },\n { id: '3', companyName: '商户C' },\n ]);\n } catch (error) {\n console.error('获取商户列表失败:', error);\n }\n };\n\n useEffect(() => {\n fetchMerchants();\n fetchOrders();\n }, []);\n\n const handleSearch = (value: string) => {\n setSearchText(value);\n };\n\n const handleMerchantFilter = (value: string) => {\n setMerchantFilter(value);\n };\n\n const handlePlatformFilter = (value: string) => {\n setPlatformFilter(value);\n };\n\n const handleStatusFilter = (value: string) => {\n setStatusFilter(value);\n };\n\n const handleDateRangeChange = (dates: any) => {\n if (dates) {\n setDateRange([dates[0].format('YYYY-MM-DD'), dates[1].format('YYYY-MM-DD')]);\n } else {\n setDateRange(null);\n }\n };\n\n const filteredOrders = orders.filter(order => {\n const matchesSearch = order.orderId.includes(searchText) ||\n order.merchantName.toLowerCase().includes(searchText.toLowerCase());\n const matchesMerchant = merchantFilter ? order.merchantId === merchantFilter : true;\n const matchesPlatform = platformFilter ? order.platform === platformFilter : true;\n const matchesStatus = statusFilter ? order.status === statusFilter : true;\n return matchesSearch && matchesMerchant && matchesPlatform && matchesStatus;\n });\n\n const handleView = (order: Order) => {\n // 跳转到订单详情页面\n message.info(`查看订单详情: ${order.orderId}`);\n };\n\n const handleStatusUpdate = (order: Order, newStatus: Order['status']) => {\n // 调用API更新订单状态\n message.info(`更新订单 ${order.orderId} 状态为: ${newStatus}`);\n };\n\n const handleRefresh = () => {\n fetchOrders();\n };\n\n const getStatusTag = (status: string) => {\n const statusConfig: Record = {\n PENDING: { color: 'blue', text: '待处理' },\n PROCESSING: { color: 'orange', text: '处理中' },\n SHIPPED: { color: 'purple', text: '已发货' },\n DELIVERED: { color: 'green', text: '已送达' },\n CANCELLED: { color: 'gray', text: '已取消' },\n REFUNDED: { color: 'red', text: '已退款' },\n };\n const config = statusConfig[status] || { color: 'default', text: status };\n return {config.text};\n };\n\n const columns = [\n {\n title: '订单ID',\n dataIndex: 'id',\n key: 'id',\n ellipsis: true,\n },\n {\n title: '平台订单号',\n dataIndex: 'orderId',\n key: 'orderId',\n ellipsis: true,\n },\n {\n title: '商户',\n dataIndex: 'merchantName',\n key: 'merchantName',\n ellipsis: true,\n },\n {\n title: '平台',\n dataIndex: 'platform',\n key: 'platform',\n ellipsis: true,\n },\n {\n title: '总金额',\n dataIndex: 'totalAmount',\n key: 'totalAmount',\n render: (amount: number) => ${amount.toFixed(2)},\n },\n {\n title: '状态',\n dataIndex: 'status',\n key: 'status',\n render: (status: string) => getStatusTag(status),\n },\n {\n title: '创建时间',\n dataIndex: 'createdAt',\n key: 'createdAt',\n ellipsis: true,\n },\n {\n title: '操作',\n key: 'action',\n render: (_: any, order: Order) => (\n
\n \n handleStatusUpdate(order, value)}\n >\n \n \n \n \n \n \n \n
\n ),\n },\n ];\n\n return (\n \n
\n 多商户订单管理\n \n
\n\n
\n }\n style={{ width: 300 }}\n onChange={(e) => handleSearch(e.target.value)}\n />\n \n {merchants.map(merchant => (\n \n ))}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n\n \n
\n );\n};\n\nexport default MerchantOrderManage;", @@ -3297,6 +2822,7 @@ "Inventory/InventoryForecast": { "path": "Inventory/InventoryForecast", "id": "Inventory/InventoryForecast", + "parentId": "@@/global-layout", "file": "Inventory/InventoryForecast.tsx", "absPath": "/Inventory/InventoryForecast", "__content": "import React, { useState, useEffect } from 'react';\nimport { Card, Row, Col, Select, DatePicker, Button, Table } from 'antd';\nimport { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar } from 'recharts';\n\nconst { Option } = Select;\nconst { RangePicker } = DatePicker;\n\ninterface ForecastData {\n date: string;\n demand: number;\n supply: number;\n stock: number;\n}\n\ninterface ProductForecast {\n id: string;\n sku: string;\n name: string;\n currentStock: number;\n forecastedDemand: number;\n recommendedOrder: number;\n leadTime: number;\n riskLevel: 'low' | 'medium' | 'high';\n}\n\nconst InventoryForecast: React.FC = () => {\n const [forecastData, setForecastData] = useState([]);\n const [productForecasts, setProductForecasts] = useState([]);\n const [loading, setLoading] = useState(false);\n const [selectedProduct, setSelectedProduct] = useState('all');\n const [dateRange, setDateRange] = useState(null);\n\n useEffect(() => {\n fetchForecastData();\n }, [selectedProduct, dateRange]);\n\n const fetchForecastData = async () => {\n setLoading(true);\n // 模拟API调用\n setTimeout(() => {\n // 模拟预测数据\n const mockForecastData: ForecastData[] = Array.from({ length: 30 }, (_, i) => ({\n date: `2026-04-${String(i + 1).padStart(2, '0')}`,\n demand: Math.random() * 50 + 20,\n supply: Math.random() * 30 + 10,\n stock: Math.random() * 100 + 50,\n }));\n\n // 模拟产品预测数据\n const mockProductForecasts: ProductForecast[] = [\n {\n id: '1',\n sku: 'SKU-001',\n name: 'Product 1',\n currentStock: 100,\n forecastedDemand: 150,\n recommendedOrder: 100,\n leadTime: 7,\n riskLevel: 'low',\n },\n {\n id: '2',\n sku: 'SKU-002',\n name: 'Product 2',\n currentStock: 15,\n forecastedDemand: 50,\n recommendedOrder: 40,\n leadTime: 10,\n riskLevel: 'high',\n },\n {\n id: '3',\n sku: 'SKU-003',\n name: 'Product 3',\n currentStock: 0,\n forecastedDemand: 30,\n recommendedOrder: 30,\n leadTime: 5,\n riskLevel: 'high',\n },\n {\n id: '4',\n sku: 'SKU-004',\n name: 'Product 4',\n currentStock: 50,\n forecastedDemand: 60,\n recommendedOrder: 20,\n leadTime: 3,\n riskLevel: 'medium',\n },\n ];\n\n setForecastData(mockForecastData);\n setProductForecasts(mockProductForecasts);\n setLoading(false);\n }, 500);\n };\n\n const columns = [\n {\n title: 'SKU',\n dataIndex: 'sku',\n key: 'sku',\n },\n {\n title: '商品名称',\n dataIndex: 'name',\n key: 'name',\n },\n {\n title: '当前库存',\n dataIndex: 'currentStock',\n key: 'currentStock',\n },\n {\n title: '预测需求',\n dataIndex: 'forecastedDemand',\n key: 'forecastedDemand',\n },\n {\n title: '建议订购量',\n dataIndex: 'recommendedOrder',\n key: 'recommendedOrder',\n },\n {\n title: '交货期(天)',\n dataIndex: 'leadTime',\n key: 'leadTime',\n },\n {\n title: '风险等级',\n dataIndex: 'riskLevel',\n key: 'riskLevel',\n render: (riskLevel: string) => {\n const riskMap = {\n low: { text: '低', color: '#3f8600' },\n medium: { text: '中', color: '#fa8c16' },\n high: { text: '高', color: '#cf1322' },\n };\n const riskInfo = riskMap[riskLevel as keyof typeof riskMap] || { text: riskLevel, color: '#000' };\n return {riskInfo.text};\n },\n },\n ];\n\n return (\n
\n
\n

库存预测

\n
\n\n
\n \n \n \n \n \n \n \n \n
\n\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default InventoryForecast;", @@ -3306,33 +2832,77 @@ "Merchant/MerchantShopManage": { "path": "Merchant/MerchantShopManage", "id": "Merchant/MerchantShopManage", + "parentId": "@@/global-layout", "file": "Merchant/MerchantShopManage.tsx", "absPath": "/Merchant/MerchantShopManage", "__content": "import React, { useState, useEffect } from 'react';\nimport { Table, Input, Button, Select, message, Card, Typography, Form, Modal } from 'antd';\nimport { SearchOutlined, PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';\nimport { getMerchantShops, createMerchantShop, updateMerchantShop, deleteMerchantShop } from '../../services/merchantShopService';\n\nconst { Option } = Select;\nconst { Title, Text } = Typography;\nconst { Item } = Form;\n\ninterface Shop {\n id: string;\n merchantId: string;\n shopName: string;\n platform: string;\n shopUrl: string;\n status: 'ACTIVE' | 'INACTIVE' | 'SUSPENDED';\n createdAt: string;\n updatedAt: string;\n}\n\nconst MerchantShopManage: React.FC = () => {\n const [shops, setShops] = useState([]);\n const [merchants, setMerchants] = useState<{ id: string; companyName: string }[]>([]);\n const [loading, setLoading] = useState(false);\n const [searchText, setSearchText] = useState('');\n const [merchantFilter, setMerchantFilter] = useState('');\n const [platformFilter, setPlatformFilter] = useState('');\n const [statusFilter, setStatusFilter] = useState('');\n const [isModalVisible, setIsModalVisible] = useState(false);\n const [form] = Form.useForm();\n const [editingShop, setEditingShop] = useState(null);\n\n const fetchShops = async () => {\n setLoading(true);\n try {\n const response = await getMerchantShops();\n setShops(response.data);\n } catch (error) {\n message.error('获取店铺列表失败');\n console.error('获取店铺列表失败:', error);\n } finally {\n setLoading(false);\n }\n };\n\n const fetchMerchants = async () => {\n try {\n // 实际项目中应该调用获取商户列表的API\n // 这里模拟数据\n setMerchants([\n { id: '1', companyName: '商户A' },\n { id: '2', companyName: '商户B' },\n { id: '3', companyName: '商户C' },\n ]);\n } catch (error) {\n console.error('获取商户列表失败:', error);\n }\n };\n\n useEffect(() => {\n fetchMerchants();\n fetchShops();\n }, []);\n\n const handleSearch = (value: string) => {\n setSearchText(value);\n };\n\n const handleMerchantFilter = (value: string) => {\n setMerchantFilter(value);\n };\n\n const handlePlatformFilter = (value: string) => {\n setPlatformFilter(value);\n };\n\n const handleStatusFilter = (value: string) => {\n setStatusFilter(value);\n };\n\n const filteredShops = shops.filter(shop => {\n const matchesSearch = shop.shopName.toLowerCase().includes(searchText.toLowerCase()) ||\n shop.shopUrl.toLowerCase().includes(searchText.toLowerCase());\n const matchesMerchant = merchantFilter ? shop.merchantId === merchantFilter : true;\n const matchesPlatform = platformFilter ? shop.platform === platformFilter : true;\n const matchesStatus = statusFilter ? shop.status === statusFilter : true;\n return matchesSearch && matchesMerchant && matchesPlatform && matchesStatus;\n });\n\n const handleCreate = () => {\n setEditingShop(null);\n form.resetFields();\n setIsModalVisible(true);\n };\n\n const handleEdit = (shop: Shop) => {\n setEditingShop(shop);\n form.setFieldsValue(shop);\n setIsModalVisible(true);\n };\n\n const handleDelete = (shopId: string) => {\n // 确认删除后调用API\n message.info(`删除店铺: ${shopId}`);\n };\n\n const handleView = (shop: Shop) => {\n // 跳转到店铺详情页面\n message.info(`查看店铺详情: ${shop.shopName}`);\n };\n\n const handleOk = async () => {\n try {\n const values = await form.validateFields();\n if (editingShop) {\n // 编辑店铺\n await updateMerchantShop(editingShop.id, values);\n message.success('店铺更新成功');\n } else {\n // 创建店铺\n await createMerchantShop(values);\n message.success('店铺创建成功');\n }\n setIsModalVisible(false);\n fetchShops();\n } catch (error) {\n message.error('操作失败');\n console.error('操作失败:', error);\n }\n };\n\n const handleCancel = () => {\n setIsModalVisible(false);\n };\n\n const columns = [\n {\n title: '店铺ID',\n dataIndex: 'id',\n key: 'id',\n ellipsis: true,\n },\n {\n title: '商户',\n dataIndex: 'merchantId',\n key: 'merchantId',\n render: (merchantId: string) => {\n const merchant = merchants.find(m => m.id === merchantId);\n return {merchant?.companyName || merchantId};\n },\n },\n {\n title: '店铺名称',\n dataIndex: 'shopName',\n key: 'shopName',\n ellipsis: true,\n },\n {\n title: '平台',\n dataIndex: 'platform',\n key: 'platform',\n ellipsis: true,\n },\n {\n title: '店铺链接',\n dataIndex: 'shopUrl',\n key: 'shopUrl',\n ellipsis: true,\n render: (shopUrl: string) => (\n \n {shopUrl}\n \n ),\n },\n {\n title: '状态',\n dataIndex: 'status',\n key: 'status',\n render: (status: string) => {\n const statusMap: Record = {\n ACTIVE: '活跃',\n INACTIVE: '未激活',\n SUSPENDED: '暂停',\n };\n return {statusMap[status] || status};\n },\n },\n {\n title: '创建时间',\n dataIndex: 'createdAt',\n key: 'createdAt',\n ellipsis: true,\n },\n {\n title: '操作',\n key: 'action',\n render: (_: any, shop: Shop) => (\n
\n \n \n \n
\n ),\n },\n ];\n\n return (\n \n
\n 商户店铺管理\n \n
\n\n
\n }\n style={{ width: 300 }}\n onChange={(e) => handleSearch(e.target.value)}\n />\n \n {merchants.map(merchant => (\n \n ))}\n \n \n \n \n \n \n \n \n \n \n \n \n
\n\n \n\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n );\n};\n\nexport default MerchantShopManage;", "__isJSFile": true, "__absFile": "D:/trae_projects/makemd/makemd/dashboard/src/pages/Merchant/MerchantShopManage.tsx" }, + "Product/CrossPlatformManage": { + "path": "Product/CrossPlatformManage", + "id": "Product/CrossPlatformManage", + "parentId": "@@/global-layout", + "file": "Product/CrossPlatformManage.tsx", + "absPath": "/Product/CrossPlatformManage", + "__content": "import React, { useState, useEffect } from 'react';\nimport {\n Card,\n Table,\n Button,\n Space,\n Tag,\n Input,\n Select,\n Row,\n Col,\n Modal,\n message,\n Tooltip,\n Badge,\n Alert,\n Descriptions,\n Divider,\n Dropdown,\n Menu,\n Image,\n Typography,\n Tabs,\n Form,\n InputNumber,\n Switch,\n} from 'antd';\nimport {\n SyncOutlined,\n EditOutlined,\n EyeOutlined,\n GlobalOutlined,\n AmazonOutlined,\n ShoppingOutlined,\n ShopOutlined,\n CheckCircleOutlined,\n CloseCircleOutlined,\n ExclamationCircleOutlined,\n LinkOutlined,\n ArrowUpOutlined,\n ArrowDownOutlined,\n} from '@ant-design/icons';\nimport type { ColumnsType } from 'antd/es/table';\n\nconst { Text, Title } = Typography;\nconst { Option } = Select;\nconst { Search } = Input;\nconst { TabPane } = Tabs;\n\n// ==================== 多商户店铺配置 ====================\n// 当前用户拥有的店铺(根据登录用户的权限动态加载)\ninterface UserShop {\n id: string;\n platform: string;\n shopId: string;\n shopName: string;\n region: string;\n status: 'ACTIVE' | 'INACTIVE';\n apiSupported: boolean; // 是否支持API管理\n}\n\n// 模拟当前用户拥有的店铺\nconst CURRENT_USER_SHOPS: UserShop[] = [\n { id: '1', platform: 'AMAZON', shopId: 'AMZ-US-001', shopName: 'Amazon US Store', region: 'US', status: 'ACTIVE', apiSupported: true },\n { id: '2', platform: 'AMAZON', shopId: 'AMZ-EU-001', shopName: 'Amazon EU Store', region: 'EU', status: 'ACTIVE', apiSupported: true },\n { id: '3', platform: 'EBAY', shopId: 'EB-US-001', shopName: 'eBay US Store', region: 'US', status: 'ACTIVE', apiSupported: true },\n { id: '4', platform: 'SHOPIFY', shopId: 'SF-001', shopName: 'My Shopify Store', region: 'US', status: 'ACTIVE', apiSupported: true },\n { id: '5', platform: 'SHOPEE', shopId: 'SP-SG-001', shopName: 'Shopee Singapore', region: 'SG', status: 'ACTIVE', apiSupported: false }, // 不支持API\n { id: '6', platform: 'TIKTOK', shopId: 'TK-US-001', shopName: 'TikTok US Shop', region: 'US', status: 'ACTIVE', apiSupported: false }, // 不支持API\n];\n\n// 平台配置\nconst PLATFORM_CONFIG: Record = {\n AMAZON: { name: 'Amazon', color: '#FF9900', icon: },\n EBAY: { name: 'eBay', color: '#E53238', icon: },\n SHOPIFY: { name: 'Shopify', color: '#96BF48', icon: },\n SHOPEE: { name: 'Shopee', color: '#EE4D2D', icon: },\n TIKTOK: { name: 'TikTok Shop', color: '#000000', icon: },\n LAZADA: { name: 'Lazada', color: '#0F156D', icon: },\n};\n\n// ==================== 跨平台商品接口 ====================\ninterface CrossPlatformProduct {\n id: string;\n localProductId: string;\n sku: string;\n name: string;\n image: string;\n category: string;\n description: string;\n platforms: {\n [shopId: string]: {\n status: 'LIVE' | 'OFFLINE' | 'PENDING' | 'ERROR' | 'SYNCING' | 'NOT_LISTED';\n platformProductId: string;\n platformSku: string;\n price: number;\n stock: number;\n sales: number;\n listingUrl: string;\n lastSync: string;\n shopInfo: UserShop;\n };\n };\n basePrice: number;\n baseStock: number;\n totalSales: number;\n createdAt: string;\n updatedAt: string;\n}\n\n// ==================== 模拟数据 ====================\nconst MOCK_CROSS_PLATFORM_PRODUCTS: CrossPlatformProduct[] = [\n {\n id: 'CP001',\n localProductId: 'P001',\n sku: 'TP-TEMP-001',\n name: '工业温度传感器 Pro',\n image: 'https://via.placeholder.com/200x200?text=Product',\n category: '工业自动化',\n description: '高精度工业温度传感器,适用于各种工业环境,测量范围-40°C至125°C',\n basePrice: 89.99,\n baseStock: 256,\n totalSales: 1250,\n createdAt: '2025-12-15 10:00:00',\n updatedAt: '2026-03-18 10:30:00',\n platforms: {\n 'AMZ-US-001': {\n status: 'LIVE',\n platformProductId: 'AMZ-123456',\n platformSku: 'TP-TEMP-001-AMZ',\n price: 99.99,\n stock: 100,\n sales: 850,\n listingUrl: 'https://amazon.com/dp/123456',\n lastSync: '2026-03-18 10:00:00',\n shopInfo: CURRENT_USER_SHOPS[0],\n },\n 'AMZ-EU-001': {\n status: 'LIVE',\n platformProductId: 'AMZ-EU-789',\n platformSku: 'TP-TEMP-001-AMZ-EU',\n price: 89.99,\n stock: 80,\n sales: 200,\n listingUrl: 'https://amazon.de/dp/789',\n lastSync: '2026-03-18 09:00:00',\n shopInfo: CURRENT_USER_SHOPS[1],\n },\n 'EB-US-001': {\n status: 'LIVE',\n platformProductId: 'EB-789012',\n platformSku: 'TP-TEMP-001-EB',\n price: 95.99,\n stock: 76,\n sales: 200,\n listingUrl: 'https://ebay.com/itm/789012',\n lastSync: '2026-03-18 09:30:00',\n shopInfo: CURRENT_USER_SHOPS[2],\n },\n 'SP-SG-001': {\n status: 'LIVE',\n platformProductId: 'SP-345678',\n platformSku: 'TP-TEMP-001-SP',\n price: 89.99,\n stock: 76,\n sales: 80,\n listingUrl: 'https://shopee.sg/product/345678',\n lastSync: '2026-03-18 09:00:00',\n shopInfo: CURRENT_USER_SHOPS[4],\n },\n },\n },\n {\n id: 'CP002',\n localProductId: 'P002',\n sku: 'TP-PRES-002',\n name: '压力传感器 Digital',\n image: 'https://via.placeholder.com/200x200?text=Product',\n category: '工业自动化',\n description: '数字式压力传感器,精度0.1%,支持多种输出信号',\n basePrice: 129.99,\n baseStock: 128,\n totalSales: 680,\n createdAt: '2026-03-10 14:30:00',\n updatedAt: '2026-03-18 09:15:00',\n platforms: {\n 'AMZ-US-001': {\n status: 'LIVE',\n platformProductId: 'AMZ-234567',\n platformSku: 'TP-PRES-002-AMZ',\n price: 139.99,\n stock: 50,\n sales: 500,\n listingUrl: 'https://amazon.com/dp/234567',\n lastSync: '2026-03-18 10:00:00',\n shopInfo: CURRENT_USER_SHOPS[0],\n },\n 'SF-001': {\n status: 'LIVE',\n platformProductId: 'SF-890123',\n platformSku: 'TP-PRES-002-SF',\n price: 129.99,\n stock: 78,\n sales: 180,\n listingUrl: 'https://mystore.myshopify.com/products/890123',\n lastSync: '2026-03-18 08:30:00',\n shopInfo: CURRENT_USER_SHOPS[3],\n },\n },\n },\n {\n id: 'CP003',\n localProductId: 'P003',\n sku: 'TP-FLOW-003',\n name: '流量计 Ultra',\n image: 'https://via.placeholder.com/200x200?text=Product',\n category: '仪器仪表',\n description: '超声波流量计,非接触式测量,精度高',\n basePrice: 299.99,\n baseStock: 64,\n totalSales: 320,\n createdAt: '2026-03-01 09:00:00',\n updatedAt: '2026-03-17 16:45:00',\n platforms: {\n 'AMZ-US-001': {\n status: 'OFFLINE',\n platformProductId: 'AMZ-345678',\n platformSku: 'TP-FLOW-003-AMZ',\n price: 319.99,\n stock: 0,\n sales: 300,\n listingUrl: 'https://amazon.com/dp/345678',\n lastSync: '2026-03-17 16:00:00',\n shopInfo: CURRENT_USER_SHOPS[0],\n },\n 'EB-US-001': {\n status: 'ERROR',\n platformProductId: 'EB-901234',\n platformSku: 'TP-FLOW-003-EB',\n price: 299.99,\n stock: 64,\n sales: 20,\n listingUrl: 'https://ebay.com/itm/901234',\n lastSync: '2026-03-17 15:30:00',\n shopInfo: CURRENT_USER_SHOPS[2],\n },\n },\n },\n];\n\nconst STATUS_CONFIG: Record = {\n LIVE: { color: 'success', text: '在线', icon: },\n OFFLINE: { color: 'default', text: '已下架', icon: },\n PENDING: { color: 'warning', text: '审核中', icon: },\n ERROR: { color: 'error', text: '异常', icon: },\n SYNCING: { color: 'processing', text: '同步中', icon: },\n NOT_LISTED: { color: 'default', text: '未上架', icon: },\n};\n\nconst CrossPlatformManage: React.FC = () => {\n const [loading, setLoading] = useState(false);\n const [products, setProducts] = useState([]);\n const [filteredProducts, setFilteredProducts] = useState([]);\n const [selectedShop, setSelectedShop] = useState('ALL');\n const [searchText, setSearchText] = useState('');\n const [selectedRows, setSelectedRows] = useState([]);\n const [syncLoading, setSyncLoading] = useState>({});\n \n // 详情弹窗\n const [detailModalVisible, setDetailModalVisible] = useState(false);\n const [selectedProduct, setSelectedProduct] = useState(null);\n \n // 同步弹窗\n const [syncModalVisible, setSyncModalVisible] = useState(false);\n const [syncForm] = Form.useForm();\n const [syncingProduct, setSyncingProduct] = useState(null);\n const [selectedSyncShops, setSelectedSyncShops] = useState([]);\n\n useEffect(() => {\n fetchProducts();\n }, []);\n\n useEffect(() => {\n filterProducts();\n }, [products, selectedShop, searchText]);\n\n const fetchProducts = async () => {\n setLoading(true);\n try {\n await new Promise(resolve => setTimeout(resolve, 500));\n setProducts(MOCK_CROSS_PLATFORM_PRODUCTS);\n } catch (error) {\n message.error('加载跨平台商品失败');\n } finally {\n setLoading(false);\n }\n };\n\n const filterProducts = () => {\n let result = [...products];\n\n // 按店铺筛选\n if (selectedShop !== 'ALL') {\n result = result.filter(p => \n p.platforms[selectedShop] && \n p.platforms[selectedShop].status !== 'NOT_LISTED'\n );\n }\n\n // 按关键词搜索\n if (searchText) {\n result = result.filter(p => \n p.name.toLowerCase().includes(searchText.toLowerCase()) ||\n p.sku.toLowerCase().includes(searchText.toLowerCase())\n );\n }\n\n setFilteredProducts(result);\n };\n\n // 获取用户拥有的店铺列表(按平台分组)\n const getUserShopsByPlatform = () => {\n const grouped: Record = {};\n CURRENT_USER_SHOPS.forEach(shop => {\n if (!grouped[shop.platform]) {\n grouped[shop.platform] = [];\n }\n grouped[shop.platform].push(shop);\n });\n return grouped;\n };\n\n // 查看商品详情\n const handleViewDetail = (product: CrossPlatformProduct) => {\n setSelectedProduct(product);\n setDetailModalVisible(true);\n };\n\n // 打开同步弹窗\n const handleOpenSyncModal = (product: CrossPlatformProduct) => {\n setSyncingProduct(product);\n // 默认选中已上架的店铺\n const listedShops = Object.entries(product.platforms)\n .filter(([_, info]) => info.status !== 'NOT_LISTED')\n .filter(([shopId, info]) => info.shopInfo.apiSupported) // 只选中支持API的店铺\n .map(([shopId]) => shopId);\n setSelectedSyncShops(listedShops);\n syncForm.setFieldsValue({\n shops: listedShops,\n syncStock: true,\n syncPrice: false,\n });\n setSyncModalVisible(true);\n };\n\n // 执行同步\n const handleSync = async () => {\n if (selectedSyncShops.length === 0) {\n message.warning('请至少选择一个目标店铺');\n return;\n }\n\n const values = syncForm.getFieldsValue();\n message.loading(`正在同步到 ${selectedSyncShops.length} 个店铺...`, 2);\n \n await new Promise(resolve => setTimeout(resolve, 2000));\n \n message.success(`同步完成!库存: ${values.syncStock ? '是' : '否'}, 价格: ${values.syncPrice ? '是' : '否'}`);\n setSyncModalVisible(false);\n fetchProducts();\n };\n\n // 批量同步库存\n const handleBatchSyncStock = async () => {\n if (selectedRows.length === 0) {\n message.warning('请先选择商品');\n return;\n }\n\n Modal.confirm({\n title: '批量同步库存',\n content: `将对 ${selectedRows.length} 个商品同步库存到所有支持API的店铺`,\n onOk: async () => {\n message.loading('正在批量同步库存...', 2);\n await new Promise(resolve => setTimeout(resolve, 2000));\n message.success('批量同步完成');\n fetchProducts();\n },\n });\n };\n\n // 获取店铺统计\n const getShopStats = () => {\n const stats: Record = { ALL: products.length };\n CURRENT_USER_SHOPS.forEach(shop => {\n stats[shop.shopId] = products.filter(p => \n p.platforms[shop.shopId] && p.platforms[shop.shopId].status !== 'NOT_LISTED'\n ).length;\n });\n return stats;\n };\n\n const shopStats = getShopStats();\n const userShopsByPlatform = getUserShopsByPlatform();\n\n // 表格列定义\n const columns: ColumnsType = [\n {\n title: '商品信息',\n key: 'productInfo',\n width: 300,\n render: (_, record) => (\n \n \n
\n
{record.name}
\n
SKU: {record.sku}
\n
{record.category}
\n
\n
\n ),\n },\n {\n title: '本地信息',\n key: 'localInfo',\n width: 150,\n render: (_, record) => (\n
\n
价格: ${record.basePrice.toFixed(2)}
\n
库存: {record.baseStock}
\n
总销量: {record.totalSales}
\n
\n ),\n },\n // 为每个用户店铺生成一列\n ...CURRENT_USER_SHOPS.map(shop => ({\n title: (\n \n
\n
\n {PLATFORM_CONFIG[shop.platform]?.icon}\n
\n
{shop.region}
\n
\n
\n ),\n key: `shop-${shop.shopId}`,\n width: 100,\n align: 'center' as const,\n render: (_: any, record: CrossPlatformProduct) => {\n const platformInfo = record.platforms[shop.shopId];\n if (!platformInfo || platformInfo.status === 'NOT_LISTED') {\n return 未上架;\n }\n\n const statusConfig = STATUS_CONFIG[platformInfo.status];\n const canManage = shop.apiSupported;\n\n return (\n \n handleViewDetail(record)}>\n 查看详情\n \n {canManage && platformInfo.status === 'LIVE' && (\n handleOpenSyncModal(record)}>\n 同步库存/价格\n \n )}\n {canManage && platformInfo.status === 'LIVE' && (\n message.info('打开价格调整')}>\n 调整价格\n \n )}\n {canManage && platformInfo.status === 'LIVE' && (\n message.info('执行下架')}>\n 下架\n \n )}\n {canManage && platformInfo.status === 'OFFLINE' && (\n message.info('执行上架')}>\n 上架\n \n )}\n {!canManage && (\n \n 不支持API管理\n \n )}\n {platformInfo.listingUrl && (\n \n \n 打开商品页\n \n \n )}\n \n }\n >\n \n {statusConfig.text}\n {!canManage && *}\n \n \n );\n },\n })),\n {\n title: '操作',\n key: 'action',\n width: 180,\n fixed: 'right',\n render: (_, record) => (\n \n \n \n \n ),\n },\n ];\n\n return (\n
\n \n

• 集中管理您所有店铺的在线商品

\n

• 标记 * 的店铺暂不支持API管理,需要手动操作

\n

• 同步操作需要指定目标店铺,只同步到支持API的店铺

\n

• 当前拥有 {CURRENT_USER_SHOPS.length} 个店铺

\n
\n }\n type=\"info\"\n showIcon\n style={{ marginBottom: 16 }}\n />\n\n \n \n
\n \n \n 跨平台商品列表\n \n \n \n \n \n \n {Object.entries(userShopsByPlatform).map(([platform, shops]) => (\n \n {shops.map(shop => (\n \n ))}\n \n ))}\n \n \n \n \n \n\n {selectedRows.length > 0 && (\n \n \n \n }\n />\n )}\n\n `共 ${total} 个商品` }}\n rowSelection={{\n selectedRowKeys: selectedRows.map(r => r.id),\n onChange: (_, rows) => setSelectedRows(rows),\n }}\n />\n \n\n {/* 商品详情弹窗 */}\n setDetailModalVisible(false)}\n width={900}\n footer={[\n ,\n ]}\n >\n {selectedProduct && (\n \n \n \n \n \n \n \n \n {selectedProduct.name}\n {selectedProduct.sku}\n {selectedProduct.category}\n ${selectedProduct.basePrice.toFixed(2)}\n {selectedProduct.baseStock}\n {selectedProduct.totalSales}\n {selectedProduct.createdAt}\n {selectedProduct.updatedAt}\n \n \n
\n 商品描述:\n

{selectedProduct.description}

\n
\n \n \n \n \n (\n \n {PLATFORM_CONFIG[info.shopInfo.platform]?.icon}\n {info.shopInfo.shopName}\n {info.shopInfo.region}\n {!info.shopInfo.apiSupported && No API}\n \n ),\n },\n {\n title: '状态',\n render: ([_, info]: [string, any]) => (\n \n {STATUS_CONFIG[info.status].text}\n \n ),\n },\n {\n title: '平台SKU',\n render: ([_, info]: [string, any]) => info.platformSku,\n },\n {\n title: '售价',\n render: ([_, info]: [string, any]) => `$${info.price.toFixed(2)}`,\n },\n {\n title: '库存',\n render: ([_, info]: [string, any]) => info.stock,\n },\n {\n title: '销量',\n render: ([_, info]: [string, any]) => info.sales,\n },\n {\n title: '最后同步',\n render: ([_, info]: [string, any]) => info.lastSync,\n },\n {\n title: '操作',\n render: ([shopId, info]: [string, any]) => (\n \n {info.shopInfo.apiSupported && (\n \n )}\n {info.listingUrl && (\n \n 查看\n \n )}\n \n ),\n },\n ]}\n />\n \n \n )}\n \n\n {/* 同步弹窗 */}\n setSyncModalVisible(false)}\n onOk={handleSync}\n width={600}\n >\n {syncingProduct && (\n
\n \n \n \n \n {Object.entries(syncingProduct.platforms)\n .filter(([_, info]) => info.status !== 'NOT_LISTED')\n .filter(([_, info]) => info.shopInfo.apiSupported)\n .map(([shopId, info]) => (\n \n ))}\n \n \n\n \n \n \n\n \n \n \n\n \n \n )}\n \n \n );\n};\n\n// 添加 OptGroup 导入\nconst { OptGroup } = Select;\n\nexport default CrossPlatformManage;\n", + "__isJSFile": true, + "__absFile": "D:/trae_projects/makemd/makemd/dashboard/src/pages/Product/CrossPlatformManage.tsx" + }, "Product/ProfitMonitor/index": { "path": "Product/ProfitMonitor", "id": "Product/ProfitMonitor/index", + "parentId": "@@/global-layout", "file": "Product/ProfitMonitor/index.tsx", "absPath": "/Product/ProfitMonitor", - "__content": "import React, { useState, useEffect } from 'react';\nimport { Card, Layout, Typography, Row, Col, Select, Button, Table, Statistic, Spin, message, Alert, Badge } from 'antd';\nimport { LineChart, Line, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';\nimport { AlertOutlined, TrendingUpOutlined, TrendingDownOutlined, ReloadOutlined, EyeOutlined } from '@ant-design/icons';\nimport { Link } from 'umi';\n\nconst { Content } = Layout;\nconst { Title, Text, Paragraph } = Typography;\nconst { Option } = Select;\n\ninterface ProfitData {\n id: string;\n date: string;\n profit: number;\n sales: number;\n cost: number;\n roi: number;\n}\n\ninterface AlertItem {\n id: string;\n productId: string;\n productName: string;\n type: string;\n message: string;\n severity: 'info' | 'warning' | 'error';\n date: string;\n}\n\ninterface ProductProfit {\n id: string;\n productId: string;\n productName: string;\n currentProfit: number;\n previousProfit: number;\n change: number;\n roi: number;\n status: 'up' | 'down' | 'stable';\n}\n\nconst ProfitMonitor: React.FC = () => {\n const [loading, setLoading] = useState(false);\n const [profitData, setProfitData] = useState([]);\n const [alerts, setAlerts] = useState([]);\n const [productProfits, setProductProfits] = useState([]);\n const [selectedProduct, setSelectedProduct] = useState('');\n const [timeRange, setTimeRange] = useState('7d');\n\n const mockProfitData: ProfitData[] = [\n { id: '1', date: '2026-03-12', profit: 14900, sales: 12000, cost: 8000, roi: 99.33 },\n { id: '2', date: '2026-03-13', profit: 15200, sales: 12500, cost: 8200, roi: 100.67 },\n { id: '3', date: '2026-03-14', profit: 14800, sales: 11800, cost: 8100, roi: 98.67 },\n { id: '4', date: '2026-03-15', profit: 16500, sales: 13500, cost: 8500, roi: 103.33 },\n { id: '5', date: '2026-03-16', profit: 17200, sales: 14000, cost: 8800, roi: 105.33 },\n { id: '6', date: '2026-03-17', profit: 16800, sales: 13800, cost: 8700, roi: 104.00 },\n { id: '7', date: '2026-03-18', profit: 18500, sales: 15000, cost: 9200, roi: 108.33 },\n ];\n\n const mockAlerts: AlertItem[] = [\n {\n id: '1',\n productId: 'P001',\n productName: '智能手表',\n type: '利润下降',\n message: '连续3天利润下降,当前利润低于预期15%',\n severity: 'warning',\n date: '2026-03-18 10:30',\n },\n {\n id: '2',\n productId: 'P002',\n productName: '无线耳机',\n type: 'ROI异常',\n message: 'ROI突然上升30%,可能存在定价问题',\n severity: 'info',\n date: '2026-03-18 09:15',\n },\n {\n id: '3',\n productId: 'P003',\n productName: '智能音箱',\n type: '成本上升',\n message: '成本上升10%,建议调整售价',\n severity: 'error',\n date: '2026-03-17 16:45',\n },\n ];\n\n const mockProductProfits: ProductProfit[] = [\n {\n id: '1',\n productId: 'P001',\n productName: '智能手表',\n currentProfit: 14900,\n previousProfit: 15200,\n change: -2.0,\n roi: 99.33,\n status: 'down',\n },\n {\n id: '2',\n productId: 'P002',\n productName: '无线耳机',\n currentProfit: 23800,\n previousProfit: 21500,\n change: 10.7,\n roi: 148.75,\n status: 'up',\n },\n {\n id: '3',\n productId: 'P003',\n productName: '智能音箱',\n currentProfit: 9500,\n previousProfit: 9400,\n change: 1.1,\n roi: 86.36,\n status: 'stable',\n },\n ];\n\n const fetchProfitData = () => {\n setLoading(true);\n // 模拟API请求\n setTimeout(() => {\n setProfitData(mockProfitData);\n setAlerts(mockAlerts);\n setProductProfits(mockProductProfits);\n setLoading(false);\n }, 1000);\n };\n\n useEffect(() => {\n fetchProfitData();\n }, []);\n\n const handleProductChange = (value: string) => {\n setSelectedProduct(value);\n };\n\n const handleTimeRangeChange = (value: string) => {\n setTimeRange(value);\n };\n\n const handleRefresh = () => {\n fetchProfitData();\n };\n\n const columns = [\n {\n title: '商品ID',\n dataIndex: 'productId',\n key: 'productId',\n },\n {\n title: '商品名称',\n dataIndex: 'productName',\n key: 'productName',\n },\n {\n title: '当前利润',\n dataIndex: 'currentProfit',\n key: 'currentProfit',\n render: (text: number) => `¥${text}`,\n },\n {\n title: '变化率',\n dataIndex: 'change',\n key: 'change',\n render: (text: number) => (\n 0 ? '#52c41a' : text < 0 ? '#ff4d4f' : '#1890ff' }}>\n {text > 0 ? '+' : ''}{text}%\n \n ),\n },\n {\n title: 'ROI',\n dataIndex: 'roi',\n key: 'roi',\n render: (text: number) => `${text}%`,\n },\n {\n title: '状态',\n dataIndex: 'status',\n key: 'status',\n render: (status: string) => (\n \n ),\n },\n ];\n\n const alertColumns = [\n {\n title: '商品ID',\n dataIndex: 'productId',\n key: 'productId',\n },\n {\n title: '商品名称',\n dataIndex: 'productName',\n key: 'productName',\n },\n {\n title: '类型',\n dataIndex: 'type',\n key: 'type',\n },\n {\n title: '消息',\n dataIndex: 'message',\n key: 'message',\n },\n {\n title: '严重程度',\n dataIndex: 'severity',\n key: 'severity',\n render: (severity: string) => (\n \n ),\n },\n {\n title: '时间',\n dataIndex: 'date',\n key: 'date',\n },\n ];\n\n return (\n \n
\n 商品利润监控\n
\n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n\n {/* 利润概览 */}\n \n
\n \n sum + item.currentProfit, 0)}\n prefix=\"¥\"\n valueStyle={{ color: '#3f8600' }}\n />\n \n \n \n \n sum + item.roi, 0) / mockProductProfits.length}\n suffix=\"%\"\n valueStyle={{ color: '#3f8600' }}\n />\n \n \n \n \n }\n valueStyle={{ color: '#faad14' }}\n />\n \n \n \n \n }\n valueStyle={{ color: '#1890ff' }}\n />\n \n \n \n\n {/* 利润趋势图表 */}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n {/* 商品利润列表 */}\n \n \n
\n \n \n\n {/* 利润预警 */}\n \n \n
\n \n \n \n \n );\n};\n\nexport default ProfitMonitor;", + "__content": "import React, { useState, useEffect } from 'react';\nimport { Card, Typography, Row, Col, Select, Button, Table, Statistic, Spin, message, Alert, Badge } from 'antd';\nimport { LineChart, Line, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';\nimport { AlertOutlined, TrendingUpOutlined, TrendingDownOutlined, ReloadOutlined, EyeOutlined } from '@ant-design/icons';\nimport { Link } from 'umi';\n\nconst { Title, Text, Paragraph } = Typography;\nconst { Option } = Select;\n\ninterface ProfitData {\n id: string;\n date: string;\n profit: number;\n sales: number;\n cost: number;\n roi: number;\n}\n\ninterface AlertItem {\n id: string;\n productId: string;\n productName: string;\n type: string;\n message: string;\n severity: 'info' | 'warning' | 'error';\n date: string;\n}\n\ninterface ProductProfit {\n id: string;\n productId: string;\n productName: string;\n currentProfit: number;\n previousProfit: number;\n change: number;\n roi: number;\n status: 'up' | 'down' | 'stable';\n}\n\nconst ProfitMonitor: React.FC = () => {\n const [loading, setLoading] = useState(false);\n const [profitData, setProfitData] = useState([]);\n const [alerts, setAlerts] = useState([]);\n const [productProfits, setProductProfits] = useState([]);\n const [selectedProduct, setSelectedProduct] = useState('');\n const [timeRange, setTimeRange] = useState('7d');\n\n const mockProfitData: ProfitData[] = [\n { id: '1', date: '2026-03-12', profit: 14900, sales: 12000, cost: 8000, roi: 99.33 },\n { id: '2', date: '2026-03-13', profit: 15200, sales: 12500, cost: 8200, roi: 100.67 },\n { id: '3', date: '2026-03-14', profit: 14800, sales: 11800, cost: 8100, roi: 98.67 },\n { id: '4', date: '2026-03-15', profit: 16500, sales: 13500, cost: 8500, roi: 103.33 },\n { id: '5', date: '2026-03-16', profit: 17200, sales: 14000, cost: 8800, roi: 105.33 },\n { id: '6', date: '2026-03-17', profit: 16800, sales: 13800, cost: 8700, roi: 104.00 },\n { id: '7', date: '2026-03-18', profit: 18500, sales: 15000, cost: 9200, roi: 108.33 },\n ];\n\n const mockAlerts: AlertItem[] = [\n {\n id: '1',\n productId: 'P001',\n productName: '智能手表',\n type: '利润下降',\n message: '连续3天利润下降,当前利润低于预期15%',\n severity: 'warning',\n date: '2026-03-18 10:30',\n },\n {\n id: '2',\n productId: 'P002',\n productName: '无线耳机',\n type: 'ROI异常',\n message: 'ROI突然上升30%,可能存在定价问题',\n severity: 'info',\n date: '2026-03-18 09:15',\n },\n {\n id: '3',\n productId: 'P003',\n productName: '智能音箱',\n type: '成本上升',\n message: '成本上升10%,建议调整售价',\n severity: 'error',\n date: '2026-03-17 16:45',\n },\n ];\n\n const mockProductProfits: ProductProfit[] = [\n {\n id: '1',\n productId: 'P001',\n productName: '智能手表',\n currentProfit: 14900,\n previousProfit: 15200,\n change: -2.0,\n roi: 99.33,\n status: 'down',\n },\n {\n id: '2',\n productId: 'P002',\n productName: '无线耳机',\n currentProfit: 23800,\n previousProfit: 21500,\n change: 10.7,\n roi: 148.75,\n status: 'up',\n },\n {\n id: '3',\n productId: 'P003',\n productName: '智能音箱',\n currentProfit: 9500,\n previousProfit: 9400,\n change: 1.1,\n roi: 86.36,\n status: 'stable',\n },\n ];\n\n const fetchProfitData = () => {\n setLoading(true);\n // 模拟API请求\n setTimeout(() => {\n setProfitData(mockProfitData);\n setAlerts(mockAlerts);\n setProductProfits(mockProductProfits);\n setLoading(false);\n }, 1000);\n };\n\n useEffect(() => {\n fetchProfitData();\n }, []);\n\n const handleProductChange = (value: string) => {\n setSelectedProduct(value);\n };\n\n const handleTimeRangeChange = (value: string) => {\n setTimeRange(value);\n };\n\n const handleRefresh = () => {\n fetchProfitData();\n };\n\n const columns = [\n {\n title: '商品ID',\n dataIndex: 'productId',\n key: 'productId',\n },\n {\n title: '商品名称',\n dataIndex: 'productName',\n key: 'productName',\n },\n {\n title: '当前利润',\n dataIndex: 'currentProfit',\n key: 'currentProfit',\n render: (text: number) => `¥${text}`,\n },\n {\n title: '变化率',\n dataIndex: 'change',\n key: 'change',\n render: (text: number) => (\n 0 ? '#52c41a' : text < 0 ? '#ff4d4f' : '#1890ff' }}>\n {text > 0 ? '+' : ''}{text}%\n \n ),\n },\n {\n title: 'ROI',\n dataIndex: 'roi',\n key: 'roi',\n render: (text: number) => `${text}%`,\n },\n {\n title: '状态',\n dataIndex: 'status',\n key: 'status',\n render: (status: string) => (\n \n ),\n },\n ];\n\n const alertColumns = [\n {\n title: '商品ID',\n dataIndex: 'productId',\n key: 'productId',\n },\n {\n title: '商品名称',\n dataIndex: 'productName',\n key: 'productName',\n },\n {\n title: '类型',\n dataIndex: 'type',\n key: 'type',\n },\n {\n title: '消息',\n dataIndex: 'message',\n key: 'message',\n },\n {\n title: '严重程度',\n dataIndex: 'severity',\n key: 'severity',\n render: (severity: string) => (\n \n ),\n },\n {\n title: '时间',\n dataIndex: 'date',\n key: 'date',\n },\n ];\n\n return (\n
\n
\n 商品利润监控\n
\n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n\n {/* 利润概览 */}\n \n
\n \n sum + item.currentProfit, 0)}\n prefix=\"¥\"\n valueStyle={{ color: '#3f8600' }}\n />\n \n \n \n \n sum + item.roi, 0) / mockProductProfits.length}\n suffix=\"%\"\n valueStyle={{ color: '#3f8600' }}\n />\n \n \n \n \n }\n valueStyle={{ color: '#faad14' }}\n />\n \n \n \n \n }\n valueStyle={{ color: '#1890ff' }}\n />\n \n \n \n\n {/* 利润趋势图表 */}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n {/* 商品利润列表 */}\n \n \n
\n \n \n\n {/* 利润预警 */}\n \n \n
\n \n \n \n \n );\n};\n\nexport default ProfitMonitor;", "__isJSFile": true, "__absFile": "D:/trae_projects/makemd/makemd/dashboard/src/pages/Product/ProfitMonitor/index.tsx" }, + "Settings/CostTemplateConfig": { + "path": "Settings/CostTemplateConfig", + "id": "Settings/CostTemplateConfig", + "parentId": "@@/global-layout", + "file": "Settings/CostTemplateConfig.tsx", + "absPath": "/Settings/CostTemplateConfig", + "__content": "import React, { useState, useEffect } from 'react';\r\nimport {\r\n Card,\r\n Table,\r\n Button,\r\n Space,\r\n Modal,\r\n Form,\r\n Input,\r\n InputNumber,\r\n Select,\r\n Switch,\r\n Tag,\r\n Badge,\r\n message,\r\n Popconfirm,\r\n Tooltip,\r\n Row,\r\n Col,\r\n Statistic,\r\n Divider,\r\n Collapse,\r\n} from 'antd';\r\nimport {\r\n PlusOutlined,\r\n EditOutlined,\r\n DeleteOutlined,\r\n CopyOutlined,\r\n CalculatorOutlined,\r\n PercentageOutlined,\r\n DollarOutlined,\r\n SettingOutlined,\r\n} from '@ant-design/icons';\r\nimport type { ColumnsType } from 'antd/es/table';\r\n\r\nconst { Option } = Select;\r\nconst { Panel } = Collapse;\r\nconst { TextArea } = Input;\r\n\r\ninterface CostTemplate {\r\n id: string;\r\n name: string;\r\n platform: string;\r\n category: string;\r\n description: string;\r\n isDefault: boolean;\r\n isActive: boolean;\r\n createdAt: string;\r\n updatedAt: string;\r\n costs: CostItem[];\r\n}\r\n\r\ninterface CostItem {\r\n id: string;\r\n name: string;\r\n type: 'FIXED' | 'PERCENTAGE' | 'PER_UNIT';\r\n value: number;\r\n currency: string;\r\n applyTo: 'PRODUCT' | 'SHIPPING' | 'ORDER' | 'PLATFORM_FEE';\r\n description: string;\r\n}\r\n\r\nconst PLATFORM_LIST = ['AMAZON', 'EBAY', 'SHOPIFY', 'SHOPEE', 'LAZADA', 'ALIBABA', 'ALL'];\r\nconst CATEGORY_LIST = ['Electronics', 'Clothing', 'Home', 'Industrial', 'General'];\r\n\r\nconst COST_TYPE_CONFIG: Record = {\r\n FIXED: { color: 'blue', text: 'Fixed', icon: },\r\n PERCENTAGE: { color: 'green', text: 'Percentage', icon: },\r\n PER_UNIT: { color: 'orange', text: 'Per Unit', icon: },\r\n};\r\n\r\nconst CostTemplateConfig: React.FC = () => {\r\n const [loading, setLoading] = useState(false);\r\n const [templates, setTemplates] = useState([]);\r\n const [modalVisible, setModalVisible] = useState(false);\r\n const [detailModalVisible, setDetailModalVisible] = useState(false);\r\n const [editingTemplate, setEditingTemplate] = useState(null);\r\n const [viewingTemplate, setViewingTemplate] = useState(null);\r\n const [costItems, setCostItems] = useState([]);\r\n const [form] = Form.useForm();\r\n\r\n useEffect(() => {\r\n fetchTemplates();\r\n }, []);\r\n\r\n const fetchTemplates = async () => {\r\n setLoading(true);\r\n try {\r\n const mockTemplates: CostTemplate[] = [\r\n {\r\n id: '1',\r\n name: 'Amazon US Standard',\r\n platform: 'AMAZON',\r\n category: 'Electronics',\r\n description: 'Standard cost template for Amazon US electronics',\r\n isDefault: true,\r\n isActive: true,\r\n createdAt: '2026-01-15',\r\n updatedAt: '2026-03-10',\r\n costs: [\r\n { id: 'c1', name: 'Platform Fee', type: 'PERCENTAGE', value: 15, currency: 'USD', applyTo: 'PLATFORM_FEE', description: 'Amazon referral fee' },\r\n { id: 'c2', name: 'FBA Fee', type: 'PER_UNIT', value: 3.5, currency: 'USD', applyTo: 'SHIPPING', description: 'Fulfillment fee' },\r\n { id: 'c3', name: 'Storage Fee', type: 'PER_UNIT', value: 0.5, currency: 'USD', applyTo: 'PRODUCT', description: 'Monthly storage' },\r\n ],\r\n },\r\n {\r\n id: '2',\r\n name: 'eBay Standard',\r\n platform: 'EBAY',\r\n category: 'General',\r\n description: 'Standard cost template for eBay',\r\n isDefault: true,\r\n isActive: true,\r\n createdAt: '2026-01-20',\r\n updatedAt: '2026-03-05',\r\n costs: [\r\n { id: 'c4', name: 'Final Value Fee', type: 'PERCENTAGE', value: 12.5, currency: 'USD', applyTo: 'PLATFORM_FEE', description: 'eBay final value fee' },\r\n { id: 'c5', name: 'PayPal Fee', type: 'PERCENTAGE', value: 2.9, currency: 'USD', applyTo: 'ORDER', description: 'Payment processing fee' },\r\n ],\r\n },\r\n {\r\n id: '3',\r\n name: 'Shopify Standard',\r\n platform: 'SHOPIFY',\r\n category: 'General',\r\n description: 'Standard cost template for Shopify stores',\r\n isDefault: true,\r\n isActive: true,\r\n createdAt: '2026-02-01',\r\n updatedAt: '2026-03-15',\r\n costs: [\r\n { id: 'c6', name: 'Transaction Fee', type: 'PERCENTAGE', value: 2.0, currency: 'USD', applyTo: 'ORDER', description: 'Shopify transaction fee' },\r\n { id: 'c7', name: 'Payment Gateway', type: 'PERCENTAGE', value: 2.9, currency: 'USD', applyTo: 'ORDER', description: 'Credit card processing' },\r\n ],\r\n },\r\n {\r\n id: '4',\r\n name: 'Industrial Equipment',\r\n platform: 'ALL',\r\n category: 'Industrial',\r\n description: 'Cost template for industrial equipment across platforms',\r\n isDefault: false,\r\n isActive: true,\r\n createdAt: '2026-02-15',\r\n updatedAt: '2026-03-18',\r\n costs: [\r\n { id: 'c8', name: 'Shipping Surcharge', type: 'PERCENTAGE', value: 5, currency: 'USD', applyTo: 'SHIPPING', description: 'Heavy item shipping' },\r\n { id: 'c9', name: 'Insurance', type: 'PERCENTAGE', value: 1.5, currency: 'USD', applyTo: 'SHIPPING', description: 'Shipping insurance' },\r\n ],\r\n },\r\n ];\r\n setTemplates(mockTemplates);\r\n } catch (error) {\r\n message.error('Failed to load cost templates');\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n const handleAdd = () => {\r\n setEditingTemplate(null);\r\n setCostItems([]);\r\n form.resetFields();\r\n setModalVisible(true);\r\n };\r\n\r\n const handleEdit = (record: CostTemplate) => {\r\n setEditingTemplate(record);\r\n setCostItems(record.costs);\r\n form.setFieldsValue(record);\r\n setModalVisible(true);\r\n };\r\n\r\n const handleView = (record: CostTemplate) => {\r\n setViewingTemplate(record);\r\n setDetailModalVisible(true);\r\n };\r\n\r\n const handleDelete = (id: string) => {\r\n setTemplates(templates.filter(t => t.id !== id));\r\n message.success('Template deleted successfully');\r\n };\r\n\r\n const handleDuplicate = (record: CostTemplate) => {\r\n const newTemplate: CostTemplate = {\r\n ...record,\r\n id: `${Date.now()}`,\r\n name: `${record.name} (Copy)`,\r\n isDefault: false,\r\n createdAt: new Date().toISOString().split('T')[0],\r\n updatedAt: new Date().toISOString().split('T')[0],\r\n };\r\n setTemplates([...templates, newTemplate]);\r\n message.success('Template duplicated successfully');\r\n };\r\n\r\n const handleSubmit = async () => {\r\n try {\r\n const values = await form.validateFields();\r\n const templateData: CostTemplate = {\r\n ...values,\r\n id: editingTemplate?.id || `${Date.now()}`,\r\n costs: costItems,\r\n createdAt: editingTemplate?.createdAt || new Date().toISOString().split('T')[0],\r\n updatedAt: new Date().toISOString().split('T')[0],\r\n isActive: true,\r\n };\r\n\r\n if (editingTemplate) {\r\n setTemplates(templates.map(t => t.id === editingTemplate.id ? templateData : t));\r\n message.success('Template updated successfully');\r\n } else {\r\n setTemplates([...templates, templateData]);\r\n message.success('Template created successfully');\r\n }\r\n setModalVisible(false);\r\n } catch (error) {\r\n console.error('Form validation failed:', error);\r\n }\r\n };\r\n\r\n const handleAddCostItem = () => {\r\n const newItem: CostItem = {\r\n id: `temp-${Date.now()}`,\r\n name: '',\r\n type: 'FIXED',\r\n value: 0,\r\n currency: 'USD',\r\n applyTo: 'PRODUCT',\r\n description: '',\r\n };\r\n setCostItems([...costItems, newItem]);\r\n };\r\n\r\n const handleUpdateCostItem = (id: string, field: keyof CostItem, value: any) => {\r\n setCostItems(costItems.map(item => \r\n item.id === id ? { ...item, [field]: value } : item\r\n ));\r\n };\r\n\r\n const handleRemoveCostItem = (id: string) => {\r\n setCostItems(costItems.filter(item => item.id !== id));\r\n };\r\n\r\n const handleSetDefault = (id: string) => {\r\n setTemplates(templates.map(t => ({\r\n ...t,\r\n isDefault: t.id === id,\r\n })));\r\n message.success('Default template updated');\r\n };\r\n\r\n const stats = {\r\n total: templates.length,\r\n active: templates.filter(t => t.isActive).length,\r\n defaults: templates.filter(t => t.isDefault).length,\r\n platforms: new Set(templates.map(t => t.platform)).size,\r\n };\r\n\r\n const columns: ColumnsType = [\r\n {\r\n title: 'Name',\r\n dataIndex: 'name',\r\n key: 'name',\r\n width: 200,\r\n render: (text, record) => (\r\n \r\n handleView(record)}>{text}\r\n {record.isDefault && Default}\r\n \r\n ),\r\n },\r\n {\r\n title: 'Platform',\r\n dataIndex: 'platform',\r\n key: 'platform',\r\n width: 100,\r\n render: (platform) => {platform},\r\n },\r\n {\r\n title: 'Category',\r\n dataIndex: 'category',\r\n key: 'category',\r\n width: 100,\r\n },\r\n {\r\n title: 'Cost Items',\r\n key: 'costCount',\r\n width: 100,\r\n render: (_, record) => ,\r\n },\r\n {\r\n title: 'Status',\r\n dataIndex: 'isActive',\r\n key: 'isActive',\r\n width: 80,\r\n render: (active) => {active ? 'Active' : 'Inactive'},\r\n },\r\n {\r\n title: 'Updated',\r\n dataIndex: 'updatedAt',\r\n key: 'updatedAt',\r\n width: 100,\r\n },\r\n {\r\n title: 'Actions',\r\n key: 'action',\r\n width: 180,\r\n fixed: 'right',\r\n render: (_, record) => (\r\n \r\n \r\n \r\n \r\n )}\r\n handleDelete(record.id)}>\r\n \r\n \r\n } />\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n } onClick={handleAdd}>\r\n Create Template\r\n \r\n }\r\n >\r\n `Total ${total} templates` }}\r\n />\r\n \r\n\r\n setModalVisible(false)}\r\n width={800}\r\n >\r\n
\r\n \r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n