zksu
/
WEB
forked from yoone/WEB
1
0
Fork 0

feat(产品属性): 重构属性管理逻辑,使用排除列表替代包含列表

refactor(订单管理): 添加发货和取消发货功能

feat(店铺管理): 新增链接管理和Webhooks管理页面

fix(评论管理): 增强评论列表的数据处理和错误处理

feat(模板管理): 实现模板实时预览功能并优化编辑界面

style(布局): 优化店铺管理侧边栏样式和结构

perf(产品同步): 改进SKU生成逻辑和错误处理

docs(API): 更新API类型定义和注释
This commit is contained in:
tikkhun 2025-12-22 17:33:17 +08:00 committed by 黄珑
parent dfcc8c80e0
commit 09093ad48b
21 changed files with 1721 additions and 527 deletions

View File

@ -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',
},
], ],
}, },
{ {

View File

@ -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),

View File

@ -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}`, { message.success('添加成功');
method: 'PUT',
data: values,
});
message.success('更新成功');
} else {
await request('/dict', { method: 'POST', data: values });
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('删除成功');
actionRef.current?.reload();
} catch (error) { // 强制刷新字典项列表
message.error('删除失败'); setTimeout(() => {
actionRef.current?.reload();
}, 100);
} 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
actionRef.current?.reload(); });
} catch (error) {
message.error(editingDictItem ? '更新失败' : '添加失败'); if (!result.success) {
throw new Error(result.message || '添加失败');
}
message.success('添加成功');
setIsAddDictItemModalVisible(false);
// 强制刷新字典项列表
setTimeout(() => {
actionRef.current?.reload();
}, 100);
} catch (error: any) {
message.error(`添加失败:${error.message || '未知错误'}`);
}
};
// 编辑字典项表单提交
const handleEditDictItemFormSubmit = async (values: any) => {
if (!editDictItemData) return;
try {
const result = await dictApi.dictcontrollerUpdatedictitem({ id: editDictItemData.id }, values);
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') {
@ -375,7 +417,7 @@ const DictPage: React.FC = () => {
<Button size="small" onClick={handleDownloadDictTemplate}> <Button size="small" onClick={handleDownloadDictTemplate}>
</Button> </Button>
</Space> </Space>
<Table <Table
dataSource={dicts} dataSource={dicts}
columns={dictColumns} columns={dictColumns}
@ -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 {
data: res.data || [],
success: res.success,
total: res.data?.length || 0,
};
}
// 兼容旧的响应格式(直接返回数组)
return { return {
data: res, 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>

View File

@ -1,8 +1,5 @@
// 限定允许管理的字典名称集合 export const notAttributes = new Set([
export const attributes = new Set([ 'zh-cn',
'brand', 'en-us',
'strength', 'category'
'flavor', ]);
'size',
'humidity',
]);

View File

@ -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('获取字典列表失败');

View File

@ -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>

View File

@ -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,56 +609,7 @@ const List: React.FC = () => {
toolBarRender={() => [ toolBarRender={() => [
// 新建按钮 // 新建按钮
<CreateForm tableRef={actionRef} />, <CreateForm tableRef={actionRef} />,
// 批量编辑按钮 // 导入 CSV(使用 customRequest 以支持 request 拦截器和鉴权)
<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 拦截器和鉴权)
<Upload <Upload
name="file" name="file"
accept=".csv" accept=".csv"
@ -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;

View File

@ -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,
})), })),

View File

@ -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}`
), ),
}} }}

View File

@ -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',

View File

@ -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>}

View File

@ -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;

View File

@ -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 () => {

View File

@ -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,20 +30,162 @@ 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>
); );
}; };
export default ReviewForm; export default ReviewForm;

View File

