zksu
/
WEB
forked from yoone/WEB
1
0
Fork 0

feat: 添加Dockerfile和nginx配置支持容器化部署

feat(订单): 实现订单发货功能并优化同步结果显示

feat(字典): 新增字典项增删改查、导入导出功能组件

feat(产品): 添加产品同步到站点功能及批量操作结果展示

refactor(API): 统一API参数命名规范并优化类型定义

fix(客户): 修复客户列表查询条件及同步功能

style: 清理无用代码并优化代码格式

docs: 更新部分组件注释说明

chore: 添加批量操作结果展示工具函数
This commit is contained in:
tikkhun 2025-12-31 12:07:24 +08:00
parent 32e2759151
commit 6f35c2aee5
38 changed files with 3546 additions and 1342 deletions

32
Dockerfile Normal file
View File

@ -0,0 +1,32 @@
# 构建阶段
FROM node:18-alpine as builder
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json
COPY package*.json ./
# 安装依赖(使用 --legacy-peer-deps 解决依赖冲突)
RUN npm install --legacy-peer-deps
# 复制源代码
COPY . .
# 构建项目
RUN npm run build
# 生产阶段
FROM nginx:alpine
# 复制构建产物到 Nginx 静态目录
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制自定义 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露端口
EXPOSE 80
# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]

33
nginx.conf Normal file
View File

