chore:根据API删减调整前端。 #36

Merged
longbot merged 5 commits from :main into main 2025-12-24 08:14:03 +00:00
35 changed files with 3374 additions and 1805 deletions

View File

@ -16,6 +16,7 @@ export default defineConfig({
layout: {
title: 'YOONE',
},
esbuildMinifyIIFE: true,
define: {
UMI_APP_API_URL,
},
@ -100,6 +101,18 @@ export default defineConfig({
path: '/site/shop/:siteId/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',
},
],
},
{
@ -119,6 +132,11 @@ export default defineConfig({
path: '/customer/list',
component: './Customer/List',
},
{
name: '数据分析列表',
path: '/customer/statistic',
component: './Customer/Statistic',
},
],
},
{

View File

@ -47,4 +47,4 @@
"prettier-plugin-packagejson": "^2.4.3",
"typescript": "^5.7.3"
}
}
}

View File

@ -0,0 +1,38 @@
import React from 'react';
interface AddressProps {
address: {
address_1?: string;
address_2?: string;
city?: string;
state?: string;
postcode?: string;
country?: string;
phone?: string;
};
style?: React.CSSProperties;
}
const Address: React.FC<AddressProps> = ({ address, style }) => {
if (!address) {
return <span>-</span>;
}
const { address_1, address_2, city, state, postcode, country, phone } =
address;
return (
<div style={{ fontSize: 12, ...style }}>
<div>
{address_1} {address_2}
</div>
<div>
{city}, {state}, {postcode}
</div>
<div>{country}</div>
<div>{phone}</div>
</div>
);
};
export default Address;

View File

