forked from yoone/WEB
Compare commits
No commits in common. "d372f72935a5d0a9b7343337c069adaa6967eb98" and "32e275915167a96fb2e5c1078748ead227355239" have entirely different histories.
d372f72935
...
32e2759151
|
|
@ -170,11 +170,6 @@ export default defineConfig({
|
||||||
path: '/product/sync',
|
path: '/product/sync',
|
||||||
component: './Product/Sync',
|
component: './Product/Sync',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: '产品CSV 工具',
|
|
||||||
path: '/product/csvtool',
|
|
||||||
component: './Product/CsvTool',
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
32
Dockerfile
32
Dockerfile
|
|
@ -1,32 +0,0 @@
|
||||||
# 构建阶段
|
|
||||||
FROM node:18-alpine as builder
|
|
||||||
|
|
||||||
# 设置工作目录
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# 复制 package.json 和 package-lock.json
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
# 安装依赖(使用 --legacy-peer-deps 解决依赖冲突)
|
|
||||||
RUN npm install --legacy-peer-deps
|
|
||||||
|
|
||||||
# 复制源代码
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# 构建项目
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# 生产阶段
|
|
||||||
FROM nginx:alpine
|
|
||||||
|
|
||||||
# 复制构建产物到 Nginx 静态目录
|
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
|
||||||
|
|
||||||
# 复制自定义 Nginx 配置
|
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
# 暴露端口
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
# 启动 Nginx
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
33
nginx.conf
33
nginx.conf
|
|
@ -1,33 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
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,92 +0,0 @@
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { sitecontrollerAll } from '@/servers/api/site';
|
|
||||||
|
|
||||||
// 站点数据的类型定义
|
|
||||||
interface Site {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义 Hook:管理站点数据
|
|
||||||
const useSites = () => {
|
|
||||||
// 添加站点数据状态
|
|
||||||
const [sites, setSites] = useState<Site[]>([]);
|
|
||||||
|
|
||||||
// 添加加载状态
|
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
|
||||||
|
|
||||||
// 添加错误状态
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// 获取站点数据
|
|
||||||
const fetchSites = async () => {
|
|
||||||
// 设置加载状态为 true
|
|
||||||
setLoading(true);
|
|
||||||
// 清空之前的错误信息
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
// 调用 API 获取所有站点数据
|
|
||||||
const { data, success } = await sitecontrollerAll();
|
|
||||||
// 判断请求是否成功
|
|
||||||
if (success) {
|
|
||||||
// 将站点数据保存到状态中
|
|
||||||
setSites(data || []);
|
|
||||||
} else {
|
|
||||||
// 如果请求失败,设置错误信息
|
|
||||||
setError('获取站点数据失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 捕获异常并打印错误日志
|
|
||||||
console.error('获取站点数据失败:', error);
|
|
||||||
// 设置错误信息
|
|
||||||
setError('获取站点数据时发生错误');
|
|
||||||
} finally {
|
|
||||||
// 无论成功与否,都将加载状态设置为 false
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据站点ID获取站点名称
|
|
||||||
const getSiteName = (siteId: number | undefined | null) => {
|
|
||||||
// 如果站点ID不存在,返回默认值
|
|
||||||
if (!siteId) return '-';
|
|
||||||
// 如果站点ID是字符串类型,直接返回
|
|
||||||
if (typeof siteId === 'string') {
|
|
||||||
return siteId;
|
|
||||||
}
|
|
||||||
// 在站点列表中查找对应的站点
|
|
||||||
const site = sites.find((s) => s.id === siteId);
|
|
||||||
// 如果找到站点,返回站点名称;否则返回站点ID的字符串形式
|
|
||||||
return site ? site.name : String(siteId);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据站点ID获取站点对象
|
|
||||||
const getSiteById = (siteId: number | undefined | null) => {
|
|
||||||
// 如果站点ID不存在,返回 null
|
|
||||||
if (!siteId) return null;
|
|
||||||
// 在站点列表中查找对应的站点
|
|
||||||
const site = sites.find((s) => s.id === siteId);
|
|
||||||
// 返回找到的站点对象,如果找不到则返回 null
|
|
||||||
return site || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 组件加载时获取站点数据
|
|
||||||
useEffect(() => {
|
|
||||||
// 调用获取站点数据的函数
|
|
||||||
fetchSites();
|
|
||||||
}, []); // 空依赖数组表示只在组件挂载时执行一次
|
|
||||||
|
|
||||||
// 返回站点数据和相关方法
|
|
||||||
return {
|
|
||||||
sites, // 站点数据列表
|
|
||||||
loading, // 加载状态
|
|
||||||
error, // 错误信息
|
|
||||||
fetchSites, // 重新获取站点数据的方法
|
|
||||||
getSiteName, // 根据ID获取站点名称的方法
|
|
||||||
getSiteById, // 根据ID获取站点对象的方法
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 导出 useSites Hook
|
|
||||||
export default useSites;
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { ordercontrollerGetorders } from '@/servers/api/order';
|
import { ordercontrollerGetorders } from '@/servers/api/order';
|
||||||
|
import { siteapicontrollerGetorders } from '@/servers/api/siteApi';
|
||||||
import {
|
import {
|
||||||
App,
|
App,
|
||||||
Col,
|
Col,
|
||||||
|
|
@ -60,7 +61,7 @@ const HistoryOrders: React.FC<HistoryOrdersProps> = ({ customer, siteId }) => {
|
||||||
const items = order.line_items || order.items || [];
|
const items = order.line_items || order.items || [];
|
||||||
if (Array.isArray(items)) {
|
if (Array.isArray(items)) {
|
||||||
items.forEach((item: any) => {
|
items.forEach((item: any) => {
|
||||||
// 检查商品名称或SKU是否包含yoone(不区分大小写)
|
// 检查商品名称或SKU是否包含yoone(不区分大小写)
|
||||||
const itemName = (item.name || '').toLowerCase();
|
const itemName = (item.name || '').toLowerCase();
|
||||||
const sku = (item.sku || '').toLowerCase();
|
const sku = (item.sku || '').toLowerCase();
|
||||||
|
|
||||||
|
|
@ -88,12 +89,12 @@ const HistoryOrders: React.FC<HistoryOrdersProps> = ({ customer, siteId }) => {
|
||||||
|
|
||||||
// 获取客户订单数据
|
// 获取客户订单数据
|
||||||
const fetchOrders = async () => {
|
const fetchOrders = async () => {
|
||||||
|
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await ordercontrollerGetorders({
|
const response = await ordercontrollerGetorders({
|
||||||
where: {
|
customer_email: customer.email,
|
||||||
customer_email: customer.email,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
|
|
@ -241,7 +242,7 @@ const HistoryOrders: React.FC<HistoryOrdersProps> = ({ customer, siteId }) => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
}}
|
}}
|
||||||
scroll={{ x: 800 }}
|
scroll={{ x: 800 }}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,10 @@ import {
|
||||||
ModalForm,
|
ModalForm,
|
||||||
PageContainer,
|
PageContainer,
|
||||||
ProColumns,
|
ProColumns,
|
||||||
ProFormDateTimeRangePicker,
|
|
||||||
ProFormSelect,
|
ProFormSelect,
|
||||||
ProFormText,
|
|
||||||
ProTable,
|
ProTable,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { App, Avatar, Button, Form, Rate, Space, Tag, Tooltip } from 'antd';
|
import { App, Avatar, Button, Rate, Space, Tag, Tooltip } from 'antd';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import HistoryOrders from './HistoryOrders';
|
import HistoryOrders from './HistoryOrders';
|
||||||
|
|
||||||
|
|
@ -115,17 +113,42 @@ const CustomerList: React.FC = () => {
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
||||||
|
const [syncLoading, setSyncLoading] = useState(false);
|
||||||
|
const [sites, setSites] = useState<any[]>([]); // 添加站点数据状态
|
||||||
|
|
||||||
|
// 获取站点数据
|
||||||
|
const fetchSites = async () => {
|
||||||
|
try {
|
||||||
|
const { data, success } = await sitecontrollerAll();
|
||||||
|
if (success) {
|
||||||
|
setSites(data || []);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取站点数据失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const columns: ProColumns<API.GetCustomerDTO>[] = [
|
// 根据站点ID获取站点名称
|
||||||
|
const getSiteName = (siteId: number | undefined | null) => {
|
||||||
|
if (!siteId) return '-';
|
||||||
|
if (typeof siteId === 'string') {
|
||||||
|
return siteId;
|
||||||
|
}
|
||||||
|
const site = sites.find((s) => s.id === siteId);
|
||||||
|
console.log(`site`, site);
|
||||||
|
return site ? site.name : String(siteId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件加载时获取站点数据
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSites();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const columns: ProColumns<API.UnifiedCustomerDTO>[] = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
},
|
hideInSearch: true,
|
||||||
{
|
|
||||||
title: '原始 ID',
|
|
||||||
dataIndex: 'origin_id',
|
|
||||||
sorter: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '站点',
|
title: '站点',
|
||||||
|
|
@ -146,6 +169,9 @@ const CustomerList: React.FC = () => {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
render: (siteId: any) => {
|
||||||
|
return <span>{getSiteName(siteId) || '-'}</span>;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '头像',
|
title: '头像',
|
||||||
|
|
@ -178,25 +204,23 @@ const CustomerList: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: '用户名',
|
title: '用户名',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
copyable: true,
|
hideInSearch: true,
|
||||||
sorter: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '邮箱',
|
title: '邮箱',
|
||||||
dataIndex: 'email',
|
dataIndex: 'email',
|
||||||
copyable: true,
|
copyable: true,
|
||||||
sorter: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '电话',
|
title: '电话',
|
||||||
dataIndex: 'phone',
|
dataIndex: 'phone',
|
||||||
|
hideInSearch: true,
|
||||||
copyable: true,
|
copyable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '账单地址',
|
title: '账单地址',
|
||||||
dataIndex: 'billing',
|
dataIndex: 'billing',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
|
|
||||||
width: 200,
|
width: 200,
|
||||||
render: (billing) => <AddressCell address={billing} title="账单地址" />,
|
render: (billing) => <AddressCell address={billing} title="账单地址" />,
|
||||||
},
|
},
|
||||||
|
|
@ -210,8 +234,8 @@ const CustomerList: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: '评分',
|
title: '评分',
|
||||||
dataIndex: 'rate',
|
dataIndex: 'rate',
|
||||||
|
width: 120,
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
sorter: true,
|
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return (
|
return (
|
||||||
<Rate
|
<Rate
|
||||||
|
|
@ -230,7 +254,7 @@ const CustomerList: React.FC = () => {
|
||||||
message.error(e?.message || '设置评分失败');
|
message.error(e?.message || '设置评分失败');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
value={record.rate || 0}
|
value={record.raw?.rate || 0}
|
||||||
allowHalf
|
allowHalf
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
@ -241,7 +265,7 @@ const CustomerList: React.FC = () => {
|
||||||
dataIndex: 'tags',
|
dataIndex: 'tags',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const tags = record?.tags || [];
|
const tags = record.raw?.tags || [];
|
||||||
return (
|
return (
|
||||||
<Space size={[0, 8]} wrap>
|
<Space size={[0, 8]} wrap>
|
||||||
{tags.map((tag: string) => {
|
{tags.map((tag: string) => {
|
||||||
|
|
@ -303,7 +327,7 @@ const CustomerList: React.FC = () => {
|
||||||
tableRef={actionRef}
|
tableRef={actionRef}
|
||||||
/>
|
/>
|
||||||
{/* 订单 */}
|
{/* 订单 */}
|
||||||
<HistoryOrders customer={record} siteId={record.site_id} />
|
<HistoryOrders customer={record} siteId={record.raw?.site_id} />
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -316,35 +340,25 @@ const CustomerList: React.FC = () => {
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
headerTitle="查询表格"
|
headerTitle="查询表格"
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
columns={columns}
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
request={async (params, sorter,filter) => {
|
request={async (params, sorter) => {
|
||||||
console.log('custoemr request',params, sorter,filter)
|
const key = Object.keys(sorter)[0];
|
||||||
const { current, pageSize, ...restParams } = params;
|
const { data, success } = await customercontrollerGetcustomerlist({
|
||||||
const orderBy:any = {}
|
...params,
|
||||||
Object.entries(sorter).forEach(([key, value]) => {
|
current: params.current?.toString(),
|
||||||
orderBy[key] = value === 'ascend' ? 'asc' : 'desc';
|
pageSize: params.pageSize?.toString(),
|
||||||
})
|
...(key
|
||||||
// 构建查询参数
|
? { sorterKey: key, sorterValue: sorter[key] as string }
|
||||||
const queryParams: any = {
|
: {}),
|
||||||
page: current || 1,
|
});
|
||||||
per_page: pageSize || 20,
|
|
||||||
where: {
|
|
||||||
...filter,
|
|
||||||
...restParams
|
|
||||||
},
|
|
||||||
orderBy
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await customercontrollerGetcustomerlist({params: queryParams});
|
|
||||||
console.log(queryParams, result);
|
|
||||||
return {
|
return {
|
||||||
total: result?.data?.total || 0,
|
total: data?.total || 0,
|
||||||
data: result?.data?.items || [],
|
data: data?.items || [],
|
||||||
success: true,
|
success,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
|
columns={columns}
|
||||||
search={{
|
search={{
|
||||||
labelWidth: 'auto',
|
labelWidth: 'auto',
|
||||||
span: 6,
|
span: 6,
|
||||||
|
|
@ -352,7 +366,8 @@ const CustomerList: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
showTotal: (total, range) =>
|
||||||
|
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
|
||||||
}}
|
}}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -454,7 +469,6 @@ const SyncCustomersModal: React.FC<{
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const [sites, setSites] = useState<any[]>([]);
|
const [sites, setSites] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [form] = Form.useForm(); // 添加表单实例
|
|
||||||
|
|
||||||
// 获取站点列表
|
// 获取站点列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -473,56 +487,15 @@ const SyncCustomersModal: React.FC<{
|
||||||
}
|
}
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
// 定义同步参数类型
|
const handleSync = async (values: { siteId: number }) => {
|
||||||
type SyncParams = {
|
|
||||||
siteId: number;
|
|
||||||
search?: string;
|
|
||||||
role?: string;
|
|
||||||
dateRange?: [string, string];
|
|
||||||
orderBy?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSync = async (values: SyncParams) => {
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// 构建过滤参数
|
|
||||||
const params: any = {};
|
|
||||||
|
|
||||||
// 添加搜索关键词
|
|
||||||
if (values.search) {
|
|
||||||
params.search = values.search;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加角色过滤
|
|
||||||
if (values.role) {
|
|
||||||
params.where = {
|
|
||||||
...params.where,
|
|
||||||
role: values.role,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加日期范围过滤(使用 after 和 before 参数)
|
|
||||||
if (values.dateRange && values.dateRange[0] && values.dateRange[1]) {
|
|
||||||
params.where = {
|
|
||||||
...params.where,
|
|
||||||
after: values.dateRange[0],
|
|
||||||
before: values.dateRange[1],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加排序
|
|
||||||
if (values.orderBy) {
|
|
||||||
params.orderBy = values.orderBy;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
success,
|
success,
|
||||||
message: msg,
|
message: msg,
|
||||||
data,
|
data,
|
||||||
} = await customercontrollerSynccustomers({
|
} = await customercontrollerSynccustomers({
|
||||||
siteId: values.siteId,
|
siteId: values.siteId,
|
||||||
params: Object.keys(params).length > 0 ? params : undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|
@ -536,7 +509,7 @@ const SyncCustomersModal: React.FC<{
|
||||||
errors = [],
|
errors = [],
|
||||||
} = result;
|
} = result;
|
||||||
|
|
||||||
let resultMessage = `同步完成!共处理 ${total} 个客户:`;
|
let resultMessage = `同步完成!共处理 ${total} 个客户:`;
|
||||||
if (created > 0) resultMessage += ` 新建 ${created} 个`;
|
if (created > 0) resultMessage += ` 新建 ${created} 个`;
|
||||||
if (updated > 0) resultMessage += ` 更新 ${updated} 个`;
|
if (updated > 0) resultMessage += ` 更新 ${updated} 个`;
|
||||||
if (synced > 0) resultMessage += ` 同步成功 ${synced} 个`;
|
if (synced > 0) resultMessage += ` 同步成功 ${synced} 个`;
|
||||||
|
|
@ -549,7 +522,7 @@ const SyncCustomersModal: React.FC<{
|
||||||
<div>
|
<div>
|
||||||
<div>{resultMessage}</div>
|
<div>{resultMessage}</div>
|
||||||
<div style={{ marginTop: 8, fontSize: 12, color: '#faad14' }}>
|
<div style={{ marginTop: 8, fontSize: 12, color: '#faad14' }}>
|
||||||
失败详情:
|
失败详情:
|
||||||
{errors
|
{errors
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
.map((err: any) => err.email || err.error)
|
.map((err: any) => err.email || err.error)
|
||||||
|
|
@ -596,7 +569,6 @@ const SyncCustomersModal: React.FC<{
|
||||||
confirmLoading: loading,
|
confirmLoading: loading,
|
||||||
}}
|
}}
|
||||||
onFinish={handleSync}
|
onFinish={handleSync}
|
||||||
form={form}
|
|
||||||
>
|
>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="siteId"
|
name="siteId"
|
||||||
|
|
@ -611,59 +583,6 @@ const SyncCustomersModal: React.FC<{
|
||||||
loading: loading,
|
loading: loading,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ProFormText
|
|
||||||
name="search"
|
|
||||||
label="搜索关键词"
|
|
||||||
placeholder="输入邮箱、姓名或用户名进行搜索"
|
|
||||||
tooltip="支持搜索邮箱、姓名、用户名等字段"
|
|
||||||
/>
|
|
||||||
<ProFormSelect
|
|
||||||
name="role"
|
|
||||||
label="客户角色"
|
|
||||||
placeholder="选择客户角色进行过滤"
|
|
||||||
options={[
|
|
||||||
{ label: '所有角色', value: '' },
|
|
||||||
{ label: '管理员', value: 'administrator' },
|
|
||||||
{ label: '编辑', value: 'editor' },
|
|
||||||
{ label: '作者', value: 'author' },
|
|
||||||
{ label: '订阅者', value: 'subscriber' },
|
|
||||||
{ label: '客户', value: 'customer' },
|
|
||||||
]}
|
|
||||||
fieldProps={{
|
|
||||||
allowClear: true,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ProFormDateTimeRangePicker
|
|
||||||
name="dateRange"
|
|
||||||
label="注册日期范围"
|
|
||||||
placeholder={['开始日期', '结束日期']}
|
|
||||||
transform={(value) => {
|
|
||||||
return {
|
|
||||||
dateRange: value,
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
fieldProps={{
|
|
||||||
showTime: false,
|
|
||||||
style: { width: '100%' },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ProFormSelect
|
|
||||||
name="orderBy"
|
|
||||||
label="排序方式"
|
|
||||||
placeholder="选择排序方式"
|
|
||||||
options={[
|
|
||||||
{ label: '默认排序', value: '' },
|
|
||||||
{ label: '注册时间(升序)', value: 'date_created:asc' },
|
|
||||||
{ label: '注册时间(降序)', value: 'date_created:desc' },
|
|
||||||
{ label: '邮箱(升序)', value: 'email:asc' },
|
|
||||||
{ label: '邮箱(降序)', value: 'email:desc' },
|
|
||||||
{ label: '姓名(升序)', value: 'first_name:asc' },
|
|
||||||
{ label: '姓名(降序)', value: 'first_name:desc' },
|
|
||||||
]}
|
|
||||||
fieldProps={{
|
|
||||||
allowClear: true,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -129,15 +129,21 @@ const ListPage: React.FC = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '联系电话',
|
title: 'phone',
|
||||||
dataIndex: 'phone',
|
dataIndex: 'phone',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
render: (_, record) => record?.billing.phone || record?.shipping.phone,
|
render: (_, record) => record?.billing.phone || record?.shipping.phone,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '账单地址',
|
title: 'state',
|
||||||
dataIndex: 'billing',
|
dataIndex: 'state',
|
||||||
render: (_, record) => JSON.stringify(record?.billing || record?.shipping),
|
render: (_, record) => record?.billing.state || record?.shipping.state,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'city',
|
||||||
|
dataIndex: 'city',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record) => record?.billing.city || record?.shipping.city,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '标签',
|
title: '标签',
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@ import {
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import DictItemActions from '../components/DictItemActions';
|
|
||||||
import DictItemModal from '../components/DictItemModal';
|
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
|
|
@ -38,10 +36,13 @@ const DictPage: React.FC = () => {
|
||||||
const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false);
|
const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false);
|
||||||
const [editDictData, setEditDictData] = useState<any>(null);
|
const [editDictData, setEditDictData] = useState<any>(null);
|
||||||
|
|
||||||
// 字典项模态框状态(由 DictItemModal 组件管理)
|
// 右侧字典项列表的状态
|
||||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
const [isAddDictItemModalVisible, setIsAddDictItemModalVisible] =
|
||||||
const [isEditDictItem, setIsEditDictItem] = useState(false);
|
useState(false);
|
||||||
const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
|
const [isEditDictItemModalVisible, setIsEditDictItemModalVisible] =
|
||||||
|
useState(false);
|
||||||
|
const [editDictItemData, setEditDictItemData] = useState<any>(null);
|
||||||
|
const [dictItemForm] = Form.useForm();
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
// 获取字典列表
|
// 获取字典列表
|
||||||
|
|
@ -137,22 +138,21 @@ const DictPage: React.FC = () => {
|
||||||
link.remove();
|
link.remove();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error('下载字典模板失败:' + (error.message || '未知错误'));
|
message.error('下载字典模板失败:' + (error.message || '未知错误'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加字典项
|
// 添加字典项
|
||||||
const handleAddDictItem = () => {
|
const handleAddDictItem = () => {
|
||||||
setIsEditDictItem(false);
|
dictItemForm.resetFields();
|
||||||
setEditingDictItemData(null);
|
setIsAddDictItemModalVisible(true);
|
||||||
setIsDictItemModalVisible(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 编辑字典项
|
// 编辑字典项
|
||||||
const handleEditDictItem = (record: any) => {
|
const handleEditDictItem = (record: any) => {
|
||||||
setIsEditDictItem(true);
|
setEditDictItemData(record);
|
||||||
setEditingDictItemData(record);
|
dictItemForm.setFieldsValue(record);
|
||||||
setIsDictItemModalVisible(true);
|
setIsEditDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除字典项
|
// 删除字典项
|
||||||
|
|
@ -173,42 +173,53 @@ const DictPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理字典项模态框提交(添加或编辑)
|
// 添加字典项表单提交
|
||||||
const handleDictItemModalOk = async (values: any) => {
|
const handleAddDictItemFormSubmit = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
if (isEditDictItem && editingDictItemData) {
|
const result = await dictApi.dictcontrollerCreatedictitem({
|
||||||
// 编辑字典项
|
...values,
|
||||||
const result = await dictApi.dictcontrollerUpdatedictitem(
|
dictId: selectedDict.id,
|
||||||
{ id: editingDictItemData.id },
|
});
|
||||||
values,
|
|
||||||
);
|
if (!result.success) {
|
||||||
if (!result.success) {
|
throw new Error(result.message || '添加失败');
|
||||||
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('添加成功');
|
|
||||||
}
|
}
|
||||||
setIsDictItemModalVisible(false);
|
|
||||||
|
message.success('添加成功');
|
||||||
|
setIsAddDictItemModalVisible(false);
|
||||||
|
|
||||||
// 强制刷新字典项列表
|
// 强制刷新字典项列表
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
actionRef.current?.reload();
|
actionRef.current?.reload();
|
||||||
}, 100);
|
}, 100);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(
|
message.error(`添加失败:${error.message || '未知错误'}`);
|
||||||
`${isEditDictItem ? '更新' : '添加'}失败:${
|
}
|
||||||
error.message || '未知错误'
|
};
|
||||||
}`,
|
|
||||||
|
// 编辑字典项表单提交
|
||||||
|
const handleEditDictItemFormSubmit = async (values: any) => {
|
||||||
|
if (!editDictItemData) return;
|
||||||
|
try {
|
||||||
|
const result = await dictApi.dictcontrollerUpdatedictitem(
|
||||||
|
{ id: editDictItemData.id },
|
||||||
|
values,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.message || '更新失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success('更新成功');
|
||||||
|
setIsEditDictItemModalVisible(false);
|
||||||
|
setEditDictItemData(null);
|
||||||
|
|
||||||
|
// 强制刷新字典项列表
|
||||||
|
setTimeout(() => {
|
||||||
|
actionRef.current?.reload();
|
||||||
|
}, 100);
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(`更新失败:${error.message || '未知错误'}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -275,7 +286,7 @@ const DictPage: React.FC = () => {
|
||||||
|
|
||||||
message.success(`成功导出 ${response.length} 条数据`);
|
message.success(`成功导出 ${response.length} 条数据`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -326,7 +337,13 @@ const DictPage: React.FC = () => {
|
||||||
key: 'shortName',
|
key: 'shortName',
|
||||||
copyable: true,
|
copyable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '图片',
|
||||||
|
dataIndex: 'image',
|
||||||
|
key: 'image',
|
||||||
|
valueType: 'image',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '标题',
|
title: '标题',
|
||||||
dataIndex: 'title',
|
dataIndex: 'title',
|
||||||
|
|
@ -339,13 +356,6 @@ const DictPage: React.FC = () => {
|
||||||
key: 'titleCN',
|
key: 'titleCN',
|
||||||
copyable: true,
|
copyable: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '图片',
|
|
||||||
dataIndex: 'image',
|
|
||||||
key: 'image',
|
|
||||||
valueType: 'image',
|
|
||||||
width: 80,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
|
|
@ -396,18 +406,7 @@ const DictPage: React.FC = () => {
|
||||||
</Button>
|
</Button>
|
||||||
<Upload
|
<Upload
|
||||||
name="file"
|
name="file"
|
||||||
action={undefined}
|
action="/api/dict/import"
|
||||||
customRequest={async (options) => {
|
|
||||||
const { file, onSuccess, onError } = options;
|
|
||||||
try {
|
|
||||||
const result = await dictApi.dictcontrollerImportdicts({}, [
|
|
||||||
file as File,
|
|
||||||
]);
|
|
||||||
onSuccess?.(result);
|
|
||||||
} catch (error) {
|
|
||||||
onError?.(error as Error);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
onChange={(info) => {
|
onChange={(info) => {
|
||||||
if (info.file.status === 'done') {
|
if (info.file.status === 'done') {
|
||||||
|
|
@ -477,7 +476,7 @@ const DictPage: React.FC = () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兼容旧的响应格式(直接返回数组)
|
// 兼容旧的响应格式(直接返回数组)
|
||||||
return {
|
return {
|
||||||
data: res || [],
|
data: res || [],
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -496,46 +495,151 @@ const DictPage: React.FC = () => {
|
||||||
size="small"
|
size="small"
|
||||||
key={selectedDict?.id}
|
key={selectedDict?.id}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
<DictItemActions
|
<Button
|
||||||
key="dictItemActions"
|
type="primary"
|
||||||
selectedDict={selectedDict}
|
onClick={handleAddDictItem}
|
||||||
actionRef={actionRef}
|
disabled={!selectedDict}
|
||||||
showExport={true}
|
size="small"
|
||||||
onImport={async (file: File, dictId: number) => {
|
key="add"
|
||||||
// 创建 FormData 对象
|
>
|
||||||
const formData = new FormData();
|
添加字典项
|
||||||
// 添加文件到 FormData
|
</Button>,
|
||||||
formData.append('file', file);
|
<Upload
|
||||||
// 添加字典 ID 到 FormData
|
name="file"
|
||||||
formData.append('dictId', String(dictId));
|
action={undefined}
|
||||||
// 调用导入字典项的 API,直接返回解析后的 JSON 对象
|
customRequest={async (options) => {
|
||||||
const result = await dictApi.dictcontrollerImportdictitems(
|
const { file, onSuccess, onError } = options;
|
||||||
formData,
|
try {
|
||||||
);
|
const result =
|
||||||
return result;
|
await dictApi.dictcontrollerImportdictitems(
|
||||||
|
{ dictId: selectedDict?.id },
|
||||||
|
[file as File],
|
||||||
|
);
|
||||||
|
onSuccess?.(result);
|
||||||
|
} catch (error) {
|
||||||
|
onError?.(error as Error);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onExport={handleExportDictItems}
|
showUploadList={false}
|
||||||
onAdd={handleAddDictItem}
|
disabled={!selectedDict}
|
||||||
onRefreshDicts={fetchDicts}
|
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>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
{/* 字典项 Modal(添加或编辑) */}
|
{/* 添加字典项 Modal */}
|
||||||
<DictItemModal
|
<Modal
|
||||||
visible={isDictItemModalVisible}
|
title={`添加字典项 - ${selectedDict?.title || '未选择字典'}`}
|
||||||
isEdit={isEditDictItem}
|
open={isAddDictItemModalVisible}
|
||||||
editingData={editingDictItemData}
|
onOk={() => dictItemForm.submit()}
|
||||||
selectedDict={selectedDict}
|
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()}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setIsDictItemModalVisible(false);
|
setIsEditDictItemModalVisible(false);
|
||||||
setEditingDictItemData(null);
|
setEditDictItemData(null);
|
||||||
}}
|
}}
|
||||||
onOk={handleDictItemModalOk}
|
>
|
||||||
/>
|
<Form
|
||||||
|
form={dictItemForm}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={handleEditDictItemFormSubmit}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label="名称"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: '请输入名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="名称 (e.g., zyn)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="标题"
|
||||||
|
name="title"
|
||||||
|
rules={[{ required: true, message: '请输入标题' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="标题 (e.g., ZYN)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="中文标题" name="titleCN">
|
||||||
|
<Input placeholder="中文标题 (e.g., 品牌)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="简称 (可选)" name="shortName">
|
||||||
|
<Input placeholder="简称 (可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="图片 (可选)" name="image">
|
||||||
|
<Input placeholder="图片链接 (可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="值 (可选)" name="value">
|
||||||
|
<Input placeholder="值 (可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
{/* 添加字典 Modal */}
|
{/* 添加字典 Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -151,10 +151,7 @@ const OrderItemsPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
request={request}
|
request={request}
|
||||||
pagination={{
|
pagination={{ showSizeChanger: true }}
|
||||||
showSizeChanger: true,
|
|
||||||
showQuickJumper: true,
|
|
||||||
}}
|
|
||||||
search={{ labelWidth: 90, span: 6 }}
|
search={{ labelWidth: 90, span: 6 }}
|
||||||
toolBarRender={false}
|
toolBarRender={false}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import styles from '../../../style/order-list.css';
|
||||||
|
|
||||||
import InternationalPhoneInput from '@/components/InternationalPhoneInput';
|
import InternationalPhoneInput from '@/components/InternationalPhoneInput';
|
||||||
import SyncForm from '@/components/SyncForm';
|
import SyncForm from '@/components/SyncForm';
|
||||||
import { showSyncResult, SyncResultData } from '@/components/SyncResultMessage';
|
|
||||||
import { ORDER_STATUS_ENUM } from '@/constants';
|
import { ORDER_STATUS_ENUM } from '@/constants';
|
||||||
import { HistoryOrder } from '@/pages/Statistics/Order';
|
import { HistoryOrder } from '@/pages/Statistics/Order';
|
||||||
import {
|
import {
|
||||||
|
|
@ -22,7 +21,6 @@ import {
|
||||||
ordercontrollerGetorders,
|
ordercontrollerGetorders,
|
||||||
ordercontrollerRefundorder,
|
ordercontrollerRefundorder,
|
||||||
ordercontrollerSyncorderbyid,
|
ordercontrollerSyncorderbyid,
|
||||||
ordercontrollerSyncorders,
|
|
||||||
ordercontrollerUpdateorderitems,
|
ordercontrollerUpdateorderitems,
|
||||||
} from '@/servers/api/order';
|
} from '@/servers/api/order';
|
||||||
import { productcontrollerSearchproducts } from '@/servers/api/product';
|
import { productcontrollerSearchproducts } from '@/servers/api/product';
|
||||||
|
|
@ -75,8 +73,8 @@ import {
|
||||||
Tag,
|
Tag,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useMemo, useRef, useState } from 'react';
|
import React, { useMemo, useRef, useState } from 'react';
|
||||||
|
import { request, useParams } from '@umijs/max';
|
||||||
import RelatedOrders from '../../Subscription/Orders/RelatedOrders';
|
import RelatedOrders from '../../Subscription/Orders/RelatedOrders';
|
||||||
import { request } from '@umijs/max';
|
|
||||||
|
|
||||||
const ListPage: React.FC = () => {
|
const ListPage: React.FC = () => {
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
@ -189,20 +187,11 @@ const ListPage: React.FC = () => {
|
||||||
dataIndex: 'siteId',
|
dataIndex: 'siteId',
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
request: async () => {
|
request: async () => {
|
||||||
try {
|
const { data = [] } = await sitecontrollerAll();
|
||||||
const result = await sitecontrollerAll();
|
return data.map((item) => ({
|
||||||
const {success, data}= result
|
label: item.name,
|
||||||
if (success && data) {
|
value: item.id,
|
||||||
return data.map((site: any) => ({
|
}));
|
||||||
label: site.name,
|
|
||||||
value: site.id,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取站点列表失败:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -210,22 +199,16 @@ const ListPage: React.FC = () => {
|
||||||
dataIndex: 'keyword',
|
dataIndex: 'keyword',
|
||||||
hideInTable: true,
|
hideInTable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '订单ID',
|
title: '订单ID',
|
||||||
dataIndex: 'externalOrderId',
|
dataIndex: 'externalOrderId',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '订单创建日期',
|
title: '订单日期',
|
||||||
dataIndex: 'date_created',
|
dataIndex: 'date_created',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
valueType: 'dateTime',
|
valueType: 'dateTime',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '付款日期',
|
|
||||||
dataIndex: 'date_paid',
|
|
||||||
hideInSearch: true,
|
|
||||||
valueType: 'dateTime',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '金额',
|
title: '金额',
|
||||||
dataIndex: 'total',
|
dataIndex: 'total',
|
||||||
|
|
@ -267,23 +250,22 @@ const ListPage: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'orderStatus',
|
dataIndex: 'orderStatus',
|
||||||
hideInSearch: true,
|
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
valueEnum: ORDER_STATUS_ENUM,
|
valueEnum: ORDER_STATUS_ENUM,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '物流',
|
title: '物流',
|
||||||
dataIndex: 'fulfillments',
|
dataIndex: 'shipmentList',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{(record as any)?.fulfillments?.map((item: any) => {
|
{(record as any)?.shipmentList?.map((item: any) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
return (
|
return (
|
||||||
<div style={{ display:"flex", alignItems:"center",'flexDirection':'column' }}>
|
<div>
|
||||||
<span>物流供应商: {item.shipping_provider}</span>
|
{item.tracking_provider}:{item.primary_tracking_number} (
|
||||||
<span>物流单号: {item.tracking_number}</span>
|
{formatShipmentState(item.state)})
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -294,6 +276,7 @@ const ListPage: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: 'IP',
|
title: 'IP',
|
||||||
dataIndex: 'customer_ip_address',
|
dataIndex: 'customer_ip_address',
|
||||||
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '设备',
|
title: '设备',
|
||||||
|
|
@ -356,18 +339,15 @@ const ListPage: React.FC = () => {
|
||||||
message.error('站点ID或外部订单ID不存在');
|
message.error('站点ID或外部订单ID不存在');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const {
|
const { success, message: errMsg } =
|
||||||
success,
|
await ordercontrollerSyncorderbyid({
|
||||||
message: errMsg,
|
siteId: record.siteId,
|
||||||
data,
|
orderId: record.externalOrderId,
|
||||||
} = await ordercontrollerSyncorderbyid({
|
});
|
||||||
siteId: record.siteId,
|
|
||||||
orderId: record.externalOrderId,
|
|
||||||
});
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
showSyncResult(data as SyncResultData, '订单');
|
message.success('同步成功');
|
||||||
actionRef.current?.reload();
|
actionRef.current?.reload();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error?.message || '同步失败');
|
message.error(error?.message || '同步失败');
|
||||||
|
|
@ -484,24 +464,19 @@ const ListPage: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSizeOptions: ['10', '20', '50', '100', '1000'],
|
pageSizeOptions: ['10', '20', '50', '100', '1000'],
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
|
||||||
defaultPageSize: 10,
|
defaultPageSize: 10,
|
||||||
}}
|
}}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
// <CreateOrder tableRef={actionRef} />,
|
<CreateOrder tableRef={actionRef} />,
|
||||||
<SyncForm
|
<SyncForm
|
||||||
onFinish={async (values: any) => {
|
onFinish={async (values: any) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const { success, message: errMsg } =
|
||||||
success,
|
await ordercontrollerSyncorderbyid(values);
|
||||||
message: errMsg,
|
|
||||||
data,
|
|
||||||
} = await ordercontrollerSyncorders(values);
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
// 使用 showSyncResult 函数显示详细的同步结果
|
message.success('同步成功');
|
||||||
showSyncResult(data as SyncResultData, '订单');
|
|
||||||
actionRef.current?.reload();
|
actionRef.current?.reload();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error?.message || '同步失败');
|
message.error(error?.message || '同步失败');
|
||||||
|
|
@ -509,24 +484,31 @@ const ListPage: React.FC = () => {
|
||||||
}}
|
}}
|
||||||
tableRef={actionRef}
|
tableRef={actionRef}
|
||||||
/>,
|
/>,
|
||||||
<Popconfirm
|
// <Button
|
||||||
|
// type="primary"
|
||||||
|
// disabled={selectedRowKeys.length === 0}
|
||||||
|
// onClick={handleBatchExport}
|
||||||
|
// >
|
||||||
|
// 批量导出
|
||||||
|
// </Button>,
|
||||||
|
<Popconfirm
|
||||||
title="批量导出"
|
title="批量导出"
|
||||||
description="确认导出选中的订单吗?"
|
description="确认导出选中的订单吗?"
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
console.log(selectedRowKeys);
|
console.log(selectedRowKeys);
|
||||||
try {
|
try {
|
||||||
const res = await request('/order/export', {
|
const res = await request('/order/order/export', {
|
||||||
method: 'POST',
|
method: 'GET',
|
||||||
data: {
|
params: {
|
||||||
ids: selectedRowKeys,
|
ids: selectedRowKeys,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (res?.success && res.data) {
|
if (res?.success && res?.data?.csv) {
|
||||||
const blob = new Blob([res.data], { type: 'text/csv;charset=utf-8;' });
|
const blob = new Blob([res.data.csv], { type: 'text/csv;charset=utf-8;' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = 'orders.csv';
|
a.download = 'customers.csv';
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -539,13 +521,10 @@ const ListPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button type="primary" ghost>
|
||||||
type="primary"
|
|
||||||
ghost
|
|
||||||
>
|
|
||||||
批量导出
|
批量导出
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>,
|
</Popconfirm>
|
||||||
]}
|
]}
|
||||||
request={async ({ date, ...param }: any) => {
|
request={async ({ date, ...param }: any) => {
|
||||||
if (param.status === 'all') {
|
if (param.status === 'all') {
|
||||||
|
|
@ -634,36 +613,33 @@ const Detail: React.FC<{
|
||||||
)
|
)
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
<Divider type="vertical" />,
|
<Divider type="vertical" />,
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
if (!record.siteId || !record.externalOrderId) {
|
if (!record.siteId || !record.externalOrderId) {
|
||||||
message.error('站点ID或外部订单ID不存在');
|
message.error('站点ID或外部订单ID不存在');
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await ordercontrollerSyncorderbyid({
|
||||||
|
siteId: record.siteId,
|
||||||
|
orderId: record.externalOrderId,
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
message.success('同步成功');
|
||||||
|
tableRef.current?.reload();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error?.message || '同步失败');
|
||||||
}
|
}
|
||||||
const {
|
}}
|
||||||
success,
|
>
|
||||||
message: errMsg,
|
同步订单
|
||||||
data,
|
</Button>,
|
||||||
} = await ordercontrollerSyncorderbyid({
|
]),
|
||||||
siteId: record.siteId,
|
|
||||||
orderId: record.externalOrderId,
|
|
||||||
});
|
|
||||||
if (!success) {
|
|
||||||
throw new Error(errMsg);
|
|
||||||
}
|
|
||||||
showSyncResult(data as SyncResultData, '订单');
|
|
||||||
tableRef.current?.reload();
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error?.message || '同步失败');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
同步订单
|
|
||||||
</Button>,
|
|
||||||
]),
|
|
||||||
// ...(['processing', 'pending_reshipment'].includes(record.orderStatus)
|
// ...(['processing', 'pending_reshipment'].includes(record.orderStatus)
|
||||||
// ? [
|
// ? [
|
||||||
// <Divider type="vertical" />,
|
// <Divider type="vertical" />,
|
||||||
|
|
@ -682,152 +658,152 @@ const Detail: React.FC<{
|
||||||
'pending_refund',
|
'pending_refund',
|
||||||
].includes(record.orderStatus)
|
].includes(record.orderStatus)
|
||||||
? [
|
? [
|
||||||
<Divider type="vertical" />,
|
<Divider type="vertical" />,
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="转至售后"
|
title="转至售后"
|
||||||
description="确认转至售后?"
|
description="确认转至售后?"
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
try {
|
try {
|
||||||
if (!record.id) {
|
if (!record.id) {
|
||||||
message.error('订单ID不存在');
|
message.error('订单ID不存在');
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await ordercontrollerChangestatus(
|
||||||
|
{
|
||||||
|
id: record.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 'after_sale_pending',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
tableRef.current?.reload();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message);
|
||||||
}
|
}
|
||||||
const { success, message: errMsg } =
|
}}
|
||||||
await ordercontrollerChangestatus(
|
>
|
||||||
{
|
<Button type="primary" ghost>
|
||||||
id: record.id,
|
转至售后
|
||||||
},
|
</Button>
|
||||||
{
|
</Popconfirm>,
|
||||||
status: 'after_sale_pending',
|
]
|
||||||
},
|
|
||||||
);
|
|
||||||
if (!success) {
|
|
||||||
throw new Error(errMsg);
|
|
||||||
}
|
|
||||||
tableRef.current?.reload();
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error.message);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button type="primary" ghost>
|
|
||||||
转至售后
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>,
|
|
||||||
]
|
|
||||||
: []),
|
: []),
|
||||||
...(record.orderStatus === 'after_sale_pending'
|
...(record.orderStatus === 'after_sale_pending'
|
||||||
? [
|
? [
|
||||||
<Divider type="vertical" />,
|
<Divider type="vertical" />,
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="转至取消"
|
title="转至取消"
|
||||||
description="确认转至取消?"
|
description="确认转至取消?"
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
try {
|
try {
|
||||||
if (!record.id) {
|
if (!record.id) {
|
||||||
message.error('订单ID不存在');
|
message.error('订单ID不存在');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { success, message: errMsg } =
|
const { success, message: errMsg } =
|
||||||
await ordercontrollerCancelorder({
|
await ordercontrollerCancelorder({
|
||||||
id: record.id,
|
|
||||||
});
|
|
||||||
if (!success) {
|
|
||||||
throw new Error(errMsg);
|
|
||||||
}
|
|
||||||
tableRef.current?.reload();
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error.message);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button type="primary" ghost>
|
|
||||||
转至取消
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>,
|
|
||||||
<Divider type="vertical" />,
|
|
||||||
<Popconfirm
|
|
||||||
title="转至退款"
|
|
||||||
description="确认转至退款?"
|
|
||||||
onConfirm={async () => {
|
|
||||||
try {
|
|
||||||
if (!record.id) {
|
|
||||||
message.error('订单ID不存在');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { success, message: errMsg } =
|
|
||||||
await ordercontrollerRefundorder({
|
|
||||||
id: record.id,
|
|
||||||
});
|
|
||||||
if (!success) {
|
|
||||||
throw new Error(errMsg);
|
|
||||||
}
|
|
||||||
tableRef.current?.reload();
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error.message);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button type="primary" ghost>
|
|
||||||
转至退款
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>,
|
|
||||||
<Divider type="vertical" />,
|
|
||||||
<Popconfirm
|
|
||||||
title="转至完成"
|
|
||||||
description="确认转至完成?"
|
|
||||||
onConfirm={async () => {
|
|
||||||
try {
|
|
||||||
if (!record.id) {
|
|
||||||
message.error('订单ID不存在');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { success, message: errMsg } =
|
|
||||||
await ordercontrollerCompletedorder({
|
|
||||||
id: record.id,
|
|
||||||
});
|
|
||||||
if (!success) {
|
|
||||||
throw new Error(errMsg);
|
|
||||||
}
|
|
||||||
tableRef.current?.reload();
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error.message);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button type="primary" ghost>
|
|
||||||
转至完成
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>,
|
|
||||||
<Divider type="vertical" />,
|
|
||||||
<Popconfirm
|
|
||||||
title="转至待补发"
|
|
||||||
description="确认转至待补发?"
|
|
||||||
onConfirm={async () => {
|
|
||||||
try {
|
|
||||||
const { success, message: errMsg } =
|
|
||||||
await ordercontrollerChangestatus(
|
|
||||||
{
|
|
||||||
id: record.id,
|
id: record.id,
|
||||||
},
|
});
|
||||||
{
|
if (!success) {
|
||||||
status: 'pending_reshipment',
|
throw new Error(errMsg);
|
||||||
},
|
}
|
||||||
);
|
tableRef.current?.reload();
|
||||||
if (!success) {
|
} catch (error: any) {
|
||||||
throw new Error(errMsg);
|
message.error(error.message);
|
||||||
}
|
}
|
||||||
tableRef.current?.reload();
|
}}
|
||||||
} catch (error: any) {
|
>
|
||||||
message.error(error.message);
|
<Button type="primary" ghost>
|
||||||
}
|
转至取消
|
||||||
}}
|
</Button>
|
||||||
>
|
</Popconfirm>,
|
||||||
<Button type="primary" ghost>
|
<Divider type="vertical" />,
|
||||||
转至待补发
|
<Popconfirm
|
||||||
</Button>
|
title="转至退款"
|
||||||
</Popconfirm>,
|
description="确认转至退款?"
|
||||||
]
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
|
if (!record.id) {
|
||||||
|
message.error('订单ID不存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await ordercontrollerRefundorder({
|
||||||
|
id: record.id,
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
tableRef.current?.reload();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" ghost>
|
||||||
|
转至退款
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
<Divider type="vertical" />,
|
||||||
|
<Popconfirm
|
||||||
|
title="转至完成"
|
||||||
|
description="确认转至完成?"
|
||||||
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
|
if (!record.id) {
|
||||||
|
message.error('订单ID不存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await ordercontrollerCompletedorder({
|
||||||
|
id: record.id,
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
tableRef.current?.reload();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" ghost>
|
||||||
|
转至完成
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
<Divider type="vertical" />,
|
||||||
|
<Popconfirm
|
||||||
|
title="转至待补发"
|
||||||
|
description="确认转至待补发?"
|
||||||
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await ordercontrollerChangestatus(
|
||||||
|
{
|
||||||
|
id: record.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 'pending_reshipment',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
tableRef.current?.reload();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" ghost>
|
||||||
|
转至待补发
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
]
|
||||||
: []),
|
: []),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -1089,31 +1065,31 @@ const Detail: React.FC<{
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
v.state === 'waiting-for-scheduling' ||
|
v.state === 'waiting-for-scheduling' ||
|
||||||
v.state === 'waiting-for-transit'
|
v.state === 'waiting-for-transit'
|
||||||
? [
|
? [
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="取消运单"
|
title="取消运单"
|
||||||
description="确认取消运单?"
|
description="确认取消运单?"
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
try {
|
try {
|
||||||
const { success, message: errMsg } =
|
const { success, message: errMsg } =
|
||||||
await logisticscontrollerDelshipment({
|
await logisticscontrollerDelshipment({
|
||||||
id: v.id,
|
id: v.id,
|
||||||
});
|
});
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
tableRef.current?.reload();
|
||||||
|
initRequest();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message);
|
||||||
}
|
}
|
||||||
tableRef.current?.reload();
|
}}
|
||||||
initRequest();
|
>
|
||||||
} catch (error: any) {
|
<DeleteFilled />
|
||||||
message.error(error.message);
|
取消运单
|
||||||
}
|
</Popconfirm>,
|
||||||
}}
|
]
|
||||||
>
|
|
||||||
<DeleteFilled />
|
|
||||||
取消运单
|
|
||||||
</Popconfirm>,
|
|
||||||
]
|
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -1501,16 +1477,16 @@ const Shipping: React.FC<{
|
||||||
<ProFormList
|
<ProFormList
|
||||||
label="发货产品"
|
label="发货产品"
|
||||||
name="sales"
|
name="sales"
|
||||||
// rules={[
|
// rules={[
|
||||||
// {
|
// {
|
||||||
// required: true,
|
// required: true,
|
||||||
// message: '至少需要一个商品',
|
// message: '至少需要一个商品',
|
||||||
// validator: (_, value) =>
|
// validator: (_, value) =>
|
||||||
// value && value.length > 0
|
// value && value.length > 0
|
||||||
// ? Promise.resolve()
|
// ? Promise.resolve()
|
||||||
// : Promise.reject('至少需要一个商品'),
|
// : Promise.reject('至少需要一个商品'),
|
||||||
// },
|
// },
|
||||||
// ]}
|
// ]}
|
||||||
>
|
>
|
||||||
<ProForm.Group>
|
<ProForm.Group>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
|
|
@ -1935,7 +1911,7 @@ const Shipping: React.FC<{
|
||||||
name="description"
|
name="description"
|
||||||
placeholder="请输入描述"
|
placeholder="请输入描述"
|
||||||
width="lg"
|
width="lg"
|
||||||
// rules={[{ required: true, message: '请输入描述' }]}
|
// rules={[{ required: true, message: '请输入描述' }]}
|
||||||
/>
|
/>
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
</ProFormList>
|
</ProFormList>
|
||||||
|
|
@ -2146,12 +2122,12 @@ const SalesChange: React.FC<{
|
||||||
params={{}}
|
params={{}}
|
||||||
request={async ({ keyWords }) => {
|
request={async ({ keyWords }) => {
|
||||||
try {
|
try {
|
||||||
const { data } = await productcontrollerSearchproducts({
|
const { data } = await wpproductcontrollerSearchproducts({
|
||||||
name: keyWords,
|
name: keyWords,
|
||||||
});
|
});
|
||||||
return data?.map((item) => {
|
return data?.map((item) => {
|
||||||
return {
|
return {
|
||||||
label: `${item.name} - ${item.nameCn}`,
|
label: `${item.name}`,
|
||||||
value: item?.sku,
|
value: item?.sku,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,22 @@
|
||||||
import * as dictApi from '@/servers/api/dict';
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
ActionType,
|
ActionType,
|
||||||
PageContainer,
|
PageContainer,
|
||||||
ProTable,
|
ProTable,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import { Button, Input, Layout, Space, Table, message } from 'antd';
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Layout,
|
||||||
|
Modal,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Upload,
|
||||||
|
message,
|
||||||
|
} from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import DictItemActions from '../../Dict/components/DictItemActions';
|
|
||||||
import DictItemModal from '../../Dict/components/DictItemModal';
|
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
|
|
@ -24,84 +32,10 @@ const AttributePage: React.FC = () => {
|
||||||
// 右侧字典项 ProTable 的引用
|
// 右侧字典项 ProTable 的引用
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
// 字典项模态框状态(由 DictItemModal 组件管理)
|
// 字典项新增/编辑模态框控制
|
||||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||||
const [isEditDictItem, setIsEditDictItem] = useState(false);
|
const [editingDictItem, setEditingDictItem] = useState<any>(null);
|
||||||
const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
|
const [dictItemForm] = Form.useForm();
|
||||||
|
|
||||||
// 导出字典项数据
|
|
||||||
const handleExportDictItems = async () => {
|
|
||||||
// 条件判断,确保已选择字典
|
|
||||||
if (!selectedDict) {
|
|
||||||
message.warning('请先选择字典');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取当前字典的所有数据
|
|
||||||
const response = await request('/dict/items', {
|
|
||||||
params: {
|
|
||||||
dictId: selectedDict.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 确保返回的是数组
|
|
||||||
const data = Array.isArray(response) ? response : response?.data || [];
|
|
||||||
|
|
||||||
// 条件判断,检查是否有数据可导出
|
|
||||||
if (data.length === 0) {
|
|
||||||
message.warning('当前字典没有数据可导出');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将数据转换为CSV格式
|
|
||||||
const headers = [
|
|
||||||
'name',
|
|
||||||
'title',
|
|
||||||
'titleCN',
|
|
||||||
'value',
|
|
||||||
'sort',
|
|
||||||
'image',
|
|
||||||
'shortName',
|
|
||||||
];
|
|
||||||
const csvContent = [
|
|
||||||
headers.join(','),
|
|
||||||
...data.map((item: any) =>
|
|
||||||
headers
|
|
||||||
.map((header) => {
|
|
||||||
const value = item[header] || '';
|
|
||||||
// 条件判断,如果值包含逗号或引号,需要转义
|
|
||||||
if (
|
|
||||||
typeof value === 'string' &&
|
|
||||||
(value.includes(',') || value.includes('"'))
|
|
||||||
) {
|
|
||||||
return `"${value.replace(/"/g, '""')}"`;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
})
|
|
||||||
.join(','),
|
|
||||||
),
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
// 创建blob并下载
|
|
||||||
const blob = new Blob(['\ufeff' + csvContent], {
|
|
||||||
// 添加BOM以支持中文
|
|
||||||
type: 'text/csv;charset=utf-8',
|
|
||||||
});
|
|
||||||
const url = window.URL.createObjectURL(blob);
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = url;
|
|
||||||
link.setAttribute('download', `${selectedDict.name}_dict_items.csv`);
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
link.remove();
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
|
|
||||||
message.success(`成功导出 ${data.length} 条数据`);
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchDicts = async (title?: string) => {
|
const fetchDicts = async (title?: string) => {
|
||||||
setLoadingDicts(true);
|
setLoadingDicts(true);
|
||||||
|
|
@ -131,24 +65,24 @@ const AttributePage: React.FC = () => {
|
||||||
|
|
||||||
// 打开添加字典项模态框
|
// 打开添加字典项模态框
|
||||||
const handleAddDictItem = () => {
|
const handleAddDictItem = () => {
|
||||||
setIsEditDictItem(false);
|
setEditingDictItem(null);
|
||||||
setEditingDictItemData(null);
|
dictItemForm.resetFields();
|
||||||
setIsDictItemModalVisible(true);
|
setIsDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开编辑字典项模态框
|
// 打开编辑字典项模态框
|
||||||
const handleEditDictItem = (item: any) => {
|
const handleEditDictItem = (item: any) => {
|
||||||
setIsEditDictItem(true);
|
setEditingDictItem(item);
|
||||||
setEditingDictItemData(item);
|
dictItemForm.setFieldsValue(item);
|
||||||
setIsDictItemModalVisible(true);
|
setIsDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 字典项表单提交(新增或编辑)
|
// 字典项表单提交(新增或编辑)
|
||||||
const handleDictItemFormSubmit = async (values: any) => {
|
const handleDictItemFormSubmit = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
if (isEditDictItem && editingDictItemData) {
|
if (editingDictItem) {
|
||||||
// 条件判断,存在编辑项则执行更新
|
// 条件判断,存在编辑项则执行更新
|
||||||
await request(`/dict/item/${editingDictItemData.id}`, {
|
await request(`/dict/item/${editingDictItem.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: values,
|
data: values,
|
||||||
});
|
});
|
||||||
|
|
@ -164,7 +98,7 @@ const AttributePage: React.FC = () => {
|
||||||
setIsDictItemModalVisible(false);
|
setIsDictItemModalVisible(false);
|
||||||
actionRef.current?.reload(); // 刷新 ProTable
|
actionRef.current?.reload(); // 刷新 ProTable
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(isEditDictItem ? '更新失败' : '添加失败');
|
message.error(editingDictItem ? '更新失败' : '添加失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -362,45 +296,85 @@ const AttributePage: React.FC = () => {
|
||||||
size="small"
|
size="small"
|
||||||
key={selectedDict?.id}
|
key={selectedDict?.id}
|
||||||
headerTitle={
|
headerTitle={
|
||||||
<DictItemActions
|
<Space>
|
||||||
selectedDict={selectedDict}
|
<Button
|
||||||
actionRef={actionRef}
|
type="primary"
|
||||||
showExport={true}
|
size="small"
|
||||||
onImport={async (file: File, dictId: number) => {
|
onClick={handleAddDictItem}
|
||||||
// 创建 FormData 对象
|
disabled={!selectedDict}
|
||||||
const formData = new FormData();
|
>
|
||||||
// 添加文件到 FormData
|
添加字典项
|
||||||
formData.append('file', file);
|
</Button>
|
||||||
// 添加字典 ID 到 FormData
|
<Upload
|
||||||
formData.append('dictId', String(dictId));
|
name="file"
|
||||||
// 调用导入字典项的 API
|
action={`/dict/item/import`}
|
||||||
const response = await dictApi.dictcontrollerImportdictitems(
|
data={{ dictId: selectedDict?.id }}
|
||||||
formData,
|
showUploadList={false}
|
||||||
);
|
disabled={!selectedDict}
|
||||||
// 返回 JSON 响应
|
onChange={(info) => {
|
||||||
return await response.json();
|
// 条件判断,上传状态处理
|
||||||
}}
|
if (info.file.status === 'done') {
|
||||||
onExport={handleExportDictItems}
|
message.success(`${info.file.name} 文件上传成功`);
|
||||||
onAdd={handleAddDictItem}
|
actionRef.current?.reload();
|
||||||
onRefreshDicts={fetchDicts}
|
} else if (info.file.status === 'error') {
|
||||||
/>
|
message.error(`${info.file.name} 文件上传失败`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<UploadOutlined />}
|
||||||
|
disabled={!selectedDict}
|
||||||
|
>
|
||||||
|
导入字典项
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
</Space>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
{/* 字典项 Modal(添加或编辑) */}
|
<Modal
|
||||||
<DictItemModal
|
title={editingDictItem ? '编辑字典项' : '添加字典项'}
|
||||||
visible={isDictItemModalVisible}
|
open={isDictItemModalVisible}
|
||||||
isEdit={isEditDictItem}
|
onOk={() => dictItemForm.submit()}
|
||||||
editingData={editingDictItemData}
|
onCancel={() => setIsDictItemModalVisible(false)}
|
||||||
selectedDict={selectedDict}
|
destroyOnClose
|
||||||
onCancel={() => {
|
>
|
||||||
setIsDictItemModalVisible(false);
|
<Form
|
||||||
setEditingDictItemData(null);
|
form={dictItemForm}
|
||||||
}}
|
layout="vertical"
|
||||||
onOk={handleDictItemFormSubmit}
|
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>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
export default function CsvTool() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>产品CSV 工具</h1>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,6 @@ import {
|
||||||
productcontrollerCreateproduct,
|
productcontrollerCreateproduct,
|
||||||
productcontrollerGetcategoriesall,
|
productcontrollerGetcategoriesall,
|
||||||
productcontrollerGetcategoryattributes,
|
productcontrollerGetcategoryattributes,
|
||||||
productcontrollerGetproductlist,
|
|
||||||
} from '@/servers/api/product';
|
} from '@/servers/api/product';
|
||||||
import { stockcontrollerGetstocks as getStocks } from '@/servers/api/stock';
|
import { stockcontrollerGetstocks as getStocks } from '@/servers/api/stock';
|
||||||
import { templatecontrollerRendertemplate } from '@/servers/api/template';
|
import { templatecontrollerRendertemplate } from '@/servers/api/template';
|
||||||
|
|
@ -77,7 +76,7 @@ const CreateForm: React.FC<{
|
||||||
const strengthName: string = String(strengthValues?.[0] || '');
|
const strengthName: string = String(strengthValues?.[0] || '');
|
||||||
const flavorName: string = String(flavorValues?.[0] || '');
|
const flavorName: string = String(flavorValues?.[0] || '');
|
||||||
const humidityName: string = String(humidityValues?.[0] || '');
|
const humidityName: string = String(humidityValues?.[0] || '');
|
||||||
console.log(formValues)
|
|
||||||
// 调用模板渲染API来生成SKU
|
// 调用模板渲染API来生成SKU
|
||||||
const {
|
const {
|
||||||
data: rendered,
|
data: rendered,
|
||||||
|
|
@ -86,25 +85,10 @@ const CreateForm: React.FC<{
|
||||||
} = await templatecontrollerRendertemplate(
|
} = await templatecontrollerRendertemplate(
|
||||||
{ name: 'product.sku' },
|
{ name: 'product.sku' },
|
||||||
{
|
{
|
||||||
category: formValues.category,
|
brand: brandName || '',
|
||||||
attributes: [
|
strength: strengthName || '',
|
||||||
{
|
flavor: flavorName || '',
|
||||||
dict: {name: "brand"},
|
humidity: humidityName ? capitalize(humidityName) : '',
|
||||||
shortName: brandName || '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dict: {name: "flavor"},
|
|
||||||
shortName: flavorName || '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dict: {name: "strength"},
|
|
||||||
shortName: strengthName || '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dict: {name: "humidity"},
|
|
||||||
shortName: humidityName ? capitalize(humidityName) : '',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|
@ -153,8 +137,8 @@ const CreateForm: React.FC<{
|
||||||
humidityName === 'dry'
|
humidityName === 'dry'
|
||||||
? 'Dry'
|
? 'Dry'
|
||||||
: humidityName === 'moisture'
|
: humidityName === 'moisture'
|
||||||
? 'Moisture'
|
? 'Moisture'
|
||||||
: capitalize(humidityName),
|
: capitalize(humidityName),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|
@ -216,23 +200,19 @@ const CreateForm: React.FC<{
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onFinish={async (values: any) => {
|
onFinish={async (values: any) => {
|
||||||
// 根据产品类型决定是否组装 attributes
|
// 组装 attributes(根据 activeAttributes 动态组装)
|
||||||
// 如果产品类型为 bundle,则 attributes 为空数组
|
const attributes = activeAttributes.flatMap((attr: any) => {
|
||||||
// 如果产品类型为 single,则根据 activeAttributes 动态组装 attributes
|
const dictName = attr.name;
|
||||||
const attributes = values.type === 'bundle'
|
const key = `${dictName}Values`;
|
||||||
? []
|
const vals = values[key];
|
||||||
: activeAttributes.flatMap((attr: any) => {
|
if (vals && Array.isArray(vals)) {
|
||||||
const dictName = attr.name;
|
return vals.map((v: string) => ({
|
||||||
const key = `${dictName}Values`;
|
dictName: dictName,
|
||||||
const vals = values[key];
|
name: v,
|
||||||
if (vals && Array.isArray(vals)) {
|
}));
|
||||||
return vals.map((v: string) => ({
|
}
|
||||||
dictName: dictName,
|
return [];
|
||||||
name: v,
|
});
|
||||||
}));
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
const payload: any = {
|
const payload: any = {
|
||||||
name: (values as any).name,
|
name: (values as any).name,
|
||||||
|
|
@ -266,7 +246,13 @@ const CreateForm: React.FC<{
|
||||||
placeholder="请输入SKU"
|
placeholder="请输入SKU"
|
||||||
rules={[{ required: true, message: '请输入SKU' }]}
|
rules={[{ required: true, message: '请输入SKU' }]}
|
||||||
/>
|
/>
|
||||||
|
<ProFormSelect
|
||||||
|
name="siteSkus"
|
||||||
|
label="站点 SKU 列表"
|
||||||
|
width="md"
|
||||||
|
mode="tags"
|
||||||
|
placeholder="输入站点 SKU,回车添加"
|
||||||
|
/>
|
||||||
<Button style={{ marginTop: '32px' }} onClick={handleGenerateSku}>
|
<Button style={{ marginTop: '32px' }} onClick={handleGenerateSku}>
|
||||||
自动生成
|
自动生成
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -279,14 +265,6 @@ const CreateForm: React.FC<{
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
<ProFormSelect
|
|
||||||
name="siteSkus"
|
|
||||||
initialValue={[]}
|
|
||||||
label="站点 SKU 列表"
|
|
||||||
width="md"
|
|
||||||
mode="tags"
|
|
||||||
placeholder="输入站点 SKU,回车添加"
|
|
||||||
/>
|
|
||||||
<ProForm.Group>
|
<ProForm.Group>
|
||||||
<ProFormText
|
<ProFormText
|
||||||
name="name"
|
name="name"
|
||||||
|
|
@ -335,18 +313,15 @@ const CreateForm: React.FC<{
|
||||||
rules={[{ required: true, message: '请输入子产品SKU' }]}
|
rules={[{ required: true, message: '请输入子产品SKU' }]}
|
||||||
request={async ({ keyWords }) => {
|
request={async ({ keyWords }) => {
|
||||||
const params = keyWords
|
const params = keyWords
|
||||||
? { sku: keyWords, name: keyWords, type: 'single' }
|
? { sku: keyWords, name: keyWords }
|
||||||
: { pageSize: 9999, type: 'single' };
|
: { pageSize: 9999 };
|
||||||
const { data } = await productcontrollerGetproductlist(
|
const { data } = await getStocks(params as any);
|
||||||
params as any,
|
|
||||||
);
|
|
||||||
if (!data || !data.items) {
|
if (!data || !data.items) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
// 只返回类型为单品的产品
|
|
||||||
return data.items
|
return data.items
|
||||||
.filter((item: any) => item.type === 'single' && item.sku)
|
.filter((item) => item.sku)
|
||||||
.map((item: any) => ({
|
.map((item) => ({
|
||||||
label: `${item.sku} - ${item.name}`,
|
label: `${item.sku} - ${item.name}`,
|
||||||
value: item.sku,
|
value: item.sku,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import {
|
||||||
productcontrollerGetcategoriesall,
|
productcontrollerGetcategoriesall,
|
||||||
productcontrollerGetcategoryattributes,
|
productcontrollerGetcategoryattributes,
|
||||||
productcontrollerGetproductcomponents,
|
productcontrollerGetproductcomponents,
|
||||||
productcontrollerGetproductlist,
|
|
||||||
productcontrollerGetproductsiteskus,
|
productcontrollerGetproductsiteskus,
|
||||||
productcontrollerUpdateproduct,
|
productcontrollerUpdateproduct,
|
||||||
} from '@/servers/api/product';
|
} from '@/servers/api/product';
|
||||||
|
|
@ -316,17 +315,17 @@ const EditForm: React.FC<{
|
||||||
<ProForm.Group>
|
<ProForm.Group>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="sku"
|
name="sku"
|
||||||
label="单品SKU"
|
label="库存SKU"
|
||||||
width="md"
|
width="md"
|
||||||
showSearch
|
showSearch
|
||||||
debounceTime={300}
|
debounceTime={300}
|
||||||
placeholder="请输入单品SKU"
|
placeholder="请输入库存SKU"
|
||||||
rules={[{ required: true, message: '请输入单品SKU' }]}
|
rules={[{ required: true, message: '请输入库存SKU' }]}
|
||||||
request={async ({ keyWords }) => {
|
request={async ({ keyWords }) => {
|
||||||
const params = keyWords
|
const params = keyWords
|
||||||
? { where: {sku: keyWords, name: keyWords, type: 'single'} }
|
? { sku: keyWords, name: keyWords }
|
||||||
: { 'per_page': 9999 , where: {type: 'single'} };
|
: { pageSize: 9999 };
|
||||||
const { data } = await productcontrollerGetproductlist(params);
|
const { data } = await getStocks(params as any);
|
||||||
if (!data || !data.items) {
|
if (!data || !data.items) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
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) => {
|
|
||||||
console.log(`values`,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}`,
|
|
||||||
}));
|
|
||||||
console.log(`data`,data)
|
|
||||||
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,53 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { ProTable, ProColumns } from "@ant-design/pro-components";
|
|
||||||
|
|
||||||
interface ProductComponentListProps {
|
|
||||||
record: API.Product;
|
|
||||||
columns: ProColumns<API.Product>[];
|
|
||||||
dataSource?: API.Product[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProductComponentList: React.FC<ProductComponentListProps> = ({ record, columns, dataSource }) => {
|
|
||||||
if (record.type !== "bundle" || !record.components || record.components.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const componentSkus = record.components.map(component => component.sku);
|
|
||||||
|
|
||||||
const includedProducts = [];
|
|
||||||
|
|
||||||
if (dataSource) {
|
|
||||||
includedProducts = dataSource
|
|
||||||
.filter(product => product.type === "single" && componentSkus.includes(product.sku));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (includedProducts.length === 0) {
|
|
||||||
return (
|
|
||||||
<div style={{ padding: "16px", textAlign: "center", color: "#999" }}>
|
|
||||||
未找到包含的单品信息
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const componentColumns = columns.filter(col =>
|
|
||||||
[200~cd ../api"option", "siteSkus", "category", "type"].includes(col.dataIndex as string)
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ padding: "8px 16px", backgroundColor: "#fafafa" }}>
|
|
||||||
<ProTable
|
|
||||||
dataSource={includedProducts}
|
|
||||||
columns={componentColumns}
|
|
||||||
pagination={false}
|
|
||||||
rowKey="id"
|
|
||||||
bordered
|
|
||||||
size="small"
|
|
||||||
scroll={{ x: "max-content" }}
|
|
||||||
headerTitle={null}
|
|
||||||
toolBarRender={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProductComponentList;
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import {
|
import {
|
||||||
productcontrollerBatchdeleteproduct,
|
productcontrollerBatchdeleteproduct,
|
||||||
productcontrollerBatchupdateproduct,
|
productcontrollerBatchupdateproduct,
|
||||||
|
productcontrollerBindproductsiteskus,
|
||||||
productcontrollerDeleteproduct,
|
productcontrollerDeleteproduct,
|
||||||
productcontrollerGetcategoriesall,
|
productcontrollerGetcategoriesall,
|
||||||
productcontrollerGetproductcomponents,
|
productcontrollerGetproductcomponents,
|
||||||
productcontrollerGetproductlist,
|
productcontrollerGetproductlist,
|
||||||
productcontrollerUpdatenamecn
|
productcontrollerUpdatenamecn,
|
||||||
} from '@/servers/api/product';
|
} from '@/servers/api/product';
|
||||||
|
import { sitecontrollerAll } from '@/servers/api/site';
|
||||||
|
import { siteapicontrollerGetproducts } from '@/servers/api/siteApi';
|
||||||
import {
|
import {
|
||||||
ActionType,
|
ActionType,
|
||||||
ModalForm,
|
ModalForm,
|
||||||
|
|
@ -14,14 +17,13 @@ import {
|
||||||
ProColumns,
|
ProColumns,
|
||||||
ProFormSelect,
|
ProFormSelect,
|
||||||
ProFormText,
|
ProFormText,
|
||||||
ProTable
|
ProTable,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd';
|
import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import CreateForm from './CreateForm';
|
import CreateForm from './CreateForm';
|
||||||
import EditForm from './EditForm';
|
import EditForm from './EditForm';
|
||||||
import SyncToSiteModal from './SyncToSiteModal';
|
|
||||||
|
|
||||||
const NameCn: React.FC<{
|
const NameCn: React.FC<{
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -67,7 +69,16 @@ const AttributesCell: React.FC<{ record: any }> = ({ record }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ComponentsCell: React.FC<{ components?: any[] }> = ({ components }) => {
|
const ComponentsCell: React.FC<{ productId: number }> = ({ productId }) => {
|
||||||
|
const [components, setComponents] = React.useState<any[]>([]);
|
||||||
|
React.useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const { data = [] } = await productcontrollerGetproductcomponents({
|
||||||
|
id: productId,
|
||||||
|
});
|
||||||
|
setComponents(data || []);
|
||||||
|
})();
|
||||||
|
}, [productId]);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{components && components.length ? (
|
{components && components.length ? (
|
||||||
|
|
@ -154,25 +165,251 @@ const BatchEditModal: React.FC<{
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const ProductList = ({ filter, columns }: { filter: { skus: string[] }, columns: any[] }) => {
|
|
||||||
|
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 (
|
return (
|
||||||
<ProTable
|
<ProTable
|
||||||
request={async (pag) => {
|
headerTitle="站点产品信息"
|
||||||
const { data, success } = await productcontrollerGetproductlist({
|
actionRef={actionRef}
|
||||||
where: filter
|
search={false}
|
||||||
});
|
options={false}
|
||||||
if (!success) return [];
|
|
||||||
return data || [];
|
|
||||||
}}
|
|
||||||
columns={columns}
|
|
||||||
pagination={false}
|
pagination={false}
|
||||||
rowKey="id"
|
toolBarRender={() => [
|
||||||
bordered
|
<Button
|
||||||
size="small"
|
key="refresh"
|
||||||
scroll={{ x: "max-content" }}
|
type="primary"
|
||||||
headerTitle={null}
|
onClick={() => actionRef.current?.reload()}
|
||||||
toolBarRender={false}
|
>
|
||||||
|
刷新
|
||||||
|
</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>,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -182,8 +419,8 @@ const List: React.FC = () => {
|
||||||
// 状态:存储当前选中的行
|
// 状态:存储当前选中的行
|
||||||
const [selectedRows, setSelectedRows] = React.useState<API.Product[]>([]);
|
const [selectedRows, setSelectedRows] = React.useState<API.Product[]>([]);
|
||||||
const [batchEditModalVisible, setBatchEditModalVisible] = useState(false);
|
const [batchEditModalVisible, setBatchEditModalVisible] = useState(false);
|
||||||
const [syncProducts, setSyncProducts] = useState<API.Product[]>([]);
|
|
||||||
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
||||||
|
const [syncProductIds, setSyncProductIds] = useState<number[]>([]);
|
||||||
|
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
// 导出产品 CSV(带认证请求)
|
// 导出产品 CSV(带认证请求)
|
||||||
|
|
@ -217,13 +454,13 @@ const List: React.FC = () => {
|
||||||
sorter: true,
|
sorter: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '关联商品',
|
title: '商品SKU',
|
||||||
dataIndex: 'siteSkus',
|
dataIndex: 'siteSkus',
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<>
|
<>
|
||||||
{record.siteSkus?.map((siteSku, index) => (
|
{record.siteSkus?.map((siteSku, index) => (
|
||||||
<Tag key={index} color="cyan">
|
<Tag key={index} color="cyan">
|
||||||
{siteSku}
|
{siteSku.siteSku}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|
@ -244,7 +481,13 @@ const List: React.FC = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '商品类型',
|
||||||
|
dataIndex: 'category',
|
||||||
|
render: (_, record: any) => {
|
||||||
|
return record.category?.title || record.category?.name || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '价格',
|
title: '价格',
|
||||||
dataIndex: 'price',
|
dataIndex: 'price',
|
||||||
|
|
@ -256,13 +499,6 @@ const List: React.FC = () => {
|
||||||
dataIndex: 'promotionPrice',
|
dataIndex: 'promotionPrice',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
sorter: true,
|
sorter: true,
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '商品类型',
|
|
||||||
dataIndex: 'category',
|
|
||||||
render: (_, record: any) => {
|
|
||||||
return record.category?.title || record.category?.name || '-';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '属性',
|
title: '属性',
|
||||||
|
|
@ -295,7 +531,7 @@ const List: React.FC = () => {
|
||||||
title: '构成',
|
title: '构成',
|
||||||
dataIndex: 'components',
|
dataIndex: 'components',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
render: (_, record) => <ComponentsCell components={record.components} />,
|
render: (_, record) => <ComponentsCell productId={(record as any).id} />,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -328,7 +564,7 @@ const List: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSyncProducts([record]);
|
setSyncProductIds([record.id]);
|
||||||
setSyncModalVisible(true);
|
setSyncModalVisible(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -443,7 +679,6 @@ const List: React.FC = () => {
|
||||||
onError?.(error);
|
onError?.(error);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
>
|
>
|
||||||
<Button>批量导入</Button>
|
<Button>批量导入</Button>
|
||||||
</Upload>,
|
</Upload>,
|
||||||
|
|
@ -458,7 +693,7 @@ const List: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
disabled={selectedRows.length <= 0}
|
disabled={selectedRows.length <= 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSyncProducts(selectedRows);
|
setSyncProductIds(selectedRows.map((row) => row.id));
|
||||||
setSyncModalVisible(true);
|
setSyncModalVisible(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -506,12 +741,9 @@ const List: React.FC = () => {
|
||||||
sortField = field;
|
sortField = field;
|
||||||
sortOrder = sort[field];
|
sortOrder = sort[field];
|
||||||
}
|
}
|
||||||
const { current, pageSize, ...where } = params
|
|
||||||
console.log(`params`, params)
|
|
||||||
const { data, success } = await productcontrollerGetproductlist({
|
const { data, success } = await productcontrollerGetproductlist({
|
||||||
where,
|
...params,
|
||||||
page: current || 1,
|
|
||||||
per_page: pageSize || 10,
|
|
||||||
sortField,
|
sortField,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
} as any);
|
} as any);
|
||||||
|
|
@ -522,18 +754,17 @@ const List: React.FC = () => {
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
// expandable={{
|
expandable={{
|
||||||
// expandedRowRender: (record) => {
|
expandedRowRender: (record) => (
|
||||||
// return <ProductList filter={{
|
<WpProductInfo
|
||||||
// skus: record.components?.map(component => component.sku) || [],
|
skus={(record.siteSkus as string[]) || []}
|
||||||
// }}
|
record={record}
|
||||||
// columns={columns}
|
parentTableRef={actionRef}
|
||||||
// ></ProductList>
|
/>
|
||||||
// }
|
),
|
||||||
// ,
|
rowExpandable: (record) =>
|
||||||
// rowExpandable: (record) =>
|
!!(record.siteSkus && record.siteSkus.length > 0),
|
||||||
// !!(record.type==='bundle'),
|
}}
|
||||||
// }}
|
|
||||||
editable={{
|
editable={{
|
||||||
type: 'single',
|
type: 'single',
|
||||||
onSave: async (key, record, originRow) => {
|
onSave: async (key, record, originRow) => {
|
||||||
|
|
@ -543,11 +774,6 @@ const List: React.FC = () => {
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
onChange: (_, selectedRows) => setSelectedRows(selectedRows),
|
onChange: (_, selectedRows) => setSelectedRows(selectedRows),
|
||||||
}}
|
}}
|
||||||
pagination={{
|
|
||||||
showSizeChanger: true,
|
|
||||||
showQuickJumper: true,
|
|
||||||
pageSizeOptions: ['10', '20', '50', '100', '1000', '2000'],
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<BatchEditModal
|
<BatchEditModal
|
||||||
visible={batchEditModalVisible}
|
visible={batchEditModalVisible}
|
||||||
|
|
@ -562,7 +788,8 @@ const List: React.FC = () => {
|
||||||
<SyncToSiteModal
|
<SyncToSiteModal
|
||||||
visible={syncModalVisible}
|
visible={syncModalVisible}
|
||||||
onClose={() => setSyncModalVisible(false)}
|
onClose={() => setSyncModalVisible(false)}
|
||||||
products={syncProducts}
|
productIds={syncProductIds}
|
||||||
|
productRows={selectedRows}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
setSyncModalVisible(false);
|
setSyncModalVisible(false);
|
||||||
setSelectedRows([]);
|
setSelectedRows([]);
|
||||||
|
|
@ -573,5 +800,4 @@ const List: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default List;
|
export default List;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import {
|
||||||
productcontrollerGetcategoryattributes,
|
productcontrollerGetcategoryattributes,
|
||||||
productcontrollerGetproductlist,
|
productcontrollerGetproductlist,
|
||||||
} from '@/servers/api/product';
|
} from '@/servers/api/product';
|
||||||
import { DownloadOutlined } from '@ant-design/icons';
|
|
||||||
import {
|
import {
|
||||||
ActionType,
|
ActionType,
|
||||||
PageContainer,
|
PageContainer,
|
||||||
|
|
@ -202,92 +201,6 @@ const PermutationPage: React.FC = () => {
|
||||||
setCreateModalVisible(true);
|
setCreateModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理导出CSV功能
|
|
||||||
const handleExport = () => {
|
|
||||||
try {
|
|
||||||
// 如果没有数据则提示用户
|
|
||||||
if (permutations.length === 0) {
|
|
||||||
message.warning('暂无数据可导出');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成CSV表头(包含所有属性列和SKU列)
|
|
||||||
const headers = [
|
|
||||||
...attributes.map((attr) => attr.title || attr.name),
|
|
||||||
'SKU',
|
|
||||||
'状态',
|
|
||||||
];
|
|
||||||
|
|
||||||
// 生成CSV数据行
|
|
||||||
const rows = permutations.map((perm) => {
|
|
||||||
const key = generateKeyFromPermutation(perm);
|
|
||||||
const product = existingProducts.get(key);
|
|
||||||
|
|
||||||
// 获取每个属性值
|
|
||||||
const attrValues = attributes.map((attr) => {
|
|
||||||
const value = perm[attr.name]?.name || '';
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取SKU和状态
|
|
||||||
const sku = product?.sku || '';
|
|
||||||
const status = product ? '已存在' : '未创建';
|
|
||||||
|
|
||||||
return [...attrValues, sku, status];
|
|
||||||
});
|
|
||||||
|
|
||||||
// 将表头和数据行合并
|
|
||||||
const csvContent = [headers, ...rows]
|
|
||||||
.map((row) =>
|
|
||||||
// 处理CSV中的特殊字符(逗号、双引号、换行符)
|
|
||||||
row
|
|
||||||
.map((cell) => {
|
|
||||||
const cellStr = String(cell || '');
|
|
||||||
// 如果包含逗号、双引号或换行符,需要用双引号包裹,并将内部的双引号转义
|
|
||||||
if (
|
|
||||||
cellStr.includes(',') ||
|
|
||||||
cellStr.includes('"') ||
|
|
||||||
cellStr.includes('\n')
|
|
||||||
) {
|
|
||||||
return `"${cellStr.replace(/"/g, '""')}"`;
|
|
||||||
}
|
|
||||||
return cellStr;
|
|
||||||
})
|
|
||||||
.join(','),
|
|
||||||
)
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
// 添加BOM以支持Excel正确显示中文
|
|
||||||
const BOM = '\uFEFF';
|
|
||||||
const blob = new Blob([BOM + csvContent], {
|
|
||||||
type: 'text/csv;charset=utf-8;',
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建下载链接并触发下载
|
|
||||||
const link = document.createElement('a');
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
link.setAttribute('href', url);
|
|
||||||
|
|
||||||
// 生成文件名(包含当前分类名称和日期)
|
|
||||||
const category = categories.find((c) => c.id === categoryId);
|
|
||||||
const categoryName = category?.name || '产品';
|
|
||||||
const date = new Date().toISOString().slice(0, 10);
|
|
||||||
link.setAttribute('download', `${categoryName}_排列组合_${date}.csv`);
|
|
||||||
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
|
|
||||||
// 清理
|
|
||||||
document.body.removeChild(link);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
|
|
||||||
message.success('导出成功');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('导出失败:', error);
|
|
||||||
message.error('导出失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns: any[] = [
|
const columns: any[] = [
|
||||||
...attributes.map((attr) => ({
|
...attributes.map((attr) => ({
|
||||||
title: attr.title || attr.name,
|
title: attr.title || attr.name,
|
||||||
|
|
@ -400,21 +313,11 @@ const PermutationPage: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
defaultPageSize: 50,
|
defaultPageSize: 50,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
|
||||||
pageSizeOptions: ['50', '100', '200', '500', '1000', '2000'],
|
pageSizeOptions: ['50', '100', '200', '500', '1000', '2000'],
|
||||||
}}
|
}}
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
search={false}
|
search={false}
|
||||||
toolBarRender={() => [
|
toolBarRender={false}
|
||||||
<Button
|
|
||||||
key="export"
|
|
||||||
type="default"
|
|
||||||
icon={<DownloadOutlined />}
|
|
||||||
onClick={handleExport}
|
|
||||||
>
|
|
||||||
导出列表
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ProCard>
|
</ProCard>
|
||||||
|
|
|
||||||
|
|
@ -1,461 +0,0 @@
|
||||||
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,11 +1,13 @@
|
||||||
import { showBatchOperationResult } from '@/utils/showResult';
|
import { productcontrollerGetproductlist } from '@/servers/api/product';
|
||||||
import {
|
import { templatecontrollerGettemplatebyname } from '@/servers/api/template';
|
||||||
productcontrollerBatchsynctosite,
|
|
||||||
productcontrollerGetproductlist,
|
|
||||||
productcontrollerSynctosite,
|
|
||||||
} from '@/servers/api/product';
|
|
||||||
import { EditOutlined, SyncOutlined } from '@ant-design/icons';
|
import { EditOutlined, SyncOutlined } from '@ant-design/icons';
|
||||||
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
import {
|
||||||
|
ActionType,
|
||||||
|
ModalForm,
|
||||||
|
ProColumns,
|
||||||
|
ProFormText,
|
||||||
|
ProTable,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -19,35 +21,39 @@ import {
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import EditForm from '../List/EditForm';
|
import EditForm from '../List/EditForm';
|
||||||
import SiteProductCell from './SiteProductCell';
|
|
||||||
|
|
||||||
// 定义站点接口
|
// 定义站点接口
|
||||||
interface Site {
|
interface Site {
|
||||||
id: number;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
skuPrefix?: string;
|
skuPrefix?: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义本地产品接口(与后端 Product 实体匹配)
|
// 定义WordPress商品接口
|
||||||
interface SiteProduct {
|
interface WpProduct {
|
||||||
id: number;
|
id?: number;
|
||||||
|
externalProductId?: string;
|
||||||
sku: string;
|
sku: string;
|
||||||
name: string;
|
name: string;
|
||||||
nameCn: string;
|
price: string;
|
||||||
shortDescription?: string;
|
regular_price?: string;
|
||||||
description?: string;
|
sale_price?: string;
|
||||||
price: number;
|
stock_quantity: number;
|
||||||
promotionPrice: number;
|
stockQuantity?: number;
|
||||||
type: string;
|
status: string;
|
||||||
categoryId?: number;
|
|
||||||
category?: any;
|
|
||||||
attributes?: any[];
|
attributes?: any[];
|
||||||
components?: any[];
|
constitution?: { sku: string; quantity: number }[];
|
||||||
siteSkus: string[];
|
}
|
||||||
source: number;
|
|
||||||
createdAt: Date;
|
// 扩展本地产品接口,包含对应的 WP 产品信息
|
||||||
updatedAt: Date;
|
interface ProductWithWP extends API.Product {
|
||||||
|
wpProducts: Record<string, WpProduct>;
|
||||||
|
attributes?: any[];
|
||||||
|
siteSkus?: Array<{
|
||||||
|
siteSku: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义API响应接口
|
// 定义API响应接口
|
||||||
|
|
@ -73,9 +79,19 @@ const getSites = async (): Promise<ApiResponse<Site>> => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getWPProducts = async (): Promise<ApiResponse<WpProduct>> => {
|
||||||
|
return request('/product/wp-products', {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const ProductSyncPage: React.FC = () => {
|
const ProductSyncPage: React.FC = () => {
|
||||||
const [sites, setSites] = useState<Site[]>([]);
|
const [sites, setSites] = useState<Site[]>([]);
|
||||||
|
// 存储所有 WP 产品,用于查找匹配。 Key: SKU (包含前缀)
|
||||||
|
const [wpProductMap, setWpProductMap] = useState<Map<string, WpProduct>>(
|
||||||
|
new Map(),
|
||||||
|
);
|
||||||
|
const [skuTemplate, setSkuTemplate] = useState<string>('');
|
||||||
const [initialLoading, setInitialLoading] = useState(true);
|
const [initialLoading, setInitialLoading] = useState(true);
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
const [selectedSiteId, setSelectedSiteId] = useState<string>('');
|
const [selectedSiteId, setSelectedSiteId] = useState<string>('');
|
||||||
|
|
@ -88,46 +104,102 @@ const ProductSyncPage: React.FC = () => {
|
||||||
errors: string[];
|
errors: string[];
|
||||||
}>({ success: 0, failed: 0, errors: [] });
|
}>({ success: 0, failed: 0, errors: [] });
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [selectedRows, setSelectedRows] = useState<SiteProduct[]>([]);
|
const [selectedRows, setSelectedRows] = useState<ProductWithWP[]>([]);
|
||||||
// 初始化加载站点列表
|
|
||||||
|
// 初始化数据:获取站点和所有 WP 产品
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
// 获取站点列表
|
setInitialLoading(true);
|
||||||
const sitesRes = await getSites();
|
// 获取所有站点
|
||||||
if (sitesRes.success && sitesRes.data.length > 0) {
|
const sitesResponse = await getSites();
|
||||||
setSites(sitesRes.data);
|
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.');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('初始化数据失败:', error);
|
message.error('获取基础数据失败,请重试');
|
||||||
message.error('初始化数据失败');
|
console.error('Error fetching data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setInitialLoading(false);
|
setInitialLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
initializeData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 同步产品到站点
|
||||||
const syncProductToSite = async (
|
const syncProductToSite = async (
|
||||||
values: any,
|
values: any,
|
||||||
record: SiteProduct,
|
record: ProductWithWP,
|
||||||
site: Site,
|
site: Site,
|
||||||
siteProductId?: string,
|
wpProductId?: string,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const hide = message.loading('正在同步...', 0);
|
const hide = message.loading('正在同步...', 0);
|
||||||
|
const data = {
|
||||||
|
name: record.name,
|
||||||
|
sku: values.sku,
|
||||||
|
regular_price: record.price?.toString(),
|
||||||
|
sale_price: record.promotionPrice?.toString(),
|
||||||
|
type: record.type === 'bundle' ? 'simple' : record.type,
|
||||||
|
description: record.description,
|
||||||
|
status: 'publish',
|
||||||
|
stock_status: 'instock',
|
||||||
|
manage_stock: false,
|
||||||
|
};
|
||||||
|
|
||||||
// 使用 productcontrollerSynctosite API 同步产品到站点
|
let res;
|
||||||
const res = await productcontrollerSynctosite({
|
if (wpProductId) {
|
||||||
productId: Number(record.id),
|
res = await request(`/site-api/${site.id}/products/${wpProductId}`, {
|
||||||
siteId: Number(site.id),
|
method: 'PUT',
|
||||||
} as any);
|
data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res = await request(`/site-api/${site.id}/products`, {
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('res', res);
|
||||||
if (!res.success) {
|
if (!res.success) {
|
||||||
hide();
|
hide();
|
||||||
throw new Error(res.message || '同步失败');
|
throw new Error(res.message || '同步失败');
|
||||||
}
|
}
|
||||||
|
// 更新本地缓存 Map,避免刷新
|
||||||
|
setWpProductMap((prev) => {
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
if (res.data && typeof res.data === 'object') {
|
||||||
|
newMap.set(values.sku, res.data as WpProduct);
|
||||||
|
}
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
|
||||||
hide();
|
hide();
|
||||||
message.success('同步成功');
|
message.success('同步成功');
|
||||||
|
|
@ -135,11 +207,12 @@ const ProductSyncPage: React.FC = () => {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error('同步失败: ' + (error.message || error.toString()));
|
message.error('同步失败: ' + (error.message || error.toString()));
|
||||||
return false;
|
return false;
|
||||||
|
} finally {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 批量同步产品到指定站点
|
// 批量同步产品到指定站点
|
||||||
const batchSyncProducts = async (productsToSync?: SiteProduct[]) => {
|
const batchSyncProducts = async (productsToSync?: ProductWithWP[]) => {
|
||||||
if (!selectedSiteId) {
|
if (!selectedSiteId) {
|
||||||
message.error('请选择要同步到的站点');
|
message.error('请选择要同步到的站点');
|
||||||
return;
|
return;
|
||||||
|
|
@ -166,7 +239,7 @@ const ProductSyncPage: React.FC = () => {
|
||||||
message.error('获取产品列表失败');
|
message.error('获取产品列表失败');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
products = data.items as SiteProduct[];
|
products = data.items as ProductWithWP[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取产品列表失败');
|
message.error('获取产品列表失败');
|
||||||
return;
|
return;
|
||||||
|
|
@ -177,44 +250,108 @@ const ProductSyncPage: React.FC = () => {
|
||||||
setSyncProgress(0);
|
setSyncProgress(0);
|
||||||
setSyncResults({ success: 0, failed: 0, errors: [] });
|
setSyncResults({ success: 0, failed: 0, errors: [] });
|
||||||
|
|
||||||
|
const totalProducts = products.length;
|
||||||
|
let processed = 0;
|
||||||
|
let successCount = 0;
|
||||||
|
let failedCount = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用 productcontrollerBatchsynctosite API 批量同步
|
// 逐个同步产品
|
||||||
const productIds = products.map((product) => Number(product.id));
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 更新进度为50%,表示正在处理
|
// 如果没有找到实际的siteSku,则根据模板生成
|
||||||
setSyncProgress(50);
|
const expectedSku =
|
||||||
|
siteProductSku ||
|
||||||
|
(skuTemplate
|
||||||
|
? renderSiteSku(skuTemplate, { site: targetSite, product })
|
||||||
|
: `${targetSite.skuPrefix || ''}-${product.sku}`);
|
||||||
|
|
||||||
const res = await productcontrollerBatchsynctosite({
|
// 检查是否已存在
|
||||||
productIds: productIds,
|
const existingProduct = wpProductMap.get(expectedSku);
|
||||||
siteId: Number(targetSite.id),
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
if (res.success) {
|
// 准备同步数据
|
||||||
const syncedCount = res.data?.synced || 0;
|
const syncData = {
|
||||||
const errors = res.data?.errors || [];
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
// 更新进度为100%,表示完成
|
let res;
|
||||||
setSyncProgress(100);
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setSyncResults({
|
console.log('res', res);
|
||||||
success: syncedCount,
|
|
||||||
failed: errors.length,
|
|
||||||
errors: errors.map((err: any) => err.error || '未知错误'),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errors.length === 0) {
|
if (res.success) {
|
||||||
message.success(`批量同步完成,成功同步 ${syncedCount} 个产品`);
|
successCount++;
|
||||||
} else {
|
// 更新本地缓存
|
||||||
message.warning(
|
setWpProductMap((prev) => {
|
||||||
`批量同步完成,成功 ${syncedCount} 个,失败 ${errors.length} 个`,
|
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 || '未知错误'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新表格
|
processed++;
|
||||||
actionRef.current?.reload();
|
setSyncProgress(Math.round((processed / totalProducts) * 100));
|
||||||
} else {
|
|
||||||
throw new Error(res.message || '批量同步失败');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSyncResults({ success: successCount, failed: failedCount, errors });
|
||||||
|
|
||||||
|
if (failedCount === 0) {
|
||||||
|
message.success(`批量同步完成,成功同步 ${successCount} 个产品`);
|
||||||
|
} else {
|
||||||
|
message.warning(
|
||||||
|
`批量同步完成,成功 ${successCount} 个,失败 ${failedCount} 个`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新表格
|
||||||
|
actionRef.current?.reload();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error('批量同步失败: ' + (error.message || error.toString()));
|
message.error('批量同步失败: ' + (error.message || error.toString()));
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -222,9 +359,27 @@ 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<Site>[] => {
|
const generateColumns = (): ProColumns<ProductWithWP>[] => {
|
||||||
const columns: ProColumns<SiteProduct>[] = [
|
const columns: ProColumns<ProductWithWP>[] = [
|
||||||
{
|
{
|
||||||
title: 'SKU',
|
title: 'SKU',
|
||||||
dataIndex: 'sku',
|
dataIndex: 'sku',
|
||||||
|
|
@ -316,21 +471,137 @@ const ProductSyncPage: React.FC = () => {
|
||||||
|
|
||||||
// 为每个站点生成列
|
// 为每个站点生成列
|
||||||
sites.forEach((site: Site) => {
|
sites.forEach((site: Site) => {
|
||||||
const siteColumn: ProColumns<SiteProduct> = {
|
const siteColumn: ProColumns<ProductWithWP> = {
|
||||||
title: site.name,
|
title: site.name,
|
||||||
key: `site_${site.id}`,
|
key: `site_${site.id}`,
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
width: 220,
|
width: 220,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
|
// 首先查找该产品在该站点的实际SKU
|
||||||
|
let siteProductSku = '';
|
||||||
|
if (record.siteSkus && record.siteSkus.length > 0) {
|
||||||
|
// 根据站点名称匹配对应的siteSku
|
||||||
|
const siteSkuInfo = record.siteSkus.find((sku: any) => {
|
||||||
|
// 这里假设可以根据站点名称或其他标识来匹配
|
||||||
|
// 如果需要更精确的匹配逻辑,可以根据实际需求调整
|
||||||
|
return (
|
||||||
|
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 (
|
return (
|
||||||
<SiteProductCell
|
<div style={{ fontSize: 12 }}>
|
||||||
product={record}
|
<div
|
||||||
site={site}
|
style={{
|
||||||
onSyncSuccess={() => {
|
display: 'flex',
|
||||||
// 同步成功后刷新表格
|
justifyContent: 'space-between',
|
||||||
actionRef.current?.reload();
|
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>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -353,7 +624,7 @@ const ProductSyncPage: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title="商品同步状态" className="product-sync-card">
|
<Card title="商品同步状态" className="product-sync-card">
|
||||||
<ProTable<SiteProduct>
|
<ProTable<ProductWithWP>
|
||||||
columns={generateColumns()}
|
columns={generateColumns()}
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
|
@ -394,27 +665,26 @@ const ProductSyncPage: React.FC = () => {
|
||||||
]}
|
]}
|
||||||
request={async (params, sort, filter) => {
|
request={async (params, sort, filter) => {
|
||||||
// 调用本地获取产品列表 API
|
// 调用本地获取产品列表 API
|
||||||
const response = await productcontrollerGetproductlist({
|
const { data, success } = await productcontrollerGetproductlist({
|
||||||
...params,
|
...params,
|
||||||
current: params.current,
|
current: params.current,
|
||||||
pageSize: params.pageSize,
|
pageSize: params.pageSize,
|
||||||
// 传递搜索参数
|
// 传递搜索参数
|
||||||
// keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段
|
keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段
|
||||||
sku: (params as any).sku,
|
sku: (params as any).sku,
|
||||||
name: (params as any).name,
|
name: (params as any).name,
|
||||||
} as any);
|
} as any);
|
||||||
console.log('result', response);
|
|
||||||
// 返回给 ProTable
|
// 返回给 ProTable
|
||||||
return {
|
return {
|
||||||
data: response.data?.items || [],
|
data: (data?.items || []) as ProductWithWP[],
|
||||||
success: response.success,
|
success,
|
||||||
total: response.data?.total || 0,
|
total: data?.total || 0,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
|
||||||
}}
|
}}
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
search={{
|
search={{
|
||||||
|
|
@ -438,7 +708,7 @@ const ProductSyncPage: React.FC = () => {
|
||||||
>
|
>
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<p>
|
<p>
|
||||||
目标站点:
|
目标站点:
|
||||||
<strong>{sites.find((s) => s.id === selectedSiteId)?.name}</strong>
|
<strong>{sites.find((s) => s.id === selectedSiteId)?.name}</strong>
|
||||||
</p>
|
</p>
|
||||||
{selectedRows.length > 0 ? (
|
{selectedRows.length > 0 ? (
|
||||||
|
|
@ -452,17 +722,17 @@ const ProductSyncPage: React.FC = () => {
|
||||||
|
|
||||||
{syncing && (
|
{syncing && (
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<div style={{ marginBottom: 8 }}>同步进度:</div>
|
<div style={{ marginBottom: 8 }}>同步进度:</div>
|
||||||
<Progress percent={syncProgress} status="active" />
|
<Progress percent={syncProgress} status="active" />
|
||||||
<div style={{ marginTop: 8, fontSize: 12, color: '#666' }}>
|
<div style={{ marginTop: 8, fontSize: 12, color: '#666' }}>
|
||||||
成功:{syncResults.success} | 失败:{syncResults.failed}
|
成功:{syncResults.success} | 失败:{syncResults.failed}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{syncResults.errors.length > 0 && (
|
{syncResults.errors.length > 0 && (
|
||||||
<div style={{ marginBottom: 16, maxHeight: 200, overflow: 'auto' }}>
|
<div style={{ marginBottom: 16, maxHeight: 200, overflow: 'auto' }}>
|
||||||
<div style={{ marginBottom: 8, color: '#ff4d4f' }}>错误详情:</div>
|
<div style={{ marginBottom: 8, color: '#ff4d4f' }}>错误详情:</div>
|
||||||
{syncResults.errors.slice(0, 10).map((error, index) => (
|
{syncResults.errors.slice(0, 10).map((error, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ordercontrollerSyncorders } from '@/servers/api/order';
|
import { ordercontrollerSyncorder } from '@/servers/api/order';
|
||||||
import {
|
import {
|
||||||
sitecontrollerCreate,
|
sitecontrollerCreate,
|
||||||
sitecontrollerDisable,
|
sitecontrollerDisable,
|
||||||
|
|
@ -6,13 +6,10 @@ import {
|
||||||
sitecontrollerUpdate,
|
sitecontrollerUpdate,
|
||||||
} from '@/servers/api/site';
|
} from '@/servers/api/site';
|
||||||
import { subscriptioncontrollerSync } from '@/servers/api/subscription';
|
import { subscriptioncontrollerSync } from '@/servers/api/subscription';
|
||||||
import { stockcontrollerGetallstockpoints } from '@/servers/api/stock';
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
import { ActionType, ProColumns, ProTable, DrawerForm, ProFormSelect, ProFormSwitch } from '@ant-design/pro-components';
|
import { Button, message, notification, Popconfirm, Space, Tag } from 'antd';
|
||||||
import { Button, message, notification, Popconfirm, Space, Tag, Form } from 'antd';
|
import React, { useRef, useState } from 'react';
|
||||||
import React, { useRef, useState, useEffect } from 'react';
|
|
||||||
import EditSiteForm from '../Shop/EditSiteForm'; // 引入重构后的表单组件
|
import EditSiteForm from '../Shop/EditSiteForm'; // 引入重构后的表单组件
|
||||||
import * as countries from 'i18n-iso-countries';
|
|
||||||
import zhCN from 'i18n-iso-countries/langs/zh';
|
|
||||||
|
|
||||||
// 区域数据项类型
|
// 区域数据项类型
|
||||||
interface AreaItem {
|
interface AreaItem {
|
||||||
|
|
@ -45,24 +42,29 @@ const SiteList: React.FC = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<SiteItem | null>(null);
|
const [editing, setEditing] = useState<SiteItem | null>(null);
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [batchEditOpen, setBatchEditOpen] = useState(false);
|
|
||||||
const [batchEditForm] = Form.useForm();
|
|
||||||
countries.registerLocale(zhCN);
|
|
||||||
|
|
||||||
|
|
||||||
const handleSync = async (ids: number[]) => {
|
const handleSync = async (ids: number[]) => {
|
||||||
if (!ids.length) return;
|
if (!ids.length) return;
|
||||||
const hide = message.loading('正在同步...', 0);
|
const hide = message.loading('正在同步...', 0);
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
|
products: { success: 0, fail: 0 },
|
||||||
orders: { success: 0, fail: 0 },
|
orders: { success: 0, fail: 0 },
|
||||||
subscriptions: { success: 0, fail: 0 },
|
subscriptions: { success: 0, fail: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
|
// 同步产品
|
||||||
|
const prodRes = await wpproductcontrollerSyncproducts({ siteId: id });
|
||||||
|
if (prodRes.success) {
|
||||||
|
stats.products.success += 1;
|
||||||
|
} else {
|
||||||
|
stats.products.fail += 1;
|
||||||
|
}
|
||||||
|
|
||||||
// 同步订单
|
// 同步订单
|
||||||
const orderRes = await ordercontrollerSyncorders({ siteId: id });
|
const orderRes = await ordercontrollerSyncorder({ siteId: id });
|
||||||
if (orderRes.success) {
|
if (orderRes.success) {
|
||||||
stats.orders.success += 1;
|
stats.orders.success += 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -83,6 +85,9 @@ const SiteList: React.FC = () => {
|
||||||
message: '同步完成',
|
message: '同步完成',
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
|
<p>
|
||||||
|
产品: 成功 {stats.products.success}, 失败 {stats.products.fail}
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
订单: 成功 {stats.orders.success}, 失败 {stats.orders.fail}
|
订单: 成功 {stats.orders.success}, 失败 {stats.orders.fail}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -103,68 +108,6 @@ const SiteList: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取所有国家/地区的选项
|
|
||||||
const getCountryOptions = () => {
|
|
||||||
// 获取所有国家的 ISO 代码
|
|
||||||
const countryCodes = countries.getAlpha2Codes();
|
|
||||||
// 将国家代码转换为选项数组
|
|
||||||
return Object.keys(countryCodes).map((code) => ({
|
|
||||||
label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码
|
|
||||||
value: code,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理批量编辑提交
|
|
||||||
const handleBatchEditFinish = async (values: any) => {
|
|
||||||
if (!selectedRowKeys.length) return;
|
|
||||||
const hide = message.loading('正在批量更新...', 0);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 遍历所有选中的站点 ID
|
|
||||||
for (const id of selectedRowKeys) {
|
|
||||||
// 构建更新数据对象,只包含用户填写了值的字段
|
|
||||||
const updateData: any = {};
|
|
||||||
|
|
||||||
// 如果用户选择了区域,则更新区域
|
|
||||||
if (values.areas && values.areas.length > 0) {
|
|
||||||
updateData.areas = values.areas;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果用户选择了仓库,则更新仓库
|
|
||||||
if (values.stockPointIds && values.stockPointIds.length > 0) {
|
|
||||||
updateData.stockPointIds = values.stockPointIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果用户设置了禁用状态,则更新状态
|
|
||||||
if (values.isDisabled !== undefined) {
|
|
||||||
updateData.isDisabled = values.isDisabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有需要更新的字段,则调用更新接口
|
|
||||||
if (Object.keys(updateData).length > 0) {
|
|
||||||
await sitecontrollerUpdate({ id: String(id) }, updateData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hide();
|
|
||||||
message.success('批量更新成功');
|
|
||||||
setBatchEditOpen(false);
|
|
||||||
setSelectedRowKeys([]);
|
|
||||||
batchEditForm.resetFields();
|
|
||||||
actionRef.current?.reload();
|
|
||||||
} catch (error: any) {
|
|
||||||
hide();
|
|
||||||
message.error(error.message || '批量更新失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 当批量编辑弹窗打开时,重置表单
|
|
||||||
useEffect(() => {
|
|
||||||
if (batchEditOpen) {
|
|
||||||
batchEditForm.resetFields();
|
|
||||||
}
|
|
||||||
}, [batchEditOpen, batchEditForm]);
|
|
||||||
|
|
||||||
// 表格列定义
|
// 表格列定义
|
||||||
const columns: ProColumns<SiteItem>[] = [
|
const columns: ProColumns<SiteItem>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -188,47 +131,26 @@ const SiteList: React.FC = () => {
|
||||||
</a>
|
</a>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ title: 'webhook地址', dataIndex: 'webhookUrl', width: 280, hideInSearch: true },
|
|
||||||
|
|
||||||
{
|
{
|
||||||
title: 'SKU 前缀',
|
title: 'SKU 前缀',
|
||||||
dataIndex: 'skuPrefix',
|
dataIndex: 'skuPrefix',
|
||||||
|
width: 160,
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '平台',
|
title: '平台',
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
|
width: 140,
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
request: async () => [
|
request: async () => [
|
||||||
{ label: 'WooCommerce', value: 'woocommerce' },
|
{ label: 'WooCommerce', value: 'woocommerce' },
|
||||||
{ label: 'Shopyy', value: 'shopyy' },
|
{ label: 'Shopyy', value: 'shopyy' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// 地区列配置
|
|
||||||
title: "地区",
|
|
||||||
dataIndex: "areas",
|
|
||||||
hideInSearch: true,
|
|
||||||
render: (_, row) => {
|
|
||||||
// 如果没有关联地区,显示"全局"标签
|
|
||||||
if (!row.areas || row.areas.length === 0) {
|
|
||||||
return <Tag color="default">全局</Tag>;
|
|
||||||
}
|
|
||||||
// 遍历显示所有关联的地区名称
|
|
||||||
return (
|
|
||||||
<Space wrap>
|
|
||||||
{row.areas.map((area) => (
|
|
||||||
<Tag color="geekblue" key={area.code}>
|
|
||||||
{area.name}
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '关联仓库',
|
title: '关联仓库',
|
||||||
dataIndex: 'stockPoints',
|
dataIndex: 'stockPoints',
|
||||||
|
width: 200,
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
render: (_, row) => {
|
render: (_, row) => {
|
||||||
if (!row.stockPoints || row.stockPoints.length === 0) {
|
if (!row.stockPoints || row.stockPoints.length === 0) {
|
||||||
|
|
@ -310,10 +232,10 @@ const SiteList: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
const { current, pageSize, name, type } = params;
|
const { current, pageSize, name, type } = params;
|
||||||
const resp = await sitecontrollerList({
|
const resp = await sitecontrollerList({
|
||||||
current,
|
current,
|
||||||
pageSize,
|
pageSize,
|
||||||
keyword: name || undefined,
|
keyword: name || undefined,
|
||||||
type: type || undefined,
|
type: type || undefined,
|
||||||
});
|
});
|
||||||
// 假设 resp 直接就是后端返回的结构,包含 items 和 total
|
// 假设 resp 直接就是后端返回的结构,包含 items 和 total
|
||||||
return {
|
return {
|
||||||
|
|
@ -357,11 +279,6 @@ const SiteList: React.FC = () => {
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
onChange: setSelectedRowKeys,
|
onChange: setSelectedRowKeys,
|
||||||
}}
|
}}
|
||||||
pagination={{
|
|
||||||
defaultPageSize: 20,
|
|
||||||
showSizeChanger: true,
|
|
||||||
showQuickJumper: true,
|
|
||||||
}}
|
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|
@ -372,12 +289,6 @@ const SiteList: React.FC = () => {
|
||||||
>
|
>
|
||||||
新建站点
|
新建站点
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button
|
|
||||||
disabled={!selectedRowKeys.length}
|
|
||||||
onClick={() => setBatchEditOpen(true)}
|
|
||||||
>
|
|
||||||
批量编辑
|
|
||||||
</Button>,
|
|
||||||
<Button
|
<Button
|
||||||
disabled={!selectedRowKeys.length}
|
disabled={!selectedRowKeys.length}
|
||||||
onClick={() => handleSync(selectedRowKeys as number[])}
|
onClick={() => handleSync(selectedRowKeys as number[])}
|
||||||
|
|
@ -399,51 +310,6 @@ const SiteList: React.FC = () => {
|
||||||
isEdit={!!editing}
|
isEdit={!!editing}
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 批量编辑弹窗 */}
|
|
||||||
<DrawerForm
|
|
||||||
title={`批量编辑站点 (${selectedRowKeys.length} 个)`}
|
|
||||||
form={batchEditForm}
|
|
||||||
open={batchEditOpen}
|
|
||||||
onOpenChange={setBatchEditOpen}
|
|
||||||
onFinish={handleBatchEditFinish}
|
|
||||||
layout="vertical"
|
|
||||||
>
|
|
||||||
<ProFormSelect
|
|
||||||
name="areas"
|
|
||||||
label="区域"
|
|
||||||
mode="multiple"
|
|
||||||
placeholder="请选择区域(留空表示不修改)"
|
|
||||||
showSearch
|
|
||||||
filterOption={(input, option) =>
|
|
||||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
|
||||||
}
|
|
||||||
options={getCountryOptions()}
|
|
||||||
/>
|
|
||||||
<ProFormSelect
|
|
||||||
name="stockPointIds"
|
|
||||||
label="关联仓库"
|
|
||||||
mode="multiple"
|
|
||||||
placeholder="请选择关联仓库(留空表示不修改)"
|
|
||||||
request={async () => {
|
|
||||||
// 从后端接口获取仓库数据
|
|
||||||
const res = await stockcontrollerGetallstockpoints();
|
|
||||||
// 使用可选链和空值合并运算符来安全地处理可能未定义的数据
|
|
||||||
return (
|
|
||||||
res?.data?.map((sp: any) => ({ label: sp.name, value: sp.id })) ??
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ProFormSwitch
|
|
||||||
name="isDisabled"
|
|
||||||
label="是否禁用"
|
|
||||||
fieldProps={{
|
|
||||||
checkedChildren: '是',
|
|
||||||
unCheckedChildren: '否',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DrawerForm>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -281,7 +281,7 @@ const CustomerPage: React.FC = () => {
|
||||||
const response = await request(`/site-api/${siteId}/customers`, {
|
const response = await request(`/site-api/${siteId}/customers`, {
|
||||||
params: {
|
params: {
|
||||||
page: current,
|
page: current,
|
||||||
per_page: pageSize,
|
page_size: pageSize,
|
||||||
where,
|
where,
|
||||||
...(orderObj ? { order: orderObj } : {}),
|
...(orderObj ? { order: orderObj } : {}),
|
||||||
...(name || email ? { search: name || email } : {}),
|
...(name || email ? { search: name || email } : {}),
|
||||||
|
|
@ -465,7 +465,7 @@ const CustomerPage: React.FC = () => {
|
||||||
<ProTable
|
<ProTable
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
search={false}
|
search={false}
|
||||||
pagination={{ pageSize: 20 ,showSizeChanger: true, showQuickJumper: true,}}
|
pagination={{ pageSize: 20 }}
|
||||||
columns={[
|
columns={[
|
||||||
{ title: '订单号', dataIndex: 'number', copyable: true },
|
{ title: '订单号', dataIndex: 'number', copyable: true },
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ import {
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import * as countries from 'i18n-iso-countries';
|
|
||||||
import zhCN from 'i18n-iso-countries/langs/zh';
|
|
||||||
|
|
||||||
// 定义组件的 props 类型
|
// 定义组件的 props 类型
|
||||||
interface EditSiteFormProps {
|
interface EditSiteFormProps {
|
||||||
|
|
@ -31,9 +29,6 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
// 初始化中文语言包
|
|
||||||
countries.registerLocale(zhCN);
|
|
||||||
|
|
||||||
// 当 initialValues 或 open 状态变化时, 更新表单的值
|
// 当 initialValues 或 open 状态变化时, 更新表单的值
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 如果抽屉是打开的
|
// 如果抽屉是打开的
|
||||||
|
|
@ -41,10 +36,8 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
||||||
// 如果是编辑模式并且有初始值
|
// 如果是编辑模式并且有初始值
|
||||||
if (isEdit && initialValues) {
|
if (isEdit && initialValues) {
|
||||||
// 编辑模式下, 设置表单值为初始值
|
// 编辑模式下, 设置表单值为初始值
|
||||||
const { token, consumerKey, consumerSecret, ...safeInitialValues } = initialValues;
|
|
||||||
// 清空敏感字段, 让用户输入最新的数据
|
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...safeInitialValues,
|
...initialValues,
|
||||||
isDisabled: initialValues.isDisabled === 1, // 将后端的 1/0 转换成 true/false
|
isDisabled: initialValues.isDisabled === 1, // 将后端的 1/0 转换成 true/false
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -54,17 +47,6 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
||||||
}
|
}
|
||||||
}, [initialValues, isEdit, open, form]);
|
}, [initialValues, isEdit, open, form]);
|
||||||
|
|
||||||
// 获取所有国家/地区的选项
|
|
||||||
const getCountryOptions = () => {
|
|
||||||
// 获取所有国家的 ISO 代码
|
|
||||||
const countryCodes = countries.getAlpha2Codes();
|
|
||||||
// 将国家代码转换为选项数组
|
|
||||||
return Object.keys(countryCodes).map((code) => ({
|
|
||||||
label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码
|
|
||||||
value: code,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DrawerForm
|
<DrawerForm
|
||||||
title={isEdit ? '编辑站点' : '新建站点'}
|
title={isEdit ? '编辑站点' : '新建站点'}
|
||||||
|
|
@ -100,11 +82,6 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
||||||
label="网站地址"
|
label="网站地址"
|
||||||
placeholder="请输入网站地址"
|
placeholder="请输入网站地址"
|
||||||
/>
|
/>
|
||||||
<ProFormText
|
|
||||||
name="webhookUrl"
|
|
||||||
label="Webhook 地址"
|
|
||||||
placeholder="请输入 Webhook 地址"
|
|
||||||
/>
|
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="type"
|
name="type"
|
||||||
label="平台"
|
label="平台"
|
||||||
|
|
@ -169,12 +146,15 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
||||||
label="区域"
|
label="区域"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="请选择区域"
|
placeholder="请选择区域"
|
||||||
|
request={async () => {
|
||||||
showSearch
|
// 从后端接口获取区域数据
|
||||||
filterOption={(input, option) =>
|
const res = await areacontrollerGetarealist({ pageSize: 1000 });
|
||||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
// areacontrollerGetarealist 直接返回数组, 所以不需要 .data.list
|
||||||
}
|
return res.map((area: any) => ({
|
||||||
options={getCountryOptions()}
|
label: area.name,
|
||||||
|
value: area.code,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="stockPointIds"
|
name="stockPointIds"
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ const ShopLayout: React.FC = () => {
|
||||||
<Select
|
<Select
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
placeholder="请选择店铺"
|
placeholder="请选择店铺"
|
||||||
options={sites?.map?.((site) => ({
|
options={sites.map((site) => ({
|
||||||
label: site.name,
|
label: site.name,
|
||||||
value: site.id,
|
value: site.id,
|
||||||
}))}
|
}))}
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ const MediaPage: React.FC = () => {
|
||||||
const response = await request(`/site-api/${siteId}/media`, {
|
const response = await request(`/site-api/${siteId}/media`, {
|
||||||
params: {
|
params: {
|
||||||
page: current,
|
page: current,
|
||||||
per_page: pageSize,
|
page_size: pageSize,
|
||||||
...(orderObj ? { order: orderObj } : {}),
|
...(orderObj ? { order: orderObj } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import {
|
||||||
CreateOrder,
|
CreateOrder,
|
||||||
EditOrder,
|
EditOrder,
|
||||||
OrderNote,
|
OrderNote,
|
||||||
ShipOrderForm,
|
|
||||||
} from '../components/Order/Forms';
|
} from '../components/Order/Forms';
|
||||||
|
|
||||||
const OrdersPage: React.FC = () => {
|
const OrdersPage: React.FC = () => {
|
||||||
|
|
@ -62,14 +61,11 @@ const OrdersPage: React.FC = () => {
|
||||||
return [{ key: 'all', label: `全部(${total})` }, ...tabs];
|
return [{ key: 'all', label: `全部(${total})` }, ...tabs];
|
||||||
}, [count]);
|
}, [count]);
|
||||||
|
|
||||||
const columns: ProColumns<API.UnifiedOrderDTO>[] = [
|
const columns: ProColumns<API.Order>[] = [
|
||||||
{
|
{
|
||||||
title: '订单ID',
|
title: '订单号',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
},
|
hideInSearch: true,
|
||||||
{
|
|
||||||
title:'订单号',
|
|
||||||
dataIndex: 'number',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
|
|
@ -77,6 +73,12 @@ const OrdersPage: React.FC = () => {
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
valueEnum: ORDER_STATUS_ENUM,
|
valueEnum: ORDER_STATUS_ENUM,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '订单日期',
|
||||||
|
dataIndex: 'date_created',
|
||||||
|
hideInSearch: true,
|
||||||
|
valueType: 'dateTime',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '金额',
|
title: '金额',
|
||||||
dataIndex: 'total',
|
dataIndex: 'total',
|
||||||
|
|
@ -87,38 +89,6 @@ const OrdersPage: React.FC = () => {
|
||||||
dataIndex: 'currency',
|
dataIndex: 'currency',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '财务状态',
|
|
||||||
dataIndex: 'financial_status',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '支付方式',
|
|
||||||
dataIndex: 'payment_method',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '支付时间',
|
|
||||||
dataIndex: 'date_paid',
|
|
||||||
hideInSearch: true,
|
|
||||||
valueType: 'dateTime',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建时间',
|
|
||||||
dataIndex: 'date_created',
|
|
||||||
hideInSearch: true,
|
|
||||||
valueType: 'dateTime',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '更新时间',
|
|
||||||
dataIndex: 'date_modified',
|
|
||||||
hideInSearch: true,
|
|
||||||
valueType: 'dateTime',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: '客户ID',
|
|
||||||
dataIndex: 'customer_id',
|
|
||||||
hideInSearch: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '客户邮箱',
|
title: '客户邮箱',
|
||||||
dataIndex: 'email',
|
dataIndex: 'email',
|
||||||
|
|
@ -126,28 +96,6 @@ const OrdersPage: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: '客户姓名',
|
title: '客户姓名',
|
||||||
dataIndex: 'customer_name',
|
dataIndex: 'customer_name',
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '客户IP',
|
|
||||||
dataIndex: 'customer_ip_address',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '联系电话',
|
|
||||||
render: (_, record) => record.shipping?.phone || record.billing?.phone,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '设备类型',
|
|
||||||
dataIndex: 'device_type',
|
|
||||||
hideInSearch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '来源类型',
|
|
||||||
dataIndex: 'source_type',
|
|
||||||
hideInSearch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'UTM来源',
|
|
||||||
dataIndex: 'utm_source',
|
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -163,9 +111,7 @@ const OrdersPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{record.line_items.map((item: any) => (
|
{record.line_items.map((item: any) => (
|
||||||
<div
|
<div key={item.id}>{`${item.name} x ${item.quantity}`}</div>
|
||||||
key={item.id}
|
|
||||||
>{`${item.name}(${item.sku}) x ${item.quantity}`}</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -174,6 +120,15 @@ const OrdersPage: React.FC = () => {
|
||||||
return '-';
|
return '-';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '支付方式',
|
||||||
|
dataIndex: 'payment_method',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '联系电话',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record) => record.shipping?.phone || record.billing?.phone,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '账单地址',
|
title: '账单地址',
|
||||||
dataIndex: 'billing_full_address',
|
dataIndex: 'billing_full_address',
|
||||||
|
|
@ -189,66 +144,6 @@ const OrdersPage: React.FC = () => {
|
||||||
width: 200,
|
width: 200,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
copyable: true,
|
copyable: true,
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '发货状态',
|
|
||||||
dataIndex: 'fulfillment_status',
|
|
||||||
// hideInSearch: true,
|
|
||||||
// render: (_, record) => {
|
|
||||||
// const fulfillmentStatus = record.fulfillment_status;
|
|
||||||
// const fulfillmentStatusMap: Record<string, string> = {
|
|
||||||
// '0': '未发货',
|
|
||||||
// '1': '部分发货',
|
|
||||||
// '2': '已发货',
|
|
||||||
// '3': '已取消',
|
|
||||||
// '4': '确认发货',
|
|
||||||
// };
|
|
||||||
// if (fulfillmentStatus === undefined || fulfillmentStatus === null) {
|
|
||||||
// return '-';
|
|
||||||
// }
|
|
||||||
// return (
|
|
||||||
// fulfillmentStatusMap[String(fulfillmentStatus)] ||
|
|
||||||
// String(fulfillmentStatus)
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '物流',
|
|
||||||
dataIndex: 'fulfillments',
|
|
||||||
hideInSearch: true,
|
|
||||||
render: (_, record) => {
|
|
||||||
// 检查是否有物流信息
|
|
||||||
if (
|
|
||||||
!record.fulfillments ||
|
|
||||||
!Array.isArray(record.fulfillments) ||
|
|
||||||
record.fulfillments.length === 0
|
|
||||||
) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
// 遍历物流信息数组, 显示每个物流的提供商和单号
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{record.fulfillments.map((item: any, index: number) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
style={{ display: 'flex', flexDirection: 'column' }}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{item.tracking_provider
|
|
||||||
? `快递方式: ${item.tracking_provider}`
|
|
||||||
: ''}
|
|
||||||
</span>
|
|
||||||
{item.tracking_number
|
|
||||||
? `物流单号: ${item.tracking_number}`
|
|
||||||
: ''}
|
|
||||||
<span>
|
|
||||||
{item.date_shipped ? `发货日期: ${item.date_shipped}` : ''}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
|
|
@ -291,17 +186,28 @@ const OrdersPage: React.FC = () => {
|
||||||
>
|
>
|
||||||
<Button type="text" icon={<EllipsisOutlined />} />
|
<Button type="text" icon={<EllipsisOutlined />} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<ShipOrderForm
|
<Button
|
||||||
orderId={record.id as number}
|
type="link"
|
||||||
tableRef={actionRef}
|
title="发货"
|
||||||
siteId={siteId}
|
onClick={async () => {
|
||||||
orderItems={(record as any).line_items?.map((item: any) => ({
|
try {
|
||||||
id: item.id,
|
const res = await request(
|
||||||
name: item.name,
|
`/site-api/${siteId}/orders/${record.id}/ship`,
|
||||||
quantity: item.quantity,
|
{ method: 'POST' },
|
||||||
sku: item.sku,
|
);
|
||||||
}))}
|
if (res.success) {
|
||||||
/>
|
message.success('发货成功');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '发货失败');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
message.error('发货失败');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
发货
|
||||||
|
</Button>
|
||||||
{record.status === 'completed' && (
|
{record.status === 'completed' && (
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="确定取消发货?"
|
title="确定取消发货?"
|
||||||
|
|
@ -373,7 +279,6 @@ const OrdersPage: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSizeOptions: ['10', '20', '50', '100', '1000'],
|
pageSizeOptions: ['10', '20', '50', '100', '1000'],
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
|
||||||
defaultPageSize: 10,
|
defaultPageSize: 10,
|
||||||
}}
|
}}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
|
|
@ -384,8 +289,6 @@ const OrdersPage: React.FC = () => {
|
||||||
setSelectedRowKeys={setSelectedRowKeys}
|
setSelectedRowKeys={setSelectedRowKeys}
|
||||||
siteId={siteId}
|
siteId={siteId}
|
||||||
/>,
|
/>,
|
||||||
<Button disabled>批量发货</Button>
|
|
||||||
,
|
|
||||||
<Button
|
<Button
|
||||||
title="批量删除"
|
title="批量删除"
|
||||||
danger
|
danger
|
||||||
|
|
@ -466,7 +369,18 @@ const OrdersPage: React.FC = () => {
|
||||||
</ModalForm>,
|
</ModalForm>,
|
||||||
]}
|
]}
|
||||||
request={async (params, sort, filter) => {
|
request={async (params, sort, filter) => {
|
||||||
const { current, pageSize, date, status, ...rest } = params;
|
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 where: Record<string, any> = { ...(filter || {}), ...rest };
|
const where: Record<string, any> = { ...(filter || {}), ...rest };
|
||||||
if (status && status !== 'all') {
|
if (status && status !== 'all') {
|
||||||
where.status = status;
|
where.status = status;
|
||||||
|
|
@ -487,9 +401,9 @@ const OrdersPage: React.FC = () => {
|
||||||
const response = await request(`/site-api/${siteId}/orders`, {
|
const response = await request(`/site-api/${siteId}/orders`, {
|
||||||
params: {
|
params: {
|
||||||
page: current,
|
page: current,
|
||||||
per_page: pageSize,
|
page_size: pageSize,
|
||||||
where,
|
where,
|
||||||
...(orderObj ? { orderBy: orderObj } : {}),
|
...(orderObj ? { order: orderObj } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -541,8 +455,12 @@ const OrdersPage: React.FC = () => {
|
||||||
return { status: key, count: 0 };
|
return { status: key, count: 0 };
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await request(`/site-api/${siteId}/orders/count`, {
|
const res = await request(`/site-api/${siteId}/orders`, {
|
||||||
params: { ...baseWhere, status: rawStatus },
|
params: {
|
||||||
|
page: 1,
|
||||||
|
per_page: 1,
|
||||||
|
where: { ...baseWhere, status: rawStatus },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const totalCount = Number(res?.data?.total || 0);
|
const totalCount = Number(res?.data?.total || 0);
|
||||||
return { status: key, count: totalCount };
|
return { status: key, count: totalCount };
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ const ProductsPage: React.FC = () => {
|
||||||
// ID
|
// ID
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
|
hideInSearch: true,
|
||||||
width: 120,
|
width: 120,
|
||||||
copyable: true,
|
copyable: true,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
|
|
@ -155,7 +156,7 @@ const ProductsPage: React.FC = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 库存
|
// 库存
|
||||||
title: '库存数量',
|
title: '库存',
|
||||||
dataIndex: 'stock_quantity',
|
dataIndex: 'stock_quantity',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
|
|
@ -393,7 +394,6 @@ const ProductsPage: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSizeOptions: ['10', '20', '50', '100', '1000', '2000'],
|
pageSizeOptions: ['10', '20', '50', '100', '1000', '2000'],
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
|
||||||
defaultPageSize: 10,
|
defaultPageSize: 10,
|
||||||
}}
|
}}
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
|
|
@ -422,7 +422,7 @@ const ProductsPage: React.FC = () => {
|
||||||
params: {
|
params: {
|
||||||
page,
|
page,
|
||||||
per_page: pageSize,
|
per_page: pageSize,
|
||||||
where,
|
...where,
|
||||||
...(orderObj
|
...(orderObj
|
||||||
? {
|
? {
|
||||||
sortField: Object.keys(orderObj)[0],
|
sortField: Object.keys(orderObj)[0],
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ const WebhooksPage: React.FC = () => {
|
||||||
message.error(isEditMode ? '更新失败' : '创建失败');
|
message.error(isEditMode ? '更新失败' : '创建失败');
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error('表单验证失败:' + error.message);
|
message.error('表单验证失败:' + error.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -305,12 +305,12 @@ const WebhooksPage: React.FC = () => {
|
||||||
{ type: 'url', message: '请输入有效的URL' },
|
{ type: 'url', message: '请输入有效的URL' },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入回调URL,如:https://example.com/webhook" />
|
<Input placeholder="请输入回调URL,如:https://example.com/webhook" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="secret"
|
name="secret"
|
||||||
label="密钥(可选)"
|
label="密钥(可选)"
|
||||||
rules={[{ max: 255, message: '密钥不能超过255个字符' }]}
|
rules={[{ max: 255, message: '密钥不能超过255个字符' }]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入密钥,用于验证webhook请求" />
|
<Input placeholder="请输入密钥,用于验证webhook请求" />
|
||||||
|
|
|
||||||
|
|
@ -47,133 +47,6 @@ const region = {
|
||||||
YT: 'Yukon',
|
YT: 'Yukon',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定义发货订单表单的数据类型
|
|
||||||
export interface ShipOrderFormData {
|
|
||||||
tracking_number?: string;
|
|
||||||
shipping_provider?: string;
|
|
||||||
shipping_method?: string;
|
|
||||||
items?: Array<{
|
|
||||||
id?: string;
|
|
||||||
quantity?: number;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发货订单表单组件
|
|
||||||
export const ShipOrderForm: React.FC<{
|
|
||||||
orderId: number;
|
|
||||||
tableRef?: React.MutableRefObject<ActionType | undefined>;
|
|
||||||
siteId?: string;
|
|
||||||
orderItems?: Array<{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
quantity: number;
|
|
||||||
sku?: string;
|
|
||||||
}>;
|
|
||||||
}> = ({ orderId, tableRef, siteId, orderItems }) => {
|
|
||||||
const { message } = App.useApp();
|
|
||||||
const formRef = useRef<ProFormInstance>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalForm
|
|
||||||
formRef={formRef}
|
|
||||||
title="发货订单"
|
|
||||||
width="600px"
|
|
||||||
modalProps={{ destroyOnHidden: true }}
|
|
||||||
trigger={
|
|
||||||
<Button type="link" title="发货">
|
|
||||||
发货
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
onFinish={async (values: ShipOrderFormData) => {
|
|
||||||
if (!siteId) {
|
|
||||||
message.error('缺少站点ID');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { success, message: errMsg } = await request(
|
|
||||||
`/site-api/${siteId}/orders/${orderId}/ship`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
data: values,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (success === false) {
|
|
||||||
throw new Error(errMsg || '发货失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
message.success('发货成功');
|
|
||||||
tableRef?.current?.reload();
|
|
||||||
return true;
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error?.message || '发货失败');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onFinishFailed={() => {
|
|
||||||
const element = document.querySelector('.ant-form-item-explain-error');
|
|
||||||
if (element) {
|
|
||||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ProFormText
|
|
||||||
name="tracking_number"
|
|
||||||
label="物流单号"
|
|
||||||
placeholder="请输入物流单号"
|
|
||||||
rules={[{ required: true, message: '请输入物流单号' }]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ProFormText
|
|
||||||
name="shipping_provider"
|
|
||||||
label="物流公司"
|
|
||||||
placeholder="请输入物流公司名称"
|
|
||||||
rules={[{ required: true, message: '请输入物流公司名称' }]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ProFormText
|
|
||||||
name="shipping_method"
|
|
||||||
label="发货方式"
|
|
||||||
placeholder="请输入发货方式"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{orderItems && orderItems.length > 0 && (
|
|
||||||
<ProFormList
|
|
||||||
label="发货商品项"
|
|
||||||
name="items"
|
|
||||||
tooltip="如果不选择,则默认发货所有商品"
|
|
||||||
>
|
|
||||||
<ProForm.Group>
|
|
||||||
<ProFormSelect
|
|
||||||
name="id"
|
|
||||||
label="商品"
|
|
||||||
placeholder="请选择商品"
|
|
||||||
options={orderItems.map((item) => ({
|
|
||||||
label: `${item.name} (SKU: ${item.sku || 'N/A'}) - 可发数量: ${
|
|
||||||
item.quantity
|
|
||||||
}`,
|
|
||||||
value: item.id,
|
|
||||||
}))}
|
|
||||||
rules={[{ required: true, message: '请选择商品' }]}
|
|
||||||
/>
|
|
||||||
<ProFormDigit
|
|
||||||
name="quantity"
|
|
||||||
label="发货数量"
|
|
||||||
placeholder="请输入发货数量"
|
|
||||||
rules={[{ required: true, message: '请输入发货数量' }]}
|
|
||||||
fieldProps={{
|
|
||||||
precision: 0,
|
|
||||||
min: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ProForm.Group>
|
|
||||||
</ProFormList>
|
|
||||||
)}
|
|
||||||
</ModalForm>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OrderNote: React.FC<{
|
export const OrderNote: React.FC<{
|
||||||
id: number;
|
id: number;
|
||||||
descRef?: React.MutableRefObject<ActionType | undefined>;
|
descRef?: React.MutableRefObject<ActionType | undefined>;
|
||||||
|
|
|
||||||
|
|
@ -69,12 +69,12 @@ export const ErpProductBindModal: React.FC<ErpProductBindModalProps> = ({
|
||||||
onFinish={handleBind}
|
onFinish={handleBind}
|
||||||
>
|
>
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<strong>站点产品信息:</strong>
|
<strong>站点产品信息:</strong>
|
||||||
<div>SKU: {siteProduct.sku}</div>
|
<div>SKU: {siteProduct.sku}</div>
|
||||||
<div>名称: {siteProduct.name}</div>
|
<div>名称: {siteProduct.name}</div>
|
||||||
{siteProduct.erpProduct && (
|
{siteProduct.erpProduct && (
|
||||||
<div style={{ color: '#ff4d4f' }}>
|
<div style={{ color: '#ff4d4f' }}>
|
||||||
⚠️ 当前已绑定ERP产品:{siteProduct.erpProduct.sku} -{' '}
|
⚠️ 当前已绑定ERP产品:{siteProduct.erpProduct.sku} -{' '}
|
||||||
{siteProduct.erpProduct.name}
|
{siteProduct.erpProduct.name}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -148,7 +148,7 @@ export const ErpProductBindModal: React.FC<ErpProductBindModalProps> = ({
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
}}
|
}}
|
||||||
toolBarRender={false}
|
toolBarRender={false}
|
||||||
options={false}
|
options={false}
|
||||||
|
|
@ -164,7 +164,7 @@ export const ErpProductBindModal: React.FC<ErpProductBindModalProps> = ({
|
||||||
border: '1px solid #b7eb8f',
|
border: '1px solid #b7eb8f',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<strong>已选择:</strong>
|
<strong>已选择:</strong>
|
||||||
<div>SKU: {selectedProduct.sku}</div>
|
<div>SKU: {selectedProduct.sku}</div>
|
||||||
<div>名称: {selectedProduct.name}</div>
|
<div>名称: {selectedProduct.name}</div>
|
||||||
{selectedProduct.nameCn && (
|
{selectedProduct.nameCn && (
|
||||||
|
|
|
||||||
|
|
@ -613,6 +613,18 @@ export const UpdateVaritation: React.FC<{
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ... SetComponent, BatchEditProducts, BatchDeleteProducts, ImportCsv ...
|
||||||
|
// I will keep them but comment out/disable parts that rely on old API if I can't easily fix them all.
|
||||||
|
// BatchEdit/Delete rely on old API.
|
||||||
|
// I'll comment out their usage in ProductsPage or just return null here.
|
||||||
|
// I'll keep them but they might break if used.
|
||||||
|
// Since I removed them from ProductsPage toolbar (Wait, I kept them in ProductsPage toolbar!), I should update them or remove them.
|
||||||
|
// I'll update BatchDelete to use new API (loop delete).
|
||||||
|
// BatchEdit? `wpproductcontrollerBatchUpdateProducts`.
|
||||||
|
// I don't have batch update in my new API.
|
||||||
|
// I'll remove BatchEdit from ProductsPage toolbar for now or implement batch update in Controller.
|
||||||
|
// I'll update BatchDelete.
|
||||||
|
|
||||||
export const BatchDeleteProducts: React.FC<{
|
export const BatchDeleteProducts: React.FC<{
|
||||||
tableRef: React.MutableRefObject<ActionType | undefined>;
|
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||||||
selectedRowKeys: React.Key[];
|
selectedRowKeys: React.Key[];
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { ORDER_STATUS_ENUM } from '@/constants';
|
||||||
import { AddTag } from '@/pages/Customer/List';
|
import { AddTag } from '@/pages/Customer/List';
|
||||||
import { customercontrollerDeltag } from '@/servers/api/customer';
|
import { customercontrollerDeltag } from '@/servers/api/customer';
|
||||||
import { sitecontrollerAll } from '@/servers/api/site';
|
import { sitecontrollerAll } from '@/servers/api/site';
|
||||||
import * as countries from 'i18n-iso-countries';
|
|
||||||
import {
|
import {
|
||||||
statisticscontrollerGetorderbydate,
|
statisticscontrollerGetorderbydate,
|
||||||
statisticscontrollerGetorderbyemail,
|
statisticscontrollerGetorderbyemail,
|
||||||
|
|
@ -23,8 +22,7 @@ import dayjs from 'dayjs';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
||||||
import zhCN from 'i18n-iso-countries/langs/zh';
|
|
||||||
countries.registerLocale(zhCN);
|
|
||||||
dayjs.extend(weekOfYear);
|
dayjs.extend(weekOfYear);
|
||||||
const highlightText = (text: string, keyword: string) => {
|
const highlightText = (text: string, keyword: string) => {
|
||||||
if (!keyword) return text;
|
if (!keyword) return text;
|
||||||
|
|
@ -40,17 +38,6 @@ const highlightText = (text: string, keyword: string) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取所有国家/地区的选项
|
|
||||||
const getCountryOptions = () => {
|
|
||||||
// 获取所有国家的 ISO 代码
|
|
||||||
const countryCodes = countries.getAlpha2Codes();
|
|
||||||
// 将国家代码转换为选项数组
|
|
||||||
return Object.keys(countryCodes).map((code) => ({
|
|
||||||
label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码
|
|
||||||
value: code,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const ListPage: React.FC = () => {
|
const ListPage: React.FC = () => {
|
||||||
const [xAxis, setXAxis] = useState([]);
|
const [xAxis, setXAxis] = useState([]);
|
||||||
const [series, setSeries] = useState<any[]>([]);
|
const [series, setSeries] = useState<any[]>([]);
|
||||||
|
|
@ -633,19 +620,6 @@ const ListPage: React.FC = () => {
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProFormSelect
|
|
||||||
name="country"
|
|
||||||
label="区域"
|
|
||||||
mode="multiple"
|
|
||||||
placeholder="请选择区域"
|
|
||||||
|
|
||||||
showSearch
|
|
||||||
filterOption={(input, option) =>
|
|
||||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
|
||||||
}
|
|
||||||
options={getCountryOptions()}
|
|
||||||
/>
|
|
||||||
{/* <ProFormSelect
|
{/* <ProFormSelect
|
||||||
label="类型"
|
label="类型"
|
||||||
name="purchaseType"
|
name="purchaseType"
|
||||||
|
|
|
||||||
|
|
@ -9,28 +9,18 @@ import {
|
||||||
PageContainer,
|
PageContainer,
|
||||||
ProColumns,
|
ProColumns,
|
||||||
ProTable,
|
ProTable,
|
||||||
ProForm,
|
|
||||||
ProFormSelect,
|
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { Space, Tag } from 'antd';
|
import { Space, Tag } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
import { HistoryOrder } from '../Order';
|
import { HistoryOrder } from '../Order';
|
||||||
import * as countries from 'i18n-iso-countries';
|
|
||||||
import zhCN from 'i18n-iso-countries/langs/zh';
|
|
||||||
countries.registerLocale(zhCN);
|
|
||||||
const ListPage: React.FC = () => {
|
const ListPage: React.FC = () => {
|
||||||
const [data, setData] = useState({});
|
const [data, setData] = useState({});
|
||||||
const initialValues = {
|
|
||||||
country: ['CA'],
|
useEffect(() => {
|
||||||
};
|
statisticscontrollerGetordersorce().then(({ data, success }) => {
|
||||||
function handleSubmit(values: typeof initialValues) {
|
|
||||||
statisticscontrollerGetordersorce({params: values}).then(({ data, success }) => {
|
|
||||||
if (success) setData(data);
|
if (success) setData(data);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
handleSubmit(initialValues)
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const option = useMemo(() => {
|
const option = useMemo(() => {
|
||||||
|
|
@ -49,11 +39,11 @@ const ListPage: React.FC = () => {
|
||||||
data: data?.inactiveRes?.map((v) => v.new_user_count)?.sort((_) => -1),
|
data: data?.inactiveRes?.map((v) => v.new_user_count)?.sort((_) => -1),
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
formatter: function (params) {
|
formatter: function (params) {
|
||||||
if (!params.value) return '';
|
if (!params.value) return '';
|
||||||
return Math.abs(params.value)
|
return Math.abs(params.value)
|
||||||
+ '\n'
|
+'\n'
|
||||||
+ Math.abs(data?.inactiveRes?.find((item) => item.order_month === params.name)?.new_user_total || 0);
|
+Math.abs(data?.inactiveRes?.find((item) => item.order_month === params.name)?.new_user_total || 0);
|
||||||
},
|
},
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
},
|
},
|
||||||
|
|
@ -69,11 +59,11 @@ const ListPage: React.FC = () => {
|
||||||
data: data?.inactiveRes?.map((v) => v.old_user_count)?.sort((_) => -1),
|
data: data?.inactiveRes?.map((v) => v.old_user_count)?.sort((_) => -1),
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
formatter: function (params) {
|
formatter: function (params) {
|
||||||
if (!params.value) return '';
|
if (!params.value) return '';
|
||||||
return Math.abs(params.value)
|
return Math.abs(params.value)
|
||||||
+ '\n'
|
+'\n'
|
||||||
+ Math.abs(data?.inactiveRes?.find((item) => item.order_month === params.name)?.old_user_total || 0);
|
+Math.abs(data?.inactiveRes?.find((item) => item.order_month === params.name)?.old_user_total || 0);
|
||||||
},
|
},
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
},
|
},
|
||||||
|
|
@ -93,11 +83,11 @@ const ListPage: React.FC = () => {
|
||||||
show: true,
|
show: true,
|
||||||
formatter: function (params) {
|
formatter: function (params) {
|
||||||
if (!params.value) return '';
|
if (!params.value) return '';
|
||||||
return Math.abs(params.value)
|
return Math.abs(params.value)
|
||||||
+ '\n' +
|
+'\n'+
|
||||||
+Math.abs(data?.res?.find((item) => item.order_month === params.name &&
|
+Math.abs(data?.res?.find((item) => item.order_month === params.name &&
|
||||||
item.first_order_month_group === v)?.total || 0);
|
item.first_order_month_group === v)?.total || 0);
|
||||||
},
|
},
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
},
|
},
|
||||||
data: xAxisData.map((month) => {
|
data: xAxisData.map((month) => {
|
||||||
|
|
@ -268,29 +258,9 @@ const ListPage: React.FC = () => {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer ghost>
|
<PageContainer ghost>
|
||||||
<ProForm
|
|
||||||
initialValues={initialValues}
|
|
||||||
layout="inline"
|
|
||||||
onFinish={handleSubmit}
|
|
||||||
>
|
|
||||||
|
|
||||||
<ProFormSelect
|
|
||||||
name="country"
|
|
||||||
label="区域"
|
|
||||||
mode="multiple"
|
|
||||||
placeholder="请选择区域"
|
|
||||||
showSearch
|
|
||||||
filterOption={(input, option) =>
|
|
||||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
|
||||||
}
|
|
||||||
options={getCountryOptions()}
|
|
||||||
/>
|
|
||||||
</ProForm>
|
|
||||||
<ReactECharts
|
<ReactECharts
|
||||||
option={option}
|
option={option}
|
||||||
style={{ height: 1050 }}
|
style={{ height: 1050 }}
|
||||||
|
|
@ -307,7 +277,6 @@ const ListPage: React.FC = () => {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{tableData?.length ? (
|
{tableData?.length ? (
|
||||||
<ProTable
|
<ProTable
|
||||||
search={false}
|
search={false}
|
||||||
|
|
@ -324,17 +293,4 @@ const ListPage: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 获取所有国家/地区的选项
|
|
||||||
const getCountryOptions = () => {
|
|
||||||
// 获取所有国家的 ISO 代码
|
|
||||||
const countryCodes = countries.getAlpha2Codes();
|
|
||||||
// 将国家代码转换为选项数组
|
|
||||||
|
|
||||||
return Object.keys(countryCodes).map((code) => ({
|
|
||||||
label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码
|
|
||||||
value: code,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
export default ListPage;
|
export default ListPage;
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,9 @@ import {
|
||||||
ProFormText,
|
ProFormText,
|
||||||
ProTable,
|
ProTable,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
|
import { request } from '@umijs/max';
|
||||||
import { App, Button, Divider, Popconfirm, Space, Tag } from 'antd';
|
import { App, Button, Divider, Popconfirm, Space, Tag } from 'antd';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import * as countries from 'i18n-iso-countries';
|
|
||||||
import zhCN from 'i18n-iso-countries/langs/zh';
|
|
||||||
|
|
||||||
// 初始化中文语言包
|
|
||||||
countries.registerLocale(zhCN);
|
|
||||||
|
|
||||||
// 区域数据项类型
|
// 区域数据项类型
|
||||||
interface AreaItem {
|
interface AreaItem {
|
||||||
|
|
@ -29,17 +25,6 @@ interface AreaItem {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有国家/地区的选项
|
|
||||||
const getCountryOptions = () => {
|
|
||||||
// 获取所有国家的 ISO 代码
|
|
||||||
const countryCodes = countries.getAlpha2Codes();
|
|
||||||
// 将国家代码转换为选项数组
|
|
||||||
return Object.keys(countryCodes).map((code) => ({
|
|
||||||
label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码
|
|
||||||
value: code,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const ListPage: React.FC = () => {
|
const ListPage: React.FC = () => {
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
@ -205,11 +190,23 @@ const CreateForm: React.FC<{
|
||||||
width="lg"
|
width="lg"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="留空表示全球"
|
placeholder="留空表示全球"
|
||||||
showSearch
|
request={async () => {
|
||||||
filterOption={(input, option) =>
|
try {
|
||||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
const resp = await request('/area', {
|
||||||
}
|
method: 'GET',
|
||||||
options={getCountryOptions()}
|
params: { pageSize: 1000 },
|
||||||
|
});
|
||||||
|
if (resp.success) {
|
||||||
|
return resp.data.list.map((area: AreaItem) => ({
|
||||||
|
label: area.name,
|
||||||
|
value: area.code,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
</DrawerForm>
|
</DrawerForm>
|
||||||
|
|
@ -292,11 +289,23 @@ const UpdateForm: React.FC<{
|
||||||
width="lg"
|
width="lg"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="留空表示全球"
|
placeholder="留空表示全球"
|
||||||
showSearch
|
request={async () => {
|
||||||
filterOption={(input, option) =>
|
try {
|
||||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
const resp = await request('/area', {
|
||||||
}
|
method: 'GET',
|
||||||
options={getCountryOptions()}
|
params: { pageSize: 1000 },
|
||||||
|
});
|
||||||
|
if (resp.success) {
|
||||||
|
return resp.data.list.map((area: AreaItem) => ({
|
||||||
|
label: area.name,
|
||||||
|
value: area.code,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
</DrawerForm>
|
</DrawerForm>
|
||||||
|
|
|
||||||
|
|
@ -161,10 +161,7 @@ const OrdersPage: React.FC = () => {
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
request={request}
|
request={request}
|
||||||
pagination={{
|
pagination={{ showSizeChanger: true }}
|
||||||
showSizeChanger: true,
|
|
||||||
showQuickJumper: true,
|
|
||||||
}}
|
|
||||||
search={{
|
search={{
|
||||||
labelWidth: 90,
|
labelWidth: 90,
|
||||||
span: 6,
|
span: 6,
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,13 @@ export async function categorycontrollerGetlist(
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /category/ */
|
/** 此处后端没有提供注释 POST /category/ */
|
||||||
export async function categorycontrollerCreate(
|
export async function categorycontrollerCreate(
|
||||||
body: API.CreateCategoryDTO,
|
body: Record<string, any>,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
return request<any>('/category/', {
|
return request<any>('/category/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'text/plain',
|
||||||
},
|
},
|
||||||
data: body,
|
data: body,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
|
|
@ -40,14 +40,14 @@ export async function categorycontrollerCreate(
|
||||||
export async function categorycontrollerUpdate(
|
export async function categorycontrollerUpdate(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
params: API.categorycontrollerUpdateParams,
|
params: API.categorycontrollerUpdateParams,
|
||||||
body: API.UpdateCategoryDTO,
|
body: Record<string, any>,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { id: param0, ...queryParams } = params;
|
const { id: param0, ...queryParams } = params;
|
||||||
return request<any>(`/category/${param0}`, {
|
return request<any>(`/category/${param0}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'text/plain',
|
||||||
},
|
},
|
||||||
params: { ...queryParams },
|
params: { ...queryParams },
|
||||||
data: body,
|
data: body,
|
||||||
|
|
|
||||||
|
|
@ -2,68 +2,6 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import { request } from 'umi';
|
import { request } from 'umi';
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /customer/ */
|
|
||||||
export async function customercontrollerCreatecustomer(
|
|
||||||
body: API.CreateCustomerDTO,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<API.GetCustomerDTO>('/customer/', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /customer/${param0} */
|
|
||||||
export async function customercontrollerGetcustomerbyid(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.customercontrollerGetcustomerbyidParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const { id: param0, ...queryParams } = params;
|
|
||||||
return request<API.GetCustomerDTO>(`/customer/${param0}`, {
|
|
||||||
method: 'GET',
|
|
||||||
params: { ...queryParams },
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /customer/${param0} */
|
|
||||||
export async function customercontrollerUpdatecustomer(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.customercontrollerUpdatecustomerParams,
|
|
||||||
body: API.UpdateCustomerDTO,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const { id: param0, ...queryParams } = params;
|
|
||||||
return request<API.GetCustomerDTO>(`/customer/${param0}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
params: { ...queryParams },
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 DELETE /customer/${param0} */
|
|
||||||
export async function customercontrollerDeletecustomer(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.customercontrollerDeletecustomerParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const { id: param0, ...queryParams } = params;
|
|
||||||
return request<Record<string, any>>(`/customer/${param0}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
params: { ...queryParams },
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /customer/addtag */
|
/** 此处后端没有提供注释 POST /customer/addtag */
|
||||||
export async function customercontrollerAddtag(
|
export async function customercontrollerAddtag(
|
||||||
body: API.CustomerTagDTO,
|
body: API.CustomerTagDTO,
|
||||||
|
|
@ -79,51 +17,6 @@ export async function customercontrollerAddtag(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /customer/batch */
|
|
||||||
export async function customercontrollerBatchupdatecustomers(
|
|
||||||
body: API.BatchUpdateCustomerDTO,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<Record<string, any>>('/customer/batch', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /customer/batch */
|
|
||||||
export async function customercontrollerBatchcreatecustomers(
|
|
||||||
body: API.BatchCreateCustomerDTO,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<Record<string, any>>('/customer/batch', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 DELETE /customer/batch */
|
|
||||||
export async function customercontrollerBatchdeletecustomers(
|
|
||||||
body: API.BatchDeleteCustomerDTO,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<Record<string, any>>('/customer/batch', {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /customer/deltag */
|
/** 此处后端没有提供注释 POST /customer/deltag */
|
||||||
export async function customercontrollerDeltag(
|
export async function customercontrollerDeltag(
|
||||||
body: API.CustomerTagDTO,
|
body: API.CustomerTagDTO,
|
||||||
|
|
@ -139,6 +32,36 @@ export async function customercontrollerDeltag(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /customer/getcustomerlist */
|
||||||
|
export async function customercontrollerGetcustomerlist(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.customercontrollerGetcustomerlistParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<Record<string, any>>('/customer/getcustomerlist', {
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /customer/getcustomerstatisticlist */
|
||||||
|
export async function customercontrollerGetcustomerstatisticlist(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.customercontrollerGetcustomerstatisticlistParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<Record<string, any>>('/customer/getcustomerstatisticlist', {
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /customer/gettags */
|
/** 此处后端没有提供注释 GET /customer/gettags */
|
||||||
export async function customercontrollerGettags(options?: {
|
export async function customercontrollerGettags(options?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
@ -149,16 +72,6 @@ export async function customercontrollerGettags(options?: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /customer/list */
|
|
||||||
export async function customercontrollerGetcustomerlist(options?: {
|
|
||||||
[key: string]: any;
|
|
||||||
}) {
|
|
||||||
return request<API.ApiResponse>('/customer/list', {
|
|
||||||
method: 'GET',
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /customer/setrate */
|
/** 此处后端没有提供注释 POST /customer/setrate */
|
||||||
export async function customercontrollerSetrate(
|
export async function customercontrollerSetrate(
|
||||||
body: Record<string, any>,
|
body: Record<string, any>,
|
||||||
|
|
@ -174,25 +87,15 @@ export async function customercontrollerSetrate(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /customer/statistic/list */
|
|
||||||
export async function customercontrollerGetcustomerstatisticlist(options?: {
|
|
||||||
[key: string]: any;
|
|
||||||
}) {
|
|
||||||
return request<Record<string, any>>('/customer/statistic/list', {
|
|
||||||
method: 'GET',
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /customer/sync */
|
/** 此处后端没有提供注释 POST /customer/sync */
|
||||||
export async function customercontrollerSynccustomers(
|
export async function customercontrollerSynccustomers(
|
||||||
body: API.SyncCustomersDTO,
|
body: Record<string, any>,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
return request<Record<string, any>>('/customer/sync', {
|
return request<Record<string, any>>('/customer/sync', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'text/plain',
|
||||||
},
|
},
|
||||||
data: body,
|
data: body,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
|
|
|
||||||
|
|
@ -151,27 +151,12 @@ export async function dictcontrollerDeletedictitem(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /dict/item/export */
|
|
||||||
export async function dictcontrollerExportdictitems(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.dictcontrollerExportdictitemsParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<any>('/dict/item/export', {
|
|
||||||
method: 'GET',
|
|
||||||
params: {
|
|
||||||
...params,
|
|
||||||
},
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /dict/item/import */
|
/** 此处后端没有提供注释 POST /dict/item/import */
|
||||||
export async function dictcontrollerImportdictitems(
|
export async function dictcontrollerImportdictitems(
|
||||||
body: Record<string, any>,
|
body: Record<string, any>,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
return request<API.ApiResponse>('/dict/item/import', {
|
return request<any>('/dict/item/import', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'text/plain',
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// API 更新时间:
|
// API 更新时间:
|
||||||
// API 唯一标识:
|
// API 唯一标识:
|
||||||
import * as area from './area';
|
import * as area from './area';
|
||||||
import * as category from './category';
|
import * as category from './category';
|
||||||
import * as customer from './customer';
|
import * as customer from './customer';
|
||||||
import * as dict from './dict';
|
import * as dict from './dict';
|
||||||
import * as locales from './locales';
|
import * as locales from './locales';
|
||||||
import * as logistics from './logistics';
|
import * as logistics from './logistics';
|
||||||
|
import * as media from './media';
|
||||||
import * as order from './order';
|
import * as order from './order';
|
||||||
import * as product from './product';
|
import * as product from './product';
|
||||||
import * as site from './site';
|
import * as site from './site';
|
||||||
|
|
@ -25,6 +26,7 @@ export default {
|
||||||
dict,
|
dict,
|
||||||
locales,
|
locales,
|
||||||
logistics,
|
logistics,
|
||||||
|
media,
|
||||||
order,
|
order,
|
||||||
product,
|
product,
|
||||||
siteApi,
|
siteApi,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
// @ts-ignore
|
||||||
|
/* eslint-disable */
|
||||||
|
import { request } from 'umi';
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 DELETE /media/${param0} */
|
||||||
|
export async function mediacontrollerDelete(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.mediacontrollerDeleteParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, ...queryParams } = params;
|
||||||
|
return request<any>(`/media/${param0}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
params: {
|
||||||
|
...queryParams,
|
||||||
|
},
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /media/list */
|
||||||
|
export async function mediacontrollerList(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.mediacontrollerListParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<any>('/media/list', {
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /media/update/${param0} */
|
||||||
|
export async function mediacontrollerUpdate(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.mediacontrollerUpdateParams,
|
||||||
|
body: Record<string, any>,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, ...queryParams } = params;
|
||||||
|
return request<any>(`/media/update/${param0}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /media/upload */
|
||||||
|
export async function mediacontrollerUpload(
|
||||||
|
body: {},
|
||||||
|
files?: File[],
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
if (files) {
|
||||||
|
files.forEach((f) => formData.append('files', f || ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(body).forEach((ele) => {
|
||||||
|
const item = (body as any)[ele];
|
||||||
|
|
||||||
|
if (item !== undefined && item !== null) {
|
||||||
|
if (typeof item === 'object' && !(item instanceof File)) {
|
||||||
|
if (item instanceof Array) {
|
||||||
|
item.forEach((f) => formData.append(ele, f || ''));
|
||||||
|
} else {
|
||||||
|
formData.append(
|
||||||
|
ele,
|
||||||
|
new Blob([JSON.stringify(item)], { type: 'application/json' }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formData.append(ele, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return request<any>('/media/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
data: formData,
|
||||||
|
requestType: 'form',
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -59,21 +59,6 @@ export async function ordercontrollerCreatenote(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /order/export */
|
|
||||||
export async function ordercontrollerExportorder(
|
|
||||||
body: Record<string, any>,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<any>('/order/export', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /order/getOrderByNumber */
|
/** 此处后端没有提供注释 POST /order/getOrderByNumber */
|
||||||
export async function ordercontrollerGetorderbynumber(
|
export async function ordercontrollerGetorderbynumber(
|
||||||
body: string,
|
body: string,
|
||||||
|
|
@ -240,15 +225,15 @@ export async function ordercontrollerChangestatus(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /order/sync/${param0} */
|
/** 此处后端没有提供注释 POST /order/syncOrder/${param0} */
|
||||||
export async function ordercontrollerSyncorders(
|
export async function ordercontrollerSyncorder(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
params: API.ordercontrollerSyncordersParams,
|
params: API.ordercontrollerSyncorderParams,
|
||||||
body: Record<string, any>,
|
body: Record<string, any>,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { siteId: param0, ...queryParams } = params;
|
const { siteId: param0, ...queryParams } = params;
|
||||||
return request<API.BooleanRes>(`/order/sync/${param0}`, {
|
return request<API.BooleanRes>(`/order/syncOrder/${param0}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'text/plain',
|
||||||
|
|
@ -266,14 +251,11 @@ export async function ordercontrollerSyncorderbyid(
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { orderId: param0, siteId: param1, ...queryParams } = params;
|
const { orderId: param0, siteId: param1, ...queryParams } = params;
|
||||||
return request<API.SyncOperationResult>(
|
return request<API.BooleanRes>(`/order/syncOrder/${param1}/order/${param0}`, {
|
||||||
`/order/syncOrder/${param1}/order/${param0}`,
|
method: 'POST',
|
||||||
{
|
params: { ...queryParams },
|
||||||
method: 'POST',
|
...(options || {}),
|
||||||
params: { ...queryParams },
|
});
|
||||||
...(options || {}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /order/updateOrderItems/${param0} */
|
/** 此处后端没有提供注释 POST /order/updateOrderItems/${param0} */
|
||||||
|
|
|
||||||
|
|
@ -229,36 +229,6 @@ export async function productcontrollerBatchdeleteproduct(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /product/batch-sync-from-site */
|
|
||||||
export async function productcontrollerBatchsyncfromsite(
|
|
||||||
body: Record<string, any>,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<API.SyncOperationResultDTO>('/product/batch-sync-from-site', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /product/batch-sync-to-site */
|
|
||||||
export async function productcontrollerBatchsynctosite(
|
|
||||||
body: API.BatchSyncProductToSiteDTO,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<API.SyncOperationResultDTO>('/product/batch-sync-to-site', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /product/batch-update */
|
/** 此处后端没有提供注释 PUT /product/batch-update */
|
||||||
export async function productcontrollerBatchupdateproduct(
|
export async function productcontrollerBatchupdateproduct(
|
||||||
body: API.BatchUpdateProductDTO,
|
body: API.BatchUpdateProductDTO,
|
||||||
|
|
@ -363,13 +333,13 @@ export async function productcontrollerGetcategoriesall(options?: {
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /product/category */
|
/** 此处后端没有提供注释 POST /product/category */
|
||||||
export async function productcontrollerCreatecategory(
|
export async function productcontrollerCreatecategory(
|
||||||
body: API.CreateCategoryDTO,
|
body: Record<string, any>,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
return request<any>('/product/category', {
|
return request<any>('/product/category', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'text/plain',
|
||||||
},
|
},
|
||||||
data: body,
|
data: body,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
|
|
@ -380,14 +350,14 @@ export async function productcontrollerCreatecategory(
|
||||||
export async function productcontrollerUpdatecategory(
|
export async function productcontrollerUpdatecategory(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
params: API.productcontrollerUpdatecategoryParams,
|
params: API.productcontrollerUpdatecategoryParams,
|
||||||
body: API.UpdateCategoryDTO,
|
body: Record<string, any>,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { id: param0, ...queryParams } = params;
|
const { id: param0, ...queryParams } = params;
|
||||||
return request<any>(`/product/category/${param0}`, {
|
return request<any>(`/product/category/${param0}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'text/plain',
|
||||||
},
|
},
|
||||||
params: { ...queryParams },
|
params: { ...queryParams },
|
||||||
data: body,
|
data: body,
|
||||||
|
|
@ -790,21 +760,6 @@ export async function productcontrollerCompatstrengthall(options?: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /product/sync-from-site */
|
|
||||||
export async function productcontrollerSyncproductfromsite(
|
|
||||||
body: Record<string, any>,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<API.ProductRes>('/product/sync-from-site', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /product/sync-stock */
|
/** 此处后端没有提供注释 POST /product/sync-stock */
|
||||||
export async function productcontrollerSyncstocktoproduct(options?: {
|
export async function productcontrollerSyncstocktoproduct(options?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
@ -815,21 +770,6 @@ export async function productcontrollerSyncstocktoproduct(options?: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /product/sync-to-site */
|
|
||||||
export async function productcontrollerSynctosite(
|
|
||||||
body: API.SyncProductToSiteDTO,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<API.SyncProductToSiteResultDTO>('/product/sync-to-site', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /productupdateNameCn/${param1}/${param0} */
|
/** 此处后端没有提供注释 PUT /productupdateNameCn/${param1}/${param0} */
|
||||||
export async function productcontrollerUpdatenamecn(
|
export async function productcontrollerUpdatenamecn(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
|
||||||
|
|
@ -59,16 +59,9 @@ export async function sitecontrollerGet(
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site/list */
|
/** 此处后端没有提供注释 GET /site/list */
|
||||||
export async function sitecontrollerList(
|
export async function sitecontrollerList(options?: { [key: string]: any }) {
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.sitecontrollerListParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<any>('/site/list', {
|
return request<any>('/site/list', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
|
||||||
...params,
|
|
||||||
},
|
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@ export async function siteapicontrollerGetcustomers(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
|
|
@ -70,6 +74,10 @@ export async function siteapicontrollerExportcustomers(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -119,6 +127,10 @@ export async function siteapicontrollerGetmedia(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -198,6 +210,10 @@ export async function siteapicontrollerExportmedia(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -214,6 +230,10 @@ export async function siteapicontrollerGetorders(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -260,38 +280,21 @@ export async function siteapicontrollerBatchorders(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-fulfill */
|
/** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-ship */
|
||||||
export async function siteapicontrollerBatchfulfillorders(
|
export async function siteapicontrollerBatchshiporders(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
params: API.siteapicontrollerBatchfulfillordersParams,
|
params: API.siteapicontrollerBatchshipordersParams,
|
||||||
body: API.BatchFulfillmentsDTO,
|
body: API.BatchShipOrdersDTO,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { siteId: param0, ...queryParams } = params;
|
const { siteId: param0, ...queryParams } = params;
|
||||||
return request<Record<string, any>>(
|
return request<Record<string, any>>(`/site-api/${param0}/orders/batch-ship`, {
|
||||||
`/site-api/${param0}/orders/batch-fulfill`,
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
params: { ...queryParams },
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
},
|
},
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param0}/orders/count */
|
|
||||||
export async function siteapicontrollerCountorders(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.siteapicontrollerCountordersParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const { siteId: param0, ...queryParams } = params;
|
|
||||||
return request<Record<string, any>>(`/site-api/${param0}/orders/count`, {
|
|
||||||
method: 'GET',
|
|
||||||
params: { ...queryParams },
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -307,6 +310,10 @@ export async function siteapicontrollerExportorders(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -344,6 +351,10 @@ export async function siteapicontrollerGetproducts(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
|
|
@ -391,28 +402,6 @@ export async function siteapicontrollerBatchproducts(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /site-api/${param0}/products/batch-upsert */
|
|
||||||
export async function siteapicontrollerBatchupsertproduct(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.siteapicontrollerBatchupsertproductParams,
|
|
||||||
body: Record<string, any>,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const { siteId: param0, ...queryParams } = params;
|
|
||||||
return request<API.BatchOperationResultDTO>(
|
|
||||||
`/site-api/${param0}/products/batch-upsert`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
},
|
|
||||||
params: { ...queryParams },
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param0}/products/export */
|
/** 此处后端没有提供注释 GET /site-api/${param0}/products/export */
|
||||||
export async function siteapicontrollerExportproducts(
|
export async function siteapicontrollerExportproducts(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
@ -424,6 +413,10 @@ export async function siteapicontrollerExportproducts(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -440,6 +433,10 @@ export async function siteapicontrollerExportproductsspecial(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -486,25 +483,6 @@ export async function siteapicontrollerImportproductsspecial(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /site-api/${param0}/products/upsert */
|
|
||||||
export async function siteapicontrollerUpsertproduct(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.siteapicontrollerUpsertproductParams,
|
|
||||||
body: API.UnifiedProductDTO,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const { siteId: param0, ...queryParams } = params;
|
|
||||||
return request<API.UnifiedProductDTO>(`/site-api/${param0}/products/upsert`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
params: { ...queryParams },
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param0}/reviews */
|
/** 此处后端没有提供注释 GET /site-api/${param0}/reviews */
|
||||||
export async function siteapicontrollerGetreviews(
|
export async function siteapicontrollerGetreviews(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
@ -518,6 +496,10 @@ export async function siteapicontrollerGetreviews(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
|
|
@ -556,6 +538,10 @@ export async function siteapicontrollerGetsubscriptions(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
|
|
@ -573,6 +559,10 @@ export async function siteapicontrollerExportsubscriptions(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -589,6 +579,10 @@ export async function siteapicontrollerGetwebhooks(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -682,6 +676,10 @@ export async function siteapicontrollerGetcustomerorders(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
orderBy: undefined,
|
||||||
|
...queryParams['orderBy'],
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
|
|
@ -768,55 +766,16 @@ export async function siteapicontrollerDeleteorder(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-fulfill */
|
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-ship */
|
||||||
export async function siteapicontrollerCancelfulfillment(
|
export async function siteapicontrollerCancelshiporder(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
params: API.siteapicontrollerCancelfulfillmentParams,
|
params: API.siteapicontrollerCancelshiporderParams,
|
||||||
body: API.CancelFulfillmentDTO,
|
body: API.CancelShipOrderDTO,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { id: param0, siteId: param1, ...queryParams } = params;
|
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||||
return request<Record<string, any>>(
|
return request<Record<string, any>>(
|
||||||
`/site-api/${param1}/orders/${param0}/cancel-fulfill`,
|
`/site-api/${param1}/orders/${param0}/cancel-ship`,
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
params: { ...queryParams },
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param1}/orders/${param0}/fulfillments */
|
|
||||||
export async function siteapicontrollerGetorderfulfillments(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.siteapicontrollerGetorderfulfillmentsParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const { orderId: param0, siteId: param1, ...queryParams } = params;
|
|
||||||
return request<Record<string, any>>(
|
|
||||||
`/site-api/${param1}/orders/${param0}/fulfillments`,
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
params: { ...queryParams },
|
|
||||||
...(options || {}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/fulfillments */
|
|
||||||
export async function siteapicontrollerCreateorderfulfillment(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.siteapicontrollerCreateorderfulfillmentParams,
|
|
||||||
body: API.UnifiedOrderTrackingDTO,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const { orderId: param0, siteId: param1, ...queryParams } = params;
|
|
||||||
return request<API.UnifiedOrderTrackingDTO>(
|
|
||||||
`/site-api/${param1}/orders/${param0}/fulfillments`,
|
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -868,6 +827,28 @@ export async function siteapicontrollerCreateordernote(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/ship */
|
||||||
|
export async function siteapicontrollerShiporder(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerShiporderParams,
|
||||||
|
body: API.ShipOrderDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||||
|
return request<Record<string, any>>(
|
||||||
|
`/site-api/${param1}/orders/${param0}/ship`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param1}/products/${param0} */
|
/** 此处后端没有提供注释 GET /site-api/${param1}/products/${param0} */
|
||||||
export async function siteapicontrollerGetproduct(
|
export async function siteapicontrollerGetproduct(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
@ -1016,55 +997,6 @@ export async function siteapicontrollerDeletewebhook(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /site-api/${param2}/orders/${param1}/fulfillments/${param0} */
|
|
||||||
export async function siteapicontrollerUpdateorderfulfillment(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.siteapicontrollerUpdateorderfulfillmentParams,
|
|
||||||
body: API.UnifiedOrderTrackingDTO,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
fulfillmentId: param0,
|
|
||||||
orderId: param1,
|
|
||||||
siteId: param2,
|
|
||||||
...queryParams
|
|
||||||
} = params;
|
|
||||||
return request<API.UnifiedOrderTrackingDTO>(
|
|
||||||
`/site-api/${param2}/orders/${param1}/fulfillments/${param0}`,
|
|
||||||
{
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
params: { ...queryParams },
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 DELETE /site-api/${param2}/orders/${param1}/fulfillments/${param0} */
|
|
||||||
export async function siteapicontrollerDeleteorderfulfillment(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.siteapicontrollerDeleteorderfulfillmentParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
fulfillmentId: param0,
|
|
||||||
orderId: param1,
|
|
||||||
siteId: param2,
|
|
||||||
...queryParams
|
|
||||||
} = params;
|
|
||||||
return request<Record<string, any>>(
|
|
||||||
`/site-api/${param2}/orders/${param1}/fulfillments/${param0}`,
|
|
||||||
{
|
|
||||||
method: 'DELETE',
|
|
||||||
params: { ...queryParams },
|
|
||||||
...(options || {}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */
|
/** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */
|
||||||
export async function siteapicontrollerUpdatevariation(
|
export async function siteapicontrollerUpdatevariation(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -10,26 +10,6 @@ export async function webhookcontrollerTest(options?: { [key: string]: any }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /webhook/shoppy */
|
|
||||||
export async function webhookcontrollerHandleshoppywebhook(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.webhookcontrollerHandleshoppywebhookParams,
|
|
||||||
body: Record<string, any>,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<any>('/webhook/shoppy', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain',
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
...params,
|
|
||||||
},
|
|
||||||
data: body,
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /webhook/woocommerce */
|
/** 此处后端没有提供注释 POST /webhook/woocommerce */
|
||||||
export async function webhookcontrollerHandlewoowebhook(
|
export async function webhookcontrollerHandlewoowebhook(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
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