forked from yoone/WEB
feat(产品属性): 重构属性管理逻辑,使用排除列表替代包含列表
refactor(订单管理): 添加发货和取消发货功能 feat(店铺管理): 新增链接管理和Webhooks管理页面 fix(评论管理): 增强评论列表的数据处理和错误处理 feat(模板管理): 实现模板实时预览功能并优化编辑界面 style(布局): 优化店铺管理侧边栏样式和结构 perf(产品同步): 改进SKU生成逻辑和错误处理 docs(API): 更新API类型定义和注释
This commit is contained in:
parent
dfcc8c80e0
commit
09093ad48b
12
.umirc.ts
12
.umirc.ts
|
|
@ -100,6 +100,18 @@ export default defineConfig({
|
||||||
path: '/site/shop/:siteId/customers',
|
path: '/site/shop/:siteId/customers',
|
||||||
component: './Site/Shop/Customers',
|
component: './Site/Shop/Customers',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/site/shop/:siteId/reviews',
|
||||||
|
component: './Site/Shop/Reviews',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/site/shop/:siteId/webhooks',
|
||||||
|
component: './Site/Shop/Webhooks',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/site/shop/:siteId/links',
|
||||||
|
component: './Site/Shop/Links',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import {
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { attributes } from '../Attribute/consts';
|
import { notAttributes } from '../Product/Attribute/consts';
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ const CategoryPage: React.FC = () => {
|
||||||
// Fetch all dicts and filter those that are allowed attributes
|
// Fetch all dicts and filter those that are allowed attributes
|
||||||
try {
|
try {
|
||||||
const res = await request('/dict/list');
|
const res = await request('/dict/list');
|
||||||
const filtered = (res || []).filter((d: any) => attributes.has(d.name));
|
const filtered = (res || []).filter((d: any) => !notAttributes.has(d.name));
|
||||||
// Filter out already added attributes
|
// Filter out already added attributes
|
||||||
const existingDictIds = new Set(
|
const existingDictIds = new Set(
|
||||||
categoryAttributes.map((ca: any) => ca.dict.id),
|
categoryAttributes.map((ca: any) => ca.dict.id),
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import {
|
||||||
PageContainer,
|
PageContainer,
|
||||||
ProTable,
|
ProTable,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { request } from '@umijs/max';
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Form,
|
Form,
|
||||||
|
|
@ -17,6 +16,7 @@ import {
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import * as dictApi from '@/servers/api/dict';
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
|
|
@ -26,14 +26,20 @@ const DictPage: React.FC = () => {
|
||||||
const [loadingDicts, setLoadingDicts] = useState(false);
|
const [loadingDicts, setLoadingDicts] = useState(false);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [selectedDict, setSelectedDict] = useState<any>(null);
|
const [selectedDict, setSelectedDict] = useState<any>(null);
|
||||||
|
|
||||||
|
// 添加字典 modal 状态
|
||||||
const [isAddDictModalVisible, setIsAddDictModalVisible] = useState(false);
|
const [isAddDictModalVisible, setIsAddDictModalVisible] = useState(false);
|
||||||
const [editingDict, setEditingDict] = useState<any>(null);
|
const [addDictName, setAddDictName] = useState('');
|
||||||
const [newDictName, setNewDictName] = useState('');
|
const [addDictTitle, setAddDictTitle] = useState('');
|
||||||
const [newDictTitle, setNewDictTitle] = useState('');
|
|
||||||
|
// 编辑字典 modal 状态
|
||||||
|
const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false);
|
||||||
|
const [editDictData, setEditDictData] = useState<any>(null);
|
||||||
|
|
||||||
// 右侧字典项列表的状态
|
// 右侧字典项列表的状态
|
||||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
const [isAddDictItemModalVisible, setIsAddDictItemModalVisible] = useState(false);
|
||||||
const [editingDictItem, setEditingDictItem] = useState<any>(null);
|
const [isEditDictItemModalVisible, setIsEditDictItemModalVisible] = useState(false);
|
||||||
|
const [editDictItemData, setEditDictItemData] = useState<any>(null);
|
||||||
const [dictItemForm] = Form.useForm();
|
const [dictItemForm] = Form.useForm();
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
|
|
@ -41,7 +47,7 @@ const DictPage: React.FC = () => {
|
||||||
const fetchDicts = async (name = '') => {
|
const fetchDicts = async (name = '') => {
|
||||||
setLoadingDicts(true);
|
setLoadingDicts(true);
|
||||||
try {
|
try {
|
||||||
const res = await request('/dict/list', { params: { name } });
|
const res = await dictApi.dictcontrollerGetdicts({ name });
|
||||||
setDicts(res);
|
setDicts(res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取字典列表失败');
|
message.error('获取字典列表失败');
|
||||||
|
|
@ -55,60 +61,66 @@ const DictPage: React.FC = () => {
|
||||||
fetchDicts(value);
|
fetchDicts(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加或编辑字典
|
// 添加字典
|
||||||
const handleAddDict = async () => {
|
const handleAddDict = async () => {
|
||||||
const values = { name: newDictName, title: newDictTitle };
|
const values = { name: addDictName, title: addDictTitle };
|
||||||
try {
|
try {
|
||||||
if (editingDict) {
|
await dictApi.dictcontrollerCreatedict(values);
|
||||||
await request(`/dict/${editingDict.id}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
data: values,
|
|
||||||
});
|
|
||||||
message.success('更新成功');
|
|
||||||
} else {
|
|
||||||
await request('/dict', { method: 'POST', data: values });
|
|
||||||
message.success('添加成功');
|
message.success('添加成功');
|
||||||
}
|
|
||||||
setIsAddDictModalVisible(false);
|
setIsAddDictModalVisible(false);
|
||||||
setEditingDict(null);
|
setAddDictName('');
|
||||||
setNewDictName('');
|
setAddDictTitle('');
|
||||||
setNewDictTitle('');
|
|
||||||
fetchDicts(); // 重新获取列表
|
fetchDicts(); // 重新获取列表
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(editingDict ? '更新失败' : '添加失败');
|
message.error('添加失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑字典
|
||||||
|
const handleEditDict = async () => {
|
||||||
|
if (!editDictData) return;
|
||||||
|
const values = { name: editDictData.name, title: editDictData.title };
|
||||||
|
try {
|
||||||
|
await dictApi.dictcontrollerUpdatedict({ id: editDictData.id }, values);
|
||||||
|
message.success('更新成功');
|
||||||
|
setIsEditDictModalVisible(false);
|
||||||
|
setEditDictData(null);
|
||||||
|
fetchDicts(); // 重新获取列表
|
||||||
|
} catch (error) {
|
||||||
|
message.error('更新失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除字典
|
// 删除字典
|
||||||
const handleDeleteDict = async (id: number) => {
|
const handleDeleteDict = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
await request(`/dict/${id}`, { method: 'DELETE' });
|
const result = await dictApi.dictcontrollerDeletedict({ id });
|
||||||
|
if(!result.success){
|
||||||
|
throw new Error(result.message || '删除失败')
|
||||||
|
}
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
fetchDicts();
|
fetchDicts();
|
||||||
if (selectedDict?.id === id) {
|
if (selectedDict?.id === id) {
|
||||||
setSelectedDict(null);
|
setSelectedDict(null);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error:any) {
|
||||||
message.error('删除失败');
|
message.error(`删除失败,原因为:${error.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 编辑字典
|
// 打开编辑字典 modal
|
||||||
const handleEditDict = (record: any) => {
|
const openEditDictModal = (record: any) => {
|
||||||
setEditingDict(record);
|
setEditDictData(record);
|
||||||
setNewDictName(record.name);
|
setIsEditDictModalVisible(true);
|
||||||
setNewDictTitle(record.title);
|
|
||||||
setIsAddDictModalVisible(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 下载字典导入模板
|
// 下载字典导入模板
|
||||||
const handleDownloadDictTemplate = async () => {
|
const handleDownloadDictTemplate = async () => {
|
||||||
try {
|
try {
|
||||||
// 使用 request 函数获取带认证的文件数据
|
// 使用 dictApi.dictcontrollerDownloaddicttemplate 获取字典模板
|
||||||
const response = await request('/dict/template', {
|
const response = await dictApi.dictcontrollerDownloaddicttemplate({
|
||||||
method: 'GET',
|
responseType: 'blob',
|
||||||
responseType: 'blob', // 指定响应类型为 blob
|
skipErrorHandler: true,
|
||||||
skipErrorHandler: true, // 跳过默认错误处理,自己处理错误
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建 blob 对象和下载链接
|
// 创建 blob 对象和下载链接
|
||||||
|
|
@ -130,46 +142,79 @@ const DictPage: React.FC = () => {
|
||||||
|
|
||||||
// 添加字典项
|
// 添加字典项
|
||||||
const handleAddDictItem = () => {
|
const handleAddDictItem = () => {
|
||||||
setEditingDictItem(null);
|
|
||||||
dictItemForm.resetFields();
|
dictItemForm.resetFields();
|
||||||
setIsDictItemModalVisible(true);
|
setIsAddDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 编辑字典项
|
// 编辑字典项
|
||||||
const handleEditDictItem = (record: any) => {
|
const handleEditDictItem = (record: any) => {
|
||||||
setEditingDictItem(record);
|
setEditDictItemData(record);
|
||||||
dictItemForm.setFieldsValue(record);
|
dictItemForm.setFieldsValue(record);
|
||||||
setIsDictItemModalVisible(true);
|
setIsEditDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除字典项
|
// 删除字典项
|
||||||
const handleDeleteDictItem = async (id: number) => {
|
const handleDeleteDictItem = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
await request(`/dict/item/${id}`, { method: 'DELETE' });
|
const result = await dictApi.dictcontrollerDeletedictitem({ id });
|
||||||
|
if(!result.success){
|
||||||
|
throw new Error(result.message || '删除失败')
|
||||||
|
}
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
|
|
||||||
|
// 强制刷新字典项列表
|
||||||
|
setTimeout(() => {
|
||||||
actionRef.current?.reload();
|
actionRef.current?.reload();
|
||||||
} catch (error) {
|
}, 100);
|
||||||
message.error('删除失败');
|
} catch (error:any) {
|
||||||
|
message.error(`删除失败,原因为:${error.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 字典项表单提交
|
// 添加字典项表单提交
|
||||||
const handleDictItemFormSubmit = async (values: any) => {
|
const handleAddDictItemFormSubmit = async (values: any) => {
|
||||||
const url = editingDictItem
|
|
||||||
? `/dict/item/${editingDictItem.id}`
|
|
||||||
: '/dict/item';
|
|
||||||
const method = editingDictItem ? 'PUT' : 'POST';
|
|
||||||
const data = editingDictItem
|
|
||||||
? { ...values }
|
|
||||||
: { ...values, dict: { id: selectedDict.id } };
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await request(url, { method, data });
|
const result = await dictApi.dictcontrollerCreatedictitem({
|
||||||
message.success(editingDictItem ? '更新成功' : '添加成功');
|
...values,
|
||||||
setIsDictItemModalVisible(false);
|
dictId: selectedDict.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.message || '添加失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success('添加成功');
|
||||||
|
setIsAddDictItemModalVisible(false);
|
||||||
|
|
||||||
|
// 强制刷新字典项列表
|
||||||
|
setTimeout(() => {
|
||||||
actionRef.current?.reload();
|
actionRef.current?.reload();
|
||||||
} catch (error) {
|
}, 100);
|
||||||
message.error(editingDictItem ? '更新失败' : '添加失败');
|
} 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);
|
||||||
|
|
||||||
|
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 || '未知错误'}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -182,10 +227,7 @@ const DictPage: React.FC = () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取当前字典的所有数据
|
// 获取当前字典的所有数据
|
||||||
const response = await request('/dict/items', {
|
const response = await dictApi.dictcontrollerGetdictitems({ dictId: selectedDict.id });
|
||||||
method: 'GET',
|
|
||||||
params: { dictId: selectedDict.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response || response.length === 0) {
|
if (!response || response.length === 0) {
|
||||||
message.warning('当前字典没有数据可导出');
|
message.warning('当前字典没有数据可导出');
|
||||||
|
|
@ -257,7 +299,7 @@ const DictPage: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => handleEditDict(record)}
|
onClick={() => openEditDictModal(record)}
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -331,7 +373,7 @@ const DictPage: React.FC = () => {
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<Layout style={{ background: '#fff' }}>
|
<Layout style={{ background: '#fff' }}>
|
||||||
<Sider
|
<Sider
|
||||||
width={240}
|
width={300}
|
||||||
style={{
|
style={{
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
|
|
@ -347,6 +389,7 @@ const DictPage: React.FC = () => {
|
||||||
enterButton
|
enterButton
|
||||||
allowClear
|
allowClear
|
||||||
/>
|
/>
|
||||||
|
<Space size="small">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => setIsAddDictModalVisible(true)}
|
onClick={() => setIsAddDictModalVisible(true)}
|
||||||
|
|
@ -354,10 +397,9 @@ const DictPage: React.FC = () => {
|
||||||
>
|
>
|
||||||
添加字典
|
添加字典
|
||||||
</Button>
|
</Button>
|
||||||
<Space size="small">
|
|
||||||
<Upload
|
<Upload
|
||||||
name="file"
|
name="file"
|
||||||
action="/dict/import"
|
action="/api/dict/import"
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
onChange={(info) => {
|
onChange={(info) => {
|
||||||
if (info.file.status === 'done') {
|
if (info.file.status === 'done') {
|
||||||
|
|
@ -401,53 +443,6 @@ const DictPage: React.FC = () => {
|
||||||
</Sider>
|
</Sider>
|
||||||
<Content style={{ padding: '8px' }}>
|
<Content style={{ padding: '8px' }}>
|
||||||
<Space direction="vertical" style={{ width: '100%' }}>
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
gap: '2px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={handleAddDictItem}
|
|
||||||
disabled={!selectedDict}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
添加字典项
|
|
||||||
</Button>
|
|
||||||
<Upload
|
|
||||||
name="file"
|
|
||||||
action={`/dict/item/import`}
|
|
||||||
data={{ dictId: selectedDict?.id }}
|
|
||||||
showUploadList={false}
|
|
||||||
disabled={!selectedDict}
|
|
||||||
onChange={(info) => {
|
|
||||||
if (info.file.status === 'done') {
|
|
||||||
message.success(`${info.file.name} 文件上传成功`);
|
|
||||||
actionRef.current?.reload();
|
|
||||||
} else if (info.file.status === 'error') {
|
|
||||||
message.error(`${info.file.name} 文件上传失败`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
icon={<UploadOutlined />}
|
|
||||||
disabled={!selectedDict}
|
|
||||||
>
|
|
||||||
导入字典项
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
|
||||||
<Button
|
|
||||||
onClick={handleExportDictItems}
|
|
||||||
disabled={!selectedDict}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
导出数据
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<ProTable
|
<ProTable
|
||||||
columns={dictItemColumns}
|
columns={dictItemColumns}
|
||||||
request={async (params) => {
|
request={async (params) => {
|
||||||
|
|
@ -459,15 +454,24 @@ const DictPage: React.FC = () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const { name, title } = params;
|
const { name, title } = params;
|
||||||
const res = await request('/dict/items', {
|
const res = await dictApi.dictcontrollerGetdictitems({
|
||||||
params: {
|
|
||||||
dictId: selectedDict?.id,
|
dictId: selectedDict?.id,
|
||||||
name,
|
name,
|
||||||
title,
|
title,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 适配新的响应格式,检查是否有 successResponse 包裹
|
||||||
|
if (res && res.success !== undefined) {
|
||||||
return {
|
return {
|
||||||
data: res,
|
data: res.data || [],
|
||||||
|
success: res.success,
|
||||||
|
total: res.data?.length || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧的响应格式(直接返回数组)
|
||||||
|
return {
|
||||||
|
data: res || [],
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
|
|
@ -476,25 +480,85 @@ const DictPage: React.FC = () => {
|
||||||
layout: 'vertical',
|
layout: 'vertical',
|
||||||
}}
|
}}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
options={false}
|
options={{
|
||||||
|
reload: true,
|
||||||
|
density: true,
|
||||||
|
setting: true,
|
||||||
|
}}
|
||||||
size="small"
|
size="small"
|
||||||
key={selectedDict?.id}
|
key={selectedDict?.id}
|
||||||
|
toolBarRender={() => [
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleAddDictItem}
|
||||||
|
disabled={!selectedDict}
|
||||||
|
size="small"
|
||||||
|
key="add"
|
||||||
|
>
|
||||||
|
添加字典项
|
||||||
|
</Button>,
|
||||||
|
<Upload
|
||||||
|
name="file"
|
||||||
|
action={undefined}
|
||||||
|
customRequest={async (options) => {
|
||||||
|
const { file, onSuccess, onError } = options;
|
||||||
|
try {
|
||||||
|
const result = await dictApi.dictcontrollerImportdictitems(
|
||||||
|
{ dictId: selectedDict?.id },
|
||||||
|
[file as File]
|
||||||
|
);
|
||||||
|
onSuccess?.(result);
|
||||||
|
} catch (error) {
|
||||||
|
onError?.(error as Error);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
showUploadList={false}
|
||||||
|
disabled={!selectedDict}
|
||||||
|
onChange={(info) => {
|
||||||
|
console.log(`info`,info)
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
message.success(`${info.file.name} 文件上传成功`);
|
||||||
|
// 重新加载字典项列表
|
||||||
|
setTimeout(() => {
|
||||||
|
actionRef.current?.reload();
|
||||||
|
}, 100);
|
||||||
|
// 重新加载字典列表以更新字典项数量
|
||||||
|
fetchDicts();
|
||||||
|
} else if (info.file.status === 'error') {
|
||||||
|
message.error(`${info.file.name} 文件上传失败`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
key="import"
|
||||||
|
>
|
||||||
|
<Button size="small" icon={<UploadOutlined />}>
|
||||||
|
导入字典项
|
||||||
|
</Button>
|
||||||
|
</Upload>,
|
||||||
|
<Button
|
||||||
|
onClick={handleExportDictItems}
|
||||||
|
disabled={!selectedDict}
|
||||||
|
size="small"
|
||||||
|
key="export"
|
||||||
|
>
|
||||||
|
导出数据
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
{/* 添加字典项 Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
title={editingDictItem ? '编辑字典项' : '添加字典项'}
|
title={`添加字典项 - ${selectedDict?.title || '未选择字典'}`}
|
||||||
open={isDictItemModalVisible}
|
open={isAddDictItemModalVisible}
|
||||||
onOk={() => dictItemForm.submit()}
|
onOk={() => dictItemForm.submit()}
|
||||||
onCancel={() => setIsDictItemModalVisible(false)}
|
onCancel={() => setIsAddDictItemModalVisible(false)}
|
||||||
destroyOnClose
|
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
form={dictItemForm}
|
form={dictItemForm}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
onFinish={handleDictItemFormSubmit}
|
onFinish={handleAddDictItemFormSubmit}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="名称"
|
label="名称"
|
||||||
|
|
@ -525,25 +589,102 @@ const DictPage: React.FC = () => {
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 编辑字典项 Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
title={editingDict ? '编辑字典' : '添加新字典'}
|
title="编辑字典项"
|
||||||
visible={isAddDictModalVisible}
|
open={isEditDictItemModalVisible}
|
||||||
|
onOk={() => dictItemForm.submit()}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsEditDictItemModalVisible(false);
|
||||||
|
setEditDictItemData(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={dictItemForm}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={handleEditDictItemFormSubmit}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label="名称"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: '请输入名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="名称 (e.g., zyn)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="标题"
|
||||||
|
name="title"
|
||||||
|
rules={[{ required: true, message: '请输入标题' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="标题 (e.g., ZYN)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="中文标题" name="titleCN">
|
||||||
|
<Input placeholder="中文标题 (e.g., 品牌)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="简称 (可选)" name="shortName">
|
||||||
|
<Input placeholder="简称 (可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="图片 (可选)" name="image">
|
||||||
|
<Input placeholder="图片链接 (可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="值 (可选)" name="value">
|
||||||
|
<Input placeholder="值 (可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* 添加字典 Modal */}
|
||||||
|
<Modal
|
||||||
|
title="添加新字典"
|
||||||
|
open={isAddDictModalVisible}
|
||||||
onOk={handleAddDict}
|
onOk={handleAddDict}
|
||||||
onCancel={() => setIsAddDictModalVisible(false)}
|
onCancel={() => {
|
||||||
|
setIsAddDictModalVisible(false);
|
||||||
|
setAddDictName('');
|
||||||
|
setAddDictTitle('');
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Form layout="vertical">
|
<Form layout="vertical">
|
||||||
<Form.Item label="字典名称">
|
<Form.Item label="字典名称">
|
||||||
<Input
|
<Input
|
||||||
placeholder="字典名称 (e.g., brand)"
|
placeholder="字典名称 (e.g., brand)"
|
||||||
value={newDictName}
|
value={addDictName}
|
||||||
onChange={(e) => setNewDictName(e.target.value)}
|
onChange={(e) => setAddDictName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="字典标题">
|
<Form.Item label="字典标题">
|
||||||
<Input
|
<Input
|
||||||
placeholder="字典标题 (e.g., 品牌)"
|
placeholder="字典标题 (e.g., 品牌)"
|
||||||
value={newDictTitle}
|
value={addDictTitle}
|
||||||
onChange={(e) => setNewDictTitle(e.target.value)}
|
onChange={(e) => setAddDictTitle(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* 编辑字典 Modal */}
|
||||||
|
<Modal
|
||||||
|
title="编辑字典"
|
||||||
|
open={isEditDictModalVisible}
|
||||||
|
onOk={handleEditDict}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsEditDictModalVisible(false);
|
||||||
|
setEditDictData(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form layout="vertical">
|
||||||
|
<Form.Item label="字典名称">
|
||||||
|
<Input
|
||||||
|
placeholder="字典名称 (e.g., brand)"
|
||||||
|
value={editDictData?.name || ''}
|
||||||
|
onChange={(e) => setEditDictData({ ...editDictData, name: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="字典标题">
|
||||||
|
<Input
|
||||||
|
placeholder="字典标题 (e.g., 品牌)"
|
||||||
|
value={editDictData?.title || ''}
|
||||||
|
onChange={(e) => setEditDictData({ ...editDictData, title: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
// 限定允许管理的字典名称集合
|
export const notAttributes = new Set([
|
||||||
export const attributes = new Set([
|
'zh-cn',
|
||||||
'brand',
|
'en-us',
|
||||||
'strength',
|
'category'
|
||||||
'flavor',
|
|
||||||
'size',
|
|
||||||
'humidity',
|
|
||||||
]);
|
]);
|
||||||
|
|
@ -20,7 +20,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
import { attributes } from './consts';
|
import { notAttributes } from './consts';
|
||||||
|
|
||||||
const AttributePage: React.FC = () => {
|
const AttributePage: React.FC = () => {
|
||||||
// 左侧字典列表状态
|
// 左侧字典列表状态
|
||||||
|
|
@ -42,7 +42,7 @@ const AttributePage: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
const res = await request('/dict/list', { params: { title } });
|
const res = await request('/dict/list', { params: { title } });
|
||||||
// 条件判断,过滤只保留 allowedDictNames 中的字典
|
// 条件判断,过滤只保留 allowedDictNames 中的字典
|
||||||
const filtered = (res || []).filter((d: any) => attributes.has(d?.name));
|
const filtered = (res || []).filter((d: any) => !notAttributes.has(d?.name));
|
||||||
setDicts(filtered);
|
setDicts(filtered);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取字典列表失败');
|
message.error('获取字典列表失败');
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import {
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { attributes } from '../Attribute/consts';
|
import { notAttributes } from '../Attribute/consts';
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
|
|
@ -116,7 +116,7 @@ const CategoryPage: React.FC = () => {
|
||||||
const res = await request('/dict/list');
|
const res = await request('/dict/list');
|
||||||
// Defensive check for response structure: handle both raw array and wrapped response
|
// Defensive check for response structure: handle both raw array and wrapped response
|
||||||
const list = Array.isArray(res) ? res : res?.data || [];
|
const list = Array.isArray(res) ? res : res?.data || [];
|
||||||
const filtered = list.filter((d: any) => attributes.has(d.name));
|
const filtered = list.filter((d: any) => !notAttributes.has(d.name));
|
||||||
// Filter out already added attributes
|
// Filter out already added attributes
|
||||||
const existingDictIds = new Set(
|
const existingDictIds = new Set(
|
||||||
categoryAttributes.map((ca: any) => ca.dictId),
|
categoryAttributes.map((ca: any) => ca.dictId),
|
||||||
|
|
@ -244,7 +244,7 @@ const CategoryPage: React.FC = () => {
|
||||||
</Popconfirm>,
|
</Popconfirm>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<List.Item.Meta title={item.title} description={item.name} />
|
<List.Item.Meta title={`${item.title}(${item.titleCN??'-'})`} description={item.name} />
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -310,16 +310,18 @@ const CategoryPage: React.FC = () => {
|
||||||
onFinish={handleCategorySubmit}
|
onFinish={handleCategorySubmit}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
>
|
>
|
||||||
<Form.Item name="title" label="标题" rules={[{ required: true }]}>
|
<Form.Item name="title" label="标题">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item name="titleCN" label="中文名称">
|
||||||
name="name"
|
|
||||||
label="标识 (Code)"
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
>
|
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item name="name" label="标识 (Code)">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="sort" label="排序">
|
||||||
|
<Input type="number" />
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -462,9 +462,9 @@ const List: React.FC = () => {
|
||||||
dataIndex: 'siteSkus',
|
dataIndex: 'siteSkus',
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<>
|
<>
|
||||||
{record.siteSkus?.map((code, index) => (
|
{record.siteSkus?.map((siteSku, index) => (
|
||||||
<Tag key={index} color="cyan">
|
<Tag key={index} color="cyan">
|
||||||
{code}
|
{siteSku.siteSku}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|
@ -609,55 +609,6 @@ const List: React.FC = () => {
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
// 新建按钮
|
// 新建按钮
|
||||||
<CreateForm tableRef={actionRef} />,
|
<CreateForm tableRef={actionRef} />,
|
||||||
// 批量编辑按钮
|
|
||||||
<Button
|
|
||||||
disabled={selectedRows.length <= 0}
|
|
||||||
onClick={() => setBatchEditModalVisible(true)}
|
|
||||||
>
|
|
||||||
批量修改
|
|
||||||
</Button>,
|
|
||||||
// 批量同步按钮
|
|
||||||
<Button
|
|
||||||
disabled={selectedRows.length <= 0}
|
|
||||||
onClick={() => {
|
|
||||||
setSyncProductIds(selectedRows.map((row) => row.id));
|
|
||||||
setSyncModalVisible(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
批量同步到站点
|
|
||||||
</Button>,
|
|
||||||
// 批量删除按钮
|
|
||||||
<Button
|
|
||||||
danger
|
|
||||||
disabled={selectedRows.length <= 0}
|
|
||||||
onClick={() => {
|
|
||||||
Modal.confirm({
|
|
||||||
title: '确认删除',
|
|
||||||
content: `确定要删除选中的 ${selectedRows.length} 个产品吗?此操作不可恢复。`,
|
|
||||||
onOk: async () => {
|
|
||||||
try {
|
|
||||||
const { success, message: errMsg } =
|
|
||||||
await productcontrollerBatchdeleteproduct({
|
|
||||||
ids: selectedRows.map((row) => row.id),
|
|
||||||
});
|
|
||||||
if (success) {
|
|
||||||
message.success('批量删除成功');
|
|
||||||
setSelectedRows([]);
|
|
||||||
actionRef.current?.reload();
|
|
||||||
} else {
|
|
||||||
message.error(errMsg || '删除失败');
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error.message || '删除失败');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
批量删除
|
|
||||||
</Button>,
|
|
||||||
// 导出 CSV(后端返回 text/csv,直接新窗口下载)
|
|
||||||
<Button onClick={handleDownloadProductsCSV}>导出CSV</Button>,
|
|
||||||
// 导入 CSV(使用 customRequest 以支持 request 拦截器和鉴权)
|
// 导入 CSV(使用 customRequest 以支持 request 拦截器和鉴权)
|
||||||
<Upload
|
<Upload
|
||||||
name="file"
|
name="file"
|
||||||
|
|
@ -733,8 +684,57 @@ const List: React.FC = () => {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button>导入CSV</Button>
|
<Button>批量导入</Button>
|
||||||
</Upload>,
|
</Upload>,
|
||||||
|
// 批量编辑按钮
|
||||||
|
<Button
|
||||||
|
disabled={selectedRows.length <= 0}
|
||||||
|
onClick={() => setBatchEditModalVisible(true)}
|
||||||
|
>
|
||||||
|
批量修改
|
||||||
|
</Button>,
|
||||||
|
// 批量同步按钮
|
||||||
|
<Button
|
||||||
|
disabled={selectedRows.length <= 0}
|
||||||
|
onClick={() => {
|
||||||
|
setSyncProductIds(selectedRows.map((row) => row.id));
|
||||||
|
setSyncModalVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
批量同步到站点
|
||||||
|
</Button>,
|
||||||
|
// 批量删除按钮
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
disabled={selectedRows.length <= 0}
|
||||||
|
onClick={() => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: `确定要删除选中的 ${selectedRows.length} 个产品吗?此操作不可恢复。`,
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await productcontrollerBatchdeleteproduct({
|
||||||
|
ids: selectedRows.map((row) => row.id),
|
||||||
|
});
|
||||||
|
if (success) {
|
||||||
|
message.success('批量删除成功');
|
||||||
|
setSelectedRows([]);
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} else {
|
||||||
|
message.error(errMsg || '删除失败');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message || '删除失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
批量删除
|
||||||
|
</Button>,
|
||||||
|
// 导出 CSV(后端返回 text/csv,直接新窗口下载)
|
||||||
|
<Button onClick={handleDownloadProductsCSV}>导出CSV</Button>,
|
||||||
]}
|
]}
|
||||||
request={async (params, sort) => {
|
request={async (params, sort) => {
|
||||||
let sortField = undefined;
|
let sortField = undefined;
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ const PermutationPage: React.FC = () => {
|
||||||
const valB = b[attr.name]?.name || '';
|
const valB = b[attr.name]?.name || '';
|
||||||
return valA.localeCompare(valB);
|
return valA.localeCompare(valB);
|
||||||
},
|
},
|
||||||
filters: attributeValues[attr.name]?.map((v: any) => ({
|
filters: attributeValues?.[attr.name]?.map?.((v: any) => ({
|
||||||
text: v.name,
|
text: v.name,
|
||||||
value: v.name,
|
value: v.name,
|
||||||
})),
|
})),
|
||||||
|
|
|
||||||
|
|
@ -249,7 +249,7 @@ const ProductSyncPage: React.FC = () => {
|
||||||
// 如果没有找到实际的siteSku,则根据模板生成
|
// 如果没有找到实际的siteSku,则根据模板生成
|
||||||
const expectedSku = siteProductSku || (
|
const expectedSku = siteProductSku || (
|
||||||
skuTemplate
|
skuTemplate
|
||||||
? renderSku(skuTemplate, { site: targetSite, product })
|
? renderSiteSku(skuTemplate, { site: targetSite, product })
|
||||||
: `${targetSite.skuPrefix || ''}-${product.sku}`
|
: `${targetSite.skuPrefix || ''}-${product.sku}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -329,7 +329,7 @@ const ProductSyncPage: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 简单的模板渲染函数
|
// 简单的模板渲染函数
|
||||||
const renderSku = (template: string, data: any) => {
|
const renderSiteSku = (template: string, data: any) => {
|
||||||
if (!template) return '';
|
if (!template) return '';
|
||||||
// 支持 <%= it.path %> (Eta) 和 {{ path }} (Mustache/Handlebars)
|
// 支持 <%= it.path %> (Eta) 和 {{ path }} (Mustache/Handlebars)
|
||||||
return template.replace(
|
return template.replace(
|
||||||
|
|
@ -463,7 +463,7 @@ const ProductSyncPage: React.FC = () => {
|
||||||
// 如果没有找到实际的siteSku,则根据模板或默认规则生成期望的SKU
|
// 如果没有找到实际的siteSku,则根据模板或默认规则生成期望的SKU
|
||||||
const expectedSku = siteProductSku || (
|
const expectedSku = siteProductSku || (
|
||||||
skuTemplate
|
skuTemplate
|
||||||
? renderSku(skuTemplate, { site, product: record })
|
? renderSiteSku(skuTemplate, { site, product: record })
|
||||||
: `${site.skuPrefix || ''}-${record.sku}`
|
: `${site.skuPrefix || ''}-${record.sku}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -472,7 +472,7 @@ const ProductSyncPage: React.FC = () => {
|
||||||
|
|
||||||
// 如果根据实际SKU没找到,再尝试用模板生成的SKU查找
|
// 如果根据实际SKU没找到,再尝试用模板生成的SKU查找
|
||||||
if (!wpProduct && siteProductSku && skuTemplate) {
|
if (!wpProduct && siteProductSku && skuTemplate) {
|
||||||
const templateSku = renderSku(skuTemplate, { site, product: record });
|
const templateSku = renderSiteSku(skuTemplate, { site, product: record });
|
||||||
wpProduct = wpProductMap.get(templateSku);
|
wpProduct = wpProductMap.get(templateSku);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -492,7 +492,7 @@ const ProductSyncPage: React.FC = () => {
|
||||||
initialValues={{
|
initialValues={{
|
||||||
sku: siteProductSku || (
|
sku: siteProductSku || (
|
||||||
skuTemplate
|
skuTemplate
|
||||||
? renderSku(skuTemplate, { site, product: record })
|
? renderSiteSku(skuTemplate, { site, product: record })
|
||||||
: `${site.skuPrefix || ''}-${record.sku}`
|
: `${site.skuPrefix || ''}-${record.sku}`
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -203,11 +203,17 @@ const CustomerPage: React.FC = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '注册时间',
|
title: '创建时间',
|
||||||
dataIndex: 'date_created',
|
dataIndex: 'date_created',
|
||||||
valueType: 'dateTime',
|
valueType: 'dateTime',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '更新时间',
|
||||||
|
dataIndex: 'date_modified',
|
||||||
|
valueType: 'dateTime',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
valueType: 'option',
|
valueType: 'option',
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { Button, Card, Col, Menu, Row, Select, Spin, message } from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import type { SiteItem } from '../List/index';
|
import type { SiteItem } from '../List/index';
|
||||||
import EditSiteForm from './EditSiteForm';
|
import EditSiteForm from './EditSiteForm';
|
||||||
|
import Sider from 'antd/es/layout/Sider';
|
||||||
|
|
||||||
const ShopLayout: React.FC = () => {
|
const ShopLayout: React.FC = () => {
|
||||||
const [sites, setSites] = useState<any[]>([]);
|
const [sites, setSites] = useState<any[]>([]);
|
||||||
|
|
@ -92,20 +93,12 @@ const ShopLayout: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
header={{ title: null, breadcrumb: undefined }}
|
header={{ title: null, breadcrumb: undefined }}
|
||||||
contentStyle={{
|
|
||||||
padding: 0,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
|
||||||
<Row gutter={16} style={{ height: 'calc(100vh - 100px)' }}>
|
<Row gutter={16} style={{ height: 'calc(100vh - 100px)' }}>
|
||||||
<Col span={4} style={{ height: '100%' }}>
|
<Col span={4} style={{ height: '100%' }}>
|
||||||
<Card
|
<Sider
|
||||||
bodyStyle={{
|
style={{ background: 'white', height: '100%', overflow: 'hidden' }}
|
||||||
padding: '10px 0',
|
|
||||||
height: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
}}
|
|
||||||
style={{ height: '100%', overflow: 'hidden' }}
|
|
||||||
>
|
>
|
||||||
<div style={{ padding: '0 10px 16px' }}>
|
<div style={{ padding: '0 10px 16px' }}>
|
||||||
<div
|
<div
|
||||||
|
|
@ -157,10 +150,12 @@ const ShopLayout: React.FC = () => {
|
||||||
{ key: 'media', label: '媒体管理' },
|
{ key: 'media', label: '媒体管理' },
|
||||||
{ key: 'customers', label: '客户管理' },
|
{ key: 'customers', label: '客户管理' },
|
||||||
{ key: 'reviews', label: '评论管理' },
|
{ key: 'reviews', label: '评论管理' },
|
||||||
|
{ key: 'webhooks', label: 'Webhooks管理' },
|
||||||
|
{ key: 'links', label: '链接管理' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Sider>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={20} style={{ height: '100%', overflowY: 'auto' }}>
|
<Col span={20} style={{ height: '100%', overflowY: 'auto' }}>
|
||||||
{siteId ? <Outlet /> : <div>请选择店铺</div>}
|
{siteId ? <Outlet /> : <div>请选择店铺</div>}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { Button, Card, List } from 'antd';
|
||||||
|
import { request, useParams } from '@umijs/max';
|
||||||
|
import { App } from 'antd';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { LinkOutlined } from '@ant-design/icons';
|
||||||
|
import { PageHeader } from '@ant-design/pro-layout';
|
||||||
|
|
||||||
|
// 定义链接项的类型
|
||||||
|
interface LinkItem {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LinksPage: React.FC = () => {
|
||||||
|
const { siteId } = useParams<{ siteId: string }>();
|
||||||
|
const { message: antMessage } = App.useApp();
|
||||||
|
const [links, setLinks] = useState<LinkItem[]>([]);
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
// 获取链接列表的函数
|
||||||
|
const fetchLinks = async () => {
|
||||||
|
if (!siteId) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await request(`/site-api/${siteId}/links`);
|
||||||
|
if (response.success && response.data) {
|
||||||
|
setLinks(response.data);
|
||||||
|
} else {
|
||||||
|
antMessage.error(response.message || '获取链接列表失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
antMessage.error('获取链接列表失败');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时获取链接列表
|
||||||
|
useEffect(() => {
|
||||||
|
fetchLinks();
|
||||||
|
}, [siteId]);
|
||||||
|
|
||||||
|
// 处理链接点击事件,在新标签页打开
|
||||||
|
const handleLinkClick = (url: string) => {
|
||||||
|
window.open(url, '_blank', 'noopener,noreferrer');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
title="站点链接"
|
||||||
|
breadcrumb={{ items: [] }}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
title="常用链接"
|
||||||
|
bordered={false}
|
||||||
|
extra={
|
||||||
|
<Button type="primary" onClick={fetchLinks} loading={loading}>
|
||||||
|
刷新列表
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
loading={loading}
|
||||||
|
dataSource={links}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<List.Item
|
||||||
|
key={item.title}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
key={`visit-${item.title}`}
|
||||||
|
type="link"
|
||||||
|
icon={<LinkOutlined />}
|
||||||
|
onClick={() => handleLinkClick(item.url)}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
访问
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<List.Item.Meta
|
||||||
|
title={item.title}
|
||||||
|
description={
|
||||||
|
<a
|
||||||
|
href={item.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{ color: '#1890ff' }}
|
||||||
|
>
|
||||||
|
{item.url}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LinksPage;
|
||||||
|
|
@ -186,6 +186,52 @@ const OrdersPage: React.FC = () => {
|
||||||
>
|
>
|
||||||
<Button type="text" icon={<EllipsisOutlined />} />
|
<Button type="text" icon={<EllipsisOutlined />} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
title="发货"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const res = await request(
|
||||||
|
`/site-api/${siteId}/orders/${record.id}/ship`,
|
||||||
|
{ method: 'POST' },
|
||||||
|
);
|
||||||
|
if (res.success) {
|
||||||
|
message.success('发货成功');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '发货失败');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
message.error('发货失败');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
发货
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定取消发货?"
|
||||||
|
description="取消发货后订单状态将恢复为处理中"
|
||||||
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
|
const res = await request(
|
||||||
|
`/site-api/${siteId}/orders/${record.id}/cancel-ship`,
|
||||||
|
{ method: 'POST' },
|
||||||
|
);
|
||||||
|
if (res.success) {
|
||||||
|
message.success('取消发货成功');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '取消发货失败');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
message.error('取消发货失败');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="link" danger title="取消发货">
|
||||||
|
取消发货
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="确定删除订单?"
|
title="确定删除订单?"
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,19 @@
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Select,
|
||||||
|
message,
|
||||||
|
} from 'antd';
|
||||||
|
import {
|
||||||
|
siteapicontrollerCreatereview,
|
||||||
|
siteapicontrollerUpdatereview,
|
||||||
|
} from '@/servers/api/siteApi';
|
||||||
|
|
||||||
|
const { TextArea } = Input;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
interface ReviewFormProps {
|
interface ReviewFormProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
@ -15,19 +30,161 @@ const ReviewForm: React.FC<ReviewFormProps> = ({
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}) => {
|
}) => {
|
||||||
// // 这是一个临时的占位符组件
|
const [form] = Form.useForm();
|
||||||
// // 你可以在这里实现表单逻辑
|
|
||||||
if (!open) {
|
// 当编辑状态改变时,重置表单数据
|
||||||
return null;
|
useEffect(() => {
|
||||||
|
if (editing) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
product_id: editing.product_id,
|
||||||
|
author: editing.author,
|
||||||
|
email: editing.email,
|
||||||
|
content: editing.content,
|
||||||
|
rating: editing.rating,
|
||||||
|
status: editing.status,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}, [editing, form]);
|
||||||
|
|
||||||
|
// 处理表单提交
|
||||||
|
const handleSubmit = async (values: any) => {
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
// 更新评论
|
||||||
|
response = await siteapicontrollerUpdatereview(
|
||||||
|
{
|
||||||
|
siteId,
|
||||||
|
id: editing.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
review: values.content,
|
||||||
|
rating: values.rating,
|
||||||
|
status: values.status,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 创建新评论
|
||||||
|
response = await siteapicontrollerCreatereview(
|
||||||
|
{
|
||||||
|
siteId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
product_id: values.product_id,
|
||||||
|
review: values.content,
|
||||||
|
rating: values.rating,
|
||||||
|
author: values.author,
|
||||||
|
author_email: values.email,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
message.success(editing ? '更新成功' : '创建成功');
|
||||||
|
onSuccess();
|
||||||
|
onClose();
|
||||||
|
form.resetFields();
|
||||||
|
} else {
|
||||||
|
message.error(response.message || '操作失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交评论表单失败:', error);
|
||||||
|
message.error('提交失败,请重试');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Modal
|
||||||
<h2>Review Form</h2>
|
title={editing ? '编辑评论' : '新建评论'}
|
||||||
<p>Site ID: {siteId}</p>
|
open={open}
|
||||||
<p>Editing: {editing ? 'Yes' : 'No'}</p>
|
onCancel={onClose}
|
||||||
<button onClick={onClose}>Close</button>
|
onOk={() => form.submit()}
|
||||||
</div>
|
okText="保存"
|
||||||
|
cancelText="取消"
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={handleSubmit}
|
||||||
|
initialValues={{
|
||||||
|
status: 'approved',
|
||||||
|
rating: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!editing && (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name="product_id"
|
||||||
|
label="产品ID"
|
||||||
|
rules={[{ required: true, message: '请输入产品ID' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入产品ID" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="author"
|
||||||
|
label="评论者"
|
||||||
|
rules={[{ required: true, message: '请输入评论者姓名' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入评论者姓名" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="email"
|
||||||
|
label="邮箱"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入邮箱' },
|
||||||
|
{ type: 'email', message: '请输入有效的邮箱地址' }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入邮箱" />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="content"
|
||||||
|
label="评论内容"
|
||||||
|
rules={[{ required: true, message: '请输入评论内容' }]}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
rows={4}
|
||||||
|
placeholder="请输入评论内容"
|
||||||
|
maxLength={1000}
|
||||||
|
showCount
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="rating"
|
||||||
|
label="评分"
|
||||||
|
rules={[{ required: true, message: '请选择评分' }]}
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
max={5}
|
||||||
|
precision={0}
|
||||||
|
placeholder="评分 (1-5)"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="status"
|
||||||
|
label="状态"
|
||||||
|
rules={[{ required: true, message: '请选择状态' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="请选择状态">
|
||||||
|
<Option value="approved">已批准</Option>
|
||||||
|
<Option value="pending">待审核</Option>
|
||||||
|
<Option value="spam">垃圾评论</Option>
|
||||||
|
<Option value="trash">回收站</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,17 +85,44 @@ const ReviewsPage: React.FC = () => {
|
||||||
columns={columns}
|
columns={columns}
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
request={async (params) => {
|
request={async (params) => {
|
||||||
|
try {
|
||||||
const response = await siteapicontrollerGetreviews({
|
const response = await siteapicontrollerGetreviews({
|
||||||
...params,
|
...params,
|
||||||
siteId,
|
siteId,
|
||||||
page: params.current,
|
page: params.current,
|
||||||
per_page: params.pageSize,
|
per_page: params.pageSize,
|
||||||
});
|
});
|
||||||
|
// 确保 response.data 存在
|
||||||
|
if (!response || !response.data) {
|
||||||
return {
|
return {
|
||||||
data: response.data.items,
|
data: [],
|
||||||
success: true,
|
success: true,
|
||||||
total: response.data.total,
|
total: 0,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
// 确保 response.data.items 是数组
|
||||||
|
const items = Array.isArray(response.data.items) ? response.data.items : [];
|
||||||
|
// 确保每个 item 有有效的 id
|
||||||
|
const processedItems = items.map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
// 如果 id 是对象,转换为字符串,否则使用索引作为后备
|
||||||
|
id: typeof item.id === 'object' ? JSON.stringify(item.id) : (item.id || index),
|
||||||
|
// 如果 product_id 是对象,转换为字符串
|
||||||
|
product_id: typeof item.product_id === 'object' ? JSON.stringify(item.product_id) : item.product_id,
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
data: processedItems,
|
||||||
|
success: true,
|
||||||
|
total: Number(response.data.total) || 0,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取评论失败:', error);
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
success: true,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
search={{
|
search={{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,297 @@
|
||||||
|
import { siteapicontrollerGetwebhooks, siteapicontrollerDeletewebhook, siteapicontrollerCreatewebhook, siteapicontrollerUpdatewebhook, siteapicontrollerGetwebhook } from '@/servers/api/siteApi';
|
||||||
|
import {
|
||||||
|
ActionType,
|
||||||
|
ProCard,
|
||||||
|
ProColumns,
|
||||||
|
ProTable,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import { useParams } from '@umijs/max';
|
||||||
|
import { Button, message, Popconfirm, Space, Modal, Form, Input, Select } from 'antd';
|
||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
|
const WebhooksPage: React.FC = () => {
|
||||||
|
const params = useParams();
|
||||||
|
const siteId = Number(params.siteId);
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
|
// 模态框状态
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [isEditMode, setIsEditMode] = useState(false);
|
||||||
|
const [currentWebhook, setCurrentWebhook] = useState<API.UnifiedWebhookDTO | null>(null);
|
||||||
|
|
||||||
|
// 表单实例
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
// webhook主题选项
|
||||||
|
const webhookTopics = [
|
||||||
|
{ label: '订单创建', value: 'order.created' },
|
||||||
|
{ label: '订单更新', value: 'order.updated' },
|
||||||
|
{ label: '订单删除', value: 'order.deleted' },
|
||||||
|
{ label: '产品创建', value: 'product.created' },
|
||||||
|
{ label: '产品更新', value: 'product.updated' },
|
||||||
|
{ label: '产品删除', value: 'product.deleted' },
|
||||||
|
{ label: '客户创建', value: 'customer.created' },
|
||||||
|
{ label: '客户更新', value: 'customer.updated' },
|
||||||
|
{ label: '客户删除', value: 'customer.deleted' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// webhook状态选项
|
||||||
|
const webhookStatuses = [
|
||||||
|
{ label: '活跃', value: 'active' },
|
||||||
|
{ label: '非活跃', value: 'inactive' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 打开新建模态框
|
||||||
|
const showCreateModal = () => {
|
||||||
|
setIsEditMode(false);
|
||||||
|
setCurrentWebhook(null);
|
||||||
|
form.resetFields();
|
||||||
|
setIsModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开编辑模态框
|
||||||
|
const showEditModal = async (record: API.UnifiedWebhookDTO) => {
|
||||||
|
setIsEditMode(true);
|
||||||
|
setCurrentWebhook(record);
|
||||||
|
try {
|
||||||
|
// 如果需要获取最新的webhook数据,可以取消下面的注释
|
||||||
|
// const response = await siteapicontrollerGetwebhook({ siteId, id: String(record.id) });
|
||||||
|
// if (response.success && response.data) {
|
||||||
|
// form.setFieldsValue(response.data);
|
||||||
|
// } else {
|
||||||
|
// form.setFieldsValue(record);
|
||||||
|
// }
|
||||||
|
form.setFieldsValue(record);
|
||||||
|
setIsModalVisible(true);
|
||||||
|
} catch (error) {
|
||||||
|
message.error('加载webhook数据失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭模态框
|
||||||
|
const handleCancel = () => {
|
||||||
|
setIsModalVisible(false);
|
||||||
|
form.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
|
||||||
|
// 准备提交数据
|
||||||
|
const webhookData = {
|
||||||
|
...values,
|
||||||
|
siteId,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response;
|
||||||
|
if (isEditMode && currentWebhook?.id) {
|
||||||
|
// 更新webhook
|
||||||
|
response = await siteapicontrollerUpdatewebhook({
|
||||||
|
...webhookData,
|
||||||
|
id: String(currentWebhook.id),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 创建新webhook
|
||||||
|
response = await siteapicontrollerCreatewebhook(webhookData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
message.success(isEditMode ? '更新成功' : '创建成功');
|
||||||
|
setIsModalVisible(false);
|
||||||
|
form.resetFields();
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} else {
|
||||||
|
message.error(isEditMode ? '更新失败' : '创建失败');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error('表单验证失败:' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ProColumns<API.UnifiedWebhookDTO>[] = [
|
||||||
|
{ title: 'ID', dataIndex: 'id', key: 'id', width: 50 },
|
||||||
|
{ title: '名称', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '主题', dataIndex: 'topic', key: 'topic' },
|
||||||
|
{ title: '回调URL', dataIndex: 'delivery_url', key: 'delivery_url', ellipsis: true },
|
||||||
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 80 },
|
||||||
|
{ title: '创建时间', dataIndex: 'date_created', key: 'date_created', valueType: 'dateTime' },
|
||||||
|
{ title: '更新时间', dataIndex: 'date_modified', key: 'date_modified', valueType: 'dateTime' },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: 150,
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
style={{ padding: 0 }}
|
||||||
|
onClick={() => showEditModal(record)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定删除吗?"
|
||||||
|
onConfirm={async () => {
|
||||||
|
if (record.id) {
|
||||||
|
try {
|
||||||
|
const response = await siteapicontrollerDeletewebhook({
|
||||||
|
siteId,
|
||||||
|
id: String(record.id),
|
||||||
|
});
|
||||||
|
if (response.success) {
|
||||||
|
message.success('删除成功');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} else {
|
||||||
|
message.error('删除失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('删除失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="link" danger>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProCard>
|
||||||
|
<ProTable<API.UnifiedWebhookDTO>
|
||||||
|
columns={columns}
|
||||||
|
actionRef={actionRef}
|
||||||
|
request={async (params) => {
|
||||||
|
try {
|
||||||
|
const response = await siteapicontrollerGetwebhooks({
|
||||||
|
...params,
|
||||||
|
siteId,
|
||||||
|
page: params.current,
|
||||||
|
per_page: params.pageSize,
|
||||||
|
});
|
||||||
|
// 确保 response.data 存在
|
||||||
|
if (!response || !response.data) {
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
success: true,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 确保 response.data.items 是数组
|
||||||
|
const items = Array.isArray(response.data.items) ? response.data.items : [];
|
||||||
|
// 确保每个 item 有有效的 id
|
||||||
|
const processedItems = items.map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
// 如果 id 是对象,转换为字符串,否则使用索引作为后备
|
||||||
|
id: typeof item.id === 'object' ? JSON.stringify(item.id) : (item.id || index),
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
data: processedItems,
|
||||||
|
success: true,
|
||||||
|
total: Number(response.data.total) || 0,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取webhooks失败:', error);
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
success: true,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
rowKey="id"
|
||||||
|
search={{
|
||||||
|
labelWidth: 'auto',
|
||||||
|
}}
|
||||||
|
headerTitle="Webhooks列表"
|
||||||
|
toolBarRender={() => [
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={showCreateModal}
|
||||||
|
>
|
||||||
|
新建Webhook
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</ProCard>
|
||||||
|
|
||||||
|
{/* Webhook编辑/新建模态框 */}
|
||||||
|
<Modal
|
||||||
|
title={isEditMode ? '编辑Webhook' : '新建Webhook'}
|
||||||
|
open={isModalVisible}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={[
|
||||||
|
<Button key="back" onClick={handleCancel}>
|
||||||
|
取消
|
||||||
|
</Button>,
|
||||||
|
<Button key="submit" type="primary" onClick={handleSubmit}>
|
||||||
|
{isEditMode ? '更新' : '创建'}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={{
|
||||||
|
status: 'active',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="名称"
|
||||||
|
rules={[{ required: true, message: '请输入webhook名称' }, { max: 100, message: '名称不能超过100个字符' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入webhook名称" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="topic"
|
||||||
|
label="主题"
|
||||||
|
rules={[{ required: true, message: '请选择webhook主题' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择webhook主题"
|
||||||
|
options={webhookTopics}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="delivery_url"
|
||||||
|
label="回调URL"
|
||||||
|
rules={[{ required: true, message: '请输入回调URL' }, { type: 'url', message: '请输入有效的URL' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入回调URL,如:https://example.com/webhook" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="secret"
|
||||||
|
label="密钥(可选)"
|
||||||
|
rules={[{ max: 255, message: '密钥不能超过255个字符' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入密钥,用于验证webhook请求" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="status"
|
||||||
|
label="状态"
|
||||||
|
rules={[{ required: true, message: '请选择webhook状态' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择webhook状态"
|
||||||
|
options={webhookStatuses}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WebhooksPage;
|
||||||
|
|
@ -3,132 +3,87 @@ import {
|
||||||
templatecontrollerDeletetemplate,
|
templatecontrollerDeletetemplate,
|
||||||
templatecontrollerGettemplatelist,
|
templatecontrollerGettemplatelist,
|
||||||
templatecontrollerRendertemplate,
|
templatecontrollerRendertemplate,
|
||||||
|
templatecontrollerRendertemplatedirect,
|
||||||
templatecontrollerUpdatetemplate,
|
templatecontrollerUpdatetemplate,
|
||||||
} from '@/servers/api/template';
|
} from '@/servers/api/template';
|
||||||
import { BugOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
|
import { EditOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
ActionType,
|
ActionType,
|
||||||
DrawerForm,
|
DrawerForm,
|
||||||
ModalForm,
|
|
||||||
PageContainer,
|
PageContainer,
|
||||||
ProColumns,
|
ProColumns,
|
||||||
ProForm,
|
ProForm,
|
||||||
ProFormText,
|
ProFormText,
|
||||||
ProFormTextArea,
|
|
||||||
ProTable,
|
ProTable,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
import { App, Button, Card, Popconfirm, Typography } from 'antd';
|
import { App, Button, Card, Popconfirm, Space, Typography } from 'antd';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import ReactJson from 'react-json-view';
|
|
||||||
|
|
||||||
const TestModal: React.FC<{
|
// 自定义hook,用于处理模板预览逻辑
|
||||||
visible: boolean;
|
const useTemplatePreview = () => {
|
||||||
onClose: () => void;
|
|
||||||
template: API.Template | null;
|
|
||||||
}> = ({ visible, onClose, template }) => {
|
|
||||||
const { message } = App.useApp();
|
|
||||||
const [inputData, setInputData] = useState<Record<string, any>>({});
|
|
||||||
const [renderedResult, setRenderedResult] = useState<string>('');
|
const [renderedResult, setRenderedResult] = useState<string>('');
|
||||||
|
const [previewData, setPreviewData] = useState<any>(null);
|
||||||
|
|
||||||
// 当模板改变时,重置数据
|
// 防抖的预览效果
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible && template) {
|
if (!previewData || !previewData.value) {
|
||||||
// 尝试解析模板中可能的变量作为初始数据(可选优化,这里先置空)
|
setRenderedResult('请输入模板内容');
|
||||||
// 或者根据模板类型提供一些默认值
|
return;
|
||||||
if (template.testData) {
|
|
||||||
try {
|
|
||||||
setInputData(JSON.parse(template.testData));
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to parse testData:', e);
|
|
||||||
setInputData({});
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setInputData({});
|
|
||||||
}
|
|
||||||
setRenderedResult('');
|
|
||||||
}
|
|
||||||
}, [visible, template]);
|
|
||||||
|
|
||||||
// 监听 inputData 变化并调用渲染 API
|
|
||||||
useEffect(() => {
|
|
||||||
if (!visible || !template) return;
|
|
||||||
|
|
||||||
const timer = setTimeout(async () => {
|
const timer = setTimeout(async () => {
|
||||||
|
let testData = {};
|
||||||
try {
|
try {
|
||||||
const res = await templatecontrollerRendertemplate(
|
if (previewData.testData) {
|
||||||
{ name: template.name || '' },
|
testData = JSON.parse(previewData.testData);
|
||||||
inputData,
|
}
|
||||||
);
|
} catch (e) {
|
||||||
|
testData = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用新的直接渲染API,传入模板内容和测试数据
|
||||||
|
const res = await templatecontrollerRendertemplatedirect({
|
||||||
|
template: previewData.value,
|
||||||
|
data: testData,
|
||||||
|
});
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
setRenderedResult(res.data as unknown as string);
|
setRenderedResult(res.data as unknown as string);
|
||||||
} else {
|
} else {
|
||||||
setRenderedResult(`Error: ${res.message}`);
|
setRenderedResult(`错误: ${res.message}`);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setRenderedResult(`Error: ${error.message}`);
|
setRenderedResult(`错误: ${error.message}`);
|
||||||
}
|
}
|
||||||
}, 500); // 防抖 500ms
|
}, 500); // 防抖 500ms
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [inputData, visible, template]);
|
}, [previewData]);
|
||||||
|
|
||||||
return (
|
// 处理实时预览逻辑
|
||||||
<ModalForm
|
const handlePreview = (_changedValues: any, allValues: any) => {
|
||||||
title={`测试模板: ${template?.name || '未知模板'}`}
|
setPreviewData(allValues);
|
||||||
open={visible}
|
};
|
||||||
onOpenChange={(open) => !open && onClose()}
|
|
||||||
modalProps={{ destroyOnClose: true, onCancel: onClose }}
|
// 手动刷新预览
|
||||||
submitter={false} // 不需要提交按钮
|
const refreshPreview = (formValues: any) => {
|
||||||
width={800}
|
setPreviewData(formValues);
|
||||||
>
|
};
|
||||||
<div style={{ display: 'flex', gap: '20px' }}>
|
|
||||||
<div style={{ flex: 1 }}>
|
return {
|
||||||
<Typography.Title level={5}>输入数据 (JSON)</Typography.Title>
|
renderedResult,
|
||||||
<Card bodyStyle={{ padding: 0, height: '300px', overflow: 'auto' }}>
|
handlePreview,
|
||||||
<ReactJson
|
refreshPreview,
|
||||||
src={inputData}
|
setPreviewData
|
||||||
onEdit={(edit) =>
|
};
|
||||||
setInputData(edit.updated_src as Record<string, any>)
|
|
||||||
}
|
|
||||||
onAdd={(add) =>
|
|
||||||
setInputData(add.updated_src as Record<string, any>)
|
|
||||||
}
|
|
||||||
onDelete={(del) =>
|
|
||||||
setInputData(del.updated_src as Record<string, any>)
|
|
||||||
}
|
|
||||||
name={false}
|
|
||||||
displayDataTypes={false}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<Typography.Title level={5}>渲染结果</Typography.Title>
|
|
||||||
<Card
|
|
||||||
bodyStyle={{
|
|
||||||
padding: '16px',
|
|
||||||
height: '300px',
|
|
||||||
overflow: 'auto',
|
|
||||||
backgroundColor: '#f5f5f5',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<pre style={{ whiteSpace: 'pre-wrap', margin: 0 }}>
|
|
||||||
{renderedResult}
|
|
||||||
</pre>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalForm>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const List: React.FC = () => {
|
const List: React.FC = () => {
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const [testModalVisible, setTestModalVisible] = useState(false);
|
|
||||||
const [currentTemplate, setCurrentTemplate] = useState<API.Template | null>(
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const columns: ProColumns<API.Template>[] = [
|
const columns: ProColumns<API.Template>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -169,17 +124,7 @@ const List: React.FC = () => {
|
||||||
dataIndex: 'option',
|
dataIndex: 'option',
|
||||||
valueType: 'option',
|
valueType: 'option',
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<>
|
<Space>
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
icon={<BugOutlined />}
|
|
||||||
onClick={() => {
|
|
||||||
setCurrentTemplate(record);
|
|
||||||
setTestModalVisible(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
测试
|
|
||||||
</Button>
|
|
||||||
<UpdateForm tableRef={actionRef} values={record} />
|
<UpdateForm tableRef={actionRef} values={record} />
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="删除"
|
title="删除"
|
||||||
|
|
@ -198,7 +143,7 @@ const List: React.FC = () => {
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -222,11 +167,6 @@ const List: React.FC = () => {
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
/>
|
/>
|
||||||
<TestModal
|
|
||||||
visible={testModalVisible}
|
|
||||||
onClose={() => setTestModalVisible(false)}
|
|
||||||
template={currentTemplate}
|
|
||||||
/>
|
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -235,9 +175,13 @@ const CreateForm: React.FC<{
|
||||||
tableRef: React.MutableRefObject<ActionType | undefined>;
|
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||||||
}> = ({ tableRef }) => {
|
}> = ({ tableRef }) => {
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
|
const [form] = ProForm.useForm();
|
||||||
|
const { renderedResult, handlePreview, refreshPreview } = useTemplatePreview();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DrawerForm<API.CreateTemplateDTO>
|
<DrawerForm<API.CreateTemplateDTO>
|
||||||
title="新建"
|
title="新建"
|
||||||
|
form={form}
|
||||||
trigger={
|
trigger={
|
||||||
<Button type="primary">
|
<Button type="primary">
|
||||||
<PlusOutlined />
|
<PlusOutlined />
|
||||||
|
|
@ -247,7 +191,9 @@ const CreateForm: React.FC<{
|
||||||
autoFocusFirstInput
|
autoFocusFirstInput
|
||||||
drawerProps={{
|
drawerProps={{
|
||||||
destroyOnHidden: true,
|
destroyOnHidden: true,
|
||||||
|
width: 1200, // 增加抽屉宽度以容纳调试面板
|
||||||
}}
|
}}
|
||||||
|
onValuesChange={handlePreview}
|
||||||
onFinish={async (values) => {
|
onFinish={async (values) => {
|
||||||
try {
|
try {
|
||||||
await templatecontrollerCreatetemplate(values);
|
await templatecontrollerCreatetemplate(values);
|
||||||
|
|
@ -260,6 +206,8 @@ const CreateForm: React.FC<{
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div style={{ display: 'flex', gap: '20px' }}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
<ProFormText
|
<ProFormText
|
||||||
name="name"
|
name="name"
|
||||||
label="模板名称"
|
label="模板名称"
|
||||||
|
|
@ -268,11 +216,11 @@ const CreateForm: React.FC<{
|
||||||
/>
|
/>
|
||||||
<ProForm.Item
|
<ProForm.Item
|
||||||
name="value"
|
name="value"
|
||||||
label="值"
|
label="模板内容"
|
||||||
rules={[{ required: true, message: '请输入值' }]}
|
rules={[{ required: true, message: '请输入模板内容' }]}
|
||||||
>
|
>
|
||||||
<Editor
|
<Editor
|
||||||
height="500px"
|
height="400px"
|
||||||
defaultLanguage="html"
|
defaultLanguage="html"
|
||||||
options={{
|
options={{
|
||||||
minimap: { enabled: false },
|
minimap: { enabled: false },
|
||||||
|
|
@ -282,13 +230,12 @@ const CreateForm: React.FC<{
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ProForm.Item>
|
</ProForm.Item>
|
||||||
<ProFormTextArea
|
<ProForm.Item
|
||||||
name="testData"
|
name="testData"
|
||||||
label="测试数据 (JSON)"
|
label="测试数据 (JSON)"
|
||||||
placeholder="请输入JSON格式的测试数据"
|
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
validator: (_, value) => {
|
validator: (_: any, value: any) => {
|
||||||
if (!value) return Promise.resolve();
|
if (!value) return Promise.resolve();
|
||||||
try {
|
try {
|
||||||
JSON.parse(value);
|
JSON.parse(value);
|
||||||
|
|
@ -299,7 +246,52 @@ const CreateForm: React.FC<{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
>
|
||||||
|
<Editor
|
||||||
|
height="200px"
|
||||||
|
defaultLanguage="json"
|
||||||
|
options={{
|
||||||
|
minimap: { enabled: false },
|
||||||
|
lineNumbers: 'on',
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
automaticLayout: true,
|
||||||
|
formatOnPaste: true,
|
||||||
|
formatOnType: true,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</ProForm.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||||
|
<Typography.Title level={5} style={{ margin: 0 }}>实时预览</Typography.Title>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<ReloadOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
// 获取当前表单数据并触发预览
|
||||||
|
const currentValues = form.getFieldsValue();
|
||||||
|
refreshPreview(currentValues);
|
||||||
|
}}
|
||||||
|
title="手动刷新预览"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Card
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
padding: '16px',
|
||||||
|
height: '600px',
|
||||||
|
overflow: 'auto',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<pre style={{ whiteSpace: 'pre-wrap', margin: 0 }}>
|
||||||
|
{renderedResult || '修改模板或测试数据后将自动预览结果...'}
|
||||||
|
</pre>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</DrawerForm>
|
</DrawerForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -309,9 +301,24 @@ const UpdateForm: React.FC<{
|
||||||
values: API.Template;
|
values: API.Template;
|
||||||
}> = ({ tableRef, values: initialValues }) => {
|
}> = ({ tableRef, values: initialValues }) => {
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
|
const [form] = ProForm.useForm();
|
||||||
|
const { renderedResult, handlePreview, refreshPreview, setPreviewData } = useTemplatePreview();
|
||||||
|
|
||||||
|
// 组件挂载时初始化预览数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialValues) {
|
||||||
|
setPreviewData({
|
||||||
|
name: initialValues.name,
|
||||||
|
value: initialValues.value,
|
||||||
|
testData: initialValues.testData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [initialValues, setPreviewData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DrawerForm<API.UpdateTemplateDTO>
|
<DrawerForm<API.UpdateTemplateDTO>
|
||||||
title="编辑"
|
title="编辑"
|
||||||
|
form={form}
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
trigger={
|
trigger={
|
||||||
<Button type="primary">
|
<Button type="primary">
|
||||||
|
|
@ -322,7 +329,9 @@ const UpdateForm: React.FC<{
|
||||||
autoFocusFirstInput
|
autoFocusFirstInput
|
||||||
drawerProps={{
|
drawerProps={{
|
||||||
destroyOnHidden: true,
|
destroyOnHidden: true,
|
||||||
|
width: 1200, // 增加抽屉宽度以容纳调试面板
|
||||||
}}
|
}}
|
||||||
|
onValuesChange={handlePreview}
|
||||||
onFinish={async (values) => {
|
onFinish={async (values) => {
|
||||||
if (!initialValues.id) return false;
|
if (!initialValues.id) return false;
|
||||||
try {
|
try {
|
||||||
|
|
@ -339,6 +348,8 @@ const UpdateForm: React.FC<{
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div style={{ display: 'flex', gap: '20px' }}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
<ProFormText
|
<ProFormText
|
||||||
name="name"
|
name="name"
|
||||||
label="模板名称"
|
label="模板名称"
|
||||||
|
|
@ -347,11 +358,11 @@ const UpdateForm: React.FC<{
|
||||||
/>
|
/>
|
||||||
<ProForm.Item
|
<ProForm.Item
|
||||||
name="value"
|
name="value"
|
||||||
label="值"
|
label="模板内容"
|
||||||
rules={[{ required: true, message: '请输入值' }]}
|
rules={[{ required: true, message: '请输入模板内容' }]}
|
||||||
>
|
>
|
||||||
<Editor
|
<Editor
|
||||||
height="500px"
|
height="400px"
|
||||||
defaultLanguage="html"
|
defaultLanguage="html"
|
||||||
options={{
|
options={{
|
||||||
minimap: { enabled: false },
|
minimap: { enabled: false },
|
||||||
|
|
@ -361,13 +372,12 @@ const UpdateForm: React.FC<{
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ProForm.Item>
|
</ProForm.Item>
|
||||||
<ProFormTextArea
|
<ProForm.Item
|
||||||
name="testData"
|
name="testData"
|
||||||
label="测试数据 (JSON)"
|
label="测试数据 (JSON)"
|
||||||
placeholder="请输入JSON格式的测试数据"
|
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
validator: (_, value) => {
|
validator: (_: any, value: any) => {
|
||||||
if (!value) return Promise.resolve();
|
if (!value) return Promise.resolve();
|
||||||
try {
|
try {
|
||||||
JSON.parse(value);
|
JSON.parse(value);
|
||||||
|
|
@ -378,7 +388,52 @@ const UpdateForm: React.FC<{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
>
|
||||||
|
<Editor
|
||||||
|
height="200px"
|
||||||
|
defaultLanguage="json"
|
||||||
|
options={{
|
||||||
|
minimap: { enabled: false },
|
||||||
|
lineNumbers: 'on',
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
automaticLayout: true,
|
||||||
|
formatOnPaste: true,
|
||||||
|
formatOnType: true,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</ProForm.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||||
|
<Typography.Title level={5} style={{ margin: 0 }}>实时预览</Typography.Title>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<ReloadOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
// 获取当前表单数据并触发预览
|
||||||
|
const currentValues = form.getFieldsValue();
|
||||||
|
refreshPreview(currentValues);
|
||||||
|
}}
|
||||||
|
title="手动刷新预览"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Card
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
padding: '16px',
|
||||||
|
height: '600px',
|
||||||
|
overflow: 'auto',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<pre style={{ whiteSpace: 'pre-wrap', margin: 0 }}>
|
||||||
|
{renderedResult || '修改模板或测试数据后将自动预览结果...'}
|
||||||
|
</pre>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</DrawerForm>
|
</DrawerForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -177,20 +177,6 @@ export async function ordercontrollerCreateorder(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /order/order/export/${param0} */
|
|
||||||
export async function ordercontrollerExportorder(
|
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
|
||||||
params: API.ordercontrollerExportorderParams,
|
|
||||||
options?: { [key: string]: any },
|
|
||||||
) {
|
|
||||||
const { ids: param0, ...queryParams } = params;
|
|
||||||
return request<any>(`/order/order/export/${param0}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
params: { ...queryParams },
|
|
||||||
...(options || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /order/order/pengding/items */
|
/** 此处后端没有提供注释 POST /order/order/pengding/items */
|
||||||
export async function ordercontrollerPengdingitems(
|
export async function ordercontrollerPengdingitems(
|
||||||
body: Record<string, any>,
|
body: Record<string, any>,
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,20 @@ export async function siteapicontrollerImportcustomers(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /site-api/${param0}/links */
|
||||||
|
export async function siteapicontrollerGetlinks(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerGetlinksParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { siteId: param0, ...queryParams } = params;
|
||||||
|
return request<any>(`/site-api/${param0}/links`, {
|
||||||
|
method: 'GET',
|
||||||
|
params: { ...queryParams },
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param0}/media */
|
/** 此处后端没有提供注释 GET /site-api/${param0}/media */
|
||||||
export async function siteapicontrollerGetmedia(
|
export async function siteapicontrollerGetmedia(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
@ -263,6 +277,25 @@ export async function siteapicontrollerBatchorders(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /site-api/${param0}/orders/batch-ship */
|
||||||
|
export async function siteapicontrollerBatchshiporders(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerBatchshipordersParams,
|
||||||
|
body: API.BatchShipOrdersDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { siteId: param0, ...queryParams } = params;
|
||||||
|
return request<Record<string, any>>(`/site-api/${param0}/orders/batch-ship`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param0}/orders/export */
|
/** 此处后端没有提供注释 GET /site-api/${param0}/orders/export */
|
||||||
export async function siteapicontrollerExportorders(
|
export async function siteapicontrollerExportorders(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
@ -529,6 +562,45 @@ export async function siteapicontrollerExportsubscriptions(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /site-api/${param0}/webhooks */
|
||||||
|
export async function siteapicontrollerGetwebhooks(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerGetwebhooksParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { siteId: param0, ...queryParams } = params;
|
||||||
|
return request<API.UnifiedPaginationDTO>(`/site-api/${param0}/webhooks`, {
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
...queryParams,
|
||||||
|
where: undefined,
|
||||||
|
...queryParams['where'],
|
||||||
|
order: undefined,
|
||||||
|
...queryParams['order'],
|
||||||
|
},
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /site-api/${param0}/webhooks */
|
||||||
|
export async function siteapicontrollerCreatewebhook(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerCreatewebhookParams,
|
||||||
|
body: API.CreateWebhookDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { siteId: param0, ...queryParams } = params;
|
||||||
|
return request<API.UnifiedWebhookDTO>(`/site-api/${param0}/webhooks`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param1}/customers/${param0} */
|
/** 此处后端没有提供注释 GET /site-api/${param1}/customers/${param0} */
|
||||||
export async function siteapicontrollerGetcustomer(
|
export async function siteapicontrollerGetcustomer(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
@ -688,6 +760,28 @@ export async function siteapicontrollerDeleteorder(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/cancel-ship */
|
||||||
|
export async function siteapicontrollerCancelshiporder(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerCancelshiporderParams,
|
||||||
|
body: API.CancelShipOrderDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||||
|
return request<Record<string, any>>(
|
||||||
|
`/site-api/${param1}/orders/${param0}/cancel-ship`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param1}/orders/${param0}/notes */
|
/** 此处后端没有提供注释 GET /site-api/${param1}/orders/${param0}/notes */
|
||||||
export async function siteapicontrollerGetordernotes(
|
export async function siteapicontrollerGetordernotes(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
@ -727,6 +821,28 @@ export async function siteapicontrollerCreateordernote(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/ship */
|
||||||
|
export async function siteapicontrollerShiporder(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerShiporderParams,
|
||||||
|
body: API.ShipOrderDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||||
|
return request<Record<string, any>>(
|
||||||
|
`/site-api/${param1}/orders/${param0}/ship`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 GET /site-api/${param1}/products/${param0} */
|
/** 此处后端没有提供注释 GET /site-api/${param1}/products/${param0} */
|
||||||
export async function siteapicontrollerGetproduct(
|
export async function siteapicontrollerGetproduct(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
@ -819,6 +935,62 @@ export async function siteapicontrollerDeletereview(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /site-api/${param1}/webhooks/${param0} */
|
||||||
|
export async function siteapicontrollerGetwebhook(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerGetwebhookParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||||
|
return request<API.UnifiedWebhookDTO>(
|
||||||
|
`/site-api/${param1}/webhooks/${param0}`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
params: { ...queryParams },
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 PUT /site-api/${param1}/webhooks/${param0} */
|
||||||
|
export async function siteapicontrollerUpdatewebhook(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerUpdatewebhookParams,
|
||||||
|
body: API.UpdateWebhookDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||||
|
return request<API.UnifiedWebhookDTO>(
|
||||||
|
`/site-api/${param1}/webhooks/${param0}`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: { ...queryParams },
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 DELETE /site-api/${param1}/webhooks/${param0} */
|
||||||
|
export async function siteapicontrollerDeletewebhook(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.siteapicontrollerDeletewebhookParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||||
|
return request<Record<string, any>>(
|
||||||
|
`/site-api/${param1}/webhooks/${param0}`,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
params: { ...queryParams },
|
||||||
|
...(options || {}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */
|
/** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */
|
||||||
export async function siteapicontrollerUpdatevariation(
|
export async function siteapicontrollerUpdatevariation(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,21 @@ export async function templatecontrollerGettemplatelist(options?: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /template/render-direct */
|
||||||
|
export async function templatecontrollerRendertemplatedirect(
|
||||||
|
body: API.RenderTemplateDTO,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<Record<string, any>>('/template/render-direct', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 此处后端没有提供注释 POST /template/render/${param0} */
|
/** 此处后端没有提供注释 POST /template/render/${param0} */
|
||||||
export async function templatecontrollerRendertemplate(
|
export async function templatecontrollerRendertemplate(
|
||||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,24 @@ declare namespace API {
|
||||||
ids: any[];
|
ids: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type BatchShipOrderItemDTO = {
|
||||||
|
/** 订单ID */
|
||||||
|
order_id?: string;
|
||||||
|
/** 物流单号 */
|
||||||
|
tracking_number?: string;
|
||||||
|
/** 物流公司 */
|
||||||
|
shipping_provider?: string;
|
||||||
|
/** 发货方式 */
|
||||||
|
shipping_method?: string;
|
||||||
|
/** 发货商品项 */
|
||||||
|
items?: ShipOrderItemDTO[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type BatchShipOrdersDTO = {
|
||||||
|
/** 批量发货订单列表 */
|
||||||
|
orders?: BatchShipOrderItemDTO[];
|
||||||
|
};
|
||||||
|
|
||||||
type BatchSyncProductsDTO = {
|
type BatchSyncProductsDTO = {
|
||||||
/** 产品ID列表 */
|
/** 产品ID列表 */
|
||||||
productIds?: number[];
|
productIds?: number[];
|
||||||
|
|
@ -113,6 +131,13 @@ declare namespace API {
|
||||||
data?: boolean;
|
data?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CancelShipOrderDTO = {
|
||||||
|
/** 取消原因 */
|
||||||
|
reason?: string;
|
||||||
|
/** 发货单ID */
|
||||||
|
shipment_id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type categorycontrollerDeletecategoryattributeParams = {
|
type categorycontrollerDeletecategoryattributeParams = {
|
||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
|
|
@ -226,6 +251,21 @@ declare namespace API {
|
||||||
testData?: string;
|
testData?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CreateWebhookDTO = {
|
||||||
|
/** 名称 */
|
||||||
|
name?: string;
|
||||||
|
/** 主题/事件 */
|
||||||
|
topic?: string;
|
||||||
|
/** 目标URL */
|
||||||
|
delivery_url?: string;
|
||||||
|
/** 秘密密钥 */
|
||||||
|
secret?: string;
|
||||||
|
/** 头部信息 */
|
||||||
|
headers?: Record<string, any>;
|
||||||
|
/** API版本 */
|
||||||
|
api_version?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Cubid = {
|
type Cubid = {
|
||||||
w?: number;
|
w?: number;
|
||||||
h?: number;
|
h?: number;
|
||||||
|
|
@ -488,10 +528,6 @@ declare namespace API {
|
||||||
orderId: number;
|
orderId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ordercontrollerExportorderParams = {
|
|
||||||
ids: number[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type ordercontrollerGetorderitemlistParams = {
|
type ordercontrollerGetorderitemlistParams = {
|
||||||
/** 页码 */
|
/** 页码 */
|
||||||
current?: number;
|
current?: number;
|
||||||
|
|
@ -804,7 +840,7 @@ declare namespace API {
|
||||||
startDate?: string;
|
startDate?: string;
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
siteId?: string;
|
siteId?: number;
|
||||||
purchaseType?: 'all' | 'first_purchase' | 'repeat_purchase';
|
purchaseType?: 'all' | 'first_purchase' | 'repeat_purchase';
|
||||||
orderType?: 'all' | 'cpc' | 'non_cpc';
|
orderType?: 'all' | 'cpc' | 'non_cpc';
|
||||||
brand?: 'all' | 'zyn' | 'yoone' | 'zolt';
|
brand?: 'all' | 'zyn' | 'yoone' | 'zolt';
|
||||||
|
|
@ -1341,6 +1377,13 @@ declare namespace API {
|
||||||
data?: RateDTO[];
|
data?: RateDTO[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type RenderTemplateDTO = {
|
||||||
|
/** 模板内容 */
|
||||||
|
template: string;
|
||||||
|
/** 渲染数据 */
|
||||||
|
data: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
type Service = {
|
type Service = {
|
||||||
id?: string;
|
id?: string;
|
||||||
carrier_name?: string;
|
carrier_name?: string;
|
||||||
|
|
@ -1393,6 +1436,24 @@ declare namespace API {
|
||||||
weightUom?: string;
|
weightUom?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ShipOrderDTO = {
|
||||||
|
/** 物流单号 */
|
||||||
|
tracking_number?: string;
|
||||||
|
/** 物流公司 */
|
||||||
|
shipping_provider?: string;
|
||||||
|
/** 发货方式 */
|
||||||
|
shipping_method?: string;
|
||||||
|
/** 发货商品项 */
|
||||||
|
items?: ShipOrderItemDTO[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ShipOrderItemDTO = {
|
||||||
|
/** 订单项ID */
|
||||||
|
order_item_id?: number;
|
||||||
|
/** 数量 */
|
||||||
|
quantity?: number;
|
||||||
|
};
|
||||||
|
|
||||||
type ShippingAddress = {
|
type ShippingAddress = {
|
||||||
id?: number;
|
id?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
@ -1446,6 +1507,15 @@ declare namespace API {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type siteapicontrollerBatchshipordersParams = {
|
||||||
|
siteId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type siteapicontrollerCancelshiporderParams = {
|
||||||
|
id: string;
|
||||||
|
siteId: number;
|
||||||
|
};
|
||||||
|
|
||||||
type siteapicontrollerConvertmediatowebpParams = {
|
type siteapicontrollerConvertmediatowebpParams = {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
@ -1475,6 +1545,10 @@ declare namespace API {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type siteapicontrollerCreatewebhookParams = {
|
||||||
|
siteId: number;
|
||||||
|
};
|
||||||
|
|
||||||
type siteapicontrollerDeletecustomerParams = {
|
type siteapicontrollerDeletecustomerParams = {
|
||||||
id: string;
|
id: string;
|
||||||
siteId: number;
|
siteId: number;
|
||||||
|
|
@ -1500,6 +1574,11 @@ declare namespace API {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type siteapicontrollerDeletewebhookParams = {
|
||||||
|
id: string;
|
||||||
|
siteId: number;
|
||||||
|
};
|
||||||
|
|
||||||
type siteapicontrollerExportcustomersParams = {
|
type siteapicontrollerExportcustomersParams = {
|
||||||
/** 页码 */
|
/** 页码 */
|
||||||
page?: number;
|
page?: number;
|
||||||
|
|
@ -1714,6 +1793,10 @@ declare namespace API {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type siteapicontrollerGetlinksParams = {
|
||||||
|
siteId: number;
|
||||||
|
};
|
||||||
|
|
||||||
type siteapicontrollerGetmediaParams = {
|
type siteapicontrollerGetmediaParams = {
|
||||||
/** 页码 */
|
/** 页码 */
|
||||||
page?: number;
|
page?: number;
|
||||||
|
|
@ -1859,6 +1942,37 @@ declare namespace API {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type siteapicontrollerGetwebhookParams = {
|
||||||
|
id: string;
|
||||||
|
siteId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type siteapicontrollerGetwebhooksParams = {
|
||||||
|
/** 页码 */
|
||||||
|
page?: number;
|
||||||
|
/** 每页数量 */
|
||||||
|
per_page?: number;
|
||||||
|
/** 每页数量别名 */
|
||||||
|
page_size?: number;
|
||||||
|
/** 搜索关键词 */
|
||||||
|
search?: string;
|
||||||
|
/** 状态 */
|
||||||
|
status?: string;
|
||||||
|
/** 客户ID,用于筛选订单 */
|
||||||
|
customer_id?: number;
|
||||||
|
/** 过滤条件对象 */
|
||||||
|
where?: Record<string, any>;
|
||||||
|
/** 排序对象,例如 { "sku": "desc" } */
|
||||||
|
order?: Record<string, any>;
|
||||||
|
/** 排序字段(兼容旧入参) */
|
||||||
|
orderby?: string;
|
||||||
|
/** 排序方式(兼容旧入参) */
|
||||||
|
orderDir?: string;
|
||||||
|
/** 选中ID列表,逗号分隔 */
|
||||||
|
ids?: string;
|
||||||
|
siteId: number;
|
||||||
|
};
|
||||||
|
|
||||||
type siteapicontrollerImportcustomersParams = {
|
type siteapicontrollerImportcustomersParams = {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
@ -1875,6 +1989,11 @@ declare namespace API {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type siteapicontrollerShiporderParams = {
|
||||||
|
id: string;
|
||||||
|
siteId: number;
|
||||||
|
};
|
||||||
|
|
||||||
type siteapicontrollerUpdatecustomerParams = {
|
type siteapicontrollerUpdatecustomerParams = {
|
||||||
id: string;
|
id: string;
|
||||||
siteId: number;
|
siteId: number;
|
||||||
|
|
@ -1906,6 +2025,11 @@ declare namespace API {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type siteapicontrollerUpdatewebhookParams = {
|
||||||
|
id: string;
|
||||||
|
siteId: number;
|
||||||
|
};
|
||||||
|
|
||||||
type SiteConfig = {
|
type SiteConfig = {
|
||||||
/** 站点 ID */
|
/** 站点 ID */
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
@ -2458,6 +2582,21 @@ declare namespace API {
|
||||||
totalPages?: number;
|
totalPages?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UnifiedPaginationDTO = {
|
||||||
|
/** 列表数据 */
|
||||||
|
items?: any;
|
||||||
|
/** 总数 */
|
||||||
|
total?: number;
|
||||||
|
/** 当前页 */
|
||||||
|
page?: number;
|
||||||
|
/** 每页数量 */
|
||||||
|
per_page?: number;
|
||||||
|
/** 每页数量别名 */
|
||||||
|
page_size?: number;
|
||||||
|
/** 总页数 */
|
||||||
|
totalPages?: number;
|
||||||
|
};
|
||||||
|
|
||||||
type UnifiedProductAttributeDTO = {
|
type UnifiedProductAttributeDTO = {
|
||||||
/** 属性ID */
|
/** 属性ID */
|
||||||
id?: Record<string, any>;
|
id?: Record<string, any>;
|
||||||
|
|
@ -2567,6 +2706,10 @@ declare namespace API {
|
||||||
status?: string;
|
status?: string;
|
||||||
/** 创建时间 */
|
/** 创建时间 */
|
||||||
date_created?: string;
|
date_created?: string;
|
||||||
|
/** 更新时间 */
|
||||||
|
date_modified?: string;
|
||||||
|
/** 原始数据 */
|
||||||
|
raw?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UnifiedReviewPaginationDTO = {
|
type UnifiedReviewPaginationDTO = {
|
||||||
|
|
@ -2656,6 +2799,29 @@ declare namespace API {
|
||||||
name?: string;
|
name?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UnifiedWebhookDTO = {
|
||||||
|
/** Webhook ID */
|
||||||
|
id?: Record<string, any>;
|
||||||
|
/** 名称 */
|
||||||
|
name?: string;
|
||||||
|
/** 状态 */
|
||||||
|
status?: string;
|
||||||
|
/** 主题/事件 */
|
||||||
|
topic?: string;
|
||||||
|
/** 目标URL */
|
||||||
|
delivery_url?: string;
|
||||||
|
/** 秘密密钥 */
|
||||||
|
secret?: string;
|
||||||
|
/** 创建时间 */
|
||||||
|
date_created?: string;
|
||||||
|
/** 更新时间 */
|
||||||
|
date_modified?: string;
|
||||||
|
/** 头部信息 */
|
||||||
|
headers?: Record<string, any>;
|
||||||
|
/** API版本 */
|
||||||
|
api_version?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type UpdateAreaDTO = {
|
type UpdateAreaDTO = {
|
||||||
/** 编码 */
|
/** 编码 */
|
||||||
code?: string;
|
code?: string;
|
||||||
|
|
@ -2762,6 +2928,23 @@ declare namespace API {
|
||||||
on_sale?: boolean;
|
on_sale?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UpdateWebhookDTO = {
|
||||||
|
/** 名称 */
|
||||||
|
name?: string;
|
||||||
|
/** 状态 */
|
||||||
|
status?: string;
|
||||||
|
/** 主题/事件 */
|
||||||
|
topic?: string;
|
||||||
|
/** 目标URL */
|
||||||
|
delivery_url?: string;
|
||||||
|
/** 秘密密钥 */
|
||||||
|
secret?: string;
|
||||||
|
/** 头部信息 */
|
||||||
|
headers?: Record<string, any>;
|
||||||
|
/** API版本 */
|
||||||
|
api_version?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type UpdateWpProductDTO = {
|
type UpdateWpProductDTO = {
|
||||||
/** 变体名称 */
|
/** 变体名称 */
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue