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() ? ( {part} ) : ( part ), ); }; const ListPage: React.FC = () => { const [xAxis, setXAxis] = useState([]); const [series, setSeries] = useState([]); 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 `${item.marker} ${item.seriesName}: ${item.value}`; }); // 每4个一行 const rows = []; for (let i = 0; i < items.length; i += 4) { rows.push(items.slice(i, i + 4).join('')); } return ( `
${xValue} ${ ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][ dayjs(xValue).day() ] }
` + rows.join('
') ); }, }, 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 ( { 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), }, ]); } }} > {/* */} { const { data = [] } = await sitecontrollerAll(); return data.map((item) => ({ label: item.name, value: item.id, })); }} /> {/* */} {series?.length ? ( { if (params.componentType === 'series') { setSelectedDate(params.name); } }, }} /> ) : ( <> )} ); }; const DailyOrders: React.FC<{ selectedDate; }> = ({ selectedDate }) => { const [orders, setOrders] = useState([]); const actionRef = useRef(); 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 ( { const { data = [] } = await sitecontrollerAll(); return data.map((item) => ({ label: item.name, 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 (
{record.orderItems?.map((item) => ( //
{highlightText(item.name, keyword)}
{item.name}
))}
); }, 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 ( {(record.tags || []).map((tag) => { return ( { const { success, message: msg } = await customercontrollerDeltag({ email: record.email, tag, }); return false; }} > {tag} ); })} ); }, }, { title: '操作', dataIndex: 'action', valueType: 'option', render: (_, record) => { return ( ); }, }, ]} dataSource={orders} onChange={handleTableChange} /> ); }; export const HistoryOrder: React.FC<{ email: string; tags?: string[]; tableRef: React.MutableRefObject; }> = ({ email, tags, tableRef }) => { const actionRef = useRef(); return ( 历史订单} modalProps={{ destroyOnHidden: true, footer: null }} width="80vw" submitter={false} > { const { data = [] } = await sitecontrollerAll(); return data.map((item) => ({ label: item.name, 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 (
{record.orderItems?.map((item) => (
${item.total} : {item.name} * {item.quantity}
))}
); }, }, { title: '标签', dataIndex: 'tags', render: (_, record) => { return ( {(record.tags || []).map((tag) => { return ( { const { success, message: msg } = await customercontrollerDeltag({ email, tag, }); return false; }} > {tag} ); })} ); }, }, ]} request={async () => { const { data, success } = await statisticscontrollerGetorderbyemail({ email, }); return { data, success, }; }} toolBarRender={() => [ , ]} />
); }; export default ListPage;