import { productcontrollerGetproductlist } from '@/servers/api/product'; import { siteapicontrollerGetproducts } from '@/servers/api/siteApi'; 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, message, Modal, Progress, Select, Spin, Tag, } from 'antd'; import React, { useEffect, useRef, useState } from 'react'; import EditForm from '../List/EditForm'; // 定义站点接口 interface Site { id: string; name: string; skuPrefix?: string; isDisabled?: boolean; } // 定义站点商品接口 interface SiteProduct { id: string; sku: string; name: string; price: number; stock: number; categories: string[]; } // 定义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 getsiteProduct = async (): Promise> => { return request('/product/wp-products', { method: 'GET', }); }; const ProductSyncPage: React.FC = () => { const [sites, setSites] = useState([]); // 存储所有 WP 产品,用于查找匹配。 Key: SKU (包含前缀) const [siteProductMap, setSiteProductMap] = 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: [] }); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selectedRows, setSelectedRows] = useState([]); // 初始化数据:获取站点和所有 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 siteProductsResponse = await siteapicontrollerGetproducts({ siteId: selectedSiteId, }); const siteProductList: SiteProduct[] = siteProductsResponse.data || []; // 构建 WP 产品 Map,Key 为 SKU const map = new Map(); siteProductList.forEach((p) => { if (p.sku) { map.set(p.sku, p); } }); setSiteProductMap(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, siteProductId?: 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 (siteProductId) { res = await request(`/site-api/${site.id}/products/${siteProductId}`, { 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,避免刷新 setSiteProductMap((prev) => { const newMap = new Map(prev); if (res.data && typeof res.data === 'object') { newMap.set(values.sku, res.data as SiteProduct); } return newMap; }); hide(); message.success('同步成功'); return true; } catch (error: any) { message.error('同步失败: ' + (error.message || error.toString())); return false; } finally { } }; // 批量同步产品到指定站点 const batchSyncProducts = async (productsToSync?: ProductWithWP[]) => { if (!selectedSiteId) { message.error('请选择要同步到的站点'); return; } const targetSite = sites.find((site) => site.id === selectedSiteId); if (!targetSite) { message.error('选择的站点不存在'); return; } // 如果没有传入产品列表,则使用选中的产品 let products = productsToSync || selectedRows; // 如果既没有传入产品也没有选中产品,则同步所有产品 if (!products || products.length === 0) { try { const { data, success } = await productcontrollerGetproductlist({ current: 1, pageSize: 10000, // 获取所有产品 } as any); if (!success || !data?.items) { message.error('获取产品列表失败'); return; } products = data.items as ProductWithWP[]; } catch (error) { message.error('获取产品列表失败'); return; } } setSyncing(true); setSyncProgress(0); setSyncResults({ success: 0, failed: 0, errors: [] }); const totalProducts = products.length; let processed = 0; let successCount = 0; let failedCount = 0; const errors: string[] = []; try { // 逐个同步产品 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 ? renderSiteSku(skuTemplate, { site: targetSite, product }) : `${targetSite.skuPrefix || ''}-${product.sku}`); // 检查是否已存在 const existingProduct = siteProductMap.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++; // 更新本地缓存 setSiteProductMap((prev) => { const newMap = new Map(prev); if (res.data && typeof res.data === 'object') { newMap.set(expectedSku, res.data as SiteProduct); } 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 renderSiteSku = (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 ? renderSiteSku(skuTemplate, { site, product: record }) : `${site.skuPrefix || ''}-${record.sku}`); // 尝试用确定的SKU获取WP产品 let siteProduct = siteProductMap.get(expectedSku); // 如果根据实际SKU没找到,再尝试用模板生成的SKU查找 if (!siteProduct && siteProductSku && skuTemplate) { const templateSku = renderSiteSku(skuTemplate, { site, product: record, }); siteProduct = siteProductMap.get(templateSku); } if (!siteProduct) { return ( }> 同步到站点 } width={400} onFinish={async (values) => { return await syncProductToSite(values, record, site); }} initialValues={{ sku: siteProductSku || (skuTemplate ? renderSiteSku(skuTemplate, { site, product: record }) : `${site.skuPrefix || ''}-${record.sku}`), }} > ); } return (
{siteProduct.sku}
} > } width={400} onFinish={async (values) => { return await syncProductToSite( values, record, site, siteProduct.externalProductId, ); }} initialValues={{ sku: siteProduct.sku, }} >
确定要将本地产品数据更新到站点吗?
Price: {siteProduct.regular_price ?? siteProduct.price}
{siteProduct.sale_price && (
Sale: {siteProduct.sale_price}
)}
Stock: {siteProduct.stock_quantity ?? siteProduct.stockQuantity}
Status:{' '} {siteProduct.status === 'publish' ? ( Published ) : ( {siteProduct.status} )}
); }, }; columns.push(siteColumn); }); return columns; }; if (initialLoading) { return ( ); } return ( columns={generateColumns()} actionRef={actionRef} rowKey="id" rowSelection={{ selectedRowKeys, onChange: (keys, rows) => { setSelectedRowKeys(keys); setSelectedRows(rows); }, }} toolBarRender={() => [