feat: 实现Operation-Agent核心功能及电商平台适配器

refactor: 重构项目结构,分离server和dashboard代码
style: 统一代码风格,修复lint警告
test: 添加平台适配器工厂测试用例
ci: 更新CI/CD流程,增加语义验证和性能测试
docs: 添加语义中心文档,定义统一数据模型和状态机
This commit is contained in:
2026-03-19 15:23:56 +08:00
parent aa2cf560c6
commit 8de9ea0aaa
41 changed files with 5615 additions and 497 deletions

View File

@@ -0,0 +1,445 @@
import React, { useState, useEffect } from 'react';
import { Button, Table, Form, Input, Select, Modal, message, Card, Tabs, Space, Tag, Divider, Alert } from 'antd';
import { PlusOutlined, SyncOutlined, EditOutlined, DeleteOutlined, CheckOutlined, CloseOutlined, LoadingOutlined } from '@ant-design/icons';
import { operationAgentService, Store, StoreBindingData } from '../services/operationAgentService';
const { Option } = Select;
const { TabPane } = Tabs;
const { TextArea } = Input;
const platforms = [
{ value: 'amazon', label: 'Amazon' },
{ value: 'shopee', label: 'Shopee' },
{ value: 'aliexpress', label: 'AliExpress' },
{ value: 'tiktok', label: 'TikTok' },
{ value: 'ebay', label: 'Ebay' },
{ value: 'lazada', label: 'Lazada' },
{ value: 'wish', label: 'Wish' },
{ value: 'shein', label: 'Shein' },
{ value: 'jd_worldwide', label: 'JD Worldwide' },
{ value: 'walmart', label: 'Walmart' },
{ value: 'etsy', label: 'Etsy' },
{ value: 'target', label: 'Target' },
{ value: 'newegg', label: 'Newegg' },
{ value: 'cdiscount', label: 'Cdiscount' },
{ value: 'allegro', label: 'Allegro' },
{ value: 'otto', label: 'Otto' },
{ value: 'rakuten', label: 'Rakuten' },
{ value: 'qoo10', label: 'Qoo10' },
];
const statusMap = {
active: { color: 'green', text: '活跃' },
inactive: { color: 'red', text: '停用' },
pending: { color: 'orange', text: '待处理' },
suspended: { color: 'gray', text: '暂停' },
};
const OperationAgent: React.FC = () => {
const [stores, setStores] = useState<Store[]>([]);
const [loading, setLoading] = useState(false);
const [bindingModalVisible, setBindingModalVisible] = useState(false);
const [currentStore, setCurrentStore] = useState<Store | null>(null);
const [merchantId, setMerchantId] = useState('merchant-1'); // 默认商户ID
const [form] = Form.useForm();
// 加载店铺列表
const loadStores = async () => {
setLoading(true);
try {
const data = await operationAgentService.getStores(merchantId);
setStores(data);
} catch (error) {
message.error('加载店铺失败');
console.error(error);
} finally {
setLoading(false);
}
};
// 初始加载
useEffect(() => {
loadStores();
}, [merchantId]);
// 绑定店铺
const handleBindStore = async (values: any) => {
setLoading(true);
try {
const bindingData: StoreBindingData = {
merchantId: values.merchantId,
platform: values.platform,
platformShopId: values.platformShopId,
name: values.name,
description: values.description,
authInfo: {
...(values.platform === 'amazon' && {
accessKey: values.accessKey,
secretKey: values.secretKey,
sellerId: values.sellerId,
marketplaceId: values.marketplaceId,
}),
...(values.platform === 'shopee' && {
partnerId: values.partnerId,
partnerKey: values.partnerKey,
shopId: values.shopId,
}),
...(values.platform === 'aliexpress' && {
appKey: values.appKey,
appSecret: values.appSecret,
accessToken: values.accessToken,
}),
...(values.platform === 'tiktok' && {
appId: values.appId,
appSecret: values.appSecret,
accessToken: values.accessToken,
}),
...(values.platform === 'ebay' && {
clientId: values.clientId,
clientSecret: values.clientSecret,
refreshToken: values.refreshToken,
}),
...(values.platform && !['amazon', 'shopee', 'aliexpress', 'tiktok', 'ebay'].includes(values.platform) && {
apiKey: values.apiKey,
apiSecret: values.apiSecret,
}),
},
};
await operationAgentService.bindStore(bindingData);
message.success('店铺绑定成功');
setBindingModalVisible(false);
form.resetFields();
loadStores();
} catch (error) {
message.error('店铺绑定失败');
console.error(error);
} finally {
setLoading(false);
}
};
// 同步商品
const handleSyncProducts = async (storeId: string) => {
setLoading(true);
try {
const result = await operationAgentService.syncProducts(storeId);
if (result.success) {
message.success(`商品同步成功,共 ${result.count} 个商品`);
} else {
message.error('商品同步失败');
}
} catch (error) {
message.error('商品同步失败');
console.error(error);
} finally {
setLoading(false);
}
};
// 同步订单
const handleSyncOrders = async (storeId: string) => {
setLoading(true);
try {
const result = await operationAgentService.syncOrders(storeId);
if (result.success) {
message.success(`订单同步成功,共 ${result.count} 个订单`);
} else {
message.error('订单同步失败');
}
} catch (error) {
message.error('订单同步失败');
console.error(error);
} finally {
setLoading(false);
}
};
// 停用店铺
const handleDeactivateStore = async (storeId: string) => {
setLoading(true);
try {
await operationAgentService.deactivateStore(storeId);
message.success('店铺已停用');
loadStores();
} catch (error) {
message.error('停用店铺失败');
console.error(error);
} finally {
setLoading(false);
}
};
// 重新激活店铺
const handleReactivateStore = async (storeId: string) => {
setLoading(true);
try {
await operationAgentService.reactivateStore(storeId);
message.success('店铺已重新激活');
loadStores();
} catch (error) {
message.error('激活店铺失败');
console.error(error);
} finally {
setLoading(false);
}
};
// 查看店铺详情
const handleViewStore = async (storeId: string) => {
setLoading(true);
try {
const store = await operationAgentService.getStore(storeId);
setCurrentStore(store);
} catch (error) {
message.error('获取店铺详情失败');
console.error(error);
} finally {
setLoading(false);
}
};
// 渲染平台特定的授权信息表单
const renderAuthInfoForm = (platform: string) => {
switch (platform) {
case 'amazon':
return (
<>
<Form.Item name="accessKey" label="Access Key" rules={[{ required: true, message: '请输入Access Key' }]}>
<Input placeholder="请输入Access Key" />
</Form.Item>
<Form.Item name="secretKey" label="Secret Key" rules={[{ required: true, message: '请输入Secret Key' }]}>
<Input.Password placeholder="请输入Secret Key" />
</Form.Item>
<Form.Item name="sellerId" label="Seller ID" rules={[{ required: true, message: '请输入Seller ID' }]}>
<Input placeholder="请输入Seller ID" />
</Form.Item>
<Form.Item name="marketplaceId" label="Marketplace ID" rules={[{ required: true, message: '请输入Marketplace ID' }]}>
<Input placeholder="请输入Marketplace ID" />
</Form.Item>
</>
);
case 'shopee':
return (
<>
<Form.Item name="partnerId" label="Partner ID" rules={[{ required: true, message: '请输入Partner ID' }]}>
<Input placeholder="请输入Partner ID" />
</Form.Item>
<Form.Item name="partnerKey" label="Partner Key" rules={[{ required: true, message: '请输入Partner Key' }]}>
<Input.Password placeholder="请输入Partner Key" />
</Form.Item>
<Form.Item name="shopId" label="Shop ID" rules={[{ required: true, message: '请输入Shop ID' }]}>
<Input placeholder="请输入Shop ID" />
</Form.Item>
</>
);
case 'aliexpress':
return (
<>
<Form.Item name="appKey" label="App Key" rules={[{ required: true, message: '请输入App Key' }]}>
<Input placeholder="请输入App Key" />
</Form.Item>
<Form.Item name="appSecret" label="App Secret" rules={[{ required: true, message: '请输入App Secret' }]}>
<Input.Password placeholder="请输入App Secret" />
</Form.Item>
<Form.Item name="accessToken" label="Access Token" rules={[{ required: true, message: '请输入Access Token' }]}>
<Input placeholder="请输入Access Token" />
</Form.Item>
</>
);
case 'tiktok':
return (
<>
<Form.Item name="appId" label="App ID" rules={[{ required: true, message: '请输入App ID' }]}>
<Input placeholder="请输入App ID" />
</Form.Item>
<Form.Item name="appSecret" label="App Secret" rules={[{ required: true, message: '请输入App Secret' }]}>
<Input.Password placeholder="请输入App Secret" />
</Form.Item>
<Form.Item name="accessToken" label="Access Token" rules={[{ required: true, message: '请输入Access Token' }]}>
<Input placeholder="请输入Access Token" />
</Form.Item>
</>
);
case 'ebay':
return (
<>
<Form.Item name="clientId" label="Client ID" rules={[{ required: true, message: '请输入Client ID' }]}>
<Input placeholder="请输入Client ID" />
</Form.Item>
<Form.Item name="clientSecret" label="Client Secret" rules={[{ required: true, message: '请输入Client Secret' }]}>
<Input.Password placeholder="请输入Client Secret" />
</Form.Item>
<Form.Item name="refreshToken" label="Refresh Token" rules={[{ required: true, message: '请输入Refresh Token' }]}>
<Input placeholder="请输入Refresh Token" />
</Form.Item>
</>
);
default:
return (
<>
<Form.Item name="apiKey" label="API Key" rules={[{ required: true, message: '请输入API Key' }]}>
<Input placeholder="请输入API Key" />
</Form.Item>
<Form.Item name="apiSecret" label="API Secret" rules={[{ required: true, message: '请输入API Secret' }]}>
<Input.Password placeholder="请输入API Secret" />
</Form.Item>
</>
);
}
};
return (
<div style={{ padding: '20px' }}>
<Card title="Operation-Agent 管理" extra={<Button type="primary" icon={<PlusOutlined />} onClick={() => setBindingModalVisible(true)}></Button>}>
<Alert
message="操作说明"
description="本页面用于管理Operation-Agent平台集成包括店铺绑定、商品/订单同步等功能。"
type="info"
style={{ marginBottom: '20px' }}
/>
<Form layout="inline" onFinish={(values) => setMerchantId(values.merchantId)} style={{ marginBottom: '20px' }}>
<Form.Item name="merchantId" initialValue={merchantId} label="商户ID">
<Input placeholder="请输入商户ID" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit"></Button>
</Form.Item>
</Form>
<Table
loading={loading}
dataSource={stores}
rowKey="id"
columns={[
{
title: '店铺名称',
dataIndex: 'name',
key: 'name',
},
{
title: '平台',
dataIndex: 'platform',
key: 'platform',
render: (platform: string) => {
const platformInfo = platforms.find(p => p.value === platform);
return platformInfo ? platformInfo.label : platform;
},
},
{
title: '平台店铺ID',
dataIndex: 'platformShopId',
key: 'platformShopId',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => {
const statusInfo = statusMap[status as keyof typeof statusMap];
return statusInfo ? (
<Tag color={statusInfo.color}>{statusInfo.text}</Tag>
) : (
<Tag color="default">{status}</Tag>
);
},
},
{
title: '创建时间',
dataIndex: 'created_at',
key: 'created_at',
},
{
title: '操作',
key: 'action',
render: (_: any, record: Store) => (
<Space size="middle">
<Button icon={<SyncOutlined />} onClick={() => handleSyncProducts(record.id)}></Button>
<Button icon={<SyncOutlined />} onClick={() => handleSyncOrders(record.id)}></Button>
{record.status === 'active' ? (
<Button danger icon={<CloseOutlined />} onClick={() => handleDeactivateStore(record.id)}></Button>
) : (
<Button type="primary" icon={<CheckOutlined />} onClick={() => handleReactivateStore(record.id)}></Button>
)}
</Space>
),
},
]}
/>
</Card>
{/* 绑定店铺模态框 */}
<Modal
title="绑定店铺"
visible={bindingModalVisible}
onCancel={() => setBindingModalVisible(false)}
footer={null}
>
<Form form={form} layout="vertical" onFinish={handleBindStore}>
<Form.Item name="merchantId" label="商户ID" rules={[{ required: true, message: '请输入商户ID' }]} initialValue={merchantId}>
<Input placeholder="请输入商户ID" />
</Form.Item>
<Form.Item name="platform" label="平台" rules={[{ required: true, message: '请选择平台' }]}>
<Select placeholder="请选择平台" onChange={() => form.resetFields(['accessKey', 'secretKey', 'sellerId', 'marketplaceId', 'partnerId', 'partnerKey', 'shopId', 'appKey', 'appSecret', 'accessToken', 'appId', 'clientId', 'clientSecret', 'refreshToken', 'apiKey', 'apiSecret'])}>
{platforms.map(platform => (
<Option key={platform.value} value={platform.value}>{platform.label}</Option>
))}
</Select>
</Form.Item>
<Form.Item name="platformShopId" label="平台店铺ID" rules={[{ required: true, message: '请输入平台店铺ID' }]}>
<Input placeholder="请输入平台店铺ID" />
</Form.Item>
<Form.Item name="name" label="店铺名称" rules={[{ required: true, message: '请输入店铺名称' }]}>
<Input placeholder="请输入店铺名称" />
</Form.Item>
<Form.Item name="description" label="店铺描述">
<TextArea placeholder="请输入店铺描述" rows={3} />
</Form.Item>
<Divider orientation="left"></Divider>
<Form.Item name="platform" noStyle shouldUpdate>
{({ getFieldValue }) => {
const platform = getFieldValue('platform');
return platform ? renderAuthInfoForm(platform) : null;
}}
</Form.Item>
<Form.Item>
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
<Button onClick={() => setBindingModalVisible(false)}></Button>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
{/* 店铺详情模态框 */}
{currentStore && (
<Modal
title="店铺详情"
visible={!!currentStore}
onCancel={() => setCurrentStore(null)}
footer={null}
>
<Card>
<Card.Meta title={currentStore.name} description={`平台: ${platforms.find(p => p.value === currentStore.platform)?.label || currentStore.platform}`} />
<Divider />
<p><strong>ID:</strong> {currentStore.merchantId}</p>
<p><strong>ID:</strong> {currentStore.platformShopId}</p>
<p><strong>:</strong> <Tag color={statusMap[currentStore.status as keyof typeof statusMap]?.color || 'default'}>{statusMap[currentStore.status as keyof typeof statusMap]?.text || currentStore.status}</Tag></p>
<p><strong>:</strong> {currentStore.description || '无'}</p>
<p><strong>:</strong> {currentStore.created_at}</p>
<p><strong>:</strong> {currentStore.updated_at}</p>
<Divider />
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
<Button onClick={() => setCurrentStore(null)}></Button>
</Space>
</Card>
</Modal>
)}
</div>
);
};
export default OperationAgent;