forked from yoone/WEB
Compare commits
13 Commits
32e2759151
...
d372f72935
| Author | SHA1 | Date |
|---|---|---|
|
|
d372f72935 | |
|
|
7247015e4c | |
|
|
6677114c46 | |
|
|
4fd9cfee75 | |
|
|
82c1bfa711 | |
|
|
bb907fa47c | |
|
|
ce12e55adc | |
|
|
e498793678 | |
|
|
2467a0c3b2 | |
|
|
5471f7a038 | |
|
|
e23b0b3e39 | |
|
|
6d97ecbcc7 | |
|
|
6f35c2aee5 |
|
|
@ -170,6 +170,11 @@ export default defineConfig({
|
||||||
path: '/product/sync',
|
path: '/product/sync',
|
||||||
component: './Product/Sync',
|
component: './Product/Sync',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: '产品CSV 工具',
|
||||||
|
path: '/product/csvtool',
|
||||||
|
component: './Product/CsvTool',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# 构建阶段
|
||||||
|
FROM node:18-alpine as builder
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 复制 package.json 和 package-lock.json
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# 安装依赖(使用 --legacy-peer-deps 解决依赖冲突)
|
||||||
|
RUN npm install --legacy-peer-deps
|
||||||
|
|
||||||
|
# 复制源代码
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 构建项目
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# 生产阶段
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# 复制构建产物到 Nginx 静态目录
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# 复制自定义 Nginx 配置
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# 启动 Nginx
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# API 代理配置
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://api:7001;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 静态文件缓存配置
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# 错误页面配置
|
||||||
|
error_page 404 /index.html;
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { message } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// 定义同步结果的数据类型
|
||||||
|
export interface SyncResultData {
|
||||||
|
total?: number;
|
||||||
|
processed?: number;
|
||||||
|
synced?: number;
|
||||||
|
created?: number;
|
||||||
|
updated?: number;
|
||||||
|
errors?: Array<{
|
||||||
|
identifier: string;
|
||||||
|
error: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义组件的 Props 类型
|
||||||
|
interface SyncResultMessageProps {
|
||||||
|
data?: SyncResultData;
|
||||||
|
entityType?: string; // 实体类型,如"订单"、"客户"等
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示同步结果的函数
|
||||||
|
export const showSyncResult = (
|
||||||
|
data: SyncResultData,
|
||||||
|
entityType: string = '订单',
|
||||||
|
) => {
|
||||||
|
const result = data || {};
|
||||||
|
const {
|
||||||
|
total = 0,
|
||||||
|
processed = 0,
|
||||||
|
synced = 0,
|
||||||
|
created = 0,
|
||||||
|
updated = 0,
|
||||||
|
errors = [],
|
||||||
|
} = result;
|
||||||
|
|
||||||
|
// 构建结果消息
|
||||||
|
let resultMessage = `同步完成!共处理 ${processed} 个${entityType}(总数 ${total} 个):`;
|
||||||
|
if (created > 0) resultMessage += ` 新建 ${created} 个`;
|
||||||
|
if (updated > 0) resultMessage += ` 更新 ${updated} 个`;
|
||||||
|
if (synced > 0) resultMessage += ` 同步成功 ${synced} 个`;
|
||||||
|
if (errors.length > 0) resultMessage += ` 失败 ${errors.length} 个`;
|
||||||
|
|
||||||
|
// 根据是否有错误显示不同的消息类型
|
||||||
|
if (errors.length > 0) {
|
||||||
|
// 如果有错误,显示警告消息
|
||||||
|
message.warning({
|
||||||
|
content: (
|
||||||
|
<div>
|
||||||
|
<div>{resultMessage}</div>
|
||||||
|
<div style={{ marginTop: 8, fontSize: 12, color: '#faad14' }}>
|
||||||
|
失败详情:
|
||||||
|
{errors
|
||||||
|
.slice(0, 3)
|
||||||
|
.map((err: any) => `${err.identifier}: ${err.error}`)
|
||||||
|
.join(', ')}
|
||||||
|
{errors.length > 3 && ` 等 ${errors.length - 3} 个错误...`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
duration: 8,
|
||||||
|
key: 'sync-result',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 完全成功
|
||||||
|
message.success({
|
||||||
|
content: resultMessage,
|
||||||
|
duration: 4,
|
||||||
|
key: 'sync-result',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 同步结果显示组件
|
||||||
|
const SyncResultMessage: React.FC<SyncResultMessageProps> = ({
|
||||||
|
data,
|
||||||
|
entityType = '订单',
|
||||||
|
}) => {
|
||||||
|
// 当组件挂载时显示结果
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
showSyncResult(data, entityType);
|
||||||
|
}
|
||||||
|
}, [data, entityType]);
|
||||||
|
|
||||||
|
// 这个组件不渲染任何内容,只用于显示消息
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SyncResultMessage;
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
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,5 +1,4 @@
|
||||||
import { ordercontrollerGetorders } from '@/servers/api/order';
|
import { ordercontrollerGetorders } from '@/servers/api/order';
|
||||||
import { siteapicontrollerGetorders } from '@/servers/api/siteApi';
|
|
||||||
import {
|
import {
|
||||||
App,
|
App,
|
||||||
Col,
|
Col,
|
||||||
|
|
@ -61,7 +60,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();
|
||||||
|
|
||||||
|
|
@ -89,12 +88,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({
|
||||||
customer_email: customer.email,
|
where: {
|
||||||
|
customer_email: customer.email,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
|
|
@ -242,7 +241,7 @@ const HistoryOrders: React.FC<HistoryOrdersProps> = ({ customer, siteId }) => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
showQuickJumper: true,
|
||||||
}}
|
}}
|
||||||
scroll={{ x: 800 }}
|
scroll={{ x: 800 }}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,12 @@ import {
|
||||||
ModalForm,
|
ModalForm,
|
||||||
PageContainer,
|
PageContainer,
|
||||||
ProColumns,
|
ProColumns,
|
||||||
|
ProFormDateTimeRangePicker,
|
||||||
ProFormSelect,
|
ProFormSelect,
|
||||||
|
ProFormText,
|
||||||
ProTable,
|
ProTable,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { App, Avatar, Button, Rate, Space, Tag, Tooltip } from 'antd';
|
import { App, Avatar, Button, Form, Rate, Space, Tag, Tooltip } from 'antd';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import HistoryOrders from './HistoryOrders';
|
import HistoryOrders from './HistoryOrders';
|
||||||
|
|
||||||
|
|
@ -113,42 +115,17 @@ 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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据站点ID获取站点名称
|
const columns: ProColumns<API.GetCustomerDTO>[] = [
|
||||||
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: '站点',
|
||||||
|
|
@ -169,9 +146,6 @@ const CustomerList: React.FC = () => {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: (siteId: any) => {
|
|
||||||
return <span>{getSiteName(siteId) || '-'}</span>;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '头像',
|
title: '头像',
|
||||||
|
|
@ -203,24 +177,26 @@ const CustomerList: React.FC = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '用户名',
|
title: '用户名',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
hideInSearch: true,
|
copyable: 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="账单地址" />,
|
||||||
},
|
},
|
||||||
|
|
@ -234,8 +210,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
|
||||||
|
|
@ -254,7 +230,7 @@ const CustomerList: React.FC = () => {
|
||||||
message.error(e?.message || '设置评分失败');
|
message.error(e?.message || '设置评分失败');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
value={record.raw?.rate || 0}
|
value={record.rate || 0}
|
||||||
allowHalf
|
allowHalf
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
@ -265,7 +241,7 @@ const CustomerList: React.FC = () => {
|
||||||
dataIndex: 'tags',
|
dataIndex: 'tags',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const tags = record.raw?.tags || [];
|
const tags = record?.tags || [];
|
||||||
return (
|
return (
|
||||||
<Space size={[0, 8]} wrap>
|
<Space size={[0, 8]} wrap>
|
||||||
{tags.map((tag: string) => {
|
{tags.map((tag: string) => {
|
||||||
|
|
@ -327,7 +303,7 @@ const CustomerList: React.FC = () => {
|
||||||
tableRef={actionRef}
|
tableRef={actionRef}
|
||||||
/>
|
/>
|
||||||
{/* 订单 */}
|
{/* 订单 */}
|
||||||
<HistoryOrders customer={record} siteId={record.raw?.site_id} />
|
<HistoryOrders customer={record} siteId={record.site_id} />
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -340,25 +316,35 @@ 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) => {
|
request={async (params, sorter,filter) => {
|
||||||
const key = Object.keys(sorter)[0];
|
console.log('custoemr request',params, sorter,filter)
|
||||||
const { data, success } = await customercontrollerGetcustomerlist({
|
const { current, pageSize, ...restParams } = params;
|
||||||
...params,
|
const orderBy:any = {}
|
||||||
current: params.current?.toString(),
|
Object.entries(sorter).forEach(([key, value]) => {
|
||||||
pageSize: params.pageSize?.toString(),
|
orderBy[key] = value === 'ascend' ? 'asc' : 'desc';
|
||||||
...(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: data?.total || 0,
|
total: result?.data?.total || 0,
|
||||||
data: data?.items || [],
|
data: result?.data?.items || [],
|
||||||
success,
|
success: true,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
|
||||||
search={{
|
search={{
|
||||||
labelWidth: 'auto',
|
labelWidth: 'auto',
|
||||||
span: 6,
|
span: 6,
|
||||||
|
|
@ -366,8 +352,7 @@ const CustomerList: React.FC = () => {
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showTotal: (total, range) =>
|
showQuickJumper: true,
|
||||||
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
|
|
||||||
}}
|
}}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -469,6 +454,7 @@ const SyncCustomersModal: React.FC<{
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const [sites, setSites] = useState<any[]>([]);
|
const [sites, setSites] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [form] = Form.useForm(); // 添加表单实例
|
||||||
|
|
||||||
// 获取站点列表
|
// 获取站点列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -487,15 +473,56 @@ const SyncCustomersModal: React.FC<{
|
||||||
}
|
}
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
const handleSync = async (values: { siteId: number }) => {
|
// 定义同步参数类型
|
||||||
|
type SyncParams = {
|
||||||
|
siteId: number;
|
||||||
|
search?: string;
|
||||||
|
role?: string;
|
||||||
|
dateRange?: [string, string];
|
||||||
|
orderBy?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSync = async (values: SyncParams) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
// 构建过滤参数
|
||||||
|
const params: any = {};
|
||||||
|
|
||||||
|
// 添加搜索关键词
|
||||||
|
if (values.search) {
|
||||||
|
params.search = values.search;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加角色过滤
|
||||||
|
if (values.role) {
|
||||||
|
params.where = {
|
||||||
|
...params.where,
|
||||||
|
role: values.role,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加日期范围过滤(使用 after 和 before 参数)
|
||||||
|
if (values.dateRange && values.dateRange[0] && values.dateRange[1]) {
|
||||||
|
params.where = {
|
||||||
|
...params.where,
|
||||||
|
after: values.dateRange[0],
|
||||||
|
before: values.dateRange[1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加排序
|
||||||
|
if (values.orderBy) {
|
||||||
|
params.orderBy = values.orderBy;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
success,
|
success,
|
||||||
message: msg,
|
message: msg,
|
||||||
data,
|
data,
|
||||||
} = await customercontrollerSynccustomers({
|
} = await customercontrollerSynccustomers({
|
||||||
siteId: values.siteId,
|
siteId: values.siteId,
|
||||||
|
params: Object.keys(params).length > 0 ? params : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|
@ -509,7 +536,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} 个`;
|
||||||
|
|
@ -522,7 +549,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)
|
||||||
|
|
@ -569,6 +596,7 @@ const SyncCustomersModal: React.FC<{
|
||||||
confirmLoading: loading,
|
confirmLoading: loading,
|
||||||
}}
|
}}
|
||||||
onFinish={handleSync}
|
onFinish={handleSync}
|
||||||
|
form={form}
|
||||||
>
|
>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="siteId"
|
name="siteId"
|
||||||
|
|
@ -583,6 +611,59 @@ const SyncCustomersModal: React.FC<{
|
||||||
loading: loading,
|
loading: loading,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<ProFormText
|
||||||
|
name="search"
|
||||||
|
label="搜索关键词"
|
||||||
|
placeholder="输入邮箱、姓名或用户名进行搜索"
|
||||||
|
tooltip="支持搜索邮箱、姓名、用户名等字段"
|
||||||
|
/>
|
||||||
|
<ProFormSelect
|
||||||
|
name="role"
|
||||||
|
label="客户角色"
|
||||||
|
placeholder="选择客户角色进行过滤"
|
||||||
|
options={[
|
||||||
|
{ label: '所有角色', value: '' },
|
||||||
|
{ label: '管理员', value: 'administrator' },
|
||||||
|
{ label: '编辑', value: 'editor' },
|
||||||
|
{ label: '作者', value: 'author' },
|
||||||
|
{ label: '订阅者', value: 'subscriber' },
|
||||||
|
{ label: '客户', value: 'customer' },
|
||||||
|
]}
|
||||||
|
fieldProps={{
|
||||||
|
allowClear: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormDateTimeRangePicker
|
||||||
|
name="dateRange"
|
||||||
|
label="注册日期范围"
|
||||||
|
placeholder={['开始日期', '结束日期']}
|
||||||
|
transform={(value) => {
|
||||||
|
return {
|
||||||
|
dateRange: value,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
fieldProps={{
|
||||||
|
showTime: false,
|
||||||
|
style: { width: '100%' },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormSelect
|
||||||
|
name="orderBy"
|
||||||
|
label="排序方式"
|
||||||
|
placeholder="选择排序方式"
|
||||||
|
options={[
|
||||||
|
{ label: '默认排序', value: '' },
|
||||||
|
{ label: '注册时间(升序)', value: 'date_created:asc' },
|
||||||
|
{ label: '注册时间(降序)', value: 'date_created:desc' },
|
||||||
|
{ label: '邮箱(升序)', value: 'email:asc' },
|
||||||
|
{ label: '邮箱(降序)', value: 'email:desc' },
|
||||||
|
{ label: '姓名(升序)', value: 'first_name:asc' },
|
||||||
|
{ label: '姓名(降序)', value: 'first_name:desc' },
|
||||||
|
]}
|
||||||
|
fieldProps={{
|
||||||
|
allowClear: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -129,21 +129,15 @@ const ListPage: React.FC = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'phone',
|
title: '联系电话',
|
||||||
dataIndex: 'phone',
|
dataIndex: 'phone',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
render: (_, record) => record?.billing.phone || record?.shipping.phone,
|
render: (_, record) => record?.billing.phone || record?.shipping.phone,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'state',
|
title: '账单地址',
|
||||||
dataIndex: 'state',
|
dataIndex: 'billing',
|
||||||
render: (_, record) => record?.billing.state || record?.shipping.state,
|
render: (_, record) => JSON.stringify(record?.billing || record?.shipping),
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'city',
|
|
||||||
dataIndex: 'city',
|
|
||||||
hideInSearch: true,
|
|
||||||
render: (_, record) => record?.billing.city || record?.shipping.city,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '标签',
|
title: '标签',
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ import {
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import DictItemActions from '../components/DictItemActions';
|
||||||
|
import DictItemModal from '../components/DictItemModal';
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
|
|
@ -36,13 +38,10 @@ const DictPage: React.FC = () => {
|
||||||
const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false);
|
const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false);
|
||||||
const [editDictData, setEditDictData] = useState<any>(null);
|
const [editDictData, setEditDictData] = useState<any>(null);
|
||||||
|
|
||||||
// 右侧字典项列表的状态
|
// 字典项模态框状态(由 DictItemModal 组件管理)
|
||||||
const [isAddDictItemModalVisible, setIsAddDictItemModalVisible] =
|
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||||
useState(false);
|
const [isEditDictItem, setIsEditDictItem] = useState(false);
|
||||||
const [isEditDictItemModalVisible, setIsEditDictItemModalVisible] =
|
const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
|
||||||
useState(false);
|
|
||||||
const [editDictItemData, setEditDictItemData] = useState<any>(null);
|
|
||||||
const [dictItemForm] = Form.useForm();
|
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
// 获取字典列表
|
// 获取字典列表
|
||||||
|
|
@ -138,21 +137,22 @@ 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 = () => {
|
||||||
dictItemForm.resetFields();
|
setIsEditDictItem(false);
|
||||||
setIsAddDictItemModalVisible(true);
|
setEditingDictItemData(null);
|
||||||
|
setIsDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 编辑字典项
|
// 编辑字典项
|
||||||
const handleEditDictItem = (record: any) => {
|
const handleEditDictItem = (record: any) => {
|
||||||
setEditDictItemData(record);
|
setIsEditDictItem(true);
|
||||||
dictItemForm.setFieldsValue(record);
|
setEditingDictItemData(record);
|
||||||
setIsEditDictItemModalVisible(true);
|
setIsDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除字典项
|
// 删除字典项
|
||||||
|
|
@ -173,53 +173,42 @@ const DictPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加字典项表单提交
|
// 处理字典项模态框提交(添加或编辑)
|
||||||
const handleAddDictItemFormSubmit = async (values: any) => {
|
const handleDictItemModalOk = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
const result = await dictApi.dictcontrollerCreatedictitem({
|
if (isEditDictItem && editingDictItemData) {
|
||||||
...values,
|
// 编辑字典项
|
||||||
dictId: selectedDict.id,
|
const result = await dictApi.dictcontrollerUpdatedictitem(
|
||||||
});
|
{ id: editingDictItemData.id },
|
||||||
|
values,
|
||||||
if (!result.success) {
|
);
|
||||||
throw new Error(result.message || '添加失败');
|
if (!result.success) {
|
||||||
|
throw new Error(result.message || '更新失败');
|
||||||
|
}
|
||||||
|
message.success('更新成功');
|
||||||
|
} else {
|
||||||
|
// 添加字典项
|
||||||
|
const result = await dictApi.dictcontrollerCreatedictitem({
|
||||||
|
...values,
|
||||||
|
dictId: selectedDict.id,
|
||||||
|
});
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.message || '添加失败');
|
||||||
|
}
|
||||||
|
message.success('添加成功');
|
||||||
}
|
}
|
||||||
|
setIsDictItemModalVisible(false);
|
||||||
message.success('添加成功');
|
|
||||||
setIsAddDictItemModalVisible(false);
|
|
||||||
|
|
||||||
// 强制刷新字典项列表
|
// 强制刷新字典项列表
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
actionRef.current?.reload();
|
actionRef.current?.reload();
|
||||||
}, 100);
|
}, 100);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(`添加失败:${error.message || '未知错误'}`);
|
message.error(
|
||||||
}
|
`${isEditDictItem ? '更新' : '添加'}失败:${
|
||||||
};
|
error.message || '未知错误'
|
||||||
|
}`,
|
||||||
// 编辑字典项表单提交
|
|
||||||
const handleEditDictItemFormSubmit = async (values: any) => {
|
|
||||||
if (!editDictItemData) return;
|
|
||||||
try {
|
|
||||||
const result = await dictApi.dictcontrollerUpdatedictitem(
|
|
||||||
{ id: editDictItemData.id },
|
|
||||||
values,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.message || '更新失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
message.success('更新成功');
|
|
||||||
setIsEditDictItemModalVisible(false);
|
|
||||||
setEditDictItemData(null);
|
|
||||||
|
|
||||||
// 强制刷新字典项列表
|
|
||||||
setTimeout(() => {
|
|
||||||
actionRef.current?.reload();
|
|
||||||
}, 100);
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(`更新失败:${error.message || '未知错误'}`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -286,7 +275,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 || '未知错误'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -337,13 +326,7 @@ const DictPage: React.FC = () => {
|
||||||
key: 'shortName',
|
key: 'shortName',
|
||||||
copyable: true,
|
copyable: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '图片',
|
|
||||||
dataIndex: 'image',
|
|
||||||
key: 'image',
|
|
||||||
valueType: 'image',
|
|
||||||
width: 80,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '标题',
|
title: '标题',
|
||||||
dataIndex: 'title',
|
dataIndex: 'title',
|
||||||
|
|
@ -356,6 +339,13 @@ const DictPage: React.FC = () => {
|
||||||
key: 'titleCN',
|
key: 'titleCN',
|
||||||
copyable: true,
|
copyable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '图片',
|
||||||
|
dataIndex: 'image',
|
||||||
|
key: 'image',
|
||||||
|
valueType: 'image',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
|
|
@ -406,7 +396,18 @@ const DictPage: React.FC = () => {
|
||||||
</Button>
|
</Button>
|
||||||
<Upload
|
<Upload
|
||||||
name="file"
|
name="file"
|
||||||
action="/api/dict/import"
|
action={undefined}
|
||||||
|
customRequest={async (options) => {
|
||||||
|
const { file, onSuccess, onError } = options;
|
||||||
|
try {
|
||||||
|
const result = await dictApi.dictcontrollerImportdicts({}, [
|
||||||
|
file as File,
|
||||||
|
]);
|
||||||
|
onSuccess?.(result);
|
||||||
|
} catch (error) {
|
||||||
|
onError?.(error as Error);
|
||||||
|
}
|
||||||
|
}}
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
onChange={(info) => {
|
onChange={(info) => {
|
||||||
if (info.file.status === 'done') {
|
if (info.file.status === 'done') {
|
||||||
|
|
@ -476,7 +477,7 @@ const DictPage: React.FC = () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兼容旧的响应格式(直接返回数组)
|
// 兼容旧的响应格式(直接返回数组)
|
||||||
return {
|
return {
|
||||||
data: res || [],
|
data: res || [],
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -495,151 +496,46 @@ const DictPage: React.FC = () => {
|
||||||
size="small"
|
size="small"
|
||||||
key={selectedDict?.id}
|
key={selectedDict?.id}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
<Button
|
<DictItemActions
|
||||||
type="primary"
|
key="dictItemActions"
|
||||||
onClick={handleAddDictItem}
|
selectedDict={selectedDict}
|
||||||
disabled={!selectedDict}
|
actionRef={actionRef}
|
||||||
size="small"
|
showExport={true}
|
||||||
key="add"
|
onImport={async (file: File, dictId: number) => {
|
||||||
>
|
// 创建 FormData 对象
|
||||||
添加字典项
|
const formData = new FormData();
|
||||||
</Button>,
|
// 添加文件到 FormData
|
||||||
<Upload
|
formData.append('file', file);
|
||||||
name="file"
|
// 添加字典 ID 到 FormData
|
||||||
action={undefined}
|
formData.append('dictId', String(dictId));
|
||||||
customRequest={async (options) => {
|
// 调用导入字典项的 API,直接返回解析后的 JSON 对象
|
||||||
const { file, onSuccess, onError } = options;
|
const result = await dictApi.dictcontrollerImportdictitems(
|
||||||
try {
|
formData,
|
||||||
const result =
|
);
|
||||||
await dictApi.dictcontrollerImportdictitems(
|
return result;
|
||||||
{ dictId: selectedDict?.id },
|
|
||||||
[file as File],
|
|
||||||
);
|
|
||||||
onSuccess?.(result);
|
|
||||||
} catch (error) {
|
|
||||||
onError?.(error as Error);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
showUploadList={false}
|
onExport={handleExportDictItems}
|
||||||
disabled={!selectedDict}
|
onAdd={handleAddDictItem}
|
||||||
onChange={(info) => {
|
onRefreshDicts={fetchDicts}
|
||||||
console.log(`info`, info);
|
/>,
|
||||||
if (info.file.status === 'done') {
|
|
||||||
message.success(`${info.file.name} 文件上传成功`);
|
|
||||||
// 重新加载字典项列表
|
|
||||||
setTimeout(() => {
|
|
||||||
actionRef.current?.reload();
|
|
||||||
}, 100);
|
|
||||||
// 重新加载字典列表以更新字典项数量
|
|
||||||
fetchDicts();
|
|
||||||
} else if (info.file.status === 'error') {
|
|
||||||
message.error(`${info.file.name} 文件上传失败`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
key="import"
|
|
||||||
>
|
|
||||||
<Button size="small" icon={<UploadOutlined />}>
|
|
||||||
导入字典项
|
|
||||||
</Button>
|
|
||||||
</Upload>,
|
|
||||||
<Button
|
|
||||||
onClick={handleExportDictItems}
|
|
||||||
disabled={!selectedDict}
|
|
||||||
size="small"
|
|
||||||
key="export"
|
|
||||||
>
|
|
||||||
导出数据
|
|
||||||
</Button>,
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
{/* 添加字典项 Modal */}
|
{/* 字典项 Modal(添加或编辑) */}
|
||||||
<Modal
|
<DictItemModal
|
||||||
title={`添加字典项 - ${selectedDict?.title || '未选择字典'}`}
|
visible={isDictItemModalVisible}
|
||||||
open={isAddDictItemModalVisible}
|
isEdit={isEditDictItem}
|
||||||
onOk={() => dictItemForm.submit()}
|
editingData={editingDictItemData}
|
||||||
onCancel={() => setIsAddDictItemModalVisible(false)}
|
selectedDict={selectedDict}
|
||||||
>
|
|
||||||
<Form
|
|
||||||
form={dictItemForm}
|
|
||||||
layout="vertical"
|
|
||||||
onFinish={handleAddDictItemFormSubmit}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
label="名称"
|
|
||||||
name="name"
|
|
||||||
rules={[{ required: true, message: '请输入名称' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="名称 (e.g., zyn)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="标题"
|
|
||||||
name="title"
|
|
||||||
rules={[{ required: true, message: '请输入标题' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="标题 (e.g., ZYN)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="中文标题" name="titleCN">
|
|
||||||
<Input placeholder="中文标题 (e.g., 品牌)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="简称 (可选)" name="shortName">
|
|
||||||
<Input placeholder="简称 (可选)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="图片 (可选)" name="image">
|
|
||||||
<Input placeholder="图片链接 (可选)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="值 (可选)" name="value">
|
|
||||||
<Input placeholder="值 (可选)" />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
{/* 编辑字典项 Modal */}
|
|
||||||
<Modal
|
|
||||||
title="编辑字典项"
|
|
||||||
open={isEditDictItemModalVisible}
|
|
||||||
onOk={() => dictItemForm.submit()}
|
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setIsEditDictItemModalVisible(false);
|
setIsDictItemModalVisible(false);
|
||||||
setEditDictItemData(null);
|
setEditingDictItemData(null);
|
||||||
}}
|
}}
|
||||||
>
|
onOk={handleDictItemModalOk}
|
||||||
<Form
|
/>
|
||||||
form={dictItemForm}
|
|
||||||
layout="vertical"
|
|
||||||
onFinish={handleEditDictItemFormSubmit}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
label="名称"
|
|
||||||
name="name"
|
|
||||||
rules={[{ required: true, message: '请输入名称' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="名称 (e.g., zyn)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="标题"
|
|
||||||
name="title"
|
|
||||||
rules={[{ required: true, message: '请输入标题' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="标题 (e.g., ZYN)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="中文标题" name="titleCN">
|
|
||||||
<Input placeholder="中文标题 (e.g., 品牌)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="简称 (可选)" name="shortName">
|
|
||||||
<Input placeholder="简称 (可选)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="图片 (可选)" name="image">
|
|
||||||
<Input placeholder="图片链接 (可选)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="值 (可选)" name="value">
|
|
||||||
<Input placeholder="值 (可选)" />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
{/* 添加字典 Modal */}
|
{/* 添加字典 Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { ActionType } from '@ant-design/pro-components';
|
||||||
|
import { Space } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import DictItemAddButton from './DictItemAddButton';
|
||||||
|
import DictItemExportButton from './DictItemExportButton';
|
||||||
|
import DictItemImportButton from './DictItemImportButton';
|
||||||
|
|
||||||
|
// 字典项操作组合组件的属性接口
|
||||||
|
interface DictItemActionsProps {
|
||||||
|
// 当前选中的字典
|
||||||
|
selectedDict?: any;
|
||||||
|
// ProTable 的 actionRef,用于刷新列表
|
||||||
|
actionRef?: React.MutableRefObject<ActionType | undefined>;
|
||||||
|
// 是否显示导出按钮(某些页面可能不需要导出功能)
|
||||||
|
showExport?: boolean;
|
||||||
|
// 导入字典项的回调函数(如果不提供,则使用默认的导入逻辑)
|
||||||
|
onImport?: (file: File, dictId: number) => Promise<any>;
|
||||||
|
// 导出字典项的回调函数
|
||||||
|
onExport?: () => Promise<void>;
|
||||||
|
// 添加字典项的回调函数
|
||||||
|
onAdd?: () => void;
|
||||||
|
// 刷新字典列表的回调函数(导入成功后可能需要刷新左侧字典列表)
|
||||||
|
onRefreshDicts?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字典项操作组合组件(包含添加、导入、导出按钮)
|
||||||
|
const DictItemActions: React.FC<DictItemActionsProps> = ({
|
||||||
|
selectedDict,
|
||||||
|
actionRef,
|
||||||
|
showExport = true,
|
||||||
|
onImport,
|
||||||
|
onExport,
|
||||||
|
onAdd,
|
||||||
|
onRefreshDicts,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
{/* 添加字典项按钮 */}
|
||||||
|
{onAdd && <DictItemAddButton disabled={!selectedDict} onClick={onAdd} />}
|
||||||
|
|
||||||
|
{/* 导入字典项按钮 */}
|
||||||
|
<DictItemImportButton
|
||||||
|
selectedDict={selectedDict}
|
||||||
|
actionRef={actionRef}
|
||||||
|
onImport={onImport}
|
||||||
|
onRefreshDicts={onRefreshDicts}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 导出字典项按钮 */}
|
||||||
|
{showExport && (
|
||||||
|
<DictItemExportButton selectedDict={selectedDict} onExport={onExport} />
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DictItemActions;
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// 字典项添加按钮组件的属性接口
|
||||||
|
interface DictItemAddButtonProps {
|
||||||
|
// 是否禁用按钮
|
||||||
|
disabled?: boolean;
|
||||||
|
// 点击按钮时的回调函数
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字典项添加按钮组件
|
||||||
|
const DictItemAddButton: React.FC<DictItemAddButtonProps> = ({
|
||||||
|
disabled = false,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Button type="primary" size="small" onClick={onClick} disabled={disabled}>
|
||||||
|
添加字典项
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DictItemAddButton;
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { DownloadOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, message } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// 字典项导出按钮组件的属性接口
|
||||||
|
interface DictItemExportButtonProps {
|
||||||
|
// 当前选中的字典
|
||||||
|
selectedDict?: any;
|
||||||
|
// 是否禁用按钮
|
||||||
|
disabled?: boolean;
|
||||||
|
// 自定义导出函数
|
||||||
|
onExport?: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字典项导出按钮组件
|
||||||
|
const DictItemExportButton: React.FC<DictItemExportButtonProps> = ({
|
||||||
|
selectedDict,
|
||||||
|
disabled = false,
|
||||||
|
onExport,
|
||||||
|
}) => {
|
||||||
|
// 处理导出操作
|
||||||
|
const handleExport = async () => {
|
||||||
|
if (!selectedDict) {
|
||||||
|
message.warning('请先选择字典');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 如果提供了自定义导出函数,则使用自定义函数
|
||||||
|
if (onExport) {
|
||||||
|
await onExport();
|
||||||
|
} else {
|
||||||
|
// 如果没有提供自定义导出函数,这里可以添加默认逻辑
|
||||||
|
message.warning('未提供导出函数');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<DownloadOutlined />}
|
||||||
|
onClick={handleExport}
|
||||||
|
disabled={disabled || !selectedDict}
|
||||||
|
>
|
||||||
|
导出
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DictItemExportButton;
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
import { ActionType } from '@ant-design/pro-components';
|
||||||
|
import { request } from '@umijs/max';
|
||||||
|
import { Button, Upload, message } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// 字典项导入按钮组件的属性接口
|
||||||
|
interface DictItemImportButtonProps {
|
||||||
|
// 当前选中的字典
|
||||||
|
selectedDict?: any;
|
||||||
|
// ProTable 的 actionRef,用于刷新列表
|
||||||
|
actionRef?: React.MutableRefObject<ActionType | undefined>;
|
||||||
|
// 是否禁用按钮
|
||||||
|
disabled?: boolean;
|
||||||
|
// 自定义导入函数,返回 Promise(如果不提供,则使用默认的导入逻辑)
|
||||||
|
onImport?: (file: File, dictId: number) => Promise<any>;
|
||||||
|
// 导入成功后刷新字典列表的回调函数
|
||||||
|
onRefreshDicts?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字典项导入按钮组件
|
||||||
|
const DictItemImportButton: React.FC<DictItemImportButtonProps> = ({
|
||||||
|
selectedDict,
|
||||||
|
actionRef,
|
||||||
|
disabled = false,
|
||||||
|
onImport,
|
||||||
|
onRefreshDicts,
|
||||||
|
}) => {
|
||||||
|
// 处理导入文件上传
|
||||||
|
const handleImportUpload = async (options: any) => {
|
||||||
|
console.log(options);
|
||||||
|
const { file, onSuccess, onError } = options;
|
||||||
|
try {
|
||||||
|
// 条件判断,确保已选择字典
|
||||||
|
if (!selectedDict?.id) {
|
||||||
|
throw new Error('请先选择字典');
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: any;
|
||||||
|
// 如果提供了自定义导入函数,则使用自定义函数
|
||||||
|
if (onImport) {
|
||||||
|
result = await onImport(file as File, selectedDict.id);
|
||||||
|
} else {
|
||||||
|
// 使用默认的导入逻辑,将 dictId 传入到 body 中
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file as File);
|
||||||
|
formData.append('dictId', String(selectedDict.id));
|
||||||
|
|
||||||
|
result = await request('/api/dict/item/import', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查返回结果是否包含 success 字段
|
||||||
|
if (result && result.success !== undefined && !result.success) {
|
||||||
|
throw new Error(result.message || '导入失败');
|
||||||
|
}
|
||||||
|
onSuccess?.(result);
|
||||||
|
|
||||||
|
// 显示导入结果详情
|
||||||
|
showImportResult(result);
|
||||||
|
|
||||||
|
// 导入成功后刷新列表
|
||||||
|
setTimeout(() => {
|
||||||
|
actionRef?.current?.reload();
|
||||||
|
onRefreshDicts?.();
|
||||||
|
}, 100);
|
||||||
|
} catch (error: any) {
|
||||||
|
onError?.(error as Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示导入结果详情
|
||||||
|
const showImportResult = (result: any) => {
|
||||||
|
// 从 result.data 中获取实际数据(因为后端返回格式为 { success: true, data: {...} })
|
||||||
|
const data = result.data || result;
|
||||||
|
const { total, processed, updated, created, errors } = data;
|
||||||
|
|
||||||
|
// 构建结果消息
|
||||||
|
let messageContent = `总共处理 ${total} 条,成功处理 ${processed} 条,新增 ${created} 条,更新 ${updated} 条`;
|
||||||
|
|
||||||
|
if (errors && errors.length > 0) {
|
||||||
|
messageContent += `,失败 ${errors.length} 条`;
|
||||||
|
// 显示错误详情
|
||||||
|
const errorDetails = errors
|
||||||
|
.map((err: any) => `${err.identifier}: ${err.error}`)
|
||||||
|
.join('\n');
|
||||||
|
message.warning(messageContent + '\n\n错误详情: \n' + errorDetails);
|
||||||
|
} else {
|
||||||
|
message.success(messageContent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理上传状态变化
|
||||||
|
const handleUploadChange = (info: any) => {
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
message.success(`${info.file.name} 文件上传成功`);
|
||||||
|
} else if (info.file.status === 'error') {
|
||||||
|
message.error(`${info.file.name} 文件上传失败`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Upload
|
||||||
|
name="file"
|
||||||
|
action={undefined}
|
||||||
|
customRequest={handleImportUpload}
|
||||||
|
showUploadList={false}
|
||||||
|
disabled={disabled || !selectedDict}
|
||||||
|
onChange={handleUploadChange}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<UploadOutlined />}
|
||||||
|
disabled={disabled || !selectedDict}
|
||||||
|
>
|
||||||
|
导入字典项
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DictItemImportButton;
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
import { Form, Input, Modal } from 'antd';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
|
interface DictItemModalProps {
|
||||||
|
// 模态框是否可见
|
||||||
|
visible: boolean;
|
||||||
|
// 是否为编辑模式
|
||||||
|
isEdit: boolean;
|
||||||
|
// 编辑时的字典项数据
|
||||||
|
editingData?: any;
|
||||||
|
// 当前选中的字典
|
||||||
|
selectedDict?: any;
|
||||||
|
// 取消回调
|
||||||
|
onCancel: () => void;
|
||||||
|
// 确认回调
|
||||||
|
onOk: (values: any) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DictItemModal: React.FC<DictItemModalProps> = ({
|
||||||
|
visible,
|
||||||
|
isEdit,
|
||||||
|
editingData,
|
||||||
|
selectedDict,
|
||||||
|
onCancel,
|
||||||
|
onOk,
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
// 当模态框打开或编辑数据变化时,重置或设置表单值
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
if (isEdit && editingData) {
|
||||||
|
// 编辑模式,设置表单值为编辑数据
|
||||||
|
form.setFieldsValue(editingData);
|
||||||
|
} else {
|
||||||
|
// 新增模式,重置表单
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [visible, isEdit, editingData, form]);
|
||||||
|
|
||||||
|
// 表单提交处理
|
||||||
|
const handleOk = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
await onOk(values);
|
||||||
|
} catch (error) {
|
||||||
|
// 表单验证失败,不关闭模态框
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
isEdit
|
||||||
|
? '编辑字典项'
|
||||||
|
: `添加字典项 - ${selectedDict?.title || '未选择字典'}`
|
||||||
|
}
|
||||||
|
open={visible}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical">
|
||||||
|
<Form.Item
|
||||||
|
label="名称"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: '请输入名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="名称 (e.g., zyn)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="标题"
|
||||||
|
name="title"
|
||||||
|
rules={[{ required: true, message: '请输入标题' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="标题 (e.g., ZYN)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="中文标题" name="titleCN">
|
||||||
|
<Input placeholder="中文标题 (e.g., 品牌)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="简称 (可选)" name="shortName">
|
||||||
|
<Input placeholder="简称 (可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="图片 (可选)" name="image">
|
||||||
|
<Input placeholder="图片链接 (可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="值 (可选)" name="value">
|
||||||
|
<Input placeholder="值 (可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DictItemModal;
|
||||||
|
|
@ -151,7 +151,10 @@ const OrderItemsPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
request={request}
|
request={request}
|
||||||
pagination={{ showSizeChanger: true }}
|
pagination={{
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
}}
|
||||||
search={{ labelWidth: 90, span: 6 }}
|
search={{ labelWidth: 90, span: 6 }}
|
||||||
toolBarRender={false}
|
toolBarRender={false}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import styles from '../../../style/order-list.css';
|
||||||
|
|
||||||
import InternationalPhoneInput from '@/components/InternationalPhoneInput';
|
import InternationalPhoneInput from '@/components/InternationalPhoneInput';
|
||||||
import SyncForm from '@/components/SyncForm';
|
import SyncForm from '@/components/SyncForm';
|
||||||
|
import { showSyncResult, SyncResultData } from '@/components/SyncResultMessage';
|
||||||
import { ORDER_STATUS_ENUM } from '@/constants';
|
import { ORDER_STATUS_ENUM } from '@/constants';
|
||||||
import { HistoryOrder } from '@/pages/Statistics/Order';
|
import { HistoryOrder } from '@/pages/Statistics/Order';
|
||||||
import {
|
import {
|
||||||
|
|
@ -21,6 +22,7 @@ import {
|
||||||
ordercontrollerGetorders,
|
ordercontrollerGetorders,
|
||||||
ordercontrollerRefundorder,
|
ordercontrollerRefundorder,
|
||||||
ordercontrollerSyncorderbyid,
|
ordercontrollerSyncorderbyid,
|
||||||
|
ordercontrollerSyncorders,
|
||||||
ordercontrollerUpdateorderitems,
|
ordercontrollerUpdateorderitems,
|
||||||
} from '@/servers/api/order';
|
} from '@/servers/api/order';
|
||||||
import { productcontrollerSearchproducts } from '@/servers/api/product';
|
import { productcontrollerSearchproducts } from '@/servers/api/product';
|
||||||
|
|
@ -73,8 +75,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>();
|
||||||
|
|
@ -187,11 +189,20 @@ const ListPage: React.FC = () => {
|
||||||
dataIndex: 'siteId',
|
dataIndex: 'siteId',
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
request: async () => {
|
request: async () => {
|
||||||
const { data = [] } = await sitecontrollerAll();
|
try {
|
||||||
return data.map((item) => ({
|
const result = await sitecontrollerAll();
|
||||||
label: item.name,
|
const {success, data}= result
|
||||||
value: item.id,
|
if (success && data) {
|
||||||
}));
|
return data.map((site: any) => ({
|
||||||
|
label: site.name,
|
||||||
|
value: site.id,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取站点列表失败:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -199,16 +210,22 @@ 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',
|
||||||
|
|
@ -250,22 +267,23 @@ 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: 'shipmentList',
|
dataIndex: 'fulfillments',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{(record as any)?.shipmentList?.map((item: any) => {
|
{(record as any)?.fulfillments?.map((item: any) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ display:"flex", alignItems:"center",'flexDirection':'column' }}>
|
||||||
{item.tracking_provider}:{item.primary_tracking_number} (
|
<span>物流供应商: {item.shipping_provider}</span>
|
||||||
{formatShipmentState(item.state)})
|
<span>物流单号: {item.tracking_number}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -276,7 +294,6 @@ const ListPage: React.FC = () => {
|
||||||
{
|
{
|
||||||
title: 'IP',
|
title: 'IP',
|
||||||
dataIndex: 'customer_ip_address',
|
dataIndex: 'customer_ip_address',
|
||||||
hideInSearch: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '设备',
|
title: '设备',
|
||||||
|
|
@ -339,15 +356,18 @@ const ListPage: React.FC = () => {
|
||||||
message.error('站点ID或外部订单ID不存在');
|
message.error('站点ID或外部订单ID不存在');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { success, message: errMsg } =
|
const {
|
||||||
await ordercontrollerSyncorderbyid({
|
success,
|
||||||
siteId: record.siteId,
|
message: errMsg,
|
||||||
orderId: record.externalOrderId,
|
data,
|
||||||
});
|
} = await ordercontrollerSyncorderbyid({
|
||||||
|
siteId: record.siteId,
|
||||||
|
orderId: record.externalOrderId,
|
||||||
|
});
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
message.success('同步成功');
|
showSyncResult(data as SyncResultData, '订单');
|
||||||
actionRef.current?.reload();
|
actionRef.current?.reload();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error?.message || '同步失败');
|
message.error(error?.message || '同步失败');
|
||||||
|
|
@ -464,19 +484,24 @@ 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 { success, message: errMsg } =
|
const {
|
||||||
await ordercontrollerSyncorderbyid(values);
|
success,
|
||||||
|
message: errMsg,
|
||||||
|
data,
|
||||||
|
} = await ordercontrollerSyncorders(values);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
message.success('同步成功');
|
// 使用 showSyncResult 函数显示详细的同步结果
|
||||||
|
showSyncResult(data as SyncResultData, '订单');
|
||||||
actionRef.current?.reload();
|
actionRef.current?.reload();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error?.message || '同步失败');
|
message.error(error?.message || '同步失败');
|
||||||
|
|
@ -484,31 +509,24 @@ const ListPage: React.FC = () => {
|
||||||
}}
|
}}
|
||||||
tableRef={actionRef}
|
tableRef={actionRef}
|
||||||
/>,
|
/>,
|
||||||
// <Button
|
<Popconfirm
|
||||||
// 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/order/export', {
|
const res = await request('/order/export', {
|
||||||
method: 'GET',
|
method: 'POST',
|
||||||
params: {
|
data: {
|
||||||
ids: selectedRowKeys,
|
ids: selectedRowKeys,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (res?.success && res?.data?.csv) {
|
if (res?.success && res.data) {
|
||||||
const blob = new Blob([res.data.csv], { type: 'text/csv;charset=utf-8;' });
|
const blob = new Blob([res.data], { 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 = 'customers.csv';
|
a.download = 'orders.csv';
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -521,10 +539,13 @@ const ListPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button type="primary" ghost>
|
<Button
|
||||||
|
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') {
|
||||||
|
|
@ -613,33 +634,36 @@ 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,
|
||||||
</Button>,
|
data,
|
||||||
]),
|
} = 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" />,
|
||||||
|
|
@ -658,152 +682,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) {
|
{
|
||||||
throw new Error(errMsg);
|
status: 'pending_reshipment',
|
||||||
}
|
},
|
||||||
tableRef.current?.reload();
|
);
|
||||||
} catch (error: any) {
|
if (!success) {
|
||||||
message.error(error.message);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
}}
|
tableRef.current?.reload();
|
||||||
>
|
} catch (error: any) {
|
||||||
<Button type="primary" ghost>
|
message.error(error.message);
|
||||||
转至取消
|
}
|
||||||
</Button>
|
}}
|
||||||
</Popconfirm>,
|
>
|
||||||
<Divider type="vertical" />,
|
<Button type="primary" ghost>
|
||||||
<Popconfirm
|
转至待补发
|
||||||
title="转至退款"
|
</Button>
|
||||||
description="确认转至退款?"
|
</Popconfirm>,
|
||||||
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>,
|
|
||||||
]
|
|
||||||
: []),
|
: []),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -1065,31 +1089,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();
|
||||||
<DeleteFilled />
|
} catch (error: any) {
|
||||||
取消运单
|
message.error(error.message);
|
||||||
</Popconfirm>,
|
}
|
||||||
]
|
}}
|
||||||
|
>
|
||||||
|
<DeleteFilled />
|
||||||
|
取消运单
|
||||||
|
</Popconfirm>,
|
||||||
|
]
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -1477,16 +1501,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
|
||||||
|
|
@ -1911,7 +1935,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>
|
||||||
|
|
@ -2122,12 +2146,12 @@ const SalesChange: React.FC<{
|
||||||
params={{}}
|
params={{}}
|
||||||
request={async ({ keyWords }) => {
|
request={async ({ keyWords }) => {
|
||||||
try {
|
try {
|
||||||
const { data } = await wpproductcontrollerSearchproducts({
|
const { data } = await productcontrollerSearchproducts({
|
||||||
name: keyWords,
|
name: keyWords,
|
||||||
});
|
});
|
||||||
return data?.map((item) => {
|
return data?.map((item) => {
|
||||||
return {
|
return {
|
||||||
label: `${item.name}`,
|
label: `${item.name} - ${item.nameCn}`,
|
||||||
value: item?.sku,
|
value: item?.sku,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,14 @@
|
||||||
import { UploadOutlined } from '@ant-design/icons';
|
import * as dictApi from '@/servers/api/dict';
|
||||||
import {
|
import {
|
||||||
ActionType,
|
ActionType,
|
||||||
PageContainer,
|
PageContainer,
|
||||||
ProTable,
|
ProTable,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import {
|
import { Button, Input, Layout, Space, Table, message } from 'antd';
|
||||||
Button,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
Layout,
|
|
||||||
Modal,
|
|
||||||
Space,
|
|
||||||
Table,
|
|
||||||
Upload,
|
|
||||||
message,
|
|
||||||
} from 'antd';
|
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import DictItemActions from '../../Dict/components/DictItemActions';
|
||||||
|
import DictItemModal from '../../Dict/components/DictItemModal';
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
|
|
@ -32,10 +24,84 @@ const AttributePage: React.FC = () => {
|
||||||
// 右侧字典项 ProTable 的引用
|
// 右侧字典项 ProTable 的引用
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
// 字典项新增/编辑模态框控制
|
// 字典项模态框状态(由 DictItemModal 组件管理)
|
||||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||||
const [editingDictItem, setEditingDictItem] = useState<any>(null);
|
const [isEditDictItem, setIsEditDictItem] = useState(false);
|
||||||
const [dictItemForm] = Form.useForm();
|
const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
|
||||||
|
|
||||||
|
// 导出字典项数据
|
||||||
|
const handleExportDictItems = async () => {
|
||||||
|
// 条件判断,确保已选择字典
|
||||||
|
if (!selectedDict) {
|
||||||
|
message.warning('请先选择字典');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取当前字典的所有数据
|
||||||
|
const response = await request('/dict/items', {
|
||||||
|
params: {
|
||||||
|
dictId: selectedDict.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 确保返回的是数组
|
||||||
|
const data = Array.isArray(response) ? response : response?.data || [];
|
||||||
|
|
||||||
|
// 条件判断,检查是否有数据可导出
|
||||||
|
if (data.length === 0) {
|
||||||
|
message.warning('当前字典没有数据可导出');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将数据转换为CSV格式
|
||||||
|
const headers = [
|
||||||
|
'name',
|
||||||
|
'title',
|
||||||
|
'titleCN',
|
||||||
|
'value',
|
||||||
|
'sort',
|
||||||
|
'image',
|
||||||
|
'shortName',
|
||||||
|
];
|
||||||
|
const csvContent = [
|
||||||
|
headers.join(','),
|
||||||
|
...data.map((item: any) =>
|
||||||
|
headers
|
||||||
|
.map((header) => {
|
||||||
|
const value = item[header] || '';
|
||||||
|
// 条件判断,如果值包含逗号或引号,需要转义
|
||||||
|
if (
|
||||||
|
typeof value === 'string' &&
|
||||||
|
(value.includes(',') || value.includes('"'))
|
||||||
|
) {
|
||||||
|
return `"${value.replace(/"/g, '""')}"`;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
.join(','),
|
||||||
|
),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
// 创建blob并下载
|
||||||
|
const blob = new Blob(['\ufeff' + csvContent], {
|
||||||
|
// 添加BOM以支持中文
|
||||||
|
type: 'text/csv;charset=utf-8',
|
||||||
|
});
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('download', `${selectedDict.name}_dict_items.csv`);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
message.success(`成功导出 ${data.length} 条数据`);
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchDicts = async (title?: string) => {
|
const fetchDicts = async (title?: string) => {
|
||||||
setLoadingDicts(true);
|
setLoadingDicts(true);
|
||||||
|
|
@ -65,24 +131,24 @@ const AttributePage: React.FC = () => {
|
||||||
|
|
||||||
// 打开添加字典项模态框
|
// 打开添加字典项模态框
|
||||||
const handleAddDictItem = () => {
|
const handleAddDictItem = () => {
|
||||||
setEditingDictItem(null);
|
setIsEditDictItem(false);
|
||||||
dictItemForm.resetFields();
|
setEditingDictItemData(null);
|
||||||
setIsDictItemModalVisible(true);
|
setIsDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开编辑字典项模态框
|
// 打开编辑字典项模态框
|
||||||
const handleEditDictItem = (item: any) => {
|
const handleEditDictItem = (item: any) => {
|
||||||
setEditingDictItem(item);
|
setIsEditDictItem(true);
|
||||||
dictItemForm.setFieldsValue(item);
|
setEditingDictItemData(item);
|
||||||
setIsDictItemModalVisible(true);
|
setIsDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 字典项表单提交(新增或编辑)
|
// 字典项表单提交(新增或编辑)
|
||||||
const handleDictItemFormSubmit = async (values: any) => {
|
const handleDictItemFormSubmit = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
if (editingDictItem) {
|
if (isEditDictItem && editingDictItemData) {
|
||||||
// 条件判断,存在编辑项则执行更新
|
// 条件判断,存在编辑项则执行更新
|
||||||
await request(`/dict/item/${editingDictItem.id}`, {
|
await request(`/dict/item/${editingDictItemData.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: values,
|
data: values,
|
||||||
});
|
});
|
||||||
|
|
@ -98,7 +164,7 @@ const AttributePage: React.FC = () => {
|
||||||
setIsDictItemModalVisible(false);
|
setIsDictItemModalVisible(false);
|
||||||
actionRef.current?.reload(); // 刷新 ProTable
|
actionRef.current?.reload(); // 刷新 ProTable
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(editingDictItem ? '更新失败' : '添加失败');
|
message.error(isEditDictItem ? '更新失败' : '添加失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -296,85 +362,45 @@ const AttributePage: React.FC = () => {
|
||||||
size="small"
|
size="small"
|
||||||
key={selectedDict?.id}
|
key={selectedDict?.id}
|
||||||
headerTitle={
|
headerTitle={
|
||||||
<Space>
|
<DictItemActions
|
||||||
<Button
|
selectedDict={selectedDict}
|
||||||
type="primary"
|
actionRef={actionRef}
|
||||||
size="small"
|
showExport={true}
|
||||||
onClick={handleAddDictItem}
|
onImport={async (file: File, dictId: number) => {
|
||||||
disabled={!selectedDict}
|
// 创建 FormData 对象
|
||||||
>
|
const formData = new FormData();
|
||||||
添加字典项
|
// 添加文件到 FormData
|
||||||
</Button>
|
formData.append('file', file);
|
||||||
<Upload
|
// 添加字典 ID 到 FormData
|
||||||
name="file"
|
formData.append('dictId', String(dictId));
|
||||||
action={`/dict/item/import`}
|
// 调用导入字典项的 API
|
||||||
data={{ dictId: selectedDict?.id }}
|
const response = await dictApi.dictcontrollerImportdictitems(
|
||||||
showUploadList={false}
|
formData,
|
||||||
disabled={!selectedDict}
|
);
|
||||||
onChange={(info) => {
|
// 返回 JSON 响应
|
||||||
// 条件判断,上传状态处理
|
return await response.json();
|
||||||
if (info.file.status === 'done') {
|
}}
|
||||||
message.success(`${info.file.name} 文件上传成功`);
|
onExport={handleExportDictItems}
|
||||||
actionRef.current?.reload();
|
onAdd={handleAddDictItem}
|
||||||
} else if (info.file.status === 'error') {
|
onRefreshDicts={fetchDicts}
|
||||||
message.error(`${info.file.name} 文件上传失败`);
|
/>
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
icon={<UploadOutlined />}
|
|
||||||
disabled={!selectedDict}
|
|
||||||
>
|
|
||||||
导入字典项
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
|
||||||
</Space>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Modal
|
{/* 字典项 Modal(添加或编辑) */}
|
||||||
title={editingDictItem ? '编辑字典项' : '添加字典项'}
|
<DictItemModal
|
||||||
open={isDictItemModalVisible}
|
visible={isDictItemModalVisible}
|
||||||
onOk={() => dictItemForm.submit()}
|
isEdit={isEditDictItem}
|
||||||
onCancel={() => setIsDictItemModalVisible(false)}
|
editingData={editingDictItemData}
|
||||||
destroyOnClose
|
selectedDict={selectedDict}
|
||||||
>
|
onCancel={() => {
|
||||||
<Form
|
setIsDictItemModalVisible(false);
|
||||||
form={dictItemForm}
|
setEditingDictItemData(null);
|
||||||
layout="vertical"
|
}}
|
||||||
onFinish={handleDictItemFormSubmit}
|
onOk={handleDictItemFormSubmit}
|
||||||
>
|
/>
|
||||||
<Form.Item
|
|
||||||
label="名称"
|
|
||||||
name="name"
|
|
||||||
rules={[{ required: true, message: '请输入名称' }]}
|
|
||||||
>
|
|
||||||
<Input size="small" placeholder="名称 (e.g., zyn)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="标题"
|
|
||||||
name="title"
|
|
||||||
rules={[{ required: true, message: '请输入标题' }]}
|
|
||||||
>
|
|
||||||
<Input size="small" placeholder="标题 (e.g., ZYN)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="中文标题" name="titleCN">
|
|
||||||
<Input size="small" placeholder="中文标题 (e.g., 品牌)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="简称 (可选)" name="shortName">
|
|
||||||
<Input size="small" placeholder="简称 (可选)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="图片 (可选)" name="image">
|
|
||||||
<Input size="small" placeholder="图片链接 (可选)" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="值 (可选)" name="value">
|
|
||||||
<Input size="small" placeholder="值 (可选)" />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default function CsvTool() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>产品CSV 工具</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ 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';
|
||||||
|
|
@ -76,7 +77,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,
|
||||||
|
|
@ -85,10 +86,25 @@ const CreateForm: React.FC<{
|
||||||
} = await templatecontrollerRendertemplate(
|
} = await templatecontrollerRendertemplate(
|
||||||
{ name: 'product.sku' },
|
{ name: 'product.sku' },
|
||||||
{
|
{
|
||||||
brand: brandName || '',
|
category: formValues.category,
|
||||||
strength: strengthName || '',
|
attributes: [
|
||||||
flavor: flavorName || '',
|
{
|
||||||
humidity: humidityName ? capitalize(humidityName) : '',
|
dict: {name: "brand"},
|
||||||
|
shortName: brandName || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dict: {name: "flavor"},
|
||||||
|
shortName: flavorName || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dict: {name: "strength"},
|
||||||
|
shortName: strengthName || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dict: {name: "humidity"},
|
||||||
|
shortName: humidityName ? capitalize(humidityName) : '',
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|
@ -137,8 +153,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) {
|
||||||
|
|
@ -200,19 +216,23 @@ const CreateForm: React.FC<{
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onFinish={async (values: any) => {
|
onFinish={async (values: any) => {
|
||||||
// 组装 attributes(根据 activeAttributes 动态组装)
|
// 根据产品类型决定是否组装 attributes
|
||||||
const attributes = activeAttributes.flatMap((attr: any) => {
|
// 如果产品类型为 bundle,则 attributes 为空数组
|
||||||
const dictName = attr.name;
|
// 如果产品类型为 single,则根据 activeAttributes 动态组装 attributes
|
||||||
const key = `${dictName}Values`;
|
const attributes = values.type === 'bundle'
|
||||||
const vals = values[key];
|
? []
|
||||||
if (vals && Array.isArray(vals)) {
|
: activeAttributes.flatMap((attr: any) => {
|
||||||
return vals.map((v: string) => ({
|
const dictName = attr.name;
|
||||||
dictName: dictName,
|
const key = `${dictName}Values`;
|
||||||
name: v,
|
const vals = values[key];
|
||||||
}));
|
if (vals && Array.isArray(vals)) {
|
||||||
}
|
return vals.map((v: string) => ({
|
||||||
return [];
|
dictName: dictName,
|
||||||
});
|
name: v,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
const payload: any = {
|
const payload: any = {
|
||||||
name: (values as any).name,
|
name: (values as any).name,
|
||||||
|
|
@ -246,13 +266,7 @@ const CreateForm: React.FC<{
|
||||||
placeholder="请输入SKU"
|
placeholder="请输入SKU"
|
||||||
rules={[{ required: true, message: '请输入SKU' }]}
|
rules={[{ required: true, message: '请输入SKU' }]}
|
||||||
/>
|
/>
|
||||||
<ProFormSelect
|
|
||||||
name="siteSkus"
|
|
||||||
label="站点 SKU 列表"
|
|
||||||
width="md"
|
|
||||||
mode="tags"
|
|
||||||
placeholder="输入站点 SKU,回车添加"
|
|
||||||
/>
|
|
||||||
<Button style={{ marginTop: '32px' }} onClick={handleGenerateSku}>
|
<Button style={{ marginTop: '32px' }} onClick={handleGenerateSku}>
|
||||||
自动生成
|
自动生成
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -265,6 +279,14 @@ const CreateForm: React.FC<{
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
|
<ProFormSelect
|
||||||
|
name="siteSkus"
|
||||||
|
initialValue={[]}
|
||||||
|
label="站点 SKU 列表"
|
||||||
|
width="md"
|
||||||
|
mode="tags"
|
||||||
|
placeholder="输入站点 SKU,回车添加"
|
||||||
|
/>
|
||||||
<ProForm.Group>
|
<ProForm.Group>
|
||||||
<ProFormText
|
<ProFormText
|
||||||
name="name"
|
name="name"
|
||||||
|
|
@ -313,15 +335,18 @@ 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 }
|
? { sku: keyWords, name: keyWords, type: 'single' }
|
||||||
: { pageSize: 9999 };
|
: { pageSize: 9999, type: 'single' };
|
||||||
const { data } = await getStocks(params as any);
|
const { data } = await productcontrollerGetproductlist(
|
||||||
|
params as any,
|
||||||
|
);
|
||||||
if (!data || !data.items) {
|
if (!data || !data.items) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
// 只返回类型为单品的产品
|
||||||
return data.items
|
return data.items
|
||||||
.filter((item) => item.sku)
|
.filter((item: any) => item.type === 'single' && item.sku)
|
||||||
.map((item) => ({
|
.map((item: any) => ({
|
||||||
label: `${item.sku} - ${item.name}`,
|
label: `${item.sku} - ${item.name}`,
|
||||||
value: item.sku,
|
value: item.sku,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
productcontrollerGetcategoriesall,
|
productcontrollerGetcategoriesall,
|
||||||
productcontrollerGetcategoryattributes,
|
productcontrollerGetcategoryattributes,
|
||||||
productcontrollerGetproductcomponents,
|
productcontrollerGetproductcomponents,
|
||||||
|
productcontrollerGetproductlist,
|
||||||
productcontrollerGetproductsiteskus,
|
productcontrollerGetproductsiteskus,
|
||||||
productcontrollerUpdateproduct,
|
productcontrollerUpdateproduct,
|
||||||
} from '@/servers/api/product';
|
} from '@/servers/api/product';
|
||||||
|
|
@ -315,17 +316,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
|
||||||
? { sku: keyWords, name: keyWords }
|
? { where: {sku: keyWords, name: keyWords, type: 'single'} }
|
||||||
: { pageSize: 9999 };
|
: { 'per_page': 9999 , where: {type: 'single'} };
|
||||||
const { data } = await getStocks(params as any);
|
const { data } = await productcontrollerGetproductlist(params);
|
||||||
if (!data || !data.items) {
|
if (!data || !data.items) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
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;
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
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,15 +1,12 @@
|
||||||
import {
|
import {
|
||||||
productcontrollerBatchdeleteproduct,
|
productcontrollerBatchdeleteproduct,
|
||||||
productcontrollerBatchupdateproduct,
|
productcontrollerBatchupdateproduct,
|
||||||
productcontrollerBindproductsiteskus,
|
|
||||||
productcontrollerDeleteproduct,
|
productcontrollerDeleteproduct,
|
||||||
productcontrollerGetcategoriesall,
|
productcontrollerGetcategoriesall,
|
||||||
productcontrollerGetproductcomponents,
|
productcontrollerGetproductcomponents,
|
||||||
productcontrollerGetproductlist,
|
productcontrollerGetproductlist,
|
||||||
productcontrollerUpdatenamecn,
|
productcontrollerUpdatenamecn
|
||||||
} from '@/servers/api/product';
|
} from '@/servers/api/product';
|
||||||
import { sitecontrollerAll } from '@/servers/api/site';
|
|
||||||
import { siteapicontrollerGetproducts } from '@/servers/api/siteApi';
|
|
||||||
import {
|
import {
|
||||||
ActionType,
|
ActionType,
|
||||||
ModalForm,
|
ModalForm,
|
||||||
|
|
@ -17,13 +14,14 @@ import {
|
||||||
ProColumns,
|
ProColumns,
|
||||||
ProFormSelect,
|
ProFormSelect,
|
||||||
ProFormText,
|
ProFormText,
|
||||||
ProTable,
|
ProTable
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd';
|
import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import CreateForm from './CreateForm';
|
import CreateForm from './CreateForm';
|
||||||
import EditForm from './EditForm';
|
import EditForm from './EditForm';
|
||||||
|
import SyncToSiteModal from './SyncToSiteModal';
|
||||||
|
|
||||||
const NameCn: React.FC<{
|
const NameCn: React.FC<{
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -69,16 +67,7 @@ const AttributesCell: React.FC<{ record: any }> = ({ record }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ComponentsCell: React.FC<{ productId: number }> = ({ productId }) => {
|
const ComponentsCell: React.FC<{ components?: any[] }> = ({ components }) => {
|
||||||
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 ? (
|
||||||
|
|
@ -165,251 +154,25 @@ 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
|
||||||
headerTitle="站点产品信息"
|
request={async (pag) => {
|
||||||
actionRef={actionRef}
|
const { data, success } = await productcontrollerGetproductlist({
|
||||||
search={false}
|
where: filter
|
||||||
options={false}
|
});
|
||||||
pagination={false}
|
if (!success) return [];
|
||||||
toolBarRender={() => [
|
return data || [];
|
||||||
<Button
|
|
||||||
key="refresh"
|
|
||||||
type="primary"
|
|
||||||
onClick={() => actionRef.current?.reload()}
|
|
||||||
>
|
|
||||||
刷新
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
request={async () => {
|
|
||||||
// 判断是否存在站点SKU列表
|
|
||||||
if (!skus || skus.length === 0) return { data: [] };
|
|
||||||
try {
|
|
||||||
// 获取所有站点列表用于遍历查询
|
|
||||||
const { data: siteResponse } = await sitecontrollerAll();
|
|
||||||
const siteList = siteResponse || [];
|
|
||||||
// 聚合所有站点的产品数据
|
|
||||||
const aggregatedProducts: any[] = [];
|
|
||||||
// 遍历每一个站点
|
|
||||||
for (const siteItem of siteList) {
|
|
||||||
// 遍历每一个SKU在当前站点进行搜索
|
|
||||||
for (const skuCode of skus) {
|
|
||||||
// 直接调用站点API根据搜索关键字获取产品列表
|
|
||||||
const response = await siteapicontrollerGetproducts({
|
|
||||||
siteId: Number(siteItem.id),
|
|
||||||
per_page: 100,
|
|
||||||
search: skuCode,
|
|
||||||
});
|
|
||||||
const productPage = response as any;
|
|
||||||
const siteProducts = productPage?.data?.items || [];
|
|
||||||
// 将站点信息附加到产品数据中便于展示
|
|
||||||
siteProducts.forEach((p: any) => {
|
|
||||||
aggregatedProducts.push({
|
|
||||||
...p,
|
|
||||||
siteId: siteItem.id,
|
|
||||||
siteName: siteItem.name,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { data: aggregatedProducts, success: true };
|
|
||||||
} catch (error: any) {
|
|
||||||
// 请求失败进行错误提示
|
|
||||||
message.error(error?.message || '获取站点产品失败');
|
|
||||||
return { data: [], success: false };
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={columns}
|
||||||
{
|
pagination={false}
|
||||||
title: '站点',
|
rowKey="id"
|
||||||
dataIndex: 'siteName',
|
bordered
|
||||||
},
|
size="small"
|
||||||
{
|
scroll={{ x: "max-content" }}
|
||||||
title: 'SKU',
|
headerTitle={null}
|
||||||
dataIndex: 'sku',
|
toolBarRender={false}
|
||||||
},
|
|
||||||
{
|
|
||||||
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>,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -419,8 +182,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(带认证请求)
|
||||||
|
|
@ -454,13 +217,13 @@ const List: React.FC = () => {
|
||||||
sorter: true,
|
sorter: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '商品SKU',
|
title: '关联商品',
|
||||||
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>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|
@ -481,13 +244,7 @@ const List: React.FC = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
title: '商品类型',
|
|
||||||
dataIndex: 'category',
|
|
||||||
render: (_, record: any) => {
|
|
||||||
return record.category?.title || record.category?.name || '-';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '价格',
|
title: '价格',
|
||||||
dataIndex: 'price',
|
dataIndex: 'price',
|
||||||
|
|
@ -499,6 +256,13 @@ 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: '属性',
|
||||||
|
|
@ -531,7 +295,7 @@ const List: React.FC = () => {
|
||||||
title: '构成',
|
title: '构成',
|
||||||
dataIndex: 'components',
|
dataIndex: 'components',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
render: (_, record) => <ComponentsCell productId={(record as any).id} />,
|
render: (_, record) => <ComponentsCell components={record.components} />,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -564,7 +328,7 @@ const List: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSyncProductIds([record.id]);
|
setSyncProducts([record]);
|
||||||
setSyncModalVisible(true);
|
setSyncModalVisible(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -679,6 +443,7 @@ const List: React.FC = () => {
|
||||||
onError?.(error);
|
onError?.(error);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
>
|
>
|
||||||
<Button>批量导入</Button>
|
<Button>批量导入</Button>
|
||||||
</Upload>,
|
</Upload>,
|
||||||
|
|
@ -693,7 +458,7 @@ const List: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
disabled={selectedRows.length <= 0}
|
disabled={selectedRows.length <= 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSyncProductIds(selectedRows.map((row) => row.id));
|
setSyncProducts(selectedRows);
|
||||||
setSyncModalVisible(true);
|
setSyncModalVisible(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -741,9 +506,12 @@ 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({
|
||||||
...params,
|
where,
|
||||||
|
page: current || 1,
|
||||||
|
per_page: pageSize || 10,
|
||||||
sortField,
|
sortField,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
} as any);
|
} as any);
|
||||||
|
|
@ -754,17 +522,18 @@ const List: React.FC = () => {
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
expandable={{
|
// expandable={{
|
||||||
expandedRowRender: (record) => (
|
// expandedRowRender: (record) => {
|
||||||
<WpProductInfo
|
// return <ProductList filter={{
|
||||||
skus={(record.siteSkus as string[]) || []}
|
// skus: record.components?.map(component => component.sku) || [],
|
||||||
record={record}
|
// }}
|
||||||
parentTableRef={actionRef}
|
// columns={columns}
|
||||||
/>
|
// ></ProductList>
|
||||||
),
|
// }
|
||||||
rowExpandable: (record) =>
|
// ,
|
||||||
!!(record.siteSkus && record.siteSkus.length > 0),
|
// rowExpandable: (record) =>
|
||||||
}}
|
// !!(record.type==='bundle'),
|
||||||
|
// }}
|
||||||
editable={{
|
editable={{
|
||||||
type: 'single',
|
type: 'single',
|
||||||
onSave: async (key, record, originRow) => {
|
onSave: async (key, record, originRow) => {
|
||||||
|
|
@ -774,6 +543,11 @@ 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}
|
||||||
|
|
@ -788,8 +562,7 @@ const List: React.FC = () => {
|
||||||
<SyncToSiteModal
|
<SyncToSiteModal
|
||||||
visible={syncModalVisible}
|
visible={syncModalVisible}
|
||||||
onClose={() => setSyncModalVisible(false)}
|
onClose={() => setSyncModalVisible(false)}
|
||||||
productIds={syncProductIds}
|
products={syncProducts}
|
||||||
productRows={selectedRows}
|
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
setSyncModalVisible(false);
|
setSyncModalVisible(false);
|
||||||
setSelectedRows([]);
|
setSelectedRows([]);
|
||||||
|
|
@ -800,4 +573,5 @@ const List: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default List;
|
export default List;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
productcontrollerGetcategoryattributes,
|
productcontrollerGetcategoryattributes,
|
||||||
productcontrollerGetproductlist,
|
productcontrollerGetproductlist,
|
||||||
} from '@/servers/api/product';
|
} from '@/servers/api/product';
|
||||||
|
import { DownloadOutlined } from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
ActionType,
|
ActionType,
|
||||||
PageContainer,
|
PageContainer,
|
||||||
|
|
@ -201,6 +202,92 @@ const PermutationPage: React.FC = () => {
|
||||||
setCreateModalVisible(true);
|
setCreateModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理导出CSV功能
|
||||||
|
const handleExport = () => {
|
||||||
|
try {
|
||||||
|
// 如果没有数据则提示用户
|
||||||
|
if (permutations.length === 0) {
|
||||||
|
message.warning('暂无数据可导出');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成CSV表头(包含所有属性列和SKU列)
|
||||||
|
const headers = [
|
||||||
|
...attributes.map((attr) => attr.title || attr.name),
|
||||||
|
'SKU',
|
||||||
|
'状态',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 生成CSV数据行
|
||||||
|
const rows = permutations.map((perm) => {
|
||||||
|
const key = generateKeyFromPermutation(perm);
|
||||||
|
const product = existingProducts.get(key);
|
||||||
|
|
||||||
|
// 获取每个属性值
|
||||||
|
const attrValues = attributes.map((attr) => {
|
||||||
|
const value = perm[attr.name]?.name || '';
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取SKU和状态
|
||||||
|
const sku = product?.sku || '';
|
||||||
|
const status = product ? '已存在' : '未创建';
|
||||||
|
|
||||||
|
return [...attrValues, sku, status];
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将表头和数据行合并
|
||||||
|
const csvContent = [headers, ...rows]
|
||||||
|
.map((row) =>
|
||||||
|
// 处理CSV中的特殊字符(逗号、双引号、换行符)
|
||||||
|
row
|
||||||
|
.map((cell) => {
|
||||||
|
const cellStr = String(cell || '');
|
||||||
|
// 如果包含逗号、双引号或换行符,需要用双引号包裹,并将内部的双引号转义
|
||||||
|
if (
|
||||||
|
cellStr.includes(',') ||
|
||||||
|
cellStr.includes('"') ||
|
||||||
|
cellStr.includes('\n')
|
||||||
|
) {
|
||||||
|
return `"${cellStr.replace(/"/g, '""')}"`;
|
||||||
|
}
|
||||||
|
return cellStr;
|
||||||
|
})
|
||||||
|
.join(','),
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
// 添加BOM以支持Excel正确显示中文
|
||||||
|
const BOM = '\uFEFF';
|
||||||
|
const blob = new Blob([BOM + csvContent], {
|
||||||
|
type: 'text/csv;charset=utf-8;',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建下载链接并触发下载
|
||||||
|
const link = document.createElement('a');
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
link.setAttribute('href', url);
|
||||||
|
|
||||||
|
// 生成文件名(包含当前分类名称和日期)
|
||||||
|
const category = categories.find((c) => c.id === categoryId);
|
||||||
|
const categoryName = category?.name || '产品';
|
||||||
|
const date = new Date().toISOString().slice(0, 10);
|
||||||
|
link.setAttribute('download', `${categoryName}_排列组合_${date}.csv`);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
message.success('导出成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出失败:', error);
|
||||||
|
message.error('导出失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const columns: any[] = [
|
const columns: any[] = [
|
||||||
...attributes.map((attr) => ({
|
...attributes.map((attr) => ({
|
||||||
title: attr.title || attr.name,
|
title: attr.title || attr.name,
|
||||||
|
|
@ -313,11 +400,21 @@ 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={false}
|
toolBarRender={() => [
|
||||||
|
<Button
|
||||||
|
key="export"
|
||||||
|
type="default"
|
||||||
|
icon={<DownloadOutlined />}
|
||||||
|
onClick={handleExport}
|
||||||
|
>
|
||||||
|
导出列表
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ProCard>
|
</ProCard>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,461 @@
|
||||||
|
import { productcontrollerGetproductlist } from '@/servers/api/product';
|
||||||
|
import {
|
||||||
|
siteapicontrollerGetproducts,
|
||||||
|
siteapicontrollerUpsertproduct,
|
||||||
|
} from '@/servers/api/siteApi';
|
||||||
|
import { templatecontrollerRendertemplate } from '@/servers/api/template';
|
||||||
|
import { SyncOutlined } from '@ant-design/icons';
|
||||||
|
import { ModalForm, ProFormText } from '@ant-design/pro-components';
|
||||||
|
import { Button, message, Spin, Tag } from 'antd';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
// 定义站点接口
|
||||||
|
interface Site {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
skuPrefix?: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义本地产品接口(与后端 Product 实体匹配)
|
||||||
|
interface SiteProduct {
|
||||||
|
id: number;
|
||||||
|
sku: string;
|
||||||
|
name: string;
|
||||||
|
nameCn: string;
|
||||||
|
shortDescription?: string;
|
||||||
|
description?: string;
|
||||||
|
price: number;
|
||||||
|
promotionPrice: number;
|
||||||
|
type: string;
|
||||||
|
categoryId?: number;
|
||||||
|
category?: any;
|
||||||
|
attributes?: any[];
|
||||||
|
components?: any[];
|
||||||
|
siteSkus: string[];
|
||||||
|
source: number;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义本地产品完整接口
|
||||||
|
interface LocalProduct {
|
||||||
|
id: number;
|
||||||
|
sku: string;
|
||||||
|
name: string;
|
||||||
|
nameCn: string;
|
||||||
|
shortDescription?: string;
|
||||||
|
description?: string;
|
||||||
|
price: number;
|
||||||
|
promotionPrice: number;
|
||||||
|
type: string;
|
||||||
|
categoryId?: number;
|
||||||
|
category?: any;
|
||||||
|
attributes?: any[];
|
||||||
|
components?: any[];
|
||||||
|
siteSkus: string[];
|
||||||
|
source: number;
|
||||||
|
images?: string[];
|
||||||
|
weight?: number;
|
||||||
|
dimensions?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义站点产品数据接口
|
||||||
|
interface SiteProductData {
|
||||||
|
sku: string;
|
||||||
|
regular_price?: number;
|
||||||
|
price?: number;
|
||||||
|
sale_price?: number;
|
||||||
|
stock_quantity?: number;
|
||||||
|
stockQuantity?: number;
|
||||||
|
status?: string;
|
||||||
|
externalProductId?: string;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
images?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SiteProductCellProps {
|
||||||
|
// 产品行数据
|
||||||
|
product: SiteProduct;
|
||||||
|
// 站点列数据
|
||||||
|
site: Site;
|
||||||
|
// 同步成功后的回调
|
||||||
|
onSyncSuccess?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SiteProductCell: React.FC<SiteProductCellProps> = ({
|
||||||
|
product,
|
||||||
|
site,
|
||||||
|
onSyncSuccess,
|
||||||
|
}) => {
|
||||||
|
// 存储该站点对应的产品数据
|
||||||
|
const [siteProduct, setSiteProduct] = useState<SiteProductData | null>(null);
|
||||||
|
// 存储本地产品完整数据
|
||||||
|
const [localProduct, setLocalProduct] = useState<LocalProduct | null>(null);
|
||||||
|
// 加载状态
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
// 是否已加载过数据
|
||||||
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
// 同步中状态
|
||||||
|
const [syncing, setSyncing] = useState(false);
|
||||||
|
|
||||||
|
// 组件挂载时加载数据
|
||||||
|
useEffect(() => {
|
||||||
|
loadSiteProduct();
|
||||||
|
}, [product.id, site.id]);
|
||||||
|
|
||||||
|
// 加载站点产品数据
|
||||||
|
const loadSiteProduct = async () => {
|
||||||
|
// 如果已经加载过,则不再重复加载
|
||||||
|
if (loaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// 首先查找该产品在该站点的实际SKU
|
||||||
|
// 注意:siteSkus 现在是字符串数组,无法直接匹配站点
|
||||||
|
// 这里使用模板生成的 SKU 作为默认值
|
||||||
|
let siteProductSku = '';
|
||||||
|
// 如果需要更精确的站点 SKU 匹配,需要后端提供额外的接口
|
||||||
|
|
||||||
|
// 如果没有找到实际的siteSku,则根据模板或默认规则生成期望的SKU
|
||||||
|
const expectedSku =
|
||||||
|
siteProductSku || `${site.skuPrefix || ''}-${product.sku}`;
|
||||||
|
|
||||||
|
// 使用 siteapicontrollerGetproducts 获取该站点的所有产品
|
||||||
|
const productsRes = await siteapicontrollerGetproducts({
|
||||||
|
siteId: site.id,
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10000,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
if (productsRes.data?.items) {
|
||||||
|
// 在该站点的产品数据中查找匹配的产品
|
||||||
|
let foundProduct = productsRes.data.items.find(
|
||||||
|
(item: any) => item.sku === expectedSku,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果根据实际SKU没找到,再尝试用模板生成的SKU查找
|
||||||
|
if (!foundProduct && siteProductSku) {
|
||||||
|
foundProduct = productsRes.data.items.find(
|
||||||
|
(item: any) => item.sku === siteProductSku,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundProduct) {
|
||||||
|
setSiteProduct(foundProduct as SiteProductData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记为已加载
|
||||||
|
setLoaded(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`加载站点 ${site.name} 的产品数据失败:`, error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取本地产品完整信息
|
||||||
|
const getLocalProduct = async (): Promise<LocalProduct | null> => {
|
||||||
|
try {
|
||||||
|
// 如果已经有本地产品数据,直接返回
|
||||||
|
if (localProduct) {
|
||||||
|
return localProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 productcontrollerGetproductlist 获取本地产品完整信息
|
||||||
|
const res = await productcontrollerGetproductlist({
|
||||||
|
where: {
|
||||||
|
id: product.id,
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
if (res.success && res.data) {
|
||||||
|
const productData = res.data as LocalProduct;
|
||||||
|
setLocalProduct(productData);
|
||||||
|
return productData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取本地产品信息失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染站点SKU
|
||||||
|
const renderSiteSku = async (data: any): Promise<string> => {
|
||||||
|
try {
|
||||||
|
// 使用 templatecontrollerRendertemplate API 渲染模板
|
||||||
|
const res = await templatecontrollerRendertemplate(
|
||||||
|
{ name: 'siteproduct-sku' } as any,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res?.template || res?.result || '';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('渲染SKU模板失败:', error);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 同步产品到站点
|
||||||
|
const syncProductToSite = async (values: any) => {
|
||||||
|
try {
|
||||||
|
setSyncing(true);
|
||||||
|
const hide = message.loading('正在同步...', 0);
|
||||||
|
|
||||||
|
// 获取本地产品完整信息
|
||||||
|
const productDetail = await getLocalProduct();
|
||||||
|
|
||||||
|
if (!productDetail) {
|
||||||
|
hide();
|
||||||
|
message.error('获取本地产品信息失败');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造要同步的产品数据
|
||||||
|
const productData: any = {
|
||||||
|
sku: values.sku,
|
||||||
|
name: productDetail.name,
|
||||||
|
description: productDetail.description || '',
|
||||||
|
regular_price: productDetail.price,
|
||||||
|
price: productDetail.price,
|
||||||
|
stock_quantity: productDetail.stock,
|
||||||
|
status: 'publish',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果有图片,添加图片信息
|
||||||
|
if (productDetail.images && productDetail.images.length > 0) {
|
||||||
|
productData.images = productDetail.images;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有重量,添加重量信息
|
||||||
|
if (productDetail.weight) {
|
||||||
|
productData.weight = productDetail.weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有尺寸,添加尺寸信息
|
||||||
|
if (productDetail.dimensions) {
|
||||||
|
productData.dimensions = productDetail.dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 siteapicontrollerUpsertproduct API 同步产品到站点
|
||||||
|
const res = await siteapicontrollerUpsertproduct(
|
||||||
|
{ siteId: site.id } as any,
|
||||||
|
productData as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.success) {
|
||||||
|
hide();
|
||||||
|
throw new Error(res.message || '同步失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新本地状态
|
||||||
|
if (res.data && typeof res.data === 'object') {
|
||||||
|
setSiteProduct(res.data as SiteProductData);
|
||||||
|
}
|
||||||
|
|
||||||
|
hide();
|
||||||
|
message.success('同步成功');
|
||||||
|
|
||||||
|
// 触发回调
|
||||||
|
if (onSyncSuccess) {
|
||||||
|
onSyncSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error('同步失败: ' + (error.message || error.toString()));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setSyncing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新同步产品到站点
|
||||||
|
const updateSyncProduct = async (values: any) => {
|
||||||
|
try {
|
||||||
|
setSyncing(true);
|
||||||
|
const hide = message.loading('正在更新...', 0);
|
||||||
|
|
||||||
|
// 获取本地产品完整信息
|
||||||
|
const productDetail = await getLocalProduct();
|
||||||
|
|
||||||
|
if (!productDetail) {
|
||||||
|
hide();
|
||||||
|
message.error('获取本地产品信息失败');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造要更新的产品数据
|
||||||
|
const productData: any = {
|
||||||
|
...siteProduct,
|
||||||
|
sku: values.sku,
|
||||||
|
name: productDetail.name,
|
||||||
|
description: productDetail.description || '',
|
||||||
|
regular_price: productDetail.price,
|
||||||
|
price: productDetail.price,
|
||||||
|
stock_quantity: productDetail.stock,
|
||||||
|
status: 'publish',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果有图片,添加图片信息
|
||||||
|
if (productDetail.images && productDetail.images.length > 0) {
|
||||||
|
productData.images = productDetail.images;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有重量,添加重量信息
|
||||||
|
if (productDetail.weight) {
|
||||||
|
productData.weight = productDetail.weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有尺寸,添加尺寸信息
|
||||||
|
if (productDetail.dimensions) {
|
||||||
|
productData.dimensions = productDetail.dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 siteapicontrollerUpsertproduct API 更新产品到站点
|
||||||
|
const res = await siteapicontrollerUpsertproduct(
|
||||||
|
{ siteId: site.id } as any,
|
||||||
|
productData as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.success) {
|
||||||
|
hide();
|
||||||
|
throw new Error(res.message || '更新失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新本地状态
|
||||||
|
if (res.data && typeof res.data === 'object') {
|
||||||
|
setSiteProduct(res.data as SiteProductData);
|
||||||
|
}
|
||||||
|
|
||||||
|
hide();
|
||||||
|
message.success('更新成功');
|
||||||
|
|
||||||
|
// 触发回调
|
||||||
|
if (onSyncSuccess) {
|
||||||
|
onSyncSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error('更新失败: ' + (error.message || error.toString()));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setSyncing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果正在加载,显示加载状态
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: 'center', padding: 10 }}>
|
||||||
|
<Spin size="small" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到站点产品,显示同步按钮
|
||||||
|
if (!siteProduct) {
|
||||||
|
// 首先查找该产品在该站点的实际SKU
|
||||||
|
// 注意:siteSkus 现在是字符串数组,无法直接匹配站点
|
||||||
|
// 这里使用模板生成的 SKU 作为默认值
|
||||||
|
let siteProductSku = '';
|
||||||
|
// 如果需要更精确的站点 SKU 匹配,需要后端提供额外的接口
|
||||||
|
|
||||||
|
const defaultSku =
|
||||||
|
siteProductSku || `${site.skuPrefix || ''}-${product.sku}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalForm
|
||||||
|
title="同步产品"
|
||||||
|
trigger={
|
||||||
|
<Button type="link" icon={<SyncOutlined />}>
|
||||||
|
同步到站点
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
width={400}
|
||||||
|
onFinish={async (values) => {
|
||||||
|
return await syncProductToSite(values);
|
||||||
|
}}
|
||||||
|
initialValues={{
|
||||||
|
sku: defaultSku,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProFormText
|
||||||
|
name="sku"
|
||||||
|
label="商店 SKU"
|
||||||
|
placeholder="请输入商店 SKU"
|
||||||
|
rules={[{ required: true, message: '请输入 SKU' }]}
|
||||||
|
/>
|
||||||
|
</ModalForm>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示站点产品信息
|
||||||
|
return (
|
||||||
|
<div style={{ fontSize: 12 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontWeight: 'bold' }}>{siteProduct.sku}</div>
|
||||||
|
<ModalForm
|
||||||
|
title="更新同步"
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon={<SyncOutlined spin={false} />}
|
||||||
|
>
|
||||||
|
更新
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
width={400}
|
||||||
|
onFinish={async (values) => {
|
||||||
|
return await updateSyncProduct(values);
|
||||||
|
}}
|
||||||
|
initialValues={{
|
||||||
|
sku: siteProduct.sku,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProFormText
|
||||||
|
name="sku"
|
||||||
|
label="商店 SKU"
|
||||||
|
placeholder="请输入商店 SKU"
|
||||||
|
rules={[{ required: true, message: '请输入 SKU' }]}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<div style={{ marginBottom: 16, color: '#666' }}>
|
||||||
|
确定要将本地产品数据更新到站点吗?
|
||||||
|
</div>
|
||||||
|
</ModalForm>
|
||||||
|
</div>
|
||||||
|
<div>Price: {siteProduct.regular_price ?? siteProduct.price}</div>
|
||||||
|
{siteProduct.sale_price && (
|
||||||
|
<div style={{ color: 'red' }}>Sale: {siteProduct.sale_price}</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
Stock: {siteProduct.stock_quantity ?? siteProduct.stockQuantity}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 2 }}>
|
||||||
|
Status:{' '}
|
||||||
|
{siteProduct.status === 'publish' ? (
|
||||||
|
<Tag color="green">Published</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag>{siteProduct.status}</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SiteProductCell;
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { productcontrollerGetproductlist } from '@/servers/api/product';
|
import { showBatchOperationResult } from '@/utils/showResult';
|
||||||
import { templatecontrollerGettemplatebyname } from '@/servers/api/template';
|
|
||||||
import { EditOutlined, SyncOutlined } from '@ant-design/icons';
|
|
||||||
import {
|
import {
|
||||||
ActionType,
|
productcontrollerBatchsynctosite,
|
||||||
ModalForm,
|
productcontrollerGetproductlist,
|
||||||
ProColumns,
|
productcontrollerSynctosite,
|
||||||
ProFormText,
|
} from '@/servers/api/product';
|
||||||
ProTable,
|
import { EditOutlined, SyncOutlined } from '@ant-design/icons';
|
||||||
} from '@ant-design/pro-components';
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -21,39 +19,35 @@ import {
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import EditForm from '../List/EditForm';
|
import EditForm from '../List/EditForm';
|
||||||
|
import SiteProductCell from './SiteProductCell';
|
||||||
|
|
||||||
// 定义站点接口
|
// 定义站点接口
|
||||||
interface Site {
|
interface Site {
|
||||||
id: string;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
skuPrefix?: string;
|
skuPrefix?: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义WordPress商品接口
|
// 定义本地产品接口(与后端 Product 实体匹配)
|
||||||
interface WpProduct {
|
interface SiteProduct {
|
||||||
id?: number;
|
id: number;
|
||||||
externalProductId?: string;
|
|
||||||
sku: string;
|
sku: string;
|
||||||
name: string;
|
name: string;
|
||||||
price: string;
|
nameCn: string;
|
||||||
regular_price?: string;
|
shortDescription?: string;
|
||||||
sale_price?: string;
|
description?: string;
|
||||||
stock_quantity: number;
|
price: number;
|
||||||
stockQuantity?: number;
|
promotionPrice: number;
|
||||||
status: string;
|
type: string;
|
||||||
|
categoryId?: number;
|
||||||
|
category?: any;
|
||||||
attributes?: any[];
|
attributes?: any[];
|
||||||
constitution?: { sku: string; quantity: number }[];
|
components?: any[];
|
||||||
}
|
siteSkus: string[];
|
||||||
|
source: number;
|
||||||
// 扩展本地产品接口,包含对应的 WP 产品信息
|
createdAt: Date;
|
||||||
interface ProductWithWP extends API.Product {
|
updatedAt: Date;
|
||||||
wpProducts: Record<string, WpProduct>;
|
|
||||||
attributes?: any[];
|
|
||||||
siteSkus?: Array<{
|
|
||||||
siteSku: string;
|
|
||||||
[key: string]: any;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义API响应接口
|
// 定义API响应接口
|
||||||
|
|
@ -79,19 +73,9 @@ const getSites = async (): Promise<ApiResponse<Site>> => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWPProducts = async (): Promise<ApiResponse<WpProduct>> => {
|
|
||||||
return request('/product/wp-products', {
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProductSyncPage: React.FC = () => {
|
const ProductSyncPage: React.FC = () => {
|
||||||
const [sites, setSites] = useState<Site[]>([]);
|
const [sites, setSites] = useState<Site[]>([]);
|
||||||
// 存储所有 WP 产品,用于查找匹配。 Key: SKU (包含前缀)
|
|
||||||
const [wpProductMap, setWpProductMap] = useState<Map<string, WpProduct>>(
|
|
||||||
new Map(),
|
|
||||||
);
|
|
||||||
const [skuTemplate, setSkuTemplate] = useState<string>('');
|
|
||||||
const [initialLoading, setInitialLoading] = useState(true);
|
const [initialLoading, setInitialLoading] = useState(true);
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
const [selectedSiteId, setSelectedSiteId] = useState<string>('');
|
const [selectedSiteId, setSelectedSiteId] = useState<string>('');
|
||||||
|
|
@ -104,102 +88,46 @@ const ProductSyncPage: React.FC = () => {
|
||||||
errors: string[];
|
errors: string[];
|
||||||
}>({ success: 0, failed: 0, errors: [] });
|
}>({ success: 0, failed: 0, errors: [] });
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [selectedRows, setSelectedRows] = useState<ProductWithWP[]>([]);
|
const [selectedRows, setSelectedRows] = useState<SiteProduct[]>([]);
|
||||||
|
// 初始化加载站点列表
|
||||||
// 初始化数据:获取站点和所有 WP 产品
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const initializeData = async () => {
|
||||||
try {
|
try {
|
||||||
setInitialLoading(true);
|
// 获取站点列表
|
||||||
// 获取所有站点
|
const sitesRes = await getSites();
|
||||||
const sitesResponse = await getSites();
|
if (sitesRes.success && sitesRes.data.length > 0) {
|
||||||
const rawSiteList = sitesResponse.data || [];
|
setSites(sitesRes.data);
|
||||||
// 过滤掉已禁用的站点
|
|
||||||
const siteList: Site[] = rawSiteList.filter((site) => !site.isDisabled);
|
|
||||||
setSites(siteList);
|
|
||||||
|
|
||||||
// 获取所有 WordPress 商品
|
|
||||||
const wpProductsResponse = await getWPProducts();
|
|
||||||
const wpProductList: WpProduct[] = wpProductsResponse.data || [];
|
|
||||||
|
|
||||||
// 构建 WP 产品 Map,Key 为 SKU
|
|
||||||
const map = new Map<string, WpProduct>();
|
|
||||||
wpProductList.forEach((p) => {
|
|
||||||
if (p.sku) {
|
|
||||||
map.set(p.sku, p);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setWpProductMap(map);
|
|
||||||
|
|
||||||
// 获取 SKU 模板
|
|
||||||
try {
|
|
||||||
const templateRes = await templatecontrollerGettemplatebyname({
|
|
||||||
name: 'site.product.sku',
|
|
||||||
});
|
|
||||||
if (templateRes && templateRes.value) {
|
|
||||||
setSkuTemplate(templateRes.value);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Template site.product.sku not found, using default.');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取基础数据失败,请重试');
|
console.error('初始化数据失败:', error);
|
||||||
console.error('Error fetching data:', error);
|
message.error('初始化数据失败');
|
||||||
} finally {
|
} finally {
|
||||||
setInitialLoading(false);
|
setInitialLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
initializeData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 同步产品到站点
|
|
||||||
const syncProductToSite = async (
|
const syncProductToSite = async (
|
||||||
values: any,
|
values: any,
|
||||||
record: ProductWithWP,
|
record: SiteProduct,
|
||||||
site: Site,
|
site: Site,
|
||||||
wpProductId?: string,
|
siteProductId?: string,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const hide = message.loading('正在同步...', 0);
|
const hide = message.loading('正在同步...', 0);
|
||||||
const data = {
|
|
||||||
name: record.name,
|
|
||||||
sku: values.sku,
|
|
||||||
regular_price: record.price?.toString(),
|
|
||||||
sale_price: record.promotionPrice?.toString(),
|
|
||||||
type: record.type === 'bundle' ? 'simple' : record.type,
|
|
||||||
description: record.description,
|
|
||||||
status: 'publish',
|
|
||||||
stock_status: 'instock',
|
|
||||||
manage_stock: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let res;
|
// 使用 productcontrollerSynctosite API 同步产品到站点
|
||||||
if (wpProductId) {
|
const res = await productcontrollerSynctosite({
|
||||||
res = await request(`/site-api/${site.id}/products/${wpProductId}`, {
|
productId: Number(record.id),
|
||||||
method: 'PUT',
|
siteId: Number(site.id),
|
||||||
data,
|
} as any);
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res = await request(`/site-api/${site.id}/products`, {
|
|
||||||
method: 'POST',
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('res', res);
|
|
||||||
if (!res.success) {
|
if (!res.success) {
|
||||||
hide();
|
hide();
|
||||||
throw new Error(res.message || '同步失败');
|
throw new Error(res.message || '同步失败');
|
||||||
}
|
}
|
||||||
// 更新本地缓存 Map,避免刷新
|
|
||||||
setWpProductMap((prev) => {
|
|
||||||
const newMap = new Map(prev);
|
|
||||||
if (res.data && typeof res.data === 'object') {
|
|
||||||
newMap.set(values.sku, res.data as WpProduct);
|
|
||||||
}
|
|
||||||
return newMap;
|
|
||||||
});
|
|
||||||
|
|
||||||
hide();
|
hide();
|
||||||
message.success('同步成功');
|
message.success('同步成功');
|
||||||
|
|
@ -207,12 +135,11 @@ const ProductSyncPage: React.FC = () => {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error('同步失败: ' + (error.message || error.toString()));
|
message.error('同步失败: ' + (error.message || error.toString()));
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 批量同步产品到指定站点
|
// 批量同步产品到指定站点
|
||||||
const batchSyncProducts = async (productsToSync?: ProductWithWP[]) => {
|
const batchSyncProducts = async (productsToSync?: SiteProduct[]) => {
|
||||||
if (!selectedSiteId) {
|
if (!selectedSiteId) {
|
||||||
message.error('请选择要同步到的站点');
|
message.error('请选择要同步到的站点');
|
||||||
return;
|
return;
|
||||||
|
|
@ -239,7 +166,7 @@ const ProductSyncPage: React.FC = () => {
|
||||||
message.error('获取产品列表失败');
|
message.error('获取产品列表失败');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
products = data.items as ProductWithWP[];
|
products = data.items as SiteProduct[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取产品列表失败');
|
message.error('获取产品列表失败');
|
||||||
return;
|
return;
|
||||||
|
|
@ -250,108 +177,44 @@ const ProductSyncPage: React.FC = () => {
|
||||||
setSyncProgress(0);
|
setSyncProgress(0);
|
||||||
setSyncResults({ success: 0, failed: 0, errors: [] });
|
setSyncResults({ success: 0, failed: 0, errors: [] });
|
||||||
|
|
||||||
const totalProducts = products.length;
|
|
||||||
let processed = 0;
|
|
||||||
let successCount = 0;
|
|
||||||
let failedCount = 0;
|
|
||||||
const errors: string[] = [];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 逐个同步产品
|
// 使用 productcontrollerBatchsynctosite API 批量同步
|
||||||
for (const product of products) {
|
const productIds = products.map((product) => Number(product.id));
|
||||||
try {
|
|
||||||
// 获取该产品在目标站点的SKU
|
|
||||||
let siteProductSku = '';
|
|
||||||
if (product.siteSkus && product.siteSkus.length > 0) {
|
|
||||||
const siteSkuInfo = product.siteSkus.find((sku: any) => {
|
|
||||||
return (
|
|
||||||
sku.siteSku &&
|
|
||||||
sku.siteSku.includes(targetSite.skuPrefix || targetSite.name)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
if (siteSkuInfo) {
|
|
||||||
siteProductSku = siteSkuInfo.siteSku;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有找到实际的siteSku,则根据模板生成
|
// 更新进度为50%,表示正在处理
|
||||||
const expectedSku =
|
setSyncProgress(50);
|
||||||
siteProductSku ||
|
|
||||||
(skuTemplate
|
|
||||||
? renderSiteSku(skuTemplate, { site: targetSite, product })
|
|
||||||
: `${targetSite.skuPrefix || ''}-${product.sku}`);
|
|
||||||
|
|
||||||
// 检查是否已存在
|
const res = await productcontrollerBatchsynctosite({
|
||||||
const existingProduct = wpProductMap.get(expectedSku);
|
productIds: productIds,
|
||||||
|
siteId: Number(targetSite.id),
|
||||||
|
} as any);
|
||||||
|
|
||||||
// 准备同步数据
|
if (res.success) {
|
||||||
const syncData = {
|
const syncedCount = res.data?.synced || 0;
|
||||||
name: product.name,
|
const errors = res.data?.errors || [];
|
||||||
sku: expectedSku,
|
|
||||||
regular_price: product.price?.toString(),
|
|
||||||
sale_price: product.promotionPrice?.toString(),
|
|
||||||
type: product.type === 'bundle' ? 'simple' : product.type,
|
|
||||||
description: product.description,
|
|
||||||
status: 'publish',
|
|
||||||
stock_status: 'instock',
|
|
||||||
manage_stock: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let res;
|
// 更新进度为100%,表示完成
|
||||||
if (existingProduct?.externalProductId) {
|
setSyncProgress(100);
|
||||||
// 更新现有产品
|
|
||||||
res = await request(
|
|
||||||
`/site-api/${targetSite.id}/products/${existingProduct.externalProductId}`,
|
|
||||||
{
|
|
||||||
method: 'PUT',
|
|
||||||
data: syncData,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 创建新产品
|
|
||||||
res = await request(`/site-api/${targetSite.id}/products`, {
|
|
||||||
method: 'POST',
|
|
||||||
data: syncData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('res', res);
|
setSyncResults({
|
||||||
|
success: syncedCount,
|
||||||
|
failed: errors.length,
|
||||||
|
errors: errors.map((err: any) => err.error || '未知错误'),
|
||||||
|
});
|
||||||
|
|
||||||
if (res.success) {
|
if (errors.length === 0) {
|
||||||
successCount++;
|
message.success(`批量同步完成,成功同步 ${syncedCount} 个产品`);
|
||||||
// 更新本地缓存
|
} else {
|
||||||
setWpProductMap((prev) => {
|
message.warning(
|
||||||
const newMap = new Map(prev);
|
`批量同步完成,成功 ${syncedCount} 个,失败 ${errors.length} 个`,
|
||||||
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++;
|
// 刷新表格
|
||||||
setSyncProgress(Math.round((processed / totalProducts) * 100));
|
actionRef.current?.reload();
|
||||||
}
|
|
||||||
|
|
||||||
setSyncResults({ success: successCount, failed: failedCount, errors });
|
|
||||||
|
|
||||||
if (failedCount === 0) {
|
|
||||||
message.success(`批量同步完成,成功同步 ${successCount} 个产品`);
|
|
||||||
} else {
|
} else {
|
||||||
message.warning(
|
throw new Error(res.message || '批量同步失败');
|
||||||
`批量同步完成,成功 ${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 {
|
||||||
|
|
@ -359,27 +222,9 @@ const ProductSyncPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 简单的模板渲染函数
|
|
||||||
const renderSiteSku = (template: string, data: any) => {
|
|
||||||
if (!template) return '';
|
|
||||||
// 支持 <%= it.path %> (Eta) 和 {{ path }} (Mustache/Handlebars)
|
|
||||||
return template.replace(
|
|
||||||
/<%=\s*it\.([\w.]+)\s*%>|\{\{\s*([\w.]+)\s*\}\}/g,
|
|
||||||
(_, p1, p2) => {
|
|
||||||
const path = p1 || p2;
|
|
||||||
const keys = path.split('.');
|
|
||||||
let value = data;
|
|
||||||
for (const key of keys) {
|
|
||||||
value = value?.[key];
|
|
||||||
}
|
|
||||||
return value === undefined || value === null ? '' : String(value);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 生成表格列配置
|
// 生成表格列配置
|
||||||
const generateColumns = (): ProColumns<ProductWithWP>[] => {
|
const generateColumns = (): ProColumns<Site>[] => {
|
||||||
const columns: ProColumns<ProductWithWP>[] = [
|
const columns: ProColumns<SiteProduct>[] = [
|
||||||
{
|
{
|
||||||
title: 'SKU',
|
title: 'SKU',
|
||||||
dataIndex: 'sku',
|
dataIndex: 'sku',
|
||||||
|
|
@ -471,137 +316,21 @@ const ProductSyncPage: React.FC = () => {
|
||||||
|
|
||||||
// 为每个站点生成列
|
// 为每个站点生成列
|
||||||
sites.forEach((site: Site) => {
|
sites.forEach((site: Site) => {
|
||||||
const siteColumn: ProColumns<ProductWithWP> = {
|
const siteColumn: ProColumns<SiteProduct> = {
|
||||||
title: site.name,
|
title: site.name,
|
||||||
key: `site_${site.id}`,
|
key: `site_${site.id}`,
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
width: 220,
|
width: 220,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
// 首先查找该产品在该站点的实际SKU
|
|
||||||
let siteProductSku = '';
|
|
||||||
if (record.siteSkus && record.siteSkus.length > 0) {
|
|
||||||
// 根据站点名称匹配对应的siteSku
|
|
||||||
const siteSkuInfo = record.siteSkus.find((sku: any) => {
|
|
||||||
// 这里假设可以根据站点名称或其他标识来匹配
|
|
||||||
// 如果需要更精确的匹配逻辑,可以根据实际需求调整
|
|
||||||
return (
|
|
||||||
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 (
|
||||||
<div style={{ fontSize: 12 }}>
|
<SiteProductCell
|
||||||
<div
|
product={record}
|
||||||
style={{
|
site={site}
|
||||||
display: 'flex',
|
onSyncSuccess={() => {
|
||||||
justifyContent: 'space-between',
|
// 同步成功后刷新表格
|
||||||
alignItems: 'start',
|
actionRef.current?.reload();
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<div style={{ fontWeight: 'bold' }}>{wpProduct.sku}</div>
|
|
||||||
<ModalForm
|
|
||||||
title="更新同步"
|
|
||||||
trigger={
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
icon={<SyncOutlined spin={false} />}
|
|
||||||
></Button>
|
|
||||||
}
|
|
||||||
width={400}
|
|
||||||
onFinish={async (values) => {
|
|
||||||
return await syncProductToSite(
|
|
||||||
values,
|
|
||||||
record,
|
|
||||||
site,
|
|
||||||
wpProduct.externalProductId,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
initialValues={{
|
|
||||||
sku: wpProduct.sku,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ProFormText
|
|
||||||
name="sku"
|
|
||||||
label="商店 SKU"
|
|
||||||
placeholder="请输入商店 SKU"
|
|
||||||
rules={[{ required: true, message: '请输入 SKU' }]}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
<div style={{ marginBottom: 16, color: '#666' }}>
|
|
||||||
确定要将本地产品数据更新到站点吗?
|
|
||||||
</div>
|
|
||||||
</ModalForm>
|
|
||||||
</div>
|
|
||||||
<div>Price: {wpProduct.regular_price ?? wpProduct.price}</div>
|
|
||||||
{wpProduct.sale_price && (
|
|
||||||
<div style={{ color: 'red' }}>Sale: {wpProduct.sale_price}</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
Stock: {wpProduct.stock_quantity ?? wpProduct.stockQuantity}
|
|
||||||
</div>
|
|
||||||
<div style={{ marginTop: 2 }}>
|
|
||||||
Status:{' '}
|
|
||||||
{wpProduct.status === 'publish' ? (
|
|
||||||
<Tag color="green">Published</Tag>
|
|
||||||
) : (
|
|
||||||
<Tag>{wpProduct.status}</Tag>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -624,7 +353,7 @@ const ProductSyncPage: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title="商品同步状态" className="product-sync-card">
|
<Card title="商品同步状态" className="product-sync-card">
|
||||||
<ProTable<ProductWithWP>
|
<ProTable<SiteProduct>
|
||||||
columns={generateColumns()}
|
columns={generateColumns()}
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
|
@ -665,26 +394,27 @@ const ProductSyncPage: React.FC = () => {
|
||||||
]}
|
]}
|
||||||
request={async (params, sort, filter) => {
|
request={async (params, sort, filter) => {
|
||||||
// 调用本地获取产品列表 API
|
// 调用本地获取产品列表 API
|
||||||
const { data, success } = await productcontrollerGetproductlist({
|
const response = await productcontrollerGetproductlist({
|
||||||
...params,
|
...params,
|
||||||
current: params.current,
|
current: params.current,
|
||||||
pageSize: params.pageSize,
|
pageSize: params.pageSize,
|
||||||
// 传递搜索参数
|
// 传递搜索参数
|
||||||
keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段
|
// keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段
|
||||||
sku: (params as any).sku,
|
sku: (params as any).sku,
|
||||||
name: (params as any).name,
|
name: (params as any).name,
|
||||||
} as any);
|
} as any);
|
||||||
|
console.log('result', response);
|
||||||
// 返回给 ProTable
|
// 返回给 ProTable
|
||||||
return {
|
return {
|
||||||
data: (data?.items || []) as ProductWithWP[],
|
data: response.data?.items || [],
|
||||||
success,
|
success: response.success,
|
||||||
total: data?.total || 0,
|
total: response.data?.total || 0,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
}}
|
}}
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
search={{
|
search={{
|
||||||
|
|
@ -708,7 +438,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 ? (
|
||||||
|
|
@ -722,17 +452,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 { ordercontrollerSyncorder } from '@/servers/api/order';
|
import { ordercontrollerSyncorders } from '@/servers/api/order';
|
||||||
import {
|
import {
|
||||||
sitecontrollerCreate,
|
sitecontrollerCreate,
|
||||||
sitecontrollerDisable,
|
sitecontrollerDisable,
|
||||||
|
|
@ -6,10 +6,13 @@ import {
|
||||||
sitecontrollerUpdate,
|
sitecontrollerUpdate,
|
||||||
} from '@/servers/api/site';
|
} from '@/servers/api/site';
|
||||||
import { subscriptioncontrollerSync } from '@/servers/api/subscription';
|
import { subscriptioncontrollerSync } from '@/servers/api/subscription';
|
||||||
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
import { stockcontrollerGetallstockpoints } from '@/servers/api/stock';
|
||||||
import { Button, message, notification, Popconfirm, Space, Tag } from 'antd';
|
import { ActionType, ProColumns, ProTable, DrawerForm, ProFormSelect, ProFormSwitch } from '@ant-design/pro-components';
|
||||||
import React, { useRef, useState } from 'react';
|
import { Button, message, notification, Popconfirm, Space, Tag, Form } from 'antd';
|
||||||
|
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 {
|
||||||
|
|
@ -42,29 +45,24 @@ 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 ordercontrollerSyncorder({ siteId: id });
|
const orderRes = await ordercontrollerSyncorders({ siteId: id });
|
||||||
if (orderRes.success) {
|
if (orderRes.success) {
|
||||||
stats.orders.success += 1;
|
stats.orders.success += 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -85,9 +83,6 @@ const SiteList: React.FC = () => {
|
||||||
message: '同步完成',
|
message: '同步完成',
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
|
||||||
产品: 成功 {stats.products.success}, 失败 {stats.products.fail}
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
订单: 成功 {stats.orders.success}, 失败 {stats.orders.fail}
|
订单: 成功 {stats.orders.success}, 失败 {stats.orders.fail}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -108,6 +103,68 @@ 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>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -131,26 +188,47 @@ 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) {
|
||||||
|
|
@ -232,10 +310,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 {
|
||||||
|
|
@ -279,6 +357,11 @@ 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"
|
||||||
|
|
@ -289,6 +372,12 @@ 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[])}
|
||||||
|
|
@ -310,6 +399,51 @@ 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,
|
||||||
page_size: pageSize,
|
per_page: 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 }}
|
pagination={{ pageSize: 20 ,showSizeChanger: true, showQuickJumper: true,}}
|
||||||
columns={[
|
columns={[
|
||||||
{ title: '订单号', dataIndex: 'number', copyable: true },
|
{ title: '订单号', dataIndex: 'number', copyable: true },
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ 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 {
|
||||||
|
|
@ -29,6 +31,9 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
// 初始化中文语言包
|
||||||
|
countries.registerLocale(zhCN);
|
||||||
|
|
||||||
// 当 initialValues 或 open 状态变化时, 更新表单的值
|
// 当 initialValues 或 open 状态变化时, 更新表单的值
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 如果抽屉是打开的
|
// 如果抽屉是打开的
|
||||||
|
|
@ -36,8 +41,10 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
||||||
// 如果是编辑模式并且有初始值
|
// 如果是编辑模式并且有初始值
|
||||||
if (isEdit && initialValues) {
|
if (isEdit && initialValues) {
|
||||||
// 编辑模式下, 设置表单值为初始值
|
// 编辑模式下, 设置表单值为初始值
|
||||||
|
const { token, consumerKey, consumerSecret, ...safeInitialValues } = initialValues;
|
||||||
|
// 清空敏感字段, 让用户输入最新的数据
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...initialValues,
|
...safeInitialValues,
|
||||||
isDisabled: initialValues.isDisabled === 1, // 将后端的 1/0 转换成 true/false
|
isDisabled: initialValues.isDisabled === 1, // 将后端的 1/0 转换成 true/false
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -47,6 +54,17 @@ 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 ? '编辑站点' : '新建站点'}
|
||||||
|
|
@ -82,6 +100,11 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
||||||
label="网站地址"
|
label="网站地址"
|
||||||
placeholder="请输入网站地址"
|
placeholder="请输入网站地址"
|
||||||
/>
|
/>
|
||||||
|
<ProFormText
|
||||||
|
name="webhookUrl"
|
||||||
|
label="Webhook 地址"
|
||||||
|
placeholder="请输入 Webhook 地址"
|
||||||
|
/>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="type"
|
name="type"
|
||||||
label="平台"
|
label="平台"
|
||||||
|
|
@ -146,15 +169,12 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
||||||
label="区域"
|
label="区域"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="请选择区域"
|
placeholder="请选择区域"
|
||||||
request={async () => {
|
|
||||||
// 从后端接口获取区域数据
|
showSearch
|
||||||
const res = await areacontrollerGetarealist({ pageSize: 1000 });
|
filterOption={(input, option) =>
|
||||||
// areacontrollerGetarealist 直接返回数组, 所以不需要 .data.list
|
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||||
return res.map((area: any) => ({
|
}
|
||||||
label: area.name,
|
options={getCountryOptions()}
|
||||||
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,
|
||||||
page_size: pageSize,
|
per_page: pageSize,
|
||||||
...(orderObj ? { order: orderObj } : {}),
|
...(orderObj ? { order: orderObj } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import {
|
||||||
CreateOrder,
|
CreateOrder,
|
||||||
EditOrder,
|
EditOrder,
|
||||||
OrderNote,
|
OrderNote,
|
||||||
|
ShipOrderForm,
|
||||||
} from '../components/Order/Forms';
|
} from '../components/Order/Forms';
|
||||||
|
|
||||||
const OrdersPage: React.FC = () => {
|
const OrdersPage: React.FC = () => {
|
||||||
|
|
@ -61,11 +62,14 @@ const OrdersPage: React.FC = () => {
|
||||||
return [{ key: 'all', label: `全部(${total})` }, ...tabs];
|
return [{ key: 'all', label: `全部(${total})` }, ...tabs];
|
||||||
}, [count]);
|
}, [count]);
|
||||||
|
|
||||||
const columns: ProColumns<API.Order>[] = [
|
const columns: ProColumns<API.UnifiedOrderDTO>[] = [
|
||||||
{
|
{
|
||||||
title: '订单号',
|
title: '订单ID',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
hideInSearch: true,
|
},
|
||||||
|
{
|
||||||
|
title:'订单号',
|
||||||
|
dataIndex: 'number',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
|
|
@ -73,12 +77,6 @@ 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',
|
||||||
|
|
@ -89,6 +87,38 @@ 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',
|
||||||
|
|
@ -96,6 +126,28 @@ 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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -111,7 +163,9 @@ const OrdersPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{record.line_items.map((item: any) => (
|
{record.line_items.map((item: any) => (
|
||||||
<div key={item.id}>{`${item.name} x ${item.quantity}`}</div>
|
<div
|
||||||
|
key={item.id}
|
||||||
|
>{`${item.name}(${item.sku}) x ${item.quantity}`}</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -120,15 +174,6 @@ 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',
|
||||||
|
|
@ -144,6 +189,66 @@ 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: '操作',
|
||||||
|
|
@ -186,28 +291,17 @@ const OrdersPage: React.FC = () => {
|
||||||
>
|
>
|
||||||
<Button type="text" icon={<EllipsisOutlined />} />
|
<Button type="text" icon={<EllipsisOutlined />} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Button
|
<ShipOrderForm
|
||||||
type="link"
|
orderId={record.id as number}
|
||||||
title="发货"
|
tableRef={actionRef}
|
||||||
onClick={async () => {
|
siteId={siteId}
|
||||||
try {
|
orderItems={(record as any).line_items?.map((item: any) => ({
|
||||||
const res = await request(
|
id: item.id,
|
||||||
`/site-api/${siteId}/orders/${record.id}/ship`,
|
name: item.name,
|
||||||
{ method: 'POST' },
|
quantity: item.quantity,
|
||||||
);
|
sku: item.sku,
|
||||||
if (res.success) {
|
}))}
|
||||||
message.success('发货成功');
|
/>
|
||||||
actionRef.current?.reload();
|
|
||||||
} else {
|
|
||||||
message.error(res.message || '发货失败');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
message.error('发货失败');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
发货
|
|
||||||
</Button>
|
|
||||||
{record.status === 'completed' && (
|
{record.status === 'completed' && (
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="确定取消发货?"
|
title="确定取消发货?"
|
||||||
|
|
@ -279,6 +373,7 @@ 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={() => [
|
||||||
|
|
@ -289,6 +384,8 @@ const OrdersPage: React.FC = () => {
|
||||||
setSelectedRowKeys={setSelectedRowKeys}
|
setSelectedRowKeys={setSelectedRowKeys}
|
||||||
siteId={siteId}
|
siteId={siteId}
|
||||||
/>,
|
/>,
|
||||||
|
<Button disabled>批量发货</Button>
|
||||||
|
,
|
||||||
<Button
|
<Button
|
||||||
title="批量删除"
|
title="批量删除"
|
||||||
danger
|
danger
|
||||||
|
|
@ -369,18 +466,7 @@ const OrdersPage: React.FC = () => {
|
||||||
</ModalForm>,
|
</ModalForm>,
|
||||||
]}
|
]}
|
||||||
request={async (params, sort, filter) => {
|
request={async (params, sort, filter) => {
|
||||||
const p: any = params || {};
|
const { current, pageSize, date, status, ...rest } = params;
|
||||||
const current = p.current;
|
|
||||||
const pageSize = p.pageSize;
|
|
||||||
const date = p.date;
|
|
||||||
const status = p.status;
|
|
||||||
const {
|
|
||||||
current: _c,
|
|
||||||
pageSize: _ps,
|
|
||||||
date: _d,
|
|
||||||
status: _s,
|
|
||||||
...rest
|
|
||||||
} = p;
|
|
||||||
const where: Record<string, any> = { ...(filter || {}), ...rest };
|
const where: Record<string, any> = { ...(filter || {}), ...rest };
|
||||||
if (status && status !== 'all') {
|
if (status && status !== 'all') {
|
||||||
where.status = status;
|
where.status = status;
|
||||||
|
|
@ -401,9 +487,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,
|
||||||
page_size: pageSize,
|
per_page: pageSize,
|
||||||
where,
|
where,
|
||||||
...(orderObj ? { order: orderObj } : {}),
|
...(orderObj ? { orderBy: orderObj } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -455,12 +541,8 @@ const OrdersPage: React.FC = () => {
|
||||||
return { status: key, count: 0 };
|
return { status: key, count: 0 };
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await request(`/site-api/${siteId}/orders`, {
|
const res = await request(`/site-api/${siteId}/orders/count`, {
|
||||||
params: {
|
params: { ...baseWhere, status: rawStatus },
|
||||||
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,7 +116,6 @@ 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) => {
|
||||||
|
|
@ -156,7 +155,7 @@ const ProductsPage: React.FC = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 库存
|
// 库存
|
||||||
title: '库存',
|
title: '库存数量',
|
||||||
dataIndex: 'stock_quantity',
|
dataIndex: 'stock_quantity',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
|
|
@ -394,6 +393,7 @@ 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,6 +47,133 @@ const region = {
|
||||||
YT: 'Yukon',
|
YT: 'Yukon',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 定义发货订单表单的数据类型
|
||||||
|
export interface ShipOrderFormData {
|
||||||
|
tracking_number?: string;
|
||||||
|
shipping_provider?: string;
|
||||||
|
shipping_method?: string;
|
||||||
|
items?: Array<{
|
||||||
|
id?: string;
|
||||||
|
quantity?: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发货订单表单组件
|
||||||
|
export const ShipOrderForm: React.FC<{
|
||||||
|
orderId: number;
|
||||||
|
tableRef?: React.MutableRefObject<ActionType | undefined>;
|
||||||
|
siteId?: string;
|
||||||
|
orderItems?: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
quantity: number;
|
||||||
|
sku?: string;
|
||||||
|
}>;
|
||||||
|
}> = ({ orderId, tableRef, siteId, orderItems }) => {
|
||||||
|
const { message } = App.useApp();
|
||||||
|
const formRef = useRef<ProFormInstance>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalForm
|
||||||
|
formRef={formRef}
|
||||||
|
title="发货订单"
|
||||||
|
width="600px"
|
||||||
|
modalProps={{ destroyOnHidden: true }}
|
||||||
|
trigger={
|
||||||
|
<Button type="link" title="发货">
|
||||||
|
发货
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
onFinish={async (values: ShipOrderFormData) => {
|
||||||
|
if (!siteId) {
|
||||||
|
message.error('缺少站点ID');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { success, message: errMsg } = await request(
|
||||||
|
`/site-api/${siteId}/orders/${orderId}/ship`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
data: values,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success === false) {
|
||||||
|
throw new Error(errMsg || '发货失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success('发货成功');
|
||||||
|
tableRef?.current?.reload();
|
||||||
|
return true;
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error?.message || '发货失败');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onFinishFailed={() => {
|
||||||
|
const element = document.querySelector('.ant-form-item-explain-error');
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProFormText
|
||||||
|
name="tracking_number"
|
||||||
|
label="物流单号"
|
||||||
|
placeholder="请输入物流单号"
|
||||||
|
rules={[{ required: true, message: '请输入物流单号' }]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProFormText
|
||||||
|
name="shipping_provider"
|
||||||
|
label="物流公司"
|
||||||
|
placeholder="请输入物流公司名称"
|
||||||
|
rules={[{ required: true, message: '请输入物流公司名称' }]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProFormText
|
||||||
|
name="shipping_method"
|
||||||
|
label="发货方式"
|
||||||
|
placeholder="请输入发货方式"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{orderItems && orderItems.length > 0 && (
|
||||||
|
<ProFormList
|
||||||
|
label="发货商品项"
|
||||||
|
name="items"
|
||||||
|
tooltip="如果不选择,则默认发货所有商品"
|
||||||
|
>
|
||||||
|
<ProForm.Group>
|
||||||
|
<ProFormSelect
|
||||||
|
name="id"
|
||||||
|
label="商品"
|
||||||
|
placeholder="请选择商品"
|
||||||
|
options={orderItems.map((item) => ({
|
||||||
|
label: `${item.name} (SKU: ${item.sku || 'N/A'}) - 可发数量: ${
|
||||||
|
item.quantity
|
||||||
|
}`,
|
||||||
|
value: item.id,
|
||||||
|
}))}
|
||||||
|
rules={[{ required: true, message: '请选择商品' }]}
|
||||||
|
/>
|
||||||
|
<ProFormDigit
|
||||||
|
name="quantity"
|
||||||
|
label="发货数量"
|
||||||
|
placeholder="请输入发货数量"
|
||||||
|
rules={[{ required: true, message: '请输入发货数量' }]}
|
||||||
|
fieldProps={{
|
||||||
|
precision: 0,
|
||||||
|
min: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ProForm.Group>
|
||||||
|
</ProFormList>
|
||||||
|
)}
|
||||||
|
</ModalForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const OrderNote: React.FC<{
|
export const OrderNote: React.FC<{
|
||||||
id: number;
|
id: number;
|
||||||
descRef?: React.MutableRefObject<ActionType | undefined>;
|
descRef?: React.MutableRefObject<ActionType | undefined>;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
showTotal: (total) => `共 ${total} 条`,
|
showQuickJumper: true,
|
||||||
}}
|
}}
|
||||||
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,18 +613,6 @@ export const UpdateVaritation: React.FC<{
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ... SetComponent, BatchEditProducts, BatchDeleteProducts, ImportCsv ...
|
|
||||||
// I will keep them but comment out/disable parts that rely on old API if I can't easily fix them all.
|
|
||||||
// BatchEdit/Delete rely on old API.
|
|
||||||
// I'll comment out their usage in ProductsPage or just return null here.
|
|
||||||
// I'll keep them but they might break if used.
|
|
||||||
// Since I removed them from ProductsPage toolbar (Wait, I kept them in ProductsPage toolbar!), I should update them or remove them.
|
|
||||||
// I'll update BatchDelete to use new API (loop delete).
|
|
||||||
// BatchEdit? `wpproductcontrollerBatchUpdateProducts`.
|
|
||||||
// I don't have batch update in my new API.
|
|
||||||
// I'll remove BatchEdit from ProductsPage toolbar for now or implement batch update in Controller.
|
|
||||||
// I'll update BatchDelete.
|
|
||||||
|
|
||||||
export const BatchDeleteProducts: React.FC<{
|
export const BatchDeleteProducts: React.FC<{
|
||||||
tableRef: React.MutableRefObject<ActionType | undefined>;
|
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||||||
selectedRowKeys: React.Key[];
|
selectedRowKeys: React.Key[];
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ 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,
|
||||||
|
|
@ -22,7 +23,8 @@ 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;
|
||||||
|
|
@ -38,6 +40,17 @@ 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[]>([]);
|
||||||
|
|
@ -620,6 +633,19 @@ 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,18 +9,28 @@ 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 = {
|
||||||
useEffect(() => {
|
country: ['CA'],
|
||||||
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(() => {
|
||||||
|
|
@ -39,11 +49,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',
|
||||||
},
|
},
|
||||||
|
|
@ -59,11 +69,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',
|
||||||
},
|
},
|
||||||
|
|
@ -83,11 +93,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) => {
|
||||||
|
|
@ -258,9 +268,29 @@ 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 }}
|
||||||
|
|
@ -277,6 +307,7 @@ const ListPage: React.FC = () => {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{tableData?.length ? (
|
{tableData?.length ? (
|
||||||
<ProTable
|
<ProTable
|
||||||
search={false}
|
search={false}
|
||||||
|
|
@ -293,4 +324,17 @@ 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,9 +15,13 @@ 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 {
|
||||||
|
|
@ -25,6 +29,17 @@ 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>();
|
||||||
|
|
@ -190,23 +205,11 @@ const CreateForm: React.FC<{
|
||||||
width="lg"
|
width="lg"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="留空表示全球"
|
placeholder="留空表示全球"
|
||||||
request={async () => {
|
showSearch
|
||||||
try {
|
filterOption={(input, option) =>
|
||||||
const resp = await request('/area', {
|
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||||
method: 'GET',
|
}
|
||||||
params: { pageSize: 1000 },
|
options={getCountryOptions()}
|
||||||
});
|
|
||||||
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>
|
||||||
|
|
@ -289,23 +292,11 @@ const UpdateForm: React.FC<{
|
||||||
width="lg"
|
width="lg"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="留空表示全球"
|
placeholder="留空表示全球"
|
||||||
request={async () => {
|
showSearch
|
||||||
try {
|
filterOption={(input, option) =>
|
||||||
const resp = await request('/area', {
|
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||||
method: 'GET',
|
}
|
||||||
params: { pageSize: 1000 },
|
options={getCountryOptions()}
|
||||||
});
|
|
||||||
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,7 +161,10 @@ const OrdersPage: React.FC = () => {
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
request={request}
|
request={request}
|
||||||
pagination={{ showSizeChanger: true }}
|
pagination={{
|
||||||
|
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: Record<string, any>,
|
body: API.CreateCategoryDTO,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
return request<any>('/category/', {
|
return request<any>('/category/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
data: body,
|
data: body,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
|
|
@ -40,14 +40,14 @@ export async function categorycontrollerCreate(
|
||||||
export async function categorycontrollerUpdate(
|
export async function categorycontrollerUpdate(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
params: API.categorycontrollerUpdateParams,
|
params: API.categorycontrollerUpdateParams,
|
||||||
body: Record<string, any>,
|
body: API.UpdateCategoryDTO,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { id: param0, ...queryParams } = params;
|
const { id: param0, ...queryParams } = params;
|
||||||
return request<any>(`/category/${param0}`, {
|
return request<any>(`/category/${param0}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
params: { ...queryParams },
|
params: { ...queryParams },
|
||||||
data: body,
|
data: body,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,68 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import { request } from 'umi';
|
import { request } from 'umi';
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /customer/ */
|
||||||
|
export async function customercontrollerCreatecustomer(
|
||||||
|
body: API.CreateCustomerDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<API.GetCustomerDTO>('/customer/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /customer/${param0} */
|
||||||
|
export async function customercontrollerGetcustomerbyid(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.customercontrollerGetcustomerbyidParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, ...queryParams } = params;
|
||||||
|
return request<API.GetCustomerDTO>(`/customer/${param0}`, {
|
||||||
|
method: 'GET',
|
||||||
|
params: { ...queryParams },
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 PUT /customer/${param0} */
|
||||||
|
export async function customercontrollerUpdatecustomer(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.customercontrollerUpdatecustomerParams,
|
||||||
|
body: API.UpdateCustomerDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, ...queryParams } = params;
|
||||||
|
return request<API.GetCustomerDTO>(`/customer/${param0}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 DELETE /customer/${param0} */
|
||||||
|
export async function customercontrollerDeletecustomer(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.customercontrollerDeletecustomerParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, ...queryParams } = params;
|
||||||
|
return request<Record<string, any>>(`/customer/${param0}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
params: { ...queryParams },
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /customer/addtag */
|
/** 此处后端没有提供注释 POST /customer/addtag */
|
||||||
export async function customercontrollerAddtag(
|
export async function customercontrollerAddtag(
|
||||||
body: API.CustomerTagDTO,
|
body: API.CustomerTagDTO,
|
||||||
|
|
@ -17,6 +79,51 @@ export async function customercontrollerAddtag(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 PUT /customer/batch */
|
||||||
|
export async function customercontrollerBatchupdatecustomers(
|
||||||
|
body: API.BatchUpdateCustomerDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<Record<string, any>>('/customer/batch', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /customer/batch */
|
||||||
|
export async function customercontrollerBatchcreatecustomers(
|
||||||
|
body: API.BatchCreateCustomerDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<Record<string, any>>('/customer/batch', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 DELETE /customer/batch */
|
||||||
|
export async function customercontrollerBatchdeletecustomers(
|
||||||
|
body: API.BatchDeleteCustomerDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<Record<string, any>>('/customer/batch', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /customer/deltag */
|
/** 此处后端没有提供注释 POST /customer/deltag */
|
||||||
export async function customercontrollerDeltag(
|
export async function customercontrollerDeltag(
|
||||||
body: API.CustomerTagDTO,
|
body: API.CustomerTagDTO,
|
||||||
|
|
@ -32,36 +139,6 @@ export async function customercontrollerDeltag(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /customer/getcustomerlist */
|
|
||||||
export async function customercontrollerGetcustomerlist(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.customercontrollerGetcustomerlistParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<Record<string, any>>('/customer/getcustomerlist', {
|
|
||||||
method: 'GET',
|
|
||||||
params: {
|
|
||||||
...params,
|
|
||||||
},
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /customer/getcustomerstatisticlist */
|
|
||||||
export async function customercontrollerGetcustomerstatisticlist(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.customercontrollerGetcustomerstatisticlistParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
return request<Record<string, any>>('/customer/getcustomerstatisticlist', {
|
|
||||||
method: 'GET',
|
|
||||||
params: {
|
|
||||||
...params,
|
|
||||||
},
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /customer/gettags */
|
/** 此处后端没有提供注释 GET /customer/gettags */
|
||||||
export async function customercontrollerGettags(options?: {
|
export async function customercontrollerGettags(options?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
@ -72,6 +149,16 @@ export async function customercontrollerGettags(options?: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /customer/list */
|
||||||
|
export async function customercontrollerGetcustomerlist(options?: {
|
||||||
|
[key: string]: any;
|
||||||
|
}) {
|
||||||
|
return request<API.ApiResponse>('/customer/list', {
|
||||||
|
method: 'GET',
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /customer/setrate */
|
/** 此处后端没有提供注释 POST /customer/setrate */
|
||||||
export async function customercontrollerSetrate(
|
export async function customercontrollerSetrate(
|
||||||
body: Record<string, any>,
|
body: Record<string, any>,
|
||||||
|
|
@ -87,15 +174,25 @@ export async function customercontrollerSetrate(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /customer/statistic/list */
|
||||||
|
export async function customercontrollerGetcustomerstatisticlist(options?: {
|
||||||
|
[key: string]: any;
|
||||||
|
}) {
|
||||||
|
return request<Record<string, any>>('/customer/statistic/list', {
|
||||||
|
method: 'GET',
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /customer/sync */
|
/** 此处后端没有提供注释 POST /customer/sync */
|
||||||
export async function customercontrollerSynccustomers(
|
export async function customercontrollerSynccustomers(
|
||||||
body: Record<string, any>,
|
body: API.SyncCustomersDTO,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
return request<Record<string, any>>('/customer/sync', {
|
return request<Record<string, any>>('/customer/sync', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
data: body,
|
data: body,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
|
|
|
||||||
|
|
@ -151,12 +151,27 @@ export async function dictcontrollerDeletedictitem(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /dict/item/export */
|
||||||
|
export async function dictcontrollerExportdictitems(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.dictcontrollerExportdictitemsParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<any>('/dict/item/export', {
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /dict/item/import */
|
/** 此处后端没有提供注释 POST /dict/item/import */
|
||||||
export async function dictcontrollerImportdictitems(
|
export async function dictcontrollerImportdictitems(
|
||||||
body: Record<string, any>,
|
body: Record<string, any>,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
return request<any>('/dict/item/import', {
|
return request<API.ApiResponse>('/dict/item/import', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'text/plain',
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
// @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';
|
||||||
|
|
@ -26,7 +25,6 @@ export default {
|
||||||
dict,
|
dict,
|
||||||
locales,
|
locales,
|
||||||
logistics,
|
logistics,
|
||||||
media,
|
|
||||||
order,
|
order,
|
||||||
product,
|
product,
|
||||||
siteApi,
|
siteApi,
|
||||||
|
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
// @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,6 +59,21 @@ 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,
|
||||||
|
|
@ -225,15 +240,15 @@ export async function ordercontrollerChangestatus(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /order/syncOrder/${param0} */
|
/** 此处后端没有提供注释 POST /order/sync/${param0} */
|
||||||
export async function ordercontrollerSyncorder(
|
export async function ordercontrollerSyncorders(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
params: API.ordercontrollerSyncorderParams,
|
params: API.ordercontrollerSyncordersParams,
|
||||||
body: Record<string, any>,
|
body: Record<string, any>,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { siteId: param0, ...queryParams } = params;
|
const { siteId: param0, ...queryParams } = params;
|
||||||
return request<API.BooleanRes>(`/order/syncOrder/${param0}`, {
|
return request<API.BooleanRes>(`/order/sync/${param0}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'text/plain',
|
||||||
|
|
@ -251,11 +266,14 @@ 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.BooleanRes>(`/order/syncOrder/${param1}/order/${param0}`, {
|
return request<API.SyncOperationResult>(
|
||||||
method: 'POST',
|
`/order/syncOrder/${param1}/order/${param0}`,
|
||||||
params: { ...queryParams },
|
{
|
||||||
...(options || {}),
|
method: 'POST',
|
||||||
});
|
params: { ...queryParams },
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /order/updateOrderItems/${param0} */
|
/** 此处后端没有提供注释 POST /order/updateOrderItems/${param0} */
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,36 @@ export async function productcontrollerBatchdeleteproduct(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /product/batch-sync-from-site */
|
||||||
|
export async function productcontrollerBatchsyncfromsite(
|
||||||
|
body: Record<string, any>,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<API.SyncOperationResultDTO>('/product/batch-sync-from-site', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /product/batch-sync-to-site */
|
||||||
|
export async function productcontrollerBatchsynctosite(
|
||||||
|
body: API.BatchSyncProductToSiteDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<API.SyncOperationResultDTO>('/product/batch-sync-to-site', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /product/batch-update */
|
/** 此处后端没有提供注释 PUT /product/batch-update */
|
||||||
export async function productcontrollerBatchupdateproduct(
|
export async function productcontrollerBatchupdateproduct(
|
||||||
body: API.BatchUpdateProductDTO,
|
body: API.BatchUpdateProductDTO,
|
||||||
|
|
@ -333,13 +363,13 @@ export async function productcontrollerGetcategoriesall(options?: {
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /product/category */
|
/** 此处后端没有提供注释 POST /product/category */
|
||||||
export async function productcontrollerCreatecategory(
|
export async function productcontrollerCreatecategory(
|
||||||
body: Record<string, any>,
|
body: API.CreateCategoryDTO,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
return request<any>('/product/category', {
|
return request<any>('/product/category', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
data: body,
|
data: body,
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
|
|
@ -350,14 +380,14 @@ export async function productcontrollerCreatecategory(
|
||||||
export async function productcontrollerUpdatecategory(
|
export async function productcontrollerUpdatecategory(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
params: API.productcontrollerUpdatecategoryParams,
|
params: API.productcontrollerUpdatecategoryParams,
|
||||||
body: Record<string, any>,
|
body: API.UpdateCategoryDTO,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { id: param0, ...queryParams } = params;
|
const { id: param0, ...queryParams } = params;
|
||||||
return request<any>(`/product/category/${param0}`, {
|
return request<any>(`/product/category/${param0}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
params: { ...queryParams },
|
params: { ...queryParams },
|
||||||
data: body,
|
data: body,
|
||||||
|
|
@ -760,6 +790,21 @@ export async function productcontrollerCompatstrengthall(options?: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /product/sync-from-site */
|
||||||
|
export async function productcontrollerSyncproductfromsite(
|
||||||
|
body: Record<string, any>,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<API.ProductRes>('/product/sync-from-site', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /product/sync-stock */
|
/** 此处后端没有提供注释 POST /product/sync-stock */
|
||||||
export async function productcontrollerSyncstocktoproduct(options?: {
|
export async function productcontrollerSyncstocktoproduct(options?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
@ -770,6 +815,21 @@ export async function productcontrollerSyncstocktoproduct(options?: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /product/sync-to-site */
|
||||||
|
export async function productcontrollerSynctosite(
|
||||||
|
body: API.SyncProductToSiteDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<API.SyncProductToSiteResultDTO>('/product/sync-to-site', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /productupdateNameCn/${param1}/${param0} */
|
/** 此处后端没有提供注释 PUT /productupdateNameCn/${param1}/${param0} */
|
||||||
export async function productcontrollerUpdatenamecn(
|
export async function productcontrollerUpdatenamecn(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,16 @@ export async function sitecontrollerGet(
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site/list */
|
/** 此处后端没有提供注释 GET /site/list */
|
||||||
export async function sitecontrollerList(options?: { [key: string]: any }) {
|
export async function sitecontrollerList(
|
||||||
|
// 叠加生成的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,10 +15,6 @@ export async function siteapicontrollerGetcustomers(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
|
|
@ -74,10 +70,6 @@ export async function siteapicontrollerExportcustomers(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -127,10 +119,6 @@ export async function siteapicontrollerGetmedia(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -210,10 +198,6 @@ export async function siteapicontrollerExportmedia(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -230,10 +214,6 @@ export async function siteapicontrollerGetorders(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -280,21 +260,38 @@ export async function siteapicontrollerBatchorders(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-ship */
|
/** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-fulfill */
|
||||||
export async function siteapicontrollerBatchshiporders(
|
export async function siteapicontrollerBatchfulfillorders(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
params: API.siteapicontrollerBatchshipordersParams,
|
params: API.siteapicontrollerBatchfulfillordersParams,
|
||||||
body: API.BatchShipOrdersDTO,
|
body: API.BatchFulfillmentsDTO,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { siteId: param0, ...queryParams } = params;
|
const { siteId: param0, ...queryParams } = params;
|
||||||
return request<Record<string, any>>(`/site-api/${param0}/orders/batch-ship`, {
|
return request<Record<string, any>>(
|
||||||
method: 'POST',
|
`/site-api/${param0}/orders/batch-fulfill`,
|
||||||
headers: {
|
{
|
||||||
'Content-Type': 'application/json',
|
method: 'POST',
|
||||||
|
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 || {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -310,10 +307,6 @@ export async function siteapicontrollerExportorders(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -351,10 +344,6 @@ export async function siteapicontrollerGetproducts(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
|
|
@ -402,6 +391,28 @@ export async function siteapicontrollerBatchproducts(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /site-api/${param0}/products/batch-upsert */
|
||||||
|
export async function siteapicontrollerBatchupsertproduct(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerBatchupsertproductParams,
|
||||||
|
body: Record<string, any>,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { siteId: param0, ...queryParams } = params;
|
||||||
|
return request<API.BatchOperationResultDTO>(
|
||||||
|
`/site-api/${param0}/products/batch-upsert`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param0}/products/export */
|
/** 此处后端没有提供注释 GET /site-api/${param0}/products/export */
|
||||||
export async function siteapicontrollerExportproducts(
|
export async function siteapicontrollerExportproducts(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
@ -413,10 +424,6 @@ export async function siteapicontrollerExportproducts(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -433,10 +440,6 @@ export async function siteapicontrollerExportproductsspecial(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -483,6 +486,25 @@ export async function siteapicontrollerImportproductsspecial(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /site-api/${param0}/products/upsert */
|
||||||
|
export async function siteapicontrollerUpsertproduct(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerUpsertproductParams,
|
||||||
|
body: API.UnifiedProductDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { siteId: param0, ...queryParams } = params;
|
||||||
|
return request<API.UnifiedProductDTO>(`/site-api/${param0}/products/upsert`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param0}/reviews */
|
/** 此处后端没有提供注释 GET /site-api/${param0}/reviews */
|
||||||
export async function siteapicontrollerGetreviews(
|
export async function siteapicontrollerGetreviews(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
@ -496,10 +518,6 @@ export async function siteapicontrollerGetreviews(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
|
|
@ -538,10 +556,6 @@ export async function siteapicontrollerGetsubscriptions(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
|
|
@ -559,10 +573,6 @@ export async function siteapicontrollerExportsubscriptions(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -579,10 +589,6 @@ export async function siteapicontrollerGetwebhooks(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
@ -676,10 +682,6 @@ export async function siteapicontrollerGetcustomerorders(
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
where: undefined,
|
|
||||||
...queryParams['where'],
|
|
||||||
orderBy: undefined,
|
|
||||||
...queryParams['orderBy'],
|
|
||||||
},
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
|
|
@ -766,16 +768,55 @@ export async function siteapicontrollerDeleteorder(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-ship */
|
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-fulfill */
|
||||||
export async function siteapicontrollerCancelshiporder(
|
export async function siteapicontrollerCancelfulfillment(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
params: API.siteapicontrollerCancelshiporderParams,
|
params: API.siteapicontrollerCancelfulfillmentParams,
|
||||||
body: API.CancelShipOrderDTO,
|
body: API.CancelFulfillmentDTO,
|
||||||
options?: { [key: string]: any },
|
options?: { [key: string]: any },
|
||||||
) {
|
) {
|
||||||
const { id: param0, siteId: param1, ...queryParams } = params;
|
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||||
return request<Record<string, any>>(
|
return request<Record<string, any>>(
|
||||||
`/site-api/${param1}/orders/${param0}/cancel-ship`,
|
`/site-api/${param1}/orders/${param0}/cancel-fulfill`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 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: {
|
||||||
|
|
@ -827,28 +868,6 @@ 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默认没有生成对象)
|
||||||
|
|
@ -997,6 +1016,55 @@ export async function siteapicontrollerDeletewebhook(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 PUT /site-api/${param2}/orders/${param1}/fulfillments/${param0} */
|
||||||
|
export async function siteapicontrollerUpdateorderfulfillment(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerUpdateorderfulfillmentParams,
|
||||||
|
body: API.UnifiedOrderTrackingDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
fulfillmentId: param0,
|
||||||
|
orderId: param1,
|
||||||
|
siteId: param2,
|
||||||
|
...queryParams
|
||||||
|
} = params;
|
||||||
|
return request<API.UnifiedOrderTrackingDTO>(
|
||||||
|
`/site-api/${param2}/orders/${param1}/fulfillments/${param0}`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 DELETE /site-api/${param2}/orders/${param1}/fulfillments/${param0} */
|
||||||
|
export async function siteapicontrollerDeleteorderfulfillment(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerDeleteorderfulfillmentParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
fulfillmentId: param0,
|
||||||
|
orderId: param1,
|
||||||
|
siteId: param2,
|
||||||
|
...queryParams
|
||||||
|
} = params;
|
||||||
|
return request<Record<string, any>>(
|
||||||
|
`/site-api/${param2}/orders/${param1}/fulfillments/${param0}`,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
params: { ...queryParams },
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */
|
/** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */
|
||||||
export async function siteapicontrollerUpdatevariation(
|
export async function siteapicontrollerUpdatevariation(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -10,6 +10,26 @@ 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默认没有生成对象)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { message } from 'antd';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作错误项接口
|
||||||
|
*/
|
||||||
|
export interface BatchErrorItem {
|
||||||
|
identifier: string;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作结果接口
|
||||||
|
*/
|
||||||
|
export interface BatchOperationResult {
|
||||||
|
total: number;
|
||||||
|
processed: number;
|
||||||
|
created?: number;
|
||||||
|
updated?: number;
|
||||||
|
deleted?: number;
|
||||||
|
synced?: number;
|
||||||
|
errors?: BatchErrorItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示批量操作结果(导入、删除等)
|
||||||
|
* @param result 批量操作结果对象
|
||||||
|
* @param operationType 操作类型,用于显示在消息中
|
||||||
|
*/
|
||||||
|
export function showBatchOperationResult(
|
||||||
|
result: BatchOperationResult,
|
||||||
|
operationType: string = '操作',
|
||||||
|
): string {
|
||||||
|
// 从 result.data 中获取实际数据(因为后端返回格式为 { success: true, data: {...} })
|
||||||
|
const data = (result as any).data || result;
|
||||||
|
const { total, processed, created, updated, deleted, errors } = data;
|
||||||
|
|
||||||
|
// 构建结果消息
|
||||||
|
let messageContent = `${operationType}结果:共 ${total} 条,成功 ${processed} 条`;
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
messageContent += `,创建 ${created} 条`;
|
||||||
|
}
|
||||||
|
if (updated) {
|
||||||
|
messageContent += `,更新 ${updated} 条`;
|
||||||
|
}
|
||||||
|
if (deleted) {
|
||||||
|
messageContent += `,删除 ${deleted} 条`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理错误情况
|
||||||
|
if (errors && errors.length > 0) {
|
||||||
|
messageContent += `,失败 ${errors.length} 条`;
|
||||||
|
// 显示错误详情
|
||||||
|
const errorDetails = errors
|
||||||
|
.map((err: BatchErrorItem) => `${err.identifier}: ${err.error}`)
|
||||||
|
.join('\n');
|
||||||
|
message.warning(messageContent + '\n\n错误详情:\n' + errorDetails);
|
||||||
|
} else {
|
||||||
|
message.success(messageContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageContent;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue