refactor: 重构项目结构并优化类型定义

- 移除extension模块,将功能迁移至node-agent
- 修复类型导出问题,使用export type明确类型导出
- 统一数据库连接方式,从直接导入改为使用config/database
- 更新文档中的项目结构描述
- 添加多个服务的实用方法,如getForecast、getBalances等
- 修复类型错误和TS1205警告
- 优化RedisService调用方式
- 添加新的实体类型定义
- 更新审计日志格式,统一字段命名
This commit is contained in:
2026-03-21 15:04:06 +08:00
parent 888d3844f3
commit 15ee1758f5
286 changed files with 9110 additions and 21453 deletions

1
dashboard/errors.txt Normal file
View File

@@ -0,0 +1 @@
../server/src/shared/types/index.ts(3,43): error TS1205: Re-exporting a type when 'isolatedModules' is enabled requires using 'export type'.

BIN
dashboard/errors_full.txt Normal file

Binary file not shown.

View File

@@ -1,8 +1,9 @@
// FE-EXP003: 前端组件库优化
import React from 'react';
import { Button, Card, Input, Table, Form, Select, DatePicker, Modal, message, Divider, Badge, Tag, Tooltip, Popconfirm, Switch, Radio, Checkbox, Slider, Rate, Upload, Progress, Alert, Space, Typography } from 'antd';
import dayjs from 'dayjs';
import { UploadOutlined, PlusOutlined, DeleteOutlined, EditOutlined, EyeOutlined, CheckOutlined, CloseOutlined, InfoCircleOutlined, WarningOutlined, ErrorOutlined, SuccessOutlined } from '@ant-design/icons';
import { UploadOutlined, PlusOutlined, DeleteOutlined, EditOutlined, EyeOutlined, CheckOutlined, CloseOutlined, InfoCircleOutlined, WarningOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
const { Text, Paragraph, Title } = Typography;
@@ -434,8 +435,8 @@ export const Icons = {
CloseOutlined,
InfoCircleOutlined,
WarningOutlined,
ErrorOutlined,
SuccessOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
};
export default {

View File

@@ -1,6 +1,6 @@
import React, { InputHTMLAttributes } from 'react';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix'> {
label?: string;
error?: string;
disabled?: boolean;

View File

@@ -1,7 +1,7 @@
// FE-EXP002: 前端性能优化
import React, { useState, useEffect, useRef } from 'react';
import { Spin, Alert, Button, Card, Row, Col, Progress, Statistic, Tooltip } from 'antd';
import { LoadingOutlined, CheckCircleOutlined, AlertOutlined, ClockCircleOutlined, ZapOutlined, DatabaseOutlined } from '@ant-design/icons';
import { LoadingOutlined, CheckCircleOutlined, AlertOutlined, ClockCircleOutlined, ApiOutlined, DatabaseOutlined } from '@ant-design/icons';
const { Meta } = Card;
@@ -95,7 +95,7 @@ export const PerformanceMonitor: React.FC<{
<Col span={8}>
<Card>
<Meta
avatar={<ZapOutlined style={{ fontSize: '24px', color: '#52c41a' }} />}
avatar={<ApiOutlined style={{ fontSize: '24px', color: '#52c41a' }} />}
title="Render Time"
description={`${metrics.renderTime.toFixed(0)}ms`}
/>
@@ -285,7 +285,7 @@ export const PerformanceSuggestions: React.FC = () => {
{
title: 'Use Lazy Loading',
description: 'Load components only when they are needed to reduce initial load time.',
icon: <ZapOutlined />,
icon: <ApiOutlined />,
severity: 'high',
},
{
@@ -297,7 +297,7 @@ export const PerformanceSuggestions: React.FC = () => {
{
title: 'Minimize JavaScript',
description: 'Bundle and minify JavaScript files to reduce file size.',
icon: <ZapOutlined />,
icon: <ApiOutlined />,
severity: 'high',
},
{

View File

@@ -28,18 +28,12 @@ import {
ThunderboltOutlined,
} from '@ant-design/icons';
import { Link, useLocation, useNavigate, Outlet } from 'react-router-dom';
import type { MenuProps } from 'antd';
const { Header, Sider, Content } = Layout;
const { Title, Text } = Typography;
// 菜单项类型定义
interface MenuItem {
key: string;
icon?: React.ReactNode;
label: React.ReactNode;
children?: MenuItem[];
type?: 'divider';
}
type MenuItem = MenuProps['items'][number];
// 菜单项配置
const menuItems: MenuItem[] = [
@@ -194,6 +188,7 @@ const userMenuItems: MenuItem[] = [
label: '账号设置',
},
{
key: 'divider-1',
type: 'divider',
},
{
@@ -211,49 +206,22 @@ const MainLayout: React.FC = () => {
// 根据当前路径设置选中的菜单项
useEffect(() => {
const pathname = location.pathname;
// 找到匹配的菜单项
let matchedKey: string | undefined;
// 递归查找匹配的菜单项
const findMatchedItem = (items: MenuItem[]): boolean => {
const findKey = (items: MenuItem[]): string | undefined => {
for (const item of items) {
if (item.type === 'divider') continue;
if (item.children) {
// 检查子菜单项
for (const child of item.children) {
if (child.type === 'divider') continue;
if (child.key === '/') {
if (pathname === '/') {
matchedKey = child.key;
return true;
}
} else if (pathname.startsWith(child.key)) {
matchedKey = child.key;
return true;
}
if (item && 'key' in item && typeof item.key === 'string') {
if (item.key.startsWith('/') && pathname.startsWith(item.key)) {
return item.key;
}
// 递归检查子菜单
if (findMatchedItem(item.children)) {
return true;
}
} else {
// 检查当前菜单项
if (item.key === '/') {
if (pathname === '/') {
matchedKey = item.key;
return true;
}
} else if (pathname.startsWith(item.key)) {
matchedKey = item.key;
return true;
if ('children' in item && Array.isArray(item.children)) {
const found = findKey(item.children as MenuItem[]);
if (found) return found;
}
}
}
return false;
return undefined;
};
findMatchedItem(menuItems);
const matchedKey = findKey(menuItems);
if (matchedKey) {
setSelectedKeys([matchedKey]);
}

View File

@@ -7,7 +7,7 @@
* @created 2026-03-19
*/
import { Certificate } from '@/types/certificate';
import { Certificate, CertificateType, CertificateStatus } from '@/types/certificate';
/**
* 证书Mock数据列表
@@ -16,8 +16,8 @@ export const mockCertificates: Certificate[] = [
{
id: '1',
name: 'CE认证证书',
type: 'PRODUCT_CERT',
status: 'APPROVED',
type: CertificateType.PRODUCT_CERT,
status: CertificateStatus.APPROVED,
fileUrl: '/files/ce-cert.pdf',
fileName: 'CE-Certificate-2026.pdf',
uploadDate: '2026-03-15',
@@ -31,8 +31,8 @@ export const mockCertificates: Certificate[] = [
{
id: '2',
name: '营业执照',
type: 'BUSINESS_LICENSE',
status: 'APPROVED',
type: CertificateType.BUSINESS_LICENSE,
status: CertificateStatus.APPROVED,
fileUrl: '/files/business-license.pdf',
fileName: 'Business-License-2026.pdf',
uploadDate: '2026-01-10',
@@ -44,8 +44,8 @@ export const mockCertificates: Certificate[] = [
{
id: '3',
name: 'FCC认证',
type: 'SAFETY_CERT',
status: 'PENDING',
type: CertificateType.SAFETY_CERT,
status: CertificateStatus.PENDING,
fileUrl: '/files/fcc-cert.pdf',
fileName: 'FCC-Certificate.pdf',
uploadDate: '2026-03-18',
@@ -57,8 +57,8 @@ export const mockCertificates: Certificate[] = [
{
id: '4',
name: 'ISO9001',
type: 'QUALITY_CERT',
status: 'EXPIRED',
type: CertificateType.QUALITY_CERT,
status: CertificateStatus.EXPIRED,
fileUrl: '/files/iso9001.pdf',
fileName: 'ISO9001-2025.pdf',
uploadDate: '2025-01-01',
@@ -70,8 +70,8 @@ export const mockCertificates: Certificate[] = [
{
id: '5',
name: 'RoHS认证',
type: 'PRODUCT_CERT',
status: 'APPROVED',
type: CertificateType.PRODUCT_CERT,
status: CertificateStatus.APPROVED,
fileUrl: '/files/rohs-cert.pdf',
fileName: 'RoHS-Certificate.pdf',
uploadDate: '2026-02-20',
@@ -85,8 +85,8 @@ export const mockCertificates: Certificate[] = [
{
id: '6',
name: 'UL认证',
type: 'SAFETY_CERT',
status: 'PENDING',
type: CertificateType.SAFETY_CERT,
status: CertificateStatus.PENDING,
fileUrl: '/files/ul-cert.pdf',
fileName: 'UL-Certificate.pdf',
uploadDate: '2026-03-19',
@@ -98,8 +98,8 @@ export const mockCertificates: Certificate[] = [
{
id: '7',
name: 'CCC认证',
type: 'PRODUCT_CERT',
status: 'APPROVED',
type: CertificateType.PRODUCT_CERT,
status: CertificateStatus.APPROVED,
fileUrl: '/files/ccc-cert.pdf',
fileName: 'CCC-Certificate.pdf',
uploadDate: '2026-01-15',
@@ -113,8 +113,8 @@ export const mockCertificates: Certificate[] = [
{
id: '8',
name: 'GS认证',
type: 'SAFETY_CERT',
status: 'REJECTED',
type: CertificateType.SAFETY_CERT,
status: CertificateStatus.REJECTED,
fileUrl: '/files/gs-cert.pdf',
fileName: 'GS-Certificate.pdf',
uploadDate: '2026-03-10',
@@ -176,8 +176,8 @@ export function createMockCertificate(data: Partial<Certificate>): Certificate {
const newCert: Certificate = {
id: `${Date.now()}`,
name: data.name || '',
type: data.type || 'OTHER',
status: 'PENDING',
type: data.type || CertificateType.OTHER,
status: CertificateStatus.PENDING,
fileUrl: data.fileUrl || '/files/uploaded.pdf',
fileName: data.fileName || 'uploaded.pdf',
uploadDate: new Date().toISOString().split('T')[0],

View File

@@ -28,6 +28,7 @@ export interface SelectionRule {
id: string;
name: string;
description: string;
category?: string;
criteria: any;
enabled: boolean;
selected_count: number;

View File

@@ -18,6 +18,7 @@ import {
updateMockCertificate,
deleteMockCertificate,
} from './data/certificate.mock';
import type { Certificate } from '@/types/certificate';
/**
* MSW请求处理器
@@ -79,7 +80,7 @@ export const handlers = [
// 创建证书
// ============================================
http.post('/api/v1/certificate/certificates', async ({ request }) => {
const body = await request.json();
const body = await request.json() as Partial<Certificate>;
console.log('[MSW] POST /api/v1/certificate/certificates', body);
@@ -99,7 +100,7 @@ export const handlers = [
// ============================================
http.put('/api/v1/certificate/certificates/:id', async ({ params, request }) => {
const { id } = params;
const body = await request.json();
const body = await request.json() as Partial<Certificate>;
console.log('[MSW] PUT /api/v1/certificate/certificates/:id', { id, body });
@@ -123,11 +124,12 @@ export const handlers = [
// ============================================
http.put('/api/v1/certificate/certificates/:id/status', async ({ params, request }) => {
const { id } = params;
const { status, approvedBy } = await request.json();
const body = await request.json() as { status: string; approvedBy?: string };
const { status, approvedBy } = body;
console.log('[MSW] PUT /api/v1/certificate/certificates/:id/status', { id, status, approvedBy });
const updates: any = { status };
const updates: Partial<Certificate> = { status: status as Certificate['status'] };
if (status === 'APPROVED') {
updates.approvedBy = approvedBy || 'admin';
updates.approvedDate = new Date().toISOString().split('T')[0];

View File

@@ -174,7 +174,7 @@ export const ABTestConfigPage: React.FC = () => {
setLoading(true);
try {
const values = await form.validateFields();
const config: ABTestConfig = {
const config: ABTestConfigData = {
...values,
variants,
};

View File

@@ -287,7 +287,12 @@ const AIDecisionLogPage: React.FC = () => {
const [loading, setLoading] = useState(false);
const [detailVisible, setDetailVisible] = useState(false);
const [selectedLog, setSelectedLog] = useState<any>(null);
const [filters, setFilters] = useState({
const [filters, setFilters] = useState<{
decision_type: string | undefined;
status: string | undefined;
date_range: [any, any] | null;
search: string;
}>({
decision_type: undefined,
status: undefined,
date_range: null,
@@ -412,7 +417,7 @@ const AIDecisionLogPage: React.FC = () => {
title: '操作',
key: 'action',
width: 150,
fixed: 'right',
fixed: 'right' as const,
render: (_: any, record: any) => (
<Space>
<Button
@@ -625,7 +630,11 @@ const AIDecisionLogPage: React.FC = () => {
showQuickJumper: true,
showTotal: total => `${total} 条记录`,
}}
onChange={setPagination}
onChange={(pag) => setPagination({
current: pag.current || 1,
pageSize: pag.pageSize || 10,
total: pag.total || 0,
})}
scroll={{ x: 1500 }}
/>
</Card>

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { Card, Typography, Row, Col, Form, Input, Select, Button, Table, Statistic, Spin, message, Alert } from 'antd';
import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts';
import { RocketOutlined, SaveOutlined, BarChartOutlined, TargetOutlined, ReloadOutlined } from '@ant-design/icons';
import { RocketOutlined, SaveOutlined, BarChartOutlined, AimOutlined, ReloadOutlined } from '@ant-design/icons';
import { Link } from 'react-router-dom';
import { adOptimizationDataSource, OptimizationSuggestion, AdPerformance } from '@/services/adOptimizationDataSource';

View File

@@ -259,8 +259,8 @@ export const AdDelivery: React.FC = () => {
return (spend / clicks).toFixed(2);
};
const calculateROAS = (sales: number, spend: number) => {
if (spend === 0) return 0;
const calculateROAS = (sales: number, spend: number): string => {
if (spend === 0) return '0.00';
return (sales / spend).toFixed(2);
};

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { Card, Typography, Row, Col, Form, Input, Select, Button, Table, Switch, DatePicker, TimePicker, message, Alert, Modal } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, SaveOutlined, PlayCircleOutlined, PauseCircleOutlined, HistoryOutlined } from '@ant-design/icons';
import { Link } from 'umi';
import { Link } from 'react-router-dom';
import { marketingDataSource, Ad } from '@/services/marketingDataSource';
const { Title, Text, Paragraph } = Typography;
@@ -49,7 +49,7 @@ const AutoAdjustment: React.FC = () => {
type: 'ROI',
condition: 'ROI < 3.0',
action: '降低CPC 10%',
status: 'active',
status: 'active' as const,
createdAt: '2026-03-10',
lastExecuted: '2026-03-18 10:30',
executionCount: 15,
@@ -62,7 +62,7 @@ const AutoAdjustment: React.FC = () => {
type: 'CTR',
condition: 'CTR < 2.0%',
action: '调整创意',
status: 'paused',
status: 'paused' as const,
createdAt: '2026-03-08',
lastExecuted: '2026-03-15 14:20',
executionCount: 8,
@@ -75,7 +75,7 @@ const AutoAdjustment: React.FC = () => {
type: 'Budget',
condition: '日花费 < 预算的80%',
action: '增加预算10%',
status: 'active',
status: 'active' as const,
createdAt: '2026-03-05',
lastExecuted: '2026-03-18 09:15',
executionCount: 20,
@@ -152,7 +152,7 @@ const AutoAdjustment: React.FC = () => {
const newStrategy: Strategy = {
id: (strategies.length + 1).toString(),
...values,
status: 'inactive' as const,
status: (values.status || 'inactive') as 'active' | 'inactive' | 'paused',
createdAt: new Date().toISOString().split('T')[0],
lastExecuted: '-',
executionCount: 0,

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { Card, Typography, Row, Col, Select, Button, Table, Statistic, Spin, message, Alert, Badge, Dropdown, Menu } from 'antd';
import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts';
import { ReloadOutlined, ExportOutlined, FilterOutlined, BellOutlined, EyeOutlined } from '@ant-design/icons';
import { Link } from 'umi';
import { Link } from 'react-router-dom';
import { marketingDataSource, Ad } from '@/services/marketingDataSource';
const { Title, Text, Paragraph } = Typography;

View File

@@ -30,30 +30,14 @@ import {
AuditOutlined,
} from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import { afterSalesDataSource, RefundRecord } from '@/services/afterSalesDataSource';
import { afterSalesDataSource } from '@/services/afterSalesDataSource';
import type { RefundRecord as RefundRecordType } from '@/services/afterSalesDataSource';
const { TextArea } = Input;
const { Option } = Select;
const { TabPane } = Tabs;
interface RefundRecord {
id: string;
orderId: string;
refundId: string;
customerId: string;
customerName: string;
amount: number;
currency: string;
reason: string;
status: 'PENDING' | 'APPROVED' | 'REJECTED' | 'PROCESSING' | 'COMPLETED' | 'FAILED';
createdAt: string;
updatedAt: string;
platform: string;
refundMethod: string;
transactionId?: string;
processedBy?: string;
remark?: string;
}
type RefundRecord = RefundRecordType;
const REFUND_STATUS_MAP: Record<string, { color: string; text: string; icon: React.ReactNode }> = {
PENDING: { color: 'warning', text: 'Pending', icon: <ClockCircleOutlined /> },

View File

@@ -42,8 +42,8 @@ import {
LineChartOutlined,
PieChartOutlined,
DollarOutlined,
TrendingUpOutlined,
TrendingDownOutlined,
RiseOutlined,
FallOutlined,
DownloadOutlined,
SearchOutlined,
FilterOutlined,
@@ -55,8 +55,6 @@ import {
ShopOutlined,
ProductOutlined,
OrderedListOutlined,
RiseOutlined,
FallOutlined,
EyeOutlined,
EditOutlined,
DeleteOutlined,
@@ -361,7 +359,7 @@ export const Analytics: React.FC = () => {
<div>
<div style={{ fontWeight: 500 }}>{text}</div>
<div style={{ fontSize: 12, color: '#999' }}>{record.productId}</div>
<Tag size="small">{record.category}</Tag>
<Tag style={{ fontSize: 11 }}>{record.category}</Tag>
</div>
</Space>
),
@@ -723,7 +721,7 @@ export const Analytics: React.FC = () => {
<Descriptions bordered column={1}>
{Object.entries(selectedItem as object).map(([key, value]) => (
<Descriptions.Item key={key} label={key}>
{typeof value === 'object' ? JSON.stringify(value) : String(value)}
{typeof value === 'object' ? JSON.stringify(value) : String(value ?? '')}
</Descriptions.Item>
))}
</Descriptions>

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { useNavigate } from 'umi';
import { useNavigate } from 'react-router-dom';
import {
Card,
Form,

View File

@@ -1 +0,0 @@
export { default } from './index.tsx';

View File

@@ -62,6 +62,7 @@ import {
AlertOutlined,
InfoCircleOutlined,
PlusOutlined,
RiseOutlined,
} from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import moment from 'moment';
@@ -193,15 +194,19 @@ const AutoProductSelection: React.FC = () => {
const values = await ruleForm.validateFields();
await productSelectionDataSource.createSelectionRule({
name: values.name,
description: values.description || '',
category: values.category,
min_roi: values.minROI,
max_roi: values.maxROI,
min_profit: values.minProfit,
max_price: values.maxPrice,
min_sales_volume: values.minSalesVolume,
max_competition_level: values.maxCompetitionLevel,
min_rating: values.minRating,
trend_filter: values.trendFilter,
criteria: {
min_roi: values.minROI,
max_roi: values.maxROI,
min_profit: values.minProfit,
max_price: values.maxPrice,
min_sales_volume: values.minSalesVolume,
max_competition_level: values.maxCompetitionLevel,
min_rating: values.minRating,
trend_filter: values.trendFilter,
},
enabled: true,
});
setRuleModalVisible(false);
ruleForm.resetFields();
@@ -335,7 +340,7 @@ const AutoProductSelection: React.FC = () => {
<div style={{ fontWeight: 500 }}>{text}</div>
<Text type="secondary" style={{ fontSize: 12 }}>SKU: {record.product_id}</Text>
<div>
{record.tags.map(tag => (
{(record.tags || []).map(tag => (
<Tag key={tag} color="blue" style={{ fontSize: 10 }}>{tag}</Tag>
))}
</div>
@@ -355,8 +360,8 @@ const AutoProductSelection: React.FC = () => {
width: 150,
render: (_, record) => (
<div>
<div>¥{record.price.toFixed(2)}</div>
<Text type="secondary" style={{ fontSize: 12 }}>: ¥{record.cost_price.toFixed(2)}</Text>
<div>¥{(record.price ?? 0).toFixed(2)}</div>
<Text type="secondary" style={{ fontSize: 12 }}>: ¥{(record.cost_price ?? 0).toFixed(2)}</Text>
</div>
),
},
@@ -366,7 +371,7 @@ const AutoProductSelection: React.FC = () => {
width: 120,
render: (_, record) => (
<div>
<div style={{ color: '#52c41a', fontWeight: 500 }}>¥{record.profit.toFixed(2)}</div>
<div style={{ color: '#52c41a', fontWeight: 500 }}>¥{(record.profit ?? 0).toFixed(2)}</div>
<Tag color="green" style={{ fontSize: 11 }}>{record.roi.toFixed(0)}% ROI</Tag>
</div>
),
@@ -377,7 +382,7 @@ const AutoProductSelection: React.FC = () => {
width: 120,
render: (_, record) => (
<div>
<div>{record.sales_volume.toLocaleString()} </div>
<div>{(record.sales_volume ?? 0).toLocaleString()} </div>
<Space>
<StarOutlined style={{ color: '#faad14', fontSize: 12 }} />
<Text style={{ fontSize: 12 }}>{record.rating} ({record.review_count})</Text>
@@ -455,7 +460,7 @@ const AutoProductSelection: React.FC = () => {
title: 'ROI范围',
key: 'roi',
width: 120,
render: (_, record) => `${record.min_roi}% - ${record.max_roi}%`,
render: (_, record) => `${record.criteria?.min_roi ?? 0}% - ${record.criteria?.max_roi ?? 0}%`,
},
{
title: '利润要求',
@@ -669,7 +674,7 @@ const AutoProductSelection: React.FC = () => {
title="平均ROI"
value={selectionStats?.avgROI.toFixed(1) || 0}
suffix="%"
prefix={<TrendingUpOutlined />}
prefix={<RiseOutlined />}
valueStyle={{ color: '#52c41a' }}
/>
</Col>
@@ -1153,7 +1158,7 @@ const AutoProductSelection: React.FC = () => {
<div>
<Text strong>: </Text>
<Space wrap>
{selectedProduct.tags.map(tag => <Tag key={tag} color="blue">{tag}</Tag>)}
{(selectedProduct.tags || []).map(tag => <Tag key={tag} color="blue">{tag}</Tag>)}
</Space>
</div>
</Space>

View File

@@ -98,11 +98,16 @@ export const BatchOrder: React.FC = () => {
const mockOrders: B2BBatchOrder[] = [
{
id: 'BO-2026-001',
batchId: 'BO-2026-001',
customerId: 'C001',
customerName: 'TechCorp Industries',
totalItems: 2,
validItems: 2,
invalidItems: 0,
currency: 'USD',
items: [
{ key: '1', productId: 'P001', productName: 'Industrial Sensor A', quantity: 50, unitPrice: 142.50, totalPrice: 7125 },
{ key: '2', productId: 'P002', productName: 'Control Module B', quantity: 20, unitPrice: 266.00, totalPrice: 5320 },
{ key: '1', lineNumber: 1, productId: 'P001', sku: 'SKU-P001', productName: 'Industrial Sensor A', quantity: 50, unitPrice: 142.50, totalPrice: 7125, customerId: 'C001', customerName: 'TechCorp Industries', status: 'VALID' },
{ key: '2', lineNumber: 2, productId: 'P002', sku: 'SKU-P002', productName: 'Control Module B', quantity: 20, unitPrice: 266.00, totalPrice: 5320, customerId: 'C001', customerName: 'TechCorp Industries', status: 'VALID' },
],
totalAmount: 12445,
status: 'PROCESSING',
@@ -111,10 +116,15 @@ export const BatchOrder: React.FC = () => {
},
{
id: 'BO-2026-002',
batchId: 'BO-2026-002',
customerId: 'C002',
customerName: 'Global Manufacturing Co',
totalItems: 1,
validItems: 1,
invalidItems: 0,
currency: 'USD',
items: [
{ key: '1', productId: 'P003', productName: 'Power Supply Unit C', quantity: 100, unitPrice: 85.50, totalPrice: 8550 },
{ key: '1', lineNumber: 1, productId: 'P003', sku: 'SKU-P003', productName: 'Power Supply Unit C', quantity: 100, unitPrice: 85.50, totalPrice: 8550, customerId: 'C002', customerName: 'Global Manufacturing Co', status: 'VALID' },
],
totalAmount: 8550,
status: 'CONFIRMED',
@@ -123,11 +133,16 @@ export const BatchOrder: React.FC = () => {
},
{
id: 'BO-2026-003',
batchId: 'BO-2026-003',
customerId: 'C003',
customerName: 'Smart Factory Solutions',
totalItems: 2,
validItems: 2,
invalidItems: 0,
currency: 'USD',
items: [
{ key: '1', productId: 'P004', productName: 'Communication Gateway D', quantity: 30, unitPrice: 399.00, totalPrice: 11970 },
{ key: '2', productId: 'P005', productName: 'Display Panel E', quantity: 50, unitPrice: 175.75, totalPrice: 8787.50 },
{ key: '1', lineNumber: 1, productId: 'P004', sku: 'SKU-P004', productName: 'Communication Gateway D', quantity: 30, unitPrice: 399.00, totalPrice: 11970, customerId: 'C003', customerName: 'Smart Factory Solutions', status: 'VALID' },
{ key: '2', lineNumber: 2, productId: 'P005', sku: 'SKU-P005', productName: 'Display Panel E', quantity: 50, unitPrice: 175.75, totalPrice: 8787.50, customerId: 'C003', customerName: 'Smart Factory Solutions', status: 'VALID' },
],
totalAmount: 20757.50,
status: 'COMPLETED',
@@ -192,12 +207,16 @@ export const BatchOrder: React.FC = () => {
try {
const newOrder: B2BBatchOrder = {
id: `BO-${Date.now()}`,
batchId: `BATCH-${Date.now()}`,
customerId: selectedCustomer.id,
customerName: selectedCustomer.name,
items: orderItems,
totalItems: orderItems.length,
totalAmount: orderItems.reduce((sum, item) => sum + item.totalPrice, 0),
currency: 'USD',
status: 'DRAFT',
createdAt: new Date().toISOString(),
validItems: orderItems.length,
invalidItems: 0,
};
setOrders([newOrder, ...orders]);

View File

@@ -7,6 +7,7 @@ import {
Modal,
Form,
Input,
InputNumber,
Select,
DatePicker,
Descriptions,
@@ -96,7 +97,7 @@ export const ContractManage: React.FC = () => {
const mockContracts: Contract[] = [
{
id: '1',
contractNo: 'CTR-2026-001',
contractId: 'CTR-2026-001',
customerId: 'C001',
customerName: 'TechCorp Industries',
title: 'Annual Supply Agreement 2026',
@@ -105,6 +106,7 @@ export const ContractManage: React.FC = () => {
startDate: '2026-01-01',
endDate: '2026-12-31',
totalAmount: 500000,
currency: 'USD',
signedDate: '2025-12-15',
signedBy: 'John Smith',
attachments: ['contract_ctr_2026_001.pdf'],
@@ -114,7 +116,7 @@ export const ContractManage: React.FC = () => {
},
{
id: '2',
contractNo: 'CTR-2026-002',
contractId: 'CTR-2026-002',
customerId: 'C002',
customerName: 'Global Manufacturing Co',
title: 'OEM Manufacturing Agreement',
@@ -123,6 +125,7 @@ export const ContractManage: React.FC = () => {
startDate: '2026-03-01',
endDate: '2027-02-28',
totalAmount: 800000,
currency: 'USD',
attachments: ['oem_agreement_draft.pdf', 'specification_v2.pdf'],
terms: 'Exclusive manufacturing rights for North America region. Quality standards: ISO 9001:2015. Lead time: 45 days.',
createdAt: '2026-03-10 09:00:00',
@@ -130,7 +133,7 @@ export const ContractManage: React.FC = () => {
},
{
id: '3',
contractNo: 'CTR-2025-015',
contractId: 'CTR-2025-015',
customerId: 'C003',
customerName: 'Smart Factory Solutions',
title: 'Distribution Partnership Agreement',
@@ -139,6 +142,7 @@ export const ContractManage: React.FC = () => {
startDate: '2025-01-01',
endDate: '2025-12-31',
totalAmount: 300000,
currency: 'USD',
signedDate: '2024-12-20',
signedBy: 'Mike Johnson',
attachments: ['distribution_2025.pdf'],
@@ -149,7 +153,7 @@ export const ContractManage: React.FC = () => {
},
{
id: '4',
contractNo: 'CTR-2026-003',
contractId: 'CTR-2026-003',
customerId: 'C001',
customerName: 'TechCorp Industries',
title: 'Service Level Agreement',
@@ -158,6 +162,7 @@ export const ContractManage: React.FC = () => {
startDate: '2026-04-01',
endDate: '2027-03-31',
totalAmount: 120000,
currency: 'USD',
attachments: [],
terms: '24/7 technical support. Response time: 4 hours for critical issues. On-site support included.',
createdAt: '2026-03-18 10:30:00',
@@ -176,7 +181,7 @@ export const ContractManage: React.FC = () => {
const customer = MOCK_CUSTOMERS.find(c => c.id === values.customerId);
const newContract: Contract = {
id: `${Date.now()}`,
contractNo: `CTR-${new Date().getFullYear()}-${String(contracts.length + 1).padStart(3, '0')}`,
contractId: `CTR-${new Date().getFullYear()}-${String(contracts.length + 1).padStart(3, '0')}`,
customerId: values.customerId,
customerName: customer?.name || '',
title: values.title,
@@ -185,6 +190,7 @@ export const ContractManage: React.FC = () => {
startDate: values.dateRange[0].format('YYYY-MM-DD'),
endDate: values.dateRange[1].format('YYYY-MM-DD'),
totalAmount: values.totalAmount,
currency: 'USD',
attachments: [],
terms: values.terms,
createdAt: new Date().toISOString(),
@@ -252,8 +258,8 @@ export const ContractManage: React.FC = () => {
const columns: ColumnsType<Contract> = [
{
title: 'Contract No',
dataIndex: 'contractNo',
key: 'contractNo',
dataIndex: 'contractId',
key: 'contractId',
width: 140,
render: (no: string, record: Contract) => (
<a onClick={() => handleViewDetail(record)}>{no}</a>
@@ -468,7 +474,7 @@ export const ContractManage: React.FC = () => {
{selectedContract && (
<>
<Descriptions bordered column={2}>
<Descriptions.Item label="Contract No">{selectedContract.contractNo}</Descriptions.Item>
<Descriptions.Item label="Contract No">{selectedContract.contractId}</Descriptions.Item>
<Descriptions.Item label="Status">
<Tag color={STATUS_CONFIG[selectedContract.status].color}>
{STATUS_CONFIG[selectedContract.status].text}
@@ -505,11 +511,11 @@ export const ContractManage: React.FC = () => {
]}
/>
{selectedContract.attachments.length > 0 && (
{selectedContract.attachments && selectedContract.attachments.length > 0 && (
<>
<Divider>Attachments</Divider>
<Space>
{selectedContract.attachments.map((file, index) => (
{selectedContract.attachments.map((file: string, index: number) => (
<Button key={index} icon={<DownloadOutlined />} size="small">
{file}
</Button>

View File

@@ -127,7 +127,7 @@ export const B2BTradeBatchOrder: React.FC = () => {
const parseUploadedFile = async (file: any) => {
try {
const items = await b2bTradeDataSource.parseOrderFile(file, selectedCustomer);
const items = await b2bTradeDataSource.parseUploadFile(file, selectedCustomer?.id || '');
setOrderItems(items);
setCurrentStep(1);
message.success('File parsed successfully');
@@ -143,9 +143,11 @@ export const B2BTradeBatchOrder: React.FC = () => {
const handleValidateItems = async () => {
setLoading(true);
try {
const result = await b2bTradeDataSource.validateOrderItems(orderItems);
setOrderItems(result.items);
message.success(`Validation complete: ${result.validCount} valid, ${result.invalidCount} invalid`);
const result = await b2bTradeDataSource.validateItems(orderItems);
setOrderItems(result);
const validCount = result.filter(i => i.status === 'VALID').length;
const invalidCount = result.filter(i => i.status === 'INVALID').length;
message.success(`Validation complete: ${validCount} valid, ${invalidCount} invalid`);
} catch (error) {
message.error('Validation failed');
} finally {
@@ -163,10 +165,9 @@ export const B2BTradeBatchOrder: React.FC = () => {
}
setLoading(true);
await b2bTradeDataSource.submitBatchOrder({
await b2bTradeDataSource.createBatchOrder({
customerId: values.customerId,
items: validItems,
note: values.note,
});
message.success('Batch order submitted successfully');

View File

@@ -3,6 +3,7 @@ import {
Card,
Form,
Input,
InputNumber,
Select,
Button,
Table,
@@ -132,6 +133,7 @@ export const ContractManage: React.FC = () => {
status: 'ACTIVE',
startDate: '2026-01-01',
endDate: '2026-12-31',
totalAmount: 500000.00,
totalValue: 500000.00,
currency: 'USD',
signedDate: '2025-12-15',
@@ -151,6 +153,7 @@ export const ContractManage: React.FC = () => {
status: 'ACTIVE',
startDate: '2026-02-01',
endDate: '2028-01-31',
totalAmount: 0,
totalValue: 0,
currency: 'USD',
signedDate: '2026-01-20',
@@ -167,9 +170,10 @@ export const ContractManage: React.FC = () => {
customerName: 'Global Import Inc.',
title: 'Purchase Agreement Q2',
type: 'PURCHASE',
status: 'PENDING_SIGN',
status: 'PENDING_REVIEW',
startDate: '2026-04-01',
endDate: '2026-06-30',
totalAmount: 150000.00,
totalValue: 150000.00,
currency: 'USD',
attachments: ['purchase_agreement.pdf'],
@@ -187,6 +191,7 @@ export const ContractManage: React.FC = () => {
status: 'EXPIRED',
startDate: '2025-01-01',
endDate: '2025-12-31',
totalAmount: 350000.00,
totalValue: 350000.00,
currency: 'USD',
signedDate: '2025-01-05',
@@ -661,7 +666,7 @@ export const ContractManage: React.FC = () => {
</Tag>
</Descriptions.Item>
<Descriptions.Item label="Value">
{selectedContract.totalValue > 0 ? `$${selectedContract.totalValue.toLocaleString()}` : '-'}
{selectedContract.totalValue != null ? `$${selectedContract.totalValue.toLocaleString()}` : '-'}
</Descriptions.Item>
<Descriptions.Item label="Start Date">{selectedContract.startDate}</Descriptions.Item>
<Descriptions.Item label="End Date">{selectedContract.endDate}</Descriptions.Item>
@@ -675,11 +680,11 @@ export const ContractManage: React.FC = () => {
<Descriptions.Item label="Updated">{selectedContract.updatedAt}</Descriptions.Item>
</Descriptions>
{selectedContract.attachments.length > 0 && (
{selectedContract.attachments && selectedContract.attachments.length > 0 && (
<>
<Divider>Attachments</Divider>
<Space>
{selectedContract.attachments.map((file, index) => (
{selectedContract.attachments.map((file: string, index: number) => (
<Button
key={index}
icon={<DownloadOutlined />}

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Link, useNavigate } from 'umi';
import { Link, useNavigate } from 'react-router-dom';
import { Card, Button, Typography, Row, Col, Tag, Carousel, Pagination } from 'antd';
import { ArrowRightOutlined, CheckCircleOutlined, DollarOutlined, BarChartOutlined, ZapOutlined, GlobeOutlined, StarOutlined, UserOutlined, CompanyOutlined, TrendingUpOutlined } from '@ant-design/icons';
import Navbar from '@/components/Navbar';

View File

@@ -0,0 +1,86 @@
.homepage-button-primary {
padding: 16px 40px;
font-size: 18px;
border-radius: 30px;
box-shadow: 0 8px 24px rgba(24, 144, 255, 0.4);
transition: all 0.3s ease;
}
.homepage-button-primary:hover {
transform: translateY(-3px);
box-shadow: 0 12px 32px rgba(24, 144, 255, 0.5);
}
.homepage-button-secondary {
padding: 16px 40px;
font-size: 18px;
border-radius: 30px;
background: rgba(255, 255, 255, 0.1);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
}
.homepage-button-secondary:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-3px);
color: #fff;
}
.homepage-card {
transition: all 0.3s ease;
}
.homepage-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}
.homepage-feature-card {
transition: all 0.3s ease;
border: 1px solid #f0f0f0;
}
.homepage-feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
border-color: #1890ff;
}
.homepage-pricing-card {
transition: all 0.3s ease;
border: 1px solid #f0f0f0;
}
.homepage-pricing-card:hover {
transform: scale(1.02);
box-shadow: 0 16px 32px rgba(0, 0, 0, 0.1);
}
.homepage-pricing-card-popular {
border: 2px solid #1890ff;
position: relative;
}
.homepage-pricing-card-popular:hover {
border-color: #40a9ff;
}
.homepage-footer-link {
color: rgba(255, 255, 255, 0.8);
transition: color 0.3s ease;
}
.homepage-footer-link:hover {
color: #fff;
}
.homepage-social-icon {
color: rgba(255, 255, 255, 0.8);
transition: all 0.3s ease;
}
.homepage-social-icon:hover {
color: #fff;
transform: translateY(-2px);
}

View File

@@ -22,27 +22,27 @@ const Homepage: React.FC = () => {
const features = [
{
title: '智能选品系统',
description: '基于AI的商品推荐自动识别高潜力商品提升选品效率',
description: '基于AI的商品推荐自动识别高潜力商品提升选品效率'
},
{
title: 'AI动态定价',
description: '实时市场分析,优化定价策略,最大化利润',
description: '实时市场分析,优化定价策略,最大化利润'
},
{
title: '自动上架系统',
description: '一键批量上架,节省人工成本,提高运营效率',
description: '一键批量上架,节省人工成本,提高运营效率'
},
{
title: '跨平台套利',
description: '发现价格差异,实现利润最大化,降低运营风险',
description: '发现价格差异,实现利润最大化,降低运营风险'
},
{
title: 'AI店铺托管',
description: '24/7智能运营解放人力提升店铺表现',
description: '24/7智能运营解放人力提升店铺表现'
},
{
title: '多商户管理',
description: '集中管理多店铺,数据一目了然,简化管理流程',
description: '集中管理多店铺,数据一目了然,简化管理流程'
},
];
@@ -130,12 +130,8 @@ const Homepage: React.FC = () => {
fontSize: '18px',
borderRadius: '30px',
boxShadow: '0 8px 24px rgba(24, 144, 255, 0.4)',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: '0 12px 32px rgba(24, 144, 255, 0.5)'
}
}}
transition: 'all 0.3s ease'
}}
>
</Button>
@@ -149,12 +145,8 @@ const Homepage: React.FC = () => {
background: 'rgba(255, 255, 255, 0.1)',
color: '#fff',
border: '1px solid rgba(255, 255, 255, 0.3)',
transition: 'all 0.3s ease',
'&:hover': {
background: 'rgba(255, 255, 255, 0.2)',
transform: 'translateY(-3px)'
}
}}
transition: 'all 0.3s ease'
}}
>
</Button>
@@ -187,12 +179,8 @@ const Homepage: React.FC = () => {
borderRadius: '16px',
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.4)',
transition: 'all 0.3s ease',
transformStyle: 'preserve-3d',
'&:hover': {
transform: 'scale(1.05) rotateY(5deg)',
boxShadow: '0 24px 72px rgba(0, 0, 0, 0.5)'
}
}}
transformStyle: 'preserve-3d'
}}
/>
</div>
</Col>
@@ -259,13 +247,8 @@ const Homepage: React.FC = () => {
transition: 'all 0.4s ease',
border: '1px solid #e8e8e8',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.08)',
height: '100%',
'&:hover': {
transform: 'translateY(-12px)',
boxShadow: '0 16px 48px rgba(0, 0, 0, 0.15)',
borderColor: '#1890ff'
}
}}
height: '100%'
}}
>
<div style={{ marginBottom: '32px', paddingTop: '32px' }}>
<span style={{
@@ -280,12 +263,8 @@ const Homepage: React.FC = () => {
color: '#fff',
borderRadius: '20px',
boxShadow: '0 6px 20px rgba(24, 144, 255, 0.4)',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'scale(1.1)',
boxShadow: '0 8px 24px rgba(24, 144, 255, 0.5)'
}
}}>{feature.title.charAt(0)}</span>
transition: 'all 0.3s ease'
}}>{feature.title.charAt(0)}</span>
</div>
<Title level={4} style={{
marginBottom: '20px',
@@ -308,12 +287,8 @@ const Homepage: React.FC = () => {
alignItems: 'center',
gap: '10px',
transition: 'all 0.3s ease',
fontSize: '16px',
'&:hover': {
color: '#40a9ff',
transform: 'translateX(6px)'
}
}}
fontSize: '16px'
}}
>
<ArrowRightOutlined />
</Button>
@@ -368,12 +343,8 @@ const Homepage: React.FC = () => {
borderRadius: '16px',
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.15)',
transition: 'all 0.4s ease',
transformStyle: 'preserve-3d',
'&:hover': {
transform: 'scale(1.05) rotateY(-5deg)',
boxShadow: '0 24px 72px rgba(0, 0, 0, 0.2)'
}
}}
transformStyle: 'preserve-3d'
}}
/>
</div>
</Col>
@@ -407,13 +378,8 @@ const Homepage: React.FC = () => {
padding: '24px',
background: '#fafafa',
borderRadius: '16px',
transition: 'all 0.4s ease',
'&:hover': {
background: 'rgba(24, 144, 255, 0.05)',
transform: 'translateX(12px)',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.08)'
}
}}>
transition: 'all 0.4s ease'
}}>
<div style={{
width: '56px',
height: '56px',
@@ -450,13 +416,8 @@ const Homepage: React.FC = () => {
padding: '24px',
background: '#fafafa',
borderRadius: '16px',
transition: 'all 0.4s ease',
'&:hover': {
background: 'rgba(24, 144, 255, 0.05)',
transform: 'translateX(12px)',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.08)'
}
}}>
transition: 'all 0.4s ease'
}}>
<div style={{
width: '56px',
height: '56px',
@@ -493,13 +454,8 @@ const Homepage: React.FC = () => {
padding: '24px',
background: '#fafafa',
borderRadius: '16px',
transition: 'all 0.4s ease',
'&:hover': {
background: 'rgba(24, 144, 255, 0.05)',
transform: 'translateX(12px)',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.08)'
}
}}>
transition: 'all 0.4s ease'
}}>
<div style={{
width: '56px',
height: '56px',
@@ -536,13 +492,8 @@ const Homepage: React.FC = () => {
padding: '24px',
background: '#fafafa',
borderRadius: '16px',
transition: 'all 0.4s ease',
'&:hover': {
background: 'rgba(24, 144, 255, 0.05)',
transform: 'translateX(12px)',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.08)'
}
}}>
transition: 'all 0.4s ease'
}}>
<div style={{
width: '56px',
height: '56px',
@@ -635,13 +586,8 @@ const Homepage: React.FC = () => {
transition: 'all 0.4s ease',
border: '1px solid #e8e8e8',
boxShadow: '0 12px 32px rgba(0, 0, 0, 0.1)',
height: '100%',
'&:hover': {
transform: 'translateY(-16px)',
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.18)',
borderColor: '#1890ff'
}
}}
height: '100%'
}}
>
<div style={{
marginBottom: '0',
@@ -656,11 +602,8 @@ const Homepage: React.FC = () => {
width: '100%',
height: '100%',
objectFit: 'cover',
transition: 'all 0.4s ease',
'&:hover': {
transform: 'scale(1.1)'
}
}}
transition: 'all 0.4s ease'
}}
/>
<div style={{
position: 'absolute',
@@ -708,12 +651,8 @@ const Homepage: React.FC = () => {
alignItems: 'center',
gap: '10px',
transition: 'all 0.3s ease',
fontSize: '16px',
'&:hover': {
color: '#40a9ff',
transform: 'translateX(6px)'
}
}}
fontSize: '16px'
}}
>
<ArrowRightOutlined />
</Button>
@@ -729,13 +668,8 @@ const Homepage: React.FC = () => {
transition: 'all 0.4s ease',
border: '1px solid #e8e8e8',
boxShadow: '0 12px 32px rgba(0, 0, 0, 0.1)',
height: '100%',
'&:hover': {
transform: 'translateY(-16px)',
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.18)',
borderColor: '#1890ff'
}
}}
height: '100%'
}}
>
<div style={{
marginBottom: '0',
@@ -750,11 +684,8 @@ const Homepage: React.FC = () => {
width: '100%',
height: '100%',
objectFit: 'cover',
transition: 'all 0.4s ease',
'&:hover': {
transform: 'scale(1.1)'
}
}}
transition: 'all 0.4s ease'
}}
/>
<div style={{
position: 'absolute',
@@ -802,12 +733,8 @@ const Homepage: React.FC = () => {
alignItems: 'center',
gap: '10px',
transition: 'all 0.3s ease',
fontSize: '16px',
'&:hover': {
color: '#40a9ff',
transform: 'translateX(6px)'
}
}}
fontSize: '16px'
}}
>
<ArrowRightOutlined />
</Button>
@@ -823,13 +750,8 @@ const Homepage: React.FC = () => {
transition: 'all 0.4s ease',
border: '1px solid #e8e8e8',
boxShadow: '0 12px 32px rgba(0, 0, 0, 0.1)',
height: '100%',
'&:hover': {
transform: 'translateY(-16px)',
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.18)',
borderColor: '#1890ff'
}
}}
height: '100%'
}}
>
<div style={{
marginBottom: '0',
@@ -844,11 +766,8 @@ const Homepage: React.FC = () => {
width: '100%',
height: '100%',
objectFit: 'cover',
transition: 'all 0.4s ease',
'&:hover': {
transform: 'scale(1.1)'
}
}}
transition: 'all 0.4s ease'
}}
/>
<div style={{
position: 'absolute',
@@ -896,12 +815,8 @@ const Homepage: React.FC = () => {
alignItems: 'center',
gap: '10px',
transition: 'all 0.3s ease',
fontSize: '16px',
'&:hover': {
color: '#40a9ff',
transform: 'translateX(6px)'
}
}}
fontSize: '16px'
}}
>
<ArrowRightOutlined />
</Button>
@@ -918,12 +833,8 @@ const Homepage: React.FC = () => {
fontSize: '18px',
borderRadius: '30px',
boxShadow: '0 6px 18px rgba(24, 144, 255, 0.4)',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: '0 8px 24px rgba(24, 144, 255, 0.5)'
}
}}
transition: 'all 0.3s ease'
}}
>
</Button>
@@ -1000,12 +911,8 @@ const Homepage: React.FC = () => {
fontSize: '20px',
borderRadius: '35px',
boxShadow: '0 8px 24px rgba(24, 144, 255, 0.4)',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: '0 12px 32px rgba(24, 144, 255, 0.5)'
}
}}
transition: 'all 0.3s ease'
}}
>
</Button>
@@ -1062,13 +969,8 @@ const Homepage: React.FC = () => {
borderRadius: '50%',
background: 'rgba(255, 255, 255, 0.08)',
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
'&:hover': {
background: '#1890ff',
color: '#fff',
transform: 'translateY(-2px)'
}
}}>
transition: 'all 0.3s ease'
}}>
<TwitterOutlined />
</a>
<a href="#" style={{
@@ -1080,13 +982,8 @@ const Homepage: React.FC = () => {
borderRadius: '50%',
background: 'rgba(255, 255, 255, 0.08)',
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
'&:hover': {
background: '#1890ff',
color: '#fff',
transform: 'translateY(-2px)'
}
}}>
transition: 'all 0.3s ease'
}}>
<LinkedinOutlined />
</a>
<a href="#" style={{
@@ -1098,13 +995,8 @@ const Homepage: React.FC = () => {
borderRadius: '50%',
background: 'rgba(255, 255, 255, 0.08)',
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
'&:hover': {
background: '#1890ff',
color: '#fff',
transform: 'translateY(-2px)'
}
}}>
transition: 'all 0.3s ease'
}}>
<GithubOutlined />
</a>
</div>
@@ -1122,42 +1014,26 @@ const Homepage: React.FC = () => {
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}></Link></li>
fontSize: '16px'
}}></Link></li>
<li style={{ marginBottom: '20px' }}><Link to="/case-study" style={{
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}></Link></li>
fontSize: '16px'
}}></Link></li>
<li style={{ marginBottom: '20px' }}><Link to="/" style={{
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}></Link></li>
fontSize: '16px'
}}></Link></li>
<li style={{ marginBottom: '20px' }}><Link to="/" style={{
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}>API文档</Link></li>
fontSize: '16px'
}}>API文档</Link></li>
</ul>
</Col>
<Col xs={24} md={6}>
@@ -1173,42 +1049,26 @@ const Homepage: React.FC = () => {
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}></Link></li>
fontSize: '16px'
}}></Link></li>
<li style={{ marginBottom: '20px' }}><Link to="/" style={{
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}></Link></li>
fontSize: '16px'
}}></Link></li>
<li style={{ marginBottom: '20px' }}><Link to="/" style={{
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}></Link></li>
fontSize: '16px'
}}></Link></li>
<li style={{ marginBottom: '20px' }}><Link to="/" style={{
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}></Link></li>
fontSize: '16px'
}}></Link></li>
</ul>
</Col>
<Col xs={24} md={6}>
@@ -1224,42 +1084,26 @@ const Homepage: React.FC = () => {
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}></Link></li>
fontSize: '16px'
}}></Link></li>
<li style={{ marginBottom: '20px' }}><Link to="/" style={{
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}></Link></li>
fontSize: '16px'
}}></Link></li>
<li style={{ marginBottom: '20px' }}><Link to="/" style={{
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}></Link></li>
fontSize: '16px'
}}></Link></li>
<li style={{ marginBottom: '20px' }}><Link to="/" style={{
color: 'rgba(255, 255, 255, 0.6)',
transition: 'all 0.3s ease',
textDecoration: 'none',
fontSize: '16px',
'&:hover': {
color: '#1890ff',
transform: 'translateX(4px)'
}
}}>API文档</Link></li>
fontSize: '16px'
}}>API文档</Link></li>
</ul>
</Col>
</Row>
@@ -1274,29 +1118,20 @@ const Homepage: React.FC = () => {
color: 'rgba(255, 255, 255, 0.4)',
transition: 'color 0.3s ease',
textDecoration: 'none',
margin: '0 16px',
'&:hover': {
color: '#1890ff'
}
}}></Link>
margin: '0 16px'
}}></Link>
<Link to="/" style={{
color: 'rgba(255, 255, 255, 0.4)',
transition: 'color 0.3s ease',
textDecoration: 'none',
margin: '0 16px',
'&:hover': {
color: '#1890ff'
}
}}></Link>
margin: '0 16px'
}}></Link>
<Link to="/" style={{
color: 'rgba(255, 255, 255, 0.4)',
transition: 'color 0.3s ease',
textDecoration: 'none',
margin: '0 16px',
'&:hover': {
color: '#1890ff'
}
}}>Cookie政策</Link>
margin: '0 16px'
}}>Cookie政策</Link>
</div>
© 2025 Crawlful Hub. All rights reserved.
</div>

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { Card, Typography, Row, Col, Button, Statistic, Space, Tabs } from 'antd';
import { FileTextOutlined, AlertOutlined, BarChartOutlined, SyncOutlined, PlusOutlined, SearchOutlined, FilterOutlined, DollarOutlined, TruckOutlined, ClockCircleOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
import { Link } from 'umi';
import { Link } from 'react-router-dom';
import { Area, Pie, Column } from '@ant-design/plots';
import moment from 'moment';

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { Card, Typography, Row, Col, Button, Table, Input, Select, Modal, Form, InputNumber, message, Tag, Space, Popconfirm, Tabs } from 'antd';
import { PlusOutlined, UploadOutlined, EditOutlined, SearchOutlined, FilterOutlined, SyncOutlined, DeleteOutlined, EyeOutlined, GlobalOutlined } from '@ant-design/icons';
import { Link } from 'umi';
import { Link } from 'react-router-dom';
import CrossPlatformManage from './CrossPlatformManage';
const { Title, Text } = Typography;

View File

@@ -35,6 +35,62 @@ export interface ReturnApplication {
updatedAt: string;
}
export interface CustomerServiceTicket {
id: string;
ticketId: string;
orderId: string;
customerId: string;
customerName: string;
customerEmail: string;
subject: string;
category: string;
priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT';
status: 'OPEN' | 'IN_PROGRESS' | 'WAITING_CUSTOMER' | 'RESOLVED' | 'CLOSED';
assignedTo?: string;
platform?: string;
lastMessage?: string;
messageCount?: number;
createdAt: string;
updatedAt: string;
messages?: CustomerServiceMessage[];
}
export interface CustomerServiceMessage {
id: string;
ticketId: string;
sender: 'customer' | 'agent';
content: string;
createdAt: string;
}
export interface RefundRecord {
id: string;
orderId: string;
refundId: string;
customerId: string;
customerName: string;
amount: number;
currency: string;
reason: string;
status: 'PENDING' | 'APPROVED' | 'REJECTED' | 'PROCESSING' | 'COMPLETED' | 'FAILED';
createdAt: string;
updatedAt: string;
platform: string;
refundMethod: string;
transactionId?: string;
processedBy?: string;
remark?: string;
}
export interface ReturnFormData {
orderId: string;
returnReason: string;
returnDescription: string;
returnItems: string[];
images: string[];
contactPhone: string;
}
export interface AfterSalesQueryParams {
orderId?: string;
status?: string;
@@ -49,9 +105,12 @@ export interface AfterSalesQueryParams {
export interface IAfterSalesDataSource {
fetchOrderItems(orderId: string): Promise<OrderItem[]>;
submitReturn(data: Partial<ReturnApplication>): Promise<ReturnApplication>;
submitReturnApply(data: ReturnFormData): Promise<ReturnApplication>;
fetchReturnApplications(params?: AfterSalesQueryParams): Promise<ReturnApplication[]>;
fetchReturnDetail(id: string): Promise<ReturnApplication | null>;
updateReturnStatus(id: string, status: string): Promise<ReturnApplication>;
fetchCustomerServiceTickets(params?: AfterSalesQueryParams): Promise<CustomerServiceTicket[]>;
fetchRefundRecords(params?: AfterSalesQueryParams): Promise<RefundRecord[]>;
}
// ============================================
@@ -75,8 +134,18 @@ class ApiAfterSalesDataSource implements IAfterSalesDataSource {
return result.data;
}
async submitReturnApply(data: ReturnFormData): Promise<ReturnApplication> {
const response = await fetch('/api/v1/returns/apply', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const result = await response.json();
return result.data;
}
async fetchReturnApplications(params?: AfterSalesQueryParams): Promise<ReturnApplication[]> {
const query = new URLSearchParams(params as any).toString();
const query = new URLSearchParams(params as Record<string, string>).toString();
const response = await fetch(`/api/v1/returns?${query}`);
const result = await response.json();
return result.data;
@@ -97,6 +166,20 @@ class ApiAfterSalesDataSource implements IAfterSalesDataSource {
const result = await response.json();
return result.data;
}
async fetchCustomerServiceTickets(params?: AfterSalesQueryParams): Promise<CustomerServiceTicket[]> {
const query = new URLSearchParams(params as Record<string, string>).toString();
const response = await fetch(`/api/v1/customer-service/tickets?${query}`);
const result = await response.json();
return result.data;
}
async fetchRefundRecords(params?: AfterSalesQueryParams): Promise<RefundRecord[]> {
const query = new URLSearchParams(params as Record<string, string>).toString();
const response = await fetch(`/api/v1/refunds?${query}`);
const result = await response.json();
return result.data;
}
}
// ============================================
@@ -190,6 +273,64 @@ class MockAfterSalesDataSource implements IAfterSalesDataSource {
return this.mockReturns[index];
}
async submitReturnApply(data: ReturnFormData): Promise<ReturnApplication> {
await this.simulateDelay(1000);
const newReturn: ReturnApplication = {
id: `RET-${Date.now()}`,
orderId: data.orderId,
returnReason: data.returnReason,
returnDescription: data.returnDescription,
returnItems: data.returnItems,
images: data.images,
contactPhone: data.contactPhone,
status: 'PENDING',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
this.mockReturns.push(newReturn);
return newReturn;
}
async fetchCustomerServiceTickets(params?: AfterSalesQueryParams): Promise<CustomerServiceTicket[]> {
await this.simulateDelay(300);
return [
{
id: 'TICKET-001',
orderId: 'order-001',
customerId: 'C001',
customerName: 'John Doe',
customerEmail: 'john@example.com',
subject: 'Product not received',
category: 'delivery',
priority: 'HIGH',
status: 'OPEN',
createdAt: '2026-03-18T10:00:00Z',
updatedAt: '2026-03-18T10:00:00Z',
},
];
}
async fetchRefundRecords(params?: AfterSalesQueryParams): Promise<RefundRecord[]> {
await this.simulateDelay(300);
return [
{
id: 'REFUND-001',
orderId: 'order-001',
refundId: 'RF-001',
customerId: 'C001',
customerName: 'John Doe',
amount: 59.98,
currency: 'USD',
reason: 'Defective product',
status: 'PENDING',
createdAt: '2026-03-18T10:00:00Z',
updatedAt: '2026-03-18T10:00:00Z',
platform: 'Amazon',
refundMethod: 'Original Payment',
},
];
}
private simulateDelay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@@ -36,10 +36,12 @@ export interface BatchOrder {
totalItems: number;
totalAmount: number;
currency: string;
status: 'DRAFT' | 'PENDING_REVIEW' | 'CONFIRMED' | 'PROCESSING' | 'COMPLETED' | 'CANCELLED';
status: 'DRAFT' | 'PENDING_REVIEW' | 'CONFIRMED' | 'PROCESSING' | 'COMPLETED' | 'CANCELLED' | 'SUBMITTED';
createdAt: string;
validItems: number;
invalidItems: number;
items?: BatchOrderItem[];
estimatedDelivery?: string;
}
export interface Customer {
@@ -47,6 +49,7 @@ export interface Customer {
name: string;
company: string;
tier: 'BASIC' | 'PRO' | 'ENTERPRISE';
email?: string;
}
export interface BatchOrderQueryParams {
@@ -64,6 +67,37 @@ export interface BatchOrderStats {
totalAmount: number;
}
export interface Contract {
id: string;
contractId: string;
contractNumber?: string;
customerId: string;
customerName: string;
type: 'PURCHASE' | 'SALES' | 'SERVICE' | 'DISTRIBUTION' | 'OEM' | 'NDA';
title: string;
startDate: string;
endDate: string;
totalAmount: number;
totalValue?: number;
currency: string;
status: 'DRAFT' | 'PENDING_REVIEW' | 'ACTIVE' | 'EXPIRED' | 'TERMINATED' | 'RENEWED';
signedDate?: string;
signedBy?: string;
attachments?: string[];
terms?: string;
remark?: string;
createdAt: string;
updatedAt: string;
}
export interface ContractQueryParams {
customerId?: string;
status?: string;
type?: string;
startDate?: string;
endDate?: string;
}
// ============================================
// B2B Trade专用接口
// ============================================
@@ -77,6 +111,10 @@ export interface IB2BTradeDataSource {
approveBatchOrder(batchId: string): Promise<BatchOrder>;
validateItems(items: BatchOrderItem[]): Promise<BatchOrderItem[]>;
parseUploadFile(file: File, customerId: string): Promise<BatchOrderItem[]>;
fetchContracts(params?: ContractQueryParams): Promise<Contract[]>;
fetchContractDetail(contractId: string): Promise<Contract | null>;
createContract(data: Partial<Contract>): Promise<Contract>;
updateContract(contractId: string, data: Partial<Contract>): Promise<Contract>;
}
// ============================================
@@ -148,6 +186,39 @@ class ApiB2BTradeDataSource implements IB2BTradeDataSource {
const result = await response.json();
return result.data;
}
async fetchContracts(params?: ContractQueryParams): Promise<Contract[]> {
const query = new URLSearchParams(params as Record<string, string>).toString();
const response = await fetch(`/api/v1/b2b/contracts?${query}`);
const result = await response.json();
return result.data;
}
async fetchContractDetail(contractId: string): Promise<Contract | null> {
const response = await fetch(`/api/v1/b2b/contracts/${contractId}`);
const result = await response.json();
return result.data;
}
async createContract(data: Partial<Contract>): Promise<Contract> {
const response = await fetch('/api/v1/b2b/contracts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const result = await response.json();
return result.data;
}
async updateContract(contractId: string, data: Partial<Contract>): Promise<Contract> {
const response = await fetch(`/api/v1/b2b/contracts/${contractId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const result = await response.json();
return result.data;
}
}
// ============================================
@@ -291,6 +362,60 @@ class MockB2BTradeDataSource implements IB2BTradeDataSource {
];
}
async fetchContracts(params?: ContractQueryParams): Promise<Contract[]> {
await this.simulateDelay(400);
return [
{
id: '1',
contractId: 'CON-2026-001',
customerId: 'CUST_001',
customerName: 'ABC Trading Co.',
type: 'PURCHASE',
title: 'Annual Purchase Agreement',
startDate: '2026-01-01',
endDate: '2026-12-31',
totalAmount: 500000,
currency: 'USD',
status: 'ACTIVE',
createdAt: '2026-01-01',
updatedAt: '2026-01-01',
},
];
}
async fetchContractDetail(contractId: string): Promise<Contract | null> {
await this.simulateDelay(300);
const contracts = await this.fetchContracts();
return contracts.find(c => c.contractId === contractId) || null;
}
async createContract(data: Partial<Contract>): Promise<Contract> {
await this.simulateDelay(500);
const newContract: Contract = {
id: `${Date.now()}`,
contractId: `CON-${Date.now()}`,
customerId: data.customerId || '',
customerName: data.customerName || '',
type: data.type || 'PURCHASE',
title: data.title || '',
startDate: data.startDate || '',
endDate: data.endDate || '',
totalAmount: data.totalAmount || 0,
currency: data.currency || 'USD',
status: 'DRAFT',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
return newContract;
}
async updateContract(contractId: string, data: Partial<Contract>): Promise<Contract> {
await this.simulateDelay(300);
const contract = await this.fetchContractDetail(contractId);
if (!contract) throw new Error('Contract not found');
return { ...contract, ...data, updatedAt: new Date().toISOString() };
}
private simulateDelay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@@ -41,8 +41,13 @@ export interface RiskAssessment {
orderId: string;
customerId: string;
customerName: string;
platform?: string;
platform_buyer_id?: string;
riskScore: number;
risk_score?: number;
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
risk_level?: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
assessment_date?: string;
riskFactors: string[];
status: 'PENDING' | 'REVIEWING' | 'APPROVED' | 'REJECTED';
reviewedBy?: string;