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 { siteapicontrollerGetorders } from '@/servers/api/siteApi';
|
||||
import {
|
||||
App,
|
||||
Col,
|
||||
|
|
@ -89,8 +88,6 @@ const HistoryOrders: React.FC<HistoryOrdersProps> = ({ customer, siteId }) => {
|
|||
|
||||
// 获取客户订单数据
|
||||
const fetchOrders = async () => {
|
||||
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await ordercontrollerGetorders({
|
||||
|
|
|
|||
|
|
@ -12,10 +12,12 @@ import {
|
|||
ModalForm,
|
||||
PageContainer,
|
||||
ProColumns,
|
||||
ProFormDateTimeRangePicker,
|
||||
ProFormSelect,
|
||||
ProFormText,
|
||||
ProTable,
|
||||
} 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 HistoryOrders from './HistoryOrders';
|
||||
|
||||
|
|
@ -113,7 +115,6 @@ const CustomerList: React.FC = () => {
|
|||
const actionRef = useRef<ActionType>();
|
||||
const { message } = App.useApp();
|
||||
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
||||
const [syncLoading, setSyncLoading] = useState(false);
|
||||
const [sites, setSites] = useState<any[]>([]); // 添加站点数据状态
|
||||
|
||||
// 获取站点数据
|
||||
|
|
@ -135,7 +136,6 @@ const CustomerList: React.FC = () => {
|
|||
return siteId;
|
||||
}
|
||||
const site = sites.find((s) => s.id === siteId);
|
||||
console.log(`site`, site);
|
||||
return site ? site.name : String(siteId);
|
||||
};
|
||||
|
||||
|
|
@ -144,11 +144,14 @@ const CustomerList: React.FC = () => {
|
|||
fetchSites();
|
||||
}, []);
|
||||
|
||||
const columns: ProColumns<API.UnifiedCustomerDTO>[] = [
|
||||
const columns: ProColumns<API.GetCustomerDTO>[] = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '原始 ID',
|
||||
dataIndex: 'origin_id',
|
||||
},
|
||||
{
|
||||
title: '站点',
|
||||
|
|
@ -169,8 +172,9 @@ const CustomerList: React.FC = () => {
|
|||
return [];
|
||||
}
|
||||
},
|
||||
render: (siteId: any) => {
|
||||
return <span>{getSiteName(siteId) || '-'}</span>;
|
||||
render(_, record) {
|
||||
// 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 || '设置评分失败');
|
||||
}
|
||||
}}
|
||||
value={record.raw?.rate || 0}
|
||||
value={record.rate || 0}
|
||||
allowHalf
|
||||
/>
|
||||
);
|
||||
|
|
@ -265,7 +269,7 @@ const CustomerList: React.FC = () => {
|
|||
dataIndex: 'tags',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => {
|
||||
const tags = record.raw?.tags || [];
|
||||
const tags = record?.tags || [];
|
||||
return (
|
||||
<Space size={[0, 8]} wrap>
|
||||
{tags.map((tag: string) => {
|
||||
|
|
@ -327,7 +331,7 @@ const CustomerList: React.FC = () => {
|
|||
tableRef={actionRef}
|
||||
/>
|
||||
{/* 订单 */}
|
||||
<HistoryOrders customer={record} siteId={record.raw?.site_id} />
|
||||
<HistoryOrders customer={record} siteId={record.site_id} />
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
|
|
@ -342,20 +346,59 @@ const CustomerList: React.FC = () => {
|
|||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
request={async (params, sorter) => {
|
||||
// 获取排序字段和排序方向
|
||||
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 {
|
||||
total: data?.total || 0,
|
||||
data: data?.items || [],
|
||||
success,
|
||||
total: result?.data?.total || 0,
|
||||
data: result?.data?.items || [],
|
||||
success: true,
|
||||
};
|
||||
}}
|
||||
columns={columns}
|
||||
|
|
@ -469,6 +512,7 @@ const SyncCustomersModal: React.FC<{
|
|||
const { message } = App.useApp();
|
||||
const [sites, setSites] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm(); // 添加表单实例
|
||||
|
||||
// 获取站点列表
|
||||
useEffect(() => {
|
||||
|
|
@ -487,15 +531,56 @@ const SyncCustomersModal: React.FC<{
|
|||
}
|
||||
}, [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 {
|
||||
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 {
|
||||
success,
|
||||
message: msg,
|
||||
data,
|
||||
} = await customercontrollerSynccustomers({
|
||||
siteId: values.siteId,
|
||||
params: Object.keys(params).length > 0 ? params : undefined,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
|
|
@ -569,6 +654,7 @@ const SyncCustomersModal: React.FC<{
|
|||
confirmLoading: loading,
|
||||
}}
|
||||
onFinish={handleSync}
|
||||
form={form}
|
||||
>
|
||||
<ProFormSelect
|
||||
name="siteId"
|
||||
|
|
@ -583,6 +669,59 @@ const SyncCustomersModal: React.FC<{
|
|||
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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -129,22 +129,16 @@ const ListPage: React.FC = () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
title: 'phone',
|
||||
title: '联系电话',
|
||||
dataIndex: 'phone',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => record?.billing.phone || record?.shipping.phone,
|
||||
},
|
||||
{
|
||||
title: 'state',
|
||||
dataIndex: 'state',
|
||||
title: '账单地址',
|
||||
dataIndex: 'billing',
|
||||
render: (_, record) => record?.billing.state || record?.shipping.state,
|
||||
},
|
||||
{
|
||||
title: 'city',
|
||||
dataIndex: 'city',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => record?.billing.city || record?.shipping.city,
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'tags',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import {
|
|||
message,
|
||||
} from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import DictItemActions from '../components/DictItemActions';
|
||||
import DictItemModal from '../components/DictItemModal';
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
|
||||
|
|
@ -36,13 +38,10 @@ const DictPage: React.FC = () => {
|
|||
const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false);
|
||||
const [editDictData, setEditDictData] = useState<any>(null);
|
||||
|
||||
// 右侧字典项列表的状态
|
||||
const [isAddDictItemModalVisible, setIsAddDictItemModalVisible] =
|
||||
useState(false);
|
||||
const [isEditDictItemModalVisible, setIsEditDictItemModalVisible] =
|
||||
useState(false);
|
||||
const [editDictItemData, setEditDictItemData] = useState<any>(null);
|
||||
const [dictItemForm] = Form.useForm();
|
||||
// 字典项模态框状态(由 DictItemModal 组件管理)
|
||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||
const [isEditDictItem, setIsEditDictItem] = useState(false);
|
||||
const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
// 获取字典列表
|
||||
|
|
@ -144,15 +143,16 @@ const DictPage: React.FC = () => {
|
|||
|
||||
// 添加字典项
|
||||
const handleAddDictItem = () => {
|
||||
dictItemForm.resetFields();
|
||||
setIsAddDictItemModalVisible(true);
|
||||
setIsEditDictItem(false);
|
||||
setEditingDictItemData(null);
|
||||
setIsDictItemModalVisible(true);
|
||||
};
|
||||
|
||||
// 编辑字典项
|
||||
const handleEditDictItem = (record: any) => {
|
||||
setEditDictItemData(record);
|
||||
dictItemForm.setFieldsValue(record);
|
||||
setIsEditDictItemModalVisible(true);
|
||||
setIsEditDictItem(true);
|
||||
setEditingDictItemData(record);
|
||||
setIsDictItemModalVisible(true);
|
||||
};
|
||||
|
||||
// 删除字典项
|
||||
|
|
@ -173,53 +173,42 @@ const DictPage: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// 添加字典项表单提交
|
||||
const handleAddDictItemFormSubmit = async (values: any) => {
|
||||
// 处理字典项模态框提交(添加或编辑)
|
||||
const handleDictItemModalOk = async (values: any) => {
|
||||
try {
|
||||
const result = await dictApi.dictcontrollerCreatedictitem({
|
||||
...values,
|
||||
dictId: selectedDict.id,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || '添加失败');
|
||||
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({
|
||||
...values,
|
||||
dictId: selectedDict.id,
|
||||
});
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || '添加失败');
|
||||
}
|
||||
message.success('添加成功');
|
||||
}
|
||||
|
||||
message.success('添加成功');
|
||||
setIsAddDictItemModalVisible(false);
|
||||
setIsDictItemModalVisible(false);
|
||||
|
||||
// 强制刷新字典项列表
|
||||
setTimeout(() => {
|
||||
actionRef.current?.reload();
|
||||
}, 100);
|
||||
} catch (error: any) {
|
||||
message.error(`添加失败:${error.message || '未知错误'}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 编辑字典项表单提交
|
||||
const handleEditDictItemFormSubmit = async (values: any) => {
|
||||
if (!editDictItemData) return;
|
||||
try {
|
||||
const result = await dictApi.dictcontrollerUpdatedictitem(
|
||||
{ id: editDictItemData.id },
|
||||
values,
|
||||
message.error(
|
||||
`${isEditDictItem ? '更新' : '添加'}失败:${
|
||||
error.message || '未知错误'
|
||||
}`,
|
||||
);
|
||||
|
||||
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',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '图片',
|
||||
dataIndex: 'image',
|
||||
key: 'image',
|
||||
valueType: 'image',
|
||||
width: 80,
|
||||
},
|
||||
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
|
|
@ -356,6 +339,13 @@ const DictPage: React.FC = () => {
|
|||
key: 'titleCN',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '图片',
|
||||
dataIndex: 'image',
|
||||
key: 'image',
|
||||
valueType: 'image',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
|
|
@ -406,7 +396,18 @@ const DictPage: React.FC = () => {
|
|||
</Button>
|
||||
<Upload
|
||||
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}
|
||||
onChange={(info) => {
|
||||
if (info.file.status === 'done') {
|
||||
|
|
@ -495,151 +496,46 @@ const DictPage: React.FC = () => {
|
|||
size="small"
|
||||
key={selectedDict?.id}
|
||||
toolBarRender={() => [
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleAddDictItem}
|
||||
disabled={!selectedDict}
|
||||
size="small"
|
||||
key="add"
|
||||
>
|
||||
添加字典项
|
||||
</Button>,
|
||||
<Upload
|
||||
name="file"
|
||||
action={undefined}
|
||||
customRequest={async (options) => {
|
||||
const { file, onSuccess, onError } = options;
|
||||
try {
|
||||
const result =
|
||||
await dictApi.dictcontrollerImportdictitems(
|
||||
{ dictId: selectedDict?.id },
|
||||
[file as File],
|
||||
);
|
||||
onSuccess?.(result);
|
||||
} catch (error) {
|
||||
onError?.(error as Error);
|
||||
}
|
||||
<DictItemActions
|
||||
key="dictItemActions"
|
||||
selectedDict={selectedDict}
|
||||
actionRef={actionRef}
|
||||
showExport={true}
|
||||
onImport={async (file: File, dictId: number) => {
|
||||
// 创建 FormData 对象
|
||||
const formData = new FormData();
|
||||
// 添加文件到 FormData
|
||||
formData.append('file', file);
|
||||
// 添加字典 ID 到 FormData
|
||||
formData.append('dictId', String(dictId));
|
||||
// 调用导入字典项的 API,直接返回解析后的 JSON 对象
|
||||
const result = await dictApi.dictcontrollerImportdictitems(
|
||||
formData,
|
||||
);
|
||||
return result;
|
||||
}}
|
||||
showUploadList={false}
|
||||
disabled={!selectedDict}
|
||||
onChange={(info) => {
|
||||
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>,
|
||||
onExport={handleExportDictItems}
|
||||
onAdd={handleAddDictItem}
|
||||
onRefreshDicts={fetchDicts}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
</Content>
|
||||
</Layout>
|
||||
|
||||
{/* 添加字典项 Modal */}
|
||||
<Modal
|
||||
title={`添加字典项 - ${selectedDict?.title || '未选择字典'}`}
|
||||
open={isAddDictItemModalVisible}
|
||||
onOk={() => dictItemForm.submit()}
|
||||
onCancel={() => setIsAddDictItemModalVisible(false)}
|
||||
>
|
||||
<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()}
|
||||
{/* 字典项 Modal(添加或编辑) */}
|
||||
<DictItemModal
|
||||
visible={isDictItemModalVisible}
|
||||
isEdit={isEditDictItem}
|
||||
editingData={editingDictItemData}
|
||||
selectedDict={selectedDict}
|
||||
onCancel={() => {
|
||||
setIsEditDictItemModalVisible(false);
|
||||
setEditDictItemData(null);
|
||||
setIsDictItemModalVisible(false);
|
||||
setEditingDictItemData(null);
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
onOk={handleDictItemModalOk}
|
||||
/>
|
||||
|
||||
{/* 添加字典 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 SyncForm from '@/components/SyncForm';
|
||||
import { showSyncResult, SyncResultData } from '@/components/SyncResultMessage';
|
||||
import { ORDER_STATUS_ENUM } from '@/constants';
|
||||
import { HistoryOrder } from '@/pages/Statistics/Order';
|
||||
import {
|
||||
|
|
@ -21,6 +22,7 @@ import {
|
|||
ordercontrollerGetorders,
|
||||
ordercontrollerRefundorder,
|
||||
ordercontrollerSyncorderbyid,
|
||||
ordercontrollerSyncorders,
|
||||
ordercontrollerUpdateorderitems,
|
||||
} from '@/servers/api/order';
|
||||
import { productcontrollerSearchproducts } from '@/servers/api/product';
|
||||
|
|
@ -73,7 +75,6 @@ import {
|
|||
Tag,
|
||||
} from 'antd';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import { request, useParams } from '@umijs/max';
|
||||
import RelatedOrders from '../../Subscription/Orders/RelatedOrders';
|
||||
|
||||
const ListPage: React.FC = () => {
|
||||
|
|
@ -199,7 +200,7 @@ const ListPage: React.FC = () => {
|
|||
dataIndex: 'keyword',
|
||||
hideInTable: true,
|
||||
},
|
||||
{
|
||||
{
|
||||
title: '订单ID',
|
||||
dataIndex: 'externalOrderId',
|
||||
},
|
||||
|
|
@ -250,6 +251,7 @@ const ListPage: React.FC = () => {
|
|||
{
|
||||
title: '状态',
|
||||
dataIndex: 'orderStatus',
|
||||
hideInSearch: true,
|
||||
valueType: 'select',
|
||||
valueEnum: ORDER_STATUS_ENUM,
|
||||
},
|
||||
|
|
@ -339,15 +341,18 @@ const ListPage: React.FC = () => {
|
|||
message.error('站点ID或外部订单ID不存在');
|
||||
return;
|
||||
}
|
||||
const { success, message: errMsg } =
|
||||
await ordercontrollerSyncorderbyid({
|
||||
siteId: record.siteId,
|
||||
orderId: record.externalOrderId,
|
||||
});
|
||||
const {
|
||||
success,
|
||||
message: errMsg,
|
||||
data,
|
||||
} = await ordercontrollerSyncorderbyid({
|
||||
siteId: record.siteId,
|
||||
orderId: record.externalOrderId,
|
||||
});
|
||||
if (!success) {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
message.success('同步成功');
|
||||
showSyncResult(data as SyncResultData, '订单');
|
||||
actionRef.current?.reload();
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '同步失败');
|
||||
|
|
@ -467,16 +472,20 @@ const ListPage: React.FC = () => {
|
|||
defaultPageSize: 10,
|
||||
}}
|
||||
toolBarRender={() => [
|
||||
<CreateOrder tableRef={actionRef} />,
|
||||
// <CreateOrder tableRef={actionRef} />,
|
||||
<SyncForm
|
||||
onFinish={async (values: any) => {
|
||||
try {
|
||||
const { success, message: errMsg } =
|
||||
await ordercontrollerSyncorderbyid(values);
|
||||
const {
|
||||
success,
|
||||
message: errMsg,
|
||||
data,
|
||||
} = await ordercontrollerSyncorders(values);
|
||||
if (!success) {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
message.success('同步成功');
|
||||
// 使用 showSyncResult 函数显示详细的同步结果
|
||||
showSyncResult(data as SyncResultData, '订单');
|
||||
actionRef.current?.reload();
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '同步失败');
|
||||
|
|
@ -491,29 +500,19 @@ const ListPage: React.FC = () => {
|
|||
// >
|
||||
// 批量导出
|
||||
// </Button>,
|
||||
<Popconfirm
|
||||
<Popconfirm
|
||||
title="批量导出"
|
||||
description="确认导出选中的订单吗?"
|
||||
onConfirm={async () => {
|
||||
console.log(selectedRowKeys);
|
||||
try {
|
||||
const res = await request('/order/order/export', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
const { success, message: errMsg } =
|
||||
await ordercontrollerExportorder({
|
||||
ids: selectedRowKeys,
|
||||
}
|
||||
});
|
||||
if (res?.success && res?.data?.csv) {
|
||||
const blob = new Blob([res.data.csv], { type: 'text/csv;charset=utf-8;' });
|
||||
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 || '导出失败');
|
||||
});
|
||||
if (!success) {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
message.success('导出成功');
|
||||
actionRef.current?.reload();
|
||||
setSelectedRowKeys([]);
|
||||
} catch (error: any) {
|
||||
|
|
@ -521,10 +520,14 @@ const ListPage: React.FC = () => {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<Button type="primary" ghost>
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={selectedRowKeys.length === 0}
|
||||
ghost
|
||||
>
|
||||
批量导出
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Popconfirm>,
|
||||
]}
|
||||
request={async ({ date, ...param }: any) => {
|
||||
if (param.status === 'all') {
|
||||
|
|
@ -622,15 +625,18 @@ const Detail: React.FC<{
|
|||
message.error('站点ID或外部订单ID不存在');
|
||||
return;
|
||||
}
|
||||
const { success, message: errMsg } =
|
||||
await ordercontrollerSyncorderbyid({
|
||||
siteId: record.siteId,
|
||||
orderId: record.externalOrderId,
|
||||
});
|
||||
const {
|
||||
success,
|
||||
message: errMsg,
|
||||
data,
|
||||
} = await ordercontrollerSyncorderbyid({
|
||||
siteId: record.siteId,
|
||||
orderId: record.externalOrderId,
|
||||
});
|
||||
if (!success) {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
message.success('同步成功');
|
||||
showSyncResult(data as SyncResultData, '订单');
|
||||
tableRef.current?.reload();
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '同步失败');
|
||||
|
|
@ -2122,12 +2128,12 @@ const SalesChange: React.FC<{
|
|||
params={{}}
|
||||
request={async ({ keyWords }) => {
|
||||
try {
|
||||
const { data } = await wpproductcontrollerSearchproducts({
|
||||
const { data } = await productcontrollerSearchproducts({
|
||||
name: keyWords,
|
||||
});
|
||||
return data?.map((item) => {
|
||||
return {
|
||||
label: `${item.name}`,
|
||||
label: `${item.name} - ${item.nameCn}`,
|
||||
value: item?.sku,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,22 +1,14 @@
|
|||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import * as dictApi from '@/servers/api/dict';
|
||||
import {
|
||||
ActionType,
|
||||
PageContainer,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { request } from '@umijs/max';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
Layout,
|
||||
Modal,
|
||||
Space,
|
||||
Table,
|
||||
Upload,
|
||||
message,
|
||||
} from 'antd';
|
||||
import { Button, Input, Layout, Space, Table, message } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import DictItemActions from '../../Dict/components/DictItemActions';
|
||||
import DictItemModal from '../../Dict/components/DictItemModal';
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
|
||||
|
|
@ -32,10 +24,84 @@ const AttributePage: React.FC = () => {
|
|||
// 右侧字典项 ProTable 的引用
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
// 字典项新增/编辑模态框控制
|
||||
// 字典项模态框状态(由 DictItemModal 组件管理)
|
||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||
const [editingDictItem, setEditingDictItem] = useState<any>(null);
|
||||
const [dictItemForm] = Form.useForm();
|
||||
const [isEditDictItem, setIsEditDictItem] = useState(false);
|
||||
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) => {
|
||||
setLoadingDicts(true);
|
||||
|
|
@ -65,24 +131,24 @@ const AttributePage: React.FC = () => {
|
|||
|
||||
// 打开添加字典项模态框
|
||||
const handleAddDictItem = () => {
|
||||
setEditingDictItem(null);
|
||||
dictItemForm.resetFields();
|
||||
setIsEditDictItem(false);
|
||||
setEditingDictItemData(null);
|
||||
setIsDictItemModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开编辑字典项模态框
|
||||
const handleEditDictItem = (item: any) => {
|
||||
setEditingDictItem(item);
|
||||
dictItemForm.setFieldsValue(item);
|
||||
setIsEditDictItem(true);
|
||||
setEditingDictItemData(item);
|
||||
setIsDictItemModalVisible(true);
|
||||
};
|
||||
|
||||
// 字典项表单提交(新增或编辑)
|
||||
const handleDictItemFormSubmit = async (values: any) => {
|
||||
try {
|
||||
if (editingDictItem) {
|
||||
if (isEditDictItem && editingDictItemData) {
|
||||
// 条件判断,存在编辑项则执行更新
|
||||
await request(`/dict/item/${editingDictItem.id}`, {
|
||||
await request(`/dict/item/${editingDictItemData.id}`, {
|
||||
method: 'PUT',
|
||||
data: values,
|
||||
});
|
||||
|
|
@ -98,7 +164,7 @@ const AttributePage: React.FC = () => {
|
|||
setIsDictItemModalVisible(false);
|
||||
actionRef.current?.reload(); // 刷新 ProTable
|
||||
} catch (error) {
|
||||
message.error(editingDictItem ? '更新失败' : '添加失败');
|
||||
message.error(isEditDictItem ? '更新失败' : '添加失败');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -296,85 +362,45 @@ const AttributePage: React.FC = () => {
|
|||
size="small"
|
||||
key={selectedDict?.id}
|
||||
headerTitle={
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={handleAddDictItem}
|
||||
disabled={!selectedDict}
|
||||
>
|
||||
添加字典项
|
||||
</Button>
|
||||
<Upload
|
||||
name="file"
|
||||
action={`/dict/item/import`}
|
||||
data={{ dictId: selectedDict?.id }}
|
||||
showUploadList={false}
|
||||
disabled={!selectedDict}
|
||||
onChange={(info) => {
|
||||
// 条件判断,上传状态处理
|
||||
if (info.file.status === 'done') {
|
||||
message.success(`${info.file.name} 文件上传成功`);
|
||||
actionRef.current?.reload();
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error(`${info.file.name} 文件上传失败`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<UploadOutlined />}
|
||||
disabled={!selectedDict}
|
||||
>
|
||||
导入字典项
|
||||
</Button>
|
||||
</Upload>
|
||||
</Space>
|
||||
<DictItemActions
|
||||
selectedDict={selectedDict}
|
||||
actionRef={actionRef}
|
||||
showExport={true}
|
||||
onImport={async (file: File, dictId: number) => {
|
||||
// 创建 FormData 对象
|
||||
const formData = new FormData();
|
||||
// 添加文件到 FormData
|
||||
formData.append('file', file);
|
||||
// 添加字典 ID 到 FormData
|
||||
formData.append('dictId', String(dictId));
|
||||
// 调用导入字典项的 API
|
||||
const response = await dictApi.dictcontrollerImportdictitems(
|
||||
formData,
|
||||
);
|
||||
// 返回 JSON 响应
|
||||
return await response.json();
|
||||
}}
|
||||
onExport={handleExportDictItems}
|
||||
onAdd={handleAddDictItem}
|
||||
onRefreshDicts={fetchDicts}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Content>
|
||||
</Layout>
|
||||
|
||||
<Modal
|
||||
title={editingDictItem ? '编辑字典项' : '添加字典项'}
|
||||
open={isDictItemModalVisible}
|
||||
onOk={() => dictItemForm.submit()}
|
||||
onCancel={() => setIsDictItemModalVisible(false)}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form
|
||||
form={dictItemForm}
|
||||
layout="vertical"
|
||||
onFinish={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>
|
||||
{/* 字典项 Modal(添加或编辑) */}
|
||||
<DictItemModal
|
||||
visible={isDictItemModalVisible}
|
||||
isEdit={isEditDictItem}
|
||||
editingData={editingDictItemData}
|
||||
selectedDict={selectedDict}
|
||||
onCancel={() => {
|
||||
setIsDictItemModalVisible(false);
|
||||
setEditingDictItemData(null);
|
||||
}}
|
||||
onOk={handleDictItemFormSubmit}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -137,8 +137,8 @@ const CreateForm: React.FC<{
|
|||
humidityName === 'dry'
|
||||
? 'Dry'
|
||||
: humidityName === 'moisture'
|
||||
? 'Moisture'
|
||||
: capitalize(humidityName),
|
||||
? 'Moisture'
|
||||
: capitalize(humidityName),
|
||||
},
|
||||
);
|
||||
if (!success) {
|
||||
|
|
@ -246,13 +246,7 @@ const CreateForm: React.FC<{
|
|||
placeholder="请输入SKU"
|
||||
rules={[{ required: true, message: '请输入SKU' }]}
|
||||
/>
|
||||
<ProFormSelect
|
||||
name="siteSkus"
|
||||
label="站点 SKU 列表"
|
||||
width="md"
|
||||
mode="tags"
|
||||
placeholder="输入站点 SKU,回车添加"
|
||||
/>
|
||||
|
||||
<Button style={{ marginTop: '32px' }} onClick={handleGenerateSku}>
|
||||
自动生成
|
||||
</Button>
|
||||
|
|
@ -265,6 +259,14 @@ const CreateForm: React.FC<{
|
|||
</Tag>
|
||||
)}
|
||||
</ProForm.Group>
|
||||
<ProFormSelect
|
||||
name="siteSkus"
|
||||
initialValue={[]}
|
||||
label="站点 SKU 列表"
|
||||
width="md"
|
||||
mode="tags"
|
||||
placeholder="输入站点 SKU,回车添加"
|
||||
/>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
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 {
|
||||
productcontrollerBatchdeleteproduct,
|
||||
productcontrollerBatchupdateproduct,
|
||||
productcontrollerBindproductsiteskus,
|
||||
productcontrollerDeleteproduct,
|
||||
productcontrollerGetcategoriesall,
|
||||
productcontrollerGetproductcomponents,
|
||||
productcontrollerGetproductlist,
|
||||
productcontrollerUpdatenamecn,
|
||||
productcontrollerUpdatenamecn
|
||||
} from '@/servers/api/product';
|
||||
import { sitecontrollerAll } from '@/servers/api/site';
|
||||
import { siteapicontrollerGetproducts } from '@/servers/api/siteApi';
|
||||
import {
|
||||
ActionType,
|
||||
ModalForm,
|
||||
|
|
@ -17,13 +14,14 @@ import {
|
|||
ProColumns,
|
||||
ProFormSelect,
|
||||
ProFormText,
|
||||
ProTable,
|
||||
ProTable
|
||||
} from '@ant-design/pro-components';
|
||||
import { request } from '@umijs/max';
|
||||
import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import CreateForm from './CreateForm';
|
||||
import EditForm from './EditForm';
|
||||
import SyncToSiteModal from './SyncToSiteModal';
|
||||
|
||||
const NameCn: React.FC<{
|
||||
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 actionRef = useRef<ActionType>();
|
||||
// 状态:存储当前选中的行
|
||||
const [selectedRows, setSelectedRows] = React.useState<API.Product[]>([]);
|
||||
const [batchEditModalVisible, setBatchEditModalVisible] = useState(false);
|
||||
const [syncProducts, setSyncProducts] = useState<API.Product[]>([]);
|
||||
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
||||
const [syncProductIds, setSyncProductIds] = useState<number[]>([]);
|
||||
|
||||
const { message } = App.useApp();
|
||||
// 导出产品 CSV(带认证请求)
|
||||
|
|
@ -460,7 +211,7 @@ const List: React.FC = () => {
|
|||
<>
|
||||
{record.siteSkus?.map((siteSku, index) => (
|
||||
<Tag key={index} color="cyan">
|
||||
{siteSku.siteSku}
|
||||
{siteSku}
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
|
|
@ -564,7 +315,7 @@ const List: React.FC = () => {
|
|||
<Button
|
||||
type="link"
|
||||
onClick={() => {
|
||||
setSyncProductIds([record.id]);
|
||||
setSyncProducts([record]);
|
||||
setSyncModalVisible(true);
|
||||
}}
|
||||
>
|
||||
|
|
@ -693,7 +444,7 @@ const List: React.FC = () => {
|
|||
<Button
|
||||
disabled={selectedRows.length <= 0}
|
||||
onClick={() => {
|
||||
setSyncProductIds(selectedRows.map((row) => row.id));
|
||||
setSyncProducts(selectedRows);
|
||||
setSyncModalVisible(true);
|
||||
}}
|
||||
>
|
||||
|
|
@ -756,7 +507,7 @@ const List: React.FC = () => {
|
|||
columns={columns}
|
||||
expandable={{
|
||||
expandedRowRender: (record) => (
|
||||
<WpProductInfo
|
||||
<SiteProductInfo
|
||||
skus={(record.siteSkus as string[]) || []}
|
||||
record={record}
|
||||
parentTableRef={actionRef}
|
||||
|
|
@ -788,8 +539,7 @@ const List: React.FC = () => {
|
|||
<SyncToSiteModal
|
||||
visible={syncModalVisible}
|
||||
onClose={() => setSyncModalVisible(false)}
|
||||
productIds={syncProductIds}
|
||||
productRows={selectedRows}
|
||||
products={syncProducts}
|
||||
onSuccess={() => {
|
||||
setSyncModalVisible(false);
|
||||
setSelectedRows([]);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
productcontrollerGetcategoryattributes,
|
||||
productcontrollerGetproductlist,
|
||||
} from '@/servers/api/product';
|
||||
import { DownloadOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
ActionType,
|
||||
PageContainer,
|
||||
|
|
@ -201,6 +202,92 @@ const PermutationPage: React.FC = () => {
|
|||
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[] = [
|
||||
...attributes.map((attr) => ({
|
||||
title: attr.title || attr.name,
|
||||
|
|
@ -317,7 +404,16 @@ const PermutationPage: React.FC = () => {
|
|||
}}
|
||||
scroll={{ x: 'max-content' }}
|
||||
search={false}
|
||||
toolBarRender={false}
|
||||
toolBarRender={() => [
|
||||
<Button
|
||||
key="export"
|
||||
type="default"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={handleExport}
|
||||
>
|
||||
导出列表
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</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 { templatecontrollerGettemplatebyname } from '@/servers/api/template';
|
||||
import { EditOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { showBatchOperationResult } from '@/utils/showResult';
|
||||
import {
|
||||
ActionType,
|
||||
ModalForm,
|
||||
ProColumns,
|
||||
ProFormText,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
productcontrollerBatchsynctosite,
|
||||
productcontrollerGetproductlist,
|
||||
productcontrollerSynctosite,
|
||||
} from '@/servers/api/product';
|
||||
import { EditOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||
import { request } from '@umijs/max';
|
||||
import {
|
||||
Button,
|
||||
|
|
@ -21,39 +19,35 @@ import {
|
|||
} from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import EditForm from '../List/EditForm';
|
||||
import SiteProductCell from './SiteProductCell';
|
||||
|
||||
// 定义站点接口
|
||||
interface Site {
|
||||
id: string;
|
||||
id: number;
|
||||
name: string;
|
||||
skuPrefix?: string;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
// 定义WordPress商品接口
|
||||
interface WpProduct {
|
||||
id?: number;
|
||||
externalProductId?: string;
|
||||
// 定义本地产品接口(与后端 Product 实体匹配)
|
||||
interface SiteProduct {
|
||||
id: number;
|
||||
sku: string;
|
||||
name: string;
|
||||
price: string;
|
||||
regular_price?: string;
|
||||
sale_price?: string;
|
||||
stock_quantity: number;
|
||||
stockQuantity?: number;
|
||||
status: string;
|
||||
nameCn: string;
|
||||
shortDescription?: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
promotionPrice: number;
|
||||
type: string;
|
||||
categoryId?: number;
|
||||
category?: any;
|
||||
attributes?: any[];
|
||||
constitution?: { sku: string; quantity: number }[];
|
||||
}
|
||||
|
||||
// 扩展本地产品接口,包含对应的 WP 产品信息
|
||||
interface ProductWithWP extends API.Product {
|
||||
wpProducts: Record<string, WpProduct>;
|
||||
attributes?: any[];
|
||||
siteSkus?: Array<{
|
||||
siteSku: string;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
components?: any[];
|
||||
siteSkus: string[];
|
||||
source: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// 定义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 [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 actionRef = useRef<ActionType>();
|
||||
const [selectedSiteId, setSelectedSiteId] = useState<string>('');
|
||||
|
|
@ -104,102 +88,46 @@ const ProductSyncPage: React.FC = () => {
|
|||
errors: string[];
|
||||
}>({ success: 0, failed: 0, errors: [] });
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const [selectedRows, setSelectedRows] = useState<ProductWithWP[]>([]);
|
||||
|
||||
// 初始化数据:获取站点和所有 WP 产品
|
||||
const [selectedRows, setSelectedRows] = useState<SiteProduct[]>([]);
|
||||
// 初始化加载站点列表
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const initializeData = async () => {
|
||||
try {
|
||||
setInitialLoading(true);
|
||||
// 获取所有站点
|
||||
const sitesResponse = await getSites();
|
||||
const rawSiteList = sitesResponse.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.');
|
||||
// 获取站点列表
|
||||
const sitesRes = await getSites();
|
||||
if (sitesRes.success && sitesRes.data.length > 0) {
|
||||
setSites(sitesRes.data);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('获取基础数据失败,请重试');
|
||||
console.error('Error fetching data:', error);
|
||||
console.error('初始化数据失败:', error);
|
||||
message.error('初始化数据失败');
|
||||
} finally {
|
||||
setInitialLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
initializeData();
|
||||
}, []);
|
||||
|
||||
// 同步产品到站点
|
||||
const syncProductToSite = async (
|
||||
values: any,
|
||||
record: ProductWithWP,
|
||||
record: SiteProduct,
|
||||
site: Site,
|
||||
wpProductId?: string,
|
||||
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 (wpProductId) {
|
||||
res = await request(`/site-api/${site.id}/products/${wpProductId}`, {
|
||||
method: 'PUT',
|
||||
data,
|
||||
});
|
||||
} else {
|
||||
res = await request(`/site-api/${site.id}/products`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
// 使用 productcontrollerSynctosite API 同步产品到站点
|
||||
const res = await productcontrollerSynctosite({
|
||||
productId: Number(record.id),
|
||||
siteId: Number(site.id),
|
||||
} as any);
|
||||
|
||||
console.log('res', res);
|
||||
if (!res.success) {
|
||||
hide();
|
||||
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();
|
||||
message.success('同步成功');
|
||||
|
|
@ -207,12 +135,11 @@ const ProductSyncPage: React.FC = () => {
|
|||
} catch (error: any) {
|
||||
message.error('同步失败: ' + (error.message || error.toString()));
|
||||
return false;
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
|
||||
// 批量同步产品到指定站点
|
||||
const batchSyncProducts = async (productsToSync?: ProductWithWP[]) => {
|
||||
const batchSyncProducts = async (productsToSync?: SiteProduct[]) => {
|
||||
if (!selectedSiteId) {
|
||||
message.error('请选择要同步到的站点');
|
||||
return;
|
||||
|
|
@ -239,7 +166,7 @@ const ProductSyncPage: React.FC = () => {
|
|||
message.error('获取产品列表失败');
|
||||
return;
|
||||
}
|
||||
products = data.items as ProductWithWP[];
|
||||
products = data.items as SiteProduct[];
|
||||
} catch (error) {
|
||||
message.error('获取产品列表失败');
|
||||
return;
|
||||
|
|
@ -250,108 +177,44 @@ const ProductSyncPage: React.FC = () => {
|
|||
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;
|
||||
}
|
||||
}
|
||||
// 使用 productcontrollerBatchsynctosite API 批量同步
|
||||
const productIds = products.map((product) => Number(product.id));
|
||||
|
||||
// 如果没有找到实际的siteSku,则根据模板生成
|
||||
const expectedSku =
|
||||
siteProductSku ||
|
||||
(skuTemplate
|
||||
? renderSiteSku(skuTemplate, { site: targetSite, product })
|
||||
: `${targetSite.skuPrefix || ''}-${product.sku}`);
|
||||
// 更新进度为50%,表示正在处理
|
||||
setSyncProgress(50);
|
||||
|
||||
// 检查是否已存在
|
||||
const existingProduct = wpProductMap.get(expectedSku);
|
||||
const res = await productcontrollerBatchsynctosite({
|
||||
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,
|
||||
};
|
||||
if (res.success) {
|
||||
const syncedCount = res.data?.synced || 0;
|
||||
const errors = res.data?.errors || [];
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
// 更新进度为100%,表示完成
|
||||
setSyncProgress(100);
|
||||
|
||||
console.log('res', res);
|
||||
setSyncResults({
|
||||
success: syncedCount,
|
||||
failed: errors.length,
|
||||
errors: errors.map((err: any) => err.error || '未知错误'),
|
||||
});
|
||||
|
||||
if (res.success) {
|
||||
successCount++;
|
||||
// 更新本地缓存
|
||||
setWpProductMap((prev) => {
|
||||
const newMap = new Map(prev);
|
||||
if (res.data && typeof res.data === 'object') {
|
||||
newMap.set(expectedSku, res.data as WpProduct);
|
||||
}
|
||||
return newMap;
|
||||
});
|
||||
} else {
|
||||
failedCount++;
|
||||
errors.push(`产品 ${product.sku}: ${res.message || '同步失败'}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
failedCount++;
|
||||
errors.push(`产品 ${product.sku}: ${error.message || '未知错误'}`);
|
||||
if (errors.length === 0) {
|
||||
message.success(`批量同步完成,成功同步 ${syncedCount} 个产品`);
|
||||
} else {
|
||||
message.warning(
|
||||
`批量同步完成,成功 ${syncedCount} 个,失败 ${errors.length} 个`,
|
||||
);
|
||||
}
|
||||
|
||||
processed++;
|
||||
setSyncProgress(Math.round((processed / totalProducts) * 100));
|
||||
}
|
||||
|
||||
setSyncResults({ success: successCount, failed: failedCount, errors });
|
||||
|
||||
if (failedCount === 0) {
|
||||
message.success(`批量同步完成,成功同步 ${successCount} 个产品`);
|
||||
// 刷新表格
|
||||
actionRef.current?.reload();
|
||||
} else {
|
||||
message.warning(
|
||||
`批量同步完成,成功 ${successCount} 个,失败 ${failedCount} 个`,
|
||||
);
|
||||
throw new Error(res.message || '批量同步失败');
|
||||
}
|
||||
|
||||
// 刷新表格
|
||||
actionRef.current?.reload();
|
||||
} catch (error: any) {
|
||||
message.error('批量同步失败: ' + (error.message || error.toString()));
|
||||
} 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 columns: ProColumns<ProductWithWP>[] = [
|
||||
const generateColumns = (): ProColumns<Site>[] => {
|
||||
const columns: ProColumns<SiteProduct>[] = [
|
||||
{
|
||||
title: 'SKU',
|
||||
dataIndex: 'sku',
|
||||
|
|
@ -471,137 +316,21 @@ const ProductSyncPage: React.FC = () => {
|
|||
|
||||
// 为每个站点生成列
|
||||
sites.forEach((site: Site) => {
|
||||
const siteColumn: ProColumns<ProductWithWP> = {
|
||||
const siteColumn: ProColumns<SiteProduct> = {
|
||||
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 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>
|
||||
<SiteProductCell
|
||||
product={record}
|
||||
site={site}
|
||||
onSyncSuccess={() => {
|
||||
// 同步成功后刷新表格
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
@ -624,7 +353,7 @@ const ProductSyncPage: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Card title="商品同步状态" className="product-sync-card">
|
||||
<ProTable<ProductWithWP>
|
||||
<ProTable<SiteProduct>
|
||||
columns={generateColumns()}
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
|
|
@ -665,21 +394,21 @@ const ProductSyncPage: React.FC = () => {
|
|||
]}
|
||||
request={async (params, sort, filter) => {
|
||||
// 调用本地获取产品列表 API
|
||||
const { data, success } = await productcontrollerGetproductlist({
|
||||
const response = await productcontrollerGetproductlist({
|
||||
...params,
|
||||
current: params.current,
|
||||
pageSize: params.pageSize,
|
||||
// 传递搜索参数
|
||||
keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段
|
||||
// keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段
|
||||
sku: (params as any).sku,
|
||||
name: (params as any).name,
|
||||
} as any);
|
||||
|
||||
console.log('result', response);
|
||||
// 返回给 ProTable
|
||||
return {
|
||||
data: (data?.items || []) as ProductWithWP[],
|
||||
success,
|
||||
total: data?.total || 0,
|
||||
data: response.data?.items || [],
|
||||
success: response.success,
|
||||
total: response.data?.total || 0,
|
||||
};
|
||||
}}
|
||||
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 {
|
||||
sitecontrollerCreate,
|
||||
sitecontrollerDisable,
|
||||
|
|
@ -48,23 +48,14 @@ const SiteList: React.FC = () => {
|
|||
const hide = message.loading('正在同步...', 0);
|
||||
|
||||
const stats = {
|
||||
products: { success: 0, fail: 0 },
|
||||
orders: { success: 0, fail: 0 },
|
||||
subscriptions: { success: 0, fail: 0 },
|
||||
};
|
||||
|
||||
try {
|
||||
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) {
|
||||
stats.orders.success += 1;
|
||||
} else {
|
||||
|
|
@ -85,9 +76,6 @@ const SiteList: React.FC = () => {
|
|||
message: '同步完成',
|
||||
description: (
|
||||
<div>
|
||||
<p>
|
||||
产品: 成功 {stats.products.success}, 失败 {stats.products.fail}
|
||||
</p>
|
||||
<p>
|
||||
订单: 成功 {stats.orders.success}, 失败 {stats.orders.fail}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ const CustomerPage: React.FC = () => {
|
|||
const response = await request(`/site-api/${siteId}/customers`, {
|
||||
params: {
|
||||
page: current,
|
||||
page_size: pageSize,
|
||||
per_page: pageSize,
|
||||
where,
|
||||
...(orderObj ? { order: orderObj } : {}),
|
||||
...(name || email ? { search: name || email } : {}),
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ const ShopLayout: React.FC = () => {
|
|||
<Select
|
||||
style={{ flex: 1 }}
|
||||
placeholder="请选择店铺"
|
||||
options={sites.map((site) => ({
|
||||
options={sites?.map?.((site) => ({
|
||||
label: site.name,
|
||||
value: site.id,
|
||||
}))}
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ const MediaPage: React.FC = () => {
|
|||
const response = await request(`/site-api/${siteId}/media`, {
|
||||
params: {
|
||||
page: current,
|
||||
page_size: pageSize,
|
||||
per_page: pageSize,
|
||||
...(orderObj ? { order: orderObj } : {}),
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
CreateOrder,
|
||||
EditOrder,
|
||||
OrderNote,
|
||||
ShipOrderForm,
|
||||
} from '../components/Order/Forms';
|
||||
|
||||
const OrdersPage: React.FC = () => {
|
||||
|
|
@ -111,7 +112,9 @@ const OrdersPage: React.FC = () => {
|
|||
return (
|
||||
<div>
|
||||
{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>
|
||||
);
|
||||
|
|
@ -145,6 +148,44 @@ const OrdersPage: React.FC = () => {
|
|||
ellipsis: 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: '操作',
|
||||
dataIndex: 'option',
|
||||
|
|
@ -186,28 +227,17 @@ const OrdersPage: React.FC = () => {
|
|||
>
|
||||
<Button type="text" icon={<EllipsisOutlined />} />
|
||||
</Dropdown>
|
||||
<Button
|
||||
type="link"
|
||||
title="发货"
|
||||
onClick={async () => {
|
||||
try {
|
||||
const res = await request(
|
||||
`/site-api/${siteId}/orders/${record.id}/ship`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
if (res.success) {
|
||||
message.success('发货成功');
|
||||
actionRef.current?.reload();
|
||||
} else {
|
||||
message.error(res.message || '发货失败');
|
||||
}
|
||||
} catch (e) {
|
||||
message.error('发货失败');
|
||||
}
|
||||
}}
|
||||
>
|
||||
发货
|
||||
</Button>
|
||||
<ShipOrderForm
|
||||
orderId={record.id as number}
|
||||
tableRef={actionRef}
|
||||
siteId={siteId}
|
||||
orderItems={(record as any).line_items?.map((item: any) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
quantity: item.quantity,
|
||||
sku: item.sku,
|
||||
}))}
|
||||
/>
|
||||
{record.status === 'completed' && (
|
||||
<Popconfirm
|
||||
title="确定取消发货?"
|
||||
|
|
@ -369,18 +399,7 @@ const OrdersPage: React.FC = () => {
|
|||
</ModalForm>,
|
||||
]}
|
||||
request={async (params, sort, filter) => {
|
||||
const p: any = 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 { current, pageSize, date, status, ...rest } = params;
|
||||
const where: Record<string, any> = { ...(filter || {}), ...rest };
|
||||
if (status && status !== 'all') {
|
||||
where.status = status;
|
||||
|
|
@ -401,7 +420,7 @@ const OrdersPage: React.FC = () => {
|
|||
const response = await request(`/site-api/${siteId}/orders`, {
|
||||
params: {
|
||||
page: current,
|
||||
page_size: pageSize,
|
||||
per_page: pageSize,
|
||||
where,
|
||||
...(orderObj ? { order: orderObj } : {}),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -47,6 +47,133 @@ const region = {
|
|||
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<{
|
||||
id: number;
|
||||
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<{
|
||||
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||||
selectedRowKeys: React.Key[];
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ export async function categorycontrollerGetlist(
|
|||
|
||||
/** 此处后端没有提供注释 POST /category/ */
|
||||
export async function categorycontrollerCreate(
|
||||
body: Record<string, any>,
|
||||
body: API.CreateCategoryDTO,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<any>('/category/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
|
|
@ -40,14 +40,14 @@ export async function categorycontrollerCreate(
|
|||
export async function categorycontrollerUpdate(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.categorycontrollerUpdateParams,
|
||||
body: Record<string, any>,
|
||||
body: API.UpdateCategoryDTO,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { id: param0, ...queryParams } = params;
|
||||
return request<any>(`/category/${param0}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: { ...queryParams },
|
||||
data: body,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,68 @@
|
|||
/* eslint-disable */
|
||||
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 */
|
||||
export async function customercontrollerAddtag(
|
||||
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 */
|
||||
export async function customercontrollerDeltag(
|
||||
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 */
|
||||
export async function customercontrollerGettags(options?: {
|
||||
[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 */
|
||||
export async function customercontrollerSetrate(
|
||||
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 */
|
||||
export async function customercontrollerSynccustomers(
|
||||
body: Record<string, any>,
|
||||
body: API.SyncCustomersDTO,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<Record<string, any>>('/customer/sync', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(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 */
|
||||
export async function dictcontrollerImportdictitems(
|
||||
body: Record<string, any>,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<any>('/dict/item/import', {
|
||||
return request<API.ApiResponse>('/dict/item/import', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
|
|
|
|||
|
|
@ -225,15 +225,15 @@ export async function ordercontrollerChangestatus(
|
|||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /order/syncOrder/${param0} */
|
||||
export async function ordercontrollerSyncorder(
|
||||
/** 此处后端没有提供注释 POST /order/sync/${param0} */
|
||||
export async function ordercontrollerSyncorders(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.ordercontrollerSyncorderParams,
|
||||
params: API.ordercontrollerSyncordersParams,
|
||||
body: Record<string, any>,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { siteId: param0, ...queryParams } = params;
|
||||
return request<API.BooleanRes>(`/order/syncOrder/${param0}`, {
|
||||
return request<API.BooleanRes>(`/order/sync/${param0}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'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 */
|
||||
export async function productcontrollerBatchupdateproduct(
|
||||
body: API.BatchUpdateProductDTO,
|
||||
|
|
@ -333,13 +363,13 @@ export async function productcontrollerGetcategoriesall(options?: {
|
|||
|
||||
/** 此处后端没有提供注释 POST /product/category */
|
||||
export async function productcontrollerCreatecategory(
|
||||
body: Record<string, any>,
|
||||
body: API.CreateCategoryDTO,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<any>('/product/category', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
|
|
@ -350,14 +380,14 @@ export async function productcontrollerCreatecategory(
|
|||
export async function productcontrollerUpdatecategory(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.productcontrollerUpdatecategoryParams,
|
||||
body: Record<string, any>,
|
||||
body: API.UpdateCategoryDTO,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { id: param0, ...queryParams } = params;
|
||||
return request<any>(`/product/category/${param0}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: { ...queryParams },
|
||||
data: body,
|
||||
|
|
@ -558,6 +588,10 @@ export async function productcontrollerGetproductlist(
|
|||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
where: undefined,
|
||||
...params['where'],
|
||||
orderBy: undefined,
|
||||
...params['orderBy'],
|
||||
},
|
||||
...(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 */
|
||||
export async function productcontrollerSyncstocktoproduct(options?: {
|
||||
[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} */
|
||||
export async function productcontrollerUpdatenamecn(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
|
|
|||
|
|
@ -280,23 +280,26 @@ export async function siteapicontrollerBatchorders(
|
|||
);
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-ship */
|
||||
export async function siteapicontrollerBatchshiporders(
|
||||
/** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-fulfill */
|
||||
export async function siteapicontrollerBatchfulfillorders(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.siteapicontrollerBatchshipordersParams,
|
||||
body: API.BatchShipOrdersDTO,
|
||||
params: API.siteapicontrollerBatchfulfillordersParams,
|
||||
body: API.BatchFulfillmentsDTO,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { siteId: param0, ...queryParams } = params;
|
||||
return request<Record<string, any>>(`/site-api/${param0}/orders/batch-ship`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
return request<Record<string, any>>(
|
||||
`/site-api/${param0}/orders/batch-fulfill`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: { ...queryParams },
|
||||
data: body,
|
||||
...(options || {}),
|
||||
},
|
||||
params: { ...queryParams },
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 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 */
|
||||
export async function siteapicontrollerExportproducts(
|
||||
// 叠加生成的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 */
|
||||
export async function siteapicontrollerGetreviews(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
|
@ -766,16 +810,60 @@ export async function siteapicontrollerDeleteorder(
|
|||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-ship */
|
||||
export async function siteapicontrollerCancelshiporder(
|
||||
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-fulfill */
|
||||
export async function siteapicontrollerCancelfulfillment(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.siteapicontrollerCancelshiporderParams,
|
||||
body: API.CancelShipOrderDTO,
|
||||
params: API.siteapicontrollerCancelfulfillmentParams,
|
||||
body: API.CancelFulfillmentDTO,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||
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',
|
||||
headers: {
|
||||
|
|
@ -827,23 +915,18 @@ export async function siteapicontrollerCreateordernote(
|
|||
);
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/ship */
|
||||
export async function siteapicontrollerShiporder(
|
||||
/** 此处后端没有提供注释 GET /site-api/${param1}/orders/${param0}/trackings */
|
||||
export async function siteapicontrollerGetordertrackings(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.siteapicontrollerShiporderParams,
|
||||
body: API.ShipOrderDTO,
|
||||
params: API.siteapicontrollerGetordertrackingsParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||
const { orderId: param0, siteId: param1, ...queryParams } = params;
|
||||
return request<Record<string, any>>(
|
||||
`/site-api/${param1}/orders/${param0}/ship`,
|
||||
`/site-api/${param1}/orders/${param0}/trackings`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
params: { ...queryParams },
|
||||
data: body,
|
||||
...(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} */
|
||||
export async function siteapicontrollerUpdatevariation(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,17 @@ declare namespace API {
|
|||
postal_code?: string;
|
||||
};
|
||||
|
||||
type ApiResponse = {
|
||||
/** 状态码 */
|
||||
code?: number;
|
||||
/** 是否成功 */
|
||||
success?: boolean;
|
||||
/** 提示信息 */
|
||||
message?: string;
|
||||
/** 返回数据 */
|
||||
data?: Record<string, any>;
|
||||
};
|
||||
|
||||
type Area = {
|
||||
/** 名称 */
|
||||
name?: string;
|
||||
|
|
@ -35,6 +46,16 @@ declare namespace API {
|
|||
id: number;
|
||||
};
|
||||
|
||||
type BatchCreateCustomerDTO = {
|
||||
/** 客户列表 */
|
||||
customers?: CreateCustomerDTO[];
|
||||
};
|
||||
|
||||
type BatchDeleteCustomerDTO = {
|
||||
/** 客户ID列表 */
|
||||
ids?: number[];
|
||||
};
|
||||
|
||||
type BatchDeleteProductDTO = {
|
||||
/** 产品ID列表 */
|
||||
ids: any[];
|
||||
|
|
@ -47,6 +68,24 @@ declare namespace API {
|
|||
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 = {
|
||||
/** 要创建的数据列表 */
|
||||
create?: any[];
|
||||
|
|
@ -73,22 +112,16 @@ declare namespace API {
|
|||
errors?: BatchErrorItemDTO[];
|
||||
};
|
||||
|
||||
type BatchShipOrderItemDTO = {
|
||||
/** 订单ID */
|
||||
order_id?: string;
|
||||
/** 物流单号 */
|
||||
tracking_number?: string;
|
||||
/** 物流公司 */
|
||||
shipping_provider?: string;
|
||||
/** 发货方式 */
|
||||
shipping_method?: string;
|
||||
/** 发货商品项 */
|
||||
items?: ShipOrderItemDTO[];
|
||||
type BatchSyncProductToSiteDTO = {
|
||||
/** 站点ID */
|
||||
siteId?: number;
|
||||
/** 产品站点SKU列表 */
|
||||
data?: ProductSiteSkuDTO[];
|
||||
};
|
||||
|
||||
type BatchShipOrdersDTO = {
|
||||
/** 批量发货订单列表 */
|
||||
orders?: BatchShipOrderItemDTO[];
|
||||
type BatchUpdateCustomerDTO = {
|
||||
/** 客户更新列表 */
|
||||
customers?: UpdateCustomerItemDTO[];
|
||||
};
|
||||
|
||||
type BatchUpdateProductDTO = {
|
||||
|
|
@ -129,7 +162,7 @@ declare namespace API {
|
|||
data?: boolean;
|
||||
};
|
||||
|
||||
type CancelShipOrderDTO = {
|
||||
type CancelFulfillmentDTO = {
|
||||
/** 取消原因 */
|
||||
reason?: string;
|
||||
/** 发货单ID */
|
||||
|
|
@ -163,6 +196,54 @@ declare namespace API {
|
|||
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 CreateDictItemDTO = {};
|
||||
|
|
@ -271,32 +352,22 @@ declare namespace API {
|
|||
unit?: string;
|
||||
};
|
||||
|
||||
type customercontrollerGetcustomerlistParams = {
|
||||
current?: string;
|
||||
pageSize?: string;
|
||||
email?: string;
|
||||
tags?: string;
|
||||
sorterKey?: string;
|
||||
sorterValue?: string;
|
||||
state?: string;
|
||||
first_purchase_date?: string;
|
||||
customerId?: number;
|
||||
type customercontrollerDeletecustomerParams = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
type customercontrollerGetcustomerstatisticlistParams = {
|
||||
current?: string;
|
||||
pageSize?: string;
|
||||
email?: string;
|
||||
tags?: string;
|
||||
sorterKey?: string;
|
||||
sorterValue?: string;
|
||||
state?: string;
|
||||
first_purchase_date?: string;
|
||||
customerId?: number;
|
||||
type customercontrollerGetcustomerbyidParams = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
type customercontrollerUpdatecustomerParams = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
type CustomerTagDTO = {
|
||||
/** 客户邮箱 */
|
||||
email?: string;
|
||||
/** 标签名称 */
|
||||
tag?: string;
|
||||
};
|
||||
|
||||
|
|
@ -324,6 +395,10 @@ declare namespace API {
|
|||
id: number;
|
||||
};
|
||||
|
||||
type dictcontrollerExportdictitemsParams = {
|
||||
dictId?: number;
|
||||
};
|
||||
|
||||
type dictcontrollerGetdictitemsbydictnameParams = {
|
||||
name?: string;
|
||||
};
|
||||
|
|
@ -353,6 +428,65 @@ declare namespace API {
|
|||
|
||||
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 = {
|
||||
lang: string;
|
||||
};
|
||||
|
|
@ -620,7 +754,7 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type ordercontrollerSyncorderParams = {
|
||||
type ordercontrollerSyncordersParams = {
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -854,7 +988,6 @@ declare namespace API {
|
|||
purchaseType?: 'all' | 'first_purchase' | 'repeat_purchase';
|
||||
orderType?: 'all' | 'cpc' | 'non_cpc';
|
||||
brand?: 'all' | 'zyn' | 'yoone' | 'zolt';
|
||||
grouping?: 'day' | 'week' | 'month';
|
||||
};
|
||||
|
||||
type OrderStatusCountDTO = {
|
||||
|
|
@ -898,7 +1031,7 @@ declare namespace API {
|
|||
/** 库存组成 */
|
||||
components?: ProductStockComponent[];
|
||||
/** 站点 SKU 列表 */
|
||||
siteSkus?: ProductSiteSku[];
|
||||
siteSkus?: string[];
|
||||
/** 来源 */
|
||||
source?: number;
|
||||
/** 创建时间 */
|
||||
|
|
@ -1019,20 +1152,16 @@ declare namespace API {
|
|||
};
|
||||
|
||||
type productcontrollerGetproductlistParams = {
|
||||
/** 当前页 */
|
||||
current?: number;
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
/** 每页数量 */
|
||||
pageSize?: number;
|
||||
/** 搜索关键字 */
|
||||
name?: string;
|
||||
/** 分类ID */
|
||||
categoryId?: number;
|
||||
/** 品牌ID */
|
||||
brandId?: number;
|
||||
/** 排序字段 */
|
||||
sortField?: string;
|
||||
/** 排序方式 */
|
||||
sortOrder?: string;
|
||||
per_page?: number;
|
||||
/** 搜索关键词 */
|
||||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
};
|
||||
|
||||
type productcontrollerGetproductsiteskusParams = {
|
||||
|
|
@ -1098,8 +1227,10 @@ declare namespace API {
|
|||
data?: Product;
|
||||
};
|
||||
|
||||
type ProductSiteSku = {
|
||||
/** 站点 SKU */
|
||||
type ProductSiteSkuDTO = {
|
||||
/** 产品ID */
|
||||
productId?: number;
|
||||
/** 站点SKU */
|
||||
siteSku?: string;
|
||||
};
|
||||
|
||||
|
|
@ -1182,18 +1313,6 @@ declare namespace API {
|
|||
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 = {
|
||||
/** 页码 */
|
||||
current?: number;
|
||||
|
|
@ -1257,23 +1376,6 @@ declare namespace API {
|
|||
pageSize?: number;
|
||||
};
|
||||
|
||||
type QueryProductDTO = {
|
||||
/** 当前页 */
|
||||
current?: number;
|
||||
/** 每页数量 */
|
||||
pageSize?: number;
|
||||
/** 搜索关键字 */
|
||||
name?: string;
|
||||
/** 分类ID */
|
||||
categoryId?: number;
|
||||
/** 品牌ID */
|
||||
brandId?: number;
|
||||
/** 排序字段 */
|
||||
sortField?: string;
|
||||
/** 排序方式 */
|
||||
sortOrder?: string;
|
||||
};
|
||||
|
||||
type QueryPurchaseOrderDTO = {
|
||||
/** 页码 */
|
||||
current?: number;
|
||||
|
|
@ -1424,24 +1526,6 @@ declare namespace API {
|
|||
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 = {
|
||||
id?: number;
|
||||
name?: string;
|
||||
|
|
@ -1481,6 +1565,10 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerBatchfulfillordersParams = {
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerBatchmediaParams = {
|
||||
siteId: number;
|
||||
};
|
||||
|
|
@ -1493,11 +1581,11 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerBatchshipordersParams = {
|
||||
type siteapicontrollerBatchupsertproductParams = {
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerCancelshiporderParams = {
|
||||
type siteapicontrollerCancelfulfillmentParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
|
@ -1514,6 +1602,11 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerCreateorderfulfillmentParams = {
|
||||
orderId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerCreateordernoteParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
|
|
@ -1545,6 +1638,12 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerDeleteorderfulfillmentParams = {
|
||||
fulfillmentId: string;
|
||||
orderId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerDeleteorderParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
|
|
@ -1574,10 +1673,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1592,10 +1687,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1610,10 +1701,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1628,10 +1715,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1646,10 +1729,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1664,15 +1743,16 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerFulfillorderParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerGetcustomerordersParams = {
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
|
|
@ -1682,10 +1762,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
customerId: number;
|
||||
|
|
@ -1706,10 +1782,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1728,10 +1800,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1756,15 +1824,16 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerGetordertrackingsParams = {
|
||||
orderId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerGetproductParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
|
|
@ -1779,10 +1848,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1797,10 +1862,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1815,10 +1876,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1838,10 +1895,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
siteId: number;
|
||||
|
|
@ -1863,11 +1916,6 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerShiporderParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerUpdatecustomerParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
|
|
@ -1878,6 +1926,12 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerUpdateorderfulfillmentParams = {
|
||||
fulfillmentId: string;
|
||||
orderId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerUpdateorderParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
|
|
@ -1904,6 +1958,10 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerUpsertproductParams = {
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type SiteConfig = {
|
||||
/** 站点 ID */
|
||||
id?: string;
|
||||
|
|
@ -2235,6 +2293,50 @@ declare namespace API {
|
|||
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 = {
|
||||
id?: number;
|
||||
name?: string;
|
||||
|
|
@ -2296,8 +2398,6 @@ declare namespace API {
|
|||
email?: string;
|
||||
/** 电话 */
|
||||
phone?: string;
|
||||
/** 配送方式 */
|
||||
method_title?: string;
|
||||
};
|
||||
|
||||
type UnifiedCategoryDTO = {
|
||||
|
|
@ -2364,10 +2464,6 @@ declare namespace API {
|
|||
per_page?: number;
|
||||
/** 总页数 */
|
||||
totalPages?: number;
|
||||
/** 分页后的数据 */
|
||||
after?: string;
|
||||
/** 分页前的数据 */
|
||||
before?: string;
|
||||
};
|
||||
|
||||
type UnifiedFeeLineDTO = {
|
||||
|
|
@ -2428,10 +2524,6 @@ declare namespace API {
|
|||
per_page?: number;
|
||||
/** 总页数 */
|
||||
totalPages?: number;
|
||||
/** 分页后的数据 */
|
||||
after?: string;
|
||||
/** 分页前的数据 */
|
||||
before?: string;
|
||||
};
|
||||
|
||||
type UnifiedOrderDTO = {
|
||||
|
|
@ -2443,8 +2535,6 @@ declare namespace API {
|
|||
status?: string;
|
||||
/** 货币 */
|
||||
currency?: string;
|
||||
/** 货币符号 */
|
||||
currency_symbol?: string;
|
||||
/** 总金额 */
|
||||
total?: string;
|
||||
/** 客户ID */
|
||||
|
|
@ -2453,8 +2543,6 @@ declare namespace API {
|
|||
customer_name?: string;
|
||||
/** 客户邮箱 */
|
||||
email?: string;
|
||||
/** 客户邮箱 */
|
||||
customer_email?: string;
|
||||
/** 订单项(具体的商品) */
|
||||
line_items?: UnifiedOrderLineItemDTO[];
|
||||
/** 销售项(兼容前端) */
|
||||
|
|
@ -2469,6 +2557,8 @@ declare namespace API {
|
|||
shipping_full_address?: string;
|
||||
/** 支付方式 */
|
||||
payment_method?: string;
|
||||
/** 退款列表 */
|
||||
refunds?: UnifiedOrderRefundDTO[];
|
||||
/** 创建时间 */
|
||||
date_created?: string;
|
||||
/** 更新时间 */
|
||||
|
|
@ -2481,16 +2571,8 @@ declare namespace API {
|
|||
fee_lines?: UnifiedFeeLineDTO[];
|
||||
/** 优惠券项 */
|
||||
coupon_lines?: UnifiedCouponLineDTO[];
|
||||
/** 支付时间 */
|
||||
date_paid?: string;
|
||||
/** 客户IP地址 */
|
||||
customer_ip_address?: string;
|
||||
/** UTM来源 */
|
||||
utm_source?: string;
|
||||
/** 设备类型 */
|
||||
device_type?: string;
|
||||
/** 来源类型 */
|
||||
source_type?: string;
|
||||
/** 物流追踪信息 */
|
||||
tracking?: UnifiedOrderTrackingDTO[];
|
||||
};
|
||||
|
||||
type UnifiedOrderLineItemDTO = {
|
||||
|
|
@ -2521,10 +2603,28 @@ declare namespace API {
|
|||
per_page?: number;
|
||||
/** 总页数 */
|
||||
totalPages?: number;
|
||||
/** 分页后的数据 */
|
||||
after?: string;
|
||||
/** 分页前的数据 */
|
||||
before?: string;
|
||||
};
|
||||
|
||||
type UnifiedOrderRefundDTO = {
|
||||
/** 退款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 = {
|
||||
|
|
@ -2538,10 +2638,6 @@ declare namespace API {
|
|||
per_page?: number;
|
||||
/** 总页数 */
|
||||
totalPages?: number;
|
||||
/** 分页后的数据 */
|
||||
after?: string;
|
||||
/** 分页前的数据 */
|
||||
before?: string;
|
||||
};
|
||||
|
||||
type UnifiedProductAttributeDTO = {
|
||||
|
|
@ -2557,6 +2653,8 @@ declare namespace API {
|
|||
variation?: boolean;
|
||||
/** 属性选项 */
|
||||
options?: string[];
|
||||
/** 变体属性值(单个值) */
|
||||
option?: string;
|
||||
};
|
||||
|
||||
type UnifiedProductDTO = {
|
||||
|
|
@ -2613,15 +2711,13 @@ declare namespace API {
|
|||
per_page?: number;
|
||||
/** 总页数 */
|
||||
totalPages?: number;
|
||||
/** 分页后的数据 */
|
||||
after?: string;
|
||||
/** 分页前的数据 */
|
||||
before?: string;
|
||||
};
|
||||
|
||||
type UnifiedProductVariationDTO = {
|
||||
/** 变体ID */
|
||||
id?: Record<string, any>;
|
||||
/** 变体名称 */
|
||||
name?: string;
|
||||
/** 变体SKU */
|
||||
sku?: string;
|
||||
/** 常规价格 */
|
||||
|
|
@ -2634,8 +2730,34 @@ declare namespace API {
|
|||
stock_status?: string;
|
||||
/** 库存数量 */
|
||||
stock_quantity?: number;
|
||||
/** 变体属性 */
|
||||
attributes?: UnifiedProductAttributeDTO[];
|
||||
/** 变体图片 */
|
||||
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 = {
|
||||
|
|
@ -2672,10 +2794,6 @@ declare namespace API {
|
|||
per_page?: number;
|
||||
/** 总页数 */
|
||||
totalPages?: number;
|
||||
/** 分页后的数据 */
|
||||
after?: string;
|
||||
/** 分页前的数据 */
|
||||
before?: string;
|
||||
};
|
||||
|
||||
type UnifiedSearchParamsDTO = {
|
||||
|
|
@ -2687,10 +2805,6 @@ declare namespace API {
|
|||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: Record<string, any>;
|
||||
/** 创建时间后 */
|
||||
after?: string;
|
||||
/** 创建时间前 */
|
||||
before?: string;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: Record<string, any>;
|
||||
};
|
||||
|
|
@ -2748,10 +2862,6 @@ declare namespace API {
|
|||
per_page?: number;
|
||||
/** 总页数 */
|
||||
totalPages?: number;
|
||||
/** 分页后的数据 */
|
||||
after?: string;
|
||||
/** 分页前的数据 */
|
||||
before?: string;
|
||||
};
|
||||
|
||||
type UnifiedTagDTO = {
|
||||
|
|
@ -2789,6 +2899,57 @@ declare namespace API {
|
|||
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 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