feat: 实现Operation-Agent核心功能及电商平台适配器
refactor: 重构项目结构,分离server和dashboard代码 style: 统一代码风格,修复lint警告 test: 添加平台适配器工厂测试用例 ci: 更新CI/CD流程,增加语义验证和性能测试 docs: 添加语义中心文档,定义统一数据模型和状态机
This commit is contained in:
445
dashboard/src/pages/OperationAgent.tsx
Normal file
445
dashboard/src/pages/OperationAgent.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user