@ -23,7 +23,7 @@ import {
message,
} from 'antd';
import React, { useEffect, useState } from 'react';
import { attributes } from '../Attribute/consts';
import { notAttributes } from '../Product/Attribute/consts';
const { Sider, Content } = Layout;
@ -114,7 +114,9 @@ const CategoryPage: React.FC = () => {
// Fetch all dicts and filter those that are allowed attributes
try {
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
const existingDictIds = new Set(
categoryAttributes.map((ca: any) => ca.dict.id),

View File

@ -0,0 +1,255 @@
import { ordercontrollerGetorders } from '@/servers/api/order';
import { siteapicontrollerGetorders } from '@/servers/api/siteApi';
import {
App,
Col,
Modal,
Row,
Spin,
Statistic,
Table,
Tag,
Typography,
} from 'antd';
import dayjs from 'dayjs';
import { useState } from 'react';
const { Text, Title } = Typography;
interface HistoryOrdersProps {
customer: API.UnifiedCustomerDTO;
siteId?: number;
}
interface OrderStats {
totalOrders: number;
totalAmount: number;
yooneOrders: number;
yooneAmount: number;
}
const HistoryOrders: React.FC<HistoryOrdersProps> = ({ customer, siteId }) => {
const { message } = App.useApp();
const [modalVisible, setModalVisible] = useState(false);
const [orders, setOrders] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [stats, setStats] = useState<OrderStats>({
totalOrders: 0,
totalAmount: 0,
yooneOrders: 0,
yooneAmount: 0,
});
// 计算订单统计信息
const calculateStats = (orders: any[]) => {
let totalOrders = 0;
let totalAmount = 0;
let yooneOrders = 0;
let yooneAmount = 0;
orders.forEach((order) => {
totalOrders++;
// total是字符串需要转换为数字
const orderTotal = parseFloat(order.total || '0');
totalAmount += orderTotal;
// 检查订单中是否包含yoone商品
let hasYoone = false;
let orderYooneAmount = 0;
// 优先使用line_items如果没有则使用items
const items = order.line_items || order.items || [];
if (Array.isArray(items)) {
items.forEach((item: any) => {
// 检查商品名称或SKU是否包含yoone不区分大小写
const itemName = (item.name || '').toLowerCase();
const sku = (item.sku || '').toLowerCase();
if (itemName.includes('yoone') || sku.includes('yoone')) {
hasYoone = true;
const itemTotal = parseFloat(item.total || item.price || '0');
orderYooneAmount += itemTotal;
}
});
}
if (hasYoone) {
yooneOrders++;
yooneAmount += orderYooneAmount;
}
});
return {
totalOrders,
totalAmount,
yooneOrders,
yooneAmount,
};
};
// 获取客户订单数据
const fetchOrders = async () => {
setLoading(true);
try {
const response = await ordercontrollerGetorders({
customer_email: customer.email,
});
if (response) {
const orderList = response.items || [];
setOrders(orderList);
const calculatedStats = calculateStats(orderList);
setStats(calculatedStats);
} else {
message.error('获取订单数据失败');
}
} catch (error) {
console.error('获取订单失败:', error);
message.error('获取订单失败');
} finally {
setLoading(false);
}
};
// 打开弹框时获取数据
const handleOpenModal = () => {
setModalVisible(true);
fetchOrders();
};
// 订单表格列配置
const orderColumns = [
{
title: '订单号',
dataIndex: 'externalOrderId',
key: 'externalOrderId',
width: 120,
},
{
title: '订单状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (status: string) => {
const statusMap: Record<string, string> = {
pending: '待处理',
processing: '处理中',
'on-hold': '等待中',
completed: '已完成',
cancelled: '已取消',
refunded: '已退款',
failed: '失败',
};
return <Tag color="blue">{statusMap[status] || status}</Tag>;
},
},
{
title: '订单金额',
dataIndex: 'total',
key: 'total',
width: 100,
render: (total: string, record: any) => (
<Text>
{record.currency_symbol || '$'}
{parseFloat(total || '0').toFixed(2)}
</Text>
),
},
{
title: '创建时间',
dataIndex: 'date_created',
key: 'date_created',
width: 140,
render: (date: string) => (
<Text>{date ? dayjs(date).format('YYYY-MM-DD HH:mm') : '-'}</Text>
),
},
{
title: '包含Yoone',
key: 'hasYoone',
width: 80,
render: (_: any, record: any) => {
let hasYoone = false;
const items = record.line_items || record.items || [];
if (Array.isArray(items)) {
hasYoone = items.some((item: any) => {
const itemName = (item.name || '').toLowerCase();
const sku = (item.sku || '').toLowerCase();
return itemName.includes('yoone') || sku.includes('yoone');
});
}
return hasYoone ? <Tag color="green"></Tag> : <Tag></Tag>;
},
},
];
return (
<>
<a onClick={handleOpenModal}></a>
<Modal
title={`${customer.fullname || customer.email} 的历史订单`}
open={modalVisible}
onCancel={() => setModalVisible(false)}
footer={null}
width={1000}
>
<Spin spinning={loading}>
{/* 统计信息 */}
<Row gutter={16} style={{ marginBottom: 24 }}>
<Col span={6}>
<Statistic
title="总订单数"
value={stats.totalOrders}
prefix="#"
/>
</Col>
<Col span={6}>
<Statistic
title="总金额"
value={stats.totalAmount}
precision={2}
prefix="$"
/>
</Col>
<Col span={6}>
<Statistic
title="Yoone订单数"
value={stats.yooneOrders}
prefix="#"
/>
</Col>
<Col span={6}>
<Statistic
title="Yoone金额"
value={stats.yooneAmount}
precision={2}
prefix="$"
/>
</Col>
</Row>
{/* 订单列表 */}
<Title level={4} style={{ marginTop: 24 }}>
</Title>
<Table
columns={orderColumns}
dataSource={orders}
rowKey="id"
pagination={{
pageSize: 10,
showSizeChanger: true,
showTotal: (total) => `${total}`,
}}
scroll={{ x: 800 }}
/>
</Spin>
</Modal>
</>
);
};
export default HistoryOrders;

View File

@ -1,11 +1,12 @@
import { HistoryOrder } from '@/pages/Statistics/Order';
import {
customercontrollerAddtag,
customercontrollerDeltag,
customercontrollerGetcustomerlist,
customercontrollerGettags,
customercontrollerSetrate,
customercontrollerSynccustomers,
} from '@/servers/api/customer';
import { sitecontrollerAll } from '@/servers/api/site';
import {
ActionType,
ModalForm,
@ -14,97 +15,227 @@ import {
ProFormSelect,
ProTable,
} from '@ant-design/pro-components';
import { App, Button, Rate, Space, Tag } from 'antd';
import dayjs from 'dayjs';
import { useRef, useState } from 'react';
import { App, Avatar, Button, Rate, Space, Tag, Tooltip } from 'antd';
import { useEffect, useRef, useState } from 'react';
import HistoryOrders from './HistoryOrders';
const ListPage: React.FC = () => {
// 地址格式化函数
const formatAddress = (address: any) => {
if (!address) return '-';
if (typeof address === 'string') {
try {
address = JSON.parse(address);
} catch (e) {
return address;
}
}
const {
first_name,
last_name,
company,
address_1,
address_2,
city,
state,
postcode,
country,
phone: addressPhone,
email: addressEmail,
} = address;
const parts = [];
// 姓名
const fullName = [first_name, last_name].filter(Boolean).join(' ');
if (fullName) parts.push(fullName);
// 公司
if (company) parts.push(company);
// 地址行
if (address_1) parts.push(address_1);
if (address_2) parts.push(address_2);
// 城市、州、邮编
const locationParts = [city, state, postcode].filter(Boolean).join(', ');
if (locationParts) parts.push(locationParts);
// 国家
if (country) parts.push(country);
// 联系方式
if (addressPhone) parts.push(`电话: ${addressPhone}`);
if (addressEmail) parts.push(`邮箱: ${addressEmail}`);
return parts.join(', ');
};
// 地址卡片组件
const AddressCell: React.FC<{ address: any; title: string }> = ({
address,
title,
}) => {
const formattedAddress = formatAddress(address);
if (formattedAddress === '-') {
return <span>-</span>;
}
return (
<Tooltip
title={
<div style={{ maxWidth: 300, whiteSpace: 'pre-line' }}>
<strong>{title}:</strong>
<br />
{formattedAddress}
</div>
}
placement="topLeft"
>
<div
style={{
maxWidth: 200,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
cursor: 'pointer',
}}
>
{formattedAddress}
</div>
</Tooltip>
);
};
const CustomerList: React.FC = () => {
const actionRef = useRef<ActionType>();
const { message } = App.useApp();
const columns: ProColumns[] = [
const [syncModalVisible, setSyncModalVisible] = useState(false);
const [syncLoading, setSyncLoading] = useState(false);
const [sites, setSites] = useState<any[]>([]); // 添加站点数据状态
// 获取站点数据
const fetchSites = async () => {
try {
const { data, success } = await sitecontrollerAll();
if (success) {
setSites(data || []);
}
} catch (error) {
console.error('获取站点数据失败:', error);
}
};
// 根据站点ID获取站点名称
const getSiteName = (siteId: number | undefined | null) => {
if (!siteId) return '-';
if (typeof siteId === 'string') {
return siteId;
}
const site = sites.find((s) => s.id === siteId);
console.log(`site`, site);
return site ? site.name : String(siteId);
};
// 组件加载时获取站点数据
useEffect(() => {
fetchSites();
}, []);
const columns: ProColumns<API.UnifiedCustomerDTO>[] = [
{
title: 'ID',
dataIndex: 'id',
hideInSearch: true,
},
{
title: '站点',
dataIndex: 'site_id',
valueType: 'select',
request: async () => {
try {
const { data, success } = await sitecontrollerAll();
if (success && data) {
return data.map((site: any) => ({
label: site.name,
value: site.id,
}));
}
return [];
} catch (error) {
console.error('获取站点列表失败:', error);
return [];
}
},
render: (siteId: any) => {
return <span>{getSiteName(siteId) || '-'}</span>;
},
},
{
title: '头像',
dataIndex: 'avatar',
hideInSearch: true,
width: 60,
render: (_, record) => (
<Avatar
src={record.avatar}
size="small"
style={{ backgroundColor: '#1890ff' }}
>
{!record.avatar && record.fullname?.charAt(0)?.toUpperCase()}
</Avatar>
),
},
{
title: '姓名',
dataIndex: 'fullname',
sorter: true,
render: (_, record) => {
return (
record.fullname ||
`${record.first_name || ''} ${record.last_name || ''}`.trim() ||
record.username ||
'-'
);
},
},
{
title: '用户名',
dataIndex: 'username',
hideInSearch: true,
render: (_, record) => {
if (record.billing.first_name || record.billing.last_name)
return record.billing.first_name + ' ' + record.billing.last_name;
return record.shipping.first_name + ' ' + record.shipping.last_name;
},
},
{
title: '邮箱',
dataIndex: 'email',
copyable: true,
},
{
title: '客户编号',
dataIndex: 'customerId',
render: (_, record) => {
if (!record.customerId) return '-';
return String(record.customerId).padStart(6, 0);
},
sorter: true,
},
{
title: '首单时间',
dataIndex: 'first_purchase_date',
valueType: 'dateMonth',
sorter: true,
render: (_, record) =>
record.first_purchase_date
? dayjs(record.first_purchase_date).format('YYYY-MM-DD HH:mm:ss')
: '-',
// search: {
// transform: (value: string) => {
// return { month: value };
// },
// },
},
{
title: '尾单时间',
title: '电话',
dataIndex: 'phone',
hideInSearch: true,
dataIndex: 'last_purchase_date',
valueType: 'dateTime',
sorter: true,
copyable: true,
},
{
title: '订单数',
dataIndex: 'orders',
title: '账单地址',
dataIndex: 'billing',
hideInSearch: true,
sorter: true,
},
{
title: '金额',
dataIndex: 'total',
hideInSearch: true,
sorter: true,
},
{
title: 'YOONE订单数',
dataIndex: 'yoone_orders',
hideInSearch: true,
sorter: true,
},
{
title: 'YOONE金额',
dataIndex: 'yoone_total',
hideInSearch: true,
sorter: true,
},
{
title: '等级',
hideInSearch: true,
render: (_, record) => {
if (!record.yoone_orders || !record.yoone_total) return '-';
if (Number(record.yoone_orders) === 1 && Number(record.yoone_total) > 0)
return 'B';
return '-';
},
},
{
title: '评星',
dataIndex: 'rate',
width: 200,
render: (billing) => <AddressCell address={billing} title="账单地址" />,
},
{
title: '物流地址',
dataIndex: 'shipping',
hideInSearch: true,
width: 200,
render: (shipping) => <AddressCell address={shipping} title="物流地址" />,
},
{
title: '评分',
dataIndex: 'rate',
width: 120,
hideInSearch: true,
render: (_, record) => {
return (
<Rate
@ -112,46 +243,32 @@ const ListPage: React.FC = () => {
try {
const { success, message: msg } =
await customercontrollerSetrate({
id: record.customerId,
id: record.id,
rate: val,
});
if (success) {
message.success(msg);
actionRef.current?.reload();
}
} catch (e) {
message.error(e.message);
} catch (e: any) {
message.error(e?.message || '设置评分失败');
}
}}
value={record.rate}
value={record.raw?.rate || 0}
allowHalf
/>
);
},
},
{
title: 'phone',
dataIndex: 'phone',
hideInSearch: true,
render: (_, record) => record?.billing.phone || record?.shipping.phone,
},
{
title: 'state',
dataIndex: 'state',
render: (_, record) => record?.billing.state || record?.shipping.state,
},
{
title: 'city',
dataIndex: 'city',
hideInSearch: true,
render: (_, record) => record?.billing.city || record?.shipping.city,
},
{
title: '标签',
dataIndex: 'tags',
hideInSearch: true,
render: (_, record) => {
const tags = record.raw?.tags || [];
return (
<Space>
{(record.tags || []).map((tag) => {
<Space size={[0, 8]} wrap>
{tags.map((tag: string) => {
return (
<Tag
key={tag}
@ -162,8 +279,14 @@ const ListPage: React.FC = () => {
email: record.email,
tag,
});
return false;
if (!success) {
message.error(msg);
return false;
}
actionRef.current?.reload();
return true;
}}
style={{ marginBottom: 4 }}
>
{tag}
</Tag>
@ -173,31 +296,46 @@ const ListPage: React.FC = () => {
);
},
},
{
title: '创建时间',
dataIndex: 'site_created_at',
valueType: 'dateTime',
hideInSearch: true,
sorter: true,
width: 140,
},
{
title: '更新时间',
dataIndex: 'site_created_at',
valueType: 'dateTime',
hideInSearch: true,
sorter: true,
width: 140,
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
fixed: 'right',
width: 120,
render: (_, record) => {
return (
<Space>
<Space direction="vertical" size="small">
<AddTag
email={record.email}
tags={record.tags}
tableRef={actionRef}
/>
<HistoryOrder
email={record.email}
tags={record.tags}
email={record.email || ''}
tags={record.raw?.tags || []}
tableRef={actionRef}
/>
{/* 订单 */}
<HistoryOrders customer={record} siteId={record.raw?.site_id} />
</Space>
);
},
},
];
return (
<PageContainer ghost>
<PageContainer header={{ title: '客户列表' }}>
<ProTable
scroll={{ x: 'max-content' }}
headerTitle="查询表格"
@ -207,7 +345,11 @@ const ListPage: React.FC = () => {
const key = Object.keys(sorter)[0];
const { data, success } = await customercontrollerGetcustomerlist({
...params,
...(key ? { sorterKey: key, sorterValue: sorter[key] } : {}),
current: params.current?.toString(),
pageSize: params.pageSize?.toString(),
...(key
? { sorterKey: key, sorterValue: sorter[key] as string }
: {}),
});
return {
@ -217,12 +359,37 @@ const ListPage: React.FC = () => {
};
}}
columns={columns}
search={{
labelWidth: 'auto',
span: 6,
}}
pagination={{
pageSize: 20,
showSizeChanger: true,
showTotal: (total, range) =>
`${range[0]}-${range[1]} 条/总共 ${total}`,
}}
toolBarRender={() => [
<Button
key="sync"
type="primary"
onClick={() => setSyncModalVisible(true)}
>
</Button>,
// 这里可以添加导出、导入等功能按钮
]}
/>
<SyncCustomersModal
visible={syncModalVisible}
onClose={() => setSyncModalVisible(false)}
tableRef={actionRef}
/>
</PageContainer>
);
};
export const AddTag: React.FC<{
const AddTag: React.FC<{
email: string;
tags?: string[];
tableRef: React.MutableRefObject<ActionType | undefined>;
@ -233,7 +400,11 @@ export const AddTag: React.FC<{
return (
<ModalForm
title={`修改标签 - ${email}`}
trigger={<Button></Button>}
trigger={
<Button type="link" size="small">
</Button>
}
width={800}
modalProps={{
destroyOnHidden: true,
@ -250,16 +421,16 @@ export const AddTag: React.FC<{
if (!success) return [];
setTagList(tags || []);
return data
.filter((tag) => {
.filter((tag: string) => {
return !(tags || []).includes(tag);
})
.map((tag) => ({ label: tag, value: tag }));
.map((tag: string) => ({ label: tag, value: tag }));
}}
fieldProps={{
value: tagList, // 当前值
onChange: async (newValue) => {
const added = newValue.filter((x) => !tagList.includes(x));
const removed = tagList.filter((x) => !newValue.includes(x));
const added = newValue.filter((x) => !(tags || []).includes(x));
const removed = (tags || []).filter((x) => !newValue.includes(x));
if (added.length) {
const { success, message: msg } = await customercontrollerAddtag({
@ -282,7 +453,6 @@ export const AddTag: React.FC<{
}
}
tableRef?.current?.reload();
setTagList(newValue);
},
}}
@ -291,4 +461,132 @@ export const AddTag: React.FC<{
);
};
export default ListPage;
const SyncCustomersModal: React.FC<{
visible: boolean;
onClose: () => void;
tableRef: React.MutableRefObject<ActionType | undefined>;
}> = ({ visible, onClose, tableRef }) => {
const { message } = App.useApp();
const [sites, setSites] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
// 获取站点列表
useEffect(() => {
if (visible) {
setLoading(true);
sitecontrollerAll()
.then((res: any) => {
setSites(res?.data || []);
})
.catch((error: any) => {
message.error('获取站点列表失败: ' + (error.message || '未知错误'));
})
.finally(() => {
setLoading(false);
});
}
}, [visible]);
const handleSync = async (values: { siteId: number }) => {
try {
setLoading(true);
const {
success,
message: msg,
data,
} = await customercontrollerSynccustomers({
siteId: values.siteId,
});
if (success) {
// 显示详细的同步结果
const result = data || {};
const {
total = 0,
synced = 0,
created = 0,
updated = 0,
errors = [],
} = result;
let resultMessage = `同步完成!共处理 ${total} 个客户:`;
if (created > 0) resultMessage += ` 新建 ${created}`;
if (updated > 0) resultMessage += ` 更新 ${updated}`;
if (synced > 0) resultMessage += ` 同步成功 ${synced}`;
if (errors.length > 0) resultMessage += ` 失败 ${errors.length}`;
if (errors.length > 0) {
// 如果有错误,显示警告消息
message.warning({
content: (
<div>
<div>{resultMessage}</div>
<div style={{ marginTop: 8, fontSize: 12, color: '#faad14' }}>
{errors
.slice(0, 3)
.map((err: any) => err.email || err.error)
.join(', ')}
{errors.length > 3 && `${errors.length - 3} 个错误...`}
</div>
</div>
),
duration: 8,
key: 'sync-result',
});
} else {
// 完全成功
message.success({
content: resultMessage,
duration: 4,
key: 'sync-result',
});
}
onClose();
// 刷新表格数据
tableRef.current?.reload();
return true;
} else {
message.error(msg || '同步失败');
return false;
}
} catch (error: any) {
message.error('同步失败: ' + (error.message || '未知错误'));
return false;
} finally {
setLoading(false);
}
};
return (
<ModalForm
title="同步客户数据"
open={visible}
onOpenChange={(open) => !open && onClose()}
modalProps={{
destroyOnClose: true,
confirmLoading: loading,
}}
onFinish={handleSync}
>
<ProFormSelect
name="siteId"
label="选择站点"
placeholder="请选择要同步的站点"
options={sites.map((site) => ({
label: site.name,
value: site.id,
}))}
rules={[{ required: true, message: '请选择站点' }]}
fieldProps={{
loading: loading,
}}
/>
</ModalForm>
);
};
export { AddTag };
export default CustomerList;

View File

@ -0,0 +1,294 @@
import { HistoryOrder } from '@/pages/Statistics/Order';
import {
customercontrollerAddtag,
customercontrollerDeltag,
customercontrollerGetcustomerlist,
customercontrollerGettags,
customercontrollerSetrate,
} from '@/servers/api/customer';
import {
ActionType,
ModalForm,
PageContainer,
ProColumns,
ProFormSelect,
ProTable,
} from '@ant-design/pro-components';
import { App, Button, Rate, Space, Tag } from 'antd';
import dayjs from 'dayjs';
import { useRef, useState } from 'react';
const ListPage: React.FC = () => {
const actionRef = useRef<ActionType>();
const { message } = App.useApp();
const columns: ProColumns[] = [
{
title: '用户名',
dataIndex: 'username',
hideInSearch: true,
render: (_, record) => {
if (record.billing.first_name || record.billing.last_name)
return record.billing.first_name + ' ' + record.billing.last_name;
return record.shipping.first_name + ' ' + record.shipping.last_name;
},
},
{
title: '邮箱',
dataIndex: 'email',
},
{
title: '客户编号',
dataIndex: 'customerId',
render: (_, record) => {
if (!record.customerId) return '-';
return String(record.customerId).padStart(6, 0);
},
sorter: true,
},
{
title: '首单时间',
dataIndex: 'first_purchase_date',
valueType: 'dateMonth',
sorter: true,
render: (_, record) =>
record.first_purchase_date
? dayjs(record.first_purchase_date).format('YYYY-MM-DD HH:mm:ss')
: '-',
// search: {
// transform: (value: string) => {
// return { month: value };
// },
// },
},
{
title: '尾单时间',
hideInSearch: true,
dataIndex: 'last_purchase_date',
valueType: 'dateTime',
sorter: true,
},
{
title: '订单数',
dataIndex: 'orders',
hideInSearch: true,
sorter: true,
},
{
title: '金额',
dataIndex: 'total',
hideInSearch: true,
sorter: true,
},
{
title: 'YOONE订单数',
dataIndex: 'yoone_orders',
hideInSearch: true,
sorter: true,
},
{
title: 'YOONE金额',
dataIndex: 'yoone_total',
hideInSearch: true,
sorter: true,
},
{
title: '等级',
hideInSearch: true,
render: (_, record) => {
if (!record.yoone_orders || !record.yoone_total) return '-';
if (Number(record.yoone_orders) === 1 && Number(record.yoone_total) > 0)
return 'B';
return '-';
},
},
{
title: '评星',
dataIndex: 'rate',
width: 200,
render: (_, record) => {
return (
<Rate
onChange={async (val) => {
try {
const { success, message: msg } =
await customercontrollerSetrate({
id: record.customerId,
rate: val,
});
if (success) {
message.success(msg);
actionRef.current?.reload();
}
} catch (e) {
message.error(e.message);
}
}}
value={record.rate}
/>
);
},
},
{
title: 'phone',
dataIndex: 'phone',
hideInSearch: true,
render: (_, record) => record?.billing.phone || record?.shipping.phone,
},
{
title: 'state',
dataIndex: 'state',
render: (_, record) => record?.billing.state || record?.shipping.state,
},
{
title: 'city',
dataIndex: 'city',
hideInSearch: true,
render: (_, record) => record?.billing.city || record?.shipping.city,
},
{
title: '标签',
dataIndex: 'tags',
render: (_, record) => {
return (
<Space>
{(record.tags || []).map((tag) => {
return (
<Tag
key={tag}
closable
onClose={async () => {
const { success, message: msg } =
await customercontrollerDeltag({
email: record.email,
tag,
});
return false;
}}
>
{tag}
</Tag>
);
})}
</Space>
);
},
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
fixed: 'right',
render: (_, record) => {
return (
<Space>
<AddTag
email={record.email}
tags={record.tags}
tableRef={actionRef}
/>
<HistoryOrder
email={record.email}
tags={record.tags}
tableRef={actionRef}
/>
</Space>
);
},
},
];
return (
<PageContainer ghost>
<ProTable
scroll={{ x: 'max-content' }}
headerTitle="查询表格"
actionRef={actionRef}
rowKey="id"
request={async (params, sorter) => {
const key = Object.keys(sorter)[0];
const { data, success } = await customercontrollerGetcustomerlist({
...params,
...(key ? { sorterKey: key, sorterValue: sorter[key] } : {}),
});
return {
total: data?.total || 0,
data: data?.items || [],
success,
};
}}
columns={columns}
/>
</PageContainer>
);
};
export const AddTag: React.FC<{
email: string;
tags?: string[];
tableRef: React.MutableRefObject<ActionType | undefined>;
}> = ({ email, tags, tableRef }) => {
const { message } = App.useApp();
const [tagList, setTagList] = useState<string[]>([]);
return (
<ModalForm
title={`修改标签 - ${email}`}
trigger={<Button></Button>}
width={800}
modalProps={{
destroyOnHidden: true,
}}
submitter={false}
>
<ProFormSelect
mode="tags"
allowClear
name="tag"
label="标签"
request={async () => {
const { data, success } = await customercontrollerGettags();
if (!success) return [];
setTagList(tags || []);
return data
.filter((tag) => {
return !(tags || []).includes(tag);
})
.map((tag) => ({ label: tag, value: tag }));
}}
fieldProps={{
value: tagList, // 当前值
onChange: async (newValue) => {
const added = newValue.filter((x) => !tagList.includes(x));
const removed = tagList.filter((x) => !newValue.includes(x));
if (added.length) {
const { success, message: msg } = await customercontrollerAddtag({
email,
tag: added[0],
});
if (!success) {
message.error(msg);
return;
}
}
if (removed.length) {
const { success, message: msg } = await customercontrollerDeltag({
email,
tag: removed[0],
});
if (!success) {
message.error(msg);
return;
}
}
tableRef?.current?.reload();
setTagList(newValue);
},
}}
></ProFormSelect>
</ModalForm>
);
};
export default ListPage;

View File

@ -1,10 +1,10 @@
import * as dictApi from '@/servers/api/dict';
import { UploadOutlined } from '@ant-design/icons';
import {
ActionType,
PageContainer,
ProTable,
} from '@ant-design/pro-components';
import { request } from '@umijs/max';
import {
Button,
Form,
@ -26,14 +26,22 @@ const DictPage: React.FC = () => {
const [loadingDicts, setLoadingDicts] = useState(false);
const [searchText, setSearchText] = useState('');
const [selectedDict, setSelectedDict] = useState<any>(null);
// 添加字典 modal 状态
const [isAddDictModalVisible, setIsAddDictModalVisible] = useState(false);
const [editingDict, setEditingDict] = useState<any>(null);
const [newDictName, setNewDictName] = useState('');
const [newDictTitle, setNewDictTitle] = useState('');
const [addDictName, setAddDictName] = useState('');
const [addDictTitle, setAddDictTitle] = useState('');
// 编辑字典 modal 状态
const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false);
const [editDictData, setEditDictData] = useState<any>(null);
// 右侧字典项列表的状态
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
const [editingDictItem, setEditingDictItem] = useState<any>(null);
const [isAddDictItemModalVisible, setIsAddDictItemModalVisible] =
useState(false);
const [isEditDictItemModalVisible, setIsEditDictItemModalVisible] =
useState(false);
const [editDictItemData, setEditDictItemData] = useState<any>(null);
const [dictItemForm] = Form.useForm();
const actionRef = useRef<ActionType>();
@ -41,7 +49,7 @@ const DictPage: React.FC = () => {
const fetchDicts = async (name = '') => {
setLoadingDicts(true);
try {
const res = await request('/dict/list', { params: { name } });
const res = await dictApi.dictcontrollerGetdicts({ name });
setDicts(res);
} catch (error) {
message.error('获取字典列表失败');
@ -55,60 +63,66 @@ const DictPage: React.FC = () => {
fetchDicts(value);
};
// 添加或编辑字典
// 添加字典
const handleAddDict = async () => {
const values = { name: newDictName, title: newDictTitle };
const values = { name: addDictName, title: addDictTitle };
try {
if (editingDict) {
await request(`/dict/${editingDict.id}`, {
method: 'PUT',
data: values,
});
message.success('更新成功');
} else {
await request('/dict', { method: 'POST', data: values });
message.success('添加成功');
}
await dictApi.dictcontrollerCreatedict(values);
message.success('添加成功');
setIsAddDictModalVisible(false);
setEditingDict(null);
setNewDictName('');
setNewDictTitle('');
setAddDictName('');
setAddDictTitle('');
fetchDicts(); // 重新获取列表
} 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) => {
try {
await request(`/dict/${id}`, { method: 'DELETE' });
const result = await dictApi.dictcontrollerDeletedict({ id });
if (!result.success) {
throw new Error(result.message || '删除失败');
}
message.success('删除成功');
fetchDicts();
if (selectedDict?.id === id) {
setSelectedDict(null);
}
} catch (error) {
message.error('删除失败');
} catch (error: any) {
message.error(`删除失败,原因为:${error.message}`);
}
};
// 编辑字典
const handleEditDict = (record: any) => {
setEditingDict(record);
setNewDictName(record.name);
setNewDictTitle(record.title);
setIsAddDictModalVisible(true);
// 打开编辑字典 modal
const openEditDictModal = (record: any) => {
setEditDictData(record);
setIsEditDictModalVisible(true);
};
// 下载字典导入模板
const handleDownloadDictTemplate = async () => {
try {
// 使用 request 函数获取带认证的文件数据
const response = await request('/dict/template', {
method: 'GET',
responseType: 'blob', // 指定响应类型为 blob
skipErrorHandler: true, // 跳过默认错误处理,自己处理错误
// 使用 dictApi.dictcontrollerDownloaddicttemplate 获取字典模板
const response = await dictApi.dictcontrollerDownloaddicttemplate({
responseType: 'blob',
skipErrorHandler: true,
});
// 创建 blob 对象和下载链接
@ -130,46 +144,82 @@ const DictPage: React.FC = () => {
// 添加字典项
const handleAddDictItem = () => {
setEditingDictItem(null);
dictItemForm.resetFields();
setIsDictItemModalVisible(true);
setIsAddDictItemModalVisible(true);
};
// 编辑字典项
const handleEditDictItem = (record: any) => {
setEditingDictItem(record);
setEditDictItemData(record);
dictItemForm.setFieldsValue(record);
setIsDictItemModalVisible(true);
setIsEditDictItemModalVisible(true);
};
// 删除字典项
const handleDeleteDictItem = async (id: number) => {
try {
await request(`/dict/item/${id}`, { method: 'DELETE' });
const result = await dictApi.dictcontrollerDeletedictitem({ id });
if (!result.success) {
throw new Error(result.message || '删除失败');
}
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 url = editingDictItem
? `/dict/item/${editingDictItem.id}`
: '/dict/item';
const method = editingDictItem ? 'PUT' : 'POST';
const data = editingDictItem
? { ...values }
: { ...values, dict: { id: selectedDict.id } };
// 添加字典项表单提交
const handleAddDictItemFormSubmit = async (values: any) => {
try {
await request(url, { method, data });
message.success(editingDictItem ? '更新成功' : '添加成功');
setIsDictItemModalVisible(false);
actionRef.current?.reload();
} catch (error) {
message.error(editingDictItem ? '更新失败' : '添加失败');
const result = await dictApi.dictcontrollerCreatedictitem({
...values,
dictId: selectedDict.id,
});
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,9 +232,8 @@ const DictPage: React.FC = () => {
try {
// 获取当前字典的所有数据
const response = await request('/dict/items', {
method: 'GET',
params: { dictId: selectedDict.id },
const response = await dictApi.dictcontrollerGetdictitems({
dictId: selectedDict.id,
});
if (!response || response.length === 0) {
@ -257,7 +306,7 @@ const DictPage: React.FC = () => {
<Button
type="link"
size="small"
onClick={() => handleEditDict(record)}
onClick={() => openEditDictModal(record)}
>
</Button>
@ -331,7 +380,7 @@ const DictPage: React.FC = () => {
<PageContainer>
<Layout style={{ background: '#fff' }}>
<Sider
width={240}
width={300}
style={{
background: '#fff',
padding: '8px',
@ -347,17 +396,17 @@ const DictPage: React.FC = () => {
enterButton
allowClear
/>
<Button
type="primary"
onClick={() => setIsAddDictModalVisible(true)}
size="small"
>
</Button>
<Space size="small">
<Button
type="primary"
onClick={() => setIsAddDictModalVisible(true)}
size="small"
>
</Button>
<Upload
name="file"
action="/dict/import"
action="/api/dict/import"
showUploadList={false}
onChange={(info) => {
if (info.file.status === 'done') {
@ -401,53 +450,6 @@ const DictPage: React.FC = () => {
</Sider>
<Content style={{ padding: '8px' }}>
<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
columns={dictItemColumns}
request={async (params) => {
@ -459,15 +461,24 @@ const DictPage: React.FC = () => {
};
}
const { name, title } = params;
const res = await request('/dict/items', {
params: {
dictId: selectedDict?.id,
name,
title,
},
const res = await dictApi.dictcontrollerGetdictitems({
dictId: selectedDict?.id,
name,
title,
});
// 适配新的响应格式,检查是否有 successResponse 包裹
if (res && res.success !== undefined) {
return {
data: res.data || [],
success: res.success,
total: res.data?.length || 0,
};
}
// 兼容旧的响应格式(直接返回数组)
return {
data: res,
data: res || [],
success: true,
};
}}
@ -476,25 +487,86 @@ const DictPage: React.FC = () => {
layout: 'vertical',
}}
pagination={false}
options={false}
options={{
reload: true,
density: true,
setting: true,
}}
size="small"
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>
</Content>
</Layout>
{/* 添加字典项 Modal */}
<Modal
title={editingDictItem ? '编辑字典项' : '添加字典项'}
open={isDictItemModalVisible}
title={`添加字典项 - ${selectedDict?.title || '未选择字典'}`}
open={isAddDictItemModalVisible}
onOk={() => dictItemForm.submit()}
onCancel={() => setIsDictItemModalVisible(false)}
destroyOnClose
onCancel={() => setIsAddDictItemModalVisible(false)}
>
<Form
form={dictItemForm}
layout="vertical"
onFinish={handleDictItemFormSubmit}
onFinish={handleAddDictItemFormSubmit}
>
<Form.Item
label="名称"
@ -525,25 +597,106 @@ const DictPage: React.FC = () => {
</Form>
</Modal>
{/* 编辑字典项 Modal */}
<Modal
title={editingDict ? '编辑字典' : '添加新字典'}
visible={isAddDictModalVisible}
title="编辑字典项"
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}
onCancel={() => setIsAddDictModalVisible(false)}
onCancel={() => {
setIsAddDictModalVisible(false);
setAddDictName('');
setAddDictTitle('');
}}
>
<Form layout="vertical">
<Form.Item label="字典名称">
<Input
placeholder="字典名称 (e.g., brand)"
value={newDictName}
onChange={(e) => setNewDictName(e.target.value)}
value={addDictName}
onChange={(e) => setAddDictName(e.target.value)}
/>
</Form.Item>
<Form.Item label="字典标题">
<Input
placeholder="字典标题 (e.g., 品牌)"
value={newDictTitle}
onChange={(e) => setNewDictTitle(e.target.value)}
value={addDictTitle}
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>

View File

@ -26,7 +26,6 @@ import {
import { productcontrollerSearchproducts } from '@/servers/api/product';
import { sitecontrollerAll } from '@/servers/api/site';
import { stockcontrollerGetallstockpoints } from '@/servers/api/stock';
import { wpproductcontrollerSearchproducts } from '@/servers/api/wpProduct';
import { formatShipmentState, formatSource } from '@/utils/format';
import {
CodeSandboxOutlined,
@ -453,7 +452,6 @@ const ListPage: React.FC = () => {
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
}}
rowClassName={(record) => {
return record.id === activeLine
? styles['selected-line-order-protable']
@ -482,9 +480,9 @@ const ListPage: React.FC = () => {
}}
tableRef={actionRef}
/>,
// <Button
// type="primary"
// disabled={selectedRowKeys.length === 0}
// <Button
// type="primary"
// disabled={selectedRowKeys.length === 0}
// onClick={handleBatchExport}
// >
// 批量导出
@ -493,7 +491,6 @@ const ListPage: React.FC = () => {
title="批量导出"
description="确认导出选中的订单吗?"
onConfirm={async () => {
try {
const { success, message: errMsg } =
await ordercontrollerExportorder({
@ -508,15 +505,16 @@ const ListPage: React.FC = () => {
} catch (error: any) {
message.error(error?.message || '导出失败');
}
}}
>
<Button type="primary" disabled={selectedRowKeys.length === 0} ghost>
<Button
type="primary"
disabled={selectedRowKeys.length === 0}
ghost
>
</Button>
</Popconfirm>
</Popconfirm>,
]}
request={async ({ date, ...param }: any) => {
if (param.status === 'all') {
@ -605,33 +603,33 @@ const Detail: React.FC<{
)
? []
: [
<Divider type="vertical" />,
<Button
type="primary"
onClick={async () => {
try {
if (!record.siteId || !record.externalOrderId) {
message.error('站点ID或外部订单ID不存在');
return;
<Divider type="vertical" />,
<Button
type="primary"
onClick={async () => {
try {
if (!record.siteId || !record.externalOrderId) {
message.error('站点ID或外部订单ID不存在');
return;
}
const { success, message: errMsg } =
await ordercontrollerSyncorderbyid({
siteId: record.siteId,
orderId: record.externalOrderId,
});
if (!success) {
throw new Error(errMsg);
}
message.success('同步成功');
tableRef.current?.reload();
} catch (error: any) {
message.error(error?.message || '同步失败');
}
const { success, message: errMsg } =
await ordercontrollerSyncorderbyid({
siteId: record.siteId,
orderId: record.externalOrderId,
});
if (!success) {
throw new Error(errMsg);
}
message.success('同步成功');
tableRef.current?.reload();
} catch (error: any) {
message.error(error?.message || '同步失败');
}
}}
>
</Button>,
]),
}}
>
</Button>,
]),
// ...(['processing', 'pending_reshipment'].includes(record.orderStatus)
// ? [
// <Divider type="vertical" />,
@ -650,152 +648,152 @@ const Detail: React.FC<{
'pending_refund',
].includes(record.orderStatus)
? [
<Divider type="vertical" />,
<Popconfirm
title="转至售后"
description="确认转至售后?"
onConfirm={async () => {
try {
if (!record.id) {
message.error('订单ID不存在');
return;
<Divider type="vertical" />,
<Popconfirm
title="转至售后"
description="确认转至售后?"
onConfirm={async () => {
try {
if (!record.id) {
message.error('订单ID不存在');
return;
}
const { success, message: errMsg } =
await ordercontrollerChangestatus(
{
id: record.id,
},
{
status: 'after_sale_pending',
},
);
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
const { success, message: errMsg } =
await ordercontrollerChangestatus(
{
id: record.id,
},
{
status: 'after_sale_pending',
},
);
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
]
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
]
: []),
...(record.orderStatus === 'after_sale_pending'
? [
<Divider type="vertical" />,
<Popconfirm
title="转至取消"
description="确认转至取消?"
onConfirm={async () => {
try {
if (!record.id) {
message.error('订单ID不存在');
return;
}
const { success, message: errMsg } =
await ordercontrollerCancelorder({
id: record.id,
});
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
<Divider type="vertical" />,
<Popconfirm
title="转至退款"
description="确认转至退款?"
onConfirm={async () => {
try {
if (!record.id) {
message.error('订单ID不存在');
return;
}
const { success, message: errMsg } =
await ordercontrollerRefundorder({
id: record.id,
});
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
退
</Button>
</Popconfirm>,
<Divider type="vertical" />,
<Popconfirm
title="转至完成"
description="确认转至完成?"
onConfirm={async () => {
try {
if (!record.id) {
message.error('订单ID不存在');
return;
}
const { success, message: errMsg } =
await ordercontrollerCompletedorder({
id: record.id,
});
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
<Divider type="vertical" />,
<Popconfirm
title="转至待补发"
description="确认转至待补发?"
onConfirm={async () => {
try {
const { success, message: errMsg } =
await ordercontrollerChangestatus(
{
<Divider type="vertical" />,
<Popconfirm
title="转至取消"
description="确认转至取消?"
onConfirm={async () => {
try {
if (!record.id) {
message.error('订单ID不存在');
return;
}
const { success, message: errMsg } =
await ordercontrollerCancelorder({
id: record.id,
},
{
status: 'pending_reshipment',
},
);
if (!success) {
throw new Error(errMsg);
});
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
]
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
<Divider type="vertical" />,
<Popconfirm
title="转至退款"
description="确认转至退款?"
onConfirm={async () => {
try {
if (!record.id) {
message.error('订单ID不存在');
return;
}
const { success, message: errMsg } =
await ordercontrollerRefundorder({
id: record.id,
});
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
退
</Button>
</Popconfirm>,
<Divider type="vertical" />,
<Popconfirm
title="转至完成"
description="确认转至完成?"
onConfirm={async () => {
try {
if (!record.id) {
message.error('订单ID不存在');
return;
}
const { success, message: errMsg } =
await ordercontrollerCompletedorder({
id: record.id,
});
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
<Divider type="vertical" />,
<Popconfirm
title="转至待补发"
description="确认转至待补发?"
onConfirm={async () => {
try {
const { success, message: errMsg } =
await ordercontrollerChangestatus(
{
id: record.id,
},
{
status: 'pending_reshipment',
},
);
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
]
: []),
]}
>
@ -1057,31 +1055,31 @@ const Detail: React.FC<{
}
actions={
v.state === 'waiting-for-scheduling' ||
v.state === 'waiting-for-transit'
v.state === 'waiting-for-transit'
? [
<Popconfirm
title="取消运单"
description="确认取消运单?"
onConfirm={async () => {
try {
const { success, message: errMsg } =
await logisticscontrollerDelshipment({
id: v.id,
});
if (!success) {
throw new Error(errMsg);
<Popconfirm
title="取消运单"
description="确认取消运单?"
onConfirm={async () => {
try {
const { success, message: errMsg } =
await logisticscontrollerDelshipment({
id: v.id,
});
if (!success) {
throw new Error(errMsg);
}
tableRef.current?.reload();
initRequest();
} catch (error: any) {
message.error(error.message);
}
tableRef.current?.reload();
initRequest();
} catch (error: any) {
message.error(error.message);
}
}}
>
<DeleteFilled />
</Popconfirm>,
]
}}
>
<DeleteFilled />
</Popconfirm>,
]
: []
}
>
@ -1469,16 +1467,16 @@ const Shipping: React.FC<{
<ProFormList
label="发货产品"
name="sales"
// rules={[
// {
// required: true,
// message: '至少需要一个商品',
// validator: (_, value) =>
// value && value.length > 0
// ? Promise.resolve()
// : Promise.reject('至少需要一个商品'),
// },
// ]}
// rules={[
// {
// required: true,
// message: '至少需要一个商品',
// validator: (_, value) =>
// value && value.length > 0
// ? Promise.resolve()
// : Promise.reject('至少需要一个商品'),
// },
// ]}
>
<ProForm.Group>
<ProFormSelect
@ -1903,7 +1901,7 @@ const Shipping: React.FC<{
name="description"
placeholder="请输入描述"
width="lg"
// rules={[{ required: true, message: '请输入描述' }]}
// rules={[{ required: true, message: '请输入描述' }]}
/>
</ProForm.Group>
</ProFormList>

View File

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

View File

@ -20,7 +20,7 @@ import React, { useEffect, useRef, useState } from 'react';
const { Sider, Content } = Layout;
import { attributes } from './consts';
import { notAttributes } from './consts';
const AttributePage: React.FC = () => {
// 左侧字典列表状态
@ -41,11 +41,14 @@ const AttributePage: React.FC = () => {
setLoadingDicts(true);
try {
const res = await request('/dict/list', { params: { title } });
// 条件判断,过滤只保留 allowedDictNames 中的字典
const filtered = (res || []).filter((d: any) => attributes.has(d?.name));
// 条件判断,确保res是数组再进行过滤
const dataList = Array.isArray(res) ? res : res?.data || [];
const filtered = dataList.filter((d: any) => !notAttributes.has(d?.name));
setDicts(filtered);
} catch (error) {
console.error('获取字典列表失败:', error);
message.error('获取字典列表失败');
setDicts([]);
}
setLoadingDicts(false);
};
@ -114,16 +117,23 @@ const AttributePage: React.FC = () => {
return;
}
if (selectedDict?.id) {
const list = await request('/dict/items', {
params: {
dictId: selectedDict.id,
},
});
const exists =
Array.isArray(list) && list.some((it: any) => it.id === itemId);
if (exists) {
message.error('删除失败');
} else {
try {
const list = await request('/dict/items', {
params: {
dictId: selectedDict.id,
},
});
// 确保list是数组再进行some操作
const dataList = Array.isArray(list) ? list : list?.data || [];
const exists = dataList.some((it: any) => it.id === itemId);
if (exists) {
message.error('删除失败');
} else {
message.success('删除成功');
actionRef.current?.reload();
}
} catch (error) {
console.error('验证删除结果失败:', error);
message.success('删除成功');
actionRef.current?.reload();
}
@ -245,24 +255,44 @@ const AttributePage: React.FC = () => {
};
}
const { name, title } = params;
const res = await request('/dict/items', {
params: {
dictId: selectedDict.id,
name,
title,
},
});
return {
data: res,
success: true,
};
try {
const res = await request('/dict/items', {
params: {
dictId: selectedDict.id,
name,
title,
},
});
// 确保返回的是数组
const data = Array.isArray(res) ? res : res?.data || [];
return {
data: data,
success: true,
};
} catch (error) {
console.error('获取字典项失败:', error);
return {
data: [],
success: false,
};
}
}}
rowKey="id"
search={{
layout: 'vertical',
}}
pagination={false}
options={false}
options={{
reload: true,
density: false,
setting: {
draggable: true,
checkable: true,
checkedReset: false,
},
search: false,
fullScreen: false,
}}
size="small"
key={selectedDict?.id}
headerTitle={

View File

@ -23,7 +23,7 @@ import {
message,
} from 'antd';
import React, { useEffect, useState } from 'react';
import { attributes } from '../Attribute/consts';
import { notAttributes } from '../Attribute/consts';
const { Sider, Content } = Layout;
@ -116,7 +116,7 @@ const CategoryPage: React.FC = () => {
const res = await request('/dict/list');
// Defensive check for response structure: handle both raw array and wrapped response
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
const existingDictIds = new Set(
categoryAttributes.map((ca: any) => ca.dictId),
@ -244,7 +244,10 @@ const CategoryPage: React.FC = () => {
</Popconfirm>,
]}
>
<List.Item.Meta title={item.title} description={item.name} />
<List.Item.Meta
title={`${item.title}(${item.titleCN ?? '-'})`}
description={item.name}
/>
</List.Item>
)}
/>
@ -310,16 +313,18 @@ const CategoryPage: React.FC = () => {
onFinish={handleCategorySubmit}
layout="vertical"
>
<Form.Item name="title" label="标题" rules={[{ required: true }]}>
<Form.Item name="title" label="标题">
<Input />
</Form.Item>
<Form.Item
name="name"
label="标识 (Code)"
rules={[{ required: true }]}
>
<Form.Item name="titleCN" label="中文名称">
<Input />
</Form.Item>
<Form.Item name="name" label="标识 (Code)">
<Input />
</Form.Item>
<Form.Item name="sort" label="排序">
<Input type="number" />
</Form.Item>
</Form>
</Modal>

View File

@ -10,10 +10,6 @@ import {
} from '@/servers/api/product';
import { sitecontrollerAll } from '@/servers/api/site';
import { siteapicontrollerGetproducts } from '@/servers/api/siteApi';
import {
wpproductcontrollerBatchsynctosite,
wpproductcontrollerSynctoproduct,
} from '@/servers/api/wpProduct';
import {
ActionType,
ModalForm,
@ -462,9 +458,9 @@ const List: React.FC = () => {
dataIndex: 'siteSkus',
render: (_, record) => (
<>
{record.siteSkus?.map((code, index) => (
{record.siteSkus?.map((siteSku, index) => (
<Tag key={index} color="cyan">
{code}
{siteSku.siteSku}
</Tag>
))}
</>
@ -609,55 +605,6 @@ const List: React.FC = () => {
toolBarRender={() => [
// 新建按钮
<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 拦截器和鉴权)
<Upload
name="file"
@ -733,8 +680,57 @@ const List: React.FC = () => {
}
}}
>
<Button>CSV</Button>
<Button></Button>
</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) => {
let sortField = undefined;

View File

@ -112,12 +112,18 @@ const PermutationPage: React.FC = () => {
// 2. Fetch Attribute Values (Dict Items)
const valuesMap: Record<string, any[]> = {};
for (const attr of attrs) {
const dictId = attr.dict?.id || attr.dictId;
if (dictId) {
const itemsRes = await request('/dict/items', {
params: { dictId },
});
valuesMap[attr.name] = itemsRes || [];
// 使用属性中直接包含的items而不是额外请求
if (attr.items && Array.isArray(attr.items)) {
valuesMap[attr.name] = attr.items;
} else {
// 如果没有items尝试通过dictId获取
const dictId = attr.dict?.id || attr.dictId;
if (dictId) {
const itemsRes = await request('/dict/items', {
params: { dictId },
});
valuesMap[attr.name] = itemsRes || [];
}
}
}
setAttributeValues(valuesMap);
@ -206,7 +212,7 @@ const PermutationPage: React.FC = () => {
const valB = b[attr.name]?.name || '';
return valA.localeCompare(valB);
},
filters: attributeValues[attr.name]?.map((v: any) => ({
filters: attributeValues?.[attr.name]?.map?.((v: any) => ({
text: v.name,
value: v.name,
})),

View File

@ -9,7 +9,16 @@ import {
ProTable,
} from '@ant-design/pro-components';
import { request } from '@umijs/max';
import { Button, Card, Spin, Tag, message, Select, Progress, Modal } from 'antd';
import {
Button,
Card,
message,
Modal,
Progress,
Select,
Spin,
Tag,
} from 'antd';
import React, { useEffect, useRef, useState } from 'react';
import EditForm from '../List/EditForm';
@ -89,7 +98,13 @@ const ProductSyncPage: React.FC = () => {
const [batchSyncModalVisible, setBatchSyncModalVisible] = useState(false);
const [syncProgress, setSyncProgress] = useState(0);
const [syncing, setSyncing] = useState(false);
const [syncResults, setSyncResults] = useState<{ success: number; failed: number; errors: string[] }>({ success: 0, failed: 0, errors: [] });
const [syncResults, setSyncResults] = useState<{
success: number;
failed: number;
errors: string[];
}>({ success: 0, failed: 0, errors: [] });
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [selectedRows, setSelectedRows] = useState<ProductWithWP[]>([]);
// 初始化数据:获取站点和所有 WP 产品
useEffect(() => {
@ -197,41 +212,51 @@ const ProductSyncPage: React.FC = () => {
};
// 批量同步产品到指定站点
const batchSyncProducts = async () => {
const batchSyncProducts = async (productsToSync?: ProductWithWP[]) => {
if (!selectedSiteId) {
message.error('请选择要同步到的站点');
return;
}
const targetSite = sites.find(site => site.id === selectedSiteId);
const targetSite = sites.find((site) => site.id === selectedSiteId);
if (!targetSite) {
message.error('选择的站点不存在');
return;
}
// 如果没有传入产品列表,则使用选中的产品
let products = productsToSync || selectedRows;
// 如果既没有传入产品也没有选中产品,则同步所有产品
if (!products || products.length === 0) {
try {
const { data, success } = await productcontrollerGetproductlist({
current: 1,
pageSize: 10000, // 获取所有产品
} as any);
if (!success || !data?.items) {
message.error('获取产品列表失败');
return;
}
products = data.items as ProductWithWP[];
} catch (error) {
message.error('获取产品列表失败');
return;
}
}
setSyncing(true);
setSyncProgress(0);
setSyncResults({ success: 0, failed: 0, errors: [] });
const totalProducts = products.length;
let processed = 0;
let successCount = 0;
let failedCount = 0;
const errors: string[] = [];
try {
// 获取所有产品
const { data, success } = await productcontrollerGetproductlist({
current: 1,
pageSize: 10000, // 获取所有产品
} as any);
if (!success || !data?.items) {
message.error('获取产品列表失败');
return;
}
const products = data.items as ProductWithWP[];
const totalProducts = products.length;
let processed = 0;
let successCount = 0;
let failedCount = 0;
const errors: string[] = [];
// 逐个同步产品
for (const product of products) {
try {
@ -239,7 +264,10 @@ const ProductSyncPage: React.FC = () => {
let siteProductSku = '';
if (product.siteSkus && product.siteSkus.length > 0) {
const siteSkuInfo = product.siteSkus.find((sku: any) => {
return sku.siteSku && sku.siteSku.includes(targetSite.skuPrefix || targetSite.name);
return (
sku.siteSku &&
sku.siteSku.includes(targetSite.skuPrefix || targetSite.name)
);
});
if (siteSkuInfo) {
siteProductSku = siteSkuInfo.siteSku;
@ -247,15 +275,15 @@ const ProductSyncPage: React.FC = () => {
}
// 如果没有找到实际的siteSku则根据模板生成
const expectedSku = siteProductSku || (
skuTemplate
? renderSku(skuTemplate, { site: targetSite, product })
: `${targetSite.skuPrefix || ''}-${product.sku}`
);
const expectedSku =
siteProductSku ||
(skuTemplate
? renderSiteSku(skuTemplate, { site: targetSite, product })
: `${targetSite.skuPrefix || ''}-${product.sku}`);
// 检查是否已存在
const existingProduct = wpProductMap.get(expectedSku);
// 准备同步数据
const syncData = {
name: product.name,
@ -272,10 +300,13 @@ const ProductSyncPage: React.FC = () => {
let res;
if (existingProduct?.externalProductId) {
// 更新现有产品
res = await request(`/site-api/${targetSite.id}/products/${existingProduct.externalProductId}`, {
method: 'PUT',
data: syncData,
});
res = await request(
`/site-api/${targetSite.id}/products/${existingProduct.externalProductId}`,
{
method: 'PUT',
data: syncData,
},
);
} else {
// 创建新产品
res = await request(`/site-api/${targetSite.id}/products`, {
@ -300,7 +331,6 @@ const ProductSyncPage: React.FC = () => {
failedCount++;
errors.push(`产品 ${product.sku}: ${res.message || '同步失败'}`);
}
} catch (error: any) {
failedCount++;
errors.push(`产品 ${product.sku}: ${error.message || '未知错误'}`);
@ -311,16 +341,17 @@ const ProductSyncPage: React.FC = () => {
}
setSyncResults({ success: successCount, failed: failedCount, errors });
if (failedCount === 0) {
message.success(`批量同步完成,成功同步 ${successCount} 个产品`);
} else {
message.warning(`批量同步完成,成功 ${successCount} 个,失败 ${failedCount}`);
message.warning(
`批量同步完成,成功 ${successCount} 个,失败 ${failedCount}`,
);
}
// 刷新表格
actionRef.current?.reload();
} catch (error: any) {
message.error('批量同步失败: ' + (error.message || error.toString()));
} finally {
@ -329,7 +360,7 @@ const ProductSyncPage: React.FC = () => {
};
// 简单的模板渲染函数
const renderSku = (template: string, data: any) => {
const renderSiteSku = (template: string, data: any) => {
if (!template) return '';
// 支持 <%= it.path %> (Eta) 和 {{ path }} (Mustache/Handlebars)
return template.replace(
@ -453,7 +484,9 @@ const ProductSyncPage: React.FC = () => {
const siteSkuInfo = record.siteSkus.find((sku: any) => {
// 这里假设可以根据站点名称或其他标识来匹配
// 如果需要更精确的匹配逻辑,可以根据实际需求调整
return sku.siteSku && sku.siteSku.includes(site.skuPrefix || site.name);
return (
sku.siteSku && sku.siteSku.includes(site.skuPrefix || site.name)
);
});
if (siteSkuInfo) {
siteProductSku = siteSkuInfo.siteSku;
@ -461,18 +494,21 @@ const ProductSyncPage: React.FC = () => {
}
// 如果没有找到实际的siteSku则根据模板或默认规则生成期望的SKU
const expectedSku = siteProductSku || (
skuTemplate
? renderSku(skuTemplate, { site, product: record })
: `${site.skuPrefix || ''}-${record.sku}`
);
const expectedSku =
siteProductSku ||
(skuTemplate
? renderSiteSku(skuTemplate, { site, product: record })
: `${site.skuPrefix || ''}-${record.sku}`);
// 尝试用确定的SKU获取WP产品
let wpProduct = wpProductMap.get(expectedSku);
// 如果根据实际SKU没找到再尝试用模板生成的SKU查找
if (!wpProduct && siteProductSku && skuTemplate) {
const templateSku = renderSku(skuTemplate, { site, product: record });
const templateSku = renderSiteSku(skuTemplate, {
site,
product: record,
});
wpProduct = wpProductMap.get(templateSku);
}
@ -490,12 +526,12 @@ const ProductSyncPage: React.FC = () => {
return await syncProductToSite(values, record, site);
}}
initialValues={{
sku: siteProductSku || (
skuTemplate
? renderSku(skuTemplate, { site, product: record })
: `${site.skuPrefix || ''}-${record.sku}`
),
}}
sku:
siteProductSku ||
(skuTemplate
? renderSiteSku(skuTemplate, { site, product: record })
: `${site.skuPrefix || ''}-${record.sku}`),
}}
>
<ProFormText
name="sku"
@ -587,36 +623,46 @@ const ProductSyncPage: React.FC = () => {
}
return (
<Card
title="商品同步状态"
className="product-sync-card"
extra={
<div style={{ display: 'flex', gap: 8 }}>
<Select
style={{ width: 200 }}
placeholder="选择目标站点"
value={selectedSiteId}
onChange={setSelectedSiteId}
options={sites.map(site => ({
label: site.name,
value: site.id,
}))}
/>
<Button
type="primary"
icon={<SyncOutlined />}
onClick={() => setBatchSyncModalVisible(true)}
disabled={!selectedSiteId || sites.length === 0}
>
</Button>
</div>
}
>
<Card title="商品同步状态" className="product-sync-card">
<ProTable<ProductWithWP>
columns={generateColumns()}
actionRef={actionRef}
rowKey="id"
rowSelection={{
selectedRowKeys,
onChange: (keys, rows) => {
setSelectedRowKeys(keys);
setSelectedRows(rows);
},
}}
toolBarRender={() => [
<Select
key="site-select"
style={{ width: 200 }}
placeholder="选择目标站点"
value={selectedSiteId}
onChange={setSelectedSiteId}
options={sites.map((site) => ({
label: site.name,
value: site.id,
}))}
/>,
<Button
key="batch-sync"
type="primary"
icon={<SyncOutlined />}
onClick={() => {
if (!selectedSiteId) {
message.warning('请先选择目标站点');
return;
}
setBatchSyncModalVisible(true);
}}
disabled={!selectedSiteId || sites.length === 0}
>
</Button>,
]}
request={async (params, sort, filter) => {
// 调用本地获取产品列表 API
const { data, success } = await productcontrollerGetproductlist({
@ -650,7 +696,7 @@ const ProductSyncPage: React.FC = () => {
}}
dateFormatter="string"
/>
{/* 批量同步模态框 */}
<Modal
title="批量同步产品"
@ -661,10 +707,19 @@ const ProductSyncPage: React.FC = () => {
maskClosable={!syncing}
>
<div style={{ marginBottom: 16 }}>
<p><strong>{sites.find(s => s.id === selectedSiteId)?.name}</strong></p>
<p></p>
<p>
<strong>{sites.find((s) => s.id === selectedSiteId)?.name}</strong>
</p>
{selectedRows.length > 0 ? (
<p>
<strong>{selectedRows.length}</strong>
</p>
) : (
<p></p>
)}
</div>
{syncing && (
<div style={{ marginBottom: 16 }}>
<div style={{ marginBottom: 8 }}></div>
@ -674,32 +729,37 @@ const ProductSyncPage: React.FC = () => {
</div>
</div>
)}
{syncResults.errors.length > 0 && (
<div style={{ marginBottom: 16, maxHeight: 200, overflow: 'auto' }}>
<div style={{ marginBottom: 8, color: '#ff4d4f' }}></div>
{syncResults.errors.slice(0, 10).map((error, index) => (
<div key={index} style={{ fontSize: 12, color: '#666', marginBottom: 4 }}>
<div
key={index}
style={{ fontSize: 12, color: '#666', marginBottom: 4 }}
>
{error}
</div>
))}
{syncResults.errors.length > 10 && (
<div style={{ fontSize: 12, color: '#999' }}>... {syncResults.errors.length - 10} </div>
<div style={{ fontSize: 12, color: '#999' }}>
... {syncResults.errors.length - 10}
</div>
)}
</div>
)}
<div style={{ textAlign: 'right' }}>
<Button
<Button
onClick={() => setBatchSyncModalVisible(false)}
disabled={syncing}
style={{ marginRight: 8 }}
>
</Button>
<Button
type="primary"
onClick={batchSyncProducts}
<Button
type="primary"
onClick={() => batchSyncProducts()}
loading={syncing}
disabled={syncing}
>

View File

@ -6,7 +6,6 @@ import {
sitecontrollerUpdate,
} from '@/servers/api/site';
import { subscriptioncontrollerSync } from '@/servers/api/subscription';
import { wpproductcontrollerSyncproducts } from '@/servers/api/wpProduct';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { Button, message, notification, Popconfirm, Space, Tag } from 'antd';
import React, { useRef, useState } from 'react';

View File

@ -1,3 +1,4 @@
import Address from '@/components/Address';
import {
DeleteFilled,
EditOutlined,
@ -187,27 +188,29 @@ const CustomerPage: React.FC = () => {
hideInSearch: true,
render: (_, record) => {
const { billing } = record;
if (!billing) return '-';
return (
<div style={{ fontSize: 12 }}>
<div>
{billing.address_1} {billing.address_2}
</div>
<div>
{billing.city}, {billing.state}, {billing.postcode}
</div>
<div>{billing.country}</div>
<div>{billing.phone}</div>
</div>
);
return <Address address={billing} />;
},
},
{
title: '注册时间',
title: '物流地址',
dataIndex: 'shipping',
hideInSearch: true,
render: (shipping) => {
return <Address address={shipping} />;
},
},
{
title: '创建时间',
dataIndex: 'date_created',
valueType: 'dateTime',
hideInSearch: true,
},
{
title: '更新时间',
dataIndex: 'date_modified',
valueType: 'dateTime',
hideInSearch: true,
},
{
title: '操作',
valueType: 'option',

View File

@ -2,7 +2,8 @@ import { sitecontrollerAll } from '@/servers/api/site';
import { EditOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { Outlet, history, request, useLocation, useParams } from '@umijs/max';
import { Button, Card, Col, Menu, Row, Select, Spin, message } from 'antd';
import { Button, Col, Menu, Row, Select, Spin, message } from 'antd';
import Sider from 'antd/es/layout/Sider';
import React, { useEffect, useState } from 'react';
import type { SiteItem } from '../List/index';
import EditSiteForm from './EditSiteForm';
@ -90,22 +91,11 @@ const ShopLayout: React.FC = () => {
};
return (
<PageContainer
header={{ title: null, breadcrumb: undefined }}
contentStyle={{
padding: 0,
}}
>
<PageContainer header={{ title: null, breadcrumb: undefined }}>
<Row gutter={16} style={{ height: 'calc(100vh - 100px)' }}>
<Col span={4} style={{ height: '100%' }}>
<Card
bodyStyle={{
padding: '10px 0',
height: '100%',
display: 'flex',
flexDirection: 'column',
}}
style={{ height: '100%', overflow: 'hidden' }}
<Sider
style={{ background: 'white', height: '100%', overflow: 'hidden' }}
>
<div style={{ padding: '0 10px 16px' }}>
<div
@ -157,10 +147,12 @@ const ShopLayout: React.FC = () => {
{ key: 'media', label: '媒体管理' },
{ key: 'customers', label: '客户管理' },
{ key: 'reviews', label: '评论管理' },
{ key: 'webhooks', label: 'Webhooks管理' },
{ key: 'links', label: '链接管理' },
]}
/>
</div>
</Card>
</Sider>
</Col>
<Col span={20} style={{ height: '100%', overflowY: 'auto' }}>
{siteId ? <Outlet /> : <div></div>}

View File

@ -0,0 +1,99 @@
import { LinkOutlined } from '@ant-design/icons';
import { PageHeader } from '@ant-design/pro-layout';
import { request, useParams } from '@umijs/max';
import { App, Button, Card, List } from 'antd';
import React, { useEffect, useState } from 'react';
// 定义链接项的类型
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,54 @@ const OrdersPage: React.FC = () => {
>
<Button type="text" icon={<EllipsisOutlined />} />
</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>
{record.status === 'completed' && (
<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
title="确定删除订单?"
onConfirm={async () => {

View File

@ -188,9 +188,9 @@ const ProductsPage: React.FC = () => {
<strong>:</strong> {record.erpProduct.category.name}
</div>
)}
<div>
<strong>:</strong> {record.erpProduct.stock_quantity ?? '-'}
</div>
<div>
<strong>:</strong> {record.erpProduct.stock_quantity ?? '-'}
</div>
</div>
);
}

View File

@ -1,4 +1,12 @@
import React from 'react';
import {
siteapicontrollerCreatereview,
siteapicontrollerUpdatereview,
} from '@/servers/api/siteApi';
import { Form, Input, InputNumber, Modal, Select, message } from 'antd';
import React, { useEffect } from 'react';
const { TextArea } = Input;
const { Option } = Select;
interface ReviewFormProps {
open: boolean;
@ -15,19 +23,161 @@ const ReviewForm: React.FC<ReviewFormProps> = ({
onClose,
onSuccess,
}) => {
// // 这是一个临时的占位符组件
// // 你可以在这里实现表单逻辑
if (!open) {
return null;
}
const [form] = Form.useForm();
// 当编辑状态改变时,重置表单数据
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 (
<div>
<h2>Review Form</h2>
<p>Site ID: {siteId}</p>
<p>Editing: {editing ? 'Yes' : 'No'}</p>
<button onClick={onClose}>Close</button>
</div>
<Modal
title={editing ? '编辑评论' : '新建评论'}
open={open}
onCancel={onClose}
onOk={() => form.submit()}
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>
);
};

View File

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

View File

@ -0,0 +1,332 @@
import {
siteapicontrollerCreatewebhook,
siteapicontrollerDeletewebhook,
siteapicontrollerGetwebhooks,
siteapicontrollerUpdatewebhook,
} from '@/servers/api/siteApi';
import {
ActionType,
ProCard,
ProColumns,
ProTable,
} from '@ant-design/pro-components';
import { useParams } from '@umijs/max';
import {
Button,
Form,
Input,
message,
Modal,
Popconfirm,
Select,
Space,
} 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

@ -2,133 +2,85 @@ import {
templatecontrollerCreatetemplate,
templatecontrollerDeletetemplate,
templatecontrollerGettemplatelist,
templatecontrollerRendertemplate,
templatecontrollerRendertemplatedirect,
templatecontrollerUpdatetemplate,
} from '@/servers/api/template';
import { BugOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
import { EditOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
import {
ActionType,
DrawerForm,
ModalForm,
PageContainer,
ProColumns,
ProForm,
ProFormText,
ProFormTextArea,
ProTable,
} from '@ant-design/pro-components';
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 ReactJson from 'react-json-view';
const TestModal: React.FC<{
visible: boolean;
onClose: () => void;
template: API.Template | null;
}> = ({ visible, onClose, template }) => {
const { message } = App.useApp();
const [inputData, setInputData] = useState<Record<string, any>>({});
// 自定义hook用于处理模板预览逻辑
const useTemplatePreview = () => {
const [renderedResult, setRenderedResult] = useState<string>('');
const [previewData, setPreviewData] = useState<any>(null);
// 当模板改变时,重置数据
// 防抖的预览效果
useEffect(() => {
if (visible && template) {
// 尝试解析模板中可能的变量作为初始数据(可选优化,这里先置空)
// 或者根据模板类型提供一些默认值
if (template.testData) {
try {
setInputData(JSON.parse(template.testData));
} catch (e) {
console.error('Failed to parse testData:', e);
setInputData({});
}
} else {
setInputData({});
}
setRenderedResult('');
if (!previewData || !previewData.value) {
setRenderedResult('请输入模板内容');
return;
}
}, [visible, template]);
// 监听 inputData 变化并调用渲染 API
useEffect(() => {
if (!visible || !template) return;
const timer = setTimeout(async () => {
let testData = {};
try {
const res = await templatecontrollerRendertemplate(
{ name: template.name || '' },
inputData,
);
if (previewData.testData) {
testData = JSON.parse(previewData.testData);
}
} catch (e) {
testData = {};
}
try {
// 使用新的直接渲染API传入模板内容和测试数据
const res = await templatecontrollerRendertemplatedirect({
template: previewData.value,
data: testData,
});
if (res.success) {
setRenderedResult(res.data as unknown as string);
} else {
setRenderedResult(`Error: ${res.message}`);
setRenderedResult(`错误: ${res.message}`);
}
} catch (error: any) {
setRenderedResult(`Error: ${error.message}`);
setRenderedResult(`错误: ${error.message}`);
}
}, 500); // 防抖 500ms
return () => clearTimeout(timer);
}, [inputData, visible, template]);
}, [previewData]);
return (
<ModalForm
title={`测试模板: ${template?.name || '未知模板'}`}
open={visible}
onOpenChange={(open) => !open && onClose()}
modalProps={{ destroyOnClose: true, onCancel: onClose }}
submitter={false} // 不需要提交按钮
width={800}
>
<div style={{ display: 'flex', gap: '20px' }}>
<div style={{ flex: 1 }}>
<Typography.Title level={5}> (JSON)</Typography.Title>
<Card bodyStyle={{ padding: 0, height: '300px', overflow: 'auto' }}>
<ReactJson
src={inputData}
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 handlePreview = (_changedValues: any, allValues: any) => {
setPreviewData(allValues);
};
// 手动刷新预览
const refreshPreview = (formValues: any) => {
setPreviewData(formValues);
};
return {
renderedResult,
handlePreview,
refreshPreview,
setPreviewData,
};
};
const List: React.FC = () => {
const actionRef = useRef<ActionType>();
const { message } = App.useApp();
const [testModalVisible, setTestModalVisible] = useState(false);
const [currentTemplate, setCurrentTemplate] = useState<API.Template | null>(
null,
);
const columns: ProColumns<API.Template>[] = [
{
@ -169,17 +121,7 @@ const List: React.FC = () => {
dataIndex: 'option',
valueType: 'option',
render: (_, record) => (
<>
<Button
type="link"
icon={<BugOutlined />}
onClick={() => {
setCurrentTemplate(record);
setTestModalVisible(true);
}}
>
</Button>
<Space>
<UpdateForm tableRef={actionRef} values={record} />
<Popconfirm
title="删除"
@ -198,7 +140,7 @@ const List: React.FC = () => {
</Button>
</Popconfirm>
</>
</Space>
),
},
];
@ -222,11 +164,6 @@ const List: React.FC = () => {
}}
columns={columns}
/>
<TestModal
visible={testModalVisible}
onClose={() => setTestModalVisible(false)}
template={currentTemplate}
/>
</PageContainer>
);
};
@ -235,9 +172,14 @@ const CreateForm: React.FC<{
tableRef: React.MutableRefObject<ActionType | undefined>;
}> = ({ tableRef }) => {
const { message } = App.useApp();
const [form] = ProForm.useForm();
const { renderedResult, handlePreview, refreshPreview } =
useTemplatePreview();
return (
<DrawerForm<API.CreateTemplateDTO>
title="新建"
form={form}
trigger={
<Button type="primary">
<PlusOutlined />
@ -247,7 +189,9 @@ const CreateForm: React.FC<{
autoFocusFirstInput
drawerProps={{
destroyOnHidden: true,
width: 1200, // 增加抽屉宽度以容纳调试面板
}}
onValuesChange={handlePreview}
onFinish={async (values) => {
try {
await templatecontrollerCreatetemplate(values);
@ -260,46 +204,101 @@ const CreateForm: React.FC<{
}
}}
>
<ProFormText
name="name"
label="模板名称"
placeholder="请输入名称"
rules={[{ required: true, message: '请输入名称' }]}
/>
<ProForm.Item
name="value"
label="值"
rules={[{ required: true, message: '请输入值' }]}
>
<Editor
height="500px"
defaultLanguage="html"
options={{
minimap: { enabled: false },
lineNumbers: 'on',
scrollBeyondLastLine: false,
automaticLayout: true,
}}
/>
</ProForm.Item>
<ProFormTextArea
name="testData"
label="测试数据 (JSON)"
placeholder="请输入JSON格式的测试数据"
rules={[
{
validator: (_, value) => {
if (!value) return Promise.resolve();
try {
JSON.parse(value);
return Promise.resolve();
} catch (e) {
return Promise.reject(new Error('请输入有效的JSON格式'));
}
},
},
]}
/>
<div style={{ display: 'flex', gap: '20px' }}>
<div style={{ flex: 1 }}>
<ProFormText
name="name"
label="模板名称"
placeholder="请输入名称"
rules={[{ required: true, message: '请输入名称' }]}
/>
<ProForm.Item
name="value"
label="模板内容"
rules={[{ required: true, message: '请输入模板内容' }]}
>
<Editor
height="400px"
defaultLanguage="html"
options={{
minimap: { enabled: false },
lineNumbers: 'on',
scrollBeyondLastLine: false,
automaticLayout: true,
}}
/>
</ProForm.Item>
<ProForm.Item
name="testData"
label="测试数据 (JSON)"
rules={[
{
validator: (_: any, value: any) => {
if (!value) return Promise.resolve();
try {
JSON.parse(value);
return Promise.resolve();
} 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>
);
};
@ -309,9 +308,25 @@ const UpdateForm: React.FC<{
values: API.Template;
}> = ({ tableRef, values: initialValues }) => {
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 (
<DrawerForm<API.UpdateTemplateDTO>
title="编辑"
form={form}
initialValues={initialValues}
trigger={
<Button type="primary">
@ -322,7 +337,9 @@ const UpdateForm: React.FC<{
autoFocusFirstInput
drawerProps={{
destroyOnHidden: true,
width: 1200, // 增加抽屉宽度以容纳调试面板
}}
onValuesChange={handlePreview}
onFinish={async (values) => {
if (!initialValues.id) return false;
try {
@ -339,46 +356,101 @@ const UpdateForm: React.FC<{
}
}}
>
<ProFormText
name="name"
label="模板名称"
placeholder="请输入名称"
rules={[{ required: true, message: '请输入名称' }]}
/>
<ProForm.Item
name="value"
label="值"
rules={[{ required: true, message: '请输入值' }]}
>
<Editor
height="500px"
defaultLanguage="html"
options={{
minimap: { enabled: false },
lineNumbers: 'on',
scrollBeyondLastLine: false,
automaticLayout: true,
}}
/>
</ProForm.Item>
<ProFormTextArea
name="testData"
label="测试数据 (JSON)"
placeholder="请输入JSON格式的测试数据"
rules={[
{
validator: (_, value) => {
if (!value) return Promise.resolve();
try {
JSON.parse(value);
return Promise.resolve();
} catch (e) {
return Promise.reject(new Error('请输入有效的JSON格式'));
}
},
},
]}
/>
<div style={{ display: 'flex', gap: '20px' }}>
<div style={{ flex: 1 }}>
<ProFormText
name="name"
label="模板名称"
placeholder="请输入名称"
rules={[{ required: true, message: '请输入名称' }]}
/>
<ProForm.Item
name="value"
label="模板内容"
rules={[{ required: true, message: '请输入模板内容' }]}
>
<Editor
height="400px"
defaultLanguage="html"
options={{
minimap: { enabled: false },
lineNumbers: 'on',
scrollBeyondLastLine: false,
automaticLayout: true,
}}
/>
</ProForm.Item>
<ProForm.Item
name="testData"
label="测试数据 (JSON)"
rules={[
{
validator: (_: any, value: any) => {
if (!value) return Promise.resolve();
try {
JSON.parse(value);
return Promise.resolve();
} 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>
);
};

View File

@ -221,6 +221,8 @@ const WpToolPage: React.FC = () => {
const [csvData, setCsvData] = useState<any[]>([]); // 解析后的 CSV 数据
const [processedData, setProcessedData] = useState<any[]>([]); // 处理后待下载的数据
const [isProcessing, setIsProcessing] = useState(false); // 是否正在处理中
const [isConfigLoading, setIsConfigLoading] = useState(false); // 是否正在加载配置
const [configLoadAttempts, setConfigLoadAttempts] = useState(0); // 配置加载重试次数
const [config, setConfig] = useState<TagConfig>({
// 动态配置
brands: [],
@ -237,22 +239,37 @@ const WpToolPage: React.FC = () => {
useEffect(() => {
const fetchAllConfigs = async () => {
try {
message.loading({
content: '正在加载字典配置...',
key: 'loading-config',
});
// 1. 获取所有字典列表以找到对应的 ID
const dictList = await request('/dict/list');
const dictListResponse = await request('/dict/list');
// 处理后端统一响应格式
const dictList = dictListResponse?.data || dictListResponse || [];
// 2. 根据字典名称获取字典项
const getItems = async (dictName: string) => {
const dict = dictList.find((d: any) => d.name === dictName);
if (!dict) {
console.warn(`Dictionary ${dictName} not found`);
try {
const dict = dictList.find((d: any) => d.name === dictName);
if (!dict) {
console.warn(`Dictionary ${dictName} not found`);
return [];
}
const response = await request('/dict/items', {
params: { dictId: dict.id },
});
// 处理后端统一响应格式,获取数据数组
const items = response?.data || response || [];
return items.map((item: any) => item.name);
} catch (error) {
console.error(`Failed to fetch items for ${dictName}:`, error);
return [];
}
const res = await request('/dict/items', {
params: { dictId: dict.id },
});
return res.map((item: any) => item.name);
};
// 3. 并行获取所有字典项
const [
brands,
fruitKeys,
@ -264,9 +281,9 @@ const WpToolPage: React.FC = () => {
categoryKeys,
] = await Promise.all([
getItems('brand'),
getItems('fruit'), // 假设字典名为 fruit
getItems('mint'), // 假设字典名为 mint
getItems('flavor'), // 假设字典名为 flavor
getItems('fruit'),
getItems('mint'),
getItems('flavor'),
getItems('strength'),
getItems('size'),
getItems('humidity'),
@ -283,11 +300,28 @@ const WpToolPage: React.FC = () => {
humidityKeys,
categoryKeys,
};
setConfig(newConfig);
form.setFieldsValue(newConfig);
message.success({ content: '字典配置加载成功', key: 'loading-config' });
// 显示加载结果统计
const totalItems =
brands.length +
fruitKeys.length +
mintKeys.length +
flavorKeys.length +
strengthKeys.length +
sizeKeys.length +
humidityKeys.length +
categoryKeys.length;
console.log(`字典配置加载完成: 共 ${totalItems} 个配置项`);
} catch (error) {
console.error('Failed to fetch configs:', error);
message.error('获取字典配置失败');
message.error({
content: '获取字典配置失败,请刷新页面重试',
key: 'loading-config',
});
}
};

View File

@ -47,6 +47,21 @@ export async function customercontrollerGetcustomerlist(
});
}
/** 此处后端没有提供注释 GET /customer/getcustomerstatisticlist */
export async function customercontrollerGetcustomerstatisticlist(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.customercontrollerGetcustomerstatisticlistParams,
options?: { [key: string]: any },
) {
return request<Record<string, any>>('/customer/getcustomerstatisticlist', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /customer/gettags */
export async function customercontrollerGettags(options?: {
[key: string]: any;
@ -71,3 +86,18 @@ export async function customercontrollerSetrate(
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /customer/sync */
export async function customercontrollerSynccustomers(
body: Record<string, any>,
options?: { [key: string]: any },
) {
return request<Record<string, any>>('/customer/sync', {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
data: body,
...(options || {}),
});
}

View File

@ -19,7 +19,6 @@ import * as subscription from './subscription';
import * as template from './template';
import * as user from './user';
import * as webhook from './webhook';
import * as wpProduct from './wpProduct';
export default {
area,
category,
@ -38,5 +37,4 @@ export default {
template,
user,
webhook,
wpProduct,
};

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 */
export async function ordercontrollerPengdingitems(
body: Record<string, any>,
@ -243,12 +229,17 @@ export async function ordercontrollerChangestatus(
export async function ordercontrollerSyncorder(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.ordercontrollerSyncorderParams,
body: Record<string, any>,
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
return request<API.BooleanRes>(`/order/syncOrder/${param0}`, {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
params: { ...queryParams },
data: body,
...(options || {}),
});
}

View File

@ -770,16 +770,6 @@ export async function productcontrollerSyncstocktoproduct(options?: {
});
}
/** 此处后端没有提供注释 GET /product/wp-products */
export async function productcontrollerGetwpproducts(options?: {
[key: string]: any;
}) {
return request<any>('/product/wp-products', {
method: 'GET',
...(options || {}),
});
}
/** 此处后端没有提供注释 PUT /productupdateNameCn/${param1}/${param0} */
export async function productcontrollerUpdatenamecn(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)

View File

@ -4,7 +4,7 @@ import { request } from 'umi';
/** 此处后端没有提供注释 GET /site/all */
export async function sitecontrollerAll(options?: { [key: string]: any }) {
return request<API.WpSitesResponse>('/site/all', {
return request<API.SitesResponse>('/site/all', {
method: 'GET',
...(options || {}),
});

View File

@ -17,8 +17,8 @@ export async function siteapicontrollerGetcustomers(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
},
@ -76,8 +76,8 @@ export async function siteapicontrollerExportcustomers(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
});
@ -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 */
export async function siteapicontrollerGetmedia(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
@ -115,8 +129,8 @@ export async function siteapicontrollerGetmedia(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
});
@ -198,8 +212,8 @@ export async function siteapicontrollerExportmedia(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
});
@ -218,8 +232,8 @@ export async function siteapicontrollerGetorders(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
});
@ -248,14 +262,36 @@ export async function siteapicontrollerCreateorder(
export async function siteapicontrollerBatchorders(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerBatchordersParams,
body: Record<string, any>,
body: API.BatchOperationDTO,
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
return request<Record<string, any>>(`/site-api/${param0}/orders/batch`, {
return request<API.BatchOperationResultDTO>(
`/site-api/${param0}/orders/batch`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
},
);
}
/** 此处后端没有提供注释 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': 'text/plain',
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
@ -276,8 +312,8 @@ export async function siteapicontrollerExportorders(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
});
@ -317,8 +353,8 @@ export async function siteapicontrollerGetproducts(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
},
@ -348,19 +384,22 @@ export async function siteapicontrollerCreateproduct(
export async function siteapicontrollerBatchproducts(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerBatchproductsParams,
body: Record<string, any>,
body: API.BatchOperationDTO,
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
return request<Record<string, any>>(`/site-api/${param0}/products/batch`, {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
return request<API.BatchOperationResultDTO>(
`/site-api/${param0}/products/batch`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
},
params: { ...queryParams },
data: body,
...(options || {}),
});
);
}
/** 此处后端没有提供注释 GET /site-api/${param0}/products/export */
@ -376,8 +415,8 @@ export async function siteapicontrollerExportproducts(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
});
@ -396,8 +435,8 @@ export async function siteapicontrollerExportproductsspecial(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
});
@ -459,8 +498,8 @@ export async function siteapicontrollerGetreviews(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
},
@ -501,8 +540,8 @@ export async function siteapicontrollerGetsubscriptions(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
},
@ -522,13 +561,52 @@ export async function siteapicontrollerExportsubscriptions(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
});
}
/** 此处后端没有提供注释 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'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(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} */
export async function siteapicontrollerGetcustomer(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
@ -600,8 +678,8 @@ export async function siteapicontrollerGetcustomerorders(
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
orderBy: undefined,
...queryParams['orderBy'],
},
...(options || {}),
},
@ -688,6 +766,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 */
export async function siteapicontrollerGetordernotes(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
@ -727,6 +827,28 @@ export async function siteapicontrollerCreateordernote(
);
}
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/ship */
export async function siteapicontrollerShiporder(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerShiporderParams,
body: API.ShipOrderDTO,
options?: { [key: string]: any },
) {
const { id: param0, siteId: param1, ...queryParams } = params;
return request<Record<string, any>>(
`/site-api/${param1}/orders/${param0}/ship`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
},
);
}
/** 此处后端没有提供注释 GET /site-api/${param1}/products/${param0} */
export async function siteapicontrollerGetproduct(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
@ -819,6 +941,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} */
export async function siteapicontrollerUpdatevariation(
// 叠加生成的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} */
export async function templatecontrollerRendertemplate(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)

File diff suppressed because it is too large Load Diff

View File

@ -1,269 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from 'umi';
/** 此处后端没有提供注释 DELETE /wp_product/${param0} */
export async function wpproductcontrollerDelete(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerDeleteParams,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<API.BooleanRes>(`/wp_product/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /wp_product/batch-sync-to-site/${param0} */
export async function wpproductcontrollerBatchsynctosite(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerBatchsynctositeParams,
body: API.BatchSyncProductsDTO,
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
return request<API.BooleanRes>(`/wp_product/batch-sync-to-site/${param0}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /wp_product/batch-update */
export async function wpproductcontrollerBatchupdateproducts(
body: API.BatchUpdateProductsDTO,
options?: { [key: string]: any },
) {
return request<API.BooleanRes>('/wp_product/batch-update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /wp_product/batch-update-tags */
export async function wpproductcontrollerBatchupdatetags(
body: API.BatchUpdateTagsDTO,
options?: { [key: string]: any },
) {
return request<API.BooleanRes>('/wp_product/batch-update-tags', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /wp_product/import/${param0} */
export async function wpproductcontrollerImportproducts(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerImportproductsParams,
body: {},
files?: File[],
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
const formData = new FormData();
if (files) {
files.forEach((f) => formData.append('files', f || ''));
}
Object.keys(body).forEach((ele) => {
const item = (body as any)[ele];
if (item !== undefined && item !== null) {
if (typeof item === 'object' && !(item instanceof File)) {
if (item instanceof Array) {
item.forEach((f) => formData.append(ele, f || ''));
} else {
formData.append(
ele,
new Blob([JSON.stringify(item)], { type: 'application/json' }),
);
}
} else {
formData.append(ele, item);
}
}
});
return request<API.BooleanRes>(`/wp_product/import/${param0}`, {
method: 'POST',
params: { ...queryParams },
data: formData,
requestType: 'form',
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /wp_product/list */
export async function wpproductcontrollerGetwpproducts(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerGetwpproductsParams,
options?: { [key: string]: any },
) {
return request<API.WpProductListRes>('/wp_product/list', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /wp_product/search */
export async function wpproductcontrollerSearchproducts(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerSearchproductsParams,
options?: { [key: string]: any },
) {
return request<API.ProductsRes>('/wp_product/search', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /wp_product/setconstitution */
export async function wpproductcontrollerSetconstitution(
body: Record<string, any>,
options?: { [key: string]: any },
) {
return request<API.BooleanRes>('/wp_product/setconstitution', {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /wp_product/siteId/${param0}/products */
export async function wpproductcontrollerCreateproduct(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerCreateproductParams,
body: Record<string, any>,
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
return request<API.BooleanRes>(`/wp_product/siteId/${param0}/products`, {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
params: { ...queryParams },
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 PUT /wp_product/siteId/${param1}/products/${param0} */
export async function wpproductcontrollerUpdateproduct(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerUpdateproductParams,
body: API.UpdateWpProductDTO,
options?: { [key: string]: any },
) {
const { productId: param0, siteId: param1, ...queryParams } = params;
return request<API.BooleanRes>(
`/wp_product/siteId/${param1}/products/${param0}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
},
);
}
/** 此处后端没有提供注释 PUT /wp_product/siteId/${param2}/products/${param1}/variations/${param0} */
export async function wpproductcontrollerUpdatevariation(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerUpdatevariationParams,
body: API.UpdateVariationDTO,
options?: { [key: string]: any },
) {
const {
variationId: param0,
productId: param1,
siteId: param2,
...queryParams
} = params;
return request<any>(
`/wp_product/siteId/${param2}/products/${param1}/variations/${param0}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
},
);
}
/** 此处后端没有提供注释 POST /wp_product/sync-to-product/${param0} */
export async function wpproductcontrollerSynctoproduct(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerSynctoproductParams,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<API.BooleanRes>(`/wp_product/sync-to-product/${param0}`, {
method: 'POST',
params: { ...queryParams },
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /wp_product/sync/${param0} */
export async function wpproductcontrollerSyncproducts(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerSyncproductsParams,
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
return request<API.BooleanRes>(`/wp_product/sync/${param0}`, {
method: 'POST',
params: { ...queryParams },
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /wp_product/updateState/${param0} */
export async function wpproductcontrollerUpdatewpproductstate(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.wpproductcontrollerUpdatewpproductstateParams,
body: Record<string, any>,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<API.BooleanRes>(`/wp_product/updateState/${param0}`, {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
params: { ...queryParams },
data: body,
...(options || {}),
});
}