diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..aa81e64 --- /dev/null +++ b/Dockerfile @@ -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;"] \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..7e658e6 --- /dev/null +++ b/nginx.conf @@ -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; + } +} \ No newline at end of file diff --git a/src/components/SyncResultMessage.tsx b/src/components/SyncResultMessage.tsx new file mode 100644 index 0000000..31ffd48 --- /dev/null +++ b/src/components/SyncResultMessage.tsx @@ -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: ( +
+
{resultMessage}
+
+ 失败详情: + {errors + .slice(0, 3) + .map((err: any) => `${err.identifier}: ${err.error}`) + .join(', ')} + {errors.length > 3 && ` 等 ${errors.length - 3} 个错误...`} +
+
+ ), + duration: 8, + key: 'sync-result', + }); + } else { + // 完全成功 + message.success({ + content: resultMessage, + duration: 4, + key: 'sync-result', + }); + } +}; + +// 同步结果显示组件 +const SyncResultMessage: React.FC = ({ + data, + entityType = '订单', +}) => { + // 当组件挂载时显示结果 + React.useEffect(() => { + if (data) { + showSyncResult(data, entityType); + } + }, [data, entityType]); + + // 这个组件不渲染任何内容,只用于显示消息 + return null; +}; + +export default SyncResultMessage; diff --git a/src/pages/Customer/List/HistoryOrders.tsx b/src/pages/Customer/List/HistoryOrders.tsx index 006e0e1..5c05da9 100644 --- a/src/pages/Customer/List/HistoryOrders.tsx +++ b/src/pages/Customer/List/HistoryOrders.tsx @@ -1,5 +1,4 @@ import { ordercontrollerGetorders } from '@/servers/api/order'; -import { siteapicontrollerGetorders } from '@/servers/api/siteApi'; import { App, Col, @@ -89,8 +88,6 @@ const HistoryOrders: React.FC = ({ customer, siteId }) => { // 获取客户订单数据 const fetchOrders = async () => { - - setLoading(true); try { const response = await ordercontrollerGetorders({ diff --git a/src/pages/Customer/List/index.tsx b/src/pages/Customer/List/index.tsx index 2280537..e993091 100644 --- a/src/pages/Customer/List/index.tsx +++ b/src/pages/Customer/List/index.tsx @@ -12,10 +12,12 @@ import { ModalForm, PageContainer, ProColumns, + ProFormDateTimeRangePicker, ProFormSelect, + ProFormText, ProTable, } from '@ant-design/pro-components'; -import { App, Avatar, Button, Rate, Space, Tag, Tooltip } from 'antd'; +import { App, Avatar, Button, Form, Rate, Space, Tag, Tooltip } from 'antd'; import { useEffect, useRef, useState } from 'react'; import HistoryOrders from './HistoryOrders'; @@ -113,7 +115,6 @@ const CustomerList: React.FC = () => { const actionRef = useRef(); const { message } = App.useApp(); const [syncModalVisible, setSyncModalVisible] = useState(false); - const [syncLoading, setSyncLoading] = useState(false); const [sites, setSites] = useState([]); // 添加站点数据状态 // 获取站点数据 @@ -135,7 +136,6 @@ const CustomerList: React.FC = () => { return siteId; } const site = sites.find((s) => s.id === siteId); - console.log(`site`, site); return site ? site.name : String(siteId); }; @@ -144,11 +144,14 @@ const CustomerList: React.FC = () => { fetchSites(); }, []); - const columns: ProColumns[] = [ + const columns: ProColumns[] = [ { title: 'ID', dataIndex: 'id', - hideInSearch: true, + }, + { + title: '原始 ID', + dataIndex: 'origin_id', }, { title: '站点', @@ -169,8 +172,9 @@ const CustomerList: React.FC = () => { return []; } }, - render: (siteId: any) => { - return {getSiteName(siteId) || '-'}; + render(_, record) { + // console.log(`siteId`, record.site_id); + return {getSiteName(record.site_id) || '-'}; }, }, { @@ -254,7 +258,7 @@ const CustomerList: React.FC = () => { message.error(e?.message || '设置评分失败'); } }} - value={record.raw?.rate || 0} + value={record.rate || 0} allowHalf /> ); @@ -265,7 +269,7 @@ const CustomerList: React.FC = () => { dataIndex: 'tags', hideInSearch: true, render: (_, record) => { - const tags = record.raw?.tags || []; + const tags = record?.tags || []; return ( {tags.map((tag: string) => { @@ -327,7 +331,7 @@ const CustomerList: React.FC = () => { tableRef={actionRef} /> {/* 订单 */} - + ); }, @@ -342,20 +346,59 @@ const CustomerList: React.FC = () => { actionRef={actionRef} rowKey="id" request={async (params, sorter) => { + // 获取排序字段和排序方向 const key = Object.keys(sorter)[0]; - const { data, success } = await customercontrollerGetcustomerlist({ - ...params, - current: params.current?.toString(), - pageSize: params.pageSize?.toString(), - ...(key - ? { sorterKey: key, sorterValue: sorter[key] as string } - : {}), - }); + // 构建过滤条件对象 + const where: any = {}; + + // 添加邮箱过滤 + if (params.email) { + where.email = params.email; + } + + // 添加站点ID过滤 + if (params.site_id) { + where.site_id = params.site_id; + } + + // 添加用户名过滤 + if (params.username) { + where.username = params.username; + } + + // 添加电话过滤 + if (params.phone) { + where.phone = params.phone; + } + + // 构建查询参数 + const queryParams: any = { + page: params.current || 1, + per_page: params.pageSize || 20, + }; + + // 添加搜索关键词 + if (params.fullname) { + queryParams.search = params.fullname; + } + + // 添加过滤条件(只有在有过滤条件时才添加) + if (Object.keys(where).length > 0) { + queryParams.where = where; + } + + // 添加排序 + if (key) { + queryParams.orderBy = { [key]: sorter[key] as 'asc' | 'desc' }; + } + + const result = await customercontrollerGetcustomerlist(queryParams); + console.log(queryParams, result); return { - total: data?.total || 0, - data: data?.items || [], - success, + total: result?.data?.total || 0, + data: result?.data?.items || [], + success: true, }; }} columns={columns} @@ -469,6 +512,7 @@ const SyncCustomersModal: React.FC<{ const { message } = App.useApp(); const [sites, setSites] = useState([]); const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); // 添加表单实例 // 获取站点列表 useEffect(() => { @@ -487,15 +531,56 @@ const SyncCustomersModal: React.FC<{ } }, [visible]); - const handleSync = async (values: { siteId: number }) => { + // 定义同步参数类型 + type SyncParams = { + siteId: number; + search?: string; + role?: string; + dateRange?: [string, string]; + orderBy?: string; + }; + + const handleSync = async (values: SyncParams) => { try { setLoading(true); + + // 构建过滤参数 + const params: any = {}; + + // 添加搜索关键词 + if (values.search) { + params.search = values.search; + } + + // 添加角色过滤 + if (values.role) { + params.where = { + ...params.where, + role: values.role, + }; + } + + // 添加日期范围过滤(使用 after 和 before 参数) + if (values.dateRange && values.dateRange[0] && values.dateRange[1]) { + params.where = { + ...params.where, + after: values.dateRange[0], + before: values.dateRange[1], + }; + } + + // 添加排序 + if (values.orderBy) { + params.orderBy = values.orderBy; + } + const { success, message: msg, data, } = await customercontrollerSynccustomers({ siteId: values.siteId, + params: Object.keys(params).length > 0 ? params : undefined, }); if (success) { @@ -569,6 +654,7 @@ const SyncCustomersModal: React.FC<{ confirmLoading: loading, }} onFinish={handleSync} + form={form} > + + + { + return { + dateRange: value, + }; + }} + fieldProps={{ + showTime: false, + style: { width: '100%' }, + }} + /> + ); }; diff --git a/src/pages/Customer/Statistic/index.tsx b/src/pages/Customer/Statistic/index.tsx index 7bd1877..0f758b3 100644 --- a/src/pages/Customer/Statistic/index.tsx +++ b/src/pages/Customer/Statistic/index.tsx @@ -129,22 +129,16 @@ const ListPage: React.FC = () => { }, }, { - title: 'phone', + title: '联系电话', dataIndex: 'phone', hideInSearch: true, render: (_, record) => record?.billing.phone || record?.shipping.phone, }, { - title: 'state', - dataIndex: 'state', + title: '账单地址', + dataIndex: 'billing', render: (_, record) => record?.billing.state || record?.shipping.state, }, - { - title: 'city', - dataIndex: 'city', - hideInSearch: true, - render: (_, record) => record?.billing.city || record?.shipping.city, - }, { title: '标签', dataIndex: 'tags', diff --git a/src/pages/Dict/List/index.tsx b/src/pages/Dict/List/index.tsx index 54e96f9..858580a 100644 --- a/src/pages/Dict/List/index.tsx +++ b/src/pages/Dict/List/index.tsx @@ -17,6 +17,8 @@ import { message, } from 'antd'; import React, { useEffect, useRef, useState } from 'react'; +import DictItemActions from '../components/DictItemActions'; +import DictItemModal from '../components/DictItemModal'; const { Sider, Content } = Layout; @@ -36,13 +38,10 @@ const DictPage: React.FC = () => { const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false); const [editDictData, setEditDictData] = useState(null); - // 右侧字典项列表的状态 - const [isAddDictItemModalVisible, setIsAddDictItemModalVisible] = - useState(false); - const [isEditDictItemModalVisible, setIsEditDictItemModalVisible] = - useState(false); - const [editDictItemData, setEditDictItemData] = useState(null); - const [dictItemForm] = Form.useForm(); + // 字典项模态框状态(由 DictItemModal 组件管理) + const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false); + const [isEditDictItem, setIsEditDictItem] = useState(false); + const [editingDictItemData, setEditingDictItemData] = useState(null); const actionRef = useRef(); // 获取字典列表 @@ -144,15 +143,16 @@ const DictPage: React.FC = () => { // 添加字典项 const handleAddDictItem = () => { - dictItemForm.resetFields(); - setIsAddDictItemModalVisible(true); + setIsEditDictItem(false); + setEditingDictItemData(null); + setIsDictItemModalVisible(true); }; // 编辑字典项 const handleEditDictItem = (record: any) => { - setEditDictItemData(record); - dictItemForm.setFieldsValue(record); - setIsEditDictItemModalVisible(true); + setIsEditDictItem(true); + setEditingDictItemData(record); + setIsDictItemModalVisible(true); }; // 删除字典项 @@ -173,53 +173,42 @@ const DictPage: React.FC = () => { } }; - // 添加字典项表单提交 - const handleAddDictItemFormSubmit = async (values: any) => { + // 处理字典项模态框提交(添加或编辑) + const handleDictItemModalOk = async (values: any) => { try { - const result = await dictApi.dictcontrollerCreatedictitem({ - ...values, - dictId: selectedDict.id, - }); - - if (!result.success) { - throw new Error(result.message || '添加失败'); + if (isEditDictItem && editingDictItemData) { + // 编辑字典项 + const result = await dictApi.dictcontrollerUpdatedictitem( + { id: editingDictItemData.id }, + values, + ); + if (!result.success) { + throw new Error(result.message || '更新失败'); + } + message.success('更新成功'); + } else { + // 添加字典项 + const result = await dictApi.dictcontrollerCreatedictitem({ + ...values, + dictId: selectedDict.id, + }); + if (!result.success) { + throw new Error(result.message || '添加失败'); + } + message.success('添加成功'); } - - message.success('添加成功'); - setIsAddDictItemModalVisible(false); + setIsDictItemModalVisible(false); // 强制刷新字典项列表 setTimeout(() => { actionRef.current?.reload(); }, 100); } catch (error: any) { - message.error(`添加失败:${error.message || '未知错误'}`); - } - }; - - // 编辑字典项表单提交 - const handleEditDictItemFormSubmit = async (values: any) => { - if (!editDictItemData) return; - try { - const result = await dictApi.dictcontrollerUpdatedictitem( - { id: editDictItemData.id }, - values, + message.error( + `${isEditDictItem ? '更新' : '添加'}失败:${ + error.message || '未知错误' + }`, ); - - if (!result.success) { - throw new Error(result.message || '更新失败'); - } - - message.success('更新成功'); - setIsEditDictItemModalVisible(false); - setEditDictItemData(null); - - // 强制刷新字典项列表 - setTimeout(() => { - actionRef.current?.reload(); - }, 100); - } catch (error: any) { - message.error(`更新失败:${error.message || '未知错误'}`); } }; @@ -337,13 +326,7 @@ const DictPage: React.FC = () => { key: 'shortName', copyable: true, }, - { - title: '图片', - dataIndex: 'image', - key: 'image', - valueType: 'image', - width: 80, - }, + { title: '标题', dataIndex: 'title', @@ -356,6 +339,13 @@ const DictPage: React.FC = () => { key: 'titleCN', copyable: true, }, + { + title: '图片', + dataIndex: 'image', + key: 'image', + valueType: 'image', + width: 80, + }, { title: '操作', key: 'action', @@ -406,7 +396,18 @@ const DictPage: React.FC = () => { { + const { file, onSuccess, onError } = options; + try { + const result = await dictApi.dictcontrollerImportdicts({}, [ + file as File, + ]); + onSuccess?.(result); + } catch (error) { + onError?.(error as Error); + } + }} showUploadList={false} onChange={(info) => { if (info.file.status === 'done') { @@ -495,151 +496,46 @@ const DictPage: React.FC = () => { size="small" key={selectedDict?.id} toolBarRender={() => [ - , - { - const { file, onSuccess, onError } = options; - try { - const result = - await dictApi.dictcontrollerImportdictitems( - { dictId: selectedDict?.id }, - [file as File], - ); - onSuccess?.(result); - } catch (error) { - onError?.(error as Error); - } + { + // 创建 FormData 对象 + const formData = new FormData(); + // 添加文件到 FormData + formData.append('file', file); + // 添加字典 ID 到 FormData + formData.append('dictId', String(dictId)); + // 调用导入字典项的 API,直接返回解析后的 JSON 对象 + const result = await dictApi.dictcontrollerImportdictitems( + formData, + ); + return result; }} - showUploadList={false} - disabled={!selectedDict} - onChange={(info) => { - console.log(`info`, info); - if (info.file.status === 'done') { - message.success(`${info.file.name} 文件上传成功`); - // 重新加载字典项列表 - setTimeout(() => { - actionRef.current?.reload(); - }, 100); - // 重新加载字典列表以更新字典项数量 - fetchDicts(); - } else if (info.file.status === 'error') { - message.error(`${info.file.name} 文件上传失败`); - } - }} - key="import" - > - - , - , + onExport={handleExportDictItems} + onAdd={handleAddDictItem} + onRefreshDicts={fetchDicts} + />, ]} /> - {/* 添加字典项 Modal */} - dictItemForm.submit()} - onCancel={() => setIsAddDictItemModalVisible(false)} - > -
- - - - - - - - - - - - - - - - - - -
-
- - {/* 编辑字典项 Modal */} - dictItemForm.submit()} + {/* 字典项 Modal(添加或编辑) */} + { - setIsEditDictItemModalVisible(false); - setEditDictItemData(null); + setIsDictItemModalVisible(false); + setEditingDictItemData(null); }} - > -
- - - - - - - - - - - - - - - - - - -
-
+ onOk={handleDictItemModalOk} + /> {/* 添加字典 Modal */} ; + // 是否显示导出按钮(某些页面可能不需要导出功能) + showExport?: boolean; + // 导入字典项的回调函数(如果不提供,则使用默认的导入逻辑) + onImport?: (file: File, dictId: number) => Promise; + // 导出字典项的回调函数 + onExport?: () => Promise; + // 添加字典项的回调函数 + onAdd?: () => void; + // 刷新字典列表的回调函数(导入成功后可能需要刷新左侧字典列表) + onRefreshDicts?: () => void; +} + +// 字典项操作组合组件(包含添加、导入、导出按钮) +const DictItemActions: React.FC = ({ + selectedDict, + actionRef, + showExport = true, + onImport, + onExport, + onAdd, + onRefreshDicts, +}) => { + return ( + + {/* 添加字典项按钮 */} + {onAdd && } + + {/* 导入字典项按钮 */} + + + {/* 导出字典项按钮 */} + {showExport && ( + + )} + + ); +}; + +export default DictItemActions; diff --git a/src/pages/Dict/components/DictItemAddButton.tsx b/src/pages/Dict/components/DictItemAddButton.tsx new file mode 100644 index 0000000..7411299 --- /dev/null +++ b/src/pages/Dict/components/DictItemAddButton.tsx @@ -0,0 +1,24 @@ +import { Button } from 'antd'; +import React from 'react'; + +// 字典项添加按钮组件的属性接口 +interface DictItemAddButtonProps { + // 是否禁用按钮 + disabled?: boolean; + // 点击按钮时的回调函数 + onClick: () => void; +} + +// 字典项添加按钮组件 +const DictItemAddButton: React.FC = ({ + disabled = false, + onClick, +}) => { + return ( + + ); +}; + +export default DictItemAddButton; diff --git a/src/pages/Dict/components/DictItemExportButton.tsx b/src/pages/Dict/components/DictItemExportButton.tsx new file mode 100644 index 0000000..04e5b2a --- /dev/null +++ b/src/pages/Dict/components/DictItemExportButton.tsx @@ -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; +} + +// 字典项导出按钮组件 +const DictItemExportButton: React.FC = ({ + 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 ( + + ); +}; + +export default DictItemExportButton; diff --git a/src/pages/Dict/components/DictItemImportButton.tsx b/src/pages/Dict/components/DictItemImportButton.tsx new file mode 100644 index 0000000..1b41e5b --- /dev/null +++ b/src/pages/Dict/components/DictItemImportButton.tsx @@ -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; + // 是否禁用按钮 + disabled?: boolean; + // 自定义导入函数,返回 Promise(如果不提供,则使用默认的导入逻辑) + onImport?: (file: File, dictId: number) => Promise; + // 导入成功后刷新字典列表的回调函数 + onRefreshDicts?: () => void; +} + +// 字典项导入按钮组件 +const DictItemImportButton: React.FC = ({ + 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 ( + + + + ); +}; + +export default DictItemImportButton; diff --git a/src/pages/Dict/components/DictItemModal.tsx b/src/pages/Dict/components/DictItemModal.tsx new file mode 100644 index 0000000..0db2eef --- /dev/null +++ b/src/pages/Dict/components/DictItemModal.tsx @@ -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; +} + +const DictItemModal: React.FC = ({ + 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 ( + +
+ + + + + + + + + + + + + + + + + + +
+
+ ); +}; + +export default DictItemModal; diff --git a/src/pages/Order/List/index.tsx b/src/pages/Order/List/index.tsx index 187f1cb..f4dc9ce 100644 --- a/src/pages/Order/List/index.tsx +++ b/src/pages/Order/List/index.tsx @@ -2,6 +2,7 @@ import styles from '../../../style/order-list.css'; import InternationalPhoneInput from '@/components/InternationalPhoneInput'; import SyncForm from '@/components/SyncForm'; +import { showSyncResult, SyncResultData } from '@/components/SyncResultMessage'; import { ORDER_STATUS_ENUM } from '@/constants'; import { HistoryOrder } from '@/pages/Statistics/Order'; import { @@ -21,6 +22,7 @@ import { ordercontrollerGetorders, ordercontrollerRefundorder, ordercontrollerSyncorderbyid, + ordercontrollerSyncorders, ordercontrollerUpdateorderitems, } from '@/servers/api/order'; import { productcontrollerSearchproducts } from '@/servers/api/product'; @@ -73,7 +75,6 @@ import { Tag, } from 'antd'; import React, { useMemo, useRef, useState } from 'react'; -import { request, useParams } from '@umijs/max'; import RelatedOrders from '../../Subscription/Orders/RelatedOrders'; const ListPage: React.FC = () => { @@ -199,7 +200,7 @@ const ListPage: React.FC = () => { dataIndex: 'keyword', hideInTable: true, }, - { + { title: '订单ID', dataIndex: 'externalOrderId', }, @@ -250,6 +251,7 @@ const ListPage: React.FC = () => { { title: '状态', dataIndex: 'orderStatus', + hideInSearch: true, valueType: 'select', valueEnum: ORDER_STATUS_ENUM, }, @@ -339,15 +341,18 @@ const ListPage: React.FC = () => { message.error('站点ID或外部订单ID不存在'); return; } - const { success, message: errMsg } = - await ordercontrollerSyncorderbyid({ - siteId: record.siteId, - orderId: record.externalOrderId, - }); + const { + success, + message: errMsg, + data, + } = await ordercontrollerSyncorderbyid({ + siteId: record.siteId, + orderId: record.externalOrderId, + }); if (!success) { throw new Error(errMsg); } - message.success('同步成功'); + showSyncResult(data as SyncResultData, '订单'); actionRef.current?.reload(); } catch (error: any) { message.error(error?.message || '同步失败'); @@ -467,16 +472,20 @@ const ListPage: React.FC = () => { defaultPageSize: 10, }} toolBarRender={() => [ - , + // , { try { - const { success, message: errMsg } = - await ordercontrollerSyncorderbyid(values); + const { + success, + message: errMsg, + data, + } = await ordercontrollerSyncorders(values); if (!success) { throw new Error(errMsg); } - message.success('同步成功'); + // 使用 showSyncResult 函数显示详细的同步结果 + showSyncResult(data as SyncResultData, '订单'); actionRef.current?.reload(); } catch (error: any) { message.error(error?.message || '同步失败'); @@ -491,29 +500,19 @@ const ListPage: React.FC = () => { // > // 批量导出 // , - { - console.log(selectedRowKeys); try { - const res = await request('/order/order/export', { - method: 'GET', - params: { + const { success, message: errMsg } = + await ordercontrollerExportorder({ ids: selectedRowKeys, - } - }); - if (res?.success && res?.data?.csv) { - const blob = new Blob([res.data.csv], { type: 'text/csv;charset=utf-8;' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'customers.csv'; - a.click(); - URL.revokeObjectURL(url); - } else { - message.error(res.message || '导出失败'); + }); + if (!success) { + throw new Error(errMsg); } + message.success('导出成功'); actionRef.current?.reload(); setSelectedRowKeys([]); } catch (error: any) { @@ -521,10 +520,14 @@ const ListPage: React.FC = () => { } }} > - - + , ]} request={async ({ date, ...param }: any) => { if (param.status === 'all') { @@ -622,15 +625,18 @@ const Detail: React.FC<{ message.error('站点ID或外部订单ID不存在'); return; } - const { success, message: errMsg } = - await ordercontrollerSyncorderbyid({ - siteId: record.siteId, - orderId: record.externalOrderId, - }); + const { + success, + message: errMsg, + data, + } = await ordercontrollerSyncorderbyid({ + siteId: record.siteId, + orderId: record.externalOrderId, + }); if (!success) { throw new Error(errMsg); } - message.success('同步成功'); + showSyncResult(data as SyncResultData, '订单'); tableRef.current?.reload(); } catch (error: any) { message.error(error?.message || '同步失败'); @@ -2122,12 +2128,12 @@ const SalesChange: React.FC<{ params={{}} request={async ({ keyWords }) => { try { - const { data } = await wpproductcontrollerSearchproducts({ + const { data } = await productcontrollerSearchproducts({ name: keyWords, }); return data?.map((item) => { return { - label: `${item.name}`, + label: `${item.name} - ${item.nameCn}`, value: item?.sku, }; }); diff --git a/src/pages/Product/Attribute/index.tsx b/src/pages/Product/Attribute/index.tsx index f7cce34..5fad811 100644 --- a/src/pages/Product/Attribute/index.tsx +++ b/src/pages/Product/Attribute/index.tsx @@ -1,22 +1,14 @@ -import { UploadOutlined } from '@ant-design/icons'; +import * as dictApi from '@/servers/api/dict'; import { ActionType, PageContainer, ProTable, } from '@ant-design/pro-components'; import { request } from '@umijs/max'; -import { - Button, - Form, - Input, - Layout, - Modal, - Space, - Table, - Upload, - message, -} from 'antd'; +import { Button, Input, Layout, Space, Table, message } from 'antd'; import React, { useEffect, useRef, useState } from 'react'; +import DictItemActions from '../../Dict/components/DictItemActions'; +import DictItemModal from '../../Dict/components/DictItemModal'; const { Sider, Content } = Layout; @@ -32,10 +24,84 @@ const AttributePage: React.FC = () => { // 右侧字典项 ProTable 的引用 const actionRef = useRef(); - // 字典项新增/编辑模态框控制 + // 字典项模态框状态(由 DictItemModal 组件管理) const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false); - const [editingDictItem, setEditingDictItem] = useState(null); - const [dictItemForm] = Form.useForm(); + const [isEditDictItem, setIsEditDictItem] = useState(false); + const [editingDictItemData, setEditingDictItemData] = useState(null); + + // 导出字典项数据 + const handleExportDictItems = async () => { + // 条件判断,确保已选择字典 + if (!selectedDict) { + message.warning('请先选择字典'); + return; + } + + try { + // 获取当前字典的所有数据 + const response = await request('/dict/items', { + params: { + dictId: selectedDict.id, + }, + }); + + // 确保返回的是数组 + const data = Array.isArray(response) ? response : response?.data || []; + + // 条件判断,检查是否有数据可导出 + if (data.length === 0) { + message.warning('当前字典没有数据可导出'); + return; + } + + // 将数据转换为CSV格式 + const headers = [ + 'name', + 'title', + 'titleCN', + 'value', + 'sort', + 'image', + 'shortName', + ]; + const csvContent = [ + headers.join(','), + ...data.map((item: any) => + headers + .map((header) => { + const value = item[header] || ''; + // 条件判断,如果值包含逗号或引号,需要转义 + if ( + typeof value === 'string' && + (value.includes(',') || value.includes('"')) + ) { + return `"${value.replace(/"/g, '""')}"`; + } + return value; + }) + .join(','), + ), + ].join('\n'); + + // 创建blob并下载 + const blob = new Blob(['\ufeff' + csvContent], { + // 添加BOM以支持中文 + type: 'text/csv;charset=utf-8', + }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', `${selectedDict.name}_dict_items.csv`); + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + + message.success(`成功导出 ${data.length} 条数据`); + } catch (error: any) { + message.error('导出字典项失败:' + (error.message || '未知错误')); + } + }; const fetchDicts = async (title?: string) => { setLoadingDicts(true); @@ -65,24 +131,24 @@ const AttributePage: React.FC = () => { // 打开添加字典项模态框 const handleAddDictItem = () => { - setEditingDictItem(null); - dictItemForm.resetFields(); + setIsEditDictItem(false); + setEditingDictItemData(null); setIsDictItemModalVisible(true); }; // 打开编辑字典项模态框 const handleEditDictItem = (item: any) => { - setEditingDictItem(item); - dictItemForm.setFieldsValue(item); + setIsEditDictItem(true); + setEditingDictItemData(item); setIsDictItemModalVisible(true); }; // 字典项表单提交(新增或编辑) const handleDictItemFormSubmit = async (values: any) => { try { - if (editingDictItem) { + if (isEditDictItem && editingDictItemData) { // 条件判断,存在编辑项则执行更新 - await request(`/dict/item/${editingDictItem.id}`, { + await request(`/dict/item/${editingDictItemData.id}`, { method: 'PUT', data: values, }); @@ -98,7 +164,7 @@ const AttributePage: React.FC = () => { setIsDictItemModalVisible(false); actionRef.current?.reload(); // 刷新 ProTable } catch (error) { - message.error(editingDictItem ? '更新失败' : '添加失败'); + message.error(isEditDictItem ? '更新失败' : '添加失败'); } }; @@ -296,85 +362,45 @@ const AttributePage: React.FC = () => { size="small" key={selectedDict?.id} headerTitle={ - - - { - // 条件判断,上传状态处理 - if (info.file.status === 'done') { - message.success(`${info.file.name} 文件上传成功`); - actionRef.current?.reload(); - } else if (info.file.status === 'error') { - message.error(`${info.file.name} 文件上传失败`); - } - }} - > - - - + { + // 创建 FormData 对象 + const formData = new FormData(); + // 添加文件到 FormData + formData.append('file', file); + // 添加字典 ID 到 FormData + formData.append('dictId', String(dictId)); + // 调用导入字典项的 API + const response = await dictApi.dictcontrollerImportdictitems( + formData, + ); + // 返回 JSON 响应 + return await response.json(); + }} + onExport={handleExportDictItems} + onAdd={handleAddDictItem} + onRefreshDicts={fetchDicts} + /> } /> - dictItemForm.submit()} - onCancel={() => setIsDictItemModalVisible(false)} - destroyOnClose - > -
- - - - - - - - - - - - - - - - - - -
-
+ {/* 字典项 Modal(添加或编辑) */} + { + setIsDictItemModalVisible(false); + setEditingDictItemData(null); + }} + onOk={handleDictItemFormSubmit} + /> ); }; diff --git a/src/pages/Product/List/CreateForm.tsx b/src/pages/Product/List/CreateForm.tsx index 270a70f..2243a7f 100644 --- a/src/pages/Product/List/CreateForm.tsx +++ b/src/pages/Product/List/CreateForm.tsx @@ -137,8 +137,8 @@ const CreateForm: React.FC<{ humidityName === 'dry' ? 'Dry' : humidityName === 'moisture' - ? 'Moisture' - : capitalize(humidityName), + ? 'Moisture' + : capitalize(humidityName), }, ); if (!success) { @@ -246,13 +246,7 @@ const CreateForm: React.FC<{ placeholder="请输入SKU" rules={[{ required: true, message: '请输入SKU' }]} /> - + @@ -265,6 +259,14 @@ const CreateForm: React.FC<{ )} + void; + products: API.Product[]; + site?: any; + onSuccess: () => void; +} + +const SyncToSiteModal: React.FC = ({ + visible, + onClose, + products, + site, + onSuccess, +}) => { + const { message } = App.useApp(); + const [sites, setSites] = useState([]); + const formRef = useRef(); + + // 生成单个产品的站点SKU + const generateSingleSiteSku = async ( + currentSite: API.Site, + product: API.Product, + ): Promise => { + 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 = {}; + 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 ( + !open && onClose()} + modalProps={{ destroyOnClose: true }} + formRef={formRef} + onValuesChange={async (changedValues) => { + if ('siteId' in changedValues && changedValues.siteId) { + const siteId = changedValues.siteId; + const currentSite = sites.find((s: any) => s.id === siteId) || {}; + // 站点改变时,重新生成所有产品的站点SKU + generateAndSetSiteSkus(currentSite); + } + }} + onFinish={async (values) => { + if (!values.siteId) return false; + try { + const siteSkusMap = values.siteSkus || {}; + const data = products.map((product) => ({ + productId: product.id, + siteSku: siteSkusMap[product.id] || `${values.siteId}_${product.sku}`, + })); + + const result = await productcontrollerBatchsynctosite({ + siteId: values.siteId, + data, + }); + + showBatchOperationResult(result, '同步到站点'); + onSuccess(); + return true; + } catch (error: any) { + message.error(error.message || '同步失败'); + return false; + } + }} + > + ({ label: site.name, value: site.id }))} + rules={[{ required: true, message: '请选择站点' }]} + /> + {products.map((row) => ( + + {({ siteId }) => ( +
+
+
原始SKU: {row.sku || '-'}
+
+ 商品SKU:{' '} + {row.siteSkus && row.siteSkus.length > 0 + ? row.siteSkus.map((siteSku: string, idx: number) => ( + + {siteSku} + + )) + : '-'} +
+
+
+
+ { + // 手动输入时更新表单值 + const currentValues = formRef.current?.getFieldValue('siteSkus') || {}; + currentValues[row.id] = e.target.value; + formRef.current?.setFieldsValue({ siteSkus: currentValues }); + }, + }} + /> +
+ +
+
+ )} +
+ ))} +
+ ); +}; + +export default SyncToSiteModal; diff --git a/src/pages/Product/List/index.tsx b/src/pages/Product/List/index.tsx index 3f3bd9f..2d8e9c2 100644 --- a/src/pages/Product/List/index.tsx +++ b/src/pages/Product/List/index.tsx @@ -1,15 +1,12 @@ import { productcontrollerBatchdeleteproduct, productcontrollerBatchupdateproduct, - productcontrollerBindproductsiteskus, productcontrollerDeleteproduct, productcontrollerGetcategoriesall, productcontrollerGetproductcomponents, productcontrollerGetproductlist, - productcontrollerUpdatenamecn, + productcontrollerUpdatenamecn } from '@/servers/api/product'; -import { sitecontrollerAll } from '@/servers/api/site'; -import { siteapicontrollerGetproducts } from '@/servers/api/siteApi'; import { ActionType, ModalForm, @@ -17,13 +14,14 @@ import { ProColumns, ProFormSelect, ProFormText, - ProTable, + ProTable } from '@ant-design/pro-components'; import { request } from '@umijs/max'; import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd'; import React, { useEffect, useRef, useState } from 'react'; import CreateForm from './CreateForm'; import EditForm from './EditForm'; +import SyncToSiteModal from './SyncToSiteModal'; const NameCn: React.FC<{ id: number; @@ -166,261 +164,14 @@ const BatchEditModal: React.FC<{ ); }; -const SyncToSiteModal: React.FC<{ - visible: boolean; - onClose: () => void; - productIds: number[]; - productRows: API.Product[]; - onSuccess: () => void; -}> = ({ visible, onClose, productIds, productRows, onSuccess }) => { - const { message } = App.useApp(); - const [sites, setSites] = useState([]); - const formRef = useRef(); - - useEffect(() => { - if (visible) { - sitecontrollerAll().then((res: any) => { - setSites(res?.data || []); - }); - } - }, [visible]); - - return ( - !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 = {}; - 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; - } - }} - > - ({ label: site.name, value: site.id }))} - rules={[{ required: true, message: '请选择站点' }]} - /> - {productRows.map((row) => ( -
-
原始SKU: {row.sku || '-'}
- - -
- ))} -
- ); -}; - -const WpProductInfo: React.FC<{ - skus: string[]; - record: API.Product; - parentTableRef: React.MutableRefObject; -}> = ({ skus, record, parentTableRef }) => { - const actionRef = useRef(); - const { message } = App.useApp(); - - return ( - [ - , - ]} - request={async () => { - // 判断是否存在站点SKU列表 - if (!skus || skus.length === 0) return { data: [] }; - try { - // 获取所有站点列表用于遍历查询 - const { data: siteResponse } = await sitecontrollerAll(); - const siteList = siteResponse || []; - // 聚合所有站点的产品数据 - const aggregatedProducts: any[] = []; - // 遍历每一个站点 - for (const siteItem of siteList) { - // 遍历每一个SKU在当前站点进行搜索 - for (const skuCode of skus) { - // 直接调用站点API根据搜索关键字获取产品列表 - const response = await siteapicontrollerGetproducts({ - siteId: Number(siteItem.id), - per_page: 100, - search: skuCode, - }); - const productPage = response as any; - const siteProducts = productPage?.data?.items || []; - // 将站点信息附加到产品数据中便于展示 - siteProducts.forEach((p: any) => { - aggregatedProducts.push({ - ...p, - siteId: siteItem.id, - siteName: siteItem.name, - }); - }); - } - } - return { data: aggregatedProducts, success: true }; - } catch (error: any) { - // 请求失败进行错误提示 - message.error(error?.message || '获取站点产品失败'); - return { data: [], success: false }; - } - }} - columns={[ - { - title: '站点', - dataIndex: 'siteName', - }, - { - title: 'SKU', - dataIndex: 'sku', - }, - { - title: '价格', - dataIndex: 'regular_price', - render: (_, row) => ( -
-
常规: {row.regular_price}
-
促销: {row.sale_price}
-
- ), - }, - { - title: '状态', - dataIndex: 'status', - }, - { - title: '操作', - valueType: 'option', - render: (_, wpRow) => [ - { - try { - await wpproductcontrollerBatchsynctosite( - { siteId: wpRow.siteId }, - { productIds: [record.id] }, - ); - message.success('同步到站点成功'); - actionRef.current?.reload(); - } catch (e: any) { - message.error(e.message || '同步失败'); - } - }} - > - 同步到站点 - , - { - try { - await wpproductcontrollerSynctoproduct({ id: wpRow.id }); - message.success('同步进商品成功'); - parentTableRef.current?.reload(); - } catch (e: any) { - message.error(e.message || '同步失败'); - } - }} - > - 同步进商品 - , - { - try { - await request(`/wp_product/${wpRow.id}`, { - method: 'DELETE', - }); - message.success('删除成功'); - actionRef.current?.reload(); - } catch (e: any) { - message.error(e.message || '删除失败'); - } - }} - > - 删除 - , - ], - }, - ]} - /> - ); -}; const List: React.FC = () => { const actionRef = useRef(); // 状态:存储当前选中的行 const [selectedRows, setSelectedRows] = React.useState([]); const [batchEditModalVisible, setBatchEditModalVisible] = useState(false); + const [syncProducts, setSyncProducts] = useState([]); const [syncModalVisible, setSyncModalVisible] = useState(false); - const [syncProductIds, setSyncProductIds] = useState([]); const { message } = App.useApp(); // 导出产品 CSV(带认证请求) @@ -460,7 +211,7 @@ const List: React.FC = () => { <> {record.siteSkus?.map((siteSku, index) => ( - {siteSku.siteSku} + {siteSku} ))} @@ -564,7 +315,7 @@ const List: React.FC = () => { , + ]} /> )} diff --git a/src/pages/Product/Sync/SiteProductCell.tsx b/src/pages/Product/Sync/SiteProductCell.tsx new file mode 100644 index 0000000..ec14968 --- /dev/null +++ b/src/pages/Product/Sync/SiteProductCell.tsx @@ -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 = ({ + product, + site, + onSyncSuccess, +}) => { + // 存储该站点对应的产品数据 + const [siteProduct, setSiteProduct] = useState(null); + // 存储本地产品完整数据 + const [localProduct, setLocalProduct] = useState(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 => { + 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 => { + 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 ( +
+ +
+ ); + } + + // 如果没有找到站点产品,显示同步按钮 + if (!siteProduct) { + // 首先查找该产品在该站点的实际SKU + // 注意:siteSkus 现在是字符串数组,无法直接匹配站点 + // 这里使用模板生成的 SKU 作为默认值 + let siteProductSku = ''; + // 如果需要更精确的站点 SKU 匹配,需要后端提供额外的接口 + + const defaultSku = + siteProductSku || `${site.skuPrefix || ''}-${product.sku}`; + + return ( + }> + 同步到站点 + + } + width={400} + onFinish={async (values) => { + return await syncProductToSite(values); + }} + initialValues={{ + sku: defaultSku, + }} + > + + + ); + } + + // 显示站点产品信息 + return ( +
+
+
{siteProduct.sku}
+ } + > + 更新 + + } + width={400} + onFinish={async (values) => { + return await updateSyncProduct(values); + }} + initialValues={{ + sku: siteProduct.sku, + }} + > + +
+ 确定要将本地产品数据更新到站点吗? +
+
+
+
Price: {siteProduct.regular_price ?? siteProduct.price}
+ {siteProduct.sale_price && ( +
Sale: {siteProduct.sale_price}
+ )} +
+ Stock: {siteProduct.stock_quantity ?? siteProduct.stockQuantity} +
+
+ Status:{' '} + {siteProduct.status === 'publish' ? ( + Published + ) : ( + {siteProduct.status} + )} +
+
+ ); +}; + +export default SiteProductCell; diff --git a/src/pages/Product/Sync/index.tsx b/src/pages/Product/Sync/index.tsx index bd6c9ae..6afb087 100644 --- a/src/pages/Product/Sync/index.tsx +++ b/src/pages/Product/Sync/index.tsx @@ -1,13 +1,11 @@ -import { productcontrollerGetproductlist } from '@/servers/api/product'; -import { templatecontrollerGettemplatebyname } from '@/servers/api/template'; -import { EditOutlined, SyncOutlined } from '@ant-design/icons'; +import { showBatchOperationResult } from '@/utils/showResult'; import { - ActionType, - ModalForm, - ProColumns, - ProFormText, - ProTable, -} from '@ant-design/pro-components'; + productcontrollerBatchsynctosite, + productcontrollerGetproductlist, + productcontrollerSynctosite, +} from '@/servers/api/product'; +import { EditOutlined, SyncOutlined } from '@ant-design/icons'; +import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; import { request } from '@umijs/max'; import { Button, @@ -21,39 +19,35 @@ import { } from 'antd'; import React, { useEffect, useRef, useState } from 'react'; import EditForm from '../List/EditForm'; +import SiteProductCell from './SiteProductCell'; // 定义站点接口 interface Site { - id: string; + id: number; name: string; skuPrefix?: string; isDisabled?: boolean; } -// 定义WordPress商品接口 -interface WpProduct { - id?: number; - externalProductId?: string; +// 定义本地产品接口(与后端 Product 实体匹配) +interface SiteProduct { + id: number; sku: string; name: string; - price: string; - regular_price?: string; - sale_price?: string; - stock_quantity: number; - stockQuantity?: number; - status: string; + nameCn: string; + shortDescription?: string; + description?: string; + price: number; + promotionPrice: number; + type: string; + categoryId?: number; + category?: any; attributes?: any[]; - constitution?: { sku: string; quantity: number }[]; -} - -// 扩展本地产品接口,包含对应的 WP 产品信息 -interface ProductWithWP extends API.Product { - wpProducts: Record; - attributes?: any[]; - siteSkus?: Array<{ - siteSku: string; - [key: string]: any; - }>; + components?: any[]; + siteSkus: string[]; + source: number; + createdAt: Date; + updatedAt: Date; } // 定义API响应接口 @@ -79,19 +73,9 @@ const getSites = async (): Promise> => { }; }; -const getWPProducts = async (): Promise> => { - return request('/product/wp-products', { - method: 'GET', - }); -}; - const ProductSyncPage: React.FC = () => { const [sites, setSites] = useState([]); - // 存储所有 WP 产品,用于查找匹配。 Key: SKU (包含前缀) - const [wpProductMap, setWpProductMap] = useState>( - new Map(), - ); - const [skuTemplate, setSkuTemplate] = useState(''); + const [initialLoading, setInitialLoading] = useState(true); const actionRef = useRef(); const [selectedSiteId, setSelectedSiteId] = useState(''); @@ -104,102 +88,46 @@ const ProductSyncPage: React.FC = () => { errors: string[]; }>({ success: 0, failed: 0, errors: [] }); const [selectedRowKeys, setSelectedRowKeys] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - - // 初始化数据:获取站点和所有 WP 产品 + const [selectedRows, setSelectedRows] = useState([]); + // 初始化加载站点列表 useEffect(() => { - const fetchData = async () => { + const initializeData = async () => { try { - setInitialLoading(true); - // 获取所有站点 - const sitesResponse = await getSites(); - const rawSiteList = sitesResponse.data || []; - // 过滤掉已禁用的站点 - const siteList: Site[] = rawSiteList.filter((site) => !site.isDisabled); - setSites(siteList); - - // 获取所有 WordPress 商品 - const wpProductsResponse = await getWPProducts(); - const wpProductList: WpProduct[] = wpProductsResponse.data || []; - - // 构建 WP 产品 Map,Key 为 SKU - const map = new Map(); - wpProductList.forEach((p) => { - if (p.sku) { - map.set(p.sku, p); - } - }); - setWpProductMap(map); - - // 获取 SKU 模板 - try { - const templateRes = await templatecontrollerGettemplatebyname({ - name: 'site.product.sku', - }); - if (templateRes && templateRes.value) { - setSkuTemplate(templateRes.value); - } - } catch (e) { - console.log('Template site.product.sku not found, using default.'); + // 获取站点列表 + const sitesRes = await getSites(); + if (sitesRes.success && sitesRes.data.length > 0) { + setSites(sitesRes.data); } } catch (error) { - message.error('获取基础数据失败,请重试'); - console.error('Error fetching data:', error); + console.error('初始化数据失败:', error); + message.error('初始化数据失败'); } finally { setInitialLoading(false); } }; - fetchData(); + initializeData(); }, []); - // 同步产品到站点 const syncProductToSite = async ( values: any, - record: ProductWithWP, + record: SiteProduct, site: Site, - wpProductId?: string, + siteProductId?: string, ) => { try { const hide = message.loading('正在同步...', 0); - const data = { - name: record.name, - sku: values.sku, - regular_price: record.price?.toString(), - sale_price: record.promotionPrice?.toString(), - type: record.type === 'bundle' ? 'simple' : record.type, - description: record.description, - status: 'publish', - stock_status: 'instock', - manage_stock: false, - }; - let res; - if (wpProductId) { - res = await request(`/site-api/${site.id}/products/${wpProductId}`, { - method: 'PUT', - data, - }); - } else { - res = await request(`/site-api/${site.id}/products`, { - method: 'POST', - data, - }); - } + // 使用 productcontrollerSynctosite API 同步产品到站点 + const res = await productcontrollerSynctosite({ + productId: Number(record.id), + siteId: Number(site.id), + } as any); - console.log('res', res); if (!res.success) { hide(); throw new Error(res.message || '同步失败'); } - // 更新本地缓存 Map,避免刷新 - setWpProductMap((prev) => { - const newMap = new Map(prev); - if (res.data && typeof res.data === 'object') { - newMap.set(values.sku, res.data as WpProduct); - } - return newMap; - }); hide(); message.success('同步成功'); @@ -207,12 +135,11 @@ const ProductSyncPage: React.FC = () => { } catch (error: any) { message.error('同步失败: ' + (error.message || error.toString())); return false; - } finally { } }; // 批量同步产品到指定站点 - const batchSyncProducts = async (productsToSync?: ProductWithWP[]) => { + const batchSyncProducts = async (productsToSync?: SiteProduct[]) => { if (!selectedSiteId) { message.error('请选择要同步到的站点'); return; @@ -239,7 +166,7 @@ const ProductSyncPage: React.FC = () => { message.error('获取产品列表失败'); return; } - products = data.items as ProductWithWP[]; + products = data.items as SiteProduct[]; } catch (error) { message.error('获取产品列表失败'); return; @@ -250,108 +177,44 @@ const ProductSyncPage: React.FC = () => { setSyncProgress(0); setSyncResults({ success: 0, failed: 0, errors: [] }); - const totalProducts = products.length; - let processed = 0; - let successCount = 0; - let failedCount = 0; - const errors: string[] = []; - try { - // 逐个同步产品 - for (const product of products) { - try { - // 获取该产品在目标站点的SKU - let siteProductSku = ''; - if (product.siteSkus && product.siteSkus.length > 0) { - const siteSkuInfo = product.siteSkus.find((sku: any) => { - return ( - sku.siteSku && - sku.siteSku.includes(targetSite.skuPrefix || targetSite.name) - ); - }); - if (siteSkuInfo) { - siteProductSku = siteSkuInfo.siteSku; - } - } + // 使用 productcontrollerBatchsynctosite API 批量同步 + const productIds = products.map((product) => Number(product.id)); - // 如果没有找到实际的siteSku,则根据模板生成 - const expectedSku = - siteProductSku || - (skuTemplate - ? renderSiteSku(skuTemplate, { site: targetSite, product }) - : `${targetSite.skuPrefix || ''}-${product.sku}`); + // 更新进度为50%,表示正在处理 + setSyncProgress(50); - // 检查是否已存在 - const existingProduct = wpProductMap.get(expectedSku); + const res = await productcontrollerBatchsynctosite({ + productIds: productIds, + siteId: Number(targetSite.id), + } as any); - // 准备同步数据 - const syncData = { - name: product.name, - sku: expectedSku, - regular_price: product.price?.toString(), - sale_price: product.promotionPrice?.toString(), - type: product.type === 'bundle' ? 'simple' : product.type, - description: product.description, - status: 'publish', - stock_status: 'instock', - manage_stock: false, - }; + if (res.success) { + const syncedCount = res.data?.synced || 0; + const errors = res.data?.errors || []; - let res; - if (existingProduct?.externalProductId) { - // 更新现有产品 - res = await request( - `/site-api/${targetSite.id}/products/${existingProduct.externalProductId}`, - { - method: 'PUT', - data: syncData, - }, - ); - } else { - // 创建新产品 - res = await request(`/site-api/${targetSite.id}/products`, { - method: 'POST', - data: syncData, - }); - } + // 更新进度为100%,表示完成 + setSyncProgress(100); - console.log('res', res); + setSyncResults({ + success: syncedCount, + failed: errors.length, + errors: errors.map((err: any) => err.error || '未知错误'), + }); - if (res.success) { - successCount++; - // 更新本地缓存 - setWpProductMap((prev) => { - const newMap = new Map(prev); - if (res.data && typeof res.data === 'object') { - newMap.set(expectedSku, res.data as WpProduct); - } - return newMap; - }); - } else { - failedCount++; - errors.push(`产品 ${product.sku}: ${res.message || '同步失败'}`); - } - } catch (error: any) { - failedCount++; - errors.push(`产品 ${product.sku}: ${error.message || '未知错误'}`); + if (errors.length === 0) { + message.success(`批量同步完成,成功同步 ${syncedCount} 个产品`); + } else { + message.warning( + `批量同步完成,成功 ${syncedCount} 个,失败 ${errors.length} 个`, + ); } - processed++; - setSyncProgress(Math.round((processed / totalProducts) * 100)); - } - - setSyncResults({ success: successCount, failed: failedCount, errors }); - - if (failedCount === 0) { - message.success(`批量同步完成,成功同步 ${successCount} 个产品`); + // 刷新表格 + actionRef.current?.reload(); } else { - message.warning( - `批量同步完成,成功 ${successCount} 个,失败 ${failedCount} 个`, - ); + throw new Error(res.message || '批量同步失败'); } - - // 刷新表格 - actionRef.current?.reload(); } catch (error: any) { message.error('批量同步失败: ' + (error.message || error.toString())); } finally { @@ -359,27 +222,9 @@ const ProductSyncPage: React.FC = () => { } }; - // 简单的模板渲染函数 - const renderSiteSku = (template: string, data: any) => { - if (!template) return ''; - // 支持 <%= it.path %> (Eta) 和 {{ path }} (Mustache/Handlebars) - return template.replace( - /<%=\s*it\.([\w.]+)\s*%>|\{\{\s*([\w.]+)\s*\}\}/g, - (_, p1, p2) => { - const path = p1 || p2; - const keys = path.split('.'); - let value = data; - for (const key of keys) { - value = value?.[key]; - } - return value === undefined || value === null ? '' : String(value); - }, - ); - }; - // 生成表格列配置 - const generateColumns = (): ProColumns[] => { - const columns: ProColumns[] = [ + const generateColumns = (): ProColumns[] => { + const columns: ProColumns[] = [ { title: 'SKU', dataIndex: 'sku', @@ -471,137 +316,21 @@ const ProductSyncPage: React.FC = () => { // 为每个站点生成列 sites.forEach((site: Site) => { - const siteColumn: ProColumns = { + const siteColumn: ProColumns = { title: site.name, key: `site_${site.id}`, hideInSearch: true, width: 220, render: (_, record) => { - // 首先查找该产品在该站点的实际SKU - let siteProductSku = ''; - if (record.siteSkus && record.siteSkus.length > 0) { - // 根据站点名称匹配对应的siteSku - const siteSkuInfo = record.siteSkus.find((sku: any) => { - // 这里假设可以根据站点名称或其他标识来匹配 - // 如果需要更精确的匹配逻辑,可以根据实际需求调整 - return ( - sku.siteSku && sku.siteSku.includes(site.skuPrefix || site.name) - ); - }); - if (siteSkuInfo) { - siteProductSku = siteSkuInfo.siteSku; - } - } - - // 如果没有找到实际的siteSku,则根据模板或默认规则生成期望的SKU - const expectedSku = - siteProductSku || - (skuTemplate - ? renderSiteSku(skuTemplate, { site, product: record }) - : `${site.skuPrefix || ''}-${record.sku}`); - - // 尝试用确定的SKU获取WP产品 - let wpProduct = wpProductMap.get(expectedSku); - - // 如果根据实际SKU没找到,再尝试用模板生成的SKU查找 - if (!wpProduct && siteProductSku && skuTemplate) { - const templateSku = renderSiteSku(skuTemplate, { - site, - product: record, - }); - wpProduct = wpProductMap.get(templateSku); - } - - if (!wpProduct) { - return ( - }> - 同步到站点 - - } - width={400} - onFinish={async (values) => { - return await syncProductToSite(values, record, site); - }} - initialValues={{ - sku: - siteProductSku || - (skuTemplate - ? renderSiteSku(skuTemplate, { site, product: record }) - : `${site.skuPrefix || ''}-${record.sku}`), - }} - > - - - ); - } return ( -
-
-
{wpProduct.sku}
- } - > - } - width={400} - onFinish={async (values) => { - return await syncProductToSite( - values, - record, - site, - wpProduct.externalProductId, - ); - }} - initialValues={{ - sku: wpProduct.sku, - }} - > - -
- 确定要将本地产品数据更新到站点吗? -
-
-
-
Price: {wpProduct.regular_price ?? wpProduct.price}
- {wpProduct.sale_price && ( -
Sale: {wpProduct.sale_price}
- )} -
- Stock: {wpProduct.stock_quantity ?? wpProduct.stockQuantity} -
-
- Status:{' '} - {wpProduct.status === 'publish' ? ( - Published - ) : ( - {wpProduct.status} - )} -
-
+ { + // 同步成功后刷新表格 + actionRef.current?.reload(); + }} + /> ); }, }; @@ -624,7 +353,7 @@ const ProductSyncPage: React.FC = () => { return ( - + columns={generateColumns()} actionRef={actionRef} rowKey="id" @@ -665,21 +394,21 @@ const ProductSyncPage: React.FC = () => { ]} request={async (params, sort, filter) => { // 调用本地获取产品列表 API - const { data, success } = await productcontrollerGetproductlist({ + const response = await productcontrollerGetproductlist({ ...params, current: params.current, pageSize: params.pageSize, // 传递搜索参数 - keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段 + // keyword: params.keyword, // 假设 ProTable 的 search 表单会传递 keyword 或其他字段 sku: (params as any).sku, name: (params as any).name, } as any); - + console.log('result', response); // 返回给 ProTable return { - data: (data?.items || []) as ProductWithWP[], - success, - total: data?.total || 0, + data: response.data?.items || [], + success: response.success, + total: response.data?.total || 0, }; }} pagination={{ diff --git a/src/pages/Product/Sync/index.tsx.bak b/src/pages/Product/Sync/index.tsx.bak new file mode 100644 index 0000000..41d868d --- /dev/null +++ b/src/pages/Product/Sync/index.tsx.bak @@ -0,0 +1,761 @@ +import { productcontrollerGetproductlist } from '@/servers/api/product'; +import { siteapicontrollerGetproducts } from '@/servers/api/siteApi'; +import { templatecontrollerGettemplatebyname } from '@/servers/api/template'; +import { EditOutlined, SyncOutlined } from '@ant-design/icons'; +import { + ActionType, + ModalForm, + ProColumns, + ProFormText, + ProTable, +} from '@ant-design/pro-components'; +import { request } from '@umijs/max'; +import { + Button, + Card, + message, + Modal, + Progress, + Select, + Spin, + Tag, +} from 'antd'; +import React, { useEffect, useRef, useState } from 'react'; +import EditForm from '../List/EditForm'; + +// 定义站点接口 +interface Site { + id: string; + name: string; + skuPrefix?: string; + isDisabled?: boolean; +} + +// 定义站点商品接口 +interface SiteProduct { + id: string; + sku: string; + name: string; + price: number; + stock: number; + categories: string[]; +} + +// 定义API响应接口 +interface ApiResponse { + data: T[]; + success: boolean; + message?: string; +} + +// 模拟API请求函数 +const getSites = async (): Promise> => { + const res = await request('/site/list', { + method: 'GET', + params: { + current: 1, + pageSize: 1000, + }, + }); + return { + data: res.data?.items || [], + success: res.success, + message: res.message, + }; +}; + +const getsiteProduct = async (): Promise> => { + return request('/product/wp-products', { + method: 'GET', + }); +}; + +const ProductSyncPage: React.FC = () => { + const [sites, setSites] = useState([]); + // 存储所有 WP 产品,用于查找匹配。 Key: SKU (包含前缀) + const [siteProductMap, setSiteProductMap] = useState>( + new Map(), + ); + const [skuTemplate, setSkuTemplate] = useState(''); + const [initialLoading, setInitialLoading] = useState(true); + const actionRef = useRef(); + const [selectedSiteId, setSelectedSiteId] = useState(''); + const [batchSyncModalVisible, setBatchSyncModalVisible] = useState(false); + const [syncProgress, setSyncProgress] = useState(0); + const [syncing, setSyncing] = useState(false); + const [syncResults, setSyncResults] = useState<{ + success: number; + failed: number; + errors: string[]; + }>({ success: 0, failed: 0, errors: [] }); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [selectedRows, setSelectedRows] = useState([]); + + // 初始化数据:获取站点和所有 WP 产品 + useEffect(() => { + const fetchData = async () => { + try { + setInitialLoading(true); + // 获取所有站点 + const sitesResponse = await getSites(); + const rawSiteList = sitesResponse.data || []; + // 过滤掉已禁用的站点 + const siteList: Site[] = rawSiteList.filter((site) => !site.isDisabled); + setSites(siteList); + + // 获取所有 WordPress 商品 + const siteProductsResponse = await siteapicontrollerGetproducts({ + siteId: selectedSiteId, + }); + const siteProductList: SiteProduct[] = siteProductsResponse.data || []; + + // 构建 WP 产品 Map,Key 为 SKU + const map = new Map(); + siteProductList.forEach((p) => { + if (p.sku) { + map.set(p.sku, p); + } + }); + setSiteProductMap(map); + + // 获取 SKU 模板 + try { + const templateRes = await templatecontrollerGettemplatebyname({ + name: 'site.product.sku', + }); + if (templateRes && templateRes.value) { + setSkuTemplate(templateRes.value); + } + } catch (e) { + console.log('Template site.product.sku not found, using default.'); + } + } catch (error) { + message.error('获取基础数据失败,请重试'); + console.error('Error fetching data:', error); + } finally { + setInitialLoading(false); + } + }; + + fetchData(); + }, []); + + // 同步产品到站点 + const syncProductToSite = async ( + values: any, + record: ProductWithWP, + site: Site, + siteProductId?: string, + ) => { + try { + const hide = message.loading('正在同步...', 0); + const data = { + name: record.name, + sku: values.sku, + regular_price: record.price?.toString(), + sale_price: record.promotionPrice?.toString(), + type: record.type === 'bundle' ? 'simple' : record.type, + description: record.description, + status: 'publish', + stock_status: 'instock', + manage_stock: false, + }; + + let res; + if (siteProductId) { + res = await request(`/site-api/${site.id}/products/${siteProductId}`, { + method: 'PUT', + data, + }); + } else { + res = await request(`/site-api/${site.id}/products`, { + method: 'POST', + data, + }); + } + + console.log('res', res); + if (!res.success) { + hide(); + throw new Error(res.message || '同步失败'); + } + // 更新本地缓存 Map,避免刷新 + setSiteProductMap((prev) => { + const newMap = new Map(prev); + if (res.data && typeof res.data === 'object') { + newMap.set(values.sku, res.data as SiteProduct); + } + return newMap; + }); + + hide(); + message.success('同步成功'); + return true; + } catch (error: any) { + message.error('同步失败: ' + (error.message || error.toString())); + return false; + } finally { + } + }; + + // 批量同步产品到指定站点 + const batchSyncProducts = async (productsToSync?: ProductWithWP[]) => { + if (!selectedSiteId) { + message.error('请选择要同步到的站点'); + return; + } + + const targetSite = sites.find((site) => site.id === selectedSiteId); + if (!targetSite) { + message.error('选择的站点不存在'); + return; + } + + // 如果没有传入产品列表,则使用选中的产品 + let products = productsToSync || selectedRows; + + // 如果既没有传入产品也没有选中产品,则同步所有产品 + if (!products || products.length === 0) { + try { + const { data, success } = await productcontrollerGetproductlist({ + current: 1, + pageSize: 10000, // 获取所有产品 + } as any); + + if (!success || !data?.items) { + message.error('获取产品列表失败'); + return; + } + products = data.items as ProductWithWP[]; + } catch (error) { + message.error('获取产品列表失败'); + return; + } + } + + setSyncing(true); + setSyncProgress(0); + setSyncResults({ success: 0, failed: 0, errors: [] }); + + const totalProducts = products.length; + let processed = 0; + let successCount = 0; + let failedCount = 0; + const errors: string[] = []; + + try { + // 逐个同步产品 + for (const product of products) { + try { + // 获取该产品在目标站点的SKU + let siteProductSku = ''; + if (product.siteSkus && product.siteSkus.length > 0) { + const siteSkuInfo = product.siteSkus.find((sku: any) => { + return ( + sku.siteSku && + sku.siteSku.includes(targetSite.skuPrefix || targetSite.name) + ); + }); + if (siteSkuInfo) { + siteProductSku = siteSkuInfo.siteSku; + } + } + + // 如果没有找到实际的siteSku,则根据模板生成 + const expectedSku = + siteProductSku || + (skuTemplate + ? renderSiteSku(skuTemplate, { site: targetSite, product }) + : `${targetSite.skuPrefix || ''}-${product.sku}`); + + // 检查是否已存在 + const existingProduct = siteProductMap.get(expectedSku); + + // 准备同步数据 + const syncData = { + name: product.name, + sku: expectedSku, + regular_price: product.price?.toString(), + sale_price: product.promotionPrice?.toString(), + type: product.type === 'bundle' ? 'simple' : product.type, + description: product.description, + status: 'publish', + stock_status: 'instock', + manage_stock: false, + }; + + let res; + if (existingProduct?.externalProductId) { + // 更新现有产品 + res = await request( + `/site-api/${targetSite.id}/products/${existingProduct.externalProductId}`, + { + method: 'PUT', + data: syncData, + }, + ); + } else { + // 创建新产品 + res = await request(`/site-api/${targetSite.id}/products`, { + method: 'POST', + data: syncData, + }); + } + + console.log('res', res); + + if (res.success) { + successCount++; + // 更新本地缓存 + setSiteProductMap((prev) => { + const newMap = new Map(prev); + if (res.data && typeof res.data === 'object') { + newMap.set(expectedSku, res.data as SiteProduct); + } + return newMap; + }); + } else { + failedCount++; + errors.push(`产品 ${product.sku}: ${res.message || '同步失败'}`); + } + } catch (error: any) { + failedCount++; + errors.push(`产品 ${product.sku}: ${error.message || '未知错误'}`); + } + + processed++; + setSyncProgress(Math.round((processed / totalProducts) * 100)); + } + + setSyncResults({ success: successCount, failed: failedCount, errors }); + + if (failedCount === 0) { + message.success(`批量同步完成,成功同步 ${successCount} 个产品`); + } else { + message.warning( + `批量同步完成,成功 ${successCount} 个,失败 ${failedCount} 个`, + ); + } + + // 刷新表格 + actionRef.current?.reload(); + } catch (error: any) { + message.error('批量同步失败: ' + (error.message || error.toString())); + } finally { + setSyncing(false); + } + }; + + // 简单的模板渲染函数 + const renderSiteSku = (template: string, data: any) => { + if (!template) return ''; + // 支持 <%= it.path %> (Eta) 和 {{ path }} (Mustache/Handlebars) + return template.replace( + /<%=\s*it\.([\w.]+)\s*%>|\{\{\s*([\w.]+)\s*\}\}/g, + (_, p1, p2) => { + const path = p1 || p2; + const keys = path.split('.'); + let value = data; + for (const key of keys) { + value = value?.[key]; + } + return value === undefined || value === null ? '' : String(value); + }, + ); + }; + + // 生成表格列配置 + const generateColumns = (): ProColumns[] => { + const columns: ProColumns[] = [ + { + title: 'SKU', + dataIndex: 'sku', + key: 'sku', + width: 150, + fixed: 'left', + copyable: true, + }, + { + title: '商品信息', + key: 'profile', + width: 300, + fixed: 'left', + render: (_, record) => ( +
+
+
+ {record.name} +
+ + } + /> +
+
+ 价格: {record.price} + {record.promotionPrice && ( + + 促销价: {record.promotionPrice} + + )} +
+ + {/* 属性 */} +
+ {record.attributes?.map((attr: any, idx: number) => ( + + {attr.dict?.name || attr.name}: {attr.name} + + ))} +
+ + {/* 组成 (如果是 Bundle) */} + {record.type === 'bundle' && + record.components && + record.components.length > 0 && ( +
+
+ Components: +
+ {record.components.map((comp: any, idx: number) => ( +
+ {comp.sku} × {comp.quantity} +
+ ))} +
+ )} +
+ ), + }, + ]; + + // 为每个站点生成列 + sites.forEach((site: Site) => { + const siteColumn: ProColumns = { + title: site.name, + key: `site_${site.id}`, + hideInSearch: true, + width: 220, + render: (_, record) => { + // 首先查找该产品在该站点的实际SKU + let siteProductSku = ''; + if (record.siteSkus && record.siteSkus.length > 0) { + // 根据站点名称匹配对应的siteSku + const siteSkuInfo = record.siteSkus.find((sku: any) => { + // 这里假设可以根据站点名称或其他标识来匹配 + // 如果需要更精确的匹配逻辑,可以根据实际需求调整 + return ( + sku.siteSku && sku.siteSku.includes(site.skuPrefix || site.name) + ); + }); + if (siteSkuInfo) { + siteProductSku = siteSkuInfo.siteSku; + } + } + + // 如果没有找到实际的siteSku,则根据模板或默认规则生成期望的SKU + const expectedSku = + siteProductSku || + (skuTemplate + ? renderSiteSku(skuTemplate, { site, product: record }) + : `${site.skuPrefix || ''}-${record.sku}`); + + // 尝试用确定的SKU获取WP产品 + let siteProduct = siteProductMap.get(expectedSku); + + // 如果根据实际SKU没找到,再尝试用模板生成的SKU查找 + if (!siteProduct && siteProductSku && skuTemplate) { + const templateSku = renderSiteSku(skuTemplate, { + site, + product: record, + }); + siteProduct = siteProductMap.get(templateSku); + } + + if (!siteProduct) { + return ( + }> + 同步到站点 + + } + width={400} + onFinish={async (values) => { + return await syncProductToSite(values, record, site); + }} + initialValues={{ + sku: + siteProductSku || + (skuTemplate + ? renderSiteSku(skuTemplate, { site, product: record }) + : `${site.skuPrefix || ''}-${record.sku}`), + }} + > + + + ); + } + return ( +
+
+
{siteProduct.sku}
+ } + > + } + width={400} + onFinish={async (values) => { + return await syncProductToSite( + values, + record, + site, + siteProduct.externalProductId, + ); + }} + initialValues={{ + sku: siteProduct.sku, + }} + > + +
+ 确定要将本地产品数据更新到站点吗? +
+
+
+
Price: {siteProduct.regular_price ?? siteProduct.price}
+ {siteProduct.sale_price && ( +
Sale: {siteProduct.sale_price}
+ )} +
+ Stock: {siteProduct.stock_quantity ?? siteProduct.stockQuantity} +
+
+ Status:{' '} + {siteProduct.status === 'publish' ? ( + Published + ) : ( + {siteProduct.status} + )} +
+
+ ); + }, + }; + columns.push(siteColumn); + }); + + return columns; + }; + + if (initialLoading) { + return ( + + + + ); + } + + return ( + + + columns={generateColumns()} + actionRef={actionRef} + rowKey="id" + rowSelection={{ + selectedRowKeys, + onChange: (keys, rows) => { + setSelectedRowKeys(keys); + setSelectedRows(rows); + }, + }} + toolBarRender={() => [ + ({ + options={sites?.map?.((site) => ({ label: site.name, value: site.id, }))} diff --git a/src/pages/Site/Shop/Media/index.tsx b/src/pages/Site/Shop/Media/index.tsx index e1b0cfd..045458d 100644 --- a/src/pages/Site/Shop/Media/index.tsx +++ b/src/pages/Site/Shop/Media/index.tsx @@ -203,7 +203,7 @@ const MediaPage: React.FC = () => { const response = await request(`/site-api/${siteId}/media`, { params: { page: current, - page_size: pageSize, + per_page: pageSize, ...(orderObj ? { order: orderObj } : {}), }, }); diff --git a/src/pages/Site/Shop/Orders/index.tsx b/src/pages/Site/Shop/Orders/index.tsx index e92ac31..a949523 100644 --- a/src/pages/Site/Shop/Orders/index.tsx +++ b/src/pages/Site/Shop/Orders/index.tsx @@ -18,6 +18,7 @@ import { CreateOrder, EditOrder, OrderNote, + ShipOrderForm, } from '../components/Order/Forms'; const OrdersPage: React.FC = () => { @@ -111,7 +112,9 @@ const OrdersPage: React.FC = () => { return (
{record.line_items.map((item: any) => ( -
{`${item.name} x ${item.quantity}`}
+
{`${item.name}(${item.sku}) x ${item.quantity}`}
))}
); @@ -145,6 +148,44 @@ const OrdersPage: React.FC = () => { ellipsis: true, copyable: true, }, + { + title: '物流', + dataIndex: 'tracking', + hideInSearch: true, + render: (_, record) => { + // 检查是否有物流信息 + if ( + !record.tracking || + !Array.isArray(record.tracking) || + record.tracking.length === 0 + ) { + return '-'; + } + // 遍历物流信息数组, 显示每个物流的提供商和单号 + return ( +
+ {record.tracking.map((item: any, index: number) => ( +
+ + {item.tracking_provider + ? `快递方式: ${item.tracking_provider}` + : ''} + + {item.tracking_number + ? `物流单号: ${item.tracking_number}` + : ''} + + {item.date_shipped ? `发货日期: ${item.date_shipped}` : ''} + +
+ ))} +
+ ); + }, + }, { title: '操作', dataIndex: 'option', @@ -186,28 +227,17 @@ const OrdersPage: React.FC = () => { > + ({ + id: item.id, + name: item.name, + quantity: item.quantity, + sku: item.sku, + }))} + /> {record.status === 'completed' && ( { , ]} request={async (params, sort, filter) => { - const p: any = params || {}; - const current = p.current; - const pageSize = p.pageSize; - const date = p.date; - const status = p.status; - const { - current: _c, - pageSize: _ps, - date: _d, - status: _s, - ...rest - } = p; + const { current, pageSize, date, status, ...rest } = params; const where: Record = { ...(filter || {}), ...rest }; if (status && status !== 'all') { where.status = status; @@ -401,7 +420,7 @@ const OrdersPage: React.FC = () => { const response = await request(`/site-api/${siteId}/orders`, { params: { page: current, - page_size: pageSize, + per_page: pageSize, where, ...(orderObj ? { order: orderObj } : {}), }, diff --git a/src/pages/Site/Shop/components/Order/Forms.tsx b/src/pages/Site/Shop/components/Order/Forms.tsx index a9bc74b..aa03f52 100644 --- a/src/pages/Site/Shop/components/Order/Forms.tsx +++ b/src/pages/Site/Shop/components/Order/Forms.tsx @@ -47,6 +47,133 @@ const region = { YT: 'Yukon', }; +// 定义发货订单表单的数据类型 +export interface ShipOrderFormData { + tracking_number?: string; + shipping_provider?: string; + shipping_method?: string; + items?: Array<{ + id?: string; + quantity?: number; + }>; +} + +// 发货订单表单组件 +export const ShipOrderForm: React.FC<{ + orderId: number; + tableRef?: React.MutableRefObject; + siteId?: string; + orderItems?: Array<{ + id: string; + name: string; + quantity: number; + sku?: string; + }>; +}> = ({ orderId, tableRef, siteId, orderItems }) => { + const { message } = App.useApp(); + const formRef = useRef(); + + return ( + + 发货 + + } + 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' }); + } + }} + > + + + + + + + {orderItems && orderItems.length > 0 && ( + + + ({ + label: `${item.name} (SKU: ${item.sku || 'N/A'}) - 可发数量: ${ + item.quantity + }`, + value: item.id, + }))} + rules={[{ required: true, message: '请选择商品' }]} + /> + + + + )} + + ); +}; + export const OrderNote: React.FC<{ id: number; descRef?: React.MutableRefObject; diff --git a/src/pages/Site/Shop/components/Product/Forms.tsx b/src/pages/Site/Shop/components/Product/Forms.tsx index cf30dbd..be086bc 100644 --- a/src/pages/Site/Shop/components/Product/Forms.tsx +++ b/src/pages/Site/Shop/components/Product/Forms.tsx @@ -613,18 +613,6 @@ export const UpdateVaritation: React.FC<{ ); }; -// ... SetComponent, BatchEditProducts, BatchDeleteProducts, ImportCsv ... -// I will keep them but comment out/disable parts that rely on old API if I can't easily fix them all. -// BatchEdit/Delete rely on old API. -// I'll comment out their usage in ProductsPage or just return null here. -// I'll keep them but they might break if used. -// Since I removed them from ProductsPage toolbar (Wait, I kept them in ProductsPage toolbar!), I should update them or remove them. -// I'll update BatchDelete to use new API (loop delete). -// BatchEdit? `wpproductcontrollerBatchUpdateProducts`. -// I don't have batch update in my new API. -// I'll remove BatchEdit from ProductsPage toolbar for now or implement batch update in Controller. -// I'll update BatchDelete. - export const BatchDeleteProducts: React.FC<{ tableRef: React.MutableRefObject; selectedRowKeys: React.Key[]; diff --git a/src/pages/Statistics/BestAttributes/index.tsx b/src/pages/Statistics/BestAttributes/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/Statistics/BestProduct/index.tsx b/src/pages/Statistics/BestProduct/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/servers/api/category.ts b/src/servers/api/category.ts index bd120e5..edbd063 100644 --- a/src/servers/api/category.ts +++ b/src/servers/api/category.ts @@ -23,13 +23,13 @@ export async function categorycontrollerGetlist( /** 此处后端没有提供注释 POST /category/ */ export async function categorycontrollerCreate( - body: Record, + body: API.CreateCategoryDTO, options?: { [key: string]: any }, ) { return request('/category/', { method: 'POST', headers: { - 'Content-Type': 'text/plain', + 'Content-Type': 'application/json', }, data: body, ...(options || {}), @@ -40,14 +40,14 @@ export async function categorycontrollerCreate( export async function categorycontrollerUpdate( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) params: API.categorycontrollerUpdateParams, - body: Record, + body: API.UpdateCategoryDTO, options?: { [key: string]: any }, ) { const { id: param0, ...queryParams } = params; return request(`/category/${param0}`, { method: 'PUT', headers: { - 'Content-Type': 'text/plain', + 'Content-Type': 'application/json', }, params: { ...queryParams }, data: body, diff --git a/src/servers/api/customer.ts b/src/servers/api/customer.ts index 2d2b137..4aaf018 100644 --- a/src/servers/api/customer.ts +++ b/src/servers/api/customer.ts @@ -2,6 +2,68 @@ /* eslint-disable */ import { request } from 'umi'; +/** 此处后端没有提供注释 POST /customer/ */ +export async function customercontrollerCreatecustomer( + body: API.CreateCustomerDTO, + options?: { [key: string]: any }, +) { + return request('/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(`/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(`/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>(`/customer/${param0}`, { + method: 'DELETE', + params: { ...queryParams }, + ...(options || {}), + }); +} + /** 此处后端没有提供注释 POST /customer/addtag */ export async function customercontrollerAddtag( body: API.CustomerTagDTO, @@ -17,6 +79,51 @@ export async function customercontrollerAddtag( }); } +/** 此处后端没有提供注释 PUT /customer/batch */ +export async function customercontrollerBatchupdatecustomers( + body: API.BatchUpdateCustomerDTO, + options?: { [key: string]: any }, +) { + return request>('/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>('/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>('/customer/batch', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + /** 此处后端没有提供注释 POST /customer/deltag */ export async function customercontrollerDeltag( body: API.CustomerTagDTO, @@ -32,36 +139,6 @@ export async function customercontrollerDeltag( }); } -/** 此处后端没有提供注释 GET /customer/getcustomerlist */ -export async function customercontrollerGetcustomerlist( - // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) - params: API.customercontrollerGetcustomerlistParams, - options?: { [key: string]: any }, -) { - return request>('/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>('/customer/getcustomerstatisticlist', { - method: 'GET', - params: { - ...params, - }, - ...(options || {}), - }); -} - /** 此处后端没有提供注释 GET /customer/gettags */ export async function customercontrollerGettags(options?: { [key: string]: any; @@ -72,6 +149,16 @@ export async function customercontrollerGettags(options?: { }); } +/** 此处后端没有提供注释 GET /customer/list */ +export async function customercontrollerGetcustomerlist(options?: { + [key: string]: any; +}) { + return request('/customer/list', { + method: 'GET', + ...(options || {}), + }); +} + /** 此处后端没有提供注释 POST /customer/setrate */ export async function customercontrollerSetrate( body: Record, @@ -87,15 +174,25 @@ export async function customercontrollerSetrate( }); } +/** 此处后端没有提供注释 GET /customer/statistic/list */ +export async function customercontrollerGetcustomerstatisticlist(options?: { + [key: string]: any; +}) { + return request>('/customer/statistic/list', { + method: 'GET', + ...(options || {}), + }); +} + /** 此处后端没有提供注释 POST /customer/sync */ export async function customercontrollerSynccustomers( - body: Record, + body: API.SyncCustomersDTO, options?: { [key: string]: any }, ) { return request>('/customer/sync', { method: 'POST', headers: { - 'Content-Type': 'text/plain', + 'Content-Type': 'application/json', }, data: body, ...(options || {}), diff --git a/src/servers/api/dict.ts b/src/servers/api/dict.ts index ae7c520..e307620 100644 --- a/src/servers/api/dict.ts +++ b/src/servers/api/dict.ts @@ -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('/dict/item/export', { + method: 'GET', + params: { + ...params, + }, + ...(options || {}), + }); +} + /** 此处后端没有提供注释 POST /dict/item/import */ export async function dictcontrollerImportdictitems( body: Record, options?: { [key: string]: any }, ) { - return request('/dict/item/import', { + return request('/dict/item/import', { method: 'POST', headers: { 'Content-Type': 'text/plain', diff --git a/src/servers/api/order.ts b/src/servers/api/order.ts index 05d9710..22dfe95 100644 --- a/src/servers/api/order.ts +++ b/src/servers/api/order.ts @@ -225,15 +225,15 @@ export async function ordercontrollerChangestatus( }); } -/** 此处后端没有提供注释 POST /order/syncOrder/${param0} */ -export async function ordercontrollerSyncorder( +/** 此处后端没有提供注释 POST /order/sync/${param0} */ +export async function ordercontrollerSyncorders( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) - params: API.ordercontrollerSyncorderParams, + params: API.ordercontrollerSyncordersParams, body: Record, options?: { [key: string]: any }, ) { const { siteId: param0, ...queryParams } = params; - return request(`/order/syncOrder/${param0}`, { + return request(`/order/sync/${param0}`, { method: 'POST', headers: { 'Content-Type': 'text/plain', diff --git a/src/servers/api/product.ts b/src/servers/api/product.ts index 905ff3f..4234c67 100644 --- a/src/servers/api/product.ts +++ b/src/servers/api/product.ts @@ -229,6 +229,36 @@ export async function productcontrollerBatchdeleteproduct( }); } +/** 此处后端没有提供注释 POST /product/batch-sync-from-site */ +export async function productcontrollerBatchsyncfromsite( + body: Record, + options?: { [key: string]: any }, +) { + return request('/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('/product/batch-sync-to-site', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + /** 此处后端没有提供注释 PUT /product/batch-update */ export async function productcontrollerBatchupdateproduct( body: API.BatchUpdateProductDTO, @@ -333,13 +363,13 @@ export async function productcontrollerGetcategoriesall(options?: { /** 此处后端没有提供注释 POST /product/category */ export async function productcontrollerCreatecategory( - body: Record, + body: API.CreateCategoryDTO, options?: { [key: string]: any }, ) { return request('/product/category', { method: 'POST', headers: { - 'Content-Type': 'text/plain', + 'Content-Type': 'application/json', }, data: body, ...(options || {}), @@ -350,14 +380,14 @@ export async function productcontrollerCreatecategory( export async function productcontrollerUpdatecategory( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) params: API.productcontrollerUpdatecategoryParams, - body: Record, + body: API.UpdateCategoryDTO, options?: { [key: string]: any }, ) { const { id: param0, ...queryParams } = params; return request(`/product/category/${param0}`, { method: 'PUT', headers: { - 'Content-Type': 'text/plain', + 'Content-Type': 'application/json', }, params: { ...queryParams }, data: body, @@ -558,6 +588,10 @@ export async function productcontrollerGetproductlist( method: 'GET', params: { ...params, + where: undefined, + ...params['where'], + orderBy: undefined, + ...params['orderBy'], }, ...(options || {}), }); @@ -760,6 +794,21 @@ export async function productcontrollerCompatstrengthall(options?: { }); } +/** 此处后端没有提供注释 POST /product/sync-from-site */ +export async function productcontrollerSyncproductfromsite( + body: Record, + options?: { [key: string]: any }, +) { + return request('/product/sync-from-site', { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + data: body, + ...(options || {}), + }); +} + /** 此处后端没有提供注释 POST /product/sync-stock */ export async function productcontrollerSyncstocktoproduct(options?: { [key: string]: any; @@ -770,6 +819,21 @@ export async function productcontrollerSyncstocktoproduct(options?: { }); } +/** 此处后端没有提供注释 POST /product/sync-to-site */ +export async function productcontrollerSynctosite( + body: API.SyncProductToSiteDTO, + options?: { [key: string]: any }, +) { + return request('/product/sync-to-site', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + /** 此处后端没有提供注释 PUT /productupdateNameCn/${param1}/${param0} */ export async function productcontrollerUpdatenamecn( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) diff --git a/src/servers/api/siteApi.ts b/src/servers/api/siteApi.ts index 9745d48..1884c2a 100644 --- a/src/servers/api/siteApi.ts +++ b/src/servers/api/siteApi.ts @@ -280,23 +280,26 @@ export async function siteapicontrollerBatchorders( ); } -/** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-ship */ -export async function siteapicontrollerBatchshiporders( +/** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-fulfill */ +export async function siteapicontrollerBatchfulfillorders( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) - params: API.siteapicontrollerBatchshipordersParams, - body: API.BatchShipOrdersDTO, + params: API.siteapicontrollerBatchfulfillordersParams, + body: API.BatchFulfillmentsDTO, options?: { [key: string]: any }, ) { const { siteId: param0, ...queryParams } = params; - return request>(`/site-api/${param0}/orders/batch-ship`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', + return request>( + `/site-api/${param0}/orders/batch-fulfill`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + params: { ...queryParams }, + data: body, + ...(options || {}), }, - params: { ...queryParams }, - data: body, - ...(options || {}), - }); + ); } /** 此处后端没有提供注释 GET /site-api/${param0}/orders/export */ @@ -402,6 +405,28 @@ export async function siteapicontrollerBatchproducts( ); } +/** 此处后端没有提供注释 POST /site-api/${param0}/products/batch-upsert */ +export async function siteapicontrollerBatchupsertproduct( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.siteapicontrollerBatchupsertproductParams, + body: Record, + options?: { [key: string]: any }, +) { + const { siteId: param0, ...queryParams } = params; + return request( + `/site-api/${param0}/products/batch-upsert`, + { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + params: { ...queryParams }, + data: body, + ...(options || {}), + }, + ); +} + /** 此处后端没有提供注释 GET /site-api/${param0}/products/export */ export async function siteapicontrollerExportproducts( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) @@ -483,6 +508,25 @@ export async function siteapicontrollerImportproductsspecial( ); } +/** 此处后端没有提供注释 POST /site-api/${param0}/products/upsert */ +export async function siteapicontrollerUpsertproduct( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.siteapicontrollerUpsertproductParams, + body: API.UnifiedProductDTO, + options?: { [key: string]: any }, +) { + const { siteId: param0, ...queryParams } = params; + return request(`/site-api/${param0}/products/upsert`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + params: { ...queryParams }, + data: body, + ...(options || {}), + }); +} + /** 此处后端没有提供注释 GET /site-api/${param0}/reviews */ export async function siteapicontrollerGetreviews( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) @@ -766,16 +810,60 @@ export async function siteapicontrollerDeleteorder( }); } -/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-ship */ -export async function siteapicontrollerCancelshiporder( +/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-fulfill */ +export async function siteapicontrollerCancelfulfillment( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) - params: API.siteapicontrollerCancelshiporderParams, - body: API.CancelShipOrderDTO, + params: API.siteapicontrollerCancelfulfillmentParams, + body: API.CancelFulfillmentDTO, options?: { [key: string]: any }, ) { const { id: param0, siteId: param1, ...queryParams } = params; return request>( - `/site-api/${param1}/orders/${param0}/cancel-ship`, + `/site-api/${param1}/orders/${param0}/cancel-fulfill`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + params: { ...queryParams }, + data: body, + ...(options || {}), + }, + ); +} + +/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/fulfill */ +export async function siteapicontrollerFulfillorder( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.siteapicontrollerFulfillorderParams, + body: API.FulfillmentDTO, + options?: { [key: string]: any }, +) { + const { id: param0, siteId: param1, ...queryParams } = params; + return request>( + `/site-api/${param1}/orders/${param0}/fulfill`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + params: { ...queryParams }, + data: body, + ...(options || {}), + }, + ); +} + +/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/fulfillments */ +export async function siteapicontrollerCreateorderfulfillment( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.siteapicontrollerCreateorderfulfillmentParams, + body: API.UnifiedOrderTrackingDTO, + options?: { [key: string]: any }, +) { + const { orderId: param0, siteId: param1, ...queryParams } = params; + return request( + `/site-api/${param1}/orders/${param0}/fulfillments`, { method: 'POST', headers: { @@ -827,23 +915,18 @@ export async function siteapicontrollerCreateordernote( ); } -/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/ship */ -export async function siteapicontrollerShiporder( +/** 此处后端没有提供注释 GET /site-api/${param1}/orders/${param0}/trackings */ +export async function siteapicontrollerGetordertrackings( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) - params: API.siteapicontrollerShiporderParams, - body: API.ShipOrderDTO, + params: API.siteapicontrollerGetordertrackingsParams, options?: { [key: string]: any }, ) { - const { id: param0, siteId: param1, ...queryParams } = params; + const { orderId: param0, siteId: param1, ...queryParams } = params; return request>( - `/site-api/${param1}/orders/${param0}/ship`, + `/site-api/${param1}/orders/${param0}/trackings`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + method: 'GET', params: { ...queryParams }, - data: body, ...(options || {}), }, ); @@ -997,6 +1080,55 @@ export async function siteapicontrollerDeletewebhook( ); } +/** 此处后端没有提供注释 PUT /site-api/${param2}/orders/${param1}/fulfillments/${param0} */ +export async function siteapicontrollerUpdateorderfulfillment( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.siteapicontrollerUpdateorderfulfillmentParams, + body: API.UnifiedOrderTrackingDTO, + options?: { [key: string]: any }, +) { + const { + fulfillmentId: param0, + orderId: param1, + siteId: param2, + ...queryParams + } = params; + return request( + `/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>( + `/site-api/${param2}/orders/${param1}/fulfillments/${param0}`, + { + method: 'DELETE', + params: { ...queryParams }, + ...(options || {}), + }, + ); +} + /** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */ export async function siteapicontrollerUpdatevariation( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) diff --git a/src/servers/api/typings.d.ts b/src/servers/api/typings.d.ts index e60d33a..9acc9d2 100644 --- a/src/servers/api/typings.d.ts +++ b/src/servers/api/typings.d.ts @@ -7,6 +7,17 @@ declare namespace API { postal_code?: string; }; + type ApiResponse = { + /** 状态码 */ + code?: number; + /** 是否成功 */ + success?: boolean; + /** 提示信息 */ + message?: string; + /** 返回数据 */ + data?: Record; + }; + type Area = { /** 名称 */ name?: string; @@ -35,6 +46,16 @@ declare namespace API { id: number; }; + type BatchCreateCustomerDTO = { + /** 客户列表 */ + customers?: CreateCustomerDTO[]; + }; + + type BatchDeleteCustomerDTO = { + /** 客户ID列表 */ + ids?: number[]; + }; + type BatchDeleteProductDTO = { /** 产品ID列表 */ ids: any[]; @@ -47,6 +68,24 @@ declare namespace API { error?: string; }; + type BatchFulfillmentItemDTO = { + /** 订单ID */ + order_id?: string; + /** 物流单号 */ + tracking_number?: string; + /** 物流公司 */ + shipping_provider?: string; + /** 发货方式 */ + shipping_method?: string; + /** 发货商品项 */ + items?: FulfillmentItemDTO[]; + }; + + type BatchFulfillmentsDTO = { + /** 批量发货订单列表 */ + orders?: BatchFulfillmentItemDTO[]; + }; + type BatchOperationDTO = { /** 要创建的数据列表 */ create?: any[]; @@ -73,22 +112,16 @@ declare namespace API { errors?: BatchErrorItemDTO[]; }; - type BatchShipOrderItemDTO = { - /** 订单ID */ - order_id?: string; - /** 物流单号 */ - tracking_number?: string; - /** 物流公司 */ - shipping_provider?: string; - /** 发货方式 */ - shipping_method?: string; - /** 发货商品项 */ - items?: ShipOrderItemDTO[]; + type BatchSyncProductToSiteDTO = { + /** 站点ID */ + siteId?: number; + /** 产品站点SKU列表 */ + data?: ProductSiteSkuDTO[]; }; - type BatchShipOrdersDTO = { - /** 批量发货订单列表 */ - orders?: BatchShipOrderItemDTO[]; + type BatchUpdateCustomerDTO = { + /** 客户更新列表 */ + customers?: UpdateCustomerItemDTO[]; }; type BatchUpdateProductDTO = { @@ -129,7 +162,7 @@ declare namespace API { data?: boolean; }; - type CancelShipOrderDTO = { + type CancelFulfillmentDTO = { /** 取消原因 */ reason?: string; /** 发货单ID */ @@ -163,6 +196,54 @@ declare namespace API { code?: string; }; + type CreateCategoryDTO = { + /** 分类显示名称 */ + title: string; + /** 分类中文名称 */ + titleCN?: string; + /** 分类唯一标识 */ + name: string; + /** 分类短名称,用于生成SKU */ + shortName?: string; + /** 排序 */ + sort?: number; + }; + + type CreateCustomerDTO = { + /** 站点ID */ + site_id?: number; + /** 原始ID */ + origin_id?: number; + /** 邮箱 */ + email?: string; + /** 名字 */ + first_name?: string; + /** 姓氏 */ + last_name?: string; + /** 全名 */ + fullname?: string; + /** 用户名 */ + username?: string; + /** 电话 */ + phone?: string; + /** 头像URL */ + avatar?: string; + /** 账单信息 */ + billing?: Record; + /** 配送信息 */ + shipping?: Record; + /** 原始数据 */ + raw?: Record; + /** 评分 */ + rate?: number; + /** 标签列表 */ + tags?: string[]; + /** 站点创建时间 */ + site_created_at?: string; + /** 站点更新时间 */ + site_updated_at?: string; + }; + type CreateDictDTO = {}; type CreateDictItemDTO = {}; @@ -271,32 +352,22 @@ declare namespace API { unit?: string; }; - type customercontrollerGetcustomerlistParams = { - current?: string; - pageSize?: string; - email?: string; - tags?: string; - sorterKey?: string; - sorterValue?: string; - state?: string; - first_purchase_date?: string; - customerId?: number; + type customercontrollerDeletecustomerParams = { + id: number; }; - type customercontrollerGetcustomerstatisticlistParams = { - current?: string; - pageSize?: string; - email?: string; - tags?: string; - sorterKey?: string; - sorterValue?: string; - state?: string; - first_purchase_date?: string; - customerId?: number; + type customercontrollerGetcustomerbyidParams = { + id: number; + }; + + type customercontrollerUpdatecustomerParams = { + id: number; }; type CustomerTagDTO = { + /** 客户邮箱 */ email?: string; + /** 标签名称 */ tag?: string; }; @@ -324,6 +395,10 @@ declare namespace API { id: number; }; + type dictcontrollerExportdictitemsParams = { + dictId?: number; + }; + type dictcontrollerGetdictitemsbydictnameParams = { name?: string; }; @@ -353,6 +428,65 @@ declare namespace API { type DisableSiteDTO = {}; + type FulfillmentDTO = { + /** 物流单号 */ + tracking_number?: string; + /** 物流公司 */ + shipping_provider?: string; + /** 发货方式 */ + shipping_method?: string; + /** 发货商品项 */ + items?: FulfillmentItemDTO[]; + }; + + type FulfillmentItemDTO = { + /** 订单项ID */ + order_item_id?: number; + /** 数量 */ + quantity?: number; + }; + + type GetCustomerDTO = { + /** 客户ID */ + id?: number; + /** 站点ID */ + site_id?: number; + /** 原始ID */ + origin_id?: number; + /** 站点创建时间 */ + site_created_at?: string; + /** 站点更新时间 */ + site_updated_at?: string; + /** 邮箱 */ + email?: string; + /** 名字 */ + first_name?: string; + /** 姓氏 */ + last_name?: string; + /** 全名 */ + fullname?: string; + /** 用户名 */ + username?: string; + /** 电话 */ + phone?: string; + /** 头像URL */ + avatar?: string; + /** 账单信息 */ + billing?: Record; + /** 配送信息 */ + shipping?: Record; + /** 原始数据 */ + raw?: Record; + /** 创建时间 */ + created_at?: string; + /** 更新时间 */ + updated_at?: string; + /** 评分 */ + rate?: number; + /** 标签列表 */ + tags?: string[]; + }; + type localecontrollerGetlocaleParams = { lang: string; }; @@ -620,7 +754,7 @@ declare namespace API { siteId: number; }; - type ordercontrollerSyncorderParams = { + type ordercontrollerSyncordersParams = { siteId: number; }; @@ -854,7 +988,6 @@ declare namespace API { purchaseType?: 'all' | 'first_purchase' | 'repeat_purchase'; orderType?: 'all' | 'cpc' | 'non_cpc'; brand?: 'all' | 'zyn' | 'yoone' | 'zolt'; - grouping?: 'day' | 'week' | 'month'; }; type OrderStatusCountDTO = { @@ -898,7 +1031,7 @@ declare namespace API { /** 库存组成 */ components?: ProductStockComponent[]; /** 站点 SKU 列表 */ - siteSkus?: ProductSiteSku[]; + siteSkus?: string[]; /** 来源 */ source?: number; /** 创建时间 */ @@ -1019,20 +1152,16 @@ declare namespace API { }; type productcontrollerGetproductlistParams = { - /** 当前页 */ - current?: number; + /** 页码 */ + page?: number; /** 每页数量 */ - pageSize?: number; - /** 搜索关键字 */ - name?: string; - /** 分类ID */ - categoryId?: number; - /** 品牌ID */ - brandId?: number; - /** 排序字段 */ - sortField?: string; - /** 排序方式 */ - sortOrder?: string; + per_page?: number; + /** 搜索关键词 */ + search?: string; + /** 过滤条件对象 */ + where?: Record; + /** 排序对象,例如 { "sku": "desc" } */ + orderBy?: Record; }; type productcontrollerGetproductsiteskusParams = { @@ -1098,8 +1227,10 @@ declare namespace API { data?: Product; }; - type ProductSiteSku = { - /** 站点 SKU */ + type ProductSiteSkuDTO = { + /** 产品ID */ + productId?: number; + /** 站点SKU */ siteSku?: string; }; @@ -1182,18 +1313,6 @@ declare namespace API { keyword?: string; }; - type QueryCustomerListDTO = { - current?: string; - pageSize?: string; - email?: string; - tags?: string; - sorterKey?: string; - sorterValue?: string; - state?: string; - first_purchase_date?: string; - customerId?: number; - }; - type QueryOrderDTO = { /** 页码 */ current?: number; @@ -1257,23 +1376,6 @@ declare namespace API { pageSize?: number; }; - type QueryProductDTO = { - /** 当前页 */ - current?: number; - /** 每页数量 */ - pageSize?: number; - /** 搜索关键字 */ - name?: string; - /** 分类ID */ - categoryId?: number; - /** 品牌ID */ - brandId?: number; - /** 排序字段 */ - sortField?: string; - /** 排序方式 */ - sortOrder?: string; - }; - type QueryPurchaseOrderDTO = { /** 页码 */ current?: number; @@ -1424,24 +1526,6 @@ declare namespace API { weightUom?: string; }; - type ShipOrderDTO = { - /** 物流单号 */ - tracking_number?: string; - /** 物流公司 */ - shipping_provider?: string; - /** 发货方式 */ - shipping_method?: string; - /** 发货商品项 */ - items?: ShipOrderItemDTO[]; - }; - - type ShipOrderItemDTO = { - /** 订单项ID */ - order_item_id?: number; - /** 数量 */ - quantity?: number; - }; - type ShippingAddress = { id?: number; name?: string; @@ -1481,6 +1565,10 @@ declare namespace API { siteId: number; }; + type siteapicontrollerBatchfulfillordersParams = { + siteId: number; + }; + type siteapicontrollerBatchmediaParams = { siteId: number; }; @@ -1493,11 +1581,11 @@ declare namespace API { siteId: number; }; - type siteapicontrollerBatchshipordersParams = { + type siteapicontrollerBatchupsertproductParams = { siteId: number; }; - type siteapicontrollerCancelshiporderParams = { + type siteapicontrollerCancelfulfillmentParams = { id: string; siteId: number; }; @@ -1514,6 +1602,11 @@ declare namespace API { siteId: number; }; + type siteapicontrollerCreateorderfulfillmentParams = { + orderId: string; + siteId: number; + }; + type siteapicontrollerCreateordernoteParams = { id: string; siteId: number; @@ -1545,6 +1638,12 @@ declare namespace API { siteId: number; }; + type siteapicontrollerDeleteorderfulfillmentParams = { + fulfillmentId: string; + orderId: string; + siteId: number; + }; + type siteapicontrollerDeleteorderParams = { id: string; siteId: number; @@ -1574,10 +1673,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1592,10 +1687,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1610,10 +1701,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1628,10 +1715,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1646,10 +1729,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1664,15 +1743,16 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; }; + type siteapicontrollerFulfillorderParams = { + id: string; + siteId: number; + }; + type siteapicontrollerGetcustomerordersParams = { /** 页码 */ page?: number; @@ -1682,10 +1762,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; customerId: number; @@ -1706,10 +1782,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1728,10 +1800,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1756,15 +1824,16 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; }; + type siteapicontrollerGetordertrackingsParams = { + orderId: string; + siteId: number; + }; + type siteapicontrollerGetproductParams = { id: string; siteId: number; @@ -1779,10 +1848,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1797,10 +1862,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1815,10 +1876,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1838,10 +1895,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; siteId: number; @@ -1863,11 +1916,6 @@ declare namespace API { siteId: number; }; - type siteapicontrollerShiporderParams = { - id: string; - siteId: number; - }; - type siteapicontrollerUpdatecustomerParams = { id: string; siteId: number; @@ -1878,6 +1926,12 @@ declare namespace API { siteId: number; }; + type siteapicontrollerUpdateorderfulfillmentParams = { + fulfillmentId: string; + orderId: string; + siteId: number; + }; + type siteapicontrollerUpdateorderParams = { id: string; siteId: number; @@ -1904,6 +1958,10 @@ declare namespace API { siteId: number; }; + type siteapicontrollerUpsertproductParams = { + siteId: number; + }; + type SiteConfig = { /** 站点 ID */ id?: string; @@ -2235,6 +2293,50 @@ declare namespace API { amount?: Money; }; + type SyncCustomersDTO = { + /** 站点ID */ + siteId?: number; + /** 查询参数(支持where和orderBy) */ + params?: UnifiedSearchParamsDTO; + }; + + type SyncOperationResultDTO = { + /** 总处理数量 */ + total?: number; + /** 成功处理数量 */ + processed?: number; + /** 创建数量 */ + created?: number; + /** 更新数量 */ + updated?: number; + /** 删除数量 */ + deleted?: number; + /** 跳过的数量 */ + skipped?: number; + /** 错误列表 */ + errors?: BatchErrorItemDTO[]; + /** 同步成功数量 */ + synced?: number; + }; + + type SyncProductToSiteDTO = { + /** 产品ID */ + productId?: number; + /** 站点SKU */ + siteSku?: string; + /** 站点ID */ + siteId?: number; + }; + + type SyncProductToSiteResultDTO = { + /** 同步状态 */ + success?: boolean; + /** 远程产品ID */ + remoteId?: string; + /** 错误信息 */ + error?: string; + }; + type Template = { id?: number; name?: string; @@ -2296,8 +2398,6 @@ declare namespace API { email?: string; /** 电话 */ phone?: string; - /** 配送方式 */ - method_title?: string; }; type UnifiedCategoryDTO = { @@ -2364,10 +2464,6 @@ declare namespace API { per_page?: number; /** 总页数 */ totalPages?: number; - /** 分页后的数据 */ - after?: string; - /** 分页前的数据 */ - before?: string; }; type UnifiedFeeLineDTO = { @@ -2428,10 +2524,6 @@ declare namespace API { per_page?: number; /** 总页数 */ totalPages?: number; - /** 分页后的数据 */ - after?: string; - /** 分页前的数据 */ - before?: string; }; type UnifiedOrderDTO = { @@ -2443,8 +2535,6 @@ declare namespace API { status?: string; /** 货币 */ currency?: string; - /** 货币符号 */ - currency_symbol?: string; /** 总金额 */ total?: string; /** 客户ID */ @@ -2453,8 +2543,6 @@ declare namespace API { customer_name?: string; /** 客户邮箱 */ email?: string; - /** 客户邮箱 */ - customer_email?: string; /** 订单项(具体的商品) */ line_items?: UnifiedOrderLineItemDTO[]; /** 销售项(兼容前端) */ @@ -2469,6 +2557,8 @@ declare namespace API { shipping_full_address?: string; /** 支付方式 */ payment_method?: string; + /** 退款列表 */ + refunds?: UnifiedOrderRefundDTO[]; /** 创建时间 */ date_created?: string; /** 更新时间 */ @@ -2481,16 +2571,8 @@ declare namespace API { fee_lines?: UnifiedFeeLineDTO[]; /** 优惠券项 */ coupon_lines?: UnifiedCouponLineDTO[]; - /** 支付时间 */ - date_paid?: string; - /** 客户IP地址 */ - customer_ip_address?: string; - /** UTM来源 */ - utm_source?: string; - /** 设备类型 */ - device_type?: string; - /** 来源类型 */ - source_type?: string; + /** 物流追踪信息 */ + tracking?: UnifiedOrderTrackingDTO[]; }; type UnifiedOrderLineItemDTO = { @@ -2521,10 +2603,28 @@ declare namespace API { per_page?: number; /** 总页数 */ totalPages?: number; - /** 分页后的数据 */ - after?: string; - /** 分页前的数据 */ - before?: string; + }; + + type UnifiedOrderRefundDTO = { + /** 退款ID */ + id?: Record; + /** 退款原因 */ + reason?: string; + /** 退款金额 */ + total?: string; + }; + + type UnifiedOrderTrackingDTO = { + /** 订单ID */ + order_id?: string; + /** 快递公司 */ + tracking_provider?: string; + /** 运单跟踪号 */ + tracking_number?: string; + /** 发货日期 */ + date_shipped?: string; + /** 发货状态 */ + status_shipped?: string; }; type UnifiedPaginationDTO = { @@ -2538,10 +2638,6 @@ declare namespace API { per_page?: number; /** 总页数 */ totalPages?: number; - /** 分页后的数据 */ - after?: string; - /** 分页前的数据 */ - before?: string; }; type UnifiedProductAttributeDTO = { @@ -2557,6 +2653,8 @@ declare namespace API { variation?: boolean; /** 属性选项 */ options?: string[]; + /** 变体属性值(单个值) */ + option?: string; }; type UnifiedProductDTO = { @@ -2613,15 +2711,13 @@ declare namespace API { per_page?: number; /** 总页数 */ totalPages?: number; - /** 分页后的数据 */ - after?: string; - /** 分页前的数据 */ - before?: string; }; type UnifiedProductVariationDTO = { /** 变体ID */ id?: Record; + /** 变体名称 */ + name?: string; /** 变体SKU */ sku?: string; /** 常规价格 */ @@ -2634,8 +2730,34 @@ declare namespace API { stock_status?: string; /** 库存数量 */ stock_quantity?: number; + /** 变体属性 */ + attributes?: UnifiedProductAttributeDTO[]; /** 变体图片 */ image?: UnifiedImageDTO; + /** 变体描述 */ + description?: string; + /** 是否启用 */ + enabled?: boolean; + /** 是否可下载 */ + downloadable?: boolean; + /** 是否为虚拟商品 */ + virtual?: boolean; + /** 管理库存 */ + manage_stock?: boolean; + /** 重量 */ + weight?: string; + /** 长度 */ + length?: string; + /** 宽度 */ + width?: string; + /** 高度 */ + height?: string; + /** 运输类别 */ + shipping_class?: string; + /** 税类别 */ + tax_class?: string; + /** 菜单顺序 */ + menu_order?: number; }; type UnifiedReviewDTO = { @@ -2672,10 +2794,6 @@ declare namespace API { per_page?: number; /** 总页数 */ totalPages?: number; - /** 分页后的数据 */ - after?: string; - /** 分页前的数据 */ - before?: string; }; type UnifiedSearchParamsDTO = { @@ -2687,10 +2805,6 @@ declare namespace API { search?: string; /** 过滤条件对象 */ where?: Record; - /** 创建时间后 */ - after?: string; - /** 创建时间前 */ - before?: string; /** 排序对象,例如 { "sku": "desc" } */ orderBy?: Record; }; @@ -2748,10 +2862,6 @@ declare namespace API { per_page?: number; /** 总页数 */ totalPages?: number; - /** 分页后的数据 */ - after?: string; - /** 分页前的数据 */ - before?: string; }; type UnifiedTagDTO = { @@ -2789,6 +2899,57 @@ declare namespace API { code?: string; }; + type UpdateCategoryDTO = { + /** 分类显示名称 */ + title?: string; + /** 分类中文名称 */ + titleCN?: string; + /** 分类唯一标识 */ + name?: string; + /** 分类短名称,用于生成SKU */ + shortName?: string; + /** 排序 */ + sort?: number; + }; + + type UpdateCustomerDTO = { + /** 站点ID */ + site_id?: number; + /** 原始ID */ + origin_id?: number; + /** 邮箱 */ + email?: string; + /** 名字 */ + first_name?: string; + /** 姓氏 */ + last_name?: string; + /** 全名 */ + fullname?: string; + /** 用户名 */ + username?: string; + /** 电话 */ + phone?: string; + /** 头像URL */ + avatar?: string; + /** 账单信息 */ + billing?: Record; + /** 配送信息 */ + shipping?: Record; + /** 原始数据 */ + raw?: Record; + /** 评分 */ + rate?: number; + /** 标签列表 */ + tags?: string[]; + }; + + type UpdateCustomerItemDTO = { + /** 客户ID */ + id?: number; + /** 更新字段 */ + update_data?: UpdateCustomerDTO; + }; + type UpdateDictDTO = {}; type UpdateDictItemDTO = {}; diff --git a/src/utils/showResult.ts b/src/utils/showResult.ts new file mode 100644 index 0000000..ac07bee --- /dev/null +++ b/src/utils/showResult.ts @@ -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; +}