Compare commits
No commits in common. "bb907fa47ca4b3850cddc9ba1e0255e896a4ea2f" and "e23b0b3e3913f289bfd90a3f143c65b6012ea403" have entirely different histories.
bb907fa47c
...
e23b0b3e39
|
|
@ -1,92 +0,0 @@
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { sitecontrollerAll } from '@/servers/api/site';
|
|
||||||
|
|
||||||
// 站点数据的类型定义
|
|
||||||
interface Site {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义 Hook:管理站点数据
|
|
||||||
const useSites = () => {
|
|
||||||
// 添加站点数据状态
|
|
||||||
const [sites, setSites] = useState<Site[]>([]);
|
|
||||||
|
|
||||||
// 添加加载状态
|
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
|
||||||
|
|
||||||
// 添加错误状态
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// 获取站点数据
|
|
||||||
const fetchSites = async () => {
|
|
||||||
// 设置加载状态为 true
|
|
||||||
setLoading(true);
|
|
||||||
// 清空之前的错误信息
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
// 调用 API 获取所有站点数据
|
|
||||||
const { data, success } = await sitecontrollerAll();
|
|
||||||
// 判断请求是否成功
|
|
||||||
if (success) {
|
|
||||||
// 将站点数据保存到状态中
|
|
||||||
setSites(data || []);
|
|
||||||
} else {
|
|
||||||
// 如果请求失败,设置错误信息
|
|
||||||
setError('获取站点数据失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 捕获异常并打印错误日志
|
|
||||||
console.error('获取站点数据失败:', error);
|
|
||||||
// 设置错误信息
|
|
||||||
setError('获取站点数据时发生错误');
|
|
||||||
} finally {
|
|
||||||
// 无论成功与否,都将加载状态设置为 false
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据站点ID获取站点名称
|
|
||||||
const getSiteName = (siteId: number | undefined | null) => {
|
|
||||||
// 如果站点ID不存在,返回默认值
|
|
||||||
if (!siteId) return '-';
|
|
||||||
// 如果站点ID是字符串类型,直接返回
|
|
||||||
if (typeof siteId === 'string') {
|
|
||||||
return siteId;
|
|
||||||
}
|
|
||||||
// 在站点列表中查找对应的站点
|
|
||||||
const site = sites.find((s) => s.id === siteId);
|
|
||||||
// 如果找到站点,返回站点名称;否则返回站点ID的字符串形式
|
|
||||||
return site ? site.name : String(siteId);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据站点ID获取站点对象
|
|
||||||
const getSiteById = (siteId: number | undefined | null) => {
|
|
||||||
// 如果站点ID不存在,返回 null
|
|
||||||
if (!siteId) return null;
|
|
||||||
// 在站点列表中查找对应的站点
|
|
||||||
const site = sites.find((s) => s.id === siteId);
|
|
||||||
// 返回找到的站点对象,如果找不到则返回 null
|
|
||||||
return site || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 组件加载时获取站点数据
|
|
||||||
useEffect(() => {
|
|
||||||
// 调用获取站点数据的函数
|
|
||||||
fetchSites();
|
|
||||||
}, []); // 空依赖数组表示只在组件挂载时执行一次
|
|
||||||
|
|
||||||
// 返回站点数据和相关方法
|
|
||||||
return {
|
|
||||||
sites, // 站点数据列表
|
|
||||||
loading, // 加载状态
|
|
||||||
error, // 错误信息
|
|
||||||
fetchSites, // 重新获取站点数据的方法
|
|
||||||
getSiteName, // 根据ID获取站点名称的方法
|
|
||||||
getSiteById, // 根据ID获取站点对象的方法
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 导出 useSites Hook
|
|
||||||
export default useSites;
|
|
||||||
|
|
@ -241,7 +241,7 @@ const HistoryOrders: React.FC<HistoryOrdersProps> = ({ customer, siteId }) => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
}}
|
}}
|
||||||
scroll={{ x: 800 }}
|
scroll={{ x: 800 }}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,34 @@ const CustomerList: React.FC = () => {
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
||||||
|
const [sites, setSites] = useState<any[]>([]); // 添加站点数据状态
|
||||||
|
|
||||||
|
// 获取站点数据
|
||||||
|
const fetchSites = async () => {
|
||||||
|
try {
|
||||||
|
const { data, success } = await sitecontrollerAll();
|
||||||
|
if (success) {
|
||||||
|
setSites(data || []);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取站点数据失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据站点ID获取站点名称
|
||||||
|
const getSiteName = (siteId: number | undefined | null) => {
|
||||||
|
if (!siteId) return '-';
|
||||||
|
if (typeof siteId === 'string') {
|
||||||
|
return siteId;
|
||||||
|
}
|
||||||
|
const site = sites.find((s) => s.id === siteId);
|
||||||
|
return site ? site.name : String(siteId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件加载时获取站点数据
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSites();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const columns: ProColumns<API.GetCustomerDTO>[] = [
|
const columns: ProColumns<API.GetCustomerDTO>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -178,7 +205,7 @@ const CustomerList: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: '用户名',
|
title: '用户名',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
copyable: true,
|
hideInSearch: true,
|
||||||
sorter: true,
|
sorter: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -196,7 +223,6 @@ const CustomerList: React.FC = () => {
|
||||||
title: '账单地址',
|
title: '账单地址',
|
||||||
dataIndex: 'billing',
|
dataIndex: 'billing',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
|
|
||||||
width: 200,
|
width: 200,
|
||||||
render: (billing) => <AddressCell address={billing} title="账单地址" />,
|
render: (billing) => <AddressCell address={billing} title="账单地址" />,
|
||||||
},
|
},
|
||||||
|
|
@ -210,6 +236,7 @@ const CustomerList: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: '评分',
|
title: '评分',
|
||||||
dataIndex: 'rate',
|
dataIndex: 'rate',
|
||||||
|
width: 120,
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
|
|
@ -316,7 +343,6 @@ const CustomerList: React.FC = () => {
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
headerTitle="查询表格"
|
headerTitle="查询表格"
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
columns={columns}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
request={async (params, sorter,filter) => {
|
request={async (params, sorter,filter) => {
|
||||||
console.log('custoemr request',params, sorter,filter)
|
console.log('custoemr request',params, sorter,filter)
|
||||||
|
|
@ -344,7 +370,7 @@ const CustomerList: React.FC = () => {
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
|
columns={columns}
|
||||||
search={{
|
search={{
|
||||||
labelWidth: 'auto',
|
labelWidth: 'auto',
|
||||||
span: 6,
|
span: 6,
|
||||||
|
|
@ -352,7 +378,8 @@ const CustomerList: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
showTotal: (total, range) =>
|
||||||
|
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
|
||||||
}}
|
}}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ const ListPage: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: '账单地址',
|
title: '账单地址',
|
||||||
dataIndex: 'billing',
|
dataIndex: 'billing',
|
||||||
render: (_, record) => JSON.stringify(record?.billing || record?.shipping),
|
render: (_, record) => record?.billing.state || record?.shipping.state,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '标签',
|
title: '标签',
|
||||||
|
|
|
||||||
|
|
@ -151,10 +151,7 @@ const OrderItemsPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
request={request}
|
request={request}
|
||||||
pagination={{
|
pagination={{ showSizeChanger: true }}
|
||||||
showSizeChanger: true,
|
|
||||||
showQuickJumper: true,
|
|
||||||
}}
|
|
||||||
search={{ labelWidth: 90, span: 6 }}
|
search={{ labelWidth: 90, span: 6 }}
|
||||||
toolBarRender={false}
|
toolBarRender={false}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -189,20 +189,11 @@ const ListPage: React.FC = () => {
|
||||||
dataIndex: 'siteId',
|
dataIndex: 'siteId',
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
request: async () => {
|
request: async () => {
|
||||||
try {
|
const { data = [] } = await sitecontrollerAll();
|
||||||
const result = await sitecontrollerAll();
|
return data.map((item) => ({
|
||||||
const {success, data}= result
|
label: item.name,
|
||||||
if (success && data) {
|
value: item.id,
|
||||||
return data.map((site: any) => ({
|
}));
|
||||||
label: site.name,
|
|
||||||
value: site.id,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取站点列表失败:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -267,17 +258,17 @@ const ListPage: React.FC = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '物流',
|
title: '物流',
|
||||||
dataIndex: 'fulfillments',
|
dataIndex: 'shipmentList',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{(record as any)?.fulfillments?.map((item: any) => {
|
{(record as any)?.shipmentList?.map((item: any) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
return (
|
return (
|
||||||
<div style={{ display:"flex", alignItems:"center" }}>
|
<div>
|
||||||
{item.tracking_provider}
|
{item.tracking_provider}:{item.primary_tracking_number} (
|
||||||
{item.tracking_number}
|
{formatShipmentState(item.state)})
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -288,6 +279,7 @@ const ListPage: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: 'IP',
|
title: 'IP',
|
||||||
dataIndex: 'customer_ip_address',
|
dataIndex: 'customer_ip_address',
|
||||||
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '设备',
|
title: '设备',
|
||||||
|
|
@ -478,7 +470,6 @@ const ListPage: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSizeOptions: ['10', '20', '50', '100', '1000'],
|
pageSizeOptions: ['10', '20', '50', '100', '1000'],
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
|
||||||
defaultPageSize: 10,
|
defaultPageSize: 10,
|
||||||
}}
|
}}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
|
|
@ -503,24 +494,31 @@ const ListPage: React.FC = () => {
|
||||||
}}
|
}}
|
||||||
tableRef={actionRef}
|
tableRef={actionRef}
|
||||||
/>,
|
/>,
|
||||||
|
// <Button
|
||||||
|
// type="primary"
|
||||||
|
// disabled={selectedRowKeys.length === 0}
|
||||||
|
// onClick={handleBatchExport}
|
||||||
|
// >
|
||||||
|
// 批量导出
|
||||||
|
// </Button>,
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="批量导出"
|
title="批量导出"
|
||||||
description="确认导出选中的订单吗?"
|
description="确认导出选中的订单吗?"
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
console.log(selectedRowKeys);
|
console.log(selectedRowKeys);
|
||||||
try {
|
try {
|
||||||
const res = await request('/order/export', {
|
const res = await request('/order/order/export', {
|
||||||
method: 'POST',
|
method: 'GET',
|
||||||
data: {
|
params: {
|
||||||
ids: selectedRowKeys,
|
ids: selectedRowKeys,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (res?.success && res.data) {
|
if (res?.success && res?.data?.csv) {
|
||||||
const blob = new Blob([res.data], { type: 'text/csv;charset=utf-8;' });
|
const blob = new Blob([res.data.csv], { type: 'text/csv;charset=utf-8;' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = 'orders.csv';
|
a.download = 'customers.csv';
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -492,12 +492,9 @@ const List: React.FC = () => {
|
||||||
sortField = field;
|
sortField = field;
|
||||||
sortOrder = sort[field];
|
sortOrder = sort[field];
|
||||||
}
|
}
|
||||||
const {current,pageSize,...where} = params
|
|
||||||
console.log(`params`,params)
|
|
||||||
const { data, success } = await productcontrollerGetproductlist({
|
const { data, success } = await productcontrollerGetproductlist({
|
||||||
where,
|
...params,
|
||||||
page: current || 1,
|
|
||||||
per_page: pageSize || 10,
|
|
||||||
sortField,
|
sortField,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
} as any);
|
} as any);
|
||||||
|
|
@ -508,17 +505,17 @@ const List: React.FC = () => {
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
// expandable={{
|
expandable={{
|
||||||
// expandedRowRender: (record) => (
|
expandedRowRender: (record) => (
|
||||||
// <SiteProductInfo
|
<SiteProductInfo
|
||||||
// skus={(record.siteSkus as string[]) || []}
|
skus={(record.siteSkus as string[]) || []}
|
||||||
// record={record}
|
record={record}
|
||||||
// parentTableRef={actionRef}
|
parentTableRef={actionRef}
|
||||||
// />
|
/>
|
||||||
// ),
|
),
|
||||||
// rowExpandable: (record) =>
|
rowExpandable: (record) =>
|
||||||
// !!(record.siteSkus && record.siteSkus.length > 0),
|
!!(record.siteSkus && record.siteSkus.length > 0),
|
||||||
// }}
|
}}
|
||||||
editable={{
|
editable={{
|
||||||
type: 'single',
|
type: 'single',
|
||||||
onSave: async (key, record, originRow) => {
|
onSave: async (key, record, originRow) => {
|
||||||
|
|
|
||||||
|
|
@ -400,7 +400,6 @@ const PermutationPage: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
defaultPageSize: 50,
|
defaultPageSize: 50,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
|
||||||
pageSizeOptions: ['50', '100', '200', '500', '1000', '2000'],
|
pageSizeOptions: ['50', '100', '200', '500', '1000', '2000'],
|
||||||
}}
|
}}
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
|
|
|
||||||
|
|
@ -414,7 +414,6 @@ const ProductSyncPage: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
|
||||||
}}
|
}}
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
search={{
|
search={{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,761 @@
|
||||||
|
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<T> {
|
||||||
|
data: T[];
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟API请求函数
|
||||||
|
const getSites = async (): Promise<ApiResponse<Site>> => {
|
||||||
|
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<ApiResponse<SiteProduct>> => {
|
||||||
|
return request('/product/wp-products', {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProductSyncPage: React.FC = () => {
|
||||||
|
const [sites, setSites] = useState<Site[]>([]);
|
||||||
|
// 存储所有 WP 产品,用于查找匹配。 Key: SKU (包含前缀)
|
||||||
|
const [siteProductMap, setSiteProductMap] = useState<Map<string, SiteProduct>>(
|
||||||
|
new Map(),
|
||||||
|
);
|
||||||
|
const [skuTemplate, setSkuTemplate] = useState<string>('');
|
||||||
|
const [initialLoading, setInitialLoading] = useState(true);
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
const [selectedSiteId, setSelectedSiteId] = useState<string>('');
|
||||||
|
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<React.Key[]>([]);
|
||||||
|
const [selectedRows, setSelectedRows] = useState<SiteProduct[]>([]);
|
||||||
|
|
||||||
|
// 初始化数据:获取站点和所有 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<string, SiteProduct>();
|
||||||
|
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<ProductWithWP>[] => {
|
||||||
|
const columns: ProColumns<ProductWithWP>[] = [
|
||||||
|
{
|
||||||
|
title: 'SKU',
|
||||||
|
dataIndex: 'sku',
|
||||||
|
key: 'sku',
|
||||||
|
width: 150,
|
||||||
|
fixed: 'left',
|
||||||
|
copyable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '商品信息',
|
||||||
|
key: 'profile',
|
||||||
|
width: 300,
|
||||||
|
fixed: 'left',
|
||||||
|
render: (_, record) => (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontWeight: 'bold', fontSize: 14 }}>
|
||||||
|
{record.name}
|
||||||
|
</div>
|
||||||
|
<EditForm
|
||||||
|
record={record}
|
||||||
|
tableRef={actionRef}
|
||||||
|
trigger={
|
||||||
|
<EditOutlined
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#1890ff',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: '#666' }}>
|
||||||
|
<span style={{ marginRight: 8 }}>价格: {record.price}</span>
|
||||||
|
{record.promotionPrice && (
|
||||||
|
<span style={{ color: 'red' }}>
|
||||||
|
促销价: {record.promotionPrice}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 属性 */}
|
||||||
|
<div style={{ marginTop: 4 }}>
|
||||||
|
{record.attributes?.map((attr: any, idx: number) => (
|
||||||
|
<Tag
|
||||||
|
key={idx}
|
||||||
|
style={{ fontSize: 10, marginRight: 4, marginBottom: 2 }}
|
||||||
|
>
|
||||||
|
{attr.dict?.name || attr.name}: {attr.name}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 组成 (如果是 Bundle) */}
|
||||||
|
{record.type === 'bundle' &&
|
||||||
|
record.components &&
|
||||||
|
record.components.length > 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
background: '#f5f5f5',
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontWeight: 'bold', marginBottom: 2 }}>
|
||||||
|
Components:
|
||||||
|
</div>
|
||||||
|
{record.components.map((comp: any, idx: number) => (
|
||||||
|
<div key={idx}>
|
||||||
|
{comp.sku} × {comp.quantity}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 为每个站点生成列
|
||||||
|
sites.forEach((site: Site) => {
|
||||||
|
const siteColumn: ProColumns<ProductWithWP> = {
|
||||||
|
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 (
|
||||||
|
<ModalForm
|
||||||
|
title="同步产品"
|
||||||
|
trigger={
|
||||||
|
<Button type="link" icon={<SyncOutlined />}>
|
||||||
|
同步到站点
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
width={400}
|
||||||
|
onFinish={async (values) => {
|
||||||
|
return await syncProductToSite(values, record, site);
|
||||||
|
}}
|
||||||
|
initialValues={{
|
||||||
|
sku:
|
||||||
|
siteProductSku ||
|
||||||
|
(skuTemplate
|
||||||
|
? renderSiteSku(skuTemplate, { site, product: record })
|
||||||
|
: `${site.skuPrefix || ''}-${record.sku}`),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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 syncProductToSite(
|
||||||
|
values,
|
||||||
|
record,
|
||||||
|
site,
|
||||||
|
siteProduct.externalProductId,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
columns.push(siteColumn);
|
||||||
|
});
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (initialLoading) {
|
||||||
|
return (
|
||||||
|
<Card title="商品同步状态" className="product-sync-card">
|
||||||
|
<Spin
|
||||||
|
size="large"
|
||||||
|
style={{ display: 'flex', justifyContent: 'center', padding: 40 }}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title="商品同步状态" className="product-sync-card">
|
||||||
|
<ProTable<ProductWithWP>
|
||||||
|
columns={generateColumns()}
|
||||||
|
actionRef={actionRef}
|
||||||
|
rowKey="id"
|
||||||
|
rowSelection={{
|
||||||
|
selectedRowKeys,
|
||||||
|
onChange: (keys, rows) => {
|
||||||
|
setSelectedRowKeys(keys);
|
||||||
|
setSelectedRows(rows);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
toolBarRender={() => [
|
||||||
|
<Select
|
||||||
|
key="site-select"
|
||||||
|
style={{ width: 200 }}
|
||||||
|
placeholder="选择目标站点"
|
||||||
|
value={selectedSiteId}
|
||||||
|
onChange={setSelectedSiteId}
|
||||||
|
options={sites.map((site) => ({
|
||||||
|
label: site.name,
|
||||||
|
value: site.id,
|
||||||
|
}))}
|
||||||
|
/>,
|
||||||
|
<Button
|
||||||
|
key="batch-sync"
|
||||||
|
type="primary"
|
||||||
|
icon={<SyncOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
if (!selectedSiteId) {
|
||||||
|
message.warning('请先选择目标站点');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setBatchSyncModalVisible(true);
|
||||||
|
}}
|
||||||
|
disabled={!selectedSiteId || sites.length === 0}
|
||||||
|
>
|
||||||
|
批量同步
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
request={async (params, sort, filter) => {
|
||||||
|
// 调用本地获取产品列表 API
|
||||||
|
const { data, success } = await productcontrollerGetproductlist({
|
||||||
|
...params,
|
||||||
|
current: params.current,
|
||||||
|
pageSize: params.pageSize,
|
||||||
|
// 传递搜索参数
|
||||||
|
keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段
|
||||||
|
sku: (params as any).sku,
|
||||||
|
name: (params as any).name,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
// 返回给 ProTable
|
||||||
|
return {
|
||||||
|
data: (data?.items || []) as ProductWithWP[],
|
||||||
|
success,
|
||||||
|
total: data?.total || 0,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
pagination={{
|
||||||
|
pageSize: 10,
|
||||||
|
showSizeChanger: true,
|
||||||
|
}}
|
||||||
|
scroll={{ x: 'max-content' }}
|
||||||
|
search={{
|
||||||
|
labelWidth: 'auto',
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
density: true,
|
||||||
|
fullScreen: true,
|
||||||
|
}}
|
||||||
|
dateFormatter="string"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 批量同步模态框 */}
|
||||||
|
<Modal
|
||||||
|
title="批量同步产品"
|
||||||
|
open={batchSyncModalVisible}
|
||||||
|
onCancel={() => !syncing && setBatchSyncModalVisible(false)}
|
||||||
|
footer={null}
|
||||||
|
closable={!syncing}
|
||||||
|
maskClosable={!syncing}
|
||||||
|
>
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<p>
|
||||||
|
目标站点:
|
||||||
|
<strong>{sites.find((s) => s.id === selectedSiteId)?.name}</strong>
|
||||||
|
</p>
|
||||||
|
{selectedRows.length > 0 ? (
|
||||||
|
<p>
|
||||||
|
已选择 <strong>{selectedRows.length}</strong> 个产品进行同步
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p>此操作将同步所有库存产品到指定站点,请确认是否继续?</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{syncing && (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<div style={{ marginBottom: 8 }}>同步进度:</div>
|
||||||
|
<Progress percent={syncProgress} status="active" />
|
||||||
|
<div style={{ marginTop: 8, fontSize: 12, color: '#666' }}>
|
||||||
|
成功:{syncResults.success} | 失败:{syncResults.failed}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{syncResults.errors.length > 0 && (
|
||||||
|
<div style={{ marginBottom: 16, maxHeight: 200, overflow: 'auto' }}>
|
||||||
|
<div style={{ marginBottom: 8, color: '#ff4d4f' }}>错误详情:</div>
|
||||||
|
{syncResults.errors.slice(0, 10).map((error, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{ fontSize: 12, color: '#666', marginBottom: 4 }}
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{syncResults.errors.length > 10 && (
|
||||||
|
<div style={{ fontSize: 12, color: '#999' }}>
|
||||||
|
...还有 {syncResults.errors.length - 10} 个错误
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ textAlign: 'right' }}>
|
||||||
|
<Button
|
||||||
|
onClick={() => setBatchSyncModalVisible(false)}
|
||||||
|
disabled={syncing}
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => batchSyncProducts()}
|
||||||
|
loading={syncing}
|
||||||
|
disabled={syncing}
|
||||||
|
>
|
||||||
|
{syncing ? '同步中...' : '开始同步'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductSyncPage;
|
||||||
|
|
@ -359,6 +359,7 @@ const SiteList: React.FC = () => {
|
||||||
defaultPageSize: 20,
|
defaultPageSize: 20,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
}}
|
}}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -465,7 +465,7 @@ const CustomerPage: React.FC = () => {
|
||||||
<ProTable
|
<ProTable
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
search={false}
|
search={false}
|
||||||
pagination={{ pageSize: 20 ,showSizeChanger: true, showQuickJumper: true,}}
|
pagination={{ pageSize: 20 }}
|
||||||
columns={[
|
columns={[
|
||||||
{ title: '订单号', dataIndex: 'number', copyable: true },
|
{ title: '订单号', dataIndex: 'number', copyable: true },
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,11 @@ const OrdersPage: React.FC = () => {
|
||||||
return [{ key: 'all', label: `全部(${total})` }, ...tabs];
|
return [{ key: 'all', label: `全部(${total})` }, ...tabs];
|
||||||
}, [count]);
|
}, [count]);
|
||||||
|
|
||||||
const columns: ProColumns<API.UnifiedOrderDTO>[] = [
|
const columns: ProColumns<API.Order>[] = [
|
||||||
{
|
{
|
||||||
title: '订单号',
|
title: '订单号',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
|
|
@ -96,6 +97,7 @@ const OrdersPage: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: '客户姓名',
|
title: '客户姓名',
|
||||||
dataIndex: 'customer_name',
|
dataIndex: 'customer_name',
|
||||||
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '商品',
|
title: '商品',
|
||||||
|
|
@ -127,6 +129,7 @@ const OrdersPage: React.FC = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '联系电话',
|
title: '联系电话',
|
||||||
|
hideInSearch: true,
|
||||||
render: (_, record) => record.shipping?.phone || record.billing?.phone,
|
render: (_, record) => record.shipping?.phone || record.billing?.phone,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -306,7 +309,6 @@ const OrdersPage: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSizeOptions: ['10', '20', '50', '100', '1000'],
|
pageSizeOptions: ['10', '20', '50', '100', '1000'],
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
|
||||||
defaultPageSize: 10,
|
defaultPageSize: 10,
|
||||||
}}
|
}}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
|
|
@ -317,8 +319,6 @@ const OrdersPage: React.FC = () => {
|
||||||
setSelectedRowKeys={setSelectedRowKeys}
|
setSelectedRowKeys={setSelectedRowKeys}
|
||||||
siteId={siteId}
|
siteId={siteId}
|
||||||
/>,
|
/>,
|
||||||
<Button disabled>批量发货</Button>
|
|
||||||
,
|
|
||||||
<Button
|
<Button
|
||||||
title="批量删除"
|
title="批量删除"
|
||||||
danger
|
danger
|
||||||
|
|
|
||||||
|
|
@ -394,7 +394,6 @@ const ProductsPage: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSizeOptions: ['10', '20', '50', '100', '1000', '2000'],
|
pageSizeOptions: ['10', '20', '50', '100', '1000', '2000'],
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
|
||||||
defaultPageSize: 10,
|
defaultPageSize: 10,
|
||||||
}}
|
}}
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ export const ErpProductBindModal: React.FC<ErpProductBindModalProps> = ({
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
}}
|
}}
|
||||||
toolBarRender={false}
|
toolBarRender={false}
|
||||||
options={false}
|
options={false}
|
||||||
|
|
|
||||||
|
|
@ -161,10 +161,7 @@ const OrdersPage: React.FC = () => {
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
request={request}
|
request={request}
|
||||||
pagination={{
|
pagination={{ showSizeChanger: true }}
|
||||||
showSizeChanger: true,
|
|
||||||
showQuickJumper: true,
|
|
||||||
}}
|
|
||||||
search={{
|
search={{
|
||||||
labelWidth: 90,
|
labelWidth: 90,
|
||||||
span: 6,
|
span: 6,
|
||||||
|
|
|
||||||
|
|
@ -59,21 +59,6 @@ export async function ordercontrollerCreatenote(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /order/export */
|
|
||||||
export async function ordercontrollerExportorder(
|
|
||||||
body: Record<string, any>,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<any>('/order/export', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /order/getOrderByNumber */
|
/** 此处后端没有提供注释 POST /order/getOrderByNumber */
|
||||||
export async function ordercontrollerGetorderbynumber(
|
export async function ordercontrollerGetorderbynumber(
|
||||||
body: string,
|
body: string,
|
||||||
|
|
@ -266,14 +251,11 @@ export async function ordercontrollerSyncorderbyid(
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { orderId: param0, siteId: param1, ...queryParams } = params;
|
const { orderId: param0, siteId: param1, ...queryParams } = params;
|
||||||
return request<API.SyncOperationResult>(
|
return request<API.BooleanRes>(`/order/syncOrder/${param1}/order/${param0}`, {
|
||||||
`/order/syncOrder/${param1}/order/${param0}`,
|
method: 'POST',
|
||||||
{
|
params: { ...queryParams },
|
||||||
method: 'POST',
|
...(options || {}),
|
||||||
params: { ...queryParams },
|
});
|
||||||
...(options || {}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /order/updateOrderItems/${param0} */
|
/** 此处后端没有提供注释 POST /order/updateOrderItems/${param0} */
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ declare namespace API {
|
||||||
/** 站点ID */
|
/** 站点ID */
|
||||||
site_id?: number;
|
site_id?: number;
|
||||||
/** 原始ID */
|
/** 原始ID */
|
||||||
origin_id?: string;
|
origin_id?: number;
|
||||||
/** 邮箱 */
|
/** 邮箱 */
|
||||||
email?: string;
|
email?: string;
|
||||||
/** 名字 */
|
/** 名字 */
|
||||||
|
|
@ -456,10 +456,6 @@ declare namespace API {
|
||||||
shipping_provider?: string;
|
shipping_provider?: string;
|
||||||
/** 发货方式 */
|
/** 发货方式 */
|
||||||
shipping_method?: string;
|
shipping_method?: string;
|
||||||
/** 状态 */
|
|
||||||
status?: string;
|
|
||||||
/** 创建时间 */
|
|
||||||
date_created?: string;
|
|
||||||
/** 发货商品项 */
|
/** 发货商品项 */
|
||||||
items?: FulfillmentItemDTO[];
|
items?: FulfillmentItemDTO[];
|
||||||
};
|
};
|
||||||
|
|
@ -2410,8 +2406,6 @@ declare namespace API {
|
||||||
params?: UnifiedSearchParamsDTO;
|
params?: UnifiedSearchParamsDTO;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SyncOperationResult = {};
|
|
||||||
|
|
||||||
type SyncOperationResultDTO = {
|
type SyncOperationResultDTO = {
|
||||||
/** 总处理数量 */
|
/** 总处理数量 */
|
||||||
total?: number;
|
total?: number;
|
||||||
|
|
@ -2647,8 +2641,6 @@ declare namespace API {
|
||||||
number?: string;
|
number?: string;
|
||||||
/** 订单状态 */
|
/** 订单状态 */
|
||||||
status?: string;
|
status?: string;
|
||||||
/** 财务状态 */
|
|
||||||
financial_status?: string;
|
|
||||||
/** 货币 */
|
/** 货币 */
|
||||||
currency?: string;
|
currency?: string;
|
||||||
/** 货币符号 */
|
/** 货币符号 */
|
||||||
|
|
@ -2691,6 +2683,8 @@ declare namespace API {
|
||||||
fee_lines?: UnifiedFeeLineDTO[];
|
fee_lines?: UnifiedFeeLineDTO[];
|
||||||
/** 优惠券项 */
|
/** 优惠券项 */
|
||||||
coupon_lines?: UnifiedCouponLineDTO[];
|
coupon_lines?: UnifiedCouponLineDTO[];
|
||||||
|
/** 物流追踪信息 */
|
||||||
|
tracking?: UnifiedOrderTrackingDTO[];
|
||||||
/** 支付时间 */
|
/** 支付时间 */
|
||||||
date_paid?: string;
|
date_paid?: string;
|
||||||
/** 客户IP地址 */
|
/** 客户IP地址 */
|
||||||
|
|
@ -2701,10 +2695,6 @@ declare namespace API {
|
||||||
device_type?: string;
|
device_type?: string;
|
||||||
/** 来源类型 */
|
/** 来源类型 */
|
||||||
source_type?: string;
|
source_type?: string;
|
||||||
/** 订单状态 */
|
|
||||||
fulfillment_status?: number;
|
|
||||||
/** 物流信息 */
|
|
||||||
fulfillments?: FulfillmentDTO[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type UnifiedOrderLineItemDTO = {
|
type UnifiedOrderLineItemDTO = {
|
||||||
|
|
@ -2749,16 +2739,14 @@ declare namespace API {
|
||||||
type UnifiedOrderTrackingDTO = {
|
type UnifiedOrderTrackingDTO = {
|
||||||
/** 订单ID */
|
/** 订单ID */
|
||||||
order_id?: string;
|
order_id?: string;
|
||||||
/** 物流单号 */
|
/** 快递公司 */
|
||||||
|
tracking_provider?: string;
|
||||||
|
/** 运单跟踪号 */
|
||||||
tracking_number?: string;
|
tracking_number?: string;
|
||||||
/** 物流公司 */
|
/** 发货日期 */
|
||||||
shipping_provider?: string;
|
date_shipped?: string;
|
||||||
/** 发货方式 */
|
/** 发货状态 */
|
||||||
shipping_method?: string;
|
status_shipped?: string;
|
||||||
/** 状态 */
|
|
||||||
status?: string;
|
|
||||||
/** 创建时间 */
|
|
||||||
date_created?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type UnifiedPaginationDTO = {
|
type UnifiedPaginationDTO = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue