2026-03-19 01:39:34 +08:00
|
|
|
|
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;
|
2026-03-23 12:41:35 +08:00
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== 多商户店铺配置 ====================
|
|
|
|
|
|
// 当前用户拥有的店铺(根据登录用户的权限动态加载)
|
|
|
|
|
|
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 }> = {
|
2026-03-23 12:41:35 +08:00
|
|
|
|
// TikTok系列
|
|
|
|
|
|
TIKTOK: { name: 'TikTok', color: '#000000', icon: <ShopOutlined /> },
|
|
|
|
|
|
TIKTOK_FULL: { name: 'TikTok全托管', color: '#000000', icon: <ShopOutlined /> },
|
|
|
|
|
|
|
|
|
|
|
|
// Shopee系列
|
2026-03-19 01:39:34 +08:00
|
|
|
|
SHOPEE: { name: 'Shopee', color: '#EE4D2D', icon: <ShopOutlined /> },
|
2026-03-23 12:41:35 +08:00
|
|
|
|
SHOPEE_FULL: { name: 'Shopee全托管', color: '#EE4D2D', icon: <ShopOutlined /> },
|
|
|
|
|
|
SHOPEE_LIGHT: { name: 'Shopee轻出海', color: '#EE4D2D', icon: <ShopOutlined /> },
|
|
|
|
|
|
|
|
|
|
|
|
// Lazada系列
|
2026-03-19 01:39:34 +08:00
|
|
|
|
LAZADA: { name: 'Lazada', color: '#0F156D', icon: <ShopOutlined /> },
|
2026-03-23 12:41:35 +08:00
|
|
|
|
LAZADA_FULL: { name: 'Lazada全托管', color: '#0F156D', icon: <ShopOutlined /> },
|
|
|
|
|
|
|
|
|
|
|
|
// Temu
|
|
|
|
|
|
TEMU_FULL: { name: 'Temu全托管', color: '#96BF48', icon: <ShopOutlined /> },
|
|
|
|
|
|
|
|
|
|
|
|
// SHEIN系列
|
|
|
|
|
|
SHEIN: { name: 'SHEIN', color: '#FF6B6B', icon: <ShopOutlined /> },
|
|
|
|
|
|
SHEIN_HALF: { name: 'SHEIN半托管', color: '#FF6B6B', icon: <ShopOutlined /> },
|
|
|
|
|
|
|
|
|
|
|
|
// 其他平台
|
|
|
|
|
|
OZON: { name: 'Ozon', color: '#FFD700', icon: <ShopOutlined /> },
|
|
|
|
|
|
YANDEX: { name: 'Yandex', color: '#FF0000', icon: <ShopOutlined /> },
|
|
|
|
|
|
ALIEXPRESS: { name: 'AliExpress', color: '#FF6A00', icon: <ShopOutlined /> },
|
|
|
|
|
|
ALIEXPRESS_HALF: { name: '速卖通半托管', color: '#FF6A00', icon: <ShopOutlined /> },
|
|
|
|
|
|
ALIEXPRESS_POP: { name: '速卖通本土POP', color: '#FF6A00', icon: <ShopOutlined /> },
|
|
|
|
|
|
COUPANG: { name: 'Coupang', color: '#FF3B30', icon: <ShopOutlined /> },
|
|
|
|
|
|
WALMART: { name: 'Walmart', color: '#007DC6', icon: <ShopOutlined /> },
|
|
|
|
|
|
WILDBERRIES: { name: 'Wildberries', color: '#9370DB', icon: <ShopOutlined /> },
|
|
|
|
|
|
ALLEGRO: { name: 'Allegro', color: '#00A870', icon: <ShopOutlined /> },
|
|
|
|
|
|
MERCADO_LIBRE: { name: 'Mercado Libre', color: '#6C3483', icon: <ShopOutlined /> },
|
|
|
|
|
|
JUMIA: { name: 'Jumia', color: '#FF6B00', icon: <ShopOutlined /> },
|
|
|
|
|
|
JOOM: { name: 'Joom', color: '#8A2BE2', icon: <ShopOutlined /> },
|
|
|
|
|
|
AMAZON: { name: 'Amazon', color: '#FF9900', icon: <AmazonOutlined /> },
|
|
|
|
|
|
WISH: { name: 'Wish', color: '#4A90E2', icon: <ShopOutlined /> },
|
|
|
|
|
|
EMAG: { name: 'eMAG', color: '#00B140', icon: <ShopOutlined /> },
|
|
|
|
|
|
MIRAVIA: { name: 'Miravia', color: '#FF69B4', icon: <ShopOutlined /> },
|
|
|
|
|
|
DARAZ: { name: 'Daraz', color: '#FF5722', icon: <ShopOutlined /> },
|
|
|
|
|
|
JOYBUY: { name: 'Joybuy', color: '#E74C3C', icon: <ShopOutlined /> },
|
|
|
|
|
|
ALIBABA: { name: 'Alibaba', color: '#FF6A00', icon: <ShopOutlined /> },
|
|
|
|
|
|
QOO10: { name: 'Qoo10', color: '#FF4500', icon: <ShopOutlined /> },
|
|
|
|
|
|
SHOPIFY: { name: 'Shopify', color: '#96BF48', icon: <ShoppingOutlined /> },
|
|
|
|
|
|
SHOPLAZZA: { name: 'Shoplazza', color: '#3498DB', icon: <ShopOutlined /> },
|
|
|
|
|
|
SHOPYY_V1: { name: 'SHOPYY v1.0', color: '#9370DB', icon: <ShopOutlined /> },
|
|
|
|
|
|
SHOPYY_V2: { name: 'SHOPYY v2.0', color: '#9370DB', icon: <ShopOutlined /> },
|
|
|
|
|
|
SHOPLINE: { name: 'SHOPLINE', color: '#27AE60', icon: <ShopOutlined /> },
|
|
|
|
|
|
GREATBOSS: { name: 'GreatBoss', color: '#3498DB', icon: <ShopOutlined /> },
|
|
|
|
|
|
OTHER: { name: '其他', color: '#999999', icon: <ShopOutlined /> },
|
|
|
|
|
|
|
|
|
|
|
|
// 原有平台
|
|
|
|
|
|
EBAY: { name: 'eBay', color: '#E53238', icon: <ShopOutlined /> },
|
2026-03-19 01:39:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 跨平台商品接口 ====================
|
|
|
|
|
|
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') {
|
2026-03-20 17:53:46 +08:00
|
|
|
|
return <Tag style={{ opacity: 0.5 }}>未上架</Tag>;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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}
|
2026-03-20 17:53:46 +08:00
|
|
|
|
{!shop.apiSupported && <Tag color="warning">No API</Tag>}
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<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 && (
|
2026-03-23 12:41:35 +08:00
|
|
|
|
<Tabs
|
|
|
|
|
|
defaultActiveKey="info"
|
|
|
|
|
|
items={[
|
|
|
|
|
|
{ key: 'info', label: '基本信息', children: (
|
|
|
|
|
|
<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>
|
|
|
|
|
|
)},
|
|
|
|
|
|
{ key: 'shops', label: '店铺分布', children: (
|
|
|
|
|
|
<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>{info.shopInfo.region}</Tag>
|
|
|
|
|
|
{!info.shopInfo.apiSupported && <Tag 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>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)},
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</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}
|
2026-03-20 17:53:46 +08:00
|
|
|
|
<Tag>{info.shopInfo.region}</Tag>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</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;
|