@ -0,0 +1,33 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# API 代理配置
location /api {
proxy_pass http://api:7001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 静态文件缓存配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 错误页面配置
error_page 404 /index.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@ -0,0 +1,91 @@
import { message } from 'antd';
import React from 'react';
// 定义同步结果的数据类型
export interface SyncResultData {
total?: number;
processed?: number;
synced?: number;
created?: number;
updated?: number;
errors?: Array<{
identifier: string;
error: string;
}>;
}
// 定义组件的 Props 类型
interface SyncResultMessageProps {
data?: SyncResultData;
entityType?: string; // 实体类型,如"订单"、"客户"等
}
// 显示同步结果的函数
export const showSyncResult = (
data: SyncResultData,
entityType: string = '订单',
) => {
const result = data || {};
const {
total = 0,
processed = 0,
synced = 0,
created = 0,
updated = 0,
errors = [],
} = result;
// 构建结果消息
let resultMessage = `同步完成!共处理 ${processed}${entityType}(总数 ${total} 个):`;
if (created > 0) resultMessage += ` 新建 ${created}`;
if (updated > 0) resultMessage += ` 更新 ${updated}`;
if (synced > 0) resultMessage += ` 同步成功 ${synced}`;
if (errors.length > 0) resultMessage += ` 失败 ${errors.length}`;
// 根据是否有错误显示不同的消息类型
if (errors.length > 0) {
// 如果有错误,显示警告消息
message.warning({
content: (
<div>
<div>{resultMessage}</div>
<div style={{ marginTop: 8, fontSize: 12, color: '#faad14' }}>
{errors
.slice(0, 3)
.map((err: any) => `${err.identifier}: ${err.error}`)
.join(', ')}
{errors.length > 3 && `${errors.length - 3} 个错误...`}
</div>
</div>
),
duration: 8,
key: 'sync-result',
});
} else {
// 完全成功
message.success({
content: resultMessage,
duration: 4,
key: 'sync-result',
});
}
};
// 同步结果显示组件
const SyncResultMessage: React.FC<SyncResultMessageProps> = ({
data,
entityType = '订单',
}) => {
// 当组件挂载时显示结果
React.useEffect(() => {
if (data) {
showSyncResult(data, entityType);
}
}, [data, entityType]);
// 这个组件不渲染任何内容,只用于显示消息
return null;
};
export default SyncResultMessage;

View File

@ -1,5 +1,4 @@
import { ordercontrollerGetorders } from '@/servers/api/order'; import { ordercontrollerGetorders } from '@/servers/api/order';
import { siteapicontrollerGetorders } from '@/servers/api/siteApi';
import { import {
App, App,
Col, Col,
@ -89,8 +88,6 @@ const HistoryOrders: React.FC<HistoryOrdersProps> = ({ customer, siteId }) => {
// 获取客户订单数据 // 获取客户订单数据
const fetchOrders = async () => { const fetchOrders = async () => {
setLoading(true); setLoading(true);
try { try {
const response = await ordercontrollerGetorders({ const response = await ordercontrollerGetorders({

View File

@ -12,10 +12,12 @@ import {
ModalForm, ModalForm,
PageContainer, PageContainer,
ProColumns, ProColumns,
ProFormDateTimeRangePicker,
ProFormSelect, ProFormSelect,
ProFormText,
ProTable, ProTable,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { App, Avatar, Button, Rate, Space, Tag, Tooltip } from 'antd'; import { App, Avatar, Button, Form, Rate, Space, Tag, Tooltip } from 'antd';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import HistoryOrders from './HistoryOrders'; import HistoryOrders from './HistoryOrders';
@ -113,7 +115,6 @@ 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 [syncLoading, setSyncLoading] = useState(false);
const [sites, setSites] = useState<any[]>([]); // 添加站点数据状态 const [sites, setSites] = useState<any[]>([]); // 添加站点数据状态
// 获取站点数据 // 获取站点数据
@ -135,7 +136,6 @@ const CustomerList: React.FC = () => {
return siteId; return siteId;
} }
const site = sites.find((s) => s.id === siteId); const site = sites.find((s) => s.id === siteId);
console.log(`site`, site);
return site ? site.name : String(siteId); return site ? site.name : String(siteId);
}; };
@ -144,11 +144,14 @@ const CustomerList: React.FC = () => {
fetchSites(); fetchSites();
}, []); }, []);
const columns: ProColumns<API.UnifiedCustomerDTO>[] = [ const columns: ProColumns<API.GetCustomerDTO>[] = [
{ {
title: 'ID', title: 'ID',
dataIndex: 'id', dataIndex: 'id',
hideInSearch: true, },
{
title: '原始 ID',
dataIndex: 'origin_id',
}, },
{ {
title: '站点', title: '站点',
@ -169,8 +172,9 @@ const CustomerList: React.FC = () => {
return []; return [];
} }
}, },
render: (siteId: any) => { render(_, record) {
return <span>{getSiteName(siteId) || '-'}</span>; // console.log(`siteId`, record.site_id);
return <span>{getSiteName(record.site_id) || '-'}</span>;
}, },
}, },
{ {
@ -254,7 +258,7 @@ const CustomerList: React.FC = () => {
message.error(e?.message || '设置评分失败'); message.error(e?.message || '设置评分失败');
} }
}} }}
value={record.raw?.rate || 0} value={record.rate || 0}
allowHalf allowHalf
/> />
); );
@ -265,7 +269,7 @@ const CustomerList: React.FC = () => {
dataIndex: 'tags', dataIndex: 'tags',
hideInSearch: true, hideInSearch: true,
render: (_, record) => { render: (_, record) => {
const tags = record.raw?.tags || []; const tags = record?.tags || [];
return ( return (
<Space size={[0, 8]} wrap> <Space size={[0, 8]} wrap>
{tags.map((tag: string) => { {tags.map((tag: string) => {
@ -327,7 +331,7 @@ const CustomerList: React.FC = () => {
tableRef={actionRef} tableRef={actionRef}
/> />
{/* 订单 */} {/* 订单 */}
<HistoryOrders customer={record} siteId={record.raw?.site_id} /> <HistoryOrders customer={record} siteId={record.site_id} />
</Space> </Space>
); );
}, },
@ -342,20 +346,59 @@ const CustomerList: React.FC = () => {
actionRef={actionRef} actionRef={actionRef}
rowKey="id" rowKey="id"
request={async (params, sorter) => { request={async (params, sorter) => {
// 获取排序字段和排序方向
const key = Object.keys(sorter)[0]; const key = Object.keys(sorter)[0];
const { data, success } = await customercontrollerGetcustomerlist({
...params,
current: params.current?.toString(),
pageSize: params.pageSize?.toString(),
...(key
? { sorterKey: key, sorterValue: sorter[key] as string }
: {}),
});
// 构建过滤条件对象
const where: any = {};
// 添加邮箱过滤
if (params.email) {
where.email = params.email;
}
// 添加站点ID过滤
if (params.site_id) {
where.site_id = params.site_id;
}
// 添加用户名过滤
if (params.username) {
where.username = params.username;
}
// 添加电话过滤
if (params.phone) {
where.phone = params.phone;
}
// 构建查询参数
const queryParams: any = {
page: params.current || 1,
per_page: params.pageSize || 20,
};
// 添加搜索关键词
if (params.fullname) {
queryParams.search = params.fullname;
}
// 添加过滤条件(只有在有过滤条件时才添加)
if (Object.keys(where).length > 0) {
queryParams.where = where;
}
// 添加排序
if (key) {
queryParams.orderBy = { [key]: sorter[key] as 'asc' | 'desc' };
}
const result = await customercontrollerGetcustomerlist(queryParams);
console.log(queryParams, result);
return { return {
total: data?.total || 0, total: result?.data?.total || 0,
data: data?.items || [], data: result?.data?.items || [],
success, success: true,
}; };
}} }}
columns={columns} columns={columns}
@ -469,6 +512,7 @@ const SyncCustomersModal: React.FC<{
const { message } = App.useApp(); const { message } = App.useApp();
const [sites, setSites] = useState<any[]>([]); const [sites, setSites] = useState<any[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [form] = Form.useForm(); // 添加表单实例
// 获取站点列表 // 获取站点列表
useEffect(() => { useEffect(() => {
@ -487,15 +531,56 @@ const SyncCustomersModal: React.FC<{
} }
}, [visible]); }, [visible]);
const handleSync = async (values: { siteId: number }) => { // 定义同步参数类型
type SyncParams = {
siteId: number;
search?: string;
role?: string;
dateRange?: [string, string];
orderBy?: string;
};
const handleSync = async (values: SyncParams) => {
try { try {
setLoading(true); setLoading(true);
// 构建过滤参数
const params: any = {};
// 添加搜索关键词
if (values.search) {
params.search = values.search;
}
// 添加角色过滤
if (values.role) {
params.where = {
...params.where,
role: values.role,
};
}
// 添加日期范围过滤(使用 after 和 before 参数)
if (values.dateRange && values.dateRange[0] && values.dateRange[1]) {
params.where = {
...params.where,
after: values.dateRange[0],
before: values.dateRange[1],
};
}
// 添加排序
if (values.orderBy) {
params.orderBy = values.orderBy;
}
const { const {
success, success,
message: msg, message: msg,
data, data,
} = await customercontrollerSynccustomers({ } = await customercontrollerSynccustomers({
siteId: values.siteId, siteId: values.siteId,
params: Object.keys(params).length > 0 ? params : undefined,
}); });
if (success) { if (success) {
@ -569,6 +654,7 @@ const SyncCustomersModal: React.FC<{
confirmLoading: loading, confirmLoading: loading,
}} }}
onFinish={handleSync} onFinish={handleSync}
form={form}
> >
<ProFormSelect <ProFormSelect
name="siteId" name="siteId"
@ -583,6 +669,59 @@ const SyncCustomersModal: React.FC<{
loading: loading, loading: loading,
}} }}
/> />
<ProFormText
name="search"
label="搜索关键词"
placeholder="输入邮箱、姓名或用户名进行搜索"
tooltip="支持搜索邮箱、姓名、用户名等字段"
/>
<ProFormSelect
name="role"
label="客户角色"
placeholder="选择客户角色进行过滤"
options={[
{ label: '所有角色', value: '' },
{ label: '管理员', value: 'administrator' },
{ label: '编辑', value: 'editor' },
{ label: '作者', value: 'author' },
{ label: '订阅者', value: 'subscriber' },
{ label: '客户', value: 'customer' },
]}
fieldProps={{
allowClear: true,
}}
/>
<ProFormDateTimeRangePicker
name="dateRange"
label="注册日期范围"
placeholder={['开始日期', '结束日期']}
transform={(value) => {
return {
dateRange: value,
};
}}
fieldProps={{
showTime: false,
style: { width: '100%' },
}}
/>
<ProFormSelect
name="orderBy"
label="排序方式"
placeholder="选择排序方式"
options={[
{ label: '默认排序', value: '' },
{ label: '注册时间(升序)', value: 'date_created:asc' },
{ label: '注册时间(降序)', value: 'date_created:desc' },
{ label: '邮箱(升序)', value: 'email:asc' },
{ label: '邮箱(降序)', value: 'email:desc' },
{ label: '姓名(升序)', value: 'first_name:asc' },
{ label: '姓名(降序)', value: 'first_name:desc' },
]}
fieldProps={{
allowClear: true,
}}
/>
</ModalForm> </ModalForm>
); );
}; };

View File

@ -129,22 +129,16 @@ const ListPage: React.FC = () => {
}, },
}, },
{ {
title: 'phone', title: '联系电话',
dataIndex: 'phone', dataIndex: 'phone',
hideInSearch: true, hideInSearch: true,
render: (_, record) => record?.billing.phone || record?.shipping.phone, render: (_, record) => record?.billing.phone || record?.shipping.phone,
}, },
{ {
title: 'state', title: '账单地址',
dataIndex: 'state', dataIndex: 'billing',
render: (_, record) => record?.billing.state || record?.shipping.state, render: (_, record) => record?.billing.state || record?.shipping.state,
}, },
{
title: 'city',
dataIndex: 'city',
hideInSearch: true,
render: (_, record) => record?.billing.city || record?.shipping.city,
},
{ {
title: '标签', title: '标签',
dataIndex: 'tags', dataIndex: 'tags',

View File

@ -17,6 +17,8 @@ import {
message, message,
} from 'antd'; } from 'antd';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import DictItemActions from '../components/DictItemActions';
import DictItemModal from '../components/DictItemModal';
const { Sider, Content } = Layout; const { Sider, Content } = Layout;
@ -36,13 +38,10 @@ const DictPage: React.FC = () => {
const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false); const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false);
const [editDictData, setEditDictData] = useState<any>(null); const [editDictData, setEditDictData] = useState<any>(null);
// 右侧字典项列表的状态 // 字典项模态框状态(由 DictItemModal 组件管理)
const [isAddDictItemModalVisible, setIsAddDictItemModalVisible] = const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
useState(false); const [isEditDictItem, setIsEditDictItem] = useState(false);
const [isEditDictItemModalVisible, setIsEditDictItemModalVisible] = const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
useState(false);
const [editDictItemData, setEditDictItemData] = useState<any>(null);
const [dictItemForm] = Form.useForm();
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
// 获取字典列表 // 获取字典列表
@ -144,15 +143,16 @@ const DictPage: React.FC = () => {
// 添加字典项 // 添加字典项
const handleAddDictItem = () => { const handleAddDictItem = () => {
dictItemForm.resetFields(); setIsEditDictItem(false);
setIsAddDictItemModalVisible(true); setEditingDictItemData(null);
setIsDictItemModalVisible(true);
}; };
// 编辑字典项 // 编辑字典项
const handleEditDictItem = (record: any) => { const handleEditDictItem = (record: any) => {
setEditDictItemData(record); setIsEditDictItem(true);
dictItemForm.setFieldsValue(record); setEditingDictItemData(record);
setIsEditDictItemModalVisible(true); setIsDictItemModalVisible(true);
}; };
// 删除字典项 // 删除字典项
@ -173,53 +173,42 @@ const DictPage: React.FC = () => {
} }
}; };
// 添加字典项表单提交 // 处理字典项模态框提交(添加或编辑)
const handleAddDictItemFormSubmit = async (values: any) => { const handleDictItemModalOk = async (values: any) => {
try { try {
if (isEditDictItem && editingDictItemData) {
// 编辑字典项
const result = await dictApi.dictcontrollerUpdatedictitem(
{ id: editingDictItemData.id },
values,
);
if (!result.success) {
throw new Error(result.message || '更新失败');
}
message.success('更新成功');
} else {
// 添加字典项
const result = await dictApi.dictcontrollerCreatedictitem({ const result = await dictApi.dictcontrollerCreatedictitem({
...values, ...values,
dictId: selectedDict.id, dictId: selectedDict.id,
}); });
if (!result.success) { if (!result.success) {
throw new Error(result.message || '添加失败'); throw new Error(result.message || '添加失败');
} }
message.success('添加成功'); message.success('添加成功');
setIsAddDictItemModalVisible(false); }
setIsDictItemModalVisible(false);
// 强制刷新字典项列表 // 强制刷新字典项列表
setTimeout(() => { setTimeout(() => {
actionRef.current?.reload(); actionRef.current?.reload();
}, 100); }, 100);
} catch (error: any) { } catch (error: any) {
message.error(`添加失败:${error.message || '未知错误'}`); message.error(
} `${isEditDictItem ? '更新' : '添加'}失败:${
}; error.message || '未知错误'
}`,
// 编辑字典项表单提交
const handleEditDictItemFormSubmit = async (values: any) => {
if (!editDictItemData) return;
try {
const result = await dictApi.dictcontrollerUpdatedictitem(
{ id: editDictItemData.id },
values,
); );
if (!result.success) {
throw new Error(result.message || '更新失败');
}
message.success('更新成功');
setIsEditDictItemModalVisible(false);
setEditDictItemData(null);
// 强制刷新字典项列表
setTimeout(() => {
actionRef.current?.reload();
}, 100);
} catch (error: any) {
message.error(`更新失败:${error.message || '未知错误'}`);
} }
}; };
@ -337,13 +326,7 @@ const DictPage: React.FC = () => {
key: 'shortName', key: 'shortName',
copyable: true, copyable: true,
}, },
{
title: '图片',
dataIndex: 'image',
key: 'image',
valueType: 'image',
width: 80,
},
{ {
title: '标题', title: '标题',
dataIndex: 'title', dataIndex: 'title',
@ -356,6 +339,13 @@ const DictPage: React.FC = () => {
key: 'titleCN', key: 'titleCN',
copyable: true, copyable: true,
}, },
{
title: '图片',
dataIndex: 'image',
key: 'image',
valueType: 'image',
width: 80,
},
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
@ -406,7 +396,18 @@ const DictPage: React.FC = () => {
</Button> </Button>
<Upload <Upload
name="file" name="file"
action="/api/dict/import" action={undefined}
customRequest={async (options) => {
const { file, onSuccess, onError } = options;
try {
const result = await dictApi.dictcontrollerImportdicts({}, [
file as File,
]);
onSuccess?.(result);
} catch (error) {
onError?.(error as Error);
}
}}
showUploadList={false} showUploadList={false}
onChange={(info) => { onChange={(info) => {
if (info.file.status === 'done') { if (info.file.status === 'done') {
@ -495,151 +496,46 @@ const DictPage: React.FC = () => {
size="small" size="small"
key={selectedDict?.id} key={selectedDict?.id}
toolBarRender={() => [ toolBarRender={() => [
<Button <DictItemActions
type="primary" key="dictItemActions"
onClick={handleAddDictItem} selectedDict={selectedDict}
disabled={!selectedDict} actionRef={actionRef}
size="small" showExport={true}
key="add" onImport={async (file: File, dictId: number) => {
> // 创建 FormData 对象
const formData = new FormData();
</Button>, // 添加文件到 FormData
<Upload formData.append('file', file);
name="file" // 添加字典 ID 到 FormData
action={undefined} formData.append('dictId', String(dictId));
customRequest={async (options) => { // 调用导入字典项的 API直接返回解析后的 JSON 对象
const { file, onSuccess, onError } = options; const result = await dictApi.dictcontrollerImportdictitems(
try { formData,
const result =
await dictApi.dictcontrollerImportdictitems(
{ dictId: selectedDict?.id },
[file as File],
); );
onSuccess?.(result); return result;
} catch (error) {
onError?.(error as Error);
}
}} }}
showUploadList={false} onExport={handleExportDictItems}
disabled={!selectedDict} onAdd={handleAddDictItem}
onChange={(info) => { onRefreshDicts={fetchDicts}
console.log(`info`, info); />,
if (info.file.status === 'done') {
message.success(`${info.file.name} 文件上传成功`);
// 重新加载字典项列表
setTimeout(() => {
actionRef.current?.reload();
}, 100);
// 重新加载字典列表以更新字典项数量
fetchDicts();
} else if (info.file.status === 'error') {
message.error(`${info.file.name} 文件上传失败`);
}
}}
key="import"
>
<Button size="small" icon={<UploadOutlined />}>
</Button>
</Upload>,
<Button
onClick={handleExportDictItems}
disabled={!selectedDict}
size="small"
key="export"
>
</Button>,
]} ]}
/> />
</Space> </Space>
</Content> </Content>
</Layout> </Layout>
{/* 添加字典项 Modal */} {/* 字典项 Modal添加或编辑 */}
<Modal <DictItemModal
title={`添加字典项 - ${selectedDict?.title || '未选择字典'}`} visible={isDictItemModalVisible}
open={isAddDictItemModalVisible} isEdit={isEditDictItem}
onOk={() => dictItemForm.submit()} editingData={editingDictItemData}
onCancel={() => setIsAddDictItemModalVisible(false)} selectedDict={selectedDict}
>
<Form
form={dictItemForm}
layout="vertical"
onFinish={handleAddDictItemFormSubmit}
>
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input placeholder="名称 (e.g., zyn)" />
</Form.Item>
<Form.Item
label="标题"
name="title"
rules={[{ required: true, message: '请输入标题' }]}
>
<Input placeholder="标题 (e.g., ZYN)" />
</Form.Item>
<Form.Item label="中文标题" name="titleCN">
<Input placeholder="中文标题 (e.g., 品牌)" />
</Form.Item>
<Form.Item label="简称 (可选)" name="shortName">
<Input placeholder="简称 (可选)" />
</Form.Item>
<Form.Item label="图片 (可选)" name="image">
<Input placeholder="图片链接 (可选)" />
</Form.Item>
<Form.Item label="值 (可选)" name="value">
<Input placeholder="值 (可选)" />
</Form.Item>
</Form>
</Modal>
{/* 编辑字典项 Modal */}
<Modal
title="编辑字典项"
open={isEditDictItemModalVisible}
onOk={() => dictItemForm.submit()}
onCancel={() => { onCancel={() => {
setIsEditDictItemModalVisible(false); setIsDictItemModalVisible(false);
setEditDictItemData(null); setEditingDictItemData(null);
}} }}
> onOk={handleDictItemModalOk}
<Form />
form={dictItemForm}
layout="vertical"
onFinish={handleEditDictItemFormSubmit}
>
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input placeholder="名称 (e.g., zyn)" />
</Form.Item>
<Form.Item
label="标题"
name="title"
rules={[{ required: true, message: '请输入标题' }]}
>
<Input placeholder="标题 (e.g., ZYN)" />
</Form.Item>
<Form.Item label="中文标题" name="titleCN">
<Input placeholder="中文标题 (e.g., 品牌)" />
</Form.Item>
<Form.Item label="简称 (可选)" name="shortName">
<Input placeholder="简称 (可选)" />
</Form.Item>
<Form.Item label="图片 (可选)" name="image">
<Input placeholder="图片链接 (可选)" />
</Form.Item>
<Form.Item label="值 (可选)" name="value">
<Input placeholder="值 (可选)" />
</Form.Item>
</Form>
</Modal>
{/* 添加字典 Modal */} {/* 添加字典 Modal */}
<Modal <Modal

View File

@ -0,0 +1,57 @@
import { ActionType } from '@ant-design/pro-components';
import { Space } from 'antd';
import React from 'react';
import DictItemAddButton from './DictItemAddButton';
import DictItemExportButton from './DictItemExportButton';
import DictItemImportButton from './DictItemImportButton';
// 字典项操作组合组件的属性接口
interface DictItemActionsProps {
// 当前选中的字典
selectedDict?: any;
// ProTable 的 actionRef用于刷新列表
actionRef?: React.MutableRefObject<ActionType | undefined>;
// 是否显示导出按钮(某些页面可能不需要导出功能)
showExport?: boolean;
// 导入字典项的回调函数(如果不提供,则使用默认的导入逻辑)
onImport?: (file: File, dictId: number) => Promise<any>;
// 导出字典项的回调函数
onExport?: () => Promise<void>;
// 添加字典项的回调函数
onAdd?: () => void;
// 刷新字典列表的回调函数(导入成功后可能需要刷新左侧字典列表)
onRefreshDicts?: () => void;
}
// 字典项操作组合组件(包含添加、导入、导出按钮)
const DictItemActions: React.FC<DictItemActionsProps> = ({
selectedDict,
actionRef,
showExport = true,
onImport,
onExport,
onAdd,
onRefreshDicts,
}) => {
return (
<Space>
{/* 添加字典项按钮 */}
{onAdd && <DictItemAddButton disabled={!selectedDict} onClick={onAdd} />}
{/* 导入字典项按钮 */}
<DictItemImportButton
selectedDict={selectedDict}
actionRef={actionRef}
onImport={onImport}
onRefreshDicts={onRefreshDicts}
/>
{/* 导出字典项按钮 */}
{showExport && (
<DictItemExportButton selectedDict={selectedDict} onExport={onExport} />
)}
</Space>
);
};
export default DictItemActions;

View File

@ -0,0 +1,24 @@
import { Button } from 'antd';
import React from 'react';
// 字典项添加按钮组件的属性接口
interface DictItemAddButtonProps {
// 是否禁用按钮
disabled?: boolean;
// 点击按钮时的回调函数
onClick: () => void;
}
// 字典项添加按钮组件
const DictItemAddButton: React.FC<DictItemAddButtonProps> = ({
disabled = false,
onClick,
}) => {
return (
<Button type="primary" size="small" onClick={onClick} disabled={disabled}>
</Button>
);
};
export default DictItemAddButton;

View File

@ -0,0 +1,53 @@
import { DownloadOutlined } from '@ant-design/icons';
import { Button, message } from 'antd';
import React from 'react';
// 字典项导出按钮组件的属性接口
interface DictItemExportButtonProps {
// 当前选中的字典
selectedDict?: any;
// 是否禁用按钮
disabled?: boolean;
// 自定义导出函数
onExport?: () => Promise<void>;
}
// 字典项导出按钮组件
const DictItemExportButton: React.FC<DictItemExportButtonProps> = ({
selectedDict,
disabled = false,
onExport,
}) => {
// 处理导出操作
const handleExport = async () => {
if (!selectedDict) {
message.warning('请先选择字典');
return;
}
try {
// 如果提供了自定义导出函数,则使用自定义函数
if (onExport) {
await onExport();
} else {
// 如果没有提供自定义导出函数,这里可以添加默认逻辑
message.warning('未提供导出函数');
}
} catch (error: any) {
message.error('导出字典项失败:' + (error.message || '未知错误'));
}
};
return (
<Button
size="small"
icon={<DownloadOutlined />}
onClick={handleExport}
disabled={disabled || !selectedDict}
>
</Button>
);
};
export default DictItemExportButton;

View File

@ -0,0 +1,124 @@
import { UploadOutlined } from '@ant-design/icons';
import { ActionType } from '@ant-design/pro-components';
import { request } from '@umijs/max';
import { Button, Upload, message } from 'antd';
import React from 'react';
// 字典项导入按钮组件的属性接口
interface DictItemImportButtonProps {
// 当前选中的字典
selectedDict?: any;
// ProTable 的 actionRef用于刷新列表
actionRef?: React.MutableRefObject<ActionType | undefined>;
// 是否禁用按钮
disabled?: boolean;
// 自定义导入函数,返回 Promise如果不提供则使用默认的导入逻辑
onImport?: (file: File, dictId: number) => Promise<any>;
// 导入成功后刷新字典列表的回调函数
onRefreshDicts?: () => void;
}
// 字典项导入按钮组件
const DictItemImportButton: React.FC<DictItemImportButtonProps> = ({
selectedDict,
actionRef,
disabled = false,
onImport,
onRefreshDicts,
}) => {
// 处理导入文件上传
const handleImportUpload = async (options: any) => {
console.log(options);
const { file, onSuccess, onError } = options;
try {
// 条件判断,确保已选择字典
if (!selectedDict?.id) {
throw new Error('请先选择字典');
}
let result: any;
// 如果提供了自定义导入函数,则使用自定义函数
if (onImport) {
result = await onImport(file as File, selectedDict.id);
} else {
// 使用默认的导入逻辑,将 dictId 传入到 body 中
const formData = new FormData();
formData.append('file', file as File);
formData.append('dictId', String(selectedDict.id));
result = await request('/api/dict/item/import', {
method: 'POST',
body: formData,
});
}
// 检查返回结果是否包含 success 字段
if (result && result.success !== undefined && !result.success) {
throw new Error(result.message || '导入失败');
}
onSuccess?.(result);
// 显示导入结果详情
showImportResult(result);
// 导入成功后刷新列表
setTimeout(() => {
actionRef?.current?.reload();
onRefreshDicts?.();
}, 100);
} catch (error: any) {
onError?.(error as Error);
}
};
// 显示导入结果详情
const showImportResult = (result: any) => {
// 从 result.data 中获取实际数据(因为后端返回格式为 { success: true, data: {...} }
const data = result.data || result;
const { total, processed, updated, created, errors } = data;
// 构建结果消息
let messageContent = `总共处理 ${total} 条,成功处理 ${processed} 条,新增 ${created} 条,更新 ${updated}`;
if (errors && errors.length > 0) {
messageContent += `,失败 ${errors.length}`;
// 显示错误详情
const errorDetails = errors
.map((err: any) => `${err.identifier}: ${err.error}`)
.join('\n');
message.warning(messageContent + '\n\n错误详情: \n' + errorDetails);
} else {
message.success(messageContent);
}
};
// 处理上传状态变化
const handleUploadChange = (info: any) => {
if (info.file.status === 'done') {
message.success(`${info.file.name} 文件上传成功`);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} 文件上传失败`);
}
};
return (
<Upload
name="file"
action={undefined}
customRequest={handleImportUpload}
showUploadList={false}
disabled={disabled || !selectedDict}
onChange={handleUploadChange}
>
<Button
size="small"
icon={<UploadOutlined />}
disabled={disabled || !selectedDict}
>
</Button>
</Upload>
);
};
export default DictItemImportButton;

View File

@ -0,0 +1,96 @@
import { Form, Input, Modal } from 'antd';
import React, { useEffect } from 'react';
interface DictItemModalProps {
// 模态框是否可见
visible: boolean;
// 是否为编辑模式
isEdit: boolean;
// 编辑时的字典项数据
editingData?: any;
// 当前选中的字典
selectedDict?: any;
// 取消回调
onCancel: () => void;
// 确认回调
onOk: (values: any) => Promise<void>;
}
const DictItemModal: React.FC<DictItemModalProps> = ({
visible,
isEdit,
editingData,
selectedDict,
onCancel,
onOk,
}) => {
const [form] = Form.useForm();
// 当模态框打开或编辑数据变化时,重置或设置表单值
useEffect(() => {
if (visible) {
if (isEdit && editingData) {
// 编辑模式,设置表单值为编辑数据
form.setFieldsValue(editingData);
} else {
// 新增模式,重置表单
form.resetFields();
}
}
}, [visible, isEdit, editingData, form]);
// 表单提交处理
const handleOk = async () => {
try {
const values = await form.validateFields();
await onOk(values);
} catch (error) {
// 表单验证失败,不关闭模态框
}
};
return (
<Modal
title={
isEdit
? '编辑字典项'
: `添加字典项 - ${selectedDict?.title || '未选择字典'}`
}
open={visible}
onOk={handleOk}
onCancel={onCancel}
destroyOnClose
>
<Form form={form} layout="vertical">
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input placeholder="名称 (e.g., zyn)" />
</Form.Item>
<Form.Item
label="标题"
name="title"
rules={[{ required: true, message: '请输入标题' }]}
>
<Input placeholder="标题 (e.g., ZYN)" />
</Form.Item>
<Form.Item label="中文标题" name="titleCN">
<Input placeholder="中文标题 (e.g., 品牌)" />
</Form.Item>
<Form.Item label="简称 (可选)" name="shortName">
<Input placeholder="简称 (可选)" />
</Form.Item>
<Form.Item label="图片 (可选)" name="image">
<Input placeholder="图片链接 (可选)" />
</Form.Item>
<Form.Item label="值 (可选)" name="value">
<Input placeholder="值 (可选)" />
</Form.Item>
</Form>
</Modal>
);
};
export default DictItemModal;

View File

@ -2,6 +2,7 @@ import styles from '../../../style/order-list.css';
import InternationalPhoneInput from '@/components/InternationalPhoneInput'; import InternationalPhoneInput from '@/components/InternationalPhoneInput';
import SyncForm from '@/components/SyncForm'; import SyncForm from '@/components/SyncForm';
import { showSyncResult, SyncResultData } from '@/components/SyncResultMessage';
import { ORDER_STATUS_ENUM } from '@/constants'; import { ORDER_STATUS_ENUM } from '@/constants';
import { HistoryOrder } from '@/pages/Statistics/Order'; import { HistoryOrder } from '@/pages/Statistics/Order';
import { import {
@ -21,6 +22,7 @@ import {
ordercontrollerGetorders, ordercontrollerGetorders,
ordercontrollerRefundorder, ordercontrollerRefundorder,
ordercontrollerSyncorderbyid, ordercontrollerSyncorderbyid,
ordercontrollerSyncorders,
ordercontrollerUpdateorderitems, ordercontrollerUpdateorderitems,
} from '@/servers/api/order'; } from '@/servers/api/order';
import { productcontrollerSearchproducts } from '@/servers/api/product'; import { productcontrollerSearchproducts } from '@/servers/api/product';
@ -73,7 +75,6 @@ import {
Tag, Tag,
} from 'antd'; } from 'antd';
import React, { useMemo, useRef, useState } from 'react'; import React, { useMemo, useRef, useState } from 'react';
import { request, useParams } from '@umijs/max';
import RelatedOrders from '../../Subscription/Orders/RelatedOrders'; import RelatedOrders from '../../Subscription/Orders/RelatedOrders';
const ListPage: React.FC = () => { const ListPage: React.FC = () => {
@ -250,6 +251,7 @@ const ListPage: React.FC = () => {
{ {
title: '状态', title: '状态',
dataIndex: 'orderStatus', dataIndex: 'orderStatus',
hideInSearch: true,
valueType: 'select', valueType: 'select',
valueEnum: ORDER_STATUS_ENUM, valueEnum: ORDER_STATUS_ENUM,
}, },
@ -339,15 +341,18 @@ const ListPage: React.FC = () => {
message.error('站点ID或外部订单ID不存在'); message.error('站点ID或外部订单ID不存在');
return; return;
} }
const { success, message: errMsg } = const {
await ordercontrollerSyncorderbyid({ success,
message: errMsg,
data,
} = await ordercontrollerSyncorderbyid({
siteId: record.siteId, siteId: record.siteId,
orderId: record.externalOrderId, orderId: record.externalOrderId,
}); });
if (!success) { if (!success) {
throw new Error(errMsg); throw new Error(errMsg);
} }
message.success('同步成功'); showSyncResult(data as SyncResultData, '订单');
actionRef.current?.reload(); actionRef.current?.reload();
} catch (error: any) { } catch (error: any) {
message.error(error?.message || '同步失败'); message.error(error?.message || '同步失败');
@ -467,16 +472,20 @@ const ListPage: React.FC = () => {
defaultPageSize: 10, defaultPageSize: 10,
}} }}
toolBarRender={() => [ toolBarRender={() => [
<CreateOrder tableRef={actionRef} />, // <CreateOrder tableRef={actionRef} />,
<SyncForm <SyncForm
onFinish={async (values: any) => { onFinish={async (values: any) => {
try { try {
const { success, message: errMsg } = const {
await ordercontrollerSyncorderbyid(values); success,
message: errMsg,
data,
} = await ordercontrollerSyncorders(values);
if (!success) { if (!success) {
throw new Error(errMsg); throw new Error(errMsg);
} }
message.success('同步成功'); // 使用 showSyncResult 函数显示详细的同步结果
showSyncResult(data as SyncResultData, '订单');
actionRef.current?.reload(); actionRef.current?.reload();
} catch (error: any) { } catch (error: any) {
message.error(error?.message || '同步失败'); message.error(error?.message || '同步失败');
@ -495,25 +504,15 @@ const ListPage: React.FC = () => {
title="批量导出" title="批量导出"
description="确认导出选中的订单吗?" description="确认导出选中的订单吗?"
onConfirm={async () => { onConfirm={async () => {
console.log(selectedRowKeys);
try { try {
const res = await request('/order/order/export', { const { success, message: errMsg } =
method: 'GET', await ordercontrollerExportorder({
params: {
ids: selectedRowKeys, ids: selectedRowKeys,
}
}); });
if (res?.success && res?.data?.csv) { if (!success) {
const blob = new Blob([res.data.csv], { type: 'text/csv;charset=utf-8;' }); throw new Error(errMsg);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'customers.csv';
a.click();
URL.revokeObjectURL(url);
} else {
message.error(res.message || '导出失败');
} }
message.success('导出成功');
actionRef.current?.reload(); actionRef.current?.reload();
setSelectedRowKeys([]); setSelectedRowKeys([]);
} catch (error: any) { } catch (error: any) {
@ -521,10 +520,14 @@ const ListPage: React.FC = () => {
} }
}} }}
> >
<Button type="primary" ghost> <Button
type="primary"
disabled={selectedRowKeys.length === 0}
ghost
>
</Button> </Button>
</Popconfirm> </Popconfirm>,
]} ]}
request={async ({ date, ...param }: any) => { request={async ({ date, ...param }: any) => {
if (param.status === 'all') { if (param.status === 'all') {
@ -622,15 +625,18 @@ const Detail: React.FC<{
message.error('站点ID或外部订单ID不存在'); message.error('站点ID或外部订单ID不存在');
return; return;
} }
const { success, message: errMsg } = const {
await ordercontrollerSyncorderbyid({ success,
message: errMsg,
data,
} = await ordercontrollerSyncorderbyid({
siteId: record.siteId, siteId: record.siteId,
orderId: record.externalOrderId, orderId: record.externalOrderId,
}); });
if (!success) { if (!success) {
throw new Error(errMsg); throw new Error(errMsg);
} }
message.success('同步成功'); showSyncResult(data as SyncResultData, '订单');
tableRef.current?.reload(); tableRef.current?.reload();
} catch (error: any) { } catch (error: any) {
message.error(error?.message || '同步失败'); message.error(error?.message || '同步失败');
@ -2122,12 +2128,12 @@ const SalesChange: React.FC<{
params={{}} params={{}}
request={async ({ keyWords }) => { request={async ({ keyWords }) => {
try { try {
const { data } = await wpproductcontrollerSearchproducts({ const { data } = await productcontrollerSearchproducts({
name: keyWords, name: keyWords,
}); });
return data?.map((item) => { return data?.map((item) => {
return { return {
label: `${item.name}`, label: `${item.name} - ${item.nameCn}`,
value: item?.sku, value: item?.sku,
}; };
}); });

View File

@ -1,22 +1,14 @@
import { UploadOutlined } from '@ant-design/icons'; import * as dictApi from '@/servers/api/dict';
import { import {
ActionType, ActionType,
PageContainer, PageContainer,
ProTable, ProTable,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { import { Button, Input, Layout, Space, Table, message } from 'antd';
Button,
Form,
Input,
Layout,
Modal,
Space,
Table,
Upload,
message,
} from 'antd';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import DictItemActions from '../../Dict/components/DictItemActions';
import DictItemModal from '../../Dict/components/DictItemModal';
const { Sider, Content } = Layout; const { Sider, Content } = Layout;
@ -32,10 +24,84 @@ const AttributePage: React.FC = () => {
// 右侧字典项 ProTable 的引用 // 右侧字典项 ProTable 的引用
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
// 字典项新增/编辑模态框控制 // 字典项模态框状态(由 DictItemModal 组件管理)
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false); const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
const [editingDictItem, setEditingDictItem] = useState<any>(null); const [isEditDictItem, setIsEditDictItem] = useState(false);
const [dictItemForm] = Form.useForm(); const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
// 导出字典项数据
const handleExportDictItems = async () => {
// 条件判断,确保已选择字典
if (!selectedDict) {
message.warning('请先选择字典');
return;
}
try {
// 获取当前字典的所有数据
const response = await request('/dict/items', {
params: {
dictId: selectedDict.id,
},
});
// 确保返回的是数组
const data = Array.isArray(response) ? response : response?.data || [];
// 条件判断,检查是否有数据可导出
if (data.length === 0) {
message.warning('当前字典没有数据可导出');
return;
}
// 将数据转换为CSV格式
const headers = [
'name',
'title',
'titleCN',
'value',
'sort',
'image',
'shortName',
];
const csvContent = [
headers.join(','),
...data.map((item: any) =>
headers
.map((header) => {
const value = item[header] || '';
// 条件判断,如果值包含逗号或引号,需要转义
if (
typeof value === 'string' &&
(value.includes(',') || value.includes('"'))
) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
})
.join(','),
),
].join('\n');
// 创建blob并下载
const blob = new Blob(['\ufeff' + csvContent], {
// 添加BOM以支持中文
type: 'text/csv;charset=utf-8',
});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${selectedDict.name}_dict_items.csv`);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
message.success(`成功导出 ${data.length} 条数据`);
} catch (error: any) {
message.error('导出字典项失败:' + (error.message || '未知错误'));
}
};
const fetchDicts = async (title?: string) => { const fetchDicts = async (title?: string) => {
setLoadingDicts(true); setLoadingDicts(true);
@ -65,24 +131,24 @@ const AttributePage: React.FC = () => {
// 打开添加字典项模态框 // 打开添加字典项模态框
const handleAddDictItem = () => { const handleAddDictItem = () => {
setEditingDictItem(null); setIsEditDictItem(false);
dictItemForm.resetFields(); setEditingDictItemData(null);
setIsDictItemModalVisible(true); setIsDictItemModalVisible(true);
}; };
// 打开编辑字典项模态框 // 打开编辑字典项模态框
const handleEditDictItem = (item: any) => { const handleEditDictItem = (item: any) => {
setEditingDictItem(item); setIsEditDictItem(true);
dictItemForm.setFieldsValue(item); setEditingDictItemData(item);
setIsDictItemModalVisible(true); setIsDictItemModalVisible(true);
}; };
// 字典项表单提交(新增或编辑) // 字典项表单提交(新增或编辑)
const handleDictItemFormSubmit = async (values: any) => { const handleDictItemFormSubmit = async (values: any) => {
try { try {
if (editingDictItem) { if (isEditDictItem && editingDictItemData) {
// 条件判断,存在编辑项则执行更新 // 条件判断,存在编辑项则执行更新
await request(`/dict/item/${editingDictItem.id}`, { await request(`/dict/item/${editingDictItemData.id}`, {
method: 'PUT', method: 'PUT',
data: values, data: values,
}); });
@ -98,7 +164,7 @@ const AttributePage: React.FC = () => {
setIsDictItemModalVisible(false); setIsDictItemModalVisible(false);
actionRef.current?.reload(); // 刷新 ProTable actionRef.current?.reload(); // 刷新 ProTable
} catch (error) { } catch (error) {
message.error(editingDictItem ? '更新失败' : '添加失败'); message.error(isEditDictItem ? '更新失败' : '添加失败');
} }
}; };
@ -296,85 +362,45 @@ const AttributePage: React.FC = () => {
size="small" size="small"
key={selectedDict?.id} key={selectedDict?.id}
headerTitle={ headerTitle={
<Space> <DictItemActions
<Button selectedDict={selectedDict}
type="primary" actionRef={actionRef}
size="small" showExport={true}
onClick={handleAddDictItem} onImport={async (file: File, dictId: number) => {
disabled={!selectedDict} // 创建 FormData 对象
> const formData = new FormData();
// 添加文件到 FormData
</Button> formData.append('file', file);
<Upload // 添加字典 ID 到 FormData
name="file" formData.append('dictId', String(dictId));
action={`/dict/item/import`} // 调用导入字典项的 API
data={{ dictId: selectedDict?.id }} const response = await dictApi.dictcontrollerImportdictitems(
showUploadList={false} formData,
disabled={!selectedDict} );
onChange={(info) => { // 返回 JSON 响应
// 条件判断,上传状态处理 return await response.json();
if (info.file.status === 'done') {
message.success(`${info.file.name} 文件上传成功`);
actionRef.current?.reload();
} else if (info.file.status === 'error') {
message.error(`${info.file.name} 文件上传失败`);
}
}} }}
> onExport={handleExportDictItems}
<Button onAdd={handleAddDictItem}
size="small" onRefreshDicts={fetchDicts}
icon={<UploadOutlined />} />
disabled={!selectedDict}
>
</Button>
</Upload>
</Space>
} }
/> />
</Content> </Content>
</Layout> </Layout>
<Modal {/* 字典项 Modal添加或编辑 */}
title={editingDictItem ? '编辑字典项' : '添加字典项'} <DictItemModal
open={isDictItemModalVisible} visible={isDictItemModalVisible}
onOk={() => dictItemForm.submit()} isEdit={isEditDictItem}
onCancel={() => setIsDictItemModalVisible(false)} editingData={editingDictItemData}
destroyOnClose selectedDict={selectedDict}
> onCancel={() => {
<Form setIsDictItemModalVisible(false);
form={dictItemForm} setEditingDictItemData(null);
layout="vertical" }}
onFinish={handleDictItemFormSubmit} onOk={handleDictItemFormSubmit}
> />
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input size="small" placeholder="名称 (e.g., zyn)" />
</Form.Item>
<Form.Item
label="标题"
name="title"
rules={[{ required: true, message: '请输入标题' }]}
>
<Input size="small" placeholder="标题 (e.g., ZYN)" />
</Form.Item>
<Form.Item label="中文标题" name="titleCN">
<Input size="small" placeholder="中文标题 (e.g., 品牌)" />
</Form.Item>
<Form.Item label="简称 (可选)" name="shortName">
<Input size="small" placeholder="简称 (可选)" />
</Form.Item>
<Form.Item label="图片 (可选)" name="image">
<Input size="small" placeholder="图片链接 (可选)" />
</Form.Item>
<Form.Item label="值 (可选)" name="value">
<Input size="small" placeholder="值 (可选)" />
</Form.Item>
</Form>
</Modal>
</PageContainer> </PageContainer>
); );
}; };

View File

@ -246,13 +246,7 @@ const CreateForm: React.FC<{
placeholder="请输入SKU" placeholder="请输入SKU"
rules={[{ required: true, message: '请输入SKU' }]} rules={[{ required: true, message: '请输入SKU' }]}
/> />
<ProFormSelect
name="siteSkus"
label="站点 SKU 列表"
width="md"
mode="tags"
placeholder="输入站点 SKU,回车添加"
/>
<Button style={{ marginTop: '32px' }} onClick={handleGenerateSku}> <Button style={{ marginTop: '32px' }} onClick={handleGenerateSku}>
</Button> </Button>
@ -265,6 +259,14 @@ const CreateForm: React.FC<{
</Tag> </Tag>
)} )}
</ProForm.Group> </ProForm.Group>
<ProFormSelect
name="siteSkus"
initialValue={[]}
label="站点 SKU 列表"
width="md"
mode="tags"
placeholder="输入站点 SKU,回车添加"
/>
<ProForm.Group> <ProForm.Group>
<ProFormText <ProFormText
name="name" name="name"

View File

@ -0,0 +1,183 @@
import { showBatchOperationResult } from '@/utils/showResult';
import { sitecontrollerAll } from '@/servers/api/site';
import { templatecontrollerRendertemplate } from '@/servers/api/template';
import { productcontrollerBatchsynctosite } from '@/servers/api/product';
import {
ModalForm,
ProFormSelect,
ProFormDependency,
ProFormText,
} from '@ant-design/pro-components';
import { App, Button, Tag } from 'antd';
import React, { useEffect, useRef, useState } from 'react';
interface SyncToSiteModalProps {
visible: boolean;
onClose: () => void;
products: API.Product[];
site?: any;
onSuccess: () => void;
}
const SyncToSiteModal: React.FC<SyncToSiteModalProps> = ({
visible,
onClose,
products,
site,
onSuccess,
}) => {
const { message } = App.useApp();
const [sites, setSites] = useState<any[]>([]);
const formRef = useRef<any>();
// 生成单个产品的站点SKU
const generateSingleSiteSku = async (
currentSite: API.Site,
product: API.Product,
): Promise<string> => {
try {
console.log('site', currentSite)
const { data: renderedSku } = await templatecontrollerRendertemplate(
{ name: 'site.product.sku' },
{ site: currentSite, product },
);
return renderedSku || `${currentSite.skuPrefix || ''}${product.sku || ''}`;
} catch (error) {
return `${currentSite.skuPrefix || ''}${product.sku || ''}`;
}
};
// 生成所有产品的站点SKU并设置到表单
const generateAndSetSiteSkus = async (currentSite: any) => {
const siteSkus: Record<string, string> = {};
for (const product of products) {
const siteSku = await generateSingleSiteSku(currentSite, product);
siteSkus[product.id] = siteSku;
}
// 设置表单值
formRef.current?.setFieldsValue({ siteSkus });
};
useEffect(() => {
if (visible) {
sitecontrollerAll().then((res: any) => {
const siteList = res?.data || [];
setSites(siteList);
// 如果有站点列表默认选择第一个站点或传入的site
const targetSite = site || (siteList.length > 0 ? siteList[0] : null);
if (targetSite) {
// 使用 setTimeout 确保 formRef 已经准备好
setTimeout(() => {
if (formRef.current) {
formRef.current.setFieldsValue({ siteId: targetSite.id });
// 自动生成所有产品的站点 SKU
generateAndSetSiteSkus(targetSite);
}
}, 0);
}
});
}
}, [visible, products, site]);
return (
<ModalForm
title={`同步到站点 (${products.length} 项)`}
open={visible}
onOpenChange={(open) => !open && onClose()}
modalProps={{ destroyOnClose: true }}
formRef={formRef}
onValuesChange={async (changedValues) => {
if ('siteId' in changedValues && changedValues.siteId) {
const siteId = changedValues.siteId;
const currentSite = sites.find((s: any) => s.id === siteId) || {};
// 站点改变时重新生成所有产品的站点SKU
generateAndSetSiteSkus(currentSite);
}
}}
onFinish={async (values) => {
if (!values.siteId) return false;
try {
const siteSkusMap = values.siteSkus || {};
const data = products.map((product) => ({
productId: product.id,
siteSku: siteSkusMap[product.id] || `${values.siteId}_${product.sku}`,
}));
const result = await productcontrollerBatchsynctosite({
siteId: values.siteId,
data,
});
showBatchOperationResult(result, '同步到站点');
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: '请选择站点' }]}
/>
{products.map((row) => (
<ProFormDependency key={row.id} name={['siteId']}>
{({ siteId }) => (
<div style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 8 }}>
<div style={{ minWidth: 220 }}>SKU: {row.sku || '-'}</div>
<div style={{ minWidth: 150 }}>
SKU:{' '}
{row.siteSkus && row.siteSkus.length > 0
? row.siteSkus.map((siteSku: string, idx: number) => (
<Tag key={idx} color="cyan">
{siteSku}
</Tag>
))
: '-'}
</div>
</div>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<div style={{ flex: 1 }}>
<ProFormText
name={['siteSkus', row.id]}
label={`商品 ${row.sku} 站点SKU`}
placeholder="请输入站点SKU"
fieldProps={{
onChange: (e) => {
// 手动输入时更新表单值
const currentValues = formRef.current?.getFieldValue('siteSkus') || {};
currentValues[row.id] = e.target.value;
formRef.current?.setFieldsValue({ siteSkus: currentValues });
},
}}
/>
</div>
<Button
type="primary"
size="small"
onClick={async () => {
if (siteId) {
const currentSite = sites.find((s: any) => s.id === siteId) || {};
const siteSku = await generateSingleSiteSku(currentSite, row);
const currentValues = formRef.current?.getFieldValue('siteSkus') || {};
currentValues[row.id] = siteSku;
formRef.current?.setFieldsValue({ siteSkus: currentValues });
}
}}
>
</Button>
</div>
</div>
)}
</ProFormDependency>
))}
</ModalForm>
);
};
export default SyncToSiteModal;

View File

@ -1,15 +1,12 @@
import { import {
productcontrollerBatchdeleteproduct, productcontrollerBatchdeleteproduct,
productcontrollerBatchupdateproduct, productcontrollerBatchupdateproduct,
productcontrollerBindproductsiteskus,
productcontrollerDeleteproduct, productcontrollerDeleteproduct,
productcontrollerGetcategoriesall, productcontrollerGetcategoriesall,
productcontrollerGetproductcomponents, productcontrollerGetproductcomponents,
productcontrollerGetproductlist, productcontrollerGetproductlist,
productcontrollerUpdatenamecn, productcontrollerUpdatenamecn
} from '@/servers/api/product'; } from '@/servers/api/product';
import { sitecontrollerAll } from '@/servers/api/site';
import { siteapicontrollerGetproducts } from '@/servers/api/siteApi';
import { import {
ActionType, ActionType,
ModalForm, ModalForm,
@ -17,13 +14,14 @@ import {
ProColumns, ProColumns,
ProFormSelect, ProFormSelect,
ProFormText, ProFormText,
ProTable, ProTable
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd'; import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import CreateForm from './CreateForm'; import CreateForm from './CreateForm';
import EditForm from './EditForm'; import EditForm from './EditForm';
import SyncToSiteModal from './SyncToSiteModal';
const NameCn: React.FC<{ const NameCn: React.FC<{
id: number; id: number;
@ -166,261 +164,14 @@ const BatchEditModal: React.FC<{
); );
}; };
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 List: React.FC = () => {
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
// 状态:存储当前选中的行 // 状态:存储当前选中的行
const [selectedRows, setSelectedRows] = React.useState<API.Product[]>([]); const [selectedRows, setSelectedRows] = React.useState<API.Product[]>([]);
const [batchEditModalVisible, setBatchEditModalVisible] = useState(false); const [batchEditModalVisible, setBatchEditModalVisible] = useState(false);
const [syncProducts, setSyncProducts] = useState<API.Product[]>([]);
const [syncModalVisible, setSyncModalVisible] = useState(false); const [syncModalVisible, setSyncModalVisible] = useState(false);
const [syncProductIds, setSyncProductIds] = useState<number[]>([]);
const { message } = App.useApp(); const { message } = App.useApp();
// 导出产品 CSV(带认证请求) // 导出产品 CSV(带认证请求)
@ -460,7 +211,7 @@ const List: React.FC = () => {
<> <>
{record.siteSkus?.map((siteSku, index) => ( {record.siteSkus?.map((siteSku, index) => (
<Tag key={index} color="cyan"> <Tag key={index} color="cyan">
{siteSku.siteSku} {siteSku}
</Tag> </Tag>
))} ))}
</> </>
@ -564,7 +315,7 @@ const List: React.FC = () => {
<Button <Button
type="link" type="link"
onClick={() => { onClick={() => {
setSyncProductIds([record.id]); setSyncProducts([record]);
setSyncModalVisible(true); setSyncModalVisible(true);
}} }}
> >
@ -693,7 +444,7 @@ const List: React.FC = () => {
<Button <Button
disabled={selectedRows.length <= 0} disabled={selectedRows.length <= 0}
onClick={() => { onClick={() => {
setSyncProductIds(selectedRows.map((row) => row.id)); setSyncProducts(selectedRows);
setSyncModalVisible(true); setSyncModalVisible(true);
}} }}
> >
@ -756,7 +507,7 @@ const List: React.FC = () => {
columns={columns} columns={columns}
expandable={{ expandable={{
expandedRowRender: (record) => ( expandedRowRender: (record) => (
<WpProductInfo <SiteProductInfo
skus={(record.siteSkus as string[]) || []} skus={(record.siteSkus as string[]) || []}
record={record} record={record}
parentTableRef={actionRef} parentTableRef={actionRef}
@ -788,8 +539,7 @@ const List: React.FC = () => {
<SyncToSiteModal <SyncToSiteModal
visible={syncModalVisible} visible={syncModalVisible}
onClose={() => setSyncModalVisible(false)} onClose={() => setSyncModalVisible(false)}
productIds={syncProductIds} products={syncProducts}
productRows={selectedRows}
onSuccess={() => { onSuccess={() => {
setSyncModalVisible(false); setSyncModalVisible(false);
setSelectedRows([]); setSelectedRows([]);

View File

@ -3,6 +3,7 @@ import {
productcontrollerGetcategoryattributes, productcontrollerGetcategoryattributes,
productcontrollerGetproductlist, productcontrollerGetproductlist,
} from '@/servers/api/product'; } from '@/servers/api/product';
import { DownloadOutlined } from '@ant-design/icons';
import { import {
ActionType, ActionType,
PageContainer, PageContainer,
@ -201,6 +202,92 @@ const PermutationPage: React.FC = () => {
setCreateModalVisible(true); setCreateModalVisible(true);
}; };
// 处理导出CSV功能
const handleExport = () => {
try {
// 如果没有数据则提示用户
if (permutations.length === 0) {
message.warning('暂无数据可导出');
return;
}
// 生成CSV表头包含所有属性列和SKU列
const headers = [
...attributes.map((attr) => attr.title || attr.name),
'SKU',
'状态',
];
// 生成CSV数据行
const rows = permutations.map((perm) => {
const key = generateKeyFromPermutation(perm);
const product = existingProducts.get(key);
// 获取每个属性值
const attrValues = attributes.map((attr) => {
const value = perm[attr.name]?.name || '';
return value;
});
// 获取SKU和状态
const sku = product?.sku || '';
const status = product ? '已存在' : '未创建';
return [...attrValues, sku, status];
});
// 将表头和数据行合并
const csvContent = [headers, ...rows]
.map((row) =>
// 处理CSV中的特殊字符逗号、双引号、换行符
row
.map((cell) => {
const cellStr = String(cell || '');
// 如果包含逗号、双引号或换行符,需要用双引号包裹,并将内部的双引号转义
if (
cellStr.includes(',') ||
cellStr.includes('"') ||
cellStr.includes('\n')
) {
return `"${cellStr.replace(/"/g, '""')}"`;
}
return cellStr;
})
.join(','),
)
.join('\n');
// 添加BOM以支持Excel正确显示中文
const BOM = '\uFEFF';
const blob = new Blob([BOM + csvContent], {
type: 'text/csv;charset=utf-8;',
});
// 创建下载链接并触发下载
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
// 生成文件名(包含当前分类名称和日期)
const category = categories.find((c) => c.id === categoryId);
const categoryName = category?.name || '产品';
const date = new Date().toISOString().slice(0, 10);
link.setAttribute('download', `${categoryName}_排列组合_${date}.csv`);
document.body.appendChild(link);
link.click();
// 清理
document.body.removeChild(link);
URL.revokeObjectURL(url);
message.success('导出成功');
} catch (error) {
console.error('导出失败:', error);
message.error('导出失败');
}
};
const columns: any[] = [ const columns: any[] = [
...attributes.map((attr) => ({ ...attributes.map((attr) => ({
title: attr.title || attr.name, title: attr.title || attr.name,
@ -317,7 +404,16 @@ const PermutationPage: React.FC = () => {
}} }}
scroll={{ x: 'max-content' }} scroll={{ x: 'max-content' }}
search={false} search={false}
toolBarRender={false} toolBarRender={() => [
<Button
key="export"
type="default"
icon={<DownloadOutlined />}
onClick={handleExport}
>
</Button>,
]}
/> />
)} )}
</ProCard> </ProCard>

View File

@ -0,0 +1,461 @@
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;

View File

@ -1,13 +1,11 @@
import { productcontrollerGetproductlist } from '@/servers/api/product'; import { showBatchOperationResult } from '@/utils/showResult';
import { templatecontrollerGettemplatebyname } from '@/servers/api/template';
import { EditOutlined, SyncOutlined } from '@ant-design/icons';
import { import {
ActionType, productcontrollerBatchsynctosite,
ModalForm, productcontrollerGetproductlist,
ProColumns, productcontrollerSynctosite,
ProFormText, } from '@/servers/api/product';
ProTable, import { EditOutlined, SyncOutlined } from '@ant-design/icons';
} from '@ant-design/pro-components'; import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { import {
Button, Button,
@ -21,39 +19,35 @@ import {
} from 'antd'; } from 'antd';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import EditForm from '../List/EditForm'; import EditForm from '../List/EditForm';
import SiteProductCell from './SiteProductCell';
// 定义站点接口 // 定义站点接口
interface Site { interface Site {
id: string; id: number;
name: string; name: string;
skuPrefix?: string; skuPrefix?: string;
isDisabled?: boolean; isDisabled?: boolean;
} }
// 定义WordPress商品接口 // 定义本地产品接口(与后端 Product 实体匹配)
interface WpProduct { interface SiteProduct {
id?: number; id: number;
externalProductId?: string;
sku: string; sku: string;
name: string; name: string;
price: string; nameCn: string;
regular_price?: string; shortDescription?: string;
sale_price?: string; description?: string;
stock_quantity: number; price: number;
stockQuantity?: number; promotionPrice: number;
status: string; type: string;
categoryId?: number;
category?: any;
attributes?: any[]; attributes?: any[];
constitution?: { sku: string; quantity: number }[]; components?: any[];
} siteSkus: string[];
source: number;
// 扩展本地产品接口,包含对应的 WP 产品信息 createdAt: Date;
interface ProductWithWP extends API.Product { updatedAt: Date;
wpProducts: Record<string, WpProduct>;
attributes?: any[];
siteSkus?: Array<{
siteSku: string;
[key: string]: any;
}>;
} }
// 定义API响应接口 // 定义API响应接口
@ -79,19 +73,9 @@ const getSites = async (): Promise<ApiResponse<Site>> => {
}; };
}; };
const getWPProducts = async (): Promise<ApiResponse<WpProduct>> => {
return request('/product/wp-products', {
method: 'GET',
});
};
const ProductSyncPage: React.FC = () => { const ProductSyncPage: React.FC = () => {
const [sites, setSites] = useState<Site[]>([]); const [sites, setSites] = useState<Site[]>([]);
// 存储所有 WP 产品,用于查找匹配。 Key: SKU (包含前缀)
const [wpProductMap, setWpProductMap] = useState<Map<string, WpProduct>>(
new Map(),
);
const [skuTemplate, setSkuTemplate] = useState<string>('');
const [initialLoading, setInitialLoading] = useState(true); const [initialLoading, setInitialLoading] = useState(true);
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const [selectedSiteId, setSelectedSiteId] = useState<string>(''); const [selectedSiteId, setSelectedSiteId] = useState<string>('');
@ -104,102 +88,46 @@ const ProductSyncPage: React.FC = () => {
errors: string[]; errors: string[];
}>({ success: 0, failed: 0, errors: [] }); }>({ success: 0, failed: 0, errors: [] });
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [selectedRows, setSelectedRows] = useState<ProductWithWP[]>([]); const [selectedRows, setSelectedRows] = useState<SiteProduct[]>([]);
// 初始化加载站点列表
// 初始化数据:获取站点和所有 WP 产品
useEffect(() => { useEffect(() => {
const fetchData = async () => { const initializeData = async () => {
try { try {
setInitialLoading(true); // 获取站点列表
// 获取所有站点 const sitesRes = await getSites();
const sitesResponse = await getSites(); if (sitesRes.success && sitesRes.data.length > 0) {
const rawSiteList = sitesResponse.data || []; setSites(sitesRes.data);
// 过滤掉已禁用的站点
const siteList: Site[] = rawSiteList.filter((site) => !site.isDisabled);
setSites(siteList);
// 获取所有 WordPress 商品
const wpProductsResponse = await getWPProducts();
const wpProductList: WpProduct[] = wpProductsResponse.data || [];
// 构建 WP 产品 MapKey 为 SKU
const map = new Map<string, WpProduct>();
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) { } catch (error) {
message.error('获取基础数据失败,请重试'); console.error('初始化数据失败:', error);
console.error('Error fetching data:', error); message.error('初始化数据失败');
} finally { } finally {
setInitialLoading(false); setInitialLoading(false);
} }
}; };
fetchData(); initializeData();
}, []); }, []);
// 同步产品到站点
const syncProductToSite = async ( const syncProductToSite = async (
values: any, values: any,
record: ProductWithWP, record: SiteProduct,
site: Site, site: Site,
wpProductId?: string, siteProductId?: string,
) => { ) => {
try { try {
const hide = message.loading('正在同步...', 0); 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; // 使用 productcontrollerSynctosite API 同步产品到站点
if (wpProductId) { const res = await productcontrollerSynctosite({
res = await request(`/site-api/${site.id}/products/${wpProductId}`, { productId: Number(record.id),
method: 'PUT', siteId: Number(site.id),
data, } as any);
});
} else {
res = await request(`/site-api/${site.id}/products`, {
method: 'POST',
data,
});
}
console.log('res', res);
if (!res.success) { if (!res.success) {
hide(); hide();
throw new Error(res.message || '同步失败'); 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(); hide();
message.success('同步成功'); message.success('同步成功');
@ -207,12 +135,11 @@ const ProductSyncPage: React.FC = () => {
} catch (error: any) { } catch (error: any) {
message.error('同步失败: ' + (error.message || error.toString())); message.error('同步失败: ' + (error.message || error.toString()));
return false; return false;
} finally {
} }
}; };
// 批量同步产品到指定站点 // 批量同步产品到指定站点
const batchSyncProducts = async (productsToSync?: ProductWithWP[]) => { const batchSyncProducts = async (productsToSync?: SiteProduct[]) => {
if (!selectedSiteId) { if (!selectedSiteId) {
message.error('请选择要同步到的站点'); message.error('请选择要同步到的站点');
return; return;
@ -239,7 +166,7 @@ const ProductSyncPage: React.FC = () => {
message.error('获取产品列表失败'); message.error('获取产品列表失败');
return; return;
} }
products = data.items as ProductWithWP[]; products = data.items as SiteProduct[];
} catch (error) { } catch (error) {
message.error('获取产品列表失败'); message.error('获取产品列表失败');
return; return;
@ -250,108 +177,44 @@ const ProductSyncPage: React.FC = () => {
setSyncProgress(0); setSyncProgress(0);
setSyncResults({ success: 0, failed: 0, errors: [] }); setSyncResults({ success: 0, failed: 0, errors: [] });
const totalProducts = products.length;
let processed = 0;
let successCount = 0;
let failedCount = 0;
const errors: string[] = [];
try { try {
// 逐个同步产品 // 使用 productcontrollerBatchsynctosite API 批量同步
for (const product of products) { const productIds = products.map((product) => Number(product.id));
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则根据模板生成 // 更新进度为50%,表示正在处理
const expectedSku = setSyncProgress(50);
siteProductSku ||
(skuTemplate
? renderSiteSku(skuTemplate, { site: targetSite, product })
: `${targetSite.skuPrefix || ''}-${product.sku}`);
// 检查是否已存在 const res = await productcontrollerBatchsynctosite({
const existingProduct = wpProductMap.get(expectedSku); productIds: productIds,
siteId: Number(targetSite.id),
// 准备同步数据 } as any);
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) { if (res.success) {
successCount++; const syncedCount = res.data?.synced || 0;
// 更新本地缓存 const errors = res.data?.errors || [];
setWpProductMap((prev) => {
const newMap = new Map(prev); // 更新进度为100%,表示完成
if (res.data && typeof res.data === 'object') { setSyncProgress(100);
newMap.set(expectedSku, res.data as WpProduct);
} setSyncResults({
return newMap; success: syncedCount,
failed: errors.length,
errors: errors.map((err: any) => err.error || '未知错误'),
}); });
} else {
failedCount++;
errors.push(`产品 ${product.sku}: ${res.message || '同步失败'}`);
}
} catch (error: any) {
failedCount++;
errors.push(`产品 ${product.sku}: ${error.message || '未知错误'}`);
}
processed++; if (errors.length === 0) {
setSyncProgress(Math.round((processed / totalProducts) * 100)); message.success(`批量同步完成,成功同步 ${syncedCount} 个产品`);
}
setSyncResults({ success: successCount, failed: failedCount, errors });
if (failedCount === 0) {
message.success(`批量同步完成,成功同步 ${successCount} 个产品`);
} else { } else {
message.warning( message.warning(
`批量同步完成,成功 ${successCount} 个,失败 ${failedCount}`, `批量同步完成,成功 ${syncedCount} 个,失败 ${errors.length}`,
); );
} }
// 刷新表格 // 刷新表格
actionRef.current?.reload(); actionRef.current?.reload();
} else {
throw new Error(res.message || '批量同步失败');
}
} catch (error: any) { } catch (error: any) {
message.error('批量同步失败: ' + (error.message || error.toString())); message.error('批量同步失败: ' + (error.message || error.toString()));
} finally { } finally {
@ -359,27 +222,9 @@ const ProductSyncPage: React.FC = () => {
} }
}; };
// 简单的模板渲染函数
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 generateColumns = (): ProColumns<Site>[] => {
const columns: ProColumns<ProductWithWP>[] = [ const columns: ProColumns<SiteProduct>[] = [
{ {
title: 'SKU', title: 'SKU',
dataIndex: 'sku', dataIndex: 'sku',
@ -471,137 +316,21 @@ const ProductSyncPage: React.FC = () => {
// 为每个站点生成列 // 为每个站点生成列
sites.forEach((site: Site) => { sites.forEach((site: Site) => {
const siteColumn: ProColumns<ProductWithWP> = { const siteColumn: ProColumns<SiteProduct> = {
title: site.name, title: site.name,
key: `site_${site.id}`, key: `site_${site.id}`,
hideInSearch: true, hideInSearch: true,
width: 220, width: 220,
render: (_, record) => { render: (_, record) => {
// 首先查找该产品在该站点的实际SKU
let siteProductSku = '';
if (record.siteSkus && record.siteSkus.length > 0) {
// 根据站点名称匹配对应的siteSku
const siteSkuInfo = record.siteSkus.find((sku: any) => {
// 这里假设可以根据站点名称或其他标识来匹配
// 如果需要更精确的匹配逻辑,可以根据实际需求调整
return ( return (
sku.siteSku && sku.siteSku.includes(site.skuPrefix || site.name) <SiteProductCell
); product={record}
}); site={site}
if (siteSkuInfo) { onSyncSuccess={() => {
siteProductSku = siteSkuInfo.siteSku; // 同步成功后刷新表格
} actionRef.current?.reload();
}
// 如果没有找到实际的siteSku则根据模板或默认规则生成期望的SKU
const expectedSku =
siteProductSku ||
(skuTemplate
? renderSiteSku(skuTemplate, { site, product: record })
: `${site.skuPrefix || ''}-${record.sku}`);
// 尝试用确定的SKU获取WP产品
let wpProduct = wpProductMap.get(expectedSku);
// 如果根据实际SKU没找到再尝试用模板生成的SKU查找
if (!wpProduct && siteProductSku && skuTemplate) {
const templateSku = renderSiteSku(skuTemplate, {
site,
product: record,
});
wpProduct = wpProductMap.get(templateSku);
}
if (!wpProduct) {
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' }}>{wpProduct.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,
wpProduct.externalProductId,
);
}}
initialValues={{
sku: wpProduct.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: {wpProduct.regular_price ?? wpProduct.price}</div>
{wpProduct.sale_price && (
<div style={{ color: 'red' }}>Sale: {wpProduct.sale_price}</div>
)}
<div>
Stock: {wpProduct.stock_quantity ?? wpProduct.stockQuantity}
</div>
<div style={{ marginTop: 2 }}>
Status:{' '}
{wpProduct.status === 'publish' ? (
<Tag color="green">Published</Tag>
) : (
<Tag>{wpProduct.status}</Tag>
)}
</div>
</div>
); );
}, },
}; };
@ -624,7 +353,7 @@ const ProductSyncPage: React.FC = () => {
return ( return (
<Card title="商品同步状态" className="product-sync-card"> <Card title="商品同步状态" className="product-sync-card">
<ProTable<ProductWithWP> <ProTable<SiteProduct>
columns={generateColumns()} columns={generateColumns()}
actionRef={actionRef} actionRef={actionRef}
rowKey="id" rowKey="id"
@ -665,21 +394,21 @@ const ProductSyncPage: React.FC = () => {
]} ]}
request={async (params, sort, filter) => { request={async (params, sort, filter) => {
// 调用本地获取产品列表 API // 调用本地获取产品列表 API
const { data, success } = await productcontrollerGetproductlist({ const response = await productcontrollerGetproductlist({
...params, ...params,
current: params.current, current: params.current,
pageSize: params.pageSize, pageSize: params.pageSize,
// 传递搜索参数 // 传递搜索参数
keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段 // keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段
sku: (params as any).sku, sku: (params as any).sku,
name: (params as any).name, name: (params as any).name,
} as any); } as any);
console.log('result', response);
// 返回给 ProTable // 返回给 ProTable
return { return {
data: (data?.items || []) as ProductWithWP[], data: response.data?.items || [],
success, success: response.success,
total: data?.total || 0, total: response.data?.total || 0,
}; };
}} }}
pagination={{ pagination={{

View File

@ -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 产品 MapKey 为 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;

View File

@ -1,4 +1,4 @@
import { ordercontrollerSyncorder } from '@/servers/api/order'; import { ordercontrollerSyncorders } from '@/servers/api/order';
import { import {
sitecontrollerCreate, sitecontrollerCreate,
sitecontrollerDisable, sitecontrollerDisable,
@ -48,23 +48,14 @@ const SiteList: React.FC = () => {
const hide = message.loading('正在同步...', 0); const hide = message.loading('正在同步...', 0);
const stats = { const stats = {
products: { success: 0, fail: 0 },
orders: { success: 0, fail: 0 }, orders: { success: 0, fail: 0 },
subscriptions: { success: 0, fail: 0 }, subscriptions: { success: 0, fail: 0 },
}; };
try { try {
for (const id of ids) { for (const id of ids) {
// 同步产品
const prodRes = await wpproductcontrollerSyncproducts({ siteId: id });
if (prodRes.success) {
stats.products.success += 1;
} else {
stats.products.fail += 1;
}
// 同步订单 // 同步订单
const orderRes = await ordercontrollerSyncorder({ siteId: id }); const orderRes = await ordercontrollerSyncorders({ siteId: id });
if (orderRes.success) { if (orderRes.success) {
stats.orders.success += 1; stats.orders.success += 1;
} else { } else {
@ -85,9 +76,6 @@ const SiteList: React.FC = () => {
message: '同步完成', message: '同步完成',
description: ( description: (
<div> <div>
<p>
产品: 成功 {stats.products.success}, {stats.products.fail}
</p>
<p> <p>
订单: 成功 {stats.orders.success}, {stats.orders.fail} 订单: 成功 {stats.orders.success}, {stats.orders.fail}
</p> </p>

View File

@ -281,7 +281,7 @@ const CustomerPage: React.FC = () => {
const response = await request(`/site-api/${siteId}/customers`, { const response = await request(`/site-api/${siteId}/customers`, {
params: { params: {
page: current, page: current,
page_size: pageSize, per_page: pageSize,
where, where,
...(orderObj ? { order: orderObj } : {}), ...(orderObj ? { order: orderObj } : {}),
...(name || email ? { search: name || email } : {}), ...(name || email ? { search: name || email } : {}),

View File

@ -108,7 +108,7 @@ const ShopLayout: React.FC = () => {
<Select <Select
style={{ flex: 1 }} style={{ flex: 1 }}
placeholder="请选择店铺" placeholder="请选择店铺"
options={sites.map((site) => ({ options={sites?.map?.((site) => ({
label: site.name, label: site.name,
value: site.id, value: site.id,
}))} }))}

View File

@ -203,7 +203,7 @@ const MediaPage: React.FC = () => {
const response = await request(`/site-api/${siteId}/media`, { const response = await request(`/site-api/${siteId}/media`, {
params: { params: {
page: current, page: current,
page_size: pageSize, per_page: pageSize,
...(orderObj ? { order: orderObj } : {}), ...(orderObj ? { order: orderObj } : {}),
}, },
}); });

View File

@ -18,6 +18,7 @@ import {
CreateOrder, CreateOrder,
EditOrder, EditOrder,
OrderNote, OrderNote,
ShipOrderForm,
} from '../components/Order/Forms'; } from '../components/Order/Forms';
const OrdersPage: React.FC = () => { const OrdersPage: React.FC = () => {
@ -111,7 +112,9 @@ const OrdersPage: React.FC = () => {
return ( return (
<div> <div>
{record.line_items.map((item: any) => ( {record.line_items.map((item: any) => (
<div key={item.id}>{`${item.name} x ${item.quantity}`}</div> <div
key={item.id}
>{`${item.name}(${item.sku}) x ${item.quantity}`}</div>
))} ))}
</div> </div>
); );
@ -145,6 +148,44 @@ const OrdersPage: React.FC = () => {
ellipsis: true, ellipsis: true,
copyable: true, copyable: true,
}, },
{
title: '物流',
dataIndex: 'tracking',
hideInSearch: true,
render: (_, record) => {
// 检查是否有物流信息
if (
!record.tracking ||
!Array.isArray(record.tracking) ||
record.tracking.length === 0
) {
return '-';
}
// 遍历物流信息数组, 显示每个物流的提供商和单号
return (
<div>
{record.tracking.map((item: any, index: number) => (
<div
key={index}
style={{ display: 'flex', flexDirection: 'column' }}
>
<span>
{item.tracking_provider
? `快递方式: ${item.tracking_provider}`
: ''}
</span>
{item.tracking_number
? `物流单号: ${item.tracking_number}`
: ''}
<span>
{item.date_shipped ? `发货日期: ${item.date_shipped}` : ''}
</span>
</div>
))}
</div>
);
},
},
{ {
title: '操作', title: '操作',
dataIndex: 'option', dataIndex: 'option',
@ -186,28 +227,17 @@ const OrdersPage: React.FC = () => {
> >
<Button type="text" icon={<EllipsisOutlined />} /> <Button type="text" icon={<EllipsisOutlined />} />
</Dropdown> </Dropdown>
<Button <ShipOrderForm
type="link" orderId={record.id as number}
title="发货" tableRef={actionRef}
onClick={async () => { siteId={siteId}
try { orderItems={(record as any).line_items?.map((item: any) => ({
const res = await request( id: item.id,
`/site-api/${siteId}/orders/${record.id}/ship`, name: item.name,
{ method: 'POST' }, quantity: item.quantity,
); sku: item.sku,
if (res.success) { }))}
message.success('发货成功'); />
actionRef.current?.reload();
} else {
message.error(res.message || '发货失败');
}
} catch (e) {
message.error('发货失败');
}
}}
>
</Button>
{record.status === 'completed' && ( {record.status === 'completed' && (
<Popconfirm <Popconfirm
title="确定取消发货?" title="确定取消发货?"
@ -369,18 +399,7 @@ const OrdersPage: React.FC = () => {
</ModalForm>, </ModalForm>,
]} ]}
request={async (params, sort, filter) => { request={async (params, sort, filter) => {
const p: any = params || {}; const { current, pageSize, date, status, ...rest } = params;
const current = p.current;
const pageSize = p.pageSize;
const date = p.date;
const status = p.status;
const {
current: _c,
pageSize: _ps,
date: _d,
status: _s,
...rest
} = p;
const where: Record<string, any> = { ...(filter || {}), ...rest }; const where: Record<string, any> = { ...(filter || {}), ...rest };
if (status && status !== 'all') { if (status && status !== 'all') {
where.status = status; where.status = status;
@ -401,7 +420,7 @@ const OrdersPage: React.FC = () => {
const response = await request(`/site-api/${siteId}/orders`, { const response = await request(`/site-api/${siteId}/orders`, {
params: { params: {
page: current, page: current,
page_size: pageSize, per_page: pageSize,
where, where,
...(orderObj ? { order: orderObj } : {}), ...(orderObj ? { order: orderObj } : {}),
}, },

View File

@ -47,6 +47,133 @@ const region = {
YT: 'Yukon', YT: 'Yukon',
}; };
// 定义发货订单表单的数据类型
export interface ShipOrderFormData {
tracking_number?: string;
shipping_provider?: string;
shipping_method?: string;
items?: Array<{
id?: string;
quantity?: number;
}>;
}
// 发货订单表单组件
export const ShipOrderForm: React.FC<{
orderId: number;
tableRef?: React.MutableRefObject<ActionType | undefined>;
siteId?: string;
orderItems?: Array<{
id: string;
name: string;
quantity: number;
sku?: string;
}>;
}> = ({ orderId, tableRef, siteId, orderItems }) => {
const { message } = App.useApp();
const formRef = useRef<ProFormInstance>();
return (
<ModalForm
formRef={formRef}
title="发货订单"
width="600px"
modalProps={{ destroyOnHidden: true }}
trigger={
<Button type="link" title="发货">
</Button>
}
onFinish={async (values: ShipOrderFormData) => {
if (!siteId) {
message.error('缺少站点ID');
return false;
}
try {
const { success, message: errMsg } = await request(
`/site-api/${siteId}/orders/${orderId}/ship`,
{
method: 'POST',
data: values,
},
);
if (success === false) {
throw new Error(errMsg || '发货失败');
}
message.success('发货成功');
tableRef?.current?.reload();
return true;
} catch (error: any) {
message.error(error?.message || '发货失败');
return false;
}
}}
onFinishFailed={() => {
const element = document.querySelector('.ant-form-item-explain-error');
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}}
>
<ProFormText
name="tracking_number"
label="物流单号"
placeholder="请输入物流单号"
rules={[{ required: true, message: '请输入物流单号' }]}
/>
<ProFormText
name="shipping_provider"
label="物流公司"
placeholder="请输入物流公司名称"
rules={[{ required: true, message: '请输入物流公司名称' }]}
/>
<ProFormText
name="shipping_method"
label="发货方式"
placeholder="请输入发货方式"
/>
{orderItems && orderItems.length > 0 && (
<ProFormList
label="发货商品项"
name="items"
tooltip="如果不选择,则默认发货所有商品"
>
<ProForm.Group>
<ProFormSelect
name="id"
label="商品"
placeholder="请选择商品"
options={orderItems.map((item) => ({
label: `${item.name} (SKU: ${item.sku || 'N/A'}) - 可发数量: ${
item.quantity
}`,
value: item.id,
}))}
rules={[{ required: true, message: '请选择商品' }]}
/>
<ProFormDigit
name="quantity"
label="发货数量"
placeholder="请输入发货数量"
rules={[{ required: true, message: '请输入发货数量' }]}
fieldProps={{
precision: 0,
min: 1,
}}
/>
</ProForm.Group>
</ProFormList>
)}
</ModalForm>
);
};
export const OrderNote: React.FC<{ export const OrderNote: React.FC<{
id: number; id: number;
descRef?: React.MutableRefObject<ActionType | undefined>; descRef?: React.MutableRefObject<ActionType | undefined>;

View File

@ -613,18 +613,6 @@ export const UpdateVaritation: React.FC<{
); );
}; };
// ... SetComponent, BatchEditProducts, BatchDeleteProducts, ImportCsv ...
// I will keep them but comment out/disable parts that rely on old API if I can't easily fix them all.
// BatchEdit/Delete rely on old API.
// I'll comment out their usage in ProductsPage or just return null here.
// I'll keep them but they might break if used.
// Since I removed them from ProductsPage toolbar (Wait, I kept them in ProductsPage toolbar!), I should update them or remove them.
// I'll update BatchDelete to use new API (loop delete).
// BatchEdit? `wpproductcontrollerBatchUpdateProducts`.
// I don't have batch update in my new API.
// I'll remove BatchEdit from ProductsPage toolbar for now or implement batch update in Controller.
// I'll update BatchDelete.
export const BatchDeleteProducts: React.FC<{ export const BatchDeleteProducts: React.FC<{
tableRef: React.MutableRefObject<ActionType | undefined>; tableRef: React.MutableRefObject<ActionType | undefined>;
selectedRowKeys: React.Key[]; selectedRowKeys: React.Key[];

View File

@ -23,13 +23,13 @@ export async function categorycontrollerGetlist(
/** 此处后端没有提供注释 POST /category/ */ /** 此处后端没有提供注释 POST /category/ */
export async function categorycontrollerCreate( export async function categorycontrollerCreate(
body: Record<string, any>, body: API.CreateCategoryDTO,
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
return request<any>('/category/', { return request<any>('/category/', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'text/plain', 'Content-Type': 'application/json',
}, },
data: body, data: body,
...(options || {}), ...(options || {}),
@ -40,14 +40,14 @@ export async function categorycontrollerCreate(
export async function categorycontrollerUpdate( export async function categorycontrollerUpdate(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.categorycontrollerUpdateParams, params: API.categorycontrollerUpdateParams,
body: Record<string, any>, body: API.UpdateCategoryDTO,
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
const { id: param0, ...queryParams } = params; const { id: param0, ...queryParams } = params;
return request<any>(`/category/${param0}`, { return request<any>(`/category/${param0}`, {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-Type': 'text/plain', 'Content-Type': 'application/json',
}, },
params: { ...queryParams }, params: { ...queryParams },
data: body, data: body,

View File

@ -2,6 +2,68 @@
/* eslint-disable */ /* eslint-disable */
import { request } from 'umi'; import { request } from 'umi';
/** 此处后端没有提供注释 POST /customer/ */
export async function customercontrollerCreatecustomer(
body: API.CreateCustomerDTO,
options?: { [key: string]: any },
) {
return request<API.GetCustomerDTO>('/customer/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /customer/${param0} */
export async function customercontrollerGetcustomerbyid(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.customercontrollerGetcustomerbyidParams,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<API.GetCustomerDTO>(`/customer/${param0}`, {
method: 'GET',
params: { ...queryParams },
...(options || {}),
});
}
/** 此处后端没有提供注释 PUT /customer/${param0} */
export async function customercontrollerUpdatecustomer(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.customercontrollerUpdatecustomerParams,
body: API.UpdateCustomerDTO,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<API.GetCustomerDTO>(`/customer/${param0}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 DELETE /customer/${param0} */
export async function customercontrollerDeletecustomer(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.customercontrollerDeletecustomerParams,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<Record<string, any>>(`/customer/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /customer/addtag */ /** 此处后端没有提供注释 POST /customer/addtag */
export async function customercontrollerAddtag( export async function customercontrollerAddtag(
body: API.CustomerTagDTO, body: API.CustomerTagDTO,
@ -17,6 +79,51 @@ export async function customercontrollerAddtag(
}); });
} }
/** 此处后端没有提供注释 PUT /customer/batch */
export async function customercontrollerBatchupdatecustomers(
body: API.BatchUpdateCustomerDTO,
options?: { [key: string]: any },
) {
return request<Record<string, any>>('/customer/batch', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /customer/batch */
export async function customercontrollerBatchcreatecustomers(
body: API.BatchCreateCustomerDTO,
options?: { [key: string]: any },
) {
return request<Record<string, any>>('/customer/batch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 DELETE /customer/batch */
export async function customercontrollerBatchdeletecustomers(
body: API.BatchDeleteCustomerDTO,
options?: { [key: string]: any },
) {
return request<Record<string, any>>('/customer/batch', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /customer/deltag */ /** 此处后端没有提供注释 POST /customer/deltag */
export async function customercontrollerDeltag( export async function customercontrollerDeltag(
body: API.CustomerTagDTO, body: API.CustomerTagDTO,
@ -32,36 +139,6 @@ export async function customercontrollerDeltag(
}); });
} }
/** 此处后端没有提供注释 GET /customer/getcustomerlist */
export async function customercontrollerGetcustomerlist(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.customercontrollerGetcustomerlistParams,
options?: { [key: string]: any },
) {
return request<Record<string, any>>('/customer/getcustomerlist', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /customer/getcustomerstatisticlist */
export async function customercontrollerGetcustomerstatisticlist(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.customercontrollerGetcustomerstatisticlistParams,
options?: { [key: string]: any },
) {
return request<Record<string, any>>('/customer/getcustomerstatisticlist', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /customer/gettags */ /** 此处后端没有提供注释 GET /customer/gettags */
export async function customercontrollerGettags(options?: { export async function customercontrollerGettags(options?: {
[key: string]: any; [key: string]: any;
@ -72,6 +149,16 @@ export async function customercontrollerGettags(options?: {
}); });
} }
/** 此处后端没有提供注释 GET /customer/list */
export async function customercontrollerGetcustomerlist(options?: {
[key: string]: any;
}) {
return request<API.ApiResponse>('/customer/list', {
method: 'GET',
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /customer/setrate */ /** 此处后端没有提供注释 POST /customer/setrate */
export async function customercontrollerSetrate( export async function customercontrollerSetrate(
body: Record<string, any>, body: Record<string, any>,
@ -87,15 +174,25 @@ export async function customercontrollerSetrate(
}); });
} }
/** 此处后端没有提供注释 GET /customer/statistic/list */
export async function customercontrollerGetcustomerstatisticlist(options?: {
[key: string]: any;
}) {
return request<Record<string, any>>('/customer/statistic/list', {
method: 'GET',
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /customer/sync */ /** 此处后端没有提供注释 POST /customer/sync */
export async function customercontrollerSynccustomers( export async function customercontrollerSynccustomers(
body: Record<string, any>, body: API.SyncCustomersDTO,
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
return request<Record<string, any>>('/customer/sync', { return request<Record<string, any>>('/customer/sync', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'text/plain', 'Content-Type': 'application/json',
}, },
data: body, data: body,
...(options || {}), ...(options || {}),

View File

@ -151,12 +151,27 @@ export async function dictcontrollerDeletedictitem(
}); });
} }
/** 此处后端没有提供注释 GET /dict/item/export */
export async function dictcontrollerExportdictitems(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.dictcontrollerExportdictitemsParams,
options?: { [key: string]: any },
) {
return request<any>('/dict/item/export', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /dict/item/import */ /** 此处后端没有提供注释 POST /dict/item/import */
export async function dictcontrollerImportdictitems( export async function dictcontrollerImportdictitems(
body: Record<string, any>, body: Record<string, any>,
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
return request<any>('/dict/item/import', { return request<API.ApiResponse>('/dict/item/import', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'text/plain', 'Content-Type': 'text/plain',

View File

@ -225,15 +225,15 @@ export async function ordercontrollerChangestatus(
}); });
} }
/** 此处后端没有提供注释 POST /order/syncOrder/${param0} */ /** 此处后端没有提供注释 POST /order/sync/${param0} */
export async function ordercontrollerSyncorder( export async function ordercontrollerSyncorders(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.ordercontrollerSyncorderParams, params: API.ordercontrollerSyncordersParams,
body: Record<string, any>, body: Record<string, any>,
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
const { siteId: param0, ...queryParams } = params; const { siteId: param0, ...queryParams } = params;
return request<API.BooleanRes>(`/order/syncOrder/${param0}`, { return request<API.BooleanRes>(`/order/sync/${param0}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'text/plain', 'Content-Type': 'text/plain',

View File

@ -229,6 +229,36 @@ export async function productcontrollerBatchdeleteproduct(
}); });
} }
/** 此处后端没有提供注释 POST /product/batch-sync-from-site */
export async function productcontrollerBatchsyncfromsite(
body: Record<string, any>,
options?: { [key: string]: any },
) {
return request<API.SyncOperationResultDTO>('/product/batch-sync-from-site', {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /product/batch-sync-to-site */
export async function productcontrollerBatchsynctosite(
body: API.BatchSyncProductToSiteDTO,
options?: { [key: string]: any },
) {
return request<API.SyncOperationResultDTO>('/product/batch-sync-to-site', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 PUT /product/batch-update */ /** 此处后端没有提供注释 PUT /product/batch-update */
export async function productcontrollerBatchupdateproduct( export async function productcontrollerBatchupdateproduct(
body: API.BatchUpdateProductDTO, body: API.BatchUpdateProductDTO,
@ -333,13 +363,13 @@ export async function productcontrollerGetcategoriesall(options?: {
/** 此处后端没有提供注释 POST /product/category */ /** 此处后端没有提供注释 POST /product/category */
export async function productcontrollerCreatecategory( export async function productcontrollerCreatecategory(
body: Record<string, any>, body: API.CreateCategoryDTO,
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
return request<any>('/product/category', { return request<any>('/product/category', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'text/plain', 'Content-Type': 'application/json',
}, },
data: body, data: body,
...(options || {}), ...(options || {}),
@ -350,14 +380,14 @@ export async function productcontrollerCreatecategory(
export async function productcontrollerUpdatecategory( export async function productcontrollerUpdatecategory(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.productcontrollerUpdatecategoryParams, params: API.productcontrollerUpdatecategoryParams,
body: Record<string, any>, body: API.UpdateCategoryDTO,
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
const { id: param0, ...queryParams } = params; const { id: param0, ...queryParams } = params;
return request<any>(`/product/category/${param0}`, { return request<any>(`/product/category/${param0}`, {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-Type': 'text/plain', 'Content-Type': 'application/json',
}, },
params: { ...queryParams }, params: { ...queryParams },
data: body, data: body,
@ -558,6 +588,10 @@ export async function productcontrollerGetproductlist(
method: 'GET', method: 'GET',
params: { params: {
...params, ...params,
where: undefined,
...params['where'],
orderBy: undefined,
...params['orderBy'],
}, },
...(options || {}), ...(options || {}),
}); });
@ -760,6 +794,21 @@ export async function productcontrollerCompatstrengthall(options?: {
}); });
} }
/** 此处后端没有提供注释 POST /product/sync-from-site */
export async function productcontrollerSyncproductfromsite(
body: Record<string, any>,
options?: { [key: string]: any },
) {
return request<API.ProductRes>('/product/sync-from-site', {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /product/sync-stock */ /** 此处后端没有提供注释 POST /product/sync-stock */
export async function productcontrollerSyncstocktoproduct(options?: { export async function productcontrollerSyncstocktoproduct(options?: {
[key: string]: any; [key: string]: any;
@ -770,6 +819,21 @@ export async function productcontrollerSyncstocktoproduct(options?: {
}); });
} }
/** 此处后端没有提供注释 POST /product/sync-to-site */
export async function productcontrollerSynctosite(
body: API.SyncProductToSiteDTO,
options?: { [key: string]: any },
) {
return request<API.SyncProductToSiteResultDTO>('/product/sync-to-site', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 PUT /productupdateNameCn/${param1}/${param0} */ /** 此处后端没有提供注释 PUT /productupdateNameCn/${param1}/${param0} */
export async function productcontrollerUpdatenamecn( export async function productcontrollerUpdatenamecn(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)

View File

@ -280,15 +280,17 @@ export async function siteapicontrollerBatchorders(
); );
} }
/** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-ship */ /** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-fulfill */
export async function siteapicontrollerBatchshiporders( export async function siteapicontrollerBatchfulfillorders(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerBatchshipordersParams, params: API.siteapicontrollerBatchfulfillordersParams,
body: API.BatchShipOrdersDTO, body: API.BatchFulfillmentsDTO,
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
const { siteId: param0, ...queryParams } = params; const { siteId: param0, ...queryParams } = params;
return request<Record<string, any>>(`/site-api/${param0}/orders/batch-ship`, { return request<Record<string, any>>(
`/site-api/${param0}/orders/batch-fulfill`,
{
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -296,7 +298,8 @@ export async function siteapicontrollerBatchshiporders(
params: { ...queryParams }, params: { ...queryParams },
data: body, data: body,
...(options || {}), ...(options || {}),
}); },
);
} }
/** 此处后端没有提供注释 GET /site-api/${param0}/orders/export */ /** 此处后端没有提供注释 GET /site-api/${param0}/orders/export */
@ -402,6 +405,28 @@ export async function siteapicontrollerBatchproducts(
); );
} }
/** 此处后端没有提供注释 POST /site-api/${param0}/products/batch-upsert */
export async function siteapicontrollerBatchupsertproduct(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerBatchupsertproductParams,
body: Record<string, any>,
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
return request<API.BatchOperationResultDTO>(
`/site-api/${param0}/products/batch-upsert`,
{
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
params: { ...queryParams },
data: body,
...(options || {}),
},
);
}
/** 此处后端没有提供注释 GET /site-api/${param0}/products/export */ /** 此处后端没有提供注释 GET /site-api/${param0}/products/export */
export async function siteapicontrollerExportproducts( export async function siteapicontrollerExportproducts(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
@ -483,6 +508,25 @@ export async function siteapicontrollerImportproductsspecial(
); );
} }
/** 此处后端没有提供注释 POST /site-api/${param0}/products/upsert */
export async function siteapicontrollerUpsertproduct(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerUpsertproductParams,
body: API.UnifiedProductDTO,
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
return request<API.UnifiedProductDTO>(`/site-api/${param0}/products/upsert`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /site-api/${param0}/reviews */ /** 此处后端没有提供注释 GET /site-api/${param0}/reviews */
export async function siteapicontrollerGetreviews( export async function siteapicontrollerGetreviews(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
@ -766,16 +810,60 @@ export async function siteapicontrollerDeleteorder(
}); });
} }
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-ship */ /** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-fulfill */
export async function siteapicontrollerCancelshiporder( export async function siteapicontrollerCancelfulfillment(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerCancelshiporderParams, params: API.siteapicontrollerCancelfulfillmentParams,
body: API.CancelShipOrderDTO, body: API.CancelFulfillmentDTO,
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
const { id: param0, siteId: param1, ...queryParams } = params; const { id: param0, siteId: param1, ...queryParams } = params;
return request<Record<string, any>>( return request<Record<string, any>>(
`/site-api/${param1}/orders/${param0}/cancel-ship`, `/site-api/${param1}/orders/${param0}/cancel-fulfill`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
},
);
}
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/fulfill */
export async function siteapicontrollerFulfillorder(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerFulfillorderParams,
body: API.FulfillmentDTO,
options?: { [key: string]: any },
) {
const { id: param0, siteId: param1, ...queryParams } = params;
return request<Record<string, any>>(
`/site-api/${param1}/orders/${param0}/fulfill`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
},
);
}
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/fulfillments */
export async function siteapicontrollerCreateorderfulfillment(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerCreateorderfulfillmentParams,
body: API.UnifiedOrderTrackingDTO,
options?: { [key: string]: any },
) {
const { orderId: param0, siteId: param1, ...queryParams } = params;
return request<API.UnifiedOrderTrackingDTO>(
`/site-api/${param1}/orders/${param0}/fulfillments`,
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@ -827,23 +915,18 @@ export async function siteapicontrollerCreateordernote(
); );
} }
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/ship */ /** 此处后端没有提供注释 GET /site-api/${param1}/orders/${param0}/trackings */
export async function siteapicontrollerShiporder( export async function siteapicontrollerGetordertrackings(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerShiporderParams, params: API.siteapicontrollerGetordertrackingsParams,
body: API.ShipOrderDTO,
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
const { id: param0, siteId: param1, ...queryParams } = params; const { orderId: param0, siteId: param1, ...queryParams } = params;
return request<Record<string, any>>( return request<Record<string, any>>(
`/site-api/${param1}/orders/${param0}/ship`, `/site-api/${param1}/orders/${param0}/trackings`,
{ {
method: 'POST', method: 'GET',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams }, params: { ...queryParams },
data: body,
...(options || {}), ...(options || {}),
}, },
); );
@ -997,6 +1080,55 @@ export async function siteapicontrollerDeletewebhook(
); );
} }
/** 此处后端没有提供注释 PUT /site-api/${param2}/orders/${param1}/fulfillments/${param0} */
export async function siteapicontrollerUpdateorderfulfillment(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerUpdateorderfulfillmentParams,
body: API.UnifiedOrderTrackingDTO,
options?: { [key: string]: any },
) {
const {
fulfillmentId: param0,
orderId: param1,
siteId: param2,
...queryParams
} = params;
return request<API.UnifiedOrderTrackingDTO>(
`/site-api/${param2}/orders/${param1}/fulfillments/${param0}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
},
);
}
/** 此处后端没有提供注释 DELETE /site-api/${param2}/orders/${param1}/fulfillments/${param0} */
export async function siteapicontrollerDeleteorderfulfillment(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerDeleteorderfulfillmentParams,
options?: { [key: string]: any },
) {
const {
fulfillmentId: param0,
orderId: param1,
siteId: param2,
...queryParams
} = params;
return request<Record<string, any>>(
`/site-api/${param2}/orders/${param1}/fulfillments/${param0}`,
{
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
},
);
}
/** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */ /** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */
export async function siteapicontrollerUpdatevariation( export async function siteapicontrollerUpdatevariation(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)

View File

@ -7,6 +7,17 @@ declare namespace API {
postal_code?: string; postal_code?: string;
}; };
type ApiResponse = {
/** 状态码 */
code?: number;
/** 是否成功 */
success?: boolean;
/** 提示信息 */
message?: string;
/** 返回数据 */
data?: Record<string, any>;
};
type Area = { type Area = {
/** 名称 */ /** 名称 */
name?: string; name?: string;
@ -35,6 +46,16 @@ declare namespace API {
id: number; id: number;
}; };
type BatchCreateCustomerDTO = {
/** 客户列表 */
customers?: CreateCustomerDTO[];
};
type BatchDeleteCustomerDTO = {
/** 客户ID列表 */
ids?: number[];
};
type BatchDeleteProductDTO = { type BatchDeleteProductDTO = {
/** 产品ID列表 */ /** 产品ID列表 */
ids: any[]; ids: any[];
@ -47,6 +68,24 @@ declare namespace API {
error?: string; error?: string;
}; };
type BatchFulfillmentItemDTO = {
/** 订单ID */
order_id?: string;
/** 物流单号 */
tracking_number?: string;
/** 物流公司 */
shipping_provider?: string;
/** 发货方式 */
shipping_method?: string;
/** 发货商品项 */
items?: FulfillmentItemDTO[];
};
type BatchFulfillmentsDTO = {
/** 批量发货订单列表 */
orders?: BatchFulfillmentItemDTO[];
};
type BatchOperationDTO = { type BatchOperationDTO = {
/** 要创建的数据列表 */ /** 要创建的数据列表 */
create?: any[]; create?: any[];
@ -73,22 +112,16 @@ declare namespace API {
errors?: BatchErrorItemDTO[]; errors?: BatchErrorItemDTO[];
}; };
type BatchShipOrderItemDTO = { type BatchSyncProductToSiteDTO = {
/** 订单ID */ /** 站点ID */
order_id?: string; siteId?: number;
/** 物流单号 */ /** 产品站点SKU列表 */
tracking_number?: string; data?: ProductSiteSkuDTO[];
/** 物流公司 */
shipping_provider?: string;
/** 发货方式 */
shipping_method?: string;
/** 发货商品项 */
items?: ShipOrderItemDTO[];
}; };
type BatchShipOrdersDTO = { type BatchUpdateCustomerDTO = {
/** 批量发货订单列表 */ /** 客户更新列表 */
orders?: BatchShipOrderItemDTO[]; customers?: UpdateCustomerItemDTO[];
}; };
type BatchUpdateProductDTO = { type BatchUpdateProductDTO = {
@ -129,7 +162,7 @@ declare namespace API {
data?: boolean; data?: boolean;
}; };
type CancelShipOrderDTO = { type CancelFulfillmentDTO = {
/** 取消原因 */ /** 取消原因 */
reason?: string; reason?: string;
/** 发货单ID */ /** 发货单ID */
@ -163,6 +196,54 @@ declare namespace API {
code?: string; code?: string;
}; };
type CreateCategoryDTO = {
/** 分类显示名称 */
title: string;
/** 分类中文名称 */
titleCN?: string;
/** 分类唯一标识 */
name: string;
/** 分类短名称,用于生成SKU */
shortName?: string;
/** 排序 */
sort?: number;
};
type CreateCustomerDTO = {
/** 站点ID */
site_id?: number;
/** 原始ID */
origin_id?: number;
/** 邮箱 */
email?: string;
/** 名字 */
first_name?: string;
/** 姓氏 */
last_name?: string;
/** 全名 */
fullname?: string;
/** 用户名 */
username?: string;
/** 电话 */
phone?: string;
/** 头像URL */
avatar?: string;
/** 账单信息 */
billing?: Record<string, any>;
/** 配送信息 */
shipping?: Record<string, any>;
/** 原始数据 */
raw?: Record<string, any>;
/** 评分 */
rate?: number;
/** 标签列表 */
tags?: string[];
/** 站点创建时间 */
site_created_at?: string;
/** 站点更新时间 */
site_updated_at?: string;
};
type CreateDictDTO = {}; type CreateDictDTO = {};
type CreateDictItemDTO = {}; type CreateDictItemDTO = {};
@ -271,32 +352,22 @@ declare namespace API {
unit?: string; unit?: string;
}; };
type customercontrollerGetcustomerlistParams = { type customercontrollerDeletecustomerParams = {
current?: string; id: number;
pageSize?: string;
email?: string;
tags?: string;
sorterKey?: string;
sorterValue?: string;
state?: string;
first_purchase_date?: string;
customerId?: number;
}; };
type customercontrollerGetcustomerstatisticlistParams = { type customercontrollerGetcustomerbyidParams = {
current?: string; id: number;
pageSize?: string; };
email?: string;
tags?: string; type customercontrollerUpdatecustomerParams = {
sorterKey?: string; id: number;
sorterValue?: string;
state?: string;
first_purchase_date?: string;
customerId?: number;
}; };
type CustomerTagDTO = { type CustomerTagDTO = {
/** 客户邮箱 */
email?: string; email?: string;
/** 标签名称 */
tag?: string; tag?: string;
}; };
@ -324,6 +395,10 @@ declare namespace API {
id: number; id: number;
}; };
type dictcontrollerExportdictitemsParams = {
dictId?: number;
};
type dictcontrollerGetdictitemsbydictnameParams = { type dictcontrollerGetdictitemsbydictnameParams = {
name?: string; name?: string;
}; };
@ -353,6 +428,65 @@ declare namespace API {
type DisableSiteDTO = {}; type DisableSiteDTO = {};
type FulfillmentDTO = {
/** 物流单号 */
tracking_number?: string;
/** 物流公司 */
shipping_provider?: string;
/** 发货方式 */
shipping_method?: string;
/** 发货商品项 */
items?: FulfillmentItemDTO[];
};
type FulfillmentItemDTO = {
/** 订单项ID */
order_item_id?: number;
/** 数量 */
quantity?: number;
};
type GetCustomerDTO = {
/** 客户ID */
id?: number;
/** 站点ID */
site_id?: number;
/** 原始ID */
origin_id?: number;
/** 站点创建时间 */
site_created_at?: string;
/** 站点更新时间 */
site_updated_at?: string;
/** 邮箱 */
email?: string;
/** 名字 */
first_name?: string;
/** 姓氏 */
last_name?: string;
/** 全名 */
fullname?: string;
/** 用户名 */
username?: string;
/** 电话 */
phone?: string;
/** 头像URL */
avatar?: string;
/** 账单信息 */
billing?: Record<string, any>;
/** 配送信息 */
shipping?: Record<string, any>;
/** 原始数据 */
raw?: Record<string, any>;
/** 创建时间 */
created_at?: string;
/** 更新时间 */
updated_at?: string;
/** 评分 */
rate?: number;
/** 标签列表 */
tags?: string[];
};
type localecontrollerGetlocaleParams = { type localecontrollerGetlocaleParams = {
lang: string; lang: string;
}; };
@ -620,7 +754,7 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type ordercontrollerSyncorderParams = { type ordercontrollerSyncordersParams = {
siteId: number; siteId: number;
}; };
@ -854,7 +988,6 @@ declare namespace API {
purchaseType?: 'all' | 'first_purchase' | 'repeat_purchase'; purchaseType?: 'all' | 'first_purchase' | 'repeat_purchase';
orderType?: 'all' | 'cpc' | 'non_cpc'; orderType?: 'all' | 'cpc' | 'non_cpc';
brand?: 'all' | 'zyn' | 'yoone' | 'zolt'; brand?: 'all' | 'zyn' | 'yoone' | 'zolt';
grouping?: 'day' | 'week' | 'month';
}; };
type OrderStatusCountDTO = { type OrderStatusCountDTO = {
@ -898,7 +1031,7 @@ declare namespace API {
/** 库存组成 */ /** 库存组成 */
components?: ProductStockComponent[]; components?: ProductStockComponent[];
/** 站点 SKU 列表 */ /** 站点 SKU 列表 */
siteSkus?: ProductSiteSku[]; siteSkus?: string[];
/** 来源 */ /** 来源 */
source?: number; source?: number;
/** 创建时间 */ /** 创建时间 */
@ -1019,20 +1152,16 @@ declare namespace API {
}; };
type productcontrollerGetproductlistParams = { type productcontrollerGetproductlistParams = {
/** 当前页 */ /** */
current?: number; page?: number;
/** 每页数量 */ /** 每页数量 */
pageSize?: number; per_page?: number;
/** 搜索关键字 */ /** 搜索关键词 */
name?: string; search?: string;
/** 分类ID */ /** 过滤条件对象 */
categoryId?: number; where?: Record<string, any>;
/** 品牌ID */ /** 排序对象,例如 { "sku": "desc" } */
brandId?: number; orderBy?: Record<string, any>;
/** 排序字段 */
sortField?: string;
/** 排序方式 */
sortOrder?: string;
}; };
type productcontrollerGetproductsiteskusParams = { type productcontrollerGetproductsiteskusParams = {
@ -1098,8 +1227,10 @@ declare namespace API {
data?: Product; data?: Product;
}; };
type ProductSiteSku = { type ProductSiteSkuDTO = {
/** 站点 SKU */ /** 产品ID */
productId?: number;
/** 站点SKU */
siteSku?: string; siteSku?: string;
}; };
@ -1182,18 +1313,6 @@ declare namespace API {
keyword?: string; keyword?: string;
}; };
type QueryCustomerListDTO = {
current?: string;
pageSize?: string;
email?: string;
tags?: string;
sorterKey?: string;
sorterValue?: string;
state?: string;
first_purchase_date?: string;
customerId?: number;
};
type QueryOrderDTO = { type QueryOrderDTO = {
/** 页码 */ /** 页码 */
current?: number; current?: number;
@ -1257,23 +1376,6 @@ declare namespace API {
pageSize?: number; pageSize?: number;
}; };
type QueryProductDTO = {
/** 当前页 */
current?: number;
/** 每页数量 */
pageSize?: number;
/** 搜索关键字 */
name?: string;
/** 分类ID */
categoryId?: number;
/** 品牌ID */
brandId?: number;
/** 排序字段 */
sortField?: string;
/** 排序方式 */
sortOrder?: string;
};
type QueryPurchaseOrderDTO = { type QueryPurchaseOrderDTO = {
/** 页码 */ /** 页码 */
current?: number; current?: number;
@ -1424,24 +1526,6 @@ declare namespace API {
weightUom?: string; weightUom?: string;
}; };
type ShipOrderDTO = {
/** 物流单号 */
tracking_number?: string;
/** 物流公司 */
shipping_provider?: string;
/** 发货方式 */
shipping_method?: string;
/** 发货商品项 */
items?: ShipOrderItemDTO[];
};
type ShipOrderItemDTO = {
/** 订单项ID */
order_item_id?: number;
/** 数量 */
quantity?: number;
};
type ShippingAddress = { type ShippingAddress = {
id?: number; id?: number;
name?: string; name?: string;
@ -1481,6 +1565,10 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type siteapicontrollerBatchfulfillordersParams = {
siteId: number;
};
type siteapicontrollerBatchmediaParams = { type siteapicontrollerBatchmediaParams = {
siteId: number; siteId: number;
}; };
@ -1493,11 +1581,11 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type siteapicontrollerBatchshipordersParams = { type siteapicontrollerBatchupsertproductParams = {
siteId: number; siteId: number;
}; };
type siteapicontrollerCancelshiporderParams = { type siteapicontrollerCancelfulfillmentParams = {
id: string; id: string;
siteId: number; siteId: number;
}; };
@ -1514,6 +1602,11 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type siteapicontrollerCreateorderfulfillmentParams = {
orderId: string;
siteId: number;
};
type siteapicontrollerCreateordernoteParams = { type siteapicontrollerCreateordernoteParams = {
id: string; id: string;
siteId: number; siteId: number;
@ -1545,6 +1638,12 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type siteapicontrollerDeleteorderfulfillmentParams = {
fulfillmentId: string;
orderId: string;
siteId: number;
};
type siteapicontrollerDeleteorderParams = { type siteapicontrollerDeleteorderParams = {
id: string; id: string;
siteId: number; siteId: number;
@ -1574,10 +1673,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1592,10 +1687,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1610,10 +1701,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1628,10 +1715,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1646,10 +1729,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1664,15 +1743,16 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
}; };
type siteapicontrollerFulfillorderParams = {
id: string;
siteId: number;
};
type siteapicontrollerGetcustomerordersParams = { type siteapicontrollerGetcustomerordersParams = {
/** 页码 */ /** 页码 */
page?: number; page?: number;
@ -1682,10 +1762,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
customerId: number; customerId: number;
@ -1706,10 +1782,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1728,10 +1800,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1756,15 +1824,16 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
}; };
type siteapicontrollerGetordertrackingsParams = {
orderId: string;
siteId: number;
};
type siteapicontrollerGetproductParams = { type siteapicontrollerGetproductParams = {
id: string; id: string;
siteId: number; siteId: number;
@ -1779,10 +1848,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1797,10 +1862,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1815,10 +1876,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1838,10 +1895,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
siteId: number; siteId: number;
@ -1863,11 +1916,6 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type siteapicontrollerShiporderParams = {
id: string;
siteId: number;
};
type siteapicontrollerUpdatecustomerParams = { type siteapicontrollerUpdatecustomerParams = {
id: string; id: string;
siteId: number; siteId: number;
@ -1878,6 +1926,12 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type siteapicontrollerUpdateorderfulfillmentParams = {
fulfillmentId: string;
orderId: string;
siteId: number;
};
type siteapicontrollerUpdateorderParams = { type siteapicontrollerUpdateorderParams = {
id: string; id: string;
siteId: number; siteId: number;
@ -1904,6 +1958,10 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type siteapicontrollerUpsertproductParams = {
siteId: number;
};
type SiteConfig = { type SiteConfig = {
/** 站点 ID */ /** 站点 ID */
id?: string; id?: string;
@ -2235,6 +2293,50 @@ declare namespace API {
amount?: Money; amount?: Money;
}; };
type SyncCustomersDTO = {
/** 站点ID */
siteId?: number;
/** 查询参数支持where和orderBy */
params?: UnifiedSearchParamsDTO;
};
type SyncOperationResultDTO = {
/** 总处理数量 */
total?: number;
/** 成功处理数量 */
processed?: number;
/** 创建数量 */
created?: number;
/** 更新数量 */
updated?: number;
/** 删除数量 */
deleted?: number;
/** 跳过的数量 */
skipped?: number;
/** 错误列表 */
errors?: BatchErrorItemDTO[];
/** 同步成功数量 */
synced?: number;
};
type SyncProductToSiteDTO = {
/** 产品ID */
productId?: number;
/** 站点SKU */
siteSku?: string;
/** 站点ID */
siteId?: number;
};
type SyncProductToSiteResultDTO = {
/** 同步状态 */
success?: boolean;
/** 远程产品ID */
remoteId?: string;
/** 错误信息 */
error?: string;
};
type Template = { type Template = {
id?: number; id?: number;
name?: string; name?: string;
@ -2296,8 +2398,6 @@ declare namespace API {
email?: string; email?: string;
/** 电话 */ /** 电话 */
phone?: string; phone?: string;
/** 配送方式 */
method_title?: string;
}; };
type UnifiedCategoryDTO = { type UnifiedCategoryDTO = {
@ -2364,10 +2464,6 @@ declare namespace API {
per_page?: number; per_page?: number;
/** 总页数 */ /** 总页数 */
totalPages?: number; totalPages?: number;
/** 分页后的数据 */
after?: string;
/** 分页前的数据 */
before?: string;
}; };
type UnifiedFeeLineDTO = { type UnifiedFeeLineDTO = {
@ -2428,10 +2524,6 @@ declare namespace API {
per_page?: number; per_page?: number;
/** 总页数 */ /** 总页数 */
totalPages?: number; totalPages?: number;
/** 分页后的数据 */
after?: string;
/** 分页前的数据 */
before?: string;
}; };
type UnifiedOrderDTO = { type UnifiedOrderDTO = {
@ -2443,8 +2535,6 @@ declare namespace API {
status?: string; status?: string;
/** 货币 */ /** 货币 */
currency?: string; currency?: string;
/** 货币符号 */
currency_symbol?: string;
/** 总金额 */ /** 总金额 */
total?: string; total?: string;
/** 客户ID */ /** 客户ID */
@ -2453,8 +2543,6 @@ declare namespace API {
customer_name?: string; customer_name?: string;
/** 客户邮箱 */ /** 客户邮箱 */
email?: string; email?: string;
/** 客户邮箱 */
customer_email?: string;
/** 订单项(具体的商品) */ /** 订单项(具体的商品) */
line_items?: UnifiedOrderLineItemDTO[]; line_items?: UnifiedOrderLineItemDTO[];
/** 销售项(兼容前端) */ /** 销售项(兼容前端) */
@ -2469,6 +2557,8 @@ declare namespace API {
shipping_full_address?: string; shipping_full_address?: string;
/** 支付方式 */ /** 支付方式 */
payment_method?: string; payment_method?: string;
/** 退款列表 */
refunds?: UnifiedOrderRefundDTO[];
/** 创建时间 */ /** 创建时间 */
date_created?: string; date_created?: string;
/** 更新时间 */ /** 更新时间 */
@ -2481,16 +2571,8 @@ declare namespace API {
fee_lines?: UnifiedFeeLineDTO[]; fee_lines?: UnifiedFeeLineDTO[];
/** 优惠券项 */ /** 优惠券项 */
coupon_lines?: UnifiedCouponLineDTO[]; coupon_lines?: UnifiedCouponLineDTO[];
/** 支付时间 */ /** 物流追踪信息 */
date_paid?: string; tracking?: UnifiedOrderTrackingDTO[];
/** 客户IP地址 */
customer_ip_address?: string;
/** UTM来源 */
utm_source?: string;
/** 设备类型 */
device_type?: string;
/** 来源类型 */
source_type?: string;
}; };
type UnifiedOrderLineItemDTO = { type UnifiedOrderLineItemDTO = {
@ -2521,10 +2603,28 @@ declare namespace API {
per_page?: number; per_page?: number;
/** 总页数 */ /** 总页数 */
totalPages?: number; totalPages?: number;
/** 分页后的数据 */ };
after?: string;
/** 分页前的数据 */ type UnifiedOrderRefundDTO = {
before?: string; /** 退款ID */
id?: Record<string, any>;
/** 退款原因 */
reason?: string;
/** 退款金额 */
total?: string;
};
type UnifiedOrderTrackingDTO = {
/** 订单ID */
order_id?: string;
/** 快递公司 */
tracking_provider?: string;
/** 运单跟踪号 */
tracking_number?: string;
/** 发货日期 */
date_shipped?: string;
/** 发货状态 */
status_shipped?: string;
}; };
type UnifiedPaginationDTO = { type UnifiedPaginationDTO = {
@ -2538,10 +2638,6 @@ declare namespace API {
per_page?: number; per_page?: number;
/** 总页数 */ /** 总页数 */
totalPages?: number; totalPages?: number;
/** 分页后的数据 */
after?: string;
/** 分页前的数据 */
before?: string;
}; };
type UnifiedProductAttributeDTO = { type UnifiedProductAttributeDTO = {
@ -2557,6 +2653,8 @@ declare namespace API {
variation?: boolean; variation?: boolean;
/** 属性选项 */ /** 属性选项 */
options?: string[]; options?: string[];
/** 变体属性值(单个值) */
option?: string;
}; };
type UnifiedProductDTO = { type UnifiedProductDTO = {
@ -2613,15 +2711,13 @@ declare namespace API {
per_page?: number; per_page?: number;
/** 总页数 */ /** 总页数 */
totalPages?: number; totalPages?: number;
/** 分页后的数据 */
after?: string;
/** 分页前的数据 */
before?: string;
}; };
type UnifiedProductVariationDTO = { type UnifiedProductVariationDTO = {
/** 变体ID */ /** 变体ID */
id?: Record<string, any>; id?: Record<string, any>;
/** 变体名称 */
name?: string;
/** 变体SKU */ /** 变体SKU */
sku?: string; sku?: string;
/** 常规价格 */ /** 常规价格 */
@ -2634,8 +2730,34 @@ declare namespace API {
stock_status?: string; stock_status?: string;
/** 库存数量 */ /** 库存数量 */
stock_quantity?: number; stock_quantity?: number;
/** 变体属性 */
attributes?: UnifiedProductAttributeDTO[];
/** 变体图片 */ /** 变体图片 */
image?: UnifiedImageDTO; image?: UnifiedImageDTO;
/** 变体描述 */
description?: string;
/** 是否启用 */
enabled?: boolean;
/** 是否可下载 */
downloadable?: boolean;
/** 是否为虚拟商品 */
virtual?: boolean;
/** 管理库存 */
manage_stock?: boolean;
/** 重量 */
weight?: string;
/** 长度 */
length?: string;
/** 宽度 */
width?: string;
/** 高度 */
height?: string;
/** 运输类别 */
shipping_class?: string;
/** 税类别 */
tax_class?: string;
/** 菜单顺序 */
menu_order?: number;
}; };
type UnifiedReviewDTO = { type UnifiedReviewDTO = {
@ -2672,10 +2794,6 @@ declare namespace API {
per_page?: number; per_page?: number;
/** 总页数 */ /** 总页数 */
totalPages?: number; totalPages?: number;
/** 分页后的数据 */
after?: string;
/** 分页前的数据 */
before?: string;
}; };
type UnifiedSearchParamsDTO = { type UnifiedSearchParamsDTO = {
@ -2687,10 +2805,6 @@ declare namespace API {
search?: string; search?: string;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: Record<string, any>; where?: Record<string, any>;
/** 创建时间后 */
after?: string;
/** 创建时间前 */
before?: string;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
orderBy?: Record<string, any>; orderBy?: Record<string, any>;
}; };
@ -2748,10 +2862,6 @@ declare namespace API {
per_page?: number; per_page?: number;
/** 总页数 */ /** 总页数 */
totalPages?: number; totalPages?: number;
/** 分页后的数据 */
after?: string;
/** 分页前的数据 */
before?: string;
}; };
type UnifiedTagDTO = { type UnifiedTagDTO = {
@ -2789,6 +2899,57 @@ declare namespace API {
code?: string; code?: string;
}; };
type UpdateCategoryDTO = {
/** 分类显示名称 */
title?: string;
/** 分类中文名称 */
titleCN?: string;
/** 分类唯一标识 */
name?: string;
/** 分类短名称,用于生成SKU */
shortName?: string;
/** 排序 */
sort?: number;
};
type UpdateCustomerDTO = {
/** 站点ID */
site_id?: number;
/** 原始ID */
origin_id?: number;
/** 邮箱 */
email?: string;
/** 名字 */
first_name?: string;
/** 姓氏 */
last_name?: string;
/** 全名 */
fullname?: string;
/** 用户名 */
username?: string;
/** 电话 */
phone?: string;
/** 头像URL */
avatar?: string;
/** 账单信息 */
billing?: Record<string, any>;
/** 配送信息 */
shipping?: Record<string, any>;
/** 原始数据 */
raw?: Record<string, any>;
/** 评分 */
rate?: number;
/** 标签列表 */
tags?: string[];
};
type UpdateCustomerItemDTO = {
/** 客户ID */
id?: number;
/** 更新字段 */
update_data?: UpdateCustomerDTO;
};
type UpdateDictDTO = {}; type UpdateDictDTO = {};
type UpdateDictItemDTO = {}; type UpdateDictItemDTO = {};

63
src/utils/showResult.ts Normal file
View File

@ -0,0 +1,63 @@
import { message } from 'antd';
/**
*
*/
export interface BatchErrorItem {
identifier: string;
error: string;
}
/**
*
*/
export interface BatchOperationResult {
total: number;
processed: number;
created?: number;
updated?: number;
deleted?: number;
synced?: number;
errors?: BatchErrorItem[];
}
/**
*
* @param result
* @param operationType
*/
export function showBatchOperationResult(
result: BatchOperationResult,
operationType: string = '操作',
): string {
// 从 result.data 中获取实际数据(因为后端返回格式为 { success: true, data: {...} }
const data = (result as any).data || result;
const { total, processed, created, updated, deleted, errors } = data;
// 构建结果消息
let messageContent = `${operationType}结果:共 ${total} 条,成功 ${processed}`;
if (created) {
messageContent += `,创建 ${created}`;
}
if (updated) {
messageContent += `,更新 ${updated}`;
}
if (deleted) {
messageContent += `,删除 ${deleted}`;
}
// 处理错误情况
if (errors && errors.length > 0) {
messageContent += `,失败 ${errors.length}`;
// 显示错误详情
const errorDetails = errors
.map((err: BatchErrorItem) => `${err.identifier}: ${err.error}`)
.join('\n');
message.warning(messageContent + '\n\n错误详情\n' + errorDetails);
} else {
message.success(messageContent);
}
return messageContent;
}