forked from yoone/WEB
feat: 添加Dockerfile和nginx配置支持容器化部署
feat(订单): 实现订单发货功能并优化同步结果显示 feat(字典): 新增字典项增删改查、导入导出功能组件 feat(产品): 添加产品同步到站点功能及批量操作结果展示 refactor(API): 统一API参数命名规范并优化类型定义 fix(客户): 修复客户列表查询条件及同步功能 style: 清理无用代码并优化代码格式 docs: 更新部分组件注释说明 chore: 添加批量操作结果展示工具函数
This commit is contained in:
parent
32e2759151
commit
6f35c2aee5
|
|
@ -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;"]
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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([]);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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 产品 Map,Key 为 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={{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,761 @@
|
||||||
|
import { productcontrollerGetproductlist } from '@/servers/api/product';
|
||||||
|
import { siteapicontrollerGetproducts } from '@/servers/api/siteApi';
|
||||||
|
import { templatecontrollerGettemplatebyname } from '@/servers/api/template';
|
||||||
|
import { EditOutlined, SyncOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
ActionType,
|
||||||
|
ModalForm,
|
||||||
|
ProColumns,
|
||||||
|
ProFormText,
|
||||||
|
ProTable,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import { request } from '@umijs/max';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Progress,
|
||||||
|
Select,
|
||||||
|
Spin,
|
||||||
|
Tag,
|
||||||
|
} from 'antd';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import EditForm from '../List/EditForm';
|
||||||
|
|
||||||
|
// 定义站点接口
|
||||||
|
interface Site {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
skuPrefix?: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义站点商品接口
|
||||||
|
interface SiteProduct {
|
||||||
|
id: string;
|
||||||
|
sku: string;
|
||||||
|
name: string;
|
||||||
|
price: number;
|
||||||
|
stock: number;
|
||||||
|
categories: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义API响应接口
|
||||||
|
interface ApiResponse<T> {
|
||||||
|
data: T[];
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟API请求函数
|
||||||
|
const getSites = async (): Promise<ApiResponse<Site>> => {
|
||||||
|
const res = await request('/site/list', {
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
current: 1,
|
||||||
|
pageSize: 1000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data: res.data?.items || [],
|
||||||
|
success: res.success,
|
||||||
|
message: res.message,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getsiteProduct = async (): Promise<ApiResponse<SiteProduct>> => {
|
||||||
|
return request('/product/wp-products', {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProductSyncPage: React.FC = () => {
|
||||||
|
const [sites, setSites] = useState<Site[]>([]);
|
||||||
|
// 存储所有 WP 产品,用于查找匹配。 Key: SKU (包含前缀)
|
||||||
|
const [siteProductMap, setSiteProductMap] = useState<Map<string, SiteProduct>>(
|
||||||
|
new Map(),
|
||||||
|
);
|
||||||
|
const [skuTemplate, setSkuTemplate] = useState<string>('');
|
||||||
|
const [initialLoading, setInitialLoading] = useState(true);
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
const [selectedSiteId, setSelectedSiteId] = useState<string>('');
|
||||||
|
const [batchSyncModalVisible, setBatchSyncModalVisible] = useState(false);
|
||||||
|
const [syncProgress, setSyncProgress] = useState(0);
|
||||||
|
const [syncing, setSyncing] = useState(false);
|
||||||
|
const [syncResults, setSyncResults] = useState<{
|
||||||
|
success: number;
|
||||||
|
failed: number;
|
||||||
|
errors: string[];
|
||||||
|
}>({ success: 0, failed: 0, errors: [] });
|
||||||
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
|
const [selectedRows, setSelectedRows] = useState<SiteProduct[]>([]);
|
||||||
|
|
||||||
|
// 初始化数据:获取站点和所有 WP 产品
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
setInitialLoading(true);
|
||||||
|
// 获取所有站点
|
||||||
|
const sitesResponse = await getSites();
|
||||||
|
const rawSiteList = sitesResponse.data || [];
|
||||||
|
// 过滤掉已禁用的站点
|
||||||
|
const siteList: Site[] = rawSiteList.filter((site) => !site.isDisabled);
|
||||||
|
setSites(siteList);
|
||||||
|
|
||||||
|
// 获取所有 WordPress 商品
|
||||||
|
const siteProductsResponse = await siteapicontrollerGetproducts({
|
||||||
|
siteId: selectedSiteId,
|
||||||
|
});
|
||||||
|
const siteProductList: SiteProduct[] = siteProductsResponse.data || [];
|
||||||
|
|
||||||
|
// 构建 WP 产品 Map,Key 为 SKU
|
||||||
|
const map = new Map<string, SiteProduct>();
|
||||||
|
siteProductList.forEach((p) => {
|
||||||
|
if (p.sku) {
|
||||||
|
map.set(p.sku, p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSiteProductMap(map);
|
||||||
|
|
||||||
|
// 获取 SKU 模板
|
||||||
|
try {
|
||||||
|
const templateRes = await templatecontrollerGettemplatebyname({
|
||||||
|
name: 'site.product.sku',
|
||||||
|
});
|
||||||
|
if (templateRes && templateRes.value) {
|
||||||
|
setSkuTemplate(templateRes.value);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Template site.product.sku not found, using default.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取基础数据失败,请重试');
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
} finally {
|
||||||
|
setInitialLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 同步产品到站点
|
||||||
|
const syncProductToSite = async (
|
||||||
|
values: any,
|
||||||
|
record: ProductWithWP,
|
||||||
|
site: Site,
|
||||||
|
siteProductId?: string,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const hide = message.loading('正在同步...', 0);
|
||||||
|
const data = {
|
||||||
|
name: record.name,
|
||||||
|
sku: values.sku,
|
||||||
|
regular_price: record.price?.toString(),
|
||||||
|
sale_price: record.promotionPrice?.toString(),
|
||||||
|
type: record.type === 'bundle' ? 'simple' : record.type,
|
||||||
|
description: record.description,
|
||||||
|
status: 'publish',
|
||||||
|
stock_status: 'instock',
|
||||||
|
manage_stock: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res;
|
||||||
|
if (siteProductId) {
|
||||||
|
res = await request(`/site-api/${site.id}/products/${siteProductId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res = await request(`/site-api/${site.id}/products`, {
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('res', res);
|
||||||
|
if (!res.success) {
|
||||||
|
hide();
|
||||||
|
throw new Error(res.message || '同步失败');
|
||||||
|
}
|
||||||
|
// 更新本地缓存 Map,避免刷新
|
||||||
|
setSiteProductMap((prev) => {
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
if (res.data && typeof res.data === 'object') {
|
||||||
|
newMap.set(values.sku, res.data as SiteProduct);
|
||||||
|
}
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
|
||||||
|
hide();
|
||||||
|
message.success('同步成功');
|
||||||
|
return true;
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error('同步失败: ' + (error.message || error.toString()));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量同步产品到指定站点
|
||||||
|
const batchSyncProducts = async (productsToSync?: ProductWithWP[]) => {
|
||||||
|
if (!selectedSiteId) {
|
||||||
|
message.error('请选择要同步到的站点');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetSite = sites.find((site) => site.id === selectedSiteId);
|
||||||
|
if (!targetSite) {
|
||||||
|
message.error('选择的站点不存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有传入产品列表,则使用选中的产品
|
||||||
|
let products = productsToSync || selectedRows;
|
||||||
|
|
||||||
|
// 如果既没有传入产品也没有选中产品,则同步所有产品
|
||||||
|
if (!products || products.length === 0) {
|
||||||
|
try {
|
||||||
|
const { data, success } = await productcontrollerGetproductlist({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10000, // 获取所有产品
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
if (!success || !data?.items) {
|
||||||
|
message.error('获取产品列表失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
products = data.items as ProductWithWP[];
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取产品列表失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSyncing(true);
|
||||||
|
setSyncProgress(0);
|
||||||
|
setSyncResults({ success: 0, failed: 0, errors: [] });
|
||||||
|
|
||||||
|
const totalProducts = products.length;
|
||||||
|
let processed = 0;
|
||||||
|
let successCount = 0;
|
||||||
|
let failedCount = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 逐个同步产品
|
||||||
|
for (const product of products) {
|
||||||
|
try {
|
||||||
|
// 获取该产品在目标站点的SKU
|
||||||
|
let siteProductSku = '';
|
||||||
|
if (product.siteSkus && product.siteSkus.length > 0) {
|
||||||
|
const siteSkuInfo = product.siteSkus.find((sku: any) => {
|
||||||
|
return (
|
||||||
|
sku.siteSku &&
|
||||||
|
sku.siteSku.includes(targetSite.skuPrefix || targetSite.name)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (siteSkuInfo) {
|
||||||
|
siteProductSku = siteSkuInfo.siteSku;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到实际的siteSku,则根据模板生成
|
||||||
|
const expectedSku =
|
||||||
|
siteProductSku ||
|
||||||
|
(skuTemplate
|
||||||
|
? renderSiteSku(skuTemplate, { site: targetSite, product })
|
||||||
|
: `${targetSite.skuPrefix || ''}-${product.sku}`);
|
||||||
|
|
||||||
|
// 检查是否已存在
|
||||||
|
const existingProduct = siteProductMap.get(expectedSku);
|
||||||
|
|
||||||
|
// 准备同步数据
|
||||||
|
const syncData = {
|
||||||
|
name: product.name,
|
||||||
|
sku: expectedSku,
|
||||||
|
regular_price: product.price?.toString(),
|
||||||
|
sale_price: product.promotionPrice?.toString(),
|
||||||
|
type: product.type === 'bundle' ? 'simple' : product.type,
|
||||||
|
description: product.description,
|
||||||
|
status: 'publish',
|
||||||
|
stock_status: 'instock',
|
||||||
|
manage_stock: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res;
|
||||||
|
if (existingProduct?.externalProductId) {
|
||||||
|
// 更新现有产品
|
||||||
|
res = await request(
|
||||||
|
`/site-api/${targetSite.id}/products/${existingProduct.externalProductId}`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
data: syncData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 创建新产品
|
||||||
|
res = await request(`/site-api/${targetSite.id}/products`, {
|
||||||
|
method: 'POST',
|
||||||
|
data: syncData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('res', res);
|
||||||
|
|
||||||
|
if (res.success) {
|
||||||
|
successCount++;
|
||||||
|
// 更新本地缓存
|
||||||
|
setSiteProductMap((prev) => {
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
if (res.data && typeof res.data === 'object') {
|
||||||
|
newMap.set(expectedSku, res.data as SiteProduct);
|
||||||
|
}
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
failedCount++;
|
||||||
|
errors.push(`产品 ${product.sku}: ${res.message || '同步失败'}`);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
failedCount++;
|
||||||
|
errors.push(`产品 ${product.sku}: ${error.message || '未知错误'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
processed++;
|
||||||
|
setSyncProgress(Math.round((processed / totalProducts) * 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
setSyncResults({ success: successCount, failed: failedCount, errors });
|
||||||
|
|
||||||
|
if (failedCount === 0) {
|
||||||
|
message.success(`批量同步完成,成功同步 ${successCount} 个产品`);
|
||||||
|
} else {
|
||||||
|
message.warning(
|
||||||
|
`批量同步完成,成功 ${successCount} 个,失败 ${failedCount} 个`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新表格
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error('批量同步失败: ' + (error.message || error.toString()));
|
||||||
|
} finally {
|
||||||
|
setSyncing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 简单的模板渲染函数
|
||||||
|
const renderSiteSku = (template: string, data: any) => {
|
||||||
|
if (!template) return '';
|
||||||
|
// 支持 <%= it.path %> (Eta) 和 {{ path }} (Mustache/Handlebars)
|
||||||
|
return template.replace(
|
||||||
|
/<%=\s*it\.([\w.]+)\s*%>|\{\{\s*([\w.]+)\s*\}\}/g,
|
||||||
|
(_, p1, p2) => {
|
||||||
|
const path = p1 || p2;
|
||||||
|
const keys = path.split('.');
|
||||||
|
let value = data;
|
||||||
|
for (const key of keys) {
|
||||||
|
value = value?.[key];
|
||||||
|
}
|
||||||
|
return value === undefined || value === null ? '' : String(value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成表格列配置
|
||||||
|
const generateColumns = (): ProColumns<ProductWithWP>[] => {
|
||||||
|
const columns: ProColumns<ProductWithWP>[] = [
|
||||||
|
{
|
||||||
|
title: 'SKU',
|
||||||
|
dataIndex: 'sku',
|
||||||
|
key: 'sku',
|
||||||
|
width: 150,
|
||||||
|
fixed: 'left',
|
||||||
|
copyable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '商品信息',
|
||||||
|
key: 'profile',
|
||||||
|
width: 300,
|
||||||
|
fixed: 'left',
|
||||||
|
render: (_, record) => (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontWeight: 'bold', fontSize: 14 }}>
|
||||||
|
{record.name}
|
||||||
|
</div>
|
||||||
|
<EditForm
|
||||||
|
record={record}
|
||||||
|
tableRef={actionRef}
|
||||||
|
trigger={
|
||||||
|
<EditOutlined
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#1890ff',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: '#666' }}>
|
||||||
|
<span style={{ marginRight: 8 }}>价格: {record.price}</span>
|
||||||
|
{record.promotionPrice && (
|
||||||
|
<span style={{ color: 'red' }}>
|
||||||
|
促销价: {record.promotionPrice}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 属性 */}
|
||||||
|
<div style={{ marginTop: 4 }}>
|
||||||
|
{record.attributes?.map((attr: any, idx: number) => (
|
||||||
|
<Tag
|
||||||
|
key={idx}
|
||||||
|
style={{ fontSize: 10, marginRight: 4, marginBottom: 2 }}
|
||||||
|
>
|
||||||
|
{attr.dict?.name || attr.name}: {attr.name}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 组成 (如果是 Bundle) */}
|
||||||
|
{record.type === 'bundle' &&
|
||||||
|
record.components &&
|
||||||
|
record.components.length > 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
background: '#f5f5f5',
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontWeight: 'bold', marginBottom: 2 }}>
|
||||||
|
Components:
|
||||||
|
</div>
|
||||||
|
{record.components.map((comp: any, idx: number) => (
|
||||||
|
<div key={idx}>
|
||||||
|
{comp.sku} × {comp.quantity}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 为每个站点生成列
|
||||||
|
sites.forEach((site: Site) => {
|
||||||
|
const siteColumn: ProColumns<ProductWithWP> = {
|
||||||
|
title: site.name,
|
||||||
|
key: `site_${site.id}`,
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 220,
|
||||||
|
render: (_, record) => {
|
||||||
|
// 首先查找该产品在该站点的实际SKU
|
||||||
|
let siteProductSku = '';
|
||||||
|
if (record.siteSkus && record.siteSkus.length > 0) {
|
||||||
|
// 根据站点名称匹配对应的siteSku
|
||||||
|
const siteSkuInfo = record.siteSkus.find((sku: any) => {
|
||||||
|
// 这里假设可以根据站点名称或其他标识来匹配
|
||||||
|
// 如果需要更精确的匹配逻辑,可以根据实际需求调整
|
||||||
|
return (
|
||||||
|
sku.siteSku && sku.siteSku.includes(site.skuPrefix || site.name)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (siteSkuInfo) {
|
||||||
|
siteProductSku = siteSkuInfo.siteSku;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到实际的siteSku,则根据模板或默认规则生成期望的SKU
|
||||||
|
const expectedSku =
|
||||||
|
siteProductSku ||
|
||||||
|
(skuTemplate
|
||||||
|
? renderSiteSku(skuTemplate, { site, product: record })
|
||||||
|
: `${site.skuPrefix || ''}-${record.sku}`);
|
||||||
|
|
||||||
|
// 尝试用确定的SKU获取WP产品
|
||||||
|
let siteProduct = siteProductMap.get(expectedSku);
|
||||||
|
|
||||||
|
// 如果根据实际SKU没找到,再尝试用模板生成的SKU查找
|
||||||
|
if (!siteProduct && siteProductSku && skuTemplate) {
|
||||||
|
const templateSku = renderSiteSku(skuTemplate, {
|
||||||
|
site,
|
||||||
|
product: record,
|
||||||
|
});
|
||||||
|
siteProduct = siteProductMap.get(templateSku);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!siteProduct) {
|
||||||
|
return (
|
||||||
|
<ModalForm
|
||||||
|
title="同步产品"
|
||||||
|
trigger={
|
||||||
|
<Button type="link" icon={<SyncOutlined />}>
|
||||||
|
同步到站点
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
width={400}
|
||||||
|
onFinish={async (values) => {
|
||||||
|
return await syncProductToSite(values, record, site);
|
||||||
|
}}
|
||||||
|
initialValues={{
|
||||||
|
sku:
|
||||||
|
siteProductSku ||
|
||||||
|
(skuTemplate
|
||||||
|
? renderSiteSku(skuTemplate, { site, product: record })
|
||||||
|
: `${site.skuPrefix || ''}-${record.sku}`),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProFormText
|
||||||
|
name="sku"
|
||||||
|
label="商店 SKU"
|
||||||
|
placeholder="请输入商店 SKU"
|
||||||
|
rules={[{ required: true, message: '请输入 SKU' }]}
|
||||||
|
/>
|
||||||
|
</ModalForm>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={{ fontSize: 12 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontWeight: 'bold' }}>{siteProduct.sku}</div>
|
||||||
|
<ModalForm
|
||||||
|
title="更新同步"
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon={<SyncOutlined spin={false} />}
|
||||||
|
></Button>
|
||||||
|
}
|
||||||
|
width={400}
|
||||||
|
onFinish={async (values) => {
|
||||||
|
return await syncProductToSite(
|
||||||
|
values,
|
||||||
|
record,
|
||||||
|
site,
|
||||||
|
siteProduct.externalProductId,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
initialValues={{
|
||||||
|
sku: siteProduct.sku,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProFormText
|
||||||
|
name="sku"
|
||||||
|
label="商店 SKU"
|
||||||
|
placeholder="请输入商店 SKU"
|
||||||
|
rules={[{ required: true, message: '请输入 SKU' }]}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<div style={{ marginBottom: 16, color: '#666' }}>
|
||||||
|
确定要将本地产品数据更新到站点吗?
|
||||||
|
</div>
|
||||||
|
</ModalForm>
|
||||||
|
</div>
|
||||||
|
<div>Price: {siteProduct.regular_price ?? siteProduct.price}</div>
|
||||||
|
{siteProduct.sale_price && (
|
||||||
|
<div style={{ color: 'red' }}>Sale: {siteProduct.sale_price}</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
Stock: {siteProduct.stock_quantity ?? siteProduct.stockQuantity}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 2 }}>
|
||||||
|
Status:{' '}
|
||||||
|
{siteProduct.status === 'publish' ? (
|
||||||
|
<Tag color="green">Published</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag>{siteProduct.status}</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
columns.push(siteColumn);
|
||||||
|
});
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (initialLoading) {
|
||||||
|
return (
|
||||||
|
<Card title="商品同步状态" className="product-sync-card">
|
||||||
|
<Spin
|
||||||
|
size="large"
|
||||||
|
style={{ display: 'flex', justifyContent: 'center', padding: 40 }}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title="商品同步状态" className="product-sync-card">
|
||||||
|
<ProTable<ProductWithWP>
|
||||||
|
columns={generateColumns()}
|
||||||
|
actionRef={actionRef}
|
||||||
|
rowKey="id"
|
||||||
|
rowSelection={{
|
||||||
|
selectedRowKeys,
|
||||||
|
onChange: (keys, rows) => {
|
||||||
|
setSelectedRowKeys(keys);
|
||||||
|
setSelectedRows(rows);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
toolBarRender={() => [
|
||||||
|
<Select
|
||||||
|
key="site-select"
|
||||||
|
style={{ width: 200 }}
|
||||||
|
placeholder="选择目标站点"
|
||||||
|
value={selectedSiteId}
|
||||||
|
onChange={setSelectedSiteId}
|
||||||
|
options={sites.map((site) => ({
|
||||||
|
label: site.name,
|
||||||
|
value: site.id,
|
||||||
|
}))}
|
||||||
|
/>,
|
||||||
|
<Button
|
||||||
|
key="batch-sync"
|
||||||
|
type="primary"
|
||||||
|
icon={<SyncOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
if (!selectedSiteId) {
|
||||||
|
message.warning('请先选择目标站点');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setBatchSyncModalVisible(true);
|
||||||
|
}}
|
||||||
|
disabled={!selectedSiteId || sites.length === 0}
|
||||||
|
>
|
||||||
|
批量同步
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
request={async (params, sort, filter) => {
|
||||||
|
// 调用本地获取产品列表 API
|
||||||
|
const { data, success } = await productcontrollerGetproductlist({
|
||||||
|
...params,
|
||||||
|
current: params.current,
|
||||||
|
pageSize: params.pageSize,
|
||||||
|
// 传递搜索参数
|
||||||
|
keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段
|
||||||
|
sku: (params as any).sku,
|
||||||
|
name: (params as any).name,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
// 返回给 ProTable
|
||||||
|
return {
|
||||||
|
data: (data?.items || []) as ProductWithWP[],
|
||||||
|
success,
|
||||||
|
total: data?.total || 0,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
pagination={{
|
||||||
|
pageSize: 10,
|
||||||
|
showSizeChanger: true,
|
||||||
|
}}
|
||||||
|
scroll={{ x: 'max-content' }}
|
||||||
|
search={{
|
||||||
|
labelWidth: 'auto',
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
density: true,
|
||||||
|
fullScreen: true,
|
||||||
|
}}
|
||||||
|
dateFormatter="string"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 批量同步模态框 */}
|
||||||
|
<Modal
|
||||||
|
title="批量同步产品"
|
||||||
|
open={batchSyncModalVisible}
|
||||||
|
onCancel={() => !syncing && setBatchSyncModalVisible(false)}
|
||||||
|
footer={null}
|
||||||
|
closable={!syncing}
|
||||||
|
maskClosable={!syncing}
|
||||||
|
>
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<p>
|
||||||
|
目标站点:
|
||||||
|
<strong>{sites.find((s) => s.id === selectedSiteId)?.name}</strong>
|
||||||
|
</p>
|
||||||
|
{selectedRows.length > 0 ? (
|
||||||
|
<p>
|
||||||
|
已选择 <strong>{selectedRows.length}</strong> 个产品进行同步
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p>此操作将同步所有库存产品到指定站点,请确认是否继续?</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{syncing && (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<div style={{ marginBottom: 8 }}>同步进度:</div>
|
||||||
|
<Progress percent={syncProgress} status="active" />
|
||||||
|
<div style={{ marginTop: 8, fontSize: 12, color: '#666' }}>
|
||||||
|
成功:{syncResults.success} | 失败:{syncResults.failed}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{syncResults.errors.length > 0 && (
|
||||||
|
<div style={{ marginBottom: 16, maxHeight: 200, overflow: 'auto' }}>
|
||||||
|
<div style={{ marginBottom: 8, color: '#ff4d4f' }}>错误详情:</div>
|
||||||
|
{syncResults.errors.slice(0, 10).map((error, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{ fontSize: 12, color: '#666', marginBottom: 4 }}
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{syncResults.errors.length > 10 && (
|
||||||
|
<div style={{ fontSize: 12, color: '#999' }}>
|
||||||
|
...还有 {syncResults.errors.length - 10} 个错误
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ textAlign: 'right' }}>
|
||||||
|
<Button
|
||||||
|
onClick={() => setBatchSyncModalVisible(false)}
|
||||||
|
disabled={syncing}
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => batchSyncProducts()}
|
||||||
|
loading={syncing}
|
||||||
|
disabled={syncing}
|
||||||
|
>
|
||||||
|
{syncing ? '同步中...' : '开始同步'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductSyncPage;
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 } : {}),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}))}
|
}))}
|
||||||
|
|
|
||||||
|
|
@ -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 } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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 } : {}),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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[];
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 || {}),
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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默认没有生成对象)
|
||||||
|
|
|
||||||
|
|
@ -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默认没有生成对象)
|
||||||
|
|
|
||||||
|
|
@ -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,7 +1227,9 @@ declare namespace API {
|
||||||
data?: Product;
|
data?: Product;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProductSiteSku = {
|
type ProductSiteSkuDTO = {
|
||||||
|
/** 产品ID */
|
||||||
|
productId?: number;
|
||||||
/** 站点SKU */
|
/** 站点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 = {};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue