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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

14
dashboard/src/App.tsx Normal file
View File

@@ -0,0 +1,14 @@
import React from 'react';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/lib/locale/zh_CN';
import AppRouter from './router';
const App: React.FC = () => {
return (
<ConfigProvider locale={zhCN}>
<AppRouter />
</ConfigProvider>
);
};
export default App;

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;

16
dashboard/src/router.tsx Normal file
View File

@@ -0,0 +1,16 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import OperationAgent from './pages/OperationAgent';
const AppRouter: React.FC = () => {
return (
<Router>
<Routes>
<Route path="/" element={<OperationAgent />} />
<Route path="/operation-agent" element={<OperationAgent />} />
</Routes>
</Router>
);
};
export default AppRouter;

View File

@@ -0,0 +1,57 @@
import axios from 'axios';
// 创建axios实例
const http = axios.create({
baseURL: 'http://localhost:3000', // 后端API基础URL
timeout: 10000, // 请求超时时间
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器
http.interceptors.request.use(
(config) => {
// 可以在这里添加token等认证信息
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
http.interceptors.response.use(
(response) => {
return response;
},
(error) => {
// 处理错误响应
if (error.response) {
switch (error.response.status) {
case 401:
// 未授权处理
break;
case 403:
// 禁止访问处理
break;
case 404:
// 资源不存在处理
break;
case 500:
// 服务器错误处理
break;
default:
// 其他错误处理
break;
}
}
return Promise.reject(error);
}
);
export { http };

View File

@@ -0,0 +1,95 @@
import { http } from './http';
export interface StoreBindingData {
merchantId: string;
platform: string;
platformShopId: string;
name: string;
description?: string;
authInfo: Record<string, any>;
}
export interface Store {
id: string;
merchantId: string;
name: string;
platform: string;
platformShopId: string;
description: string;
status: string;
created_at: string;
updated_at: string;
}
export interface SyncResult {
success: boolean;
count: number;
}
export class OperationAgentService {
/**
* 绑定店铺
*/
async bindStore(data: StoreBindingData): Promise<Store> {
const response = await http.post<Store>('/api/operation-agent/stores', data);
return response.data;
}
/**
* 获取商户的店铺列表
*/
async getStores(merchantId: string): Promise<Store[]> {
const response = await http.get<Store[]>(`/api/operation-agent/stores/${merchantId}`);
return response.data;
}
/**
* 获取店铺详情
*/
async getStore(storeId: string): Promise<Store> {
const response = await http.get<Store>(`/api/operation-agent/stores/detail/${storeId}`);
return response.data;
}
/**
* 同步店铺商品
*/
async syncProducts(storeId: string): Promise<SyncResult> {
const response = await http.post<SyncResult>(`/api/operation-agent/stores/${storeId}/products/sync`);
return response.data;
}
/**
* 同步店铺订单
*/
async syncOrders(storeId: string): Promise<SyncResult> {
const response = await http.post<SyncResult>(`/api/operation-agent/stores/${storeId}/orders/sync`);
return response.data;
}
/**
* 更新商品价格
*/
async updateProductPrice(storeId: string, productId: string, price: number): Promise<boolean> {
const response = await http.put<boolean>(`/api/operation-agent/stores/${storeId}/products/${productId}/price`, { price });
return response.data;
}
/**
* 停用店铺
*/
async deactivateStore(storeId: string): Promise<Store> {
const response = await http.put<Store>(`/api/operation-agent/stores/${storeId}/deactivate`);
return response.data;
}
/**
* 重新激活店铺
*/
async reactivateStore(storeId: string): Promise<Store> {
const response = await http.put<Store>(`/api/operation-agent/stores/${storeId}/reactivate`);
return response.data;
}
}
export const operationAgentService = new OperationAgentService();