feat(shop): 新增评论管理功能并优化店铺管理

refactor(api): 重构API请求参数处理逻辑
style(product): 调整产品表单字段顺序
fix(orders): 修正订单列表分页参数
perf(logistics): 优化批量删除操作性能
docs(typings): 更新API类型定义
chore: 移除无用代码和注释
This commit is contained in:
tikkhun 2025-12-18 15:23:31 +08:00
parent dc616f5e8d
commit d10052104a
19 changed files with 1179 additions and 572 deletions

View File

@ -310,11 +310,6 @@ export default defineConfig({
changeOrigin: true, changeOrigin: true,
pathRewrite: { '^/api': '' }, pathRewrite: { '^/api': '' },
}, },
'/site-api': {
target: UMI_APP_API_URL,
changeOrigin: true,
pathRewrite: { '^/site-api': '/site-api' },
},
}, },
npmClient: 'pnpm', npmClient: 'pnpm',
}); });

View File

@ -72,7 +72,7 @@ export const layout = (): ProLayoutProps => {
}; };
export const request: RequestConfig = { export const request: RequestConfig = {
baseURL: UMI_APP_API_URL, baseURL: '/api', // baseURL: UMI_APP_API_URL,
requestInterceptors: [ requestInterceptors: [
(url: string, options: any) => { (url: string, options: any) => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');

View File

@ -296,45 +296,7 @@ const PermutationPage: React.FC = () => {
}} }}
scroll={{ x: 'max-content' }} scroll={{ x: 'max-content' }}
search={false} search={false}
toolBarRender={() => [ toolBarRender={false}
<Button key="export" onClick={() => {
const exportColumns = columns.filter((c: any) => c.key !== 'action');
const headers = exportColumns.map((c: any) => String(c.title ?? ''));
const escapeCsv = (val: any) => {
const s = val === undefined || val === null ? '' : String(val);
if (/[",\n]/.test(s)) {
return '"' + s.replace(/"/g, '""') + '"';
}
return s;
};
const rows: string[][] = permutations.map((record: any) => {
return exportColumns.map((c: any) => {
if (c.key === 'sku') {
const key = generateKeyFromPermutation(record);
const product = existingProducts.get(key);
const value = product?.sku || '';
return escapeCsv(value);
}
const valueItem = c.dataIndex ? record[c.dataIndex] : undefined;
const value = valueItem?.name || '';
return escapeCsv(value);
});
});
const csvContent = [headers, ...rows].map(row => row.join(',')).join('\n');
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
const categoryName = categories.find(c => c.id === categoryId)?.name || 'Export';
link.href = url;
link.download = categoryName + '-permutations.csv';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}}>
CSV
</Button>
]}
/> />
)} )}
</ProCard> </ProCard>

View File

@ -1,17 +1,21 @@
import { import {
ActionType, ActionType,
DrawerForm,
ProColumns, ProColumns,
ProFormDependency,
ProFormInstance,
ProFormSelect,
ProFormSwitch,
ProFormText,
ProTable, ProTable,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import {
sitecontrollerCreate,
sitecontrollerDisable,
sitecontrollerList,
sitecontrollerUpdate,
} from '@/servers/api/site';
import { wpproductcontrollerSyncproducts } from '@/servers/api/wpProduct';
import { ordercontrollerSyncorder } from '@/servers/api/order';
import { subscriptioncontrollerSync } from '@/servers/api/subscription';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { Button, message, notification, Popconfirm, Space, Tag } from 'antd'; import { Button, message, notification, Popconfirm, Space, Tag } from 'antd';
import React, { useEffect, useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import EditSiteForm from '../Shop/EditSiteForm'; // 引入重构后的表单组件
// 区域数据项类型 // 区域数据项类型
interface AreaItem { interface AreaItem {
@ -26,11 +30,12 @@ interface StockPointItem {
} }
// 站点数据项类型(前端不包含密钥字段,后端列表不返回密钥) // 站点数据项类型(前端不包含密钥字段,后端列表不返回密钥)
interface SiteItem { export interface SiteItem {
id: number; id: number;
name: string; name: string;
description?: string; description?: string;
apiUrl?: string; apiUrl?: string;
websiteUrl?: string; // 网站地址
type?: 'woocommerce' | 'shopyy'; type?: 'woocommerce' | 'shopyy';
skuPrefix?: string; skuPrefix?: string;
isDisabled: number; isDisabled: number;
@ -38,24 +43,8 @@ interface SiteItem {
stockPoints?: StockPointItem[]; stockPoints?: StockPointItem[];
} }
// 创建/更新表单的值类型,包含可选的密钥字段
interface SiteFormValues {
name: string;
description?: string;
apiUrl?: string;
type?: 'woocommerce' | 'shopyy';
isDisabled?: boolean;
consumerKey?: string; // WooCommerce REST API 的 consumer key
consumerSecret?: string; // WooCommerce REST API 的 consumer secret
token?: string; // Shopyy token
skuPrefix?: string;
areas?: string[];
stockPointIds?: number[];
}
const SiteList: React.FC = () => { const SiteList: React.FC = () => {
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const formRef = useRef<ProFormInstance>();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [editing, setEditing] = useState<SiteItem | null>(null); const [editing, setEditing] = useState<SiteItem | null>(null);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
@ -73,24 +62,27 @@ const SiteList: React.FC = () => {
try { try {
for (const id of ids) { for (const id of ids) {
// 同步产品 // 同步产品
const prodRes = await request(`/wp_product/sync/${id}`, { method: 'POST' }); const prodRes = await wpproductcontrollerSyncproducts({ siteId: id });
if (prodRes) { if (prodRes.success) {
stats.products.success += prodRes.successCount || 0; stats.products.success += 1;
stats.products.fail += prodRes.failureCount || 0; } else {
stats.products.fail += 1;
} }
// 同步订单 // 同步订单
const orderRes = await request(`/order/syncOrder/${id}`, { method: 'POST' }); const orderRes = await ordercontrollerSyncorder({ siteId: id });
if (orderRes) { if (orderRes.success) {
stats.orders.success += orderRes.successCount || 0; stats.orders.success += 1;
stats.orders.fail += orderRes.failureCount || 0; } else {
stats.orders.fail += 1;
} }
// 同步订阅 // 同步订阅
const subRes = await request(`/subscription/sync/${id}`, { method: 'POST' }); const subRes = await subscriptioncontrollerSync({ siteId: id });
if (subRes) { if (subRes.success) {
stats.subscriptions.success += subRes.successCount || 0; stats.subscriptions.success += 1;
stats.subscriptions.fail += subRes.failureCount || 0; } else {
stats.subscriptions.fail += 1;
} }
} }
hide(); hide();
@ -115,39 +107,6 @@ const SiteList: React.FC = () => {
} }
}; };
useEffect(() => {
if (!open) return;
if (editing) {
formRef.current?.setFieldsValue({
name: editing.name,
description: editing.description,
apiUrl: editing.apiUrl,
type: editing.type,
skuPrefix: editing.skuPrefix,
isDisabled: !!editing.isDisabled,
consumerKey: undefined,
consumerSecret: undefined,
token: undefined,
areas: editing.areas?.map((area) => area.code) ?? [],
stockPointIds: editing.stockPoints?.map((sp) => sp.id) ?? [],
});
} else {
formRef.current?.setFieldsValue({
name: undefined,
description: undefined,
apiUrl: undefined,
type: 'woocommerce',
skuPrefix: undefined,
isDisabled: false,
consumerKey: undefined,
consumerSecret: undefined,
token: undefined,
areas: [],
stockPointIds: [],
});
}
}, [open, editing]);
// 表格列定义 // 表格列定义
const columns: ProColumns<SiteItem>[] = [ const columns: ProColumns<SiteItem>[] = [
{ {
@ -160,6 +119,13 @@ const SiteList: React.FC = () => {
{ title: '名称', dataIndex: 'name', width: 220 }, { title: '名称', dataIndex: 'name', width: 220 },
{ title: '描述', dataIndex: 'description', width: 220, hideInSearch: true }, { title: '描述', dataIndex: 'description', width: 220, hideInSearch: true },
{ title: 'API 地址', dataIndex: 'apiUrl', width: 280, hideInSearch: true }, { title: 'API 地址', dataIndex: 'apiUrl', width: 280, hideInSearch: true },
{
title: '网站地址',
dataIndex: 'websiteUrl',
width: 280,
hideInSearch: true,
render: (text) => <a href={text as string} target="_blank" rel="noopener noreferrer">{text}</a>
},
{ {
title: 'SKU 前缀', title: 'SKU 前缀',
dataIndex: 'skuPrefix', dataIndex: 'skuPrefix',
@ -176,24 +142,6 @@ const SiteList: React.FC = () => {
{ label: 'Shopyy', value: 'shopyy' }, { label: 'Shopyy', value: 'shopyy' },
], ],
}, },
// {
// title: '区域',
// dataIndex: 'areas',
// width: 200,
// hideInSearch: true,
// render: (_, row) => {
// if (!row.areas || row.areas.length === 0) {
// return <Tag color="blue">全球</Tag>;
// }
// return (
// <Space wrap>
// {row.areas.map((area) => (
// <Tag key={area.code}>{area.name}</Tag>
// ))}
// </Space>
// );
// },
// },
{ {
title: '关联仓库', title: '关联仓库',
dataIndex: 'stockPoints', dataIndex: 'stockPoints',
@ -254,10 +202,7 @@ const SiteList: React.FC = () => {
} }
onConfirm={async () => { onConfirm={async () => {
try { try {
await request(`/site/disable/${row.id}`, { await sitecontrollerDisable({ id: String(row.id) }, { disabled: !row.isDisabled });
method: 'PUT',
data: { disabled: !row.isDisabled },
});
message.success('更新成功'); message.success('更新成功');
actionRef.current?.reload(); actionRef.current?.reload();
} catch (e: any) { } catch (e: any) {
@ -277,21 +222,17 @@ const SiteList: React.FC = () => {
// 表格数据请求 // 表格数据请求
const tableRequest = async (params: Record<string, any>) => { const tableRequest = async (params: Record<string, any>) => {
try { try {
const { current = 1, pageSize = 10, name, type } = params; const { current, pageSize, name, type } = params;
const resp = await request('/site/list', { const resp = await sitecontrollerList({
method: 'GET', current,
params: { pageSize,
current, keyword: name || undefined,
pageSize, type: type || undefined,
keyword: name || undefined,
type: type || undefined,
},
}); });
const { success, data, message: errMsg } = resp as any; // 假设 resp 直接就是后端返回的结构,包含 items 和 total
if (!success) throw new Error(errMsg || '获取失败');
return { return {
data: (data?.items ?? []) as SiteItem[], data: (resp?.data?.items ?? []) as SiteItem[],
total: data?.total ?? 0, total: resp?.data?.total ?? 0,
success: true, success: true,
}; };
} catch (e: any) { } catch (e: any) {
@ -300,80 +241,20 @@ const SiteList: React.FC = () => {
} }
}; };
// 提交创建/更新逻辑;编辑时未填写密钥则不提交(保持原值) const handleFinish = async (values: any) => {
const handleSubmit = async (values: SiteFormValues) => {
try { try {
const isShopyy = values.type === 'shopyy';
const apiUrl = isShopyy ? 'https://openapi.oemapps.com' : values.apiUrl;
if (editing) { if (editing) {
const payload: Record<string, any> = { await sitecontrollerUpdate({ id: String(editing.id) }, values);
// 仅提交存在的字段,避免覆盖为 null/空 message.success('更新成功');
...(values.name ? { name: values.name } : {}),
...(values.description ? { description: values.description } : {}),
...(apiUrl ? { apiUrl: apiUrl } : {}),
...(values.type ? { type: values.type } : {}),
...(typeof values.isDisabled === 'boolean'
? { isDisabled: values.isDisabled }
: {}),
...(values.skuPrefix ? { skuPrefix: values.skuPrefix } : {}),
areas: values.areas ?? [],
stockPointIds: values.stockPointIds ?? [],
};
if (isShopyy) {
if (values.token && values.token.trim()) {
payload.token = values.token.trim();
}
} else {
// 仅当输入了新密钥时才提交,未输入则保持原本值
if (values.consumerKey && values.consumerKey.trim()) {
payload.consumerKey = values.consumerKey.trim();
}
if (values.consumerSecret && values.consumerSecret.trim()) {
payload.consumerSecret = values.consumerSecret.trim();
}
}
await request(`/site/update/${editing.id}`, {
method: 'PUT',
data: payload,
});
} else { } else {
if (isShopyy) { await sitecontrollerCreate(values);
if (!values.token) { message.success('创建成功');
throw new Error('Token is required for Shopyy');
}
} else {
// 新增站点时要求填写 consumerKey 和 consumerSecret
if (!values.consumerKey || !values.consumerSecret) {
throw new Error('Consumer Key and Secret are required for WooCommerce');
}
}
await request('/site/create', {
method: 'POST',
data: {
name: values.name,
description: values.description,
apiUrl: apiUrl,
type: values.type || 'woocommerce',
consumerKey: isShopyy ? undefined : values.consumerKey,
consumerSecret: isShopyy ? undefined : values.consumerSecret,
token: isShopyy ? values.token : undefined,
skuPrefix: values.skuPrefix,
areas: values.areas ?? [],
stockPointIds: values.stockPointIds ?? [],
},
});
} }
message.success('提交成功');
setOpen(false); setOpen(false);
setEditing(null);
actionRef.current?.reload(); actionRef.current?.reload();
return true; return true;
} catch (e: any) { } catch (error: any) {
message.error(e?.message || '提交失败'); message.error(error.message || '操作失败');
return false; return false;
} }
}; };
@ -381,7 +262,7 @@ const SiteList: React.FC = () => {
return ( return (
<> <>
<ProTable<SiteItem> <ProTable<SiteItem>
scroll={{ x: 'max-content' }} scroll={{ x: 'max-content' }}
actionRef={actionRef} actionRef={actionRef}
rowKey="id" rowKey="id"
columns={columns} columns={columns}
@ -392,161 +273,35 @@ const SiteList: React.FC = () => {
}} }}
toolBarRender={() => [ toolBarRender={() => [
<Button <Button
key="new"
type="primary" type="primary"
onClick={() => { onClick={() => {
setEditing(null); setEditing(null);
setOpen(true); setOpen(true);
}} }}
> >
</Button>, </Button>,
// 同步包括 orders subscriptions 等等
<Button <Button
key="sync"
disabled={!selectedRowKeys.length} disabled={!selectedRowKeys.length}
type="primary"
onClick={() => handleSync(selectedRowKeys as number[])} onClick={() => handleSync(selectedRowKeys as number[])}
> >
</Button>, </Button>,
]} ]}
/> />
<DrawerForm<SiteFormValues> <EditSiteForm
title={editing ? '编辑站点' : '新增站点'}
open={open} open={open}
onOpenChange={setOpen} onOpenChange={(visible) => {
formRef={formRef} setOpen(visible);
onFinish={handleSubmit} if (!visible) {
> setEditing(null);
{/* 站点名称,必填 */} }
<ProFormText }}
name="name" initialValues={editing}
label="站点名称" isEdit={!!editing}
placeholder="例如:本地商店" onFinish={handleFinish}
rules={[{ required: true, message: '站点名称为必填项' }]} />
/>
<ProFormText
name="description"
label="描述"
placeholder="请输入站点描述"
/>
{/* 仓库选择 */}
<ProFormSelect
name="stockPointIds"
label="关联仓库"
mode="multiple"
placeholder="请选择关联仓库"
request={async () => {
try {
const resp = await request('/stock/stock-point/all', {
method: 'GET',
});
if (resp.success) {
return resp.data.map((item: any) => ({
label: item.name,
value: item.id,
}));
}
return [];
} catch (e) {
return [];
}
}}
/>
{/* 区域选择 - 暂时隐藏 */}
{/* <ProFormSelect
name="areas"
label="区域"
mode="multiple"
placeholder="留空表示全球"
request={async () => {
try {
const resp = await request('/area', {
method: 'GET',
params: { pageSize: 1000 },
});
if (resp.success) {
return resp.data.list.map((area: AreaItem) => ({
label: area.name,
value: area.code,
}));
}
return [];
} catch (e) {
return [];
}
}}
/> */}
{/* 平台类型选择 */}
<ProFormSelect
name="type"
label="平台"
options={[
{ label: 'WooCommerce', value: 'woocommerce' },
{ label: 'Shopyy', value: 'shopyy' },
]}
/>
<ProFormDependency name={['type']}>
{({ type }) => {
const isShopyy = type === 'shopyy';
return isShopyy ? (
<>
<ProFormText
name="apiUrl"
label="API 地址"
disabled
initialValue="https://openapi.oemapps.com"
placeholder="https://openapi.oemapps.com"
/>
<ProFormText
name="token"
label="Token"
placeholder={editing ? '留空表示不修改' : '必填'}
rules={editing ? [] : [{ required: true, message: 'Token 为必填项' }]}
/>
</>
) : (
<>
<ProFormText
name="apiUrl"
label="API 地址"
placeholder="例如:https://shop.example.com"
rules={[{ required: true, message: 'API 地址为必填项' }]}
/>
{/* WooCommerce REST consumer key */}
<ProFormText
name="consumerKey"
label="Key"
placeholder={editing ? '留空表示不修改' : '必填'}
rules={editing ? [] : [{ required: true, message: 'Key 为必填项' }]}
/>
{/* WooCommerce REST consumer secret */}
<ProFormText
name="consumerSecret"
label="Secret"
placeholder={editing ? '留空表示不修改' : '必填'}
rules={editing ? [] : [{ required: true, message: 'Secret 为必填项' }]}
/>
</>
);
}}
</ProFormDependency>
{editing && <ProFormSwitch name="isDisabled" label="禁用" />}
<ProFormText
name="skuPrefix"
label="SKU 前缀"
placeholder={editing ? '留空表示不修改' : '可选'}
/>
</DrawerForm>
</> </>
); );
}; };

View File

@ -144,18 +144,6 @@ const CustomerPage: React.FC = () => {
return <Tag color="blue">{role}</Tag>; return <Tag color="blue">{role}</Tag>;
}, },
}, },
{
title: '订单数',
dataIndex: 'orders',
sorter: true,
hideInSearch: true,
},
{
title: '总花费',
dataIndex: 'total_spend',
sorter: true,
hideInSearch: true,
},
{ {
title: '账单地址', title: '账单地址',
dataIndex: 'billing', dataIndex: 'billing',
@ -173,23 +161,6 @@ const CustomerPage: React.FC = () => {
); );
}, },
}, },
{
title: '收货地址',
dataIndex: 'shipping',
hideInSearch: true,
render: (_, record) => {
const { shipping } = record;
if (!shipping) return '-';
return (
<div style={{ fontSize: 12 }}>
<div>{shipping.address_1} {shipping.address_2}</div>
<div>{shipping.city}, {shipping.state}, {shipping.postcode}</div>
<div>{shipping.country}</div>
<div>{shipping.phone}</div>
</div>
);
},
},
{ {
title: '注册时间', title: '注册时间',
dataIndex: 'date_created', dataIndex: 'date_created',
@ -268,21 +239,9 @@ const CustomerPage: React.FC = () => {
} }
const data = response.data; const data = response.data;
let items = (data?.items || []) as any[];
if (sort && typeof sort === 'object') {
const [field, dir] = Object.entries(sort)[0] || [];
if (field === 'orders' || field === 'total_spend') {
const isDesc = dir === 'descend';
items = items.slice().sort((a, b) => {
const av = Number(a?.[field] ?? 0);
const bv = Number(b?.[field] ?? 0);
return isDesc ? bv - av : av - bv;
});
}
}
return { return {
total: data?.total || 0, total: data?.total || 0,
data: items, data: data?.items || [],
success: true, success: true,
}; };
}} }}
@ -411,19 +370,42 @@ const CustomerPage: React.FC = () => {
search={false} search={false}
pagination={{ pageSize: 20 }} pagination={{ pageSize: 20 }}
columns={[ columns={[
{ title: '订单ID', dataIndex: 'id' }, { title: '订单号', dataIndex: 'number', copyable: true },
{ title: '订单号', dataIndex: 'number' }, {
{ title: '状态', dataIndex: 'status' }, title: '客户邮箱',
{ title: '币种', dataIndex: 'currency' }, dataIndex: 'email',
{ title: '金额', dataIndex: 'total' }, copyable: true,
{ title: '创建时间', dataIndex: 'date_created', valueType: 'dateTime' }, render: () => {
return ordersCustomer?.email;
},
},
{ title: '支付时间', dataIndex: 'date_paid', valueType: 'dateTime', hideInSearch: true },
{ title: '订单金额', dataIndex: 'total', hideInSearch: true },
{ title: '状态', dataIndex: 'status', hideInSearch: true },
{ title: '来源', dataIndex: 'created_via', hideInSearch: true },
{
title: '订单内容',
dataIndex: 'line_items',
hideInSearch: true,
render: (_, record) => {
return (
<div>
{record.line_items?.map((item: any) => (
<div key={item.id}>
{item.name} x {item.quantity}
</div>
))}
</div>
);
},
},
]} ]}
request={async (params) => { request={async (params) => {
if (!siteId || !ordersCustomer?.id) return { data: [], total: 0, success: true }; if (!siteId || !ordersCustomer?.id) return { data: [], total: 0, success: true };
const res = await request(`/site-api/${siteId}/customers/${ordersCustomer.id}/orders`, { const res = await request(`/site-api/${siteId}/customers/${ordersCustomer.id}/orders`, {
params: { params: {
page: params.current, page: params.current,
page_size: params.pageSize, per_page: params.pageSize,
}, },
}); });
if (!res?.success) { if (!res?.success) {

View File

@ -0,0 +1,158 @@
import {
DrawerForm,
ProFormDependency,
ProFormSelect,
ProFormSwitch,
ProFormText,
ProFormTextArea,
} from '@ant-design/pro-components';
import { Form } from 'antd';
import React, { useEffect } from 'react';
import { areacontrollerGetarealist } from '@/servers/api/area';
import { stockcontrollerGetallstockpoints } from '@/servers/api/stock';
// 定义组件的 props 类型
interface EditSiteFormProps {
open: boolean; // 控制抽屉表单的显示和隐藏
onOpenChange: (visible: boolean) => void; // 当抽屉表单显示状态改变时调用
onFinish: (values: any) => Promise<boolean | void>; // 表单提交成功时的回调
initialValues?: any; // 表单的初始值
isEdit: boolean; // 标记当前是编辑模式还是新建模式
}
const EditSiteForm: React.FC<EditSiteFormProps> = ({
open,
onOpenChange,
onFinish,
initialValues,
isEdit,
}) => {
const [form] = Form.useForm();
// 当 initialValues 或 open 状态变化时, 更新表单的值
useEffect(() => {
// 如果抽屉是打开的
if (open) {
// 如果是编辑模式并且有初始值
if (isEdit && initialValues) {
// 编辑模式下, 设置表单值为初始值
form.setFieldsValue({
...initialValues,
isDisabled: initialValues.isDisabled === 1, // 将后端的 1/0 转换成 true/false
});
} else {
// 新建模式或抽屉关闭时, 重置表单
form.resetFields();
}
}
}, [initialValues, isEdit, open, form]);
return (
<DrawerForm
title={isEdit ? '编辑站点' : '新建站点'}
form={form}
open={open}
onOpenChange={onOpenChange}
onFinish={async (values) => {
// 直接将表单值传递给 onFinish 回调
// 后端需要布尔值, 而 ProFormSwitch 已经提供了布尔值
return onFinish(values);
}}
layout="vertical"
>
<ProFormText
name="name"
label="名称"
rules={[{ required: true, message: '请输入名称' }]}
placeholder="请输入名称"
/>
<ProFormTextArea name="description" label="描述" placeholder="请输入描述" />
<ProFormText
name="apiUrl"
label="API 地址"
rules={[{ required: true, message: '请输入 API 地址' }]}
placeholder="请输入 API 地址"
/>
<ProFormText
name="websiteUrl"
label="网站地址"
placeholder="请输入网站地址"
/>
<ProFormSelect
name="type"
label="平台"
options={[
{ label: 'WooCommerce', value: 'woocommerce' },
{ label: 'Shopyy', value: 'shopyy' },
]}
rules={[{ required: true, message: '请选择平台' }]}
placeholder="请选择平台"
/>
{/* 根据选择的平台动态显示不同的认证字段 */}
<ProFormDependency name={['type']}>
{({ type }) => {
// 如果平台是 woocommerce
if (type === 'woocommerce') {
return (
<>
<ProFormText
name="consumerKey"
label="Consumer Key"
rules={[{ required: !isEdit, message: '请输入 Consumer Key' }]}
placeholder={isEdit ? '留空表示不修改' : '请输入 Consumer Key'}
/>
<ProFormText
name="consumerSecret"
label="Consumer Secret"
rules={[{ required: !isEdit, message: '请输入 Consumer Secret' }]}
placeholder={isEdit ? '留空表示不修改' : '请输入 Consumer Secret'}
/>
</>
);
}
// 如果平台是 shopyy
if (type === 'shopyy') {
return (
<ProFormText
name="token"
label="Token"
rules={[{ required: !isEdit, message: '请输入 Token' }]}
placeholder={isEdit ? '留空表示不修改' : '请输入 Token'}
/>
);
}
return null;
}}
</ProFormDependency>
<ProFormText name="skuPrefix" label="SKU 前缀" placeholder="请输入 SKU 前缀" />
<ProFormSelect
name="areas"
label="区域"
mode="multiple"
placeholder="请选择区域"
request={async () => {
// 从后端接口获取区域数据
const res = await areacontrollerGetarealist({ pageSize: 1000 });
// areacontrollerGetarealist 直接返回数组, 所以不需要 .data.list
return res.map((area: any) => ({ label: area.name, value: area.code }));
}}
/>
<ProFormSelect
name="stockPointIds"
label="关联仓库"
mode="multiple"
placeholder="请选择关联仓库"
request={async () => {
// 从后端接口获取仓库数据
const res = await stockcontrollerGetallstockpoints();
// 使用可选链和空值合并运算符来安全地处理可能未定义的数据
return res?.data?.map((sp: any) => ({ label: sp.name, value: sp.id })) ?? [];
}}
/>
<ProFormSwitch name="isDisabled" label="是否禁用" />
</DrawerForm>
);
};
export default EditSiteForm;

View File

@ -1,52 +1,51 @@
import { EditOutlined } from '@ant-design/icons';
import { request } from '@umijs/max';
import { sitecontrollerAll } from '@/servers/api/site'; import { sitecontrollerAll } from '@/servers/api/site';
import { import {
PageContainer, PageContainer,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { Outlet, history, useLocation, useParams } from '@umijs/max'; import { Outlet, history, useLocation, useParams } from '@umijs/max';
import { App, Button, Card, Col, Menu, Row, Select, Spin } from 'antd'; import { Button, Card, Col, Menu, Row, Select, Spin, message } from 'antd';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import EditSiteForm from './EditSiteForm';
import type { SiteItem } from '../List/index';
const ShopLayout: React.FC = () => { const ShopLayout: React.FC = () => {
const [sites, setSites] = useState<{ label: string; value: number }[]>([]); const [sites, setSites] = useState<any[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const { siteId } = useParams<{ siteId: string }>(); const { siteId } = useParams<{ siteId: string }>();
const location = useLocation(); const location = useLocation();
const { message } = App.useApp();
const [editModalOpen, setEditModalOpen] = useState(false);
const [editingSite, setEditingSite] = useState<SiteItem | null>(null);
const fetchSites = async () => {
try {
setLoading(true);
const { data = [] } = await sitecontrollerAll();
setSites(data);
if (!siteId && data.length > 0) {
history.replace(`/site/shop/${data[0].id}/products`);
}
} catch (error) {
console.error('Failed to fetch sites', error);
} finally {
setLoading(false);
}
};
useEffect(() => { useEffect(() => {
const fetchSites = async () => {
try {
const { data = [] } = await sitecontrollerAll();
const siteOptions = data.map((item: any) => ({
label: item.name,
value: item.id,
}));
setSites(siteOptions);
// 如果 URL 中没有 siteId且有站点数据默认跳转到第一个站点的 products 页面
if (!siteId && siteOptions.length > 0) {
history.replace(`/site/shop/${siteOptions[0].value}/products`);
}
} catch (error) {
console.error('Failed to fetch sites', error);
} finally {
setLoading(false);
}
};
fetchSites(); fetchSites();
}, []); }, []);
const handleSiteChange = (value: number) => { const handleSiteChange = (value: number) => {
// 切换站点时保持当前的功能模块products/orders/etc只改变 siteId
const currentPath = location.pathname; const currentPath = location.pathname;
const parts = currentPath.split('/'); const parts = currentPath.split('/');
// 假设路径结构是 /site/shop/:siteId/module...
// parts: ['', 'site', 'shop', '123', 'products']
if (parts.length >= 5) { if (parts.length >= 5) {
parts[3] = String(value); parts[3] = String(value);
history.push(parts.join('/')); history.push(parts.join('/'));
} else { } else {
// Fallback
history.push(`/site/shop/${value}/products`); history.push(`/site/shop/${value}/products`);
} }
}; };
@ -56,18 +55,14 @@ const ShopLayout: React.FC = () => {
history.push(`/site/shop/${siteId}/${e.key}`); history.push(`/site/shop/${siteId}/${e.key}`);
}; };
// 获取当前选中的菜单项
const getSelectedKey = () => { const getSelectedKey = () => {
const parts = location.pathname.split('/'); const parts = location.pathname.split('/');
// /site/shop/:siteId/:module
if (parts.length >= 5) { if (parts.length >= 5) {
return parts[4]; // products, orders, subscriptions, logistics return parts[4];
} }
return 'products'; return 'products';
}; };
// 已移除店铺同步逻辑,页面加载即从站点实时拉取数据
if (loading) { if (loading) {
return ( return (
<Spin <Spin
@ -77,8 +72,33 @@ const ShopLayout: React.FC = () => {
); );
} }
const handleFinish = async (values: any) => {
if (!editingSite) {
message.error('未找到要编辑的站点');
return false;
}
try {
await request(`/site/${editingSite.id}`, {
method: 'PUT',
data: values,
});
message.success('更新成功');
setEditModalOpen(false);
fetchSites(); // 重新获取站点列表以更新数据
return true;
} catch (error: any) {
message.error(error.message || '操作失败');
return false;
}
};
return ( return (
<PageContainer header={{ title: null, breadcrumb: undefined }}> <PageContainer
header={{ title: null, breadcrumb: undefined }}
contentStyle={{
padding: 0,
}}
>
<Row gutter={16} style={{ height: 'calc(100vh - 100px)' }}> <Row gutter={16} style={{ height: 'calc(100vh - 100px)' }}>
<Col span={4} style={{ height: '100%' }}> <Col span={4} style={{ height: '100%' }}>
<Card <Card
@ -86,17 +106,30 @@ const ShopLayout: React.FC = () => {
style={{ height: '100%', overflow: 'hidden' }} style={{ height: '100%', overflow: 'hidden' }}
> >
<div style={{ padding: '0 10px 16px' }}> <div style={{ padding: '0 10px 16px' }}>
<div style={{ marginBottom: 8, color: '#666', fontSize: '12px' }}>:</div> <div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
<Select <Select
style={{ width: '100%' }} style={{ flex: 1 }}
placeholder="请选择店铺" placeholder="请选择店铺"
options={sites} options={sites.map(site => ({ label: site.name, value: site.id }))}
value={siteId ? Number(siteId) : undefined} value={siteId ? Number(siteId) : undefined}
onChange={handleSiteChange} onChange={handleSiteChange}
showSearch showSearch
optionFilterProp="label" optionFilterProp="label"
/> />
{/* 店铺同步功能已废弃 */} <Button
icon={<EditOutlined />}
style={{ marginLeft: 8 }}
onClick={() => {
const currentSite = sites.find(site => site.id === Number(siteId));
if (currentSite) {
setEditingSite(currentSite);
setEditModalOpen(true);
} else {
message.warning('请先选择一个店铺');
}
}}
/>
</div>
</div> </div>
<div style={{ flex: 1, overflowY: 'auto' }}> <div style={{ flex: 1, overflowY: 'auto' }}>
<Menu <Menu
@ -108,21 +141,31 @@ const ShopLayout: React.FC = () => {
{ key: 'products', label: '产品管理' }, { key: 'products', label: '产品管理' },
{ key: 'orders', label: '订单管理' }, { key: 'orders', label: '订单管理' },
{ key: 'subscriptions', label: '订阅管理' }, { key: 'subscriptions', label: '订阅管理' },
{ key: 'logistics', label: '物流管理' },
{ key: 'media', label: '媒体管理' }, { key: 'media', label: '媒体管理' },
{ key: 'customers', label: '客户管理' }, { key: 'customers', label: '客户管理' },
{ key: 'reviews', label: '评论管理' },
]} ]}
/> />
</div> </div>
</Card> </Card>
</Col> </Col>
<Col span={20} style={{ height: '100%', overflowY: 'auto' }}> <Col span={20} style={{ height: '100%', overflowY: 'auto' }}>
{/* 这里的 Outlet 会渲染子路由组件,如 Products, Orders 等 */}
{siteId ? <Outlet /> : <div></div>} {siteId ? <Outlet /> : <div></div>}
</Col> </Col>
</Row> </Row>
{/* 店铺同步弹窗已移除 */} <EditSiteForm
open={editModalOpen}
onOpenChange={(visible: boolean) => {
setEditModalOpen(visible);
if (!visible) {
setEditingSite(null);
}
}}
initialValues={editingSite}
isEdit={!!editingSite}
onFinish={handleFinish}
/>
</PageContainer> </PageContainer>
); );
}; };

View File

@ -209,41 +209,28 @@ const LogisticsPage: React.FC = () => {
<Button onClick={handleBatchPrint} type="primary"> <Button onClick={handleBatchPrint} type="primary">
</Button> </Button>
<Popconfirm <Button
title="确定批量删除选中项吗?" danger
okText="确定" type="primary"
cancelText="取消" onClick={async () => {
onConfirm={async () => {
// 条件判断 如果当前未选择任何行则直接返回
if (!selectedRows || selectedRows.length === 0) return;
try { try {
// 进入批量删除处理流程
setIsLoading(true); setIsLoading(true);
let successCount = 0; let ok = 0;
for (const row of selectedRows) { for (const row of selectedRows) {
// 逐条删除 每次调用服务端删除接口 const { success } = await logisticscontrollerDeleteshipment({ id: row.id });
const { success } = await logisticscontrollerDeleteshipment({ id: row.id }); if (success) ok++;
// 条件判断 累计成功次数
if (success) successCount++;
} }
// 删除完成后提示成功条数 message.success(`成功删除 ${ok}`);
message.success(`成功删除 ${successCount}`);
// 结束加载状态
setIsLoading(false); setIsLoading(false);
// 刷新表格数据
actionRef.current?.reload(); actionRef.current?.reload();
// 清空选择列表
setSelectedRows([]); setSelectedRows([]);
} catch (e) { } catch (e) {
// 异常处理 结束加载状态
setIsLoading(false); setIsLoading(false);
} }
}} }}
> >
<Button danger type="primary">
</Button>
</Button>
</Popconfirm>
</Space> </Space>
); );
}} }}

View File

@ -113,6 +113,30 @@ const OrdersPage: React.FC = () => {
dataIndex: 'customer_name', dataIndex: 'customer_name',
hideInSearch: true, hideInSearch: true,
}, },
{
title: '商品',
dataIndex: 'line_items',
hideInSearch: true,
width: 200,
ellipsis: true,
render: (_, record) => {
// 检查 record.line_items 是否是数组并且有内容
if (Array.isArray(record.line_items) && record.line_items.length > 0) {
// 遍历 line_items 数组, 显示每个商品的名称和数量
return (
<div>
{record.line_items.map((item: any) => (
<div key={item.id}>
{`${item.name} x ${item.quantity}`}
</div>
))}
</div>
);
}
// 如果 line_items 不存在或不是数组, 则显示占位符
return '-';
},
},
{ {
title: '支付方式', title: '支付方式',
dataIndex: 'payment_method', dataIndex: 'payment_method',
@ -372,7 +396,7 @@ const OrdersPage: React.FC = () => {
const res = await request(`/site-api/${siteId}/orders`, { const res = await request(`/site-api/${siteId}/orders`, {
params: { params: {
page: 1, page: 1,
page_size: 1, per_page: 1,
where: { ...baseWhere, status: rawStatus }, where: { ...baseWhere, status: rawStatus },
}, },
}); });

View File

@ -8,7 +8,7 @@ import {
ProTable, ProTable,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { App, Button, Divider, Popconfirm, Tag } from 'antd'; import { App, Button, Divider, Popconfirm, Tag } from 'antd';
import { DeleteFilled } from '@ant-design/icons'; import { DeleteFilled, LinkOutlined } from '@ant-design/icons';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { import {
BatchEditProducts, BatchEditProducts,
@ -21,13 +21,13 @@ import {
CreateProduct, CreateProduct,
} from '../components/Product/Forms'; } from '../components/Product/Forms';
import { TagConfig } from '../components/Product/utils'; import { TagConfig } from '../components/Product/utils';
const ProductsPage: React.FC = () => { const ProductsPage: React.FC = () => {
const { message } = App.useApp(); const { message } = App.useApp();
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [selectedRows, setSelectedRows] = useState<any[]>([]); // Use any or unified DTO type const [selectedRows, setSelectedRows] = useState<any[]>([]); // Use any or unified DTO type
const { siteId } = useParams<{ siteId: string }>(); const { siteId } = useParams<{ siteId: string }>();
const [siteInfo, setSiteInfo] = useState<any>();
const [config, setConfig] = useState<TagConfig>({ const [config, setConfig] = useState<TagConfig>({
brands: [], brands: [],
fruits: [], fruits: [],
@ -43,6 +43,20 @@ const ProductsPage: React.FC = () => {
actionRef.current?.reload(); actionRef.current?.reload();
}, [siteId]); }, [siteId]);
useEffect(() => {
const loadSiteInfo = async () => {
try {
const res = await request(`/site/get/${siteId}`);
if (res?.success && res?.data) {
setSiteInfo(res.data);
}
} catch (e) {}
};
if (siteId) {
loadSiteInfo();
}
}, [siteId]);
useEffect(() => { useEffect(() => {
const fetchAllConfigs = async () => { const fetchAllConfigs = async () => {
try { try {
@ -86,8 +100,9 @@ const ProductsPage: React.FC = () => {
fetchAllConfigs(); fetchAllConfigs();
}, []); }, []);
const columns: ProColumns<any>[] = [ const columns: ProColumns<API.UnifiedProductDTO>[] = [
{ {
// ID
title: 'ID', title: 'ID',
dataIndex: 'id', dataIndex: 'id',
hideInSearch: true, hideInSearch: true,
@ -98,36 +113,43 @@ const ProductsPage: React.FC = () => {
} }
}, },
{ {
// sku
title: 'sku', title: 'sku',
dataIndex: 'sku', dataIndex: 'sku',
fixed: 'left', fixed: 'left',
}, },
{ {
// 名称
title: '名称', title: '名称',
dataIndex: 'name', dataIndex: 'name',
}, },
{ {
// 产品状态
title: '产品状态', title: '产品状态',
dataIndex: 'status', dataIndex: 'status',
valueType: 'select', valueType: 'select',
valueEnum: PRODUCT_STATUS_ENUM, valueEnum: PRODUCT_STATUS_ENUM,
}, },
{ {
// 产品类型
title: '产品类型', title: '产品类型',
dataIndex: 'type', dataIndex: 'type',
}, },
{ {
// 库存状态
title: '库存状态', title: '库存状态',
dataIndex: 'stock_status', dataIndex: 'stock_status',
valueType: 'select', valueType: 'select',
valueEnum: PRODUCT_STOCK_STATUS_ENUM, valueEnum: PRODUCT_STOCK_STATUS_ENUM,
}, },
{ {
// 库存
title: '库存', title: '库存',
dataIndex: 'stock_quantity', dataIndex: 'stock_quantity',
hideInSearch: true, hideInSearch: true,
}, },
{ {
// 图片
title: '图片', title: '图片',
dataIndex: 'images', dataIndex: 'images',
hideInSearch: true, hideInSearch: true,
@ -139,16 +161,101 @@ const ProductsPage: React.FC = () => {
}, },
}, },
{ {
// 常规价格
title: '常规价格', title: '常规价格',
dataIndex: 'regular_price', dataIndex: 'regular_price',
hideInSearch: true, hideInSearch: true,
}, },
{ {
// 销售价格
title: '销售价格', title: '销售价格',
dataIndex: 'sale_price', dataIndex: 'sale_price',
hideInSearch: true, hideInSearch: true,
}, },
{ {
// 标签
title: '标签',
dataIndex: 'tags',
hideInSearch: true,
width: 250,
render: (_, record) => {
// 检查 record.tags 是否存在并且是一个数组
if (record.tags && Array.isArray(record.tags)) {
// 遍历 tags 数组并为每个 tag 对象渲染一个 Tag 组件
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>
{record.tags.map((tag: any) => (
// 使用 tag.name 作为 key, 因为 tag.id 可能是对象, 会导致 React key 错误
<Tag key={tag.name}>{tag.name}</Tag>
))}
</div>
);
}
// 如果 record.tags 不是一个有效的数组,则不渲染任何内容
return null;
},
},
{
// 分类
title: '分类',
dataIndex: 'categories',
hideInSearch: true,
width: 250,
render: (_, record) => {
// 检查 record.categories 是否存在并且是一个数组
if (record.categories && Array.isArray(record.categories)) {
// 遍历 categories 数组并为每个 category 对象渲染一个 Tag 组件
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>
{record.categories.map((cat: any) => (
// 使用 cat.name 作为 key
<Tag key={cat.name}>{cat.name}</Tag>
))}
</div>
);
}
// 如果 record.categories 不是一个有效的数组,则不渲染任何内容
return null;
},
},
{
// 属性
title: '属性',
dataIndex: 'attributes',
hideInSearch: true,
width: 250,
render: (_, record) => {
// 检查 record.attributes 是否存在并且是一个数组
if (record.attributes && Array.isArray(record.attributes)) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
{(record.attributes as any[]).map((attr: any) => (
<div key={attr.name}>
<strong>{attr.name}:</strong> {Array.isArray(attr.options) ? attr.options.join(', ') : ''}
</div>
))}
</div>
);
}
return null;
},
},
{
// 创建时间
title: '创建时间',
dataIndex: 'date_created',
valueType: 'dateTime',
hideInSearch: true,
},
{
// 修改时间
title: '修改时间',
dataIndex: 'date_modified',
valueType: 'dateTime',
hideInSearch: true,
},
{
// 操作
title: '操作', title: '操作',
dataIndex: 'option', dataIndex: 'option',
valueType: 'option', valueType: 'option',
@ -158,6 +265,19 @@ const ProductsPage: React.FC = () => {
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<UpdateForm tableRef={actionRef} values={record} config={config} siteId={siteId} /> <UpdateForm tableRef={actionRef} values={record} config={config} siteId={siteId} />
<UpdateStatus tableRef={actionRef} values={record} siteId={siteId} /> <UpdateStatus tableRef={actionRef} values={record} siteId={siteId} />
<Button
type="link"
title="店铺链接"
icon={<LinkOutlined />}
disabled={!record.frontendUrl}
onClick={() => {
if (record.frontendUrl) {
window.open(record.frontendUrl, '_blank', 'noopener,noreferrer');
} else {
message.warning('未能生成店铺链接');
}
}}
/>
<Popconfirm <Popconfirm
key="delete" key="delete"
title="删除" title="删除"
@ -192,10 +312,10 @@ const ProductsPage: React.FC = () => {
return ( return (
<PageContainer header={{ title: null, breadcrumb: undefined }}> <PageContainer header={{ title: null, breadcrumb: undefined }}>
<ProTable<any> <ProTable<API.UnifiedProductDTO>
scroll={{ x: 'max-content' }} scroll={{ x: 'max-content' }}
pagination={{ pagination={{
pageSizeOptions: ['10', '20', '50', '100', '1000'], pageSizeOptions: ['10', '20', '50', '100', '1000','2000'],
showSizeChanger: true, showSizeChanger: true,
defaultPageSize: 10, defaultPageSize: 10,
}} }}
@ -209,21 +329,24 @@ const ProductsPage: React.FC = () => {
}, },
}} }}
request={async (params, sort, filter) => { request={async (params, sort, filter) => {
const { current, pageSize, ...rest } = params || {}; // 从参数中解构分页和筛选条件, ProTable 使用 current 作为页码, 但后端需要 page, 所以在这里进行重命名
const { current: page, pageSize, ...rest } = params || {};
const where = { ...rest, ...(filter || {}) }; const where = { ...rest, ...(filter || {}) };
let orderObj: Record<string, 'asc' | 'desc'> | undefined = undefined; let orderObj: Record<string, 'asc' | 'desc'> | undefined = undefined;
// 如果存在排序条件, 则进行处理
if (sort && typeof sort === 'object') { if (sort && typeof sort === 'object') {
const [field, dir] = Object.entries(sort)[0] || []; const [field, dir] = Object.entries(sort)[0] || [];
if (field && dir) { if (field && dir) {
orderObj = { [field]: dir === 'descend' ? 'desc' : 'asc' }; orderObj = { [field]: dir === 'descend' ? 'desc' : 'asc' };
} }
} }
// 发起获取产品列表的请求
const response = await request(`/site-api/${siteId}/products`, { const response = await request(`/site-api/${siteId}/products`, {
params: { params: {
page: current, page,
page_size: pageSize, per_page: pageSize,
where, ...where,
...(orderObj ? { order: orderObj } : {}), ...(orderObj ? { sortField: Object.keys(orderObj)[0], sortOrder: Object.values(orderObj)[0] } : {}),
} }
}); });
@ -279,7 +402,7 @@ const ProductsPage: React.FC = () => {
expandable={{ expandable={{
rowExpandable: (record) => record.type === 'variable', rowExpandable: (record) => record.type === 'variable',
expandedRowRender: (record) => { expandedRowRender: (record) => {
const productExternalId = record.externalProductId || record.external_product_id || record.id; const productExternalId = (record as any).externalProductId || (record as any).external_product_id || record.id;
const innerColumns: ProColumns<any>[] = [ const innerColumns: ProColumns<any>[] = [
{ {
title: 'ID', title: 'ID',
@ -294,6 +417,26 @@ const ProductsPage: React.FC = () => {
{ title: 'sku', dataIndex: 'sku' }, { title: 'sku', dataIndex: 'sku' },
{ title: '常规价格', dataIndex: 'regular_price', hideInSearch: true }, { title: '常规价格', dataIndex: 'regular_price', hideInSearch: true },
{ title: '销售价格', dataIndex: 'sale_price', hideInSearch: true }, { title: '销售价格', dataIndex: 'sale_price', hideInSearch: true },
{
title: 'Attributes',
dataIndex: 'attributes',
hideInSearch: true,
render: (_, row) => {
// 检查 row.attributes 是否存在并且是一个数组
if (row.attributes && Array.isArray(row.attributes)) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
{(row.attributes as any[]).map((attr: any) => (
<div key={attr.name}>
<strong>{attr.name}:</strong> {attr.option}
</div>
))}
</div>
);
}
return null;
},
},
{ {
title: '操作', title: '操作',
dataIndex: 'option', dataIndex: 'option',

View File

@ -0,0 +1,29 @@
import React from 'react';
interface ReviewFormProps {
open: boolean;
editing: any;
siteId: number;
onClose: () => void;
onSuccess: () => void;
}
const ReviewForm: React.FC<ReviewFormProps> = ({ open, editing, siteId, onClose, onSuccess }) => {
// // 这是一个临时的占位符组件
// // 你可以在这里实现表单逻辑
if (!open) {
return null;
}
return (
<div>
<h2>Review Form</h2>
<p>Site ID: {siteId}</p>
<p>Editing: {editing ? 'Yes' : 'No'}</p>
<button onClick={onClose}>Close</button>
</div>
);
};
export default ReviewForm;

View File

@ -0,0 +1,101 @@
import React, { useRef, useState } from 'react';
import { ActionType, ProTable, ProColumns, ProCard } from '@ant-design/pro-components';
import { Button, Popconfirm, message, Space } from 'antd';
import { siteapicontrollerGetreviews, siteapicontrollerDeletereview } from '@/servers/api/siteApi';
import ReviewForm from './ReviewForm';
import { useParams } from '@umijs/max';
const ReviewsPage: React.FC = () => {
const params = useParams();
const siteId = Number(params.siteId);
const actionRef = useRef<ActionType>();
const [open, setOpen] = useState(false);
const [editing, setEditing] = useState<any>(null);
const columns: ProColumns<API.UnifiedReviewDTO>[] = [
{ title: 'ID', dataIndex: 'id', key: 'id', width: 50 },
{ title: '产品ID', dataIndex: 'product_id', key: 'product_id', width: 80 },
{ title: '作者', dataIndex: 'author', key: 'author' },
{ title: '评分', dataIndex: 'rating', key: 'rating', width: 80 },
{ title: '状态', dataIndex: 'status', key: 'status', width: 100 },
{
title: '创建时间',
dataIndex: 'date_created',
key: 'date_created',
valueType: 'dateTime',
width: 150,
},
{
title: '操作',
key: 'action',
width: 150,
render: (_, record) => (
<Space>
<Button type="link" style={{padding:0}} onClick={() => {
setEditing(record);
setOpen(true);
}}></Button>
<Popconfirm title="确定删除吗?" onConfirm={async () => {
if (record.id) {
try {
const response = await siteapicontrollerDeletereview({ 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.UnifiedReviewDTO>
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,
};
}}
rowKey="id"
search={{
labelWidth: 'auto',
}}
headerTitle="评论列表"
toolBarRender={() => [
<Button type="primary" onClick={() => {
setEditing(null);
setOpen(true);
}}>
</Button>,
]}
/>
<ReviewForm
open={open}
editing={editing}
siteId={siteId}
onClose={() => setOpen(false)}
onSuccess={() => {
setOpen(false);
actionRef.current?.reload();
}}
/>
</ProCard>
);
};
export default ReviewsPage;

View File

@ -56,7 +56,6 @@ const SubscriptionsPage: React.FC = () => {
title: '订阅ID', title: '订阅ID',
dataIndex: 'id', dataIndex: 'id',
hideInSearch: true, hideInSearch: true,
width: 120,
}, },
{ {
title: '状态', title: '状态',
@ -70,25 +69,21 @@ const SubscriptionsPage: React.FC = () => {
) : ( ) : (
'-' '-'
), ),
width: 120,
}, },
{ {
title: '客户ID', title: '客户ID',
dataIndex: 'customer_id', dataIndex: 'customer_id',
hideInSearch: true, hideInSearch: true,
width: 120,
}, },
{ {
title: '计费周期', title: '计费周期',
dataIndex: 'billing_period', dataIndex: 'billing_period',
hideInSearch: true, hideInSearch: true,
width: 120,
}, },
{ {
title: '计费间隔', title: '计费间隔',
dataIndex: 'billing_interval', dataIndex: 'billing_interval',
hideInSearch: true, hideInSearch: true,
width: 120,
}, },
{ {
title: '开始时间', title: '开始时间',
@ -102,10 +97,23 @@ const SubscriptionsPage: React.FC = () => {
hideInSearch: true, hideInSearch: true,
width: 160, width: 160,
}, },
{
// 创建时间
title: '创建时间',
dataIndex: 'date_created',
valueType: 'dateTime',
hideInSearch: true,
},
{
// 修改时间
title: '修改时间',
dataIndex: 'date_modified',
valueType: 'dateTime',
hideInSearch: true,
},
{ {
title: '操作', title: '操作',
valueType: 'option', valueType: 'option',
width: 120,
render: (_, row) => ( render: (_, row) => (
<Space> <Space>
<Button type="link" title="编辑" icon={<EditOutlined />} onClick={() => setEditing(row)} /> <Button type="link" title="编辑" icon={<EditOutlined />} onClick={() => setEditing(row)} />
@ -127,23 +135,13 @@ const SubscriptionsPage: React.FC = () => {
* ; * ;
* data.items data.list * data.items data.list
*/ */
request={async (params, sort, filter) => { request={async (params) => {
if (!siteId) return { data: [], success: true }; if (!siteId) return { data: [], success: true };
const { current, pageSize, ...rest } = params || {};
const where = { ...rest, ...(filter || {}) };
let orderObj: Record<string, 'asc' | 'desc'> | undefined = undefined;
if (sort && typeof sort === 'object') {
const [field, dir] = Object.entries(sort)[0] || [];
if (field && dir) {
orderObj = { [field]: dir === 'descend' ? 'desc' : 'asc' };
}
}
const response = await request(`/site-api/${siteId}/subscriptions`, { const response = await request(`/site-api/${siteId}/subscriptions`, {
params: { params: {
page: current, ...params,
page_size: pageSize, page: params.current,
where, per_page: params.pageSize,
...(orderObj ? { order: orderObj } : {}),
} }
}); });
@ -173,8 +171,7 @@ const SubscriptionsPage: React.FC = () => {
title="批量导出" title="批量导出"
onClick={async () => { onClick={async () => {
if (!siteId) return; if (!siteId) return;
const idsParam = selectedRowKeys.length ? (selectedRowKeys as any[]).join(',') : undefined; const res = await request(`/site-api/${siteId}/subscriptions/export`, { params: {} });
const res = await request(`/site-api/${siteId}/subscriptions/export`, { params: { ids: idsParam } });
if (res?.success && res?.data?.csv) { if (res?.success && res?.data?.csv) {
const blob = new Blob([res.data.csv], { type: 'text/csv;charset=utf-8;' }); const blob = new Blob([res.data.csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
@ -188,23 +185,8 @@ const SubscriptionsPage: React.FC = () => {
} }
}} }}
/>, />,
<Popconfirm <Button title="批量删除" danger icon={<DeleteFilled />} onClick={() => message.info('订阅删除未实现')} />
title="确定批量删除选中项吗?" ]} />
okText="确定"
cancelText="取消"
onConfirm={() => {
// 条件判断 如果当前未选择任何行则直接返回
if (!selectedRowKeys || selectedRowKeys.length === 0) {
message.warning('请选择要删除的订阅');
return;
}
// 暂未实现批量删除接口 进行用户提示
message.info('订阅删除未实现');
}}
>
<Button title="批量删除" danger icon={<DeleteFilled />} />
</Popconfirm>
]} />
<Drawer <Drawer
open={drawerOpen} open={drawerOpen}
title={drawerTitle} title={drawerTitle}

View File

@ -17,6 +17,7 @@ import {
DeleteFilled, DeleteFilled,
EditOutlined, EditOutlined,
FileDoneOutlined, FileDoneOutlined,
PlusOutlined,
TagsOutlined, TagsOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { import {
@ -578,7 +579,7 @@ export const CreateOrder: React.FC<{
}, },
}} }}
trigger={ trigger={
<Button type="primary" size="small" icon={<CodeSandboxOutlined />}> <Button type="primary" icon={<PlusOutlined />}>
</Button> </Button>
} }

View File

@ -355,16 +355,17 @@ export const UpdateForm: React.FC<{
placeholder="请输入SKU" placeholder="请输入SKU"
rules={[{ required: true, message: '请输入SKU' }]} rules={[{ required: true, message: '请输入SKU' }]}
/> />
<ProFormTextArea
name="description"
label="描述"
width="lg"
/>
<ProFormTextArea <ProFormTextArea
name="short_description" name="short_description"
label="简短描述" label="简短描述"
width="lg" width="lg"
/> />
<ProFormTextArea
name="description"
label="描述"
width="lg"
/>
{initialValues.type === 'simple' ? ( {initialValues.type === 'simple' ? (
<> <>
<ProFormDigit <ProFormDigit

View File

@ -11,6 +11,7 @@ import * as logistics from './logistics';
import * as media from './media'; import * as media from './media';
import * as order from './order'; import * as order from './order';
import * as product from './product'; import * as product from './product';
import * as review from './review';
import * as site from './site'; import * as site from './site';
import * as siteApi from './siteApi'; import * as siteApi from './siteApi';
import * as statistics from './statistics'; import * as statistics from './statistics';
@ -30,6 +31,7 @@ export default {
media, media,
order, order,
product, product,
review,
siteApi, siteApi,
site, site,
statistics, statistics,

80
src/servers/api/review.ts Normal file
View File

@ -0,0 +1,80 @@
// @ts-ignore
/* eslint-disable */
import { request } from 'umi';
/** 此处后端没有提供注释 POST /review/create */
export async function reviewcontrollerCreate(
body: API.CreateReviewDTO,
options?: { [key: string]: any },
) {
return request<any>('/review/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 DELETE /review/delete/${param0} */
export async function reviewcontrollerDelete(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.reviewcontrollerDeleteParams,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<any>(`/review/delete/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /review/get/${param0} */
export async function reviewcontrollerGet(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.reviewcontrollerGetParams,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<any>(`/review/get/${param0}`, {
method: 'GET',
params: { ...queryParams },
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /review/list */
export async function reviewcontrollerList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.reviewcontrollerListParams,
options?: { [key: string]: any },
) {
return request<any>('/review/list', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 PUT /review/update/${param0} */
export async function reviewcontrollerUpdate(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.reviewcontrollerUpdateParams,
body: API.UpdateReviewDTO,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<any>(`/review/update/${param0}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
});
}

View File

@ -15,6 +15,10 @@ export async function siteapicontrollerGetcustomers(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}, },
@ -70,6 +74,10 @@ export async function siteapicontrollerExportcustomers(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}); });
@ -105,6 +113,10 @@ export async function siteapicontrollerGetmedia(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}); });
@ -162,6 +174,10 @@ export async function siteapicontrollerExportmedia(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}); });
@ -178,6 +194,10 @@ export async function siteapicontrollerGetorders(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}); });
@ -232,6 +252,10 @@ export async function siteapicontrollerExportorders(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}); });
@ -269,6 +293,10 @@ export async function siteapicontrollerGetproducts(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}, },
@ -324,6 +352,10 @@ export async function siteapicontrollerExportproducts(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}); });
@ -340,6 +372,10 @@ export async function siteapicontrollerExportproductsspecial(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}); });
@ -386,6 +422,48 @@ export async function siteapicontrollerImportproductsspecial(
); );
} }
/** 此处后端没有提供注释 GET /site-api/${param0}/reviews */
export async function siteapicontrollerGetreviews(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerGetreviewsParams,
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
return request<API.UnifiedReviewPaginationDTO>(
`/site-api/${param0}/reviews`,
{
method: 'GET',
params: {
...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
},
...(options || {}),
},
);
}
/** 此处后端没有提供注释 POST /site-api/${param0}/reviews */
export async function siteapicontrollerCreatereview(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerCreatereviewParams,
body: API.CreateReviewDTO,
options?: { [key: string]: any },
) {
const { siteId: param0, ...queryParams } = params;
return request<API.UnifiedReviewDTO>(`/site-api/${param0}/reviews`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /site-api/${param0}/subscriptions */ /** 此处后端没有提供注释 GET /site-api/${param0}/subscriptions */
export async function siteapicontrollerGetsubscriptions( export async function siteapicontrollerGetsubscriptions(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
@ -399,6 +477,10 @@ export async function siteapicontrollerGetsubscriptions(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}, },
@ -416,6 +498,10 @@ export async function siteapicontrollerExportsubscriptions(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}); });
@ -490,6 +576,10 @@ export async function siteapicontrollerGetcustomerorders(
method: 'GET', method: 'GET',
params: { params: {
...queryParams, ...queryParams,
where: undefined,
...queryParams['where'],
order: undefined,
...queryParams['order'],
}, },
...(options || {}), ...(options || {}),
}, },
@ -671,6 +761,42 @@ export async function siteapicontrollerDeleteproduct(
); );
} }
/** 此处后端没有提供注释 PUT /site-api/${param1}/reviews/${param0} */
export async function siteapicontrollerUpdatereview(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerUpdatereviewParams,
body: API.UpdateReviewDTO,
options?: { [key: string]: any },
) {
const { id: param0, siteId: param1, ...queryParams } = params;
return request<API.UnifiedReviewDTO>(
`/site-api/${param1}/reviews/${param0}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
params: { ...queryParams },
data: body,
...(options || {}),
},
);
}
/** 此处后端没有提供注释 DELETE /site-api/${param1}/reviews/${param0} */
export async function siteapicontrollerDeletereview(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.siteapicontrollerDeletereviewParams,
options?: { [key: string]: any },
) {
const { id: param0, siteId: param1, ...queryParams } = params;
return request<Record<string, any>>(`/site-api/${param1}/reviews/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
});
}
/** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */ /** 此处后端没有提供注释 PUT /site-api/${param2}/products/${param1}/variations/${param0} */
export async function siteapicontrollerUpdatevariation( export async function siteapicontrollerUpdatevariation(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象) // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)

View File

@ -184,6 +184,25 @@ declare namespace API {
items?: PurchaseOrderItem[]; items?: PurchaseOrderItem[];
}; };
type CreateReviewDTO = {
/** 站点ID */
siteId?: number;
/** 产品ID */
productId?: number;
/** 客户ID */
customerId?: number;
/** 评论者姓名 */
author?: string;
/** 评论者电子邮件 */
email?: string;
/** 评论内容 */
content?: string;
/** 评分 (1-5) */
rating?: number;
/** 状态 */
status?: 'pending' | 'approved' | 'rejected';
};
type CreateSiteDTO = { type CreateSiteDTO = {
/** 区域 */ /** 区域 */
areas?: any; areas?: any;
@ -1211,6 +1230,19 @@ declare namespace API {
stockPointId?: number; stockPointId?: number;
}; };
type QueryReviewDTO = {
/** 当前页码 */
current?: number;
/** 每页数量 */
pageSize?: number;
/** 站点ID */
siteId?: number;
/** 产品ID */
productId?: number;
/** 状态 */
status?: string;
};
type QueryServiceDTO = { type QueryServiceDTO = {
/** 页码 */ /** 页码 */
current?: number; current?: number;
@ -1316,6 +1348,31 @@ declare namespace API {
data?: RateDTO[]; data?: RateDTO[];
}; };
type reviewcontrollerDeleteParams = {
id: number;
};
type reviewcontrollerGetParams = {
id: number;
};
type reviewcontrollerListParams = {
/** 当前页码 */
current?: number;
/** 每页数量 */
pageSize?: number;
/** 站点ID */
siteId?: number;
/** 产品ID */
productId?: number;
/** 状态 */
status?: string;
};
type reviewcontrollerUpdateParams = {
id: number;
};
type Service = { type Service = {
id?: string; id?: string;
carrier_name?: string; carrier_name?: string;
@ -1442,6 +1499,10 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type siteapicontrollerCreatereviewParams = {
siteId: number;
};
type siteapicontrollerDeletecustomerParams = { type siteapicontrollerDeletecustomerParams = {
id: string; id: string;
siteId: number; siteId: number;
@ -1462,6 +1523,11 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type siteapicontrollerDeletereviewParams = {
id: number;
siteId: number;
};
type siteapicontrollerExportcustomersParams = { type siteapicontrollerExportcustomersParams = {
/** 页码 */ /** 页码 */
page?: number; page?: number;
@ -1476,9 +1542,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1502,9 +1568,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1528,9 +1594,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1554,9 +1620,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1580,9 +1646,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1606,9 +1672,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1632,9 +1698,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1664,9 +1730,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1690,9 +1756,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1726,9 +1792,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1757,9 +1823,35 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */
orderby?: string;
/** 排序方式(兼容旧入参) */
orderDir?: string;
/** 选中ID列表,逗号分隔 */
ids?: string;
siteId: number;
};
type siteapicontrollerGetreviewsParams = {
/** 页码 */
page?: number;
/** 每页数量 */
per_page?: number;
/** 每页数量别名 */
page_size?: number;
/** 搜索关键词 */
search?: string;
/** 状态 */
status?: string;
/** 客户ID,用于筛选订单 */
customer_id?: number;
/** 过滤条件对象 */
where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */
order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1783,9 +1875,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -1831,6 +1923,11 @@ declare namespace API {
siteId: number; siteId: number;
}; };
type siteapicontrollerUpdatereviewParams = {
id: number;
siteId: number;
};
type siteapicontrollerUpdatevariationParams = { type siteapicontrollerUpdatevariationParams = {
variationId: string; variationId: string;
productId: string; productId: string;
@ -2193,6 +2290,40 @@ declare namespace API {
minute?: string; minute?: string;
}; };
type UnifiedAddressDTO = {
/** 名 */
first_name?: string;
/** 姓 */
last_name?: string;
/** 全名 */
fullname?: string;
/** 公司 */
company?: string;
/** 地址1 */
address_1?: string;
/** 地址2 */
address_2?: string;
/** 城市 */
city?: string;
/** 省/州 */
state?: string;
/** 邮政编码 */
postcode?: string;
/** 国家 */
country?: string;
/** 邮箱 */
email?: string;
/** 电话 */
phone?: string;
};
type UnifiedCategoryDTO = {
/** 分类ID */
id?: Record<string, any>;
/** 分类名称 */
name?: string;
};
type UnifiedCustomerDTO = { type UnifiedCustomerDTO = {
/** 客户ID */ /** 客户ID */
id?: Record<string, any>; id?: Record<string, any>;
@ -2219,11 +2350,11 @@ declare namespace API {
/** 电话 */ /** 电话 */
phone?: string; phone?: string;
/** 账单地址 */ /** 账单地址 */
billing?: any; billing?: UnifiedAddressDTO;
/** 收货地址 */ /** 收货地址 */
shipping?: any; shipping?: UnifiedAddressDTO;
/** 原始数据 */ /** 原始数据 */
raw?: any; raw?: Record<string, any>;
}; };
type UnifiedCustomerPaginationDTO = { type UnifiedCustomerPaginationDTO = {
@ -2301,14 +2432,14 @@ declare namespace API {
customer_name?: string; customer_name?: string;
/** 客户邮箱 */ /** 客户邮箱 */
email?: string; email?: string;
/** 订单项 */ /** 订单项(具体的商品) */
line_items?: any; line_items?: UnifiedOrderLineItemDTO[];
/** 销售项(兼容前端) */ /** 销售项(兼容前端) */
sales?: any; sales?: UnifiedOrderLineItemDTO[];
/** 账单地址 */ /** 账单地址 */
billing?: any; billing?: UnifiedAddressDTO;
/** 收货地址 */ /** 收货地址 */
shipping?: any; shipping?: UnifiedAddressDTO;
/** 账单地址全称 */ /** 账单地址全称 */
billing_full_address?: string; billing_full_address?: string;
/** 收货地址全称 */ /** 收货地址全称 */
@ -2320,7 +2451,24 @@ declare namespace API {
/** 更新时间 */ /** 更新时间 */
date_modified?: string; date_modified?: string;
/** 原始数据 */ /** 原始数据 */
raw?: any; raw?: Record<string, any>;
};
type UnifiedOrderLineItemDTO = {
/** 订单项ID */
id?: Record<string, any>;
/** 产品名称 */
name?: string;
/** 产品ID */
product_id?: Record<string, any>;
/** 变体ID */
variation_id?: Record<string, any>;
/** 数量 */
quantity?: number;
/** 总计 */
total?: string;
/** SKU */
sku?: string;
}; };
type UnifiedOrderPaginationDTO = { type UnifiedOrderPaginationDTO = {
@ -2338,6 +2486,21 @@ declare namespace API {
totalPages?: number; totalPages?: number;
}; };
type UnifiedProductAttributeDTO = {
/** 属性ID */
id?: Record<string, any>;
/** 属性名称 */
name?: string;
/** 属性位置 */
position?: number;
/** 对变体是否可见 */
visible?: boolean;
/** 是否为变体属性 */
variation?: boolean;
/** 属性选项 */
options?: string[];
};
type UnifiedProductDTO = { type UnifiedProductDTO = {
/** 产品ID */ /** 产品ID */
id?: Record<string, any>; id?: Record<string, any>;
@ -2362,17 +2525,21 @@ declare namespace API {
/** 产品图片 */ /** 产品图片 */
images?: UnifiedImageDTO[]; images?: UnifiedImageDTO[];
/** 产品标签 */ /** 产品标签 */
tags?: any; tags?: UnifiedTagDTO[];
/** 产品分类 */
categories?: UnifiedCategoryDTO[];
/** 产品属性 */ /** 产品属性 */
attributes?: any; attributes?: UnifiedProductAttributeDTO[];
/** 产品变体 */ /** 产品变体 */
variations?: any; variations?: UnifiedProductVariationDTO[];
/** 创建时间 */ /** 创建时间 */
date_created?: string; date_created?: string;
/** 更新时间 */ /** 更新时间 */
date_modified?: string; date_modified?: string;
/** 产品链接 */
frontendUrl?: string;
/** 原始数据(保留备用) */ /** 原始数据(保留备用) */
raw?: any; raw?: Record<string, any>;
}; };
type UnifiedProductPaginationDTO = { type UnifiedProductPaginationDTO = {
@ -2390,6 +2557,59 @@ declare namespace API {
totalPages?: number; totalPages?: number;
}; };
type UnifiedProductVariationDTO = {
/** 变体ID */
id?: Record<string, any>;
/** 变体SKU */
sku?: string;
/** 常规价格 */
regular_price?: string;
/** 销售价格 */
sale_price?: string;
/** 当前价格 */
price?: string;
/** 库存状态 */
stock_status?: string;
/** 库存数量 */
stock_quantity?: number;
/** 变体图片 */
image?: UnifiedImageDTO;
};
type UnifiedReviewDTO = {
/** 评论ID */
id?: Record<string, any>;
/** 产品ID */
product_id?: Record<string, any>;
/** 评论者 */
author?: string;
/** 评论者邮箱 */
email?: string;
/** 评论内容 */
content?: string;
/** 评分 */
rating?: number;
/** 状态 */
status?: string;
/** 创建时间 */
date_created?: string;
};
type UnifiedReviewPaginationDTO = {
/** 列表数据 */
items?: UnifiedReviewDTO[];
/** 总数 */
total?: number;
/** 当前页 */
page?: number;
/** 每页数量 */
per_page?: number;
/** 每页数量别名 */
page_size?: number;
/** 总页数 */
totalPages?: number;
};
type UnifiedSearchParamsDTO = { type UnifiedSearchParamsDTO = {
/** 页码 */ /** 页码 */
page?: number; page?: number;
@ -2404,9 +2624,9 @@ declare namespace API {
/** 客户ID,用于筛选订单 */ /** 客户ID,用于筛选订单 */
customer_id?: number; customer_id?: number;
/** 过滤条件对象 */ /** 过滤条件对象 */
where?: any; where?: Record<string, any>;
/** 排序对象,例如 { "sku": "desc" } */ /** 排序对象,例如 { "sku": "desc" } */
order?: any; order?: Record<string, any>;
/** 排序字段(兼容旧入参) */ /** 排序字段(兼容旧入参) */
orderby?: string; orderby?: string;
/** 排序方式(兼容旧入参) */ /** 排序方式(兼容旧入参) */
@ -2435,9 +2655,9 @@ declare namespace API {
/** 下次支付时间 */ /** 下次支付时间 */
next_payment_date?: string; next_payment_date?: string;
/** 订单项 */ /** 订单项 */
line_items?: any; line_items?: UnifiedOrderLineItemDTO[];
/** 原始数据 */ /** 原始数据 */
raw?: any; raw?: Record<string, any>;
}; };
type UnifiedSubscriptionPaginationDTO = { type UnifiedSubscriptionPaginationDTO = {
@ -2455,6 +2675,13 @@ declare namespace API {
totalPages?: number; totalPages?: number;
}; };
type UnifiedTagDTO = {
/** 标签ID */
id?: Record<string, any>;
/** 标签名称 */
name?: string;
};
type UpdateAreaDTO = { type UpdateAreaDTO = {
/** 编码 */ /** 编码 */
code?: string; code?: string;
@ -2499,6 +2726,15 @@ declare namespace API {
items?: PurchaseOrderItem[]; items?: PurchaseOrderItem[];
}; };
type UpdateReviewDTO = {
/** 评论内容 */
content?: string;
/** 评分 (1-5) */
rating?: number;
/** 状态 */
status?: 'pending' | 'approved' | 'rejected';
};
type UpdateSiteDTO = { type UpdateSiteDTO = {
/** 区域 */ /** 区域 */
areas?: any; areas?: any;