Compare commits
No commits in common. "3690fd00f7002f2ecf5dc500d6b351f09576dc9c" and "2471eb52995988f6587a0e2f08c55c6edfeb7b53" have entirely different histories.
3690fd00f7
...
2471eb5299
|
|
@ -134,14 +134,9 @@ export default defineConfig({
|
|||
},
|
||||
{
|
||||
name: '数据分析列表',
|
||||
path: '/customer/statistic/list',
|
||||
component: './Customer/StatisticList',
|
||||
path: '/customer/statistic',
|
||||
component: './Customer/Statistic',
|
||||
},
|
||||
// {
|
||||
// name: '客户统计',
|
||||
// path: '/customer/statistic/home',
|
||||
// component: './Customer/Statistic',
|
||||
// }
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,289 @@
|
|||
export default function Statistic() {
|
||||
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 (
|
||||
<div>
|
||||
<h1>客户统计</h1>
|
||||
</div>
|
||||
<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;
|
||||
|
|
|
|||
|
|
@ -1,289 +0,0 @@
|
|||
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;
|
||||
|
|
@ -19,7 +19,7 @@ const ShopLayout: React.FC = () => {
|
|||
|
||||
const fetchSites = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setEditingSiteLoading(true);
|
||||
const { data = [] } = await sitecontrollerAll();
|
||||
setSites(data);
|
||||
|
||||
|
|
@ -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', zIndex: 1 }}
|
||||
style={{ background: 'white', height: '100%', overflow: 'hidden' }}
|
||||
>
|
||||
<div style={{ padding: '0 10px 16px' }}>
|
||||
<div
|
||||
|
|
@ -125,10 +125,10 @@ const ShopLayout: React.FC = () => {
|
|||
(site) => site.id === Number(siteId),
|
||||
);
|
||||
if (currentSite) {
|
||||
function normalizeEditing(site: SiteItem) {
|
||||
function normalizeEditing (site: SiteItem){
|
||||
return {
|
||||
...site,
|
||||
areas: site.areas?.map(area => area.code) || [],
|
||||
areas: site.areas?.map(area=>area.code) || [],
|
||||
}
|
||||
}
|
||||
setEditingSite(normalizeEditing(currentSite));
|
||||
|
|
|
|||
|
|
@ -228,28 +228,21 @@ const OrdersPage: React.FC = () => {
|
|||
// 遍历物流信息数组, 显示每个物流的提供商和单号
|
||||
return (
|
||||
<div>
|
||||
{record.fulfillments.map((item, index: number) => (
|
||||
{record.fulfillments.map((item: any, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{ display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<span>
|
||||
{item.shipping_provider
|
||||
? `快递方式: ${item.shipping_provider}`
|
||||
{item.tracking_provider
|
||||
? `快递方式: ${item.tracking_provider}`
|
||||
: ''}
|
||||
</span>
|
||||
{
|
||||
item.shipping_method
|
||||
? `发货方式: ${item.shipping_method}`
|
||||
: ''
|
||||
}
|
||||
{item.tracking_number
|
||||
? `物流单号: ${item.tracking_number}`
|
||||
: ''}
|
||||
<span>
|
||||
{item.tracking_number
|
||||
? `物流单号: ${item.tracking_number}`
|
||||
: ''}
|
||||
</span>
|
||||
<span>
|
||||
{item.date_created ? `发货日期: ${item.date_created}` : ''}
|
||||
{item.date_shipped ? `发货日期: ${item.date_shipped}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -456,8 +456,6 @@ declare namespace API {
|
|||
};
|
||||
|
||||
type FulfillmentDTO = {
|
||||
/** 物流id */
|
||||
tracking_id?: string;
|
||||
/** 物流单号 */
|
||||
tracking_number?: string;
|
||||
/** 物流公司 */
|
||||
|
|
|
|||
Loading…
Reference in New Issue