808 lines
24 KiB
TypeScript
808 lines
24 KiB
TypeScript
import {
|
||
productcontrollerBatchdeleteproduct,
|
||
productcontrollerBatchupdateproduct,
|
||
productcontrollerBindproductsiteskus,
|
||
productcontrollerDeleteproduct,
|
||
productcontrollerGetcategoriesall,
|
||
productcontrollerGetproductcomponents,
|
||
productcontrollerGetproductlist,
|
||
productcontrollerUpdatenamecn,
|
||
} from '@/servers/api/product';
|
||
import { sitecontrollerAll } from '@/servers/api/site';
|
||
import { siteapicontrollerGetproducts } from '@/servers/api/siteApi';
|
||
import {
|
||
wpproductcontrollerBatchsynctosite,
|
||
wpproductcontrollerSynctoproduct,
|
||
} from '@/servers/api/wpProduct';
|
||
import {
|
||
ActionType,
|
||
ModalForm,
|
||
PageContainer,
|
||
ProColumns,
|
||
ProFormSelect,
|
||
ProFormText,
|
||
ProTable,
|
||
} from '@ant-design/pro-components';
|
||
import { request } from '@umijs/max';
|
||
import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd';
|
||
import React, { useEffect, useRef, useState } from 'react';
|
||
import CreateForm from './CreateForm';
|
||
import EditForm from './EditForm';
|
||
|
||
const NameCn: React.FC<{
|
||
id: number;
|
||
value: string | undefined;
|
||
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||
}> = ({ value, tableRef, id }) => {
|
||
const { message } = App.useApp();
|
||
const [editable, setEditable] = React.useState<boolean>(false);
|
||
if (!editable)
|
||
return <div onClick={() => setEditable(true)}>{value || '-'}</div>;
|
||
return (
|
||
<ProFormText
|
||
initialValue={value}
|
||
fieldProps={{
|
||
autoFocus: true,
|
||
onBlur: async (e: React.FocusEvent<HTMLInputElement>) => {
|
||
if (!e.target.value) return setEditable(false);
|
||
const { success, message: errMsg } =
|
||
await productcontrollerUpdatenamecn({
|
||
id,
|
||
nameCn: e.target.value,
|
||
});
|
||
setEditable(false);
|
||
if (!success) {
|
||
return message.error(errMsg);
|
||
}
|
||
tableRef?.current?.reloadAndRest?.();
|
||
},
|
||
}}
|
||
/>
|
||
);
|
||
};
|
||
|
||
const AttributesCell: React.FC<{ record: any }> = ({ record }) => {
|
||
return (
|
||
<div>
|
||
{(record.attributes || []).map((data: any, idx: number) => (
|
||
<Tag key={idx} color="purple" style={{ marginBottom: 4 }}>
|
||
{data?.dict?.name}: {data.name}
|
||
</Tag>
|
||
))}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const ComponentsCell: React.FC<{ productId: number }> = ({ productId }) => {
|
||
const [components, setComponents] = React.useState<any[]>([]);
|
||
React.useEffect(() => {
|
||
(async () => {
|
||
const { data = [] } = await productcontrollerGetproductcomponents({
|
||
id: productId,
|
||
});
|
||
setComponents(data || []);
|
||
})();
|
||
}, [productId]);
|
||
return (
|
||
<div>
|
||
{components && components.length ? (
|
||
components.map((component: any) => (
|
||
<Tag key={component.id} color="blue" style={{ marginBottom: 4 }}>
|
||
{component.sku || `#${component.id}`} × {component.quantity}
|
||
(库存:
|
||
{component.stock
|
||
?.map((s: any) => `${s.name}:${s.quantity}`)
|
||
.join(', ') || '-'}
|
||
)
|
||
</Tag>
|
||
))
|
||
) : (
|
||
<span>-</span>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const BatchEditModal: React.FC<{
|
||
visible: boolean;
|
||
onClose: () => void;
|
||
selectedRows: API.Product[];
|
||
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||
onSuccess: () => void;
|
||
}> = ({ visible, onClose, selectedRows, tableRef, onSuccess }) => {
|
||
const { message } = App.useApp();
|
||
const [categories, setCategories] = useState<any[]>([]);
|
||
|
||
useEffect(() => {
|
||
if (visible) {
|
||
productcontrollerGetcategoriesall().then((res: any) => {
|
||
setCategories(res?.data || []);
|
||
});
|
||
}
|
||
}, [visible]);
|
||
|
||
return (
|
||
<ModalForm
|
||
title={`批量修改 (${selectedRows.length} 项)`}
|
||
open={visible}
|
||
onOpenChange={(open) => !open && onClose()}
|
||
modalProps={{ destroyOnClose: true }}
|
||
onFinish={async (values) => {
|
||
const ids = selectedRows.map((row) => row.id);
|
||
const updateData: any = { ids };
|
||
// 只有当用户输入了值才进行更新
|
||
if (values.price) updateData.price = Number(values.price);
|
||
if (values.promotionPrice)
|
||
updateData.promotionPrice = Number(values.promotionPrice);
|
||
if (values.categoryId) updateData.categoryId = values.categoryId;
|
||
|
||
if (Object.keys(updateData).length <= 1) {
|
||
message.warning('未修改任何属性');
|
||
return false;
|
||
}
|
||
|
||
const { success, message: errMsg } =
|
||
await productcontrollerBatchupdateproduct(updateData);
|
||
if (success) {
|
||
message.success('批量修改成功');
|
||
onSuccess();
|
||
tableRef.current?.reload();
|
||
return true;
|
||
} else {
|
||
message.error(errMsg);
|
||
return false;
|
||
}
|
||
}}
|
||
>
|
||
<ProFormText name="price" label="价格" placeholder="不修改请留空" />
|
||
<ProFormText
|
||
name="promotionPrice"
|
||
label="促销价格"
|
||
placeholder="不修改请留空"
|
||
/>
|
||
<ProFormSelect
|
||
name="categoryId"
|
||
label="分类"
|
||
options={categories.map((c) => ({ label: c.title, value: c.id }))}
|
||
placeholder="不修改请留空"
|
||
/>
|
||
</ModalForm>
|
||
);
|
||
};
|
||
|
||
const SyncToSiteModal: React.FC<{
|
||
visible: boolean;
|
||
onClose: () => void;
|
||
productIds: number[];
|
||
productRows: API.Product[];
|
||
onSuccess: () => void;
|
||
}> = ({ visible, onClose, productIds, productRows, onSuccess }) => {
|
||
const { message } = App.useApp();
|
||
const [sites, setSites] = useState<any[]>([]);
|
||
const formRef = useRef<any>();
|
||
|
||
useEffect(() => {
|
||
if (visible) {
|
||
sitecontrollerAll().then((res: any) => {
|
||
setSites(res?.data || []);
|
||
});
|
||
}
|
||
}, [visible]);
|
||
|
||
return (
|
||
<ModalForm
|
||
title={`同步到站点 (${productIds.length} 项)`}
|
||
open={visible}
|
||
onOpenChange={(open) => !open && onClose()}
|
||
modalProps={{ destroyOnClose: true }}
|
||
formRef={formRef}
|
||
onValuesChange={(changedValues) => {
|
||
if ('siteId' in changedValues && changedValues.siteId) {
|
||
const siteId = changedValues.siteId;
|
||
const site = sites.find((s: any) => s.id === siteId) || {};
|
||
const prefix = site.skuPrefix || '';
|
||
const map: Record<string, any> = {};
|
||
productRows.forEach((p) => {
|
||
map[p.id] = {
|
||
code: `${prefix}${p.sku || ''}`,
|
||
quantity: undefined,
|
||
};
|
||
});
|
||
formRef.current?.setFieldsValue({ productSiteSkus: map });
|
||
}
|
||
}}
|
||
onFinish={async (values) => {
|
||
if (!values.siteId) return false;
|
||
try {
|
||
await wpproductcontrollerBatchsynctosite(
|
||
{ siteId: values.siteId },
|
||
{ productIds },
|
||
);
|
||
const map = values.productSiteSkus || {};
|
||
for (const currentProductId of productIds) {
|
||
const entry = map?.[currentProductId];
|
||
if (entry && entry.code) {
|
||
await productcontrollerBindproductsiteskus(
|
||
{ id: currentProductId },
|
||
{
|
||
siteSkus: [
|
||
{
|
||
siteId: values.siteId,
|
||
code: entry.code,
|
||
quantity: entry.quantity,
|
||
},
|
||
],
|
||
},
|
||
);
|
||
}
|
||
}
|
||
message.success('同步任务已提交');
|
||
onSuccess();
|
||
return true;
|
||
} catch (error: any) {
|
||
message.error(error.message || '同步失败');
|
||
return false;
|
||
}
|
||
}}
|
||
>
|
||
<ProFormSelect
|
||
name="siteId"
|
||
label="选择站点"
|
||
options={sites.map((site) => ({ label: site.name, value: site.id }))}
|
||
rules={[{ required: true, message: '请选择站点' }]}
|
||
/>
|
||
{productRows.map((row) => (
|
||
<div
|
||
key={row.id}
|
||
style={{ display: 'flex', gap: 12, alignItems: 'flex-end' }}
|
||
>
|
||
<div style={{ minWidth: 220 }}>原始SKU: {row.sku || '-'}</div>
|
||
<ProFormText
|
||
name={['productSiteSkus', row.id, 'code']}
|
||
label={`商品 ${row.id} 站点SKU`}
|
||
placeholder="请输入站点SKU"
|
||
/>
|
||
<ProFormText
|
||
name={['productSiteSkus', row.id, 'quantity']}
|
||
label="数量"
|
||
placeholder="请输入数量"
|
||
/>
|
||
</div>
|
||
))}
|
||
</ModalForm>
|
||
);
|
||
};
|
||
|
||
const WpProductInfo: React.FC<{
|
||
skus: string[];
|
||
record: API.Product;
|
||
parentTableRef: React.MutableRefObject<ActionType | undefined>;
|
||
}> = ({ skus, record, parentTableRef }) => {
|
||
const actionRef = useRef<ActionType>();
|
||
const { message } = App.useApp();
|
||
|
||
return (
|
||
<ProTable
|
||
headerTitle="站点产品信息"
|
||
actionRef={actionRef}
|
||
search={false}
|
||
options={false}
|
||
pagination={false}
|
||
toolBarRender={() => [
|
||
<Button
|
||
key="refresh"
|
||
type="primary"
|
||
onClick={() => actionRef.current?.reload()}
|
||
>
|
||
刷新
|
||
</Button>,
|
||
]}
|
||
request={async () => {
|
||
// 判断是否存在站点SKU列表
|
||
if (!skus || skus.length === 0) return { data: [] };
|
||
try {
|
||
// 获取所有站点列表用于遍历查询
|
||
const { data: siteResponse } = await sitecontrollerAll();
|
||
const siteList = siteResponse || [];
|
||
// 聚合所有站点的产品数据
|
||
const aggregatedProducts: any[] = [];
|
||
// 遍历每一个站点
|
||
for (const siteItem of siteList) {
|
||
// 遍历每一个SKU在当前站点进行搜索
|
||
for (const skuCode of skus) {
|
||
// 直接调用站点API根据搜索关键字获取产品列表
|
||
const response = await siteapicontrollerGetproducts({
|
||
siteId: Number(siteItem.id),
|
||
per_page: 100,
|
||
search: skuCode,
|
||
});
|
||
const productPage = response as any;
|
||
const siteProducts = productPage?.data?.items || [];
|
||
// 将站点信息附加到产品数据中便于展示
|
||
siteProducts.forEach((p: any) => {
|
||
aggregatedProducts.push({
|
||
...p,
|
||
siteId: siteItem.id,
|
||
siteName: siteItem.name,
|
||
});
|
||
});
|
||
}
|
||
}
|
||
return { data: aggregatedProducts, success: true };
|
||
} catch (error: any) {
|
||
// 请求失败进行错误提示
|
||
message.error(error?.message || '获取站点产品失败');
|
||
return { data: [], success: false };
|
||
}
|
||
}}
|
||
columns={[
|
||
{
|
||
title: '站点',
|
||
dataIndex: 'siteName',
|
||
},
|
||
{
|
||
title: 'SKU',
|
||
dataIndex: 'sku',
|
||
},
|
||
{
|
||
title: '价格',
|
||
dataIndex: 'regular_price',
|
||
render: (_, row) => (
|
||
<div>
|
||
<div>常规: {row.regular_price}</div>
|
||
<div>促销: {row.sale_price}</div>
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
},
|
||
{
|
||
title: '操作',
|
||
valueType: 'option',
|
||
render: (_, wpRow) => [
|
||
<a
|
||
key="syncToSite"
|
||
onClick={async () => {
|
||
try {
|
||
await wpproductcontrollerBatchsynctosite(
|
||
{ siteId: wpRow.siteId },
|
||
{ productIds: [record.id] },
|
||
);
|
||
message.success('同步到站点成功');
|
||
actionRef.current?.reload();
|
||
} catch (e: any) {
|
||
message.error(e.message || '同步失败');
|
||
}
|
||
}}
|
||
>
|
||
同步到站点
|
||
</a>,
|
||
<a
|
||
key="syncToProduct"
|
||
onClick={async () => {
|
||
try {
|
||
await wpproductcontrollerSynctoproduct({ id: wpRow.id });
|
||
message.success('同步进商品成功');
|
||
parentTableRef.current?.reload();
|
||
} catch (e: any) {
|
||
message.error(e.message || '同步失败');
|
||
}
|
||
}}
|
||
>
|
||
同步进商品
|
||
</a>,
|
||
<Popconfirm
|
||
key="delete"
|
||
title="删除"
|
||
description="确认删除?"
|
||
onConfirm={async () => {
|
||
try {
|
||
await request(`/wp_product/${wpRow.id}`, {
|
||
method: 'DELETE',
|
||
});
|
||
message.success('删除成功');
|
||
actionRef.current?.reload();
|
||
} catch (e: any) {
|
||
message.error(e.message || '删除失败');
|
||
}
|
||
}}
|
||
>
|
||
<a style={{ color: 'red' }}>删除</a>
|
||
</Popconfirm>,
|
||
],
|
||
},
|
||
]}
|
||
/>
|
||
);
|
||
};
|
||
|
||
const List: React.FC = () => {
|
||
const actionRef = useRef<ActionType>();
|
||
// 状态:存储当前选中的行
|
||
const [selectedRows, setSelectedRows] = React.useState<API.Product[]>([]);
|
||
const [batchEditModalVisible, setBatchEditModalVisible] = useState(false);
|
||
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
||
const [syncProductIds, setSyncProductIds] = useState<number[]>([]);
|
||
|
||
const { message } = App.useApp();
|
||
// 导出产品 CSV(带认证请求)
|
||
const handleDownloadProductsCSV = async () => {
|
||
try {
|
||
// 发起认证请求获取 CSV Blob
|
||
const blob = await request('/product/export', { responseType: 'blob' });
|
||
// 构建下载文件名
|
||
const d = new Date();
|
||
const pad = (n: number) => String(n).padStart(2, '0');
|
||
const filename = `products-${d.getFullYear()}${pad(
|
||
d.getMonth() + 1,
|
||
)}${pad(d.getDate())}.csv`;
|
||
// 创建临时链接并触发下载
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = filename;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
a.remove();
|
||
window.URL.revokeObjectURL(url);
|
||
} catch (error) {
|
||
message.error('导出失败');
|
||
}
|
||
};
|
||
const columns: ProColumns<API.Product>[] = [
|
||
{
|
||
title: 'sku',
|
||
dataIndex: 'sku',
|
||
sorter: true,
|
||
},
|
||
{
|
||
title: '商品SKU',
|
||
dataIndex: 'siteSkus',
|
||
render: (_, record) => (
|
||
<>
|
||
{record.siteSkus?.map((code, index) => (
|
||
<Tag key={index} color="cyan">
|
||
{code}
|
||
</Tag>
|
||
))}
|
||
</>
|
||
),
|
||
},
|
||
{
|
||
title: '名称',
|
||
dataIndex: 'name',
|
||
sorter: true,
|
||
},
|
||
{
|
||
title: '中文名',
|
||
dataIndex: 'nameCn',
|
||
render: (_, record) => {
|
||
return (
|
||
<NameCn value={record.nameCn} id={record.id} tableRef={actionRef} />
|
||
);
|
||
},
|
||
},
|
||
|
||
{
|
||
title: '商品类型',
|
||
dataIndex: 'category',
|
||
render: (_, record: any) => {
|
||
return record.category?.title || record.category?.name || '-';
|
||
},
|
||
},
|
||
{
|
||
title: '价格',
|
||
dataIndex: 'price',
|
||
hideInSearch: true,
|
||
sorter: true,
|
||
},
|
||
{
|
||
title: '促销价',
|
||
dataIndex: 'promotionPrice',
|
||
hideInSearch: true,
|
||
sorter: true,
|
||
},
|
||
{
|
||
title: '属性',
|
||
dataIndex: 'attributes',
|
||
hideInSearch: true,
|
||
render: (_, record) => <AttributesCell record={record} />,
|
||
},
|
||
{
|
||
title: '产品类型',
|
||
dataIndex: 'type',
|
||
valueType: 'select',
|
||
valueEnum: {
|
||
single: { text: '单品' },
|
||
bundle: { text: '套装' },
|
||
},
|
||
render: (_, record) => {
|
||
// 如果类型不存在,则返回-
|
||
if (!record.type) return '-';
|
||
// 判断是否为单品
|
||
const isSingle = record.type === 'single';
|
||
// 根据类型显示不同颜色的标签
|
||
return (
|
||
<Tag color={isSingle ? 'green' : 'orange'}>
|
||
{isSingle ? '单品' : '套装'}
|
||
</Tag>
|
||
);
|
||
},
|
||
},
|
||
{
|
||
title: '构成',
|
||
dataIndex: 'components',
|
||
hideInSearch: true,
|
||
render: (_, record) => <ComponentsCell productId={(record as any).id} />,
|
||
},
|
||
|
||
{
|
||
title: '描述',
|
||
dataIndex: 'description',
|
||
hideInSearch: true,
|
||
},
|
||
{
|
||
title: '更新时间',
|
||
dataIndex: 'updatedAt',
|
||
valueType: 'dateTime',
|
||
hideInSearch: true,
|
||
sorter: true,
|
||
},
|
||
{
|
||
title: '创建时间',
|
||
dataIndex: 'createdAt',
|
||
valueType: 'dateTime',
|
||
hideInSearch: true,
|
||
sorter: true,
|
||
},
|
||
{
|
||
title: '操作',
|
||
dataIndex: 'option',
|
||
valueType: 'option',
|
||
fixed: 'right',
|
||
render: (_, record) => (
|
||
<>
|
||
<EditForm record={record} tableRef={actionRef} />
|
||
<Button
|
||
type="link"
|
||
onClick={() => {
|
||
setSyncProductIds([record.id]);
|
||
setSyncModalVisible(true);
|
||
}}
|
||
>
|
||
同步到站点
|
||
</Button>
|
||
<Popconfirm
|
||
title="删除"
|
||
description="确认删除?"
|
||
onConfirm={async () => {
|
||
try {
|
||
const { success, message: errMsg } =
|
||
await productcontrollerDeleteproduct({ id: record.id });
|
||
if (!success) {
|
||
throw new Error(errMsg);
|
||
}
|
||
actionRef.current?.reload();
|
||
} catch (error: any) {
|
||
message.error(error.message);
|
||
}
|
||
}}
|
||
>
|
||
<Button type="link" danger>
|
||
删除
|
||
</Button>
|
||
</Popconfirm>
|
||
</>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<PageContainer header={{ title: '产品列表' }}>
|
||
<ProTable<API.Product>
|
||
scroll={{ x: 'max-content' }}
|
||
headerTitle="查询表格"
|
||
actionRef={actionRef}
|
||
rowKey="id"
|
||
toolBarRender={() => [
|
||
// 新建按钮
|
||
<CreateForm tableRef={actionRef} />,
|
||
// 批量编辑按钮
|
||
<Button
|
||
disabled={selectedRows.length <= 0}
|
||
onClick={() => setBatchEditModalVisible(true)}
|
||
>
|
||
批量修改
|
||
</Button>,
|
||
// 批量同步按钮
|
||
<Button
|
||
disabled={selectedRows.length <= 0}
|
||
onClick={() => {
|
||
setSyncProductIds(selectedRows.map((row) => row.id));
|
||
setSyncModalVisible(true);
|
||
}}
|
||
>
|
||
批量同步到站点
|
||
</Button>,
|
||
// 批量删除按钮
|
||
<Button
|
||
danger
|
||
disabled={selectedRows.length <= 0}
|
||
onClick={() => {
|
||
Modal.confirm({
|
||
title: '确认删除',
|
||
content: `确定要删除选中的 ${selectedRows.length} 个产品吗?此操作不可恢复。`,
|
||
onOk: async () => {
|
||
try {
|
||
const { success, message: errMsg } =
|
||
await productcontrollerBatchdeleteproduct({
|
||
ids: selectedRows.map((row) => row.id),
|
||
});
|
||
if (success) {
|
||
message.success('批量删除成功');
|
||
setSelectedRows([]);
|
||
actionRef.current?.reload();
|
||
} else {
|
||
message.error(errMsg || '删除失败');
|
||
}
|
||
} catch (error: any) {
|
||
message.error(error.message || '删除失败');
|
||
}
|
||
},
|
||
});
|
||
}}
|
||
>
|
||
批量删除
|
||
</Button>,
|
||
// 导出 CSV(后端返回 text/csv,直接新窗口下载)
|
||
<Button onClick={handleDownloadProductsCSV}>导出CSV</Button>,
|
||
// 导入 CSV(使用 customRequest 以支持 request 拦截器和鉴权)
|
||
<Upload
|
||
name="file"
|
||
accept=".csv"
|
||
showUploadList={false}
|
||
maxCount={1}
|
||
customRequest={async (options) => {
|
||
const { file, onSuccess, onError } = options;
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
try {
|
||
const res = await request('/product/import', {
|
||
method: 'POST',
|
||
data: formData,
|
||
requestType: 'form',
|
||
});
|
||
|
||
const {
|
||
created = 0,
|
||
updated = 0,
|
||
errors = [],
|
||
} = res.data || {};
|
||
|
||
if (errors && errors.length > 0) {
|
||
Modal.warning({
|
||
title: '导入结果 (存在错误)',
|
||
width: 600,
|
||
content: (
|
||
<div>
|
||
<p>创建成功: {created}</p>
|
||
<p>更新成功: {updated}</p>
|
||
<p style={{ color: 'red', fontWeight: 'bold' }}>
|
||
失败数量: {errors.length}
|
||
</p>
|
||
<div
|
||
style={{
|
||
maxHeight: '300px',
|
||
overflowY: 'auto',
|
||
background: '#f5f5f5',
|
||
padding: '8px',
|
||
marginTop: '8px',
|
||
borderRadius: '4px',
|
||
border: '1px solid #d9d9d9',
|
||
}}
|
||
>
|
||
{errors.map((err: string, idx: number) => (
|
||
<div
|
||
key={idx}
|
||
style={{
|
||
fontSize: '12px',
|
||
marginBottom: '4px',
|
||
borderBottom: '1px solid #e8e8e8',
|
||
paddingBottom: '2px',
|
||
color: '#ff4d4f',
|
||
}}
|
||
>
|
||
{idx + 1}. {err}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
),
|
||
});
|
||
} else {
|
||
message.success(`导入成功: 创建 ${created}, 更新 ${updated}`);
|
||
}
|
||
|
||
onSuccess?.('ok');
|
||
actionRef.current?.reload();
|
||
} catch (error: any) {
|
||
message.error('导入失败: ' + (error.message || '未知错误'));
|
||
onError?.(error);
|
||
}
|
||
}}
|
||
>
|
||
<Button>导入CSV</Button>
|
||
</Upload>,
|
||
]}
|
||
request={async (params, sort) => {
|
||
let sortField = undefined;
|
||
let sortOrder = undefined;
|
||
|
||
if (sort && Object.keys(sort).length > 0) {
|
||
const field = Object.keys(sort)[0];
|
||
sortField = field;
|
||
sortOrder = sort[field];
|
||
}
|
||
|
||
const { data, success } = await productcontrollerGetproductlist({
|
||
...params,
|
||
sortField,
|
||
sortOrder,
|
||
} as any);
|
||
return {
|
||
total: data?.total || 0,
|
||
data: data?.items || [],
|
||
success,
|
||
};
|
||
}}
|
||
columns={columns}
|
||
expandable={{
|
||
expandedRowRender: (record) => (
|
||
<WpProductInfo
|
||
skus={(record.siteSkus as string[]) || []}
|
||
record={record}
|
||
parentTableRef={actionRef}
|
||
/>
|
||
),
|
||
rowExpandable: (record) =>
|
||
!!(record.siteSkus && record.siteSkus.length > 0),
|
||
}}
|
||
editable={{
|
||
type: 'single',
|
||
onSave: async (key, record, originRow) => {
|
||
console.log('保存数据:', record);
|
||
},
|
||
}}
|
||
rowSelection={{
|
||
onChange: (_, selectedRows) => setSelectedRows(selectedRows),
|
||
}}
|
||
/>
|
||
<BatchEditModal
|
||
visible={batchEditModalVisible}
|
||
onClose={() => setBatchEditModalVisible(false)}
|
||
selectedRows={selectedRows}
|
||
tableRef={actionRef}
|
||
onSuccess={() => {
|
||
setBatchEditModalVisible(false);
|
||
setSelectedRows([]);
|
||
}}
|
||
/>
|
||
<SyncToSiteModal
|
||
visible={syncModalVisible}
|
||
onClose={() => setSyncModalVisible(false)}
|
||
productIds={syncProductIds}
|
||
productRows={selectedRows}
|
||
onSuccess={() => {
|
||
setSyncModalVisible(false);
|
||
setSelectedRows([]);
|
||
actionRef.current?.reload();
|
||
}}
|
||
/>
|
||
</PageContainer>
|
||
);
|
||
};
|
||
|
||
export default List;
|