import { productcontrollerGetproductlist } from '@/servers/api/product'; import { templatecontrollerGettemplatebyname } from '@/servers/api/template'; import { EditOutlined, SyncOutlined } from '@ant-design/icons'; import { ActionType, ModalForm, ProColumns, ProFormText, ProTable, } from '@ant-design/pro-components'; import { request } from '@umijs/max'; import { Button, Card, Spin, Tag, message, Select, Progress, Modal } from 'antd'; import React, { useEffect, useRef, useState } from 'react'; import EditForm from '../List/EditForm'; // 定义站点接口 interface Site { id: string; name: string; skuPrefix?: string; isDisabled?: boolean; } // 定义WordPress商品接口 interface WpProduct { id?: number; externalProductId?: string; sku: string; name: string; price: string; regular_price?: string; sale_price?: string; stock_quantity: number; stockQuantity?: number; status: string; attributes?: any[]; constitution?: { sku: string; quantity: number }[]; } // 扩展本地产品接口,包含对应的 WP 产品信息 interface ProductWithWP extends API.Product { wpProducts: Record; attributes?: any[]; siteSkus?: Array<{ siteSku: string; [key: string]: any; }>; } // 定义API响应接口 interface ApiResponse { data: T[]; success: boolean; message?: string; } // 模拟API请求函数 const getSites = async (): Promise> => { const res = await request('/site/list', { method: 'GET', params: { current: 1, pageSize: 1000, }, }); return { data: res.data?.items || [], success: res.success, message: res.message, }; }; const getWPProducts = async (): Promise> => { return request('/product/wp-products', { method: 'GET', }); }; const ProductSyncPage: React.FC = () => { const [sites, setSites] = useState([]); // 存储所有 WP 产品,用于查找匹配。 Key: SKU (包含前缀) const [wpProductMap, setWpProductMap] = useState>( new Map(), ); const [skuTemplate, setSkuTemplate] = useState(''); const [initialLoading, setInitialLoading] = useState(true); const actionRef = useRef(); const [selectedSiteId, setSelectedSiteId] = useState(''); const [batchSyncModalVisible, setBatchSyncModalVisible] = useState(false); const [syncProgress, setSyncProgress] = useState(0); const [syncing, setSyncing] = useState(false); const [syncResults, setSyncResults] = useState<{ success: number; failed: number; errors: string[] }>({ success: 0, failed: 0, errors: [] }); // 初始化数据:获取站点和所有 WP 产品 useEffect(() => { const fetchData = async () => { try { setInitialLoading(true); // 获取所有站点 const sitesResponse = await getSites(); const rawSiteList = sitesResponse.data || []; // 过滤掉已禁用的站点 const siteList: Site[] = rawSiteList.filter((site) => !site.isDisabled); setSites(siteList); // 获取所有 WordPress 商品 const wpProductsResponse = await getWPProducts(); const wpProductList: WpProduct[] = wpProductsResponse.data || []; // 构建 WP 产品 Map,Key 为 SKU const map = new Map(); wpProductList.forEach((p) => { if (p.sku) { map.set(p.sku, p); } }); setWpProductMap(map); // 获取 SKU 模板 try { const templateRes = await templatecontrollerGettemplatebyname({ name: 'site.product.sku', }); if (templateRes && templateRes.value) { setSkuTemplate(templateRes.value); } } catch (e) { console.log('Template site.product.sku not found, using default.'); } } catch (error) { message.error('获取基础数据失败,请重试'); console.error('Error fetching data:', error); } finally { setInitialLoading(false); } }; fetchData(); }, []); // 同步产品到站点 const syncProductToSite = async ( values: any, record: ProductWithWP, site: Site, wpProductId?: string, ) => { try { const hide = message.loading('正在同步...', 0); const data = { name: record.name, sku: values.sku, regular_price: record.price?.toString(), sale_price: record.promotionPrice?.toString(), type: record.type === 'bundle' ? 'simple' : record.type, description: record.description, status: 'publish', stock_status: 'instock', manage_stock: false, }; let res; if (wpProductId) { res = await request(`/site-api/${site.id}/products/${wpProductId}`, { method: 'PUT', data, }); } else { res = await request(`/site-api/${site.id}/products`, { method: 'POST', data, }); } console.log('res', res); if (!res.success) { hide(); throw new Error(res.message || '同步失败'); } // 更新本地缓存 Map,避免刷新 setWpProductMap((prev) => { const newMap = new Map(prev); if (res.data && typeof res.data === 'object') { newMap.set(values.sku, res.data as WpProduct); } return newMap; }); hide(); message.success('同步成功'); return true; } catch (error: any) { message.error('同步失败: ' + (error.message || error.toString())); return false; } finally { } }; // 批量同步产品到指定站点 const batchSyncProducts = async () => { if (!selectedSiteId) { message.error('请选择要同步到的站点'); return; } const targetSite = sites.find(site => site.id === selectedSiteId); if (!targetSite) { message.error('选择的站点不存在'); return; } setSyncing(true); setSyncProgress(0); setSyncResults({ success: 0, failed: 0, errors: [] }); try { // 获取所有产品 const { data, success } = await productcontrollerGetproductlist({ current: 1, pageSize: 10000, // 获取所有产品 } as any); if (!success || !data?.items) { message.error('获取产品列表失败'); return; } const products = data.items as ProductWithWP[]; const totalProducts = products.length; let processed = 0; let successCount = 0; let failedCount = 0; const errors: string[] = []; // 逐个同步产品 for (const product of products) { try { // 获取该产品在目标站点的SKU let siteProductSku = ''; if (product.siteSkus && product.siteSkus.length > 0) { const siteSkuInfo = product.siteSkus.find((sku: any) => { return sku.siteSku && sku.siteSku.includes(targetSite.skuPrefix || targetSite.name); }); if (siteSkuInfo) { siteProductSku = siteSkuInfo.siteSku; } } // 如果没有找到实际的siteSku,则根据模板生成 const expectedSku = siteProductSku || ( skuTemplate ? renderSku(skuTemplate, { site: targetSite, product }) : `${targetSite.skuPrefix || ''}-${product.sku}` ); // 检查是否已存在 const existingProduct = wpProductMap.get(expectedSku); // 准备同步数据 const syncData = { name: product.name, sku: expectedSku, regular_price: product.price?.toString(), sale_price: product.promotionPrice?.toString(), type: product.type === 'bundle' ? 'simple' : product.type, description: product.description, status: 'publish', stock_status: 'instock', manage_stock: false, }; let res; if (existingProduct?.externalProductId) { // 更新现有产品 res = await request(`/site-api/${targetSite.id}/products/${existingProduct.externalProductId}`, { method: 'PUT', data: syncData, }); } else { // 创建新产品 res = await request(`/site-api/${targetSite.id}/products`, { method: 'POST', data: syncData, }); } console.log('res', res); if (res.success) { successCount++; // 更新本地缓存 setWpProductMap((prev) => { const newMap = new Map(prev); if (res.data && typeof res.data === 'object') { newMap.set(expectedSku, res.data as WpProduct); } return newMap; }); } else { failedCount++; errors.push(`产品 ${product.sku}: ${res.message || '同步失败'}`); } } catch (error: any) { failedCount++; errors.push(`产品 ${product.sku}: ${error.message || '未知错误'}`); } processed++; setSyncProgress(Math.round((processed / totalProducts) * 100)); } setSyncResults({ success: successCount, failed: failedCount, errors }); if (failedCount === 0) { message.success(`批量同步完成,成功同步 ${successCount} 个产品`); } else { message.warning(`批量同步完成,成功 ${successCount} 个,失败 ${failedCount} 个`); } // 刷新表格 actionRef.current?.reload(); } catch (error: any) { message.error('批量同步失败: ' + (error.message || error.toString())); } finally { setSyncing(false); } }; // 简单的模板渲染函数 const renderSku = (template: string, data: any) => { if (!template) return ''; // 支持 <%= it.path %> (Eta) 和 {{ path }} (Mustache/Handlebars) return template.replace( /<%=\s*it\.([\w.]+)\s*%>|\{\{\s*([\w.]+)\s*\}\}/g, (_, p1, p2) => { const path = p1 || p2; const keys = path.split('.'); let value = data; for (const key of keys) { value = value?.[key]; } return value === undefined || value === null ? '' : String(value); }, ); }; // 生成表格列配置 const generateColumns = (): ProColumns[] => { const columns: ProColumns[] = [ { title: 'SKU', dataIndex: 'sku', key: 'sku', width: 150, fixed: 'left', copyable: true, }, { title: '商品信息', key: 'profile', width: 300, fixed: 'left', render: (_, record) => (
{record.name}
} />
价格: {record.price} {record.promotionPrice && ( 促销价: {record.promotionPrice} )}
{/* 属性 */}
{record.attributes?.map((attr: any, idx: number) => ( {attr.dict?.name || attr.name}: {attr.name} ))}
{/* 组成 (如果是 Bundle) */} {record.type === 'bundle' && record.components && record.components.length > 0 && (
Components:
{record.components.map((comp: any, idx: number) => (
{comp.sku} × {comp.quantity}
))}
)}
), }, ]; // 为每个站点生成列 sites.forEach((site: Site) => { const siteColumn: ProColumns = { title: site.name, key: `site_${site.id}`, hideInSearch: true, width: 220, render: (_, record) => { // 首先查找该产品在该站点的实际SKU let siteProductSku = ''; if (record.siteSkus && record.siteSkus.length > 0) { // 根据站点名称匹配对应的siteSku const siteSkuInfo = record.siteSkus.find((sku: any) => { // 这里假设可以根据站点名称或其他标识来匹配 // 如果需要更精确的匹配逻辑,可以根据实际需求调整 return sku.siteSku && sku.siteSku.includes(site.skuPrefix || site.name); }); if (siteSkuInfo) { siteProductSku = siteSkuInfo.siteSku; } } // 如果没有找到实际的siteSku,则根据模板或默认规则生成期望的SKU const expectedSku = siteProductSku || ( skuTemplate ? renderSku(skuTemplate, { site, product: record }) : `${site.skuPrefix || ''}-${record.sku}` ); // 尝试用确定的SKU获取WP产品 let wpProduct = wpProductMap.get(expectedSku); // 如果根据实际SKU没找到,再尝试用模板生成的SKU查找 if (!wpProduct && siteProductSku && skuTemplate) { const templateSku = renderSku(skuTemplate, { site, product: record }); wpProduct = wpProductMap.get(templateSku); } if (!wpProduct) { return ( }> 同步到站点 } width={400} onFinish={async (values) => { return await syncProductToSite(values, record, site); }} initialValues={{ sku: siteProductSku || ( skuTemplate ? renderSku(skuTemplate, { site, product: record }) : `${site.skuPrefix || ''}-${record.sku}` ), }} > ); } return (
{wpProduct.sku}
} > } width={400} onFinish={async (values) => { return await syncProductToSite( values, record, site, wpProduct.externalProductId, ); }} initialValues={{ sku: wpProduct.sku, }} >
确定要将本地产品数据更新到站点吗?
Price: {wpProduct.regular_price ?? wpProduct.price}
{wpProduct.sale_price && (
Sale: {wpProduct.sale_price}
)}
Stock: {wpProduct.stock_quantity ?? wpProduct.stockQuantity}
Status:{' '} {wpProduct.status === 'publish' ? ( Published ) : ( {wpProduct.status} )}
); }, }; columns.push(siteColumn); }); return columns; }; if (initialLoading) { return ( ); } return (