feat: 添加MSW模拟服务和数据源集成
refactor: 重构页面组件移除冗余Layout组件 feat: 实现WebSocket和事件总线系统 feat: 添加队列和调度系统 docs: 更新架构文档和服务映射 style: 清理重复接口定义使用数据源 chore: 更新依赖项配置 feat: 添加运行时系统和领域引导 ci: 配置ESLint边界检查规则 build: 添加Redis和WebSocket依赖 test: 添加MSW浏览器环境入口 perf: 优化数据获取逻辑使用统一数据源 fix: 修复类型定义和状态管理问题
This commit is contained in:
807
dashboard/src/pages/Product/CrossPlatformManage.tsx
Normal file
807
dashboard/src/pages/Product/CrossPlatformManage.tsx
Normal file
@@ -0,0 +1,807 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Card,
|
||||
Table,
|
||||
Button,
|
||||
Space,
|
||||
Tag,
|
||||
Input,
|
||||
Select,
|
||||
Row,
|
||||
Col,
|
||||
Modal,
|
||||
message,
|
||||
Tooltip,
|
||||
Badge,
|
||||
Alert,
|
||||
Descriptions,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Image,
|
||||
Typography,
|
||||
Tabs,
|
||||
Form,
|
||||
InputNumber,
|
||||
Switch,
|
||||
} from 'antd';
|
||||
import {
|
||||
SyncOutlined,
|
||||
EditOutlined,
|
||||
EyeOutlined,
|
||||
GlobalOutlined,
|
||||
AmazonOutlined,
|
||||
ShoppingOutlined,
|
||||
ShopOutlined,
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
LinkOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
const { Option } = Select;
|
||||
const { Search } = Input;
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
// ==================== 多商户店铺配置 ====================
|
||||
// 当前用户拥有的店铺(根据登录用户的权限动态加载)
|
||||
interface UserShop {
|
||||
id: string;
|
||||
platform: string;
|
||||
shopId: string;
|
||||
shopName: string;
|
||||
region: string;
|
||||
status: 'ACTIVE' | 'INACTIVE';
|
||||
apiSupported: boolean; // 是否支持API管理
|
||||
}
|
||||
|
||||
// 模拟当前用户拥有的店铺
|
||||
const CURRENT_USER_SHOPS: UserShop[] = [
|
||||
{ id: '1', platform: 'AMAZON', shopId: 'AMZ-US-001', shopName: 'Amazon US Store', region: 'US', status: 'ACTIVE', apiSupported: true },
|
||||
{ id: '2', platform: 'AMAZON', shopId: 'AMZ-EU-001', shopName: 'Amazon EU Store', region: 'EU', status: 'ACTIVE', apiSupported: true },
|
||||
{ id: '3', platform: 'EBAY', shopId: 'EB-US-001', shopName: 'eBay US Store', region: 'US', status: 'ACTIVE', apiSupported: true },
|
||||
{ id: '4', platform: 'SHOPIFY', shopId: 'SF-001', shopName: 'My Shopify Store', region: 'US', status: 'ACTIVE', apiSupported: true },
|
||||
{ id: '5', platform: 'SHOPEE', shopId: 'SP-SG-001', shopName: 'Shopee Singapore', region: 'SG', status: 'ACTIVE', apiSupported: false }, // 不支持API
|
||||
{ id: '6', platform: 'TIKTOK', shopId: 'TK-US-001', shopName: 'TikTok US Shop', region: 'US', status: 'ACTIVE', apiSupported: false }, // 不支持API
|
||||
];
|
||||
|
||||
// 平台配置
|
||||
const PLATFORM_CONFIG: Record<string, { name: string; color: string; icon: React.ReactNode }> = {
|
||||
AMAZON: { name: 'Amazon', color: '#FF9900', icon: <AmazonOutlined /> },
|
||||
EBAY: { name: 'eBay', color: '#E53238', icon: <ShopOutlined /> },
|
||||
SHOPIFY: { name: 'Shopify', color: '#96BF48', icon: <ShoppingOutlined /> },
|
||||
SHOPEE: { name: 'Shopee', color: '#EE4D2D', icon: <ShopOutlined /> },
|
||||
TIKTOK: { name: 'TikTok Shop', color: '#000000', icon: <ShopOutlined /> },
|
||||
LAZADA: { name: 'Lazada', color: '#0F156D', icon: <ShopOutlined /> },
|
||||
};
|
||||
|
||||
// ==================== 跨平台商品接口 ====================
|
||||
interface CrossPlatformProduct {
|
||||
id: string;
|
||||
localProductId: string;
|
||||
sku: string;
|
||||
name: string;
|
||||
image: string;
|
||||
category: string;
|
||||
description: string;
|
||||
platforms: {
|
||||
[shopId: string]: {
|
||||
status: 'LIVE' | 'OFFLINE' | 'PENDING' | 'ERROR' | 'SYNCING' | 'NOT_LISTED';
|
||||
platformProductId: string;
|
||||
platformSku: string;
|
||||
price: number;
|
||||
stock: number;
|
||||
sales: number;
|
||||
listingUrl: string;
|
||||
lastSync: string;
|
||||
shopInfo: UserShop;
|
||||
};
|
||||
};
|
||||
basePrice: number;
|
||||
baseStock: number;
|
||||
totalSales: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// ==================== 模拟数据 ====================
|
||||
const MOCK_CROSS_PLATFORM_PRODUCTS: CrossPlatformProduct[] = [
|
||||
{
|
||||
id: 'CP001',
|
||||
localProductId: 'P001',
|
||||
sku: 'TP-TEMP-001',
|
||||
name: '工业温度传感器 Pro',
|
||||
image: 'https://via.placeholder.com/200x200?text=Product',
|
||||
category: '工业自动化',
|
||||
description: '高精度工业温度传感器,适用于各种工业环境,测量范围-40°C至125°C',
|
||||
basePrice: 89.99,
|
||||
baseStock: 256,
|
||||
totalSales: 1250,
|
||||
createdAt: '2025-12-15 10:00:00',
|
||||
updatedAt: '2026-03-18 10:30:00',
|
||||
platforms: {
|
||||
'AMZ-US-001': {
|
||||
status: 'LIVE',
|
||||
platformProductId: 'AMZ-123456',
|
||||
platformSku: 'TP-TEMP-001-AMZ',
|
||||
price: 99.99,
|
||||
stock: 100,
|
||||
sales: 850,
|
||||
listingUrl: 'https://amazon.com/dp/123456',
|
||||
lastSync: '2026-03-18 10:00:00',
|
||||
shopInfo: CURRENT_USER_SHOPS[0],
|
||||
},
|
||||
'AMZ-EU-001': {
|
||||
status: 'LIVE',
|
||||
platformProductId: 'AMZ-EU-789',
|
||||
platformSku: 'TP-TEMP-001-AMZ-EU',
|
||||
price: 89.99,
|
||||
stock: 80,
|
||||
sales: 200,
|
||||
listingUrl: 'https://amazon.de/dp/789',
|
||||
lastSync: '2026-03-18 09:00:00',
|
||||
shopInfo: CURRENT_USER_SHOPS[1],
|
||||
},
|
||||
'EB-US-001': {
|
||||
status: 'LIVE',
|
||||
platformProductId: 'EB-789012',
|
||||
platformSku: 'TP-TEMP-001-EB',
|
||||
price: 95.99,
|
||||
stock: 76,
|
||||
sales: 200,
|
||||
listingUrl: 'https://ebay.com/itm/789012',
|
||||
lastSync: '2026-03-18 09:30:00',
|
||||
shopInfo: CURRENT_USER_SHOPS[2],
|
||||
},
|
||||
'SP-SG-001': {
|
||||
status: 'LIVE',
|
||||
platformProductId: 'SP-345678',
|
||||
platformSku: 'TP-TEMP-001-SP',
|
||||
price: 89.99,
|
||||
stock: 76,
|
||||
sales: 80,
|
||||
listingUrl: 'https://shopee.sg/product/345678',
|
||||
lastSync: '2026-03-18 09:00:00',
|
||||
shopInfo: CURRENT_USER_SHOPS[4],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'CP002',
|
||||
localProductId: 'P002',
|
||||
sku: 'TP-PRES-002',
|
||||
name: '压力传感器 Digital',
|
||||
image: 'https://via.placeholder.com/200x200?text=Product',
|
||||
category: '工业自动化',
|
||||
description: '数字式压力传感器,精度0.1%,支持多种输出信号',
|
||||
basePrice: 129.99,
|
||||
baseStock: 128,
|
||||
totalSales: 680,
|
||||
createdAt: '2026-03-10 14:30:00',
|
||||
updatedAt: '2026-03-18 09:15:00',
|
||||
platforms: {
|
||||
'AMZ-US-001': {
|
||||
status: 'LIVE',
|
||||
platformProductId: 'AMZ-234567',
|
||||
platformSku: 'TP-PRES-002-AMZ',
|
||||
price: 139.99,
|
||||
stock: 50,
|
||||
sales: 500,
|
||||
listingUrl: 'https://amazon.com/dp/234567',
|
||||
lastSync: '2026-03-18 10:00:00',
|
||||
shopInfo: CURRENT_USER_SHOPS[0],
|
||||
},
|
||||
'SF-001': {
|
||||
status: 'LIVE',
|
||||
platformProductId: 'SF-890123',
|
||||
platformSku: 'TP-PRES-002-SF',
|
||||
price: 129.99,
|
||||
stock: 78,
|
||||
sales: 180,
|
||||
listingUrl: 'https://mystore.myshopify.com/products/890123',
|
||||
lastSync: '2026-03-18 08:30:00',
|
||||
shopInfo: CURRENT_USER_SHOPS[3],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'CP003',
|
||||
localProductId: 'P003',
|
||||
sku: 'TP-FLOW-003',
|
||||
name: '流量计 Ultra',
|
||||
image: 'https://via.placeholder.com/200x200?text=Product',
|
||||
category: '仪器仪表',
|
||||
description: '超声波流量计,非接触式测量,精度高',
|
||||
basePrice: 299.99,
|
||||
baseStock: 64,
|
||||
totalSales: 320,
|
||||
createdAt: '2026-03-01 09:00:00',
|
||||
updatedAt: '2026-03-17 16:45:00',
|
||||
platforms: {
|
||||
'AMZ-US-001': {
|
||||
status: 'OFFLINE',
|
||||
platformProductId: 'AMZ-345678',
|
||||
platformSku: 'TP-FLOW-003-AMZ',
|
||||
price: 319.99,
|
||||
stock: 0,
|
||||
sales: 300,
|
||||
listingUrl: 'https://amazon.com/dp/345678',
|
||||
lastSync: '2026-03-17 16:00:00',
|
||||
shopInfo: CURRENT_USER_SHOPS[0],
|
||||
},
|
||||
'EB-US-001': {
|
||||
status: 'ERROR',
|
||||
platformProductId: 'EB-901234',
|
||||
platformSku: 'TP-FLOW-003-EB',
|
||||
price: 299.99,
|
||||
stock: 64,
|
||||
sales: 20,
|
||||
listingUrl: 'https://ebay.com/itm/901234',
|
||||
lastSync: '2026-03-17 15:30:00',
|
||||
shopInfo: CURRENT_USER_SHOPS[2],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const STATUS_CONFIG: Record<string, { color: string; text: string; icon: React.ReactNode }> = {
|
||||
LIVE: { color: 'success', text: '在线', icon: <CheckCircleOutlined /> },
|
||||
OFFLINE: { color: 'default', text: '已下架', icon: <CloseCircleOutlined /> },
|
||||
PENDING: { color: 'warning', text: '审核中', icon: <ExclamationCircleOutlined /> },
|
||||
ERROR: { color: 'error', text: '异常', icon: <ExclamationCircleOutlined /> },
|
||||
SYNCING: { color: 'processing', text: '同步中', icon: <SyncOutlined spin /> },
|
||||
NOT_LISTED: { color: 'default', text: '未上架', icon: <CloseCircleOutlined /> },
|
||||
};
|
||||
|
||||
const CrossPlatformManage: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [products, setProducts] = useState<CrossPlatformProduct[]>([]);
|
||||
const [filteredProducts, setFilteredProducts] = useState<CrossPlatformProduct[]>([]);
|
||||
const [selectedShop, setSelectedShop] = useState<string>('ALL');
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [selectedRows, setSelectedRows] = useState<CrossPlatformProduct[]>([]);
|
||||
const [syncLoading, setSyncLoading] = useState<Record<string, boolean>>({});
|
||||
|
||||
// 详情弹窗
|
||||
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
||||
const [selectedProduct, setSelectedProduct] = useState<CrossPlatformProduct | null>(null);
|
||||
|
||||
// 同步弹窗
|
||||
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
||||
const [syncForm] = Form.useForm();
|
||||
const [syncingProduct, setSyncingProduct] = useState<CrossPlatformProduct | null>(null);
|
||||
const [selectedSyncShops, setSelectedSyncShops] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchProducts();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
filterProducts();
|
||||
}, [products, selectedShop, searchText]);
|
||||
|
||||
const fetchProducts = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
setProducts(MOCK_CROSS_PLATFORM_PRODUCTS);
|
||||
} catch (error) {
|
||||
message.error('加载跨平台商品失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const filterProducts = () => {
|
||||
let result = [...products];
|
||||
|
||||
// 按店铺筛选
|
||||
if (selectedShop !== 'ALL') {
|
||||
result = result.filter(p =>
|
||||
p.platforms[selectedShop] &&
|
||||
p.platforms[selectedShop].status !== 'NOT_LISTED'
|
||||
);
|
||||
}
|
||||
|
||||
// 按关键词搜索
|
||||
if (searchText) {
|
||||
result = result.filter(p =>
|
||||
p.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
p.sku.toLowerCase().includes(searchText.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
setFilteredProducts(result);
|
||||
};
|
||||
|
||||
// 获取用户拥有的店铺列表(按平台分组)
|
||||
const getUserShopsByPlatform = () => {
|
||||
const grouped: Record<string, UserShop[]> = {};
|
||||
CURRENT_USER_SHOPS.forEach(shop => {
|
||||
if (!grouped[shop.platform]) {
|
||||
grouped[shop.platform] = [];
|
||||
}
|
||||
grouped[shop.platform].push(shop);
|
||||
});
|
||||
return grouped;
|
||||
};
|
||||
|
||||
// 查看商品详情
|
||||
const handleViewDetail = (product: CrossPlatformProduct) => {
|
||||
setSelectedProduct(product);
|
||||
setDetailModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开同步弹窗
|
||||
const handleOpenSyncModal = (product: CrossPlatformProduct) => {
|
||||
setSyncingProduct(product);
|
||||
// 默认选中已上架的店铺
|
||||
const listedShops = Object.entries(product.platforms)
|
||||
.filter(([_, info]) => info.status !== 'NOT_LISTED')
|
||||
.filter(([shopId, info]) => info.shopInfo.apiSupported) // 只选中支持API的店铺
|
||||
.map(([shopId]) => shopId);
|
||||
setSelectedSyncShops(listedShops);
|
||||
syncForm.setFieldsValue({
|
||||
shops: listedShops,
|
||||
syncStock: true,
|
||||
syncPrice: false,
|
||||
});
|
||||
setSyncModalVisible(true);
|
||||
};
|
||||
|
||||
// 执行同步
|
||||
const handleSync = async () => {
|
||||
if (selectedSyncShops.length === 0) {
|
||||
message.warning('请至少选择一个目标店铺');
|
||||
return;
|
||||
}
|
||||
|
||||
const values = syncForm.getFieldsValue();
|
||||
message.loading(`正在同步到 ${selectedSyncShops.length} 个店铺...`, 2);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
message.success(`同步完成!库存: ${values.syncStock ? '是' : '否'}, 价格: ${values.syncPrice ? '是' : '否'}`);
|
||||
setSyncModalVisible(false);
|
||||
fetchProducts();
|
||||
};
|
||||
|
||||
// 批量同步库存
|
||||
const handleBatchSyncStock = async () => {
|
||||
if (selectedRows.length === 0) {
|
||||
message.warning('请先选择商品');
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '批量同步库存',
|
||||
content: `将对 ${selectedRows.length} 个商品同步库存到所有支持API的店铺`,
|
||||
onOk: async () => {
|
||||
message.loading('正在批量同步库存...', 2);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
message.success('批量同步完成');
|
||||
fetchProducts();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 获取店铺统计
|
||||
const getShopStats = () => {
|
||||
const stats: Record<string, number> = { ALL: products.length };
|
||||
CURRENT_USER_SHOPS.forEach(shop => {
|
||||
stats[shop.shopId] = products.filter(p =>
|
||||
p.platforms[shop.shopId] && p.platforms[shop.shopId].status !== 'NOT_LISTED'
|
||||
).length;
|
||||
});
|
||||
return stats;
|
||||
};
|
||||
|
||||
const shopStats = getShopStats();
|
||||
const userShopsByPlatform = getUserShopsByPlatform();
|
||||
|
||||
// 表格列定义
|
||||
const columns: ColumnsType<CrossPlatformProduct> = [
|
||||
{
|
||||
title: '商品信息',
|
||||
key: 'productInfo',
|
||||
width: 300,
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Image src={record.image} width={60} height={60} style={{ objectFit: 'cover' }} />
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{record.name}</div>
|
||||
<div style={{ fontSize: 12, color: '#999' }}>SKU: {record.sku}</div>
|
||||
<div style={{ fontSize: 12, color: '#666' }}>{record.category}</div>
|
||||
</div>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '本地信息',
|
||||
key: 'localInfo',
|
||||
width: 150,
|
||||
render: (_, record) => (
|
||||
<div>
|
||||
<div>价格: ${record.basePrice.toFixed(2)}</div>
|
||||
<div>库存: {record.baseStock}</div>
|
||||
<div>总销量: {record.totalSales}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
// 为每个用户店铺生成一列
|
||||
...CURRENT_USER_SHOPS.map(shop => ({
|
||||
title: (
|
||||
<Tooltip title={`${shop.shopName} (${shop.region})`}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ color: PLATFORM_CONFIG[shop.platform]?.color }}>
|
||||
{PLATFORM_CONFIG[shop.platform]?.icon}
|
||||
</div>
|
||||
<div style={{ fontSize: 10 }}>{shop.region}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
),
|
||||
key: `shop-${shop.shopId}`,
|
||||
width: 100,
|
||||
align: 'center' as const,
|
||||
render: (_: any, record: CrossPlatformProduct) => {
|
||||
const platformInfo = record.platforms[shop.shopId];
|
||||
if (!platformInfo || platformInfo.status === 'NOT_LISTED') {
|
||||
return <Tag size="small" style={{ opacity: 0.5 }}>未上架</Tag>;
|
||||
}
|
||||
|
||||
const statusConfig = STATUS_CONFIG[platformInfo.status];
|
||||
const canManage = shop.apiSupported;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item onClick={() => handleViewDetail(record)}>
|
||||
<EyeOutlined /> 查看详情
|
||||
</Menu.Item>
|
||||
{canManage && platformInfo.status === 'LIVE' && (
|
||||
<Menu.Item onClick={() => handleOpenSyncModal(record)}>
|
||||
<SyncOutlined /> 同步库存/价格
|
||||
</Menu.Item>
|
||||
)}
|
||||
{canManage && platformInfo.status === 'LIVE' && (
|
||||
<Menu.Item onClick={() => message.info('打开价格调整')}>
|
||||
<EditOutlined /> 调整价格
|
||||
</Menu.Item>
|
||||
)}
|
||||
{canManage && platformInfo.status === 'LIVE' && (
|
||||
<Menu.Item danger onClick={() => message.info('执行下架')}>
|
||||
<CloseCircleOutlined /> 下架
|
||||
</Menu.Item>
|
||||
)}
|
||||
{canManage && platformInfo.status === 'OFFLINE' && (
|
||||
<Menu.Item onClick={() => message.info('执行上架')}>
|
||||
<CheckCircleOutlined /> 上架
|
||||
</Menu.Item>
|
||||
)}
|
||||
{!canManage && (
|
||||
<Menu.Item disabled>
|
||||
<ExclamationCircleOutlined /> 不支持API管理
|
||||
</Menu.Item>
|
||||
)}
|
||||
{platformInfo.listingUrl && (
|
||||
<Menu.Item>
|
||||
<a href={platformInfo.listingUrl} target="_blank" rel="noopener noreferrer">
|
||||
<LinkOutlined /> 打开商品页
|
||||
</a>
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Tag
|
||||
color={statusConfig.color}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
opacity: canManage ? 1 : 0.5,
|
||||
}}
|
||||
>
|
||||
{statusConfig.text}
|
||||
{!canManage && <span style={{ fontSize: 10 }}>*</span>}
|
||||
</Tag>
|
||||
</Dropdown>
|
||||
);
|
||||
},
|
||||
})),
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
render: (_, record) => (
|
||||
<Space size="small">
|
||||
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => handleViewDetail(record)}>
|
||||
详情
|
||||
</Button>
|
||||
<Button type="link" size="small" icon={<SyncOutlined />} onClick={() => handleOpenSyncModal(record)}>
|
||||
同步
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="cross-platform-manage">
|
||||
<Alert
|
||||
message="跨平台商品管理说明"
|
||||
description={
|
||||
<div>
|
||||
<p>• 集中管理您所有店铺的在线商品</p>
|
||||
<p>• 标记 * 的店铺暂不支持API管理,需要手动操作</p>
|
||||
<p>• 同步操作需要指定目标店铺,只同步到支持API的店铺</p>
|
||||
<p>• 当前拥有 {CURRENT_USER_SHOPS.length} 个店铺</p>
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Col span={24}>
|
||||
<Space style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
|
||||
<Space>
|
||||
<Title level={5} style={{ margin: 0 }}>跨平台商品列表</Title>
|
||||
<Badge count={filteredProducts.length} showZero style={{ backgroundColor: '#1890ff' }} />
|
||||
</Space>
|
||||
<Space>
|
||||
<Search
|
||||
placeholder="搜索商品名称或SKU"
|
||||
allowClear
|
||||
onSearch={setSearchText}
|
||||
style={{ width: 250 }}
|
||||
/>
|
||||
<Select
|
||||
value={selectedShop}
|
||||
onChange={setSelectedShop}
|
||||
style={{ width: 200 }}
|
||||
placeholder="选择店铺"
|
||||
>
|
||||
<Option value="ALL">全部店铺 ({shopStats.ALL})</Option>
|
||||
{Object.entries(userShopsByPlatform).map(([platform, shops]) => (
|
||||
<OptGroup key={platform} label={PLATFORM_CONFIG[platform]?.name || platform}>
|
||||
{shops.map(shop => (
|
||||
<Option key={shop.shopId} value={shop.shopId}>
|
||||
<Space>
|
||||
{shop.shopName}
|
||||
{!shop.apiSupported && <Tag size="small" color="warning">No API</Tag>}
|
||||
<span style={{ color: '#999' }}>({shopStats[shop.shopId] || 0})</span>
|
||||
</Space>
|
||||
</Option>
|
||||
))}
|
||||
</OptGroup>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{selectedRows.length > 0 && (
|
||||
<Alert
|
||||
message={`已选择 ${selectedRows.length} 个商品`}
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
action={
|
||||
<Space>
|
||||
<Button size="small" onClick={handleBatchSyncStock}>
|
||||
<SyncOutlined /> 批量同步库存
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={filteredProducts}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
scroll={{ x: 1500 }}
|
||||
pagination={{ showSizeChanger: true, showTotal: (total) => `共 ${total} 个商品` }}
|
||||
rowSelection={{
|
||||
selectedRowKeys: selectedRows.map(r => r.id),
|
||||
onChange: (_, rows) => setSelectedRows(rows),
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 商品详情弹窗 */}
|
||||
<Modal
|
||||
title="商品详情"
|
||||
visible={detailModalVisible}
|
||||
onCancel={() => setDetailModalVisible(false)}
|
||||
width={900}
|
||||
footer={[
|
||||
<Button key="close" onClick={() => setDetailModalVisible(false)}>
|
||||
关闭
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
{selectedProduct && (
|
||||
<Tabs defaultActiveKey="info">
|
||||
<TabPane tab="基本信息" key="info">
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Image src={selectedProduct.image} style={{ width: '100%' }} />
|
||||
</Col>
|
||||
<Col span={16}>
|
||||
<Descriptions column={2}>
|
||||
<Descriptions.Item label="商品名称">{selectedProduct.name}</Descriptions.Item>
|
||||
<Descriptions.Item label="SKU">{selectedProduct.sku}</Descriptions.Item>
|
||||
<Descriptions.Item label="分类">{selectedProduct.category}</Descriptions.Item>
|
||||
<Descriptions.Item label="本地价格">${selectedProduct.basePrice.toFixed(2)}</Descriptions.Item>
|
||||
<Descriptions.Item label="本地库存">{selectedProduct.baseStock}</Descriptions.Item>
|
||||
<Descriptions.Item label="总销量">{selectedProduct.totalSales}</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间">{selectedProduct.createdAt}</Descriptions.Item>
|
||||
<Descriptions.Item label="更新时间">{selectedProduct.updatedAt}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Divider />
|
||||
<div>
|
||||
<Text strong>商品描述:</Text>
|
||||
<p>{selectedProduct.description}</p>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</TabPane>
|
||||
<TabPane tab="店铺分布" key="shops">
|
||||
<Table
|
||||
dataSource={Object.entries(selectedProduct.platforms)}
|
||||
rowKey="[0]"
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
title: '店铺',
|
||||
render: ([shopId, info]: [string, any]) => (
|
||||
<Space>
|
||||
{PLATFORM_CONFIG[info.shopInfo.platform]?.icon}
|
||||
<span>{info.shopInfo.shopName}</span>
|
||||
<Tag size="small">{info.shopInfo.region}</Tag>
|
||||
{!info.shopInfo.apiSupported && <Tag size="small" color="warning">No API</Tag>}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
render: ([_, info]: [string, any]) => (
|
||||
<Tag color={STATUS_CONFIG[info.status].color}>
|
||||
{STATUS_CONFIG[info.status].text}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '平台SKU',
|
||||
render: ([_, info]: [string, any]) => info.platformSku,
|
||||
},
|
||||
{
|
||||
title: '售价',
|
||||
render: ([_, info]: [string, any]) => `$${info.price.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
title: '库存',
|
||||
render: ([_, info]: [string, any]) => info.stock,
|
||||
},
|
||||
{
|
||||
title: '销量',
|
||||
render: ([_, info]: [string, any]) => info.sales,
|
||||
},
|
||||
{
|
||||
title: '最后同步',
|
||||
render: ([_, info]: [string, any]) => info.lastSync,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
render: ([shopId, info]: [string, any]) => (
|
||||
<Space>
|
||||
{info.shopInfo.apiSupported && (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => handleOpenSyncModal(selectedProduct)}
|
||||
>
|
||||
同步
|
||||
</Button>
|
||||
)}
|
||||
{info.listingUrl && (
|
||||
<a href={info.listingUrl} target="_blank" rel="noopener noreferrer">
|
||||
<LinkOutlined /> 查看
|
||||
</a>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* 同步弹窗 */}
|
||||
<Modal
|
||||
title="同步商品"
|
||||
visible={syncModalVisible}
|
||||
onCancel={() => setSyncModalVisible(false)}
|
||||
onOk={handleSync}
|
||||
width={600}
|
||||
>
|
||||
{syncingProduct && (
|
||||
<Form form={syncForm} layout="vertical">
|
||||
<Alert
|
||||
message={`正在同步: ${syncingProduct.name}`}
|
||||
description={`SKU: ${syncingProduct.sku}`}
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
|
||||
<Form.Item
|
||||
name="shops"
|
||||
label="选择目标店铺"
|
||||
rules={[{ required: true, message: '请至少选择一个店铺' }]}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="选择要同步的店铺"
|
||||
onChange={setSelectedSyncShops}
|
||||
>
|
||||
{Object.entries(syncingProduct.platforms)
|
||||
.filter(([_, info]) => info.status !== 'NOT_LISTED')
|
||||
.filter(([_, info]) => info.shopInfo.apiSupported)
|
||||
.map(([shopId, info]) => (
|
||||
<Option key={shopId} value={shopId}>
|
||||
<Space>
|
||||
{PLATFORM_CONFIG[info.shopInfo.platform]?.icon}
|
||||
{info.shopInfo.shopName}
|
||||
<Tag size="small">{info.shopInfo.region}</Tag>
|
||||
</Space>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="syncStock"
|
||||
label="同步库存"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch checkedChildren="是" unCheckedChildren="否" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="syncPrice"
|
||||
label="同步价格"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch checkedChildren="是" unCheckedChildren="否" />
|
||||
</Form.Item>
|
||||
|
||||
<Alert
|
||||
message="同步说明"
|
||||
description="只支持同步到拥有API权限的店铺。不支持API的店铺需要手动操作。"
|
||||
type="warning"
|
||||
showIcon
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 添加 OptGroup 导入
|
||||
const { OptGroup } = Select;
|
||||
|
||||
export default CrossPlatformManage;
|
||||
Reference in New Issue
Block a user