462 lines
12 KiB
TypeScript
462 lines
12 KiB
TypeScript
import { productcontrollerGetproductlist } from '@/servers/api/product';
|
||
import {
|
||
siteapicontrollerGetproducts,
|
||
siteapicontrollerUpsertproduct,
|
||
} from '@/servers/api/siteApi';
|
||
import { templatecontrollerRendertemplate } from '@/servers/api/template';
|
||
import { SyncOutlined } from '@ant-design/icons';
|
||
import { ModalForm, ProFormText } from '@ant-design/pro-components';
|
||
import { Button, message, Spin, Tag } from 'antd';
|
||
import React, { useEffect, useState } from 'react';
|
||
|
||
// 定义站点接口
|
||
interface Site {
|
||
id: number;
|
||
name: string;
|
||
skuPrefix?: string;
|
||
isDisabled?: boolean;
|
||
}
|
||
|
||
// 定义本地产品接口(与后端 Product 实体匹配)
|
||
interface SiteProduct {
|
||
id: number;
|
||
sku: string;
|
||
name: string;
|
||
nameCn: string;
|
||
shortDescription?: string;
|
||
description?: string;
|
||
price: number;
|
||
promotionPrice: number;
|
||
type: string;
|
||
categoryId?: number;
|
||
category?: any;
|
||
attributes?: any[];
|
||
components?: any[];
|
||
siteSkus: string[];
|
||
source: number;
|
||
createdAt: Date;
|
||
updatedAt: Date;
|
||
}
|
||
|
||
// 定义本地产品完整接口
|
||
interface LocalProduct {
|
||
id: number;
|
||
sku: string;
|
||
name: string;
|
||
nameCn: string;
|
||
shortDescription?: string;
|
||
description?: string;
|
||
price: number;
|
||
promotionPrice: number;
|
||
type: string;
|
||
categoryId?: number;
|
||
category?: any;
|
||
attributes?: any[];
|
||
components?: any[];
|
||
siteSkus: string[];
|
||
source: number;
|
||
images?: string[];
|
||
weight?: number;
|
||
dimensions?: any;
|
||
}
|
||
|
||
// 定义站点产品数据接口
|
||
interface SiteProductData {
|
||
sku: string;
|
||
regular_price?: number;
|
||
price?: number;
|
||
sale_price?: number;
|
||
stock_quantity?: number;
|
||
stockQuantity?: number;
|
||
status?: string;
|
||
externalProductId?: string;
|
||
name?: string;
|
||
description?: string;
|
||
images?: string[];
|
||
}
|
||
|
||
interface SiteProductCellProps {
|
||
// 产品行数据
|
||
product: SiteProduct;
|
||
// 站点列数据
|
||
site: Site;
|
||
// 同步成功后的回调
|
||
onSyncSuccess?: () => void;
|
||
}
|
||
|
||
const SiteProductCell: React.FC<SiteProductCellProps> = ({
|
||
product,
|
||
site,
|
||
onSyncSuccess,
|
||
}) => {
|
||
// 存储该站点对应的产品数据
|
||
const [siteProduct, setSiteProduct] = useState<SiteProductData | null>(null);
|
||
// 存储本地产品完整数据
|
||
const [localProduct, setLocalProduct] = useState<LocalProduct | null>(null);
|
||
// 加载状态
|
||
const [loading, setLoading] = useState(false);
|
||
// 是否已加载过数据
|
||
const [loaded, setLoaded] = useState(false);
|
||
// 同步中状态
|
||
const [syncing, setSyncing] = useState(false);
|
||
|
||
// 组件挂载时加载数据
|
||
useEffect(() => {
|
||
loadSiteProduct();
|
||
}, [product.id, site.id]);
|
||
|
||
// 加载站点产品数据
|
||
const loadSiteProduct = async () => {
|
||
// 如果已经加载过,则不再重复加载
|
||
if (loaded) {
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
try {
|
||
// 首先查找该产品在该站点的实际SKU
|
||
// 注意:siteSkus 现在是字符串数组,无法直接匹配站点
|
||
// 这里使用模板生成的 SKU 作为默认值
|
||
let siteProductSku = '';
|
||
// 如果需要更精确的站点 SKU 匹配,需要后端提供额外的接口
|
||
|
||
// 如果没有找到实际的siteSku,则根据模板或默认规则生成期望的SKU
|
||
const expectedSku =
|
||
siteProductSku || `${site.skuPrefix || ''}-${product.sku}`;
|
||
|
||
// 使用 siteapicontrollerGetproducts 获取该站点的所有产品
|
||
const productsRes = await siteapicontrollerGetproducts({
|
||
siteId: site.id,
|
||
current: 1,
|
||
pageSize: 10000,
|
||
} as any);
|
||
|
||
if (productsRes.data?.items) {
|
||
// 在该站点的产品数据中查找匹配的产品
|
||
let foundProduct = productsRes.data.items.find(
|
||
(item: any) => item.sku === expectedSku,
|
||
);
|
||
|
||
// 如果根据实际SKU没找到,再尝试用模板生成的SKU查找
|
||
if (!foundProduct && siteProductSku) {
|
||
foundProduct = productsRes.data.items.find(
|
||
(item: any) => item.sku === siteProductSku,
|
||
);
|
||
}
|
||
|
||
if (foundProduct) {
|
||
setSiteProduct(foundProduct as SiteProductData);
|
||
}
|
||
}
|
||
|
||
// 标记为已加载
|
||
setLoaded(true);
|
||
} catch (error) {
|
||
console.error(`加载站点 ${site.name} 的产品数据失败:`, error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
// 获取本地产品完整信息
|
||
const getLocalProduct = async (): Promise<LocalProduct | null> => {
|
||
try {
|
||
// 如果已经有本地产品数据,直接返回
|
||
if (localProduct) {
|
||
return localProduct;
|
||
}
|
||
|
||
// 使用 productcontrollerGetproductlist 获取本地产品完整信息
|
||
const res = await productcontrollerGetproductlist({
|
||
where: {
|
||
id: product.id,
|
||
},
|
||
} as any);
|
||
|
||
if (res.success && res.data) {
|
||
const productData = res.data as LocalProduct;
|
||
setLocalProduct(productData);
|
||
return productData;
|
||
}
|
||
|
||
return null;
|
||
} catch (error) {
|
||
console.error('获取本地产品信息失败:', error);
|
||
return null;
|
||
}
|
||
};
|
||
|
||
// 渲染站点SKU
|
||
const renderSiteSku = async (data: any): Promise<string> => {
|
||
try {
|
||
// 使用 templatecontrollerRendertemplate API 渲染模板
|
||
const res = await templatecontrollerRendertemplate(
|
||
{ name: 'siteproduct-sku' } as any,
|
||
data,
|
||
);
|
||
|
||
return res?.template || res?.result || '';
|
||
} catch (error) {
|
||
console.error('渲染SKU模板失败:', error);
|
||
return '';
|
||
}
|
||
};
|
||
|
||
// 同步产品到站点
|
||
const syncProductToSite = async (values: any) => {
|
||
try {
|
||
setSyncing(true);
|
||
const hide = message.loading('正在同步...', 0);
|
||
|
||
// 获取本地产品完整信息
|
||
const productDetail = await getLocalProduct();
|
||
|
||
if (!productDetail) {
|
||
hide();
|
||
message.error('获取本地产品信息失败');
|
||
return false;
|
||
}
|
||
|
||
// 构造要同步的产品数据
|
||
const productData: any = {
|
||
sku: values.sku,
|
||
name: productDetail.name,
|
||
description: productDetail.description || '',
|
||
regular_price: productDetail.price,
|
||
price: productDetail.price,
|
||
stock_quantity: productDetail.stock,
|
||
status: 'publish',
|
||
};
|
||
|
||
// 如果有图片,添加图片信息
|
||
if (productDetail.images && productDetail.images.length > 0) {
|
||
productData.images = productDetail.images;
|
||
}
|
||
|
||
// 如果有重量,添加重量信息
|
||
if (productDetail.weight) {
|
||
productData.weight = productDetail.weight;
|
||
}
|
||
|
||
// 如果有尺寸,添加尺寸信息
|
||
if (productDetail.dimensions) {
|
||
productData.dimensions = productDetail.dimensions;
|
||
}
|
||
|
||
// 使用 siteapicontrollerUpsertproduct API 同步产品到站点
|
||
const res = await siteapicontrollerUpsertproduct(
|
||
{ siteId: site.id } as any,
|
||
productData as any,
|
||
);
|
||
|
||
if (!res.success) {
|
||
hide();
|
||
throw new Error(res.message || '同步失败');
|
||
}
|
||
|
||
// 更新本地状态
|
||
if (res.data && typeof res.data === 'object') {
|
||
setSiteProduct(res.data as SiteProductData);
|
||
}
|
||
|
||
hide();
|
||
message.success('同步成功');
|
||
|
||
// 触发回调
|
||
if (onSyncSuccess) {
|
||
onSyncSuccess();
|
||
}
|
||
|
||
return true;
|
||
} catch (error: any) {
|
||
message.error('同步失败: ' + (error.message || error.toString()));
|
||
return false;
|
||
} finally {
|
||
setSyncing(false);
|
||
}
|
||
};
|
||
|
||
// 更新同步产品到站点
|
||
const updateSyncProduct = async (values: any) => {
|
||
try {
|
||
setSyncing(true);
|
||
const hide = message.loading('正在更新...', 0);
|
||
|
||
// 获取本地产品完整信息
|
||
const productDetail = await getLocalProduct();
|
||
|
||
if (!productDetail) {
|
||
hide();
|
||
message.error('获取本地产品信息失败');
|
||
return false;
|
||
}
|
||
|
||
// 构造要更新的产品数据
|
||
const productData: any = {
|
||
...siteProduct,
|
||
sku: values.sku,
|
||
name: productDetail.name,
|
||
description: productDetail.description || '',
|
||
regular_price: productDetail.price,
|
||
price: productDetail.price,
|
||
stock_quantity: productDetail.stock,
|
||
status: 'publish',
|
||
};
|
||
|
||
// 如果有图片,添加图片信息
|
||
if (productDetail.images && productDetail.images.length > 0) {
|
||
productData.images = productDetail.images;
|
||
}
|
||
|
||
// 如果有重量,添加重量信息
|
||
if (productDetail.weight) {
|
||
productData.weight = productDetail.weight;
|
||
}
|
||
|
||
// 如果有尺寸,添加尺寸信息
|
||
if (productDetail.dimensions) {
|
||
productData.dimensions = productDetail.dimensions;
|
||
}
|
||
|
||
// 使用 siteapicontrollerUpsertproduct API 更新产品到站点
|
||
const res = await siteapicontrollerUpsertproduct(
|
||
{ siteId: site.id } as any,
|
||
productData as any,
|
||
);
|
||
|
||
if (!res.success) {
|
||
hide();
|
||
throw new Error(res.message || '更新失败');
|
||
}
|
||
|
||
// 更新本地状态
|
||
if (res.data && typeof res.data === 'object') {
|
||
setSiteProduct(res.data as SiteProductData);
|
||
}
|
||
|
||
hide();
|
||
message.success('更新成功');
|
||
|
||
// 触发回调
|
||
if (onSyncSuccess) {
|
||
onSyncSuccess();
|
||
}
|
||
|
||
return true;
|
||
} catch (error: any) {
|
||
message.error('更新失败: ' + (error.message || error.toString()));
|
||
return false;
|
||
} finally {
|
||
setSyncing(false);
|
||
}
|
||
};
|
||
|
||
// 如果正在加载,显示加载状态
|
||
if (loading) {
|
||
return (
|
||
<div style={{ textAlign: 'center', padding: 10 }}>
|
||
<Spin size="small" />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 如果没有找到站点产品,显示同步按钮
|
||
if (!siteProduct) {
|
||
// 首先查找该产品在该站点的实际SKU
|
||
// 注意:siteSkus 现在是字符串数组,无法直接匹配站点
|
||
// 这里使用模板生成的 SKU 作为默认值
|
||
let siteProductSku = '';
|
||
// 如果需要更精确的站点 SKU 匹配,需要后端提供额外的接口
|
||
|
||
const defaultSku =
|
||
siteProductSku || `${site.skuPrefix || ''}-${product.sku}`;
|
||
|
||
return (
|
||
<ModalForm
|
||
title="同步产品"
|
||
trigger={
|
||
<Button type="link" icon={<SyncOutlined />}>
|
||
同步到站点
|
||
</Button>
|
||
}
|
||
width={400}
|
||
onFinish={async (values) => {
|
||
return await syncProductToSite(values);
|
||
}}
|
||
initialValues={{
|
||
sku: defaultSku,
|
||
}}
|
||
>
|
||
<ProFormText
|
||
name="sku"
|
||
label="商店 SKU"
|
||
placeholder="请输入商店 SKU"
|
||
rules={[{ required: true, message: '请输入 SKU' }]}
|
||
/>
|
||
</ModalForm>
|
||
);
|
||
}
|
||
|
||
// 显示站点产品信息
|
||
return (
|
||
<div style={{ fontSize: 12 }}>
|
||
<div
|
||
style={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'start',
|
||
}}
|
||
>
|
||
<div style={{ fontWeight: 'bold' }}>{siteProduct.sku}</div>
|
||
<ModalForm
|
||
title="更新同步"
|
||
trigger={
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
icon={<SyncOutlined spin={false} />}
|
||
>
|
||
更新
|
||
</Button>
|
||
}
|
||
width={400}
|
||
onFinish={async (values) => {
|
||
return await updateSyncProduct(values);
|
||
}}
|
||
initialValues={{
|
||
sku: siteProduct.sku,
|
||
}}
|
||
>
|
||
<ProFormText
|
||
name="sku"
|
||
label="商店 SKU"
|
||
placeholder="请输入商店 SKU"
|
||
rules={[{ required: true, message: '请输入 SKU' }]}
|
||
disabled
|
||
/>
|
||
<div style={{ marginBottom: 16, color: '#666' }}>
|
||
确定要将本地产品数据更新到站点吗?
|
||
</div>
|
||
</ModalForm>
|
||
</div>
|
||
<div>Price: {siteProduct.regular_price ?? siteProduct.price}</div>
|
||
{siteProduct.sale_price && (
|
||
<div style={{ color: 'red' }}>Sale: {siteProduct.sale_price}</div>
|
||
)}
|
||
<div>
|
||
Stock: {siteProduct.stock_quantity ?? siteProduct.stockQuantity}
|
||
</div>
|
||
<div style={{ marginTop: 2 }}>
|
||
Status:{' '}
|
||
{siteProduct.status === 'publish' ? (
|
||
<Tag color="green">Published</Tag>
|
||
) : (
|
||
<Tag>{siteProduct.status}</Tag>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default SiteProductCell;
|