zksu
/
WEB
forked from yoone/WEB
1
0
Fork 0
WEB/src/pages/Statistics/Order/index.tsx

1003 lines
29 KiB
TypeScript

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 {
statisticscontrollerGetorderbydate,
statisticscontrollerGetorderbyemail,
statisticscontrollerGetorderstatistics,
} from '@/servers/api/statistics';
import { formatSource } from '@/utils/format';
import {
ActionType,
ModalForm,
PageContainer,
ProForm,
ProFormDateRangePicker,
ProFormSelect,
ProTable,
} from '@ant-design/pro-components';
import { Button, Space, Tag } from 'antd';
import dayjs from 'dayjs';
import ReactECharts from 'echarts-for-react';
import { useEffect, useMemo, useRef, useState } from 'react';
const highlightText = (text: string, keyword: string) => {
if (!keyword) return text;
const parts = text.split(new RegExp(`(${keyword})`, 'gi'));
return parts.map((part, index) =>
part.toLowerCase() === keyword.toLowerCase() ? (
<span key={index} style={{ color: 'red', fontWeight: 'bold' }}>
{part}
</span>
) : (
part
),
);
};
const ListPage: React.FC = () => {
const [xAxis, setXAxis] = useState([]);
const [series, setSeries] = useState<any[]>([]);
const [selectedDate, setSelectedDate] = useState(null);
const option = useMemo(
() => ({
tooltip: {
trigger: 'axis',
formatter: function (params) {
const xValue = params[0].axisValue;
const items = params.map((item) => {
return `<span style="display: inline-block;width: 250px">${item.marker} ${item.seriesName}: ${item.value}</span>`;
});
// 每4个一行
const rows = [];
for (let i = 0; i < items.length; i += 4) {
rows.push(items.slice(i, i + 4).join(''));
}
return (
`<div>${xValue} ${
['周日', '周一', '周二', '周三', '周四', '周五', '周六'][
dayjs(xValue).day()
]
}</div>` + rows.join('<br/>')
);
},
},
legend: {
data: ['订单数', '总金额', '平均金额', '盒数'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xAxis,
},
yAxis: [
{
type: 'value',
name: '订单数',
position: 'left',
},
{
type: 'value',
name: '总金额',
position: 'right',
offset: 60,
},
{
type: 'value',
name: '平均金额',
position: 'right',
},
{
type: 'value',
name: '盒数',
position: 'left',
offset: 60,
},
],
series,
}),
[xAxis, series],
);
return (
<PageContainer ghost>
<ProForm
layout="inline"
onFinish={async (values) => {
setXAxis([]);
setSeries([]);
setSelectedDate(null);
// setKeyword(values?.keyword || '');
const { date, ...params } = values;
const [startDate, endDate] = date;
const { data, success } =
await statisticscontrollerGetorderstatistics({
startDate,
endDate,
...params,
});
if (success) {
const res = data?.sort(() => -1);
setXAxis(res?.map((v) => dayjs(v.order_date).format('YYYY-MM-DD')));
setSeries([
{
name: 'TOGO CPC订单数',
type: 'line',
data: res?.map((v) => v.togo_cpc_orders),
},
{
name: 'TOGO 非CPC订单数',
type: 'line',
data: res?.map((v) => v.non_togo_cpc_orders),
},
{
name: 'CAN CPC订单数',
type: 'line',
data: res?.map((v) => v.can_cpc_orders),
},
{
name: 'CAN 非CPC订单数',
type: 'line',
data: res?.map((v) => v.non_can_cpc_orders),
},
{
name: '直达订单数',
type: 'line',
data: res?.map((v) => v.direct_orders),
},
{
name: '直达金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.direct_total),
},
{
name: '首购订单数',
type: 'line',
data: res?.map((v) => v.first_purchase_orders),
},
{
name: '首购金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.first_purchase_total),
},
{
name: 'organic订单数',
type: 'line',
data: res?.map((v) => v.organic_orders),
},
{
name: 'organic金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.organic_total),
},
{
name: '复购订单数',
type: 'line',
data: res?.map((v) => v.repeat_purchase_orders),
},
{
name: '复购金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.repeat_purchase_total),
},
{
name: 'CPC订单数',
type: 'line',
data: res?.map((v) => v.cpc_orders),
},
{
name: 'ZYN CPC订单数',
type: 'line',
data: res?.map((v) => v.zyn_orders),
},
{
name: 'YOONE CPC订单数',
type: 'line',
data: res?.map((v) => v.yoone_orders),
},
{
name: 'ZEX CPC订单数',
type: 'line',
data: res?.map((v) => v.zex_orders),
},
{
name: 'CPC金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.cpc_total),
},
{
name: 'ZYN CPC金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.zyn_total),
},
{
name: 'YOONE CPC金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.yoone_total),
},
{
name: 'ZEX CPC金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.zex_total),
},
{
name: '非CPC订单数',
type: 'line',
data: res?.map((v) => v.non_cpc_orders),
},
{
name: 'ZYN 非CPC订单数',
type: 'line',
data: res?.map((v) => v.non_zyn_orders),
},
{
name: 'YOONE 非CPC订单数',
type: 'line',
data: res?.map((v) => v.non_yoone_orders),
},
{
name: 'ZEX 非CPC订单数',
type: 'line',
data: res?.map((v) => v.non_zex_orders),
},
{
name: '非CPC金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.non_cpc_total),
},
{
name: 'ZYN 非CPC金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.non_zyn_total),
},
{
name: 'YOONE 非CPC金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.non_yoone_total),
},
{
name: 'ZEX 非CPC金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.non_zex_total),
},
{
name: 'ZYN 盒数金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.zyn_amount),
},
{
name: 'ZYN 盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.zyn_quantity),
},
{
name: 'ZYN CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.cpc_zyn_quantity),
},
{
name: 'ZYN 非CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.non_cpc_zyn_quantity),
},
{
name: 'YOONE 盒数金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.yoone_amount),
},
{
name: 'YOONE 盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.yoone_quantity),
},
{
name: 'YOONE CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.cpc_yoone_quantity),
},
{
name: 'YOONE 非CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.non_cpc_yoone_quantity),
},
{
name: 'YOONE套装金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.yoone_G_amount),
},
{
name: 'YOONE套装盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.yoone_G_quantity),
},
{
name: 'YOONE套装CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.cpc_yoone_G_quantity),
},
{
name: 'YOONE套装非CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.non_cpc_yoone_G_quantity),
},
{
name: 'YOONE单盒金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.yoone_S_amount),
},
{
name: 'YOONE单盒盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.yoone_S_quantity),
},
{
name: 'YOONE单盒CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.cpc_yoone_S_quantity),
},
{
name: 'YOONE单盒非CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.non_cpc_yoone_S_quantity),
},
{
name: 'YOONE 3MG 盒数金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.yoone_3_amount),
},
{
name: 'YOONE 3MG 盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.yoone_3_quantity),
},
{
name: 'YOONE 3MG CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.cpc_yoone_3_quantity),
},
{
name: 'YOONE 3MG 非CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.non_cpc_yoone_3_quantity),
},
{
name: 'YOONE 6MG 盒数金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.yoone_6_amount),
},
{
name: 'YOONE 6MG 盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.yoone_6_quantity),
},
{
name: 'YOONE 6MG CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.cpc_yoone_6_quantity),
},
{
name: 'YOONE 6MG 非CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.non_cpc_yoone_6_quantity),
},
{
name: 'YOONE 9MG 盒数金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.yoone_9_amount),
},
{
name: 'YOONE 9MG 盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.yoone_9_quantity),
},
{
name: 'YOONE 9MG CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.cpc_yoone_9_quantity),
},
{
name: 'YOONE 9MG 非CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.non_cpc_yoone_9_quantity),
},
{
name: 'YOONE 12MG 盒数金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.yoone_12_amount),
},
{
name: 'YOONE 12MG 盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.yoone_12_quantity),
},
{
name: 'YOONE 12MG CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.cpc_yoone_12_quantity),
},
{
name: 'YOONE 12MG 非CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.non_cpc_yoone_12_quantity),
},
{
name: 'YOONE 15MG 盒数金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.yoone_15_amount),
},
{
name: 'YOONE 15MG 盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.yoone_15_quantity),
},
{
name: 'YOONE 15MG CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.cpc_yoone_15_quantity),
},
{
name: 'YOONE 15MG 非CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.non_cpc_yoone_15_quantity),
},
{
name: 'ZEX 盒数金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.zex_amount),
},
{
name: 'ZEX 盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.zex_quantity),
},
{
name: 'ZEX CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.cpc_zex_quantity),
},
{
name: 'ZEX 非CPC盒数',
type: 'line',
yAxisIndex: 3,
data: res?.map((v) => v.non_cpc_zex_quantity),
},
{
name: '订单数',
type: 'line',
data: res?.map((v) => v.total_orders),
},
{
name: 'TOGO订单数',
type: 'line',
data: res?.map((v) => v.togo_total_orders),
},
{
name: 'CAN订单数',
type: 'line',
data: res?.map((v) => v.can_total_orders),
},
{
name: '总金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.total_amount),
},
{
name: 'TOGO总金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.togo_total_amount),
},
{
name: 'CAN总金额',
type: 'line',
yAxisIndex: 1,
data: res?.map((v) => v.can_total_amount),
},
{
name: '平均金额',
type: 'line',
yAxisIndex: 2,
data: res?.map((v) => v.avg_total_amount),
},
{
name: 'TOGO平均金额',
type: 'line',
yAxisIndex: 2,
data: res?.map((v) => v.avg_togo_total_amount),
},
{
name: 'CAN平均金额',
type: 'line',
yAxisIndex: 2,
data: res?.map((v) => v.avg_can_total_amount),
},
]);
}
}}
>
<ProFormDateRangePicker
label="日期"
rules={[{ required: true, message: '请选择日期' }]}
name="date"
/>
{/* <ProFormText label="关键词" name="keyword" /> */}
<ProFormSelect
label="站点"
name="siteId"
request={async () => {
const { data = [] } = await sitecontrollerAll();
return data.map((item) => ({
label: item.siteName,
value: item.id,
}));
}}
/>
{/* <ProFormSelect
label="类型"
name="purchaseType"
valueEnum={{
first_purchase: '首购',
repeat_purchase: '复购',
}}
/>
<ProFormSelect
label="来源"
name="orderType"
valueEnum={{
cpc: '广告',
non_cpc: '非广告',
}}
/>
<ProFormSelect
label="品牌"
name="brand"
valueEnum={{
zyn: 'ZYN',
zex: 'ZEX',
yoone: 'YOONE',
}}
/> */}
</ProForm>
{series?.length ? (
<ReactECharts
style={{ height: 450 }}
option={option}
onEvents={{
click: (params) => {
if (params.componentType === 'series') {
setSelectedDate(params.name);
}
},
}}
/>
) : (
<></>
)}
<DailyOrders selectedDate={selectedDate} />
</PageContainer>
);
};
const DailyOrders: React.FC<{
selectedDate;
}> = ({ selectedDate }) => {
const [orders, setOrders] = useState([]);
const actionRef = useRef<ActionType>();
useEffect(() => {
if (!selectedDate) {
setOrders([]);
return;
}
statisticscontrollerGetorderbydate({
date: selectedDate,
}).then(({ data, success }) => {
if (success) {
setOrders(data);
}
});
}, [selectedDate]);
const handleTableChange = (pagination, filters, sorter) => {
// 获取排序字段和排序方式
const { field, order } = sorter || {};
if (field && order) {
const sortedData = [...orders].sort((a, b) => {
if (['total', 'order_count', 'total_spent'].includes(field)) {
const valA = Number(a[field]);
const valB = Number(b[field]);
if (isNaN(valA)) return 1;
if (isNaN(valB)) return -1;
if (order === 'ascend') {
return valA - valB;
}
return valB - valA;
}
if (order === 'ascend') {
return a[field] > b[field] ? 1 : -1;
}
return a[field] < b[field] ? 1 : -1;
});
setOrders(sortedData);
}
};
if (!selectedDate) return <></>;
return (
<ProTable
search={false}
columns={[
{
title: '订单号',
dataIndex: 'externalOrderId',
},
{
title: '客户邮箱',
dataIndex: 'customer_email',
},
{
title: '站点',
dataIndex: 'siteId',
valueType: 'select',
request: async () => {
const { data = [] } = await sitecontrollerAll();
return data.map((item) => ({
label: item.siteName,
value: item.id,
}));
},
},
{
title: '首单时间',
dataIndex: 'first_purchase_date',
valueType: 'dateTime',
sorter: true,
},
{
title: '尾单时间',
dataIndex: 'last_purchase_date',
valueType: 'dateTime',
sorter: true,
},
{
title: '支付时间',
dataIndex: 'date_paid',
valueType: 'dateTime',
sorter: true,
},
{
title: '订单金额',
dataIndex: 'total',
sorter: true,
},
{
title: '总订单数',
dataIndex: 'order_count',
sorter: true,
},
{
title: '总订单金额',
dataIndex: 'total_spent',
sorter: true,
},
{
title: '订单类型',
dataIndex: 'purchase_type',
sorter: true,
render: (_, record) => {
if (record.purchase_type === 'first_purchase') return '首单';
return '复购单';
},
filters: [
{ text: '首单', value: 'first_purchase' },
{ text: '复购单', value: 'repeat_purchase' },
],
onFilter: (value, record) => {
return record.purchase_type === value;
},
},
{
title: '状态',
dataIndex: 'orderStatus',
hideInSearch: true,
valueType: 'select',
valueEnum: ORDER_STATUS_ENUM,
sorter: true,
},
{
title: '来源',
hideInSearch: true,
sorter: true,
render: (_, record) =>
formatSource(record.source_type, record.utm_source),
filters: [
{ text: 'google广告', value: 'source' },
{ text: 'google搜索', value: 'organic' },
{ text: '直达', value: 'direct' },
{ text: '其他', value: 'other' },
],
onFilter: (value, record) => {
if (value === 'source') {
return (
record.source_type === 'utm' && record.utm_source === 'google'
);
}
if (value === 'organic') {
return (
record.source_type === 'organic' &&
record.utm_source === 'google'
);
}
if (value === 'direct') {
return record.source_type === 'typein';
}
if (value === '其他') {
return !['utm', 'organic', 'typein'].includes(record.source_type);
}
},
},
{
title: '订单内容',
dataIndex: 'orderItems',
render: (_, record) => {
return (
<div>
{record.orderItems?.map((item) => (
// <div>{highlightText(item.name, keyword)}</div>
<div>{item.name}</div>
))}
</div>
);
},
filters: [
{ text: 'ZYN', value: 'zyn' },
{ text: 'YOONE', value: 'yoone' },
{ text: 'ZEX', value: 'zex' },
],
onFilter: (value, record) => {
return record.orderItems?.some((v) =>
v?.name?.toLowerCase().includes(value),
);
},
},
{
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: 'action',
valueType: 'option',
render: (_, record) => {
return (
<Space>
<AddTag
email={record.customer_email}
tags={record.tags}
tableRef={actionRef}
/>
<HistoryOrder
email={record.customer_email}
tags={record.tags}
tableRef={actionRef}
/>
</Space>
);
},
},
]}
dataSource={orders}
onChange={handleTableChange}
/>
);
};
export const HistoryOrder: React.FC<{
email: string;
tags?: string[];
tableRef: React.MutableRefObject<ActionType | undefined>;
}> = ({ email, tags, tableRef }) => {
const actionRef = useRef<ActionType>();
return (
<ModalForm
title={`历史订单${email}`}
trigger={<Button type="primary"></Button>}
modalProps={{ destroyOnHidden: true, footer: null }}
width="80vw"
submitter={false}
>
<ProTable
search={false}
columns={[
{
title: '订单号',
dataIndex: 'externalOrderId',
},
{
title: '客户邮箱',
dataIndex: 'customer_email',
},
{
title: '站点',
dataIndex: 'siteId',
valueType: 'select',
request: async () => {
const { data = [] } = await sitecontrollerAll();
return data.map((item) => ({
label: item.siteName,
value: item.id,
}));
},
},
{
title: '支付时间',
dataIndex: 'date_paid',
valueType: 'dateTime',
},
{
title: '订单金额',
dataIndex: 'total',
},
{
title: '状态',
dataIndex: 'orderStatus',
hideInSearch: true,
valueType: 'select',
valueEnum: ORDER_STATUS_ENUM,
},
{
title: '来源',
hideInSearch: true,
render: (_, record) =>
formatSource(record.source_type, record.utm_source),
},
{
title: '订单内容',
dataIndex: 'orderItems',
render: (_, record) => {
return (
<div>
{record.orderItems?.map((item) => (
<div>
${item.total} : {item.name} * {item.quantity}
</div>
))}
</div>
);
},
},
{
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,
tag,
});
return false;
}}
>
{tag}
</Tag>
);
})}
</Space>
);
},
},
]}
request={async () => {
const { data, success } = await statisticscontrollerGetorderbyemail({
email,
});
return {
data,
success,
};
}}
toolBarRender={() => [
<AddTag email={email} tags={tags} tableRef={actionRef} />,
]}
/>
</ModalForm>
);
};
export default ListPage;