@ -85,17 +85,44 @@ const ReviewsPage: React.FC = () => {
columns={columns} columns={columns}
actionRef={actionRef} actionRef={actionRef}
request={async (params) => { request={async (params) => {
const response = await siteapicontrollerGetreviews({ try {
...params, const response = await siteapicontrollerGetreviews({
siteId, ...params,
page: params.current, siteId,
per_page: params.pageSize, page: params.current,
}); per_page: params.pageSize,
return { });
data: response.data.items, // 确保 response.data 存在
success: true, if (!response || !response.data) {
total: response.data.total, 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),
// 如果 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={{

View File

@ -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="请输入回调URLhttps://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;

View File

@ -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,46 +206,92 @@ const CreateForm: React.FC<{
} }
}} }}
> >
<ProFormText <div style={{ display: 'flex', gap: '20px' }}>
name="name" <div style={{ flex: 1 }}>
label="模板名称" <ProFormText
placeholder="请输入名称" name="name"
rules={[{ required: true, message: '请输入名称' }]} label="模板名称"
/> placeholder="请输入名称"
<ProForm.Item rules={[{ required: true, message: '请输入名称' }]}
name="value" />
label="值" <ProForm.Item
rules={[{ required: true, message: '请输入值' }]} name="value"
> label="模板内容"
<Editor rules={[{ required: true, message: '请输入模板内容' }]}
height="500px" >
defaultLanguage="html" <Editor
options={{ height="400px"
minimap: { enabled: false }, defaultLanguage="html"
lineNumbers: 'on', options={{
scrollBeyondLastLine: false, minimap: { enabled: false },
automaticLayout: true, lineNumbers: 'on',
}} scrollBeyondLastLine: false,
/> automaticLayout: true,
</ProForm.Item> }}
<ProFormTextArea />
name="testData" </ProForm.Item>
label="测试数据 (JSON)" <ProForm.Item
placeholder="请输入JSON格式的测试数据" name="testData"
rules={[ label="测试数据 (JSON)"
{ rules={[
validator: (_, value) => { {
if (!value) return Promise.resolve(); validator: (_: any, value: any) => {
try { if (!value) return Promise.resolve();
JSON.parse(value); try {
return Promise.resolve(); JSON.parse(value);
} catch (e) { return Promise.resolve();
return Promise.reject(new Error('请输入有效的JSON格式')); } catch (e) {
return Promise.reject(new Error('请输入有效的JSON格式'));
}
},
},
]}
>
<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();
return ( 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 (
<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,46 +348,92 @@ const UpdateForm: React.FC<{
} }
}} }}
> >
<ProFormText <div style={{ display: 'flex', gap: '20px' }}>
name="name" <div style={{ flex: 1 }}>
label="模板名称" <ProFormText
placeholder="请输入名称" name="name"
rules={[{ required: true, message: '请输入名称' }]} label="模板名称"
/> placeholder="请输入名称"
<ProForm.Item rules={[{ required: true, message: '请输入名称' }]}
name="value" />
label="值" <ProForm.Item
rules={[{ required: true, message: '请输入值' }]} name="value"
> label="模板内容"
<Editor rules={[{ required: true, message: '请输入模板内容' }]}
height="500px" >
defaultLanguage="html" <Editor
options={{ height="400px"
minimap: { enabled: false }, defaultLanguage="html"
lineNumbers: 'on', options={{
scrollBeyondLastLine: false, minimap: { enabled: false },
automaticLayout: true, lineNumbers: 'on',
}} scrollBeyondLastLine: false,
/> automaticLayout: true,
</ProForm.Item> }}
<ProFormTextArea />
name="testData" </ProForm.Item>
label="测试数据 (JSON)" <ProForm.Item
placeholder="请输入JSON格式的测试数据" name="testData"
rules={[ label="测试数据 (JSON)"
{ rules={[
validator: (_, value) => { {
if (!value) return Promise.resolve(); validator: (_: any, value: any) => {
try { if (!value) return Promise.resolve();
JSON.parse(value); try {
return Promise.resolve(); JSON.parse(value);
} catch (e) { return Promise.resolve();
return Promise.reject(new Error('请输入有效的JSON格式')); } catch (e) {
return Promise.reject(new Error('请输入有效的JSON格式'));
}
},
},
]}
>
<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>
); );
}; };

View File

@ -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>,

View File

@ -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默认没有生成对象)

View File

@ -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默认没有生成对象)

View File

@ -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[];
@ -85,14 +103,14 @@ declare namespace API {
tags?: string[]; tags?: string[];
/** 状态 */ /** 状态 */
status?: status?:
| 'publish' | 'publish'
| 'draft' | 'draft'
| 'pending' | 'pending'
| 'private' | 'private'
| 'trash' | 'trash'
| 'auto-draft' | 'auto-draft'
| 'future' | 'future'
| 'inherit'; | 'inherit';
}; };
type BatchUpdateTagsDTO = { type BatchUpdateTagsDTO = {
@ -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;
@ -544,18 +580,18 @@ declare namespace API {
startDate?: string; startDate?: string;
endDate?: string; endDate?: string;
status?: status?:
| 'pending' | 'pending'
| 'processing' | 'processing'
| 'completed' | 'completed'
| 'cancelled' | 'cancelled'
| 'refunded' | 'refunded'
| 'failed' | 'failed'
| 'after_sale_pending' | 'after_sale_pending'
| 'pending_reshipment' | 'pending_reshipment'
| 'pending_refund' | 'pending_refund'
| 'return-requested' | 'return-requested'
| 'return-approved' | 'return-approved'
| 'return-cancelled'; | 'return-cancelled';
payment_method?: string; payment_method?: string;
/** 仅订阅订单(父订阅订单或包含订阅商品) */ /** 仅订阅订单(父订阅订单或包含订阅商品) */
isSubscriptionOnly?: boolean; isSubscriptionOnly?: boolean;
@ -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';
@ -1160,18 +1196,18 @@ declare namespace API {
startDate?: string; startDate?: string;
endDate?: string; endDate?: string;
status?: status?:
| 'pending' | 'pending'
| 'processing' | 'processing'
| 'completed' | 'completed'
| 'cancelled' | 'cancelled'
| 'refunded' | 'refunded'
| 'failed' | 'failed'
| 'after_sale_pending' | 'after_sale_pending'
| 'pending_reshipment' | 'pending_reshipment'
| 'pending_refund' | 'pending_refund'
| 'return-requested' | 'return-requested'
| 'return-approved' | 'return-approved'
| 'return-cancelled'; | 'return-cancelled';
payment_method?: string; payment_method?: string;
/** 仅订阅订单(父订阅订单或包含订阅商品) */ /** 仅订阅订单(父订阅订单或包含订阅商品) */
isSubscriptionOnly?: boolean; isSubscriptionOnly?: boolean;
@ -1282,12 +1318,12 @@ declare namespace API {
siteId?: string; siteId?: string;
/** 订阅状态 */ /** 订阅状态 */
status?: status?:
| 'active' | 'active'
| 'pending' | 'pending'
| 'on-hold' | 'on-hold'
| 'cancelled' | 'cancelled'
| 'expired' | 'expired'
| 'pending-cancel'; | 'pending-cancel';
/** 客户邮箱 */ /** 客户邮箱 */
customer_email?: string; customer_email?: string;
/** 关键字(订阅ID,邮箱等) */ /** 关键字(订阅ID,邮箱等) */
@ -1305,14 +1341,14 @@ declare namespace API {
siteId?: string; siteId?: string;
/** 产品状态 */ /** 产品状态 */
status?: status?:
| 'publish' | 'publish'
| 'draft' | 'draft'
| 'pending' | 'pending'
| 'private' | 'private'
| 'trash' | 'trash'
| 'auto-draft' | 'auto-draft'
| 'future' | 'future'
| 'inherit'; | 'inherit';
/** SKU列表 */ /** SKU列表 */
skus?: any[]; skus?: any[];
}; };
@ -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;
@ -2183,12 +2307,12 @@ declare namespace API {
siteId?: string; siteId?: string;
/** 订阅状态 */ /** 订阅状态 */
status?: status?:
| 'active' | 'active'
| 'pending' | 'pending'
| 'on-hold' | 'on-hold'
| 'cancelled' | 'cancelled'
| 'expired' | 'expired'
| 'pending-cancel'; | 'pending-cancel';
/** 客户邮箱 */ /** 客户邮箱 */
customer_email?: string; customer_email?: string;
/** 关键字(订阅ID,邮箱等) */ /** 关键字(订阅ID,邮箱等) */
@ -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;
@ -2848,14 +3031,14 @@ declare namespace API {
siteId?: string; siteId?: string;
/** 产品状态 */ /** 产品状态 */
status?: status?:
| 'publish' | 'publish'
| 'draft' | 'draft'
| 'pending' | 'pending'
| 'private' | 'private'
| 'trash' | 'trash'
| 'auto-draft' | 'auto-draft'
| 'future' | 'future'
| 'inherit'; | 'inherit';
/** SKU列表 */ /** SKU列表 */
skus?: any[]; skus?: any[];
}; };
@ -2906,14 +3089,14 @@ declare namespace API {
name: string; name: string;
/** 产品状态 */ /** 产品状态 */
status?: status?:
| 'publish' | 'publish'
| 'draft' | 'draft'
| 'pending' | 'pending'
| 'private' | 'private'
| 'trash' | 'trash'
| 'auto-draft' | 'auto-draft'
| 'future' | 'future'
| 'inherit'; | 'inherit';
/** 是否为特色产品 */ /** 是否为特色产品 */
featured?: boolean; featured?: boolean;
/** 目录可见性 */ /** 目录可见性 */