forked from yoone/WEB
Compare commits
10 Commits
af1cebe29d
...
67aa625785
| Author | SHA1 | Date |
|---|---|---|
|
|
67aa625785 | |
|
|
1d9838f72e | |
|
|
3690fd00f7 | |
|
|
1fbfd95003 | |
|
|
2471eb5299 | |
|
|
d78b587035 | |
|
|
2ee8964bce | |
|
|
d2a84a9a4a | |
|
|
04b9fed6f7 | |
|
|
49e3049681 |
23
.umirc.ts
23
.umirc.ts
|
|
@ -134,9 +134,14 @@ export default defineConfig({
|
|||
},
|
||||
{
|
||||
name: '数据分析列表',
|
||||
path: '/customer/statistic',
|
||||
component: './Customer/Statistic',
|
||||
path: '/customer/statistic/list',
|
||||
component: './Customer/StatisticList',
|
||||
},
|
||||
// {
|
||||
// name: '客户统计',
|
||||
// path: '/customer/statistic/home',
|
||||
// component: './Customer/Statistic',
|
||||
// }
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -149,11 +154,6 @@ export default defineConfig({
|
|||
path: '/product/list',
|
||||
component: './Product/List',
|
||||
},
|
||||
{
|
||||
name: '产品属性排列',
|
||||
path: '/product/permutation',
|
||||
component: './Product/Permutation',
|
||||
},
|
||||
{
|
||||
name: '产品分类',
|
||||
path: '/product/category',
|
||||
|
|
@ -164,10 +164,15 @@ export default defineConfig({
|
|||
path: '/product/attribute',
|
||||
component: './Product/Attribute',
|
||||
},
|
||||
{
|
||||
name: '产品属性排列',
|
||||
path: '/product/permutation',
|
||||
component: './Product/Permutation',
|
||||
},
|
||||
{
|
||||
name: '产品品牌空间',
|
||||
path: '/product/brandspace',
|
||||
component: './Product/BrandSpace',
|
||||
path: '/product/groupBy',
|
||||
component: './Product/GroupBy',
|
||||
},
|
||||
// sync
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import {
|
|||
ActionType,
|
||||
DrawerForm,
|
||||
ProForm,
|
||||
ProFormSelect,
|
||||
ProFormDateRangePicker,
|
||||
ProFormSelect,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Button } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
|
|
@ -24,7 +24,12 @@ interface SyncFormProps {
|
|||
* @param {SyncFormProps} props 组件属性
|
||||
* @returns {React.ReactElement} 抽屉表单
|
||||
*/
|
||||
const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId, dateRange }) => {
|
||||
const SyncForm: React.FC<SyncFormProps> = ({
|
||||
tableRef,
|
||||
onFinish,
|
||||
siteId,
|
||||
dateRange,
|
||||
}) => {
|
||||
// 使用 antd 的 App 组件提供的 message API
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
|
|
@ -55,7 +60,6 @@ const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId, dateRan
|
|||
initialValues={{
|
||||
dateRange: [dayjs().subtract(1, 'week'), dayjs()],
|
||||
}}
|
||||
|
||||
title="同步订单"
|
||||
// 表单的触发器,一个带图标的按钮
|
||||
trigger={
|
||||
|
|
@ -74,7 +78,6 @@ const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId, dateRan
|
|||
onFinish={onFinish}
|
||||
>
|
||||
<ProForm.Group>
|
||||
|
||||
{/* 站点选择框 */}
|
||||
<ProFormSelect
|
||||
name="siteId"
|
||||
|
|
@ -96,7 +99,6 @@ const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId, dateRan
|
|||
name="dateRange"
|
||||
label="同步日期范围"
|
||||
placeholder={['开始日期', '结束日期']}
|
||||
|
||||
transform={(value) => {
|
||||
return {
|
||||
dateRange: value,
|
||||
|
|
|
|||
|
|
@ -1,289 +1,7 @@
|
|||
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) => {
|
||||
export default function Statistic() {
|
||||
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}
|
||||
/>
|
||||
<div>
|
||||
<h1>客户统计</h1>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
dataIndex: 'phone',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => record?.billing.phone || record?.shipping.phone,
|
||||
},
|
||||
{
|
||||
title: '账单地址',
|
||||
dataIndex: 'billing',
|
||||
render: (_, record) =>
|
||||
JSON.stringify(record?.billing || record?.shipping),
|
||||
},
|
||||
{
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,289 @@
|
|||
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: '联系电话',
|
||||
dataIndex: 'phone',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => record?.billing.phone || record?.shipping.phone,
|
||||
},
|
||||
{
|
||||
title: '账单地址',
|
||||
dataIndex: 'billing',
|
||||
render: (_, record) =>
|
||||
JSON.stringify(record?.billing || record?.shipping),
|
||||
},
|
||||
{
|
||||
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;
|
||||
|
|
@ -52,12 +52,6 @@ const DictItemImportButton: React.FC<DictItemImportButtonProps> = ({
|
|||
});
|
||||
}
|
||||
|
||||
// 检查返回结果是否包含 success 字段
|
||||
if (result && result.success !== undefined && !result.success) {
|
||||
throw new Error(result.message || '导入失败');
|
||||
}
|
||||
onSuccess?.(result);
|
||||
|
||||
// 显示导入结果详情
|
||||
showImportResult(result);
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ import {
|
|||
} from 'antd';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import RelatedOrders from '../../Subscription/Orders/RelatedOrders';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const ListPage: React.FC = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
|
@ -502,7 +503,10 @@ const ListPage: React.FC = () => {
|
|||
success,
|
||||
message: errMsg,
|
||||
data,
|
||||
} = await ordercontrollerSyncorders(values,{after:values.dateRange?.[0]+'T00:00:00Z',before:values.dateRange?.[1]+'T23:59:59Z'});
|
||||
} = await ordercontrollerSyncorders(values, {
|
||||
after: values.dateRange?.[0] + 'T00:00:00Z',
|
||||
before: values.dateRange?.[1] + 'T23:59:59Z',
|
||||
});
|
||||
if (!success) {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
|
@ -990,7 +994,7 @@ const Detail: React.FC<{
|
|||
<ul>
|
||||
{record?.items?.map((item: any) => (
|
||||
<li key={item.id}>
|
||||
{item.name}:{item.quantity}
|
||||
{item.name}({item.sku}):{item.quantity}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
@ -1015,7 +1019,7 @@ const Detail: React.FC<{
|
|||
<ul>
|
||||
{record?.sales?.map((item: any) => (
|
||||
<li key={item.id}>
|
||||
{item.name}:{item.quantity}
|
||||
{item.name}({item.sku}):{item.quantity}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
@ -1264,7 +1268,10 @@ const Shipping: React.FC<{
|
|||
const [rates, setRates] = useState<API.RateDTO[]>([]);
|
||||
const [ratesLoading, setRatesLoading] = useState(false);
|
||||
const { message } = App.useApp();
|
||||
|
||||
const [shipmentPlatforms, setShipmentPlatforms] = useState([
|
||||
{ label: 'uniuni', value: 'uniuni' },
|
||||
{ label: 'tms.freightwaves', value: 'freightwaves' },
|
||||
]);
|
||||
return (
|
||||
<ModalForm
|
||||
formRef={formRef}
|
||||
|
|
@ -1293,6 +1300,7 @@ const Shipping: React.FC<{
|
|||
await ordercontrollerGetorderdetail({
|
||||
orderId: id,
|
||||
});
|
||||
console.log('success data',success,data)
|
||||
if (!success || !data) return {};
|
||||
data.sales = data.sales?.reduce(
|
||||
(acc: API.OrderSale[], cur: API.OrderSale) => {
|
||||
|
|
@ -1315,7 +1323,8 @@ const Shipping: React.FC<{
|
|||
if (reShipping) data.sales = [{}];
|
||||
let shipmentInfo = localStorage.getItem('shipmentInfo');
|
||||
if (shipmentInfo) shipmentInfo = JSON.parse(shipmentInfo);
|
||||
return {
|
||||
const a = {
|
||||
shipmentPlatform: 'uniuni',
|
||||
...data,
|
||||
// payment_method_id: shipmentInfo?.payment_method_id,
|
||||
stockPointId: shipmentInfo?.stockPointId,
|
||||
|
|
@ -1375,6 +1384,8 @@ const Shipping: React.FC<{
|
|||
},
|
||||
},
|
||||
};
|
||||
console.log('data',a)
|
||||
return a
|
||||
}}
|
||||
onFinish={async ({
|
||||
customer_note,
|
||||
|
|
@ -1438,7 +1449,18 @@ const Shipping: React.FC<{
|
|||
}
|
||||
}}
|
||||
>
|
||||
<ProFormText label="订单号" readonly name={'externalOrderId'} />
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<ProFormSelect
|
||||
name="shipmentPlatform"
|
||||
label="发货平台"
|
||||
options={shipmentPlatforms}
|
||||
placeholder="请选择发货平台"
|
||||
rules={[{ required: true, message: '请选择一个选项' }]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<ProFormText label="订单号" readonly name='externalOrderId' />
|
||||
<ProFormText label="客户备注" readonly name="customer_note" />
|
||||
<ProFormList
|
||||
label="后台备注"
|
||||
|
|
@ -1570,16 +1592,20 @@ const Shipping: React.FC<{
|
|||
title="发货信息"
|
||||
extra={
|
||||
<AddressPicker
|
||||
onChange={({
|
||||
onChange={(row) => {
|
||||
console.log(row);
|
||||
const {
|
||||
address,
|
||||
phone_number,
|
||||
phone_number_extension,
|
||||
stockPointId,
|
||||
}) => {
|
||||
} = row;
|
||||
formRef?.current?.setFieldsValue({
|
||||
stockPointId,
|
||||
// address_id: row.id,
|
||||
details: {
|
||||
origin: {
|
||||
|
||||
address,
|
||||
phone_number: {
|
||||
phone: phone_number,
|
||||
|
|
@ -1592,6 +1618,11 @@ const Shipping: React.FC<{
|
|||
/>
|
||||
}
|
||||
>
|
||||
{/* <ProFormText
|
||||
label="address_id"
|
||||
name={'address_id'}
|
||||
rules={[{ required: true, message: '请输入ID' }]}
|
||||
/> */}
|
||||
<ProFormSelect
|
||||
name="stockPointId"
|
||||
width="md"
|
||||
|
|
@ -1684,7 +1715,7 @@ const Shipping: React.FC<{
|
|||
<ProFormText
|
||||
label="公司名称"
|
||||
name={['details', 'destination', 'name']}
|
||||
rules={[{ required: true, message: '请输入公司名称' }]}
|
||||
rules={[{ message: '请输入公司名称' }]}
|
||||
/>
|
||||
<ProFormItem
|
||||
name={['details', 'destination', 'address', 'country']}
|
||||
|
|
@ -2014,6 +2045,7 @@ const Shipping: React.FC<{
|
|||
details.origin.phone_number.number =
|
||||
details.origin.phone_number.phone;
|
||||
const res = await logisticscontrollerGetshipmentfee({
|
||||
shipmentPlatform: data.shipmentPlatform,
|
||||
stockPointId: data.stockPointId,
|
||||
|
||||
sender: details.origin.contact_name,
|
||||
|
|
@ -2340,7 +2372,7 @@ const CreateOrder: React.FC<{
|
|||
<ProFormText
|
||||
label="公司名称"
|
||||
name={['billing', 'company']}
|
||||
rules={[{ required: true, message: '请输入公司名称' }]}
|
||||
rules={[{ message: '请输入公司名称' }]}
|
||||
/>
|
||||
<ProFormItem
|
||||
name={['billing', 'country']}
|
||||
|
|
@ -2426,6 +2458,11 @@ const AddressPicker: React.FC<{
|
|||
value: item.id,
|
||||
}));
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'id',
|
||||
dataIndex: ['id'],
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '地区',
|
||||
|
|
|
|||
|
|
@ -1,452 +0,0 @@
|
|||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { request } from '@umijs/max';
|
||||
import {
|
||||
Card,
|
||||
Col,
|
||||
Image,
|
||||
Layout,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Typography,
|
||||
message,
|
||||
} from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
const { Title, Text } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
// Define interfaces
|
||||
interface Brand {
|
||||
id: number;
|
||||
name: string;
|
||||
shortName?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
interface Attribute {
|
||||
id: number;
|
||||
name: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface AttributeValue {
|
||||
id: number;
|
||||
name: string;
|
||||
title: string;
|
||||
titleCN?: string;
|
||||
value?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
interface Product {
|
||||
id: number;
|
||||
sku: string;
|
||||
name: string;
|
||||
image?: string;
|
||||
brandId: number;
|
||||
brandName: string;
|
||||
attributes: { [key: string]: any };
|
||||
}
|
||||
|
||||
const BrandSpace: React.FC = () => {
|
||||
// State management
|
||||
const [brands, setBrands] = useState<Brand[]>([]);
|
||||
const [selectedBrand, setSelectedBrand] = useState<number | null>(null);
|
||||
|
||||
const [attributes, setAttributes] = useState<Attribute[]>([]);
|
||||
const [selectedAttribute, setSelectedAttribute] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const [attributeValues, setAttributeValues] = useState<AttributeValue[]>([]);
|
||||
const [selectedAttributeValue, setSelectedAttributeValue] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Fetch brands list
|
||||
const fetchBrands = async () => {
|
||||
try {
|
||||
const response = await request('/dict/items', {
|
||||
params: { dictId: 'brand' }, // Assuming brand is a dict
|
||||
});
|
||||
const brandList = Array.isArray(response)
|
||||
? response
|
||||
: response?.data || [];
|
||||
setBrands(brandList);
|
||||
|
||||
// Set default brand to "yoone" if exists
|
||||
const defaultBrand = brandList.find((brand) => brand.name === 'yoone');
|
||||
if (defaultBrand) {
|
||||
setSelectedBrand(defaultBrand.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch brands:', error);
|
||||
message.error('获取品牌列表失败');
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch attributes list
|
||||
const fetchAttributes = async () => {
|
||||
try {
|
||||
// Get all dicts that are attributes (excluding non-attribute dicts)
|
||||
const response = await request('/dict/list');
|
||||
const dictList = Array.isArray(response)
|
||||
? response
|
||||
: response?.data || [];
|
||||
|
||||
// Filter out non-attribute dicts (assuming attributes are specific dicts)
|
||||
const attributeDicts = dictList.filter((dict: any) =>
|
||||
['strength', 'flavor', 'humidity', 'size', 'version'].includes(
|
||||
dict.name,
|
||||
),
|
||||
);
|
||||
|
||||
setAttributes(attributeDicts);
|
||||
|
||||
// Set default attribute to strength if exists
|
||||
const defaultAttribute = attributeDicts.find(
|
||||
(attr) => attr.name === 'strength',
|
||||
);
|
||||
if (defaultAttribute) {
|
||||
setSelectedAttribute(defaultAttribute.name);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch attributes:', error);
|
||||
message.error('获取属性列表失败');
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch attribute values based on selected attribute
|
||||
const fetchAttributeValues = async (attributeName: string) => {
|
||||
try {
|
||||
const response = await request('/dict/items', {
|
||||
params: { dictId: attributeName },
|
||||
});
|
||||
const values = Array.isArray(response) ? response : response?.data || [];
|
||||
setAttributeValues(values);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch attribute values:', error);
|
||||
message.error('获取属性值列表失败');
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch products based on filters
|
||||
const fetchProducts = async () => {
|
||||
if (!selectedBrand) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const params: any = {
|
||||
brandId: selectedBrand,
|
||||
};
|
||||
|
||||
// Add attribute filter if selected
|
||||
if (selectedAttribute) {
|
||||
// If attribute value is selected, filter by both attribute and value
|
||||
if (selectedAttributeValue) {
|
||||
params[selectedAttribute] = selectedAttributeValue;
|
||||
} else {
|
||||
// If only attribute is selected, filter by attribute presence
|
||||
params[selectedAttribute] = 'hasValue';
|
||||
}
|
||||
}
|
||||
|
||||
const response = await request('/product/list', {
|
||||
params,
|
||||
});
|
||||
|
||||
const productList = Array.isArray(response)
|
||||
? response
|
||||
: response?.data || [];
|
||||
setProducts(productList);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch products:', error);
|
||||
message.error('获取产品列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Initial data fetch
|
||||
useEffect(() => {
|
||||
fetchBrands();
|
||||
fetchAttributes();
|
||||
}, []);
|
||||
|
||||
// Fetch attribute values when attribute changes
|
||||
useEffect(() => {
|
||||
if (selectedAttribute) {
|
||||
fetchAttributeValues(selectedAttribute);
|
||||
setSelectedAttributeValue(null); // Reset selected value when attribute changes
|
||||
}
|
||||
}, [selectedAttribute]);
|
||||
|
||||
// Fetch products when filters change
|
||||
useEffect(() => {
|
||||
fetchProducts();
|
||||
}, [selectedBrand, selectedAttribute, selectedAttributeValue]);
|
||||
|
||||
// Handle brand selection change
|
||||
const handleBrandChange = (value: number) => {
|
||||
setSelectedBrand(value);
|
||||
};
|
||||
|
||||
// Handle attribute selection change
|
||||
const handleAttributeChange = (value: string) => {
|
||||
setSelectedAttribute(value);
|
||||
};
|
||||
|
||||
// Handle attribute value selection change
|
||||
const handleAttributeValueChange = (value: number) => {
|
||||
setSelectedAttributeValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer title="品牌空间">
|
||||
<Layout style={{ minHeight: 'calc(100vh - 64px)', background: '#fff' }}>
|
||||
{/* Top Brand Selection */}
|
||||
<div style={{ padding: '16px', borderBottom: '1px solid #f0f0f0' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
选择品牌
|
||||
</Title>
|
||||
<Select
|
||||
placeholder="请选择品牌"
|
||||
style={{ width: 300 }}
|
||||
value={selectedBrand}
|
||||
onChange={handleBrandChange}
|
||||
allowClear
|
||||
>
|
||||
{brands.map((brand) => (
|
||||
<Option key={brand.id} value={brand.id}>
|
||||
{brand.name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Layout>
|
||||
{/* Left Attribute Selection */}
|
||||
<Sider
|
||||
width={240}
|
||||
style={{ background: '#fafafa', borderRight: '1px solid #f0f0f0' }}
|
||||
>
|
||||
<div style={{ padding: '16px' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Title level={5} style={{ margin: 0 }}>
|
||||
选择属性
|
||||
</Title>
|
||||
<Select
|
||||
placeholder="请选择属性类型"
|
||||
style={{ width: '100%' }}
|
||||
value={selectedAttribute}
|
||||
onChange={handleAttributeChange}
|
||||
allowClear
|
||||
>
|
||||
{attributes.map((attr) => (
|
||||
<Option key={attr.id} value={attr.name}>
|
||||
{attr.title}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
{selectedAttribute && (
|
||||
<>
|
||||
<Title level={5} style={{ margin: '16px 0 8px 0' }}>
|
||||
属性值
|
||||
</Title>
|
||||
<Select
|
||||
placeholder={`请选择${
|
||||
attributes.find((a) => a.name === selectedAttribute)
|
||||
?.title
|
||||
}`}
|
||||
style={{ width: '100%' }}
|
||||
value={selectedAttributeValue}
|
||||
onChange={handleAttributeValueChange}
|
||||
allowClear
|
||||
>
|
||||
{attributeValues.map((value) => (
|
||||
<Option key={value.id} value={value.id}>
|
||||
<Space>
|
||||
{value.image && (
|
||||
<Image
|
||||
src={value.image}
|
||||
style={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
objectFit: 'cover',
|
||||
borderRadius: 4,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<span>
|
||||
{value.titleCN || value.title || value.name}
|
||||
</span>
|
||||
</Space>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Filter Summary */}
|
||||
{selectedBrand && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 24,
|
||||
padding: 12,
|
||||
background: '#fff',
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Text strong>当前筛选条件:</Text>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Text type="secondary">品牌: </Text>
|
||||
<Text>
|
||||
{brands.find((b) => b.id === selectedBrand)?.name}
|
||||
</Text>
|
||||
</div>
|
||||
{selectedAttribute && (
|
||||
<div style={{ marginTop: 4 }}>
|
||||
<Text type="secondary">
|
||||
{
|
||||
attributes.find((a) => a.name === selectedAttribute)
|
||||
?.title
|
||||
}
|
||||
:
|
||||
</Text>
|
||||
<Text>
|
||||
{selectedAttributeValue
|
||||
? attributeValues.find(
|
||||
(v) => v.id === selectedAttributeValue,
|
||||
)?.titleCN ||
|
||||
attributeValues.find(
|
||||
(v) => v.id === selectedAttributeValue,
|
||||
)?.title
|
||||
: '所有值'}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
</Sider>
|
||||
|
||||
{/* Main Content - Product List */}
|
||||
<Content style={{ padding: '16px' }}>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
产品列表
|
||||
<Text type="secondary" style={{ fontSize: 16, marginLeft: 8 }}>
|
||||
({products.length} 个产品)
|
||||
</Text>
|
||||
</Title>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', padding: '64px' }}>
|
||||
<Text>加载中...</Text>
|
||||
</div>
|
||||
) : products.length > 0 ? (
|
||||
<Row gutter={[16, 16]}>
|
||||
{products.map((product) => (
|
||||
<Col xs={24} sm={12} md={8} lg={6} key={product.id}>
|
||||
<Card
|
||||
hoverable
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: 200,
|
||||
overflow: 'hidden',
|
||||
marginBottom: 12,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={
|
||||
product.image ||
|
||||
'https://via.placeholder.com/200x200?text=No+Image'
|
||||
}
|
||||
alt={product.name}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{ fontSize: 12, marginBottom: 4 }}
|
||||
>
|
||||
{product.sku}
|
||||
</Text>
|
||||
<Title
|
||||
level={5}
|
||||
style={{
|
||||
margin: '4px 0',
|
||||
fontSize: 16,
|
||||
height: 48,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{product.name}
|
||||
</Title>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
strong
|
||||
style={{ fontSize: 18, color: '#ff4d4f' }}
|
||||
>
|
||||
¥{product.price || '--'}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: '64px',
|
||||
background: '#fafafa',
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Text type="secondary">暂无符合条件的产品</Text>
|
||||
</div>
|
||||
)}
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrandSpace;
|
||||
|
|
@ -415,12 +415,11 @@ const CsvTool: React.FC = () => {
|
|||
}
|
||||
|
||||
// 如果type为single且启用了生成bundle SKU,则添加quantity
|
||||
if (
|
||||
quantity
|
||||
) {
|
||||
console.log(quantity, attributeMappings.quantities[quantity])
|
||||
if (quantity) {
|
||||
// 使用quantity的shortName,如果没有则使用quantity但匹配 4 个零
|
||||
const quantityShortName = attributeMappings.quantities[quantity] || Number(quantity).toString().padStart(4, '0');
|
||||
const quantityShortName =
|
||||
attributeMappings.quantities[quantity] ||
|
||||
Number(quantity).toString().padStart(4, '0');
|
||||
skuComponents.push(quantityShortName);
|
||||
}
|
||||
// 合并所有组件,使用短横线分隔
|
||||
|
|
@ -464,9 +463,7 @@ const CsvTool: React.FC = () => {
|
|||
if (size) nameComponents.push(size);
|
||||
|
||||
// 如果有数量且类型为bundle或者生成bundle的single产品,则添加数量
|
||||
if (
|
||||
type==='bundle' && quantity
|
||||
) {
|
||||
if (type === 'bundle' && quantity) {
|
||||
nameComponents.push(String(quantity));
|
||||
}
|
||||
|
||||
|
|
@ -570,7 +567,7 @@ const CsvTool: React.FC = () => {
|
|||
|
||||
// Determine which data to use for processing and download
|
||||
let finalData = dataWithSku;
|
||||
|
||||
console.log('generateBundleSkuForSingle', generateBundleSkuForSingle);
|
||||
// If generateBundleSkuForSingle is enabled, generate bundle products for single products
|
||||
if (generateBundleSkuForSingle) {
|
||||
// Filter out single records
|
||||
|
|
@ -579,7 +576,9 @@ const CsvTool: React.FC = () => {
|
|||
);
|
||||
|
||||
// Get quantity values from the config (same source as other attributes like brand)
|
||||
const quantityValues = config.quantities.map(quantity=>quantity.name)
|
||||
const quantityValues = config.quantities.map(
|
||||
(quantity) => quantity.name,
|
||||
);
|
||||
|
||||
// Generate bundle products for each single record and quantity
|
||||
const generatedBundleRecords = singleRecords.flatMap((singleRecord) => {
|
||||
|
|
@ -773,7 +772,11 @@ const CsvTool: React.FC = () => {
|
|||
valuePropName="checked"
|
||||
initialValue={true}
|
||||
>
|
||||
<Checkbox onChange={setGenerateBundleSkuForSingle}>
|
||||
<Checkbox
|
||||
onChange={(e) =>
|
||||
setGenerateBundleSkuForSingle(e.target.checked)
|
||||
}
|
||||
>
|
||||
启用为single类型生成bundle SKU
|
||||
</Checkbox>
|
||||
</ProForm.Item>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,368 @@
|
|||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { PageContainer, ProFromSelect } from '@ant-design/pro-components';
|
||||
import { Card, Collapse, Divider, Image, Select, Space, Typography, message } from 'antd';
|
||||
import { categorycontrollerGetall } from '@/servers/api/category';
|
||||
import { productcontrollerGetproductlistgrouped } from '@/servers/api/product';
|
||||
import { dictcontrollerGetdictitems } from '@/servers/api/dict';
|
||||
|
||||
// Define interfaces
|
||||
interface Category {
|
||||
id: number;
|
||||
name: string;
|
||||
title: string;
|
||||
attributes: string[]; // List of attribute names for this category
|
||||
}
|
||||
interface Attribute {
|
||||
id: number;
|
||||
name: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface AttributeValue {
|
||||
id: number;
|
||||
name: string;
|
||||
title: string;
|
||||
titleCN?: string;
|
||||
value?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
interface Product {
|
||||
id: number;
|
||||
sku: string;
|
||||
name: string;
|
||||
image?: string;
|
||||
brandId: number;
|
||||
brandName: string;
|
||||
attributes: { [key: string]: number }; // attribute name to attribute value id mapping
|
||||
price?: number;
|
||||
}
|
||||
|
||||
// Grouped products by attribute value
|
||||
interface GroupedProducts {
|
||||
[attributeValueId: string]: Product[];
|
||||
}
|
||||
|
||||
// ProductCard component for displaying single product
|
||||
const ProductCard: React.FC<{ product: Product }> = ({ product }) => {
|
||||
return (
|
||||
<Card hoverable style={{ width: 240 }}>
|
||||
{/* <div style={{ height: 180, overflow: 'hidden', marginBottom: '12px' }}>
|
||||
<Image
|
||||
src={product.image || 'https://via.placeholder.com/240x180?text=No+Image'}
|
||||
alt={product.name}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
</div> */}
|
||||
<div>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: '4px' }}>
|
||||
{product.sku}
|
||||
</Typography.Text>
|
||||
<Typography.Text ellipsis style={{ width: '100%', display: 'block', marginBottom: '8px' }}>
|
||||
{product.name}
|
||||
</Typography.Text>
|
||||
<Typography.Text strong style={{ fontSize: 16, color: '#ff4d4f', display: 'block' }}>
|
||||
¥{product.price || '--'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// ProductGroup component for displaying grouped products
|
||||
const ProductGroup: React.FC<{
|
||||
attributeValueId: string;
|
||||
groupProducts: Product[];
|
||||
attributeValue: AttributeValue | undefined;
|
||||
attributeName: string;
|
||||
}> = ({ attributeValueId, groupProducts, attributeValue }) => {
|
||||
// State for collapse control
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
|
||||
// Create collapse panel header
|
||||
const panelHeader = (
|
||||
<Space>
|
||||
{attributeValue?.image && (
|
||||
<Image
|
||||
src={attributeValue.image}
|
||||
style={{ width: 24, height: 24, objectFit: 'cover', borderRadius: 4 }}
|
||||
/>
|
||||
)}
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||
<span>
|
||||
{attributeValue?.titleCN || attributeValue?.title || attributeValue?.name || attributeValueId||'未知'}
|
||||
(共 {groupProducts.length} 个产品)
|
||||
</span>
|
||||
</Typography.Title>
|
||||
</Space>
|
||||
);
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
activeKey={isCollapsed ? [] : [attributeValueId]}
|
||||
onChange={(key) => setIsCollapsed(Array.isArray(key) && key.length === 0)}
|
||||
ghost
|
||||
bordered={false}
|
||||
items={[
|
||||
{
|
||||
key: attributeValueId,
|
||||
label: panelHeader,
|
||||
children: (
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '16px', paddingTop: '8px' }}>
|
||||
{groupProducts.map((product) => (
|
||||
<ProductCard key={product.id} product={product} />
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// Main ProductGroupBy component
|
||||
const ProductGroupBy: React.FC = () => {
|
||||
// State management
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
// Store selected values for each attribute
|
||||
const [attributeFilters, setAttributeFilters] = useState<{ [key: string]: number | null }>({});
|
||||
|
||||
// Group by attribute
|
||||
const [groupByAttribute, setGroupByAttribute] = useState<string | null>(null);
|
||||
|
||||
// Products
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
const [groupedProducts, setGroupedProducts] = useState<GroupedProducts>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Extract all unique attributes from categories
|
||||
const categoryAttributes = useMemo(() => {
|
||||
if (!selectedCategory) return [];
|
||||
const categoryItem = categories.find((category: any) => category.name === selectedCategory);
|
||||
if (!categoryItem) return [];
|
||||
const attributesList: Attribute[] = categoryItem.attributes.map((attribute: any, index) => ({
|
||||
...attribute.attributeDict,
|
||||
id: index + 1,
|
||||
}));
|
||||
return attributesList;
|
||||
}, [selectedCategory]);
|
||||
|
||||
// Fetch categories list
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const response = await categorycontrollerGetall();
|
||||
const rawCategories = Array.isArray(response) ? response : response?.data || [];
|
||||
setCategories(rawCategories);
|
||||
|
||||
// Set default category
|
||||
if (rawCategories.length > 0) {
|
||||
const defaultCategory = rawCategories.find((category: any) => category.name === 'nicotine-pouches');
|
||||
setSelectedCategory(defaultCategory?.name || rawCategories[0].name);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch categories:', error);
|
||||
message.error('获取分类列表失败');
|
||||
}
|
||||
};
|
||||
|
||||
// Update category attributes when selected category changes
|
||||
useEffect(() => {
|
||||
if (!selectedCategory) return;
|
||||
|
||||
const category = categories.find(cat => cat.name === selectedCategory);
|
||||
if (!category) return;
|
||||
|
||||
// Get attributes for this category
|
||||
const attributesForCategory = categoryAttributes.filter(attr =>
|
||||
attr.name === 'brand' || category.attributes.includes(attr.name)
|
||||
);
|
||||
// Reset attribute filters when category changes
|
||||
const newFilters: { [key: string]: number | null } = {};
|
||||
attributesForCategory.forEach(attr => {
|
||||
newFilters[attr.name] = null;
|
||||
});
|
||||
setAttributeFilters(newFilters);
|
||||
|
||||
// Set default group by attribute
|
||||
if (attributesForCategory.length > 0) {
|
||||
setGroupByAttribute(attributesForCategory[0].name);
|
||||
}
|
||||
}, [selectedCategory, categories, categoryAttributes]);
|
||||
|
||||
// Handle attribute filter change
|
||||
const handleAttributeFilterChange = (attributeName: string, value: number | null) => {
|
||||
setAttributeFilters(prev => ({ ...prev, [attributeName]: value }));
|
||||
};
|
||||
|
||||
// Fetch products based on filters
|
||||
const fetchProducts = async () => {
|
||||
if (!selectedCategory || !groupByAttribute) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const params: any = {
|
||||
category: selectedCategory,
|
||||
groupBy: groupByAttribute
|
||||
};
|
||||
|
||||
|
||||
const response = await productcontrollerGetproductlistgrouped(params);
|
||||
const grouped = response?.data || {};
|
||||
setGroupedProducts(grouped);
|
||||
|
||||
// Flatten grouped products to get all products
|
||||
const allProducts = Object.values(grouped).flat() as Product[];
|
||||
setProducts(allProducts);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch grouped products:', error);
|
||||
message.error('获取分组产品列表失败');
|
||||
setProducts([]);
|
||||
setGroupedProducts({});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Initial data fetch
|
||||
useEffect(() => {
|
||||
fetchCategories();
|
||||
}, []);
|
||||
|
||||
// Fetch products when filters change
|
||||
useEffect(() => {
|
||||
fetchProducts();
|
||||
}, [selectedCategory, attributeFilters, groupByAttribute]);
|
||||
|
||||
// Destructure antd components
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
return (
|
||||
<PageContainer title="品牌空间">
|
||||
<div style={{ padding: '16px', background: '#fff' }}>
|
||||
{/* Filter Section */}
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<Title level={4} style={{ marginBottom: '16px' }}>筛选条件</Title>
|
||||
<Space direction="vertical" size="large">
|
||||
{/* Category Filter */}
|
||||
<div>
|
||||
<Text strong>选择分类:</Text>
|
||||
<Select
|
||||
placeholder="请选择分类"
|
||||
style={{ width: 300, marginLeft: '8px' }}
|
||||
value={selectedCategory}
|
||||
onChange={setSelectedCategory}
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="children"
|
||||
>
|
||||
{categories.map(category => (
|
||||
<Option key={category.id} value={category.name}>
|
||||
{category.title}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Attribute Filters */}
|
||||
{categoryAttributes.length > 0 && (
|
||||
<div>
|
||||
<Text strong>属性筛选:</Text>
|
||||
<Space direction="vertical" style={{ marginTop: '8px', width: '100%' }}>
|
||||
{categoryAttributes.map(attr => (
|
||||
<div key={attr.id} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Text style={{ width: '100px' }}>{attr.title}:</Text>
|
||||
<ProFromSelect
|
||||
placeholder={`请选择${attr.title}`}
|
||||
style={{ width: 300 }}
|
||||
value={attributeFilters[attr.name] || null}
|
||||
onChange={value => handleAttributeFilterChange(attr.name, value)}
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="children"
|
||||
request={async (params) => {
|
||||
try {
|
||||
console.log('params', params,attr);
|
||||
const response = await dictcontrollerGetdictitems({ dictId: attr.name });
|
||||
const rawValues = Array.isArray(response) ? response : response?.data?.items || [];
|
||||
const filteredValues = rawValues.filter((value: any) =>
|
||||
value.dictId === attr.name || value.dict?.id === attr.name || value.dict?.name === attr.name
|
||||
);
|
||||
return {
|
||||
options: filteredValues.map((value: any) => ({
|
||||
label: `${value.name}${value.titleCN || value.title}`,
|
||||
value: value.id
|
||||
}))
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch ${attr.title} values:`, error);
|
||||
message.error(`获取${attr.title}属性值失败`);
|
||||
return { options: [] };
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Group By Attribute */}
|
||||
{categoryAttributes.length > 0 && (
|
||||
<div>
|
||||
<Text strong>分组依据:</Text>
|
||||
<Select
|
||||
placeholder="请选择分组属性"
|
||||
style={{ width: 300, marginLeft: '8px' }}
|
||||
value={groupByAttribute}
|
||||
onChange={setGroupByAttribute}
|
||||
showSearch
|
||||
optionFilterProp="children"
|
||||
>
|
||||
{categoryAttributes.map(attr => (
|
||||
<Option key={attr.id} value={attr.name}>
|
||||
{attr.title}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Products Section */}
|
||||
<div>
|
||||
<Title level={4} style={{ marginBottom: '16px' }}>产品列表 ({products.length} 个产品)</Title>
|
||||
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', padding: '64px' }}>
|
||||
<Text>加载中...</Text>
|
||||
</div>
|
||||
) : groupByAttribute && Object.keys(groupedProducts).length > 0 ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
||||
{Object.entries(groupedProducts).map(([attrValueId, groupProducts]) => {
|
||||
return (
|
||||
<ProductGroup
|
||||
key={attrValueId}
|
||||
attributeValueId={attrValueId}
|
||||
groupProducts={groupProducts}
|
||||
// attributeValue={}
|
||||
attributeName={groupByAttribute!}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', padding: '64px', background: '#fafafa', borderRadius: 8 }}>
|
||||
<Text type="secondary">暂无产品</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductGroupBy;
|
||||
|
|
@ -234,6 +234,12 @@ const List: React.FC = () => {
|
|||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '图片',
|
||||
dataIndex: 'image',
|
||||
width: 100,
|
||||
valueType: 'image',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export interface SiteItem {
|
|||
const SiteList: React.FC = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<SiteItem | null>(null);
|
||||
const [editing, setEditing] = useState<SiteItem & { areas: string[] } | null>(null);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const [batchEditOpen, setBatchEditOpen] = useState(false);
|
||||
const [batchEditForm] = Form.useForm();
|
||||
|
|
@ -202,7 +202,11 @@ const SiteList: React.FC = () => {
|
|||
</a>
|
||||
),
|
||||
},
|
||||
{ title: 'webhook地址', dataIndex: 'webhookUrl', width: 280, hideInSearch: true },
|
||||
{
|
||||
title: 'webhook地址',
|
||||
dataIndex: 'webhookUrl',
|
||||
hideInSearch: true,
|
||||
},
|
||||
|
||||
{
|
||||
title: 'SKU 前缀',
|
||||
|
|
@ -288,7 +292,13 @@ const SiteList: React.FC = () => {
|
|||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setEditing(row);
|
||||
function normalEditing(row:SiteItem){
|
||||
return {
|
||||
...row,
|
||||
areas: row.areas?.map(area=>area.code) || [],
|
||||
}
|
||||
}
|
||||
setEditing(normalEditing(row));
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
|||
}}
|
||||
layout="vertical"
|
||||
>
|
||||
{JSON.stringify(initialValues)}
|
||||
<ProFormText
|
||||
name="name"
|
||||
label="名称"
|
||||
|
|
@ -169,7 +170,6 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
|
|||
label="区域"
|
||||
mode="multiple"
|
||||
placeholder="请选择区域"
|
||||
|
||||
showSearch
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const ShopLayout: React.FC = () => {
|
|||
const location = useLocation();
|
||||
|
||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||
const [editingSite, setEditingSite] = useState<SiteItem | null>(null);
|
||||
const [editingSite, setEditingSite] = useState<SiteItem & { areas: string[] } | null>(null);
|
||||
|
||||
const fetchSites = async () => {
|
||||
try {
|
||||
|
|
@ -95,7 +95,7 @@ const ShopLayout: React.FC = () => {
|
|||
<Row gutter={16} style={{ height: 'calc(100vh - 100px)' }}>
|
||||
<Col span={4} style={{ height: '100%' }}>
|
||||
<Sider
|
||||
style={{ background: 'white', height: '100%', overflow: 'hidden' }}
|
||||
style={{ background: 'white', height: '100%', overflow: 'hidden', zIndex: 1 }}
|
||||
>
|
||||
<div style={{ padding: '0 10px 16px' }}>
|
||||
<div
|
||||
|
|
@ -103,6 +103,7 @@ const ShopLayout: React.FC = () => {
|
|||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: '4px'
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
|
|
@ -119,13 +120,18 @@ const ShopLayout: React.FC = () => {
|
|||
/>
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
style={{ marginLeft: 8 }}
|
||||
onClick={() => {
|
||||
const currentSite = sites.find(
|
||||
(site) => site.id === Number(siteId),
|
||||
);
|
||||
if (currentSite) {
|
||||
setEditingSite(currentSite);
|
||||
function normalizeEditing(site: SiteItem) {
|
||||
return {
|
||||
...site,
|
||||
areas: site.areas?.map(area => area.code) || [],
|
||||
}
|
||||
}
|
||||
setEditingSite(normalizeEditing(currentSite));
|
||||
setEditModalOpen(true);
|
||||
} else {
|
||||
message.warning('请先选择一个店铺');
|
||||
|
|
|
|||
|
|
@ -228,21 +228,28 @@ const OrdersPage: React.FC = () => {
|
|||
// 遍历物流信息数组, 显示每个物流的提供商和单号
|
||||
return (
|
||||
<div>
|
||||
{record.fulfillments.map((item: any, index: number) => (
|
||||
{record.fulfillments.map((item, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{ display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<span>
|
||||
{item.tracking_provider
|
||||
? `快递方式: ${item.tracking_provider}`
|
||||
{item.shipping_provider
|
||||
? `快递方式: ${item.shipping_provider}`
|
||||
: ''}
|
||||
</span>
|
||||
{
|
||||
item.shipping_method
|
||||
? `发货方式: ${item.shipping_method}`
|
||||
: ''
|
||||
}
|
||||
<span>
|
||||
{item.tracking_number
|
||||
? `物流单号: ${item.tracking_number}`
|
||||
: ''}
|
||||
</span>
|
||||
<span>
|
||||
{item.date_shipped ? `发货日期: ${item.date_shipped}` : ''}
|
||||
{item.date_created ? `发货日期: ${item.date_created}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { ORDER_STATUS_ENUM } from '@/constants';
|
|||
import { AddTag } from '@/pages/Customer/List';
|
||||
import { customercontrollerDeltag } from '@/servers/api/customer';
|
||||
import { sitecontrollerAll } from '@/servers/api/site';
|
||||
import * as countries from 'i18n-iso-countries';
|
||||
import {
|
||||
statisticscontrollerGetorderbydate,
|
||||
statisticscontrollerGetorderbyemail,
|
||||
|
|
@ -22,6 +21,7 @@ import { Button, Space, Tag } from 'antd';
|
|||
import dayjs from 'dayjs';
|
||||
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import * as countries from 'i18n-iso-countries';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
dayjs.extend(weekOfYear);
|
||||
|
|
@ -640,7 +640,6 @@ const ListPage: React.FC = () => {
|
|||
label="区域"
|
||||
mode="multiple"
|
||||
placeholder="请选择区域"
|
||||
|
||||
showSearch
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
|
|
|
|||
|
|
@ -8,16 +8,16 @@ import {
|
|||
ActionType,
|
||||
PageContainer,
|
||||
ProColumns,
|
||||
ProTable,
|
||||
ProForm,
|
||||
ProFormSelect,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Space, Tag } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import { HistoryOrder } from '../Order';
|
||||
import * as countries from 'i18n-iso-countries';
|
||||
import zhCN from 'i18n-iso-countries/langs/zh';
|
||||
import { HistoryOrder } from '../Order';
|
||||
countries.registerLocale(zhCN);
|
||||
const ListPage: React.FC = () => {
|
||||
const [data, setData] = useState({});
|
||||
|
|
@ -25,12 +25,14 @@ const ListPage: React.FC = () => {
|
|||
country: ['CA'],
|
||||
};
|
||||
function handleSubmit(values: typeof initialValues) {
|
||||
statisticscontrollerGetordersource({params: values}).then(({ data, success }) => {
|
||||
statisticscontrollerGetordersource({ params: values }).then(
|
||||
({ data, success }) => {
|
||||
if (success) setData(data);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
handleSubmit(initialValues)
|
||||
handleSubmit(initialValues);
|
||||
}, []);
|
||||
|
||||
const option = useMemo(() => {
|
||||
|
|
@ -287,7 +289,6 @@ const ListPage: React.FC = () => {
|
|||
);
|
||||
},
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
@ -297,7 +298,6 @@ const ListPage: React.FC = () => {
|
|||
layout="inline"
|
||||
onFinish={handleSubmit}
|
||||
>
|
||||
|
||||
<ProFormSelect
|
||||
name="country"
|
||||
label="区域"
|
||||
|
|
@ -343,8 +343,6 @@ const ListPage: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 获取所有国家/地区的选项
|
||||
const getCountryOptions = () => {
|
||||
// 获取所有国家的 ISO 代码
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
import * as area from './area';
|
||||
import * as category from './category';
|
||||
import * as customer from './customer';
|
||||
|
|
|
|||
|
|
@ -125,6 +125,21 @@ export async function productcontrollerBindproductsiteskus(
|
|||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /product/all */
|
||||
export async function productcontrollerGetallproducts(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.productcontrollerGetallproductsParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<API.ProductListRes>('/product/all', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /product/attribute */
|
||||
export async function productcontrollerGetattributelist(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
|
@ -539,6 +554,21 @@ export async function productcontrollerCompatflavorsall(options?: {
|
|||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /product/grouped */
|
||||
export async function productcontrollerGetgroupedproducts(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.productcontrollerGetgroupedproductsParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<any>('/product/grouped', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /product/import */
|
||||
export async function productcontrollerImportproductscsv(
|
||||
body: {},
|
||||
|
|
@ -593,6 +623,21 @@ export async function productcontrollerGetproductlist(
|
|||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /product/list/grouped */
|
||||
export async function productcontrollerGetproductlistgrouped(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.productcontrollerGetproductlistgroupedParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<Record<string, any>>('/product/list/grouped', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /product/search */
|
||||
export async function productcontrollerSearchproducts(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export async function statisticscontrollerGetorderbyemail(
|
|||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /statistics/orderSource */
|
||||
export async function statisticscontrollerGetordersorce(options?: {
|
||||
export async function statisticscontrollerGetordersource(options?: {
|
||||
[key: string]: any;
|
||||
}) {
|
||||
return request<any>('/statistics/orderSource', {
|
||||
|
|
|
|||
|
|
@ -145,6 +145,8 @@ declare namespace API {
|
|||
price?: number;
|
||||
/** 促销价格 */
|
||||
promotionPrice?: number;
|
||||
/** 产品图片URL */
|
||||
image?: string;
|
||||
/** 属性列表 */
|
||||
attributes?: any[];
|
||||
/** 商品类型 */
|
||||
|
|
@ -266,6 +268,8 @@ declare namespace API {
|
|||
sku?: string;
|
||||
/** 分类ID (DictItem ID) */
|
||||
categoryId?: number;
|
||||
/** 分类名称 */
|
||||
categoryName?: string;
|
||||
/** 站点 SKU 列表 */
|
||||
siteSkus?: any[];
|
||||
/** 属性列表 */
|
||||
|
|
@ -274,6 +278,8 @@ declare namespace API {
|
|||
price?: number;
|
||||
/** 促销价格 */
|
||||
promotionPrice?: number;
|
||||
/** 产品图片URL */
|
||||
image?: string;
|
||||
/** 商品类型 */
|
||||
type?: 'single' | 'bundle';
|
||||
/** 产品组成 */
|
||||
|
|
@ -450,6 +456,8 @@ declare namespace API {
|
|||
};
|
||||
|
||||
type FulfillmentDTO = {
|
||||
/** 物流id */
|
||||
tracking_id?: string;
|
||||
/** 物流单号 */
|
||||
tracking_number?: string;
|
||||
/** 物流公司 */
|
||||
|
|
@ -645,6 +653,10 @@ declare namespace API {
|
|||
createdAt: string;
|
||||
/** 更新时间 */
|
||||
updatedAt: string;
|
||||
/** 订单项列表 */
|
||||
orderItems?: any;
|
||||
/** 销售项列表 */
|
||||
orderSales?: any;
|
||||
};
|
||||
|
||||
type OrderAddress = {
|
||||
|
|
@ -695,28 +707,48 @@ declare namespace API {
|
|||
};
|
||||
|
||||
type ordercontrollerGetorderitemsParams = {
|
||||
/** 是否为原产品还是库存产品 */
|
||||
isSource?: boolean;
|
||||
exceptPackage?: boolean;
|
||||
/** 页码 */
|
||||
current?: number;
|
||||
/** 每页大小 */
|
||||
pageSize?: number;
|
||||
/** 排序对象,格式如 { productName: "asc", sku: "desc" } */
|
||||
orderBy?: any;
|
||||
/** 是否排除套餐 */
|
||||
exceptPackage?: boolean;
|
||||
/** 站点ID */
|
||||
siteId?: number;
|
||||
/** 名称 */
|
||||
name?: string;
|
||||
/** SKU */
|
||||
sku?: string;
|
||||
/** 开始日期 */
|
||||
startDate?: string;
|
||||
/** 结束日期 */
|
||||
endDate?: string;
|
||||
};
|
||||
|
||||
type ordercontrollerGetordersalesParams = {
|
||||
/** 是否为原产品还是库存产品 */
|
||||
isSource?: boolean;
|
||||
exceptPackage?: boolean;
|
||||
/** 页码 */
|
||||
current?: number;
|
||||
/** 每页大小 */
|
||||
pageSize?: number;
|
||||
/** 排序对象,格式如 { productName: "asc", sku: "desc" } */
|
||||
orderBy?: any;
|
||||
/** 是否排除套餐 */
|
||||
exceptPackage?: boolean;
|
||||
/** 站点ID */
|
||||
siteId?: number;
|
||||
/** 名称 */
|
||||
name?: string;
|
||||
/** SKU */
|
||||
sku?: string;
|
||||
/** 开始日期 */
|
||||
startDate?: string;
|
||||
/** 结束日期 */
|
||||
endDate?: string;
|
||||
};
|
||||
|
||||
|
|
@ -820,6 +852,10 @@ declare namespace API {
|
|||
createdAt: string;
|
||||
/** 更新时间 */
|
||||
updatedAt: string;
|
||||
/** 订单项列表 */
|
||||
orderItems?: any;
|
||||
/** 销售项列表 */
|
||||
orderSales?: any;
|
||||
items?: OrderItem[];
|
||||
sales?: OrderSale[];
|
||||
refundItems?: OrderRefundItem[];
|
||||
|
|
@ -929,16 +965,26 @@ declare namespace API {
|
|||
orderId?: number;
|
||||
siteId?: number;
|
||||
externalOrderItemId?: string;
|
||||
parentProductId?: number;
|
||||
productId?: number;
|
||||
name?: string;
|
||||
/** sku */
|
||||
sku?: string;
|
||||
quantity?: number;
|
||||
isPackage?: boolean;
|
||||
isYoone?: boolean;
|
||||
isZex?: boolean;
|
||||
size?: number;
|
||||
isYooneNew?: boolean;
|
||||
/** 商品品类 */
|
||||
category?: string;
|
||||
/** 品牌 */
|
||||
brand?: string;
|
||||
/** 口味 */
|
||||
flavor?: string;
|
||||
/** 湿度 */
|
||||
humidity?: string;
|
||||
/** 尺寸 */
|
||||
size?: string;
|
||||
strength?: string;
|
||||
/** 版本 */
|
||||
version?: string;
|
||||
/** 创建时间 */
|
||||
createdAt?: string;
|
||||
/** 更新时间 */
|
||||
|
|
@ -950,16 +996,26 @@ declare namespace API {
|
|||
orderId?: number;
|
||||
siteId?: number;
|
||||
externalOrderItemId?: string;
|
||||
parentProductId?: number;
|
||||
productId?: number;
|
||||
name?: string;
|
||||
/** sku */
|
||||
sku?: string;
|
||||
quantity?: number;
|
||||
isPackage?: boolean;
|
||||
isYoone?: boolean;
|
||||
isZex?: boolean;
|
||||
size?: number;
|
||||
isYooneNew?: boolean;
|
||||
/** 商品品类 */
|
||||
category?: string;
|
||||
/** 品牌 */
|
||||
brand?: string;
|
||||
/** 口味 */
|
||||
flavor?: string;
|
||||
/** 湿度 */
|
||||
humidity?: string;
|
||||
/** 尺寸 */
|
||||
size?: string;
|
||||
strength?: string;
|
||||
/** 版本 */
|
||||
version?: string;
|
||||
/** 创建时间 */
|
||||
createdAt?: string;
|
||||
/** 更新时间 */
|
||||
|
|
@ -994,6 +1050,7 @@ declare namespace API {
|
|||
endDate?: string;
|
||||
keyword?: string;
|
||||
siteId?: number;
|
||||
country?: any;
|
||||
purchaseType?: 'all' | 'first_purchase' | 'repeat_purchase';
|
||||
orderType?: 'all' | 'cpc' | 'non_cpc';
|
||||
brand?: 'all' | 'zyn' | 'yoone' | 'zolt';
|
||||
|
|
@ -1034,10 +1091,14 @@ declare namespace API {
|
|||
shortDescription?: string;
|
||||
/** 产品描述 */
|
||||
description?: string;
|
||||
/** 产品图片URL */
|
||||
image?: string;
|
||||
/** 价格 */
|
||||
price?: number;
|
||||
/** 促销价格 */
|
||||
promotionPrice?: number;
|
||||
/** 分类 ID */
|
||||
categoryId?: number;
|
||||
/** 库存组成 */
|
||||
components?: ProductStockComponent[];
|
||||
/** 站点 SKU 列表 */
|
||||
|
|
@ -1134,6 +1195,10 @@ declare namespace API {
|
|||
id: number;
|
||||
};
|
||||
|
||||
type productcontrollerGetallproductsParams = {
|
||||
brand?: string;
|
||||
};
|
||||
|
||||
type productcontrollerGetattributeallParams = {
|
||||
dictName?: string;
|
||||
};
|
||||
|
|
@ -1149,6 +1214,11 @@ declare namespace API {
|
|||
id: number;
|
||||
};
|
||||
|
||||
type productcontrollerGetgroupedproductsParams = {
|
||||
attribute?: string;
|
||||
brand?: string;
|
||||
};
|
||||
|
||||
type productcontrollerGetproductbyidParams = {
|
||||
id: number;
|
||||
};
|
||||
|
|
@ -1161,6 +1231,25 @@ declare namespace API {
|
|||
id: number;
|
||||
};
|
||||
|
||||
type productcontrollerGetproductlistgroupedParams = {
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
/** 每页数量 */
|
||||
per_page?: number;
|
||||
/** 查询时间范围开始 */
|
||||
after?: string;
|
||||
/** 查询时间范围结束 */
|
||||
before?: string;
|
||||
/** 搜索关键词 */
|
||||
search?: string;
|
||||
/** 过滤条件对象 */
|
||||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
};
|
||||
|
||||
type productcontrollerGetproductlistParams = {
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
|
|
@ -1176,6 +1265,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
};
|
||||
|
||||
type productcontrollerGetproductsiteskusParams = {
|
||||
|
|
@ -1371,15 +1462,25 @@ declare namespace API {
|
|||
};
|
||||
|
||||
type QueryOrderSalesDTO = {
|
||||
/** 是否为原产品还是库存产品 */
|
||||
isSource?: boolean;
|
||||
exceptPackage?: boolean;
|
||||
/** 页码 */
|
||||
current?: number;
|
||||
/** 每页大小 */
|
||||
pageSize?: number;
|
||||
/** 排序对象,格式如 { productName: "asc", sku: "desc" } */
|
||||
orderBy?: any;
|
||||
/** 是否排除套餐 */
|
||||
exceptPackage?: boolean;
|
||||
/** 站点ID */
|
||||
siteId?: number;
|
||||
/** 名称 */
|
||||
name?: string;
|
||||
/** SKU */
|
||||
sku?: string;
|
||||
/** 开始日期 */
|
||||
startDate?: string;
|
||||
/** 结束日期 */
|
||||
endDate?: string;
|
||||
};
|
||||
|
||||
|
|
@ -1526,9 +1627,11 @@ declare namespace API {
|
|||
details?: ShippingDetailsDTO;
|
||||
stockPointId?: number;
|
||||
orderIds?: number[];
|
||||
shipmentPlatform?: string;
|
||||
};
|
||||
|
||||
type ShipmentFeeBookDTO = {
|
||||
shipmentPlatform?: string;
|
||||
stockPointId?: number;
|
||||
sender?: string;
|
||||
startPhone?: string;
|
||||
|
|
@ -1549,6 +1652,7 @@ declare namespace API {
|
|||
dimensionUom?: string;
|
||||
weight?: number;
|
||||
weightUom?: string;
|
||||
address_id?: number;
|
||||
};
|
||||
|
||||
type ShippingAddress = {
|
||||
|
|
@ -1708,6 +1812,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1726,6 +1832,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1744,6 +1852,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1762,6 +1872,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1780,6 +1892,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1798,6 +1912,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1816,6 +1932,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
customerId: number;
|
||||
siteId: number;
|
||||
};
|
||||
|
|
@ -1840,6 +1958,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1862,6 +1982,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1895,6 +2017,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1918,6 +2042,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1936,6 +2062,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1954,6 +2082,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -1977,6 +2107,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
|
|
@ -2928,6 +3060,8 @@ declare namespace API {
|
|||
where?: any;
|
||||
/** 排序对象,例如 { "sku": "desc" } */
|
||||
orderBy?: any;
|
||||
/** 分组字段,例如 "categoryId" */
|
||||
groupBy?: string;
|
||||
};
|
||||
|
||||
type UnifiedShippingLineDTO = {
|
||||
|
|
@ -3088,12 +3222,16 @@ declare namespace API {
|
|||
sku?: string;
|
||||
/** 分类ID (DictItem ID) */
|
||||
categoryId?: number;
|
||||
/** 分类名称 */
|
||||
categoryName?: string;
|
||||
/** 站点 SKU 列表 */
|
||||
siteSkus?: any[];
|
||||
/** 价格 */
|
||||
price?: number;
|
||||
/** 促销价格 */
|
||||
promotionPrice?: number;
|
||||
/** 产品图片URL */
|
||||
image?: string;
|
||||
/** 属性列表 */
|
||||
attributes?: any[];
|
||||
/** 商品类型 */
|
||||
|
|
@ -3144,6 +3282,8 @@ declare namespace API {
|
|||
stockPointIds?: any;
|
||||
/** 站点网站URL */
|
||||
websiteUrl?: string;
|
||||
/** Webhook URL */
|
||||
webhookUrl?: string;
|
||||
};
|
||||
|
||||
type UpdateStockDTO = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue