perf: 老客户. 明细; -->zustand

main
Lei OT 3 months ago
parent d9fccc5433
commit 259afa80f1

@ -6,13 +6,24 @@ import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import SearchForm from './../components/search/SearchForm';
import LineWithAvg from '../components/LineWithAvg';
import { flow } from 'mobx';
import { TableExportBtn } from '../components/Data';
import { toJS } from 'mobx';
import { TableExportBtn, VSDataTag, RenderVSDataCell } from '../components/Data';
import useCustomerRelationsStore from '../zustand/CustomerRelations';
import { useShallow } from 'zustand/shallow';
import { fixTo2Decimals } from '@haina/utils-commons';
const Customer_care_regular = () => {
const { orders_store, date_picker_store, customer_store } = useContext(stores_Context);
const { date_picker_store, customer_store } = useContext(stores_Context);
const regular_data = customer_store.regular_data;
const [loading, loading2, searchValues, ] = useCustomerRelationsStore(useShallow((state) => [state.loading, state.loading2, state.searchValues,]));
const [setSearchValues] = useCustomerRelationsStore(useShallow((state) => [state.setSearchValues]));
const [regular] = useCustomerRelationsStore(useShallow((state) => [state.regular]));
const getRegularCustomer = useCustomerRelationsStore((state) => state.getRegularCustomer);
const columns = [
{
title: '订单号',
@ -205,13 +216,14 @@ const Customer_care_regular = () => {
return (
<div>
<Row gutter={16} className={date_picker_store.siderBroken ? '' : 'sticky-top'}>
<Row gutter={16} className={toJS(date_picker_store.siderBroken) ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...regular_data.searchValues,
...toJS(date_picker_store.formValues),
// ...toJS(regular_data.searchValues),
...searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'dates', 'IncludeTickets'],
fieldProps: {
@ -222,21 +234,25 @@ const Customer_care_regular = () => {
}}
onSubmit={async (_err, obj, form, str) => {
customer_store.setSearchValues(obj, form, 'regular_data');
regular_data.data_compare=[];
if (obj.DateDiff1 && obj.DateDiff2){
regular_data.isCompareLine=true;
regular_data.showCompareSum=true;
await customer_store.regular_customer_order();
customer_store.regular_customer_order(false,true);
// customer_store.regular_customer_order(true,false,true);
// customer_store.regular_customer_order(true,true,true);
}
else{
regular_data.isCompareLine=false;
regular_data.showCompareSum=false;
customer_store.regular_customer_order();
// customer_store.regular_customer_order(true);
}
setSearchValues(obj, form);
getRegularCustomer({ ...obj, IsDetail: 0 });
getRegularCustomer({ ...obj, IsDetail: 1 });
// regular_data.data_compare=[];
// if (obj.DateDiff1 && obj.DateDiff2){
// regular_data.isCompareLine=true;
// regular_data.showCompareSum=true;
// await customer_store.regular_customer_order();
// customer_store.regular_customer_order(false,true);
// // customer_store.regular_customer_order(true,false,true);
// customer_store.regular_customer_order(true,true,true);
// }
// else{
// regular_data.isCompareLine=false;
// regular_data.showCompareSum=false;
// customer_store.regular_customer_order();
// customer_store.regular_customer_order(true);
// }
}}
/>
</Col>
@ -247,8 +263,8 @@ const Customer_care_regular = () => {
</Col>
<Col span={24}>
<Table
dataSource={regular_data.data}
loading={regular_data.loading}
dataSource={regular.data}
loading={loading}
columns={[
{
title: '统计条目',
@ -259,7 +275,7 @@ const Customer_care_regular = () => {
title: () => (
<>
订单数{' '}
<Tooltip key='total_data_tips_title' title="总订单: 当同时勾选老客户和推荐时, 将重复计数">
<Tooltip key="total_data_tips_title" title="总订单: 当同时勾选老客户和推荐时, 将重复计数">
<InfoCircleOutlined />
</Tooltip>
</>
@ -268,10 +284,14 @@ const Customer_care_regular = () => {
key: 'OrderNum',
render: (text, record, index) => (
<>
<span>{text}</span>&nbsp;&nbsp;
{<Tooltip key='total_data_tips' title={regular_data.total_data_tips}>
{index === 0 && regular_data.total_data_tips!=='' && <InfoCircleOutlined className='ant-tag-gold' />}
</Tooltip>}
<RenderVSDataCell showDiffData={true} data1={record.OrderNum} data2={record.diff?.OrderNum} />
{/* <span>{text}</span> */}
&nbsp;&nbsp;
{
<Tooltip key="total_data_tips" title={regular.total_data_tips}>
{index === 0 && regular.total_data_tips !== '' && <InfoCircleOutlined className="ant-tag-gold" />}
</Tooltip>
}
</>
),
},
@ -279,46 +299,49 @@ const Customer_care_regular = () => {
title: '订单数占比',
dataIndex: 'OrderRate',
key: 'OrderRate',
render: (text) => typeof text === 'number'?<span>{parseFloat((text * 100).toFixed(2))}%</span>:text,
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={fixTo2Decimals(record.OrderRate*100)} data2={fixTo2Decimals(record.diff?.OrderRate*100)} dataSuffix='%' />
},
{
title: '订单数占比(市场)',
dataIndex: 'OrderRate2',
key: 'OrderRate2',
render: (text) => typeof text === 'number'?<span>{parseFloat((text * 100).toFixed(2))}%</span>:text,
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={fixTo2Decimals(record.OrderRate2*100)} data2={fixTo2Decimals(record.diff?.OrderRate2*100)} dataSuffix='%' />
},
{
title: '成行数',
dataIndex: 'SUCOrderNum',
key: 'SUCOrderNum',
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={(record.SUCOrderNum)} data2={(record.diff?.SUCOrderNum)} />
},
{
title: '成行率',
dataIndex: 'SUCRate',
key: 'SUCRate',
render: (text) => typeof text === 'number'?<span>{Math.round(text * 100)}%</span>:text,
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={fixTo2Decimals(record.SUCRate*100)} data2={fixTo2Decimals(record.diff?.SUCRate*100)} dataSuffix='%' />
},
{
title: '毛利',
dataIndex: 'ML',
key: 'ML',
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={(record.ML)} data2={(record.diff?.ML)} />
},
{
title: '毛利占比',
dataIndex: 'OrderMLRate',
key: 'OrderMLRate',
render: (text) => typeof text === 'number'?<span>{parseFloat((text * 100).toFixed(2))}%</span>:text,
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={fixTo2Decimals(record.OrderMLRate*100)} data2={fixTo2Decimals(record.diff?.OrderMLRate*100)} dataSuffix='%' />
},
{
title: '毛利占比(市场)',
dataIndex: 'OrderMLRate2',
key: 'OrderMLRate2',
render: (text) => typeof text === 'number'?<span>{parseFloat((text * 100).toFixed(2))}%</span>:text,
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={fixTo2Decimals(record.OrderMLRate2*100)} data2={fixTo2Decimals(record.diff?.OrderMLRate2*100)} dataSuffix='%' />
},
{
title: '人数(含成人+儿童)',
dataIndex: 'PersonNum',
key: 'PersonNum',
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={(record.PersonNum)} data2={(record.diff?.PersonNum)} />
},
]}
size="small"
@ -327,65 +350,38 @@ const Customer_care_regular = () => {
/>
</Col>
<Col span={24} >
<div style={{height: '100%'}}>
<>
<dialog open={false} style={{
// position: 'fixed',
// top: 0,
// left: '200px',
// width: 'calc(100vw - 200px)',
// height: '100vh',
width: 'inherit',
height: 'inherit',
background: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
border: 'none',
zIndex: 2,
margin: 0,
padding: 0
}}>
<div style={{
background: 'white',
padding: '20px',
borderRadius: '8px',
textAlign: 'center'
}}>
维护中, 暂不可用, 敬请期待
</div>
</dialog></>
<Col span={24}>
<LineWithAvg dataSource={regular_data.pivotData} loading={regular_data.detail_loading} xField={regular_data.pivotX} yField={regular_data.pivotY}
seriesField='_ylabel' showCompareSum={regular_data.showCompareSum} solidLineTime={regular_data.solidLineTime} solidLineCompareTime={regular_data.solidLineCompareTime}
solidLineDash={regular_data.solidLineDash} isCompareLine={regular_data.isCompareLine}/>
</Col>
<div style={{ height: '100%' }}>
<Col span={24}>
<LineWithAvg
dataSource={regular.pivotData}
loading={loading2}
xField={regular.pivotX}
yField={regular.pivotY}
seriesField="_ylabel"
showCompareSum={false}
solidLineTime={false}
solidLineCompareTime={false}
solidLineDash={false}
isCompareLine={false}
/>
</Col>
<Col span={24}>
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById('table_to_xlsx').getElementsByTagName('table')[0]);
writeFileXLSX(wb, '老客户.xlsx');
}}
>
导出下表
</a>
<TableExportBtn btnTxt='导出详情' label={'老客户-详情'} columns={export_columns} dataSource={regular_data.data_detail} style={{ marginLeft: '10px' }} />
</Divider>
<Table
id="table_to_xlsx"
pagination={false}
loading={regular_data.detail_loading}
dataSource={regular_data.data_detail}
scroll={{ x: 1200 }}
columns={columns}
size="small"
rowKey={(record) => record.COLI_ID}
/>
</Col>
</div>
<Col span={24}>
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById('table_to_xlsx').getElementsByTagName('table')[0]);
writeFileXLSX(wb, '老客户.xlsx');
}}
>
导出下表
</a>
<TableExportBtn btnTxt="导出详情" label={'老客户-详情'} columns={export_columns} dataSource={regular.details} style={{ marginLeft: '10px' }} />
</Divider>
<Table id="table_to_xlsx" pagination={false} loading={loading2} dataSource={regular.details} scroll={{ x: 1200 }} columns={columns} size="small" rowKey={(record) => record.COLI_ID} />
</Col>
</div>
</Col>
</Row>
</div>

@ -58,6 +58,23 @@ export const VSDataTag = ({ diffPercent=0, diffData=0, data1=0, data2=0, dataSuf
);
};
/**
* 表格中显示数据对比
*
* @property {boolean | undefined} [showDiffData=false]
* @property {number} [diffPercent=0]
* @property {number} diffData
* @property {number} data1
* @property {number} data2
* @property {string} dataSuffix
*/
export const RenderVSDataCell = ({ showDiffData=false, data1, data2, dataSuffix = '', ...props }) => {
if (showDiffData) {
return <VSDataTag data1={data1} data2={data2} dataSuffix={dataSuffix} {...props} />;
}
return <div>{data1}{dataSuffix}</div>;
};
/**
* 导出表格数据存为xlsx
* @property label 文件名字

@ -0,0 +1,171 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { fetchJSON } from '@haina/utils-request';
import { HT_HOST } from '../config';
import { formatDate, isEmpty, sortBy } from '@haina/utils-commons';
import { groupsMappedByKey, sitesMappedByCode, pivotBy } from './../libs/ht';
const defaultParams = {};
export const fetchRegularCustomer = async (params) => {
const _params = {
Website: params.WebCode || '', // CHT,AH,JH,GH,ZWQD,GH_ZWQD_HW,GHKYZG,GHKYHW,HTravel
DEI_SNList: params.DepartmentList || '', // 1,2,28,7
ApplydateCheck: params.DateType === 'applyDate' ? 1 : 0, //
EntrancedateCheck: params.DateType === 'startDate' ? 1 : 0, //
ConfirmDateCheck: params.DateType === 'confirmDate' ? 1 : 0, //
ApplydateStart: params.Date1 || '',
ApplydateEnd: params.Date2 || '',
EntrancedateStart: params.Date1 || '',
EntrancedateEnd: params.Date2 || '',
ConfirmdateStart: params.Date1 || '',
ConfirmdateEnd: params.Date2 || '',
IsDetail: '', //
IncludeTickets: '', //
...params,
};
const { WebCode, DepartmentList, DateType, Date1, Date2, ...readyParams } = _params;
const [result1, result2] = await Promise.all([
fetchJSON(HT_HOST + '/service-tourdesign/RegularCusOrder', { ...defaultParams, ...readyParams }),
...(params.DateDiff1 && params.IsDetail === 0
? [
fetchJSON(HT_HOST + '/service-tourdesign/RegularCusOrder', {
...defaultParams,
...readyParams,
ApplydateStart: params.DateDiff1 || '',
ApplydateEnd: params.DateDiff2 || '',
EntrancedateStart: params.DateDiff1 || '',
EntrancedateEnd: params.DateDiff2 || '',
ConfirmdateStart: params.DateDiff1 || '',
ConfirmdateEnd: params.DateDiff2 || '',
}),
]
: []),
]);
if (params.IsDetail === 1) {
return { result1, result2 };
}
const ret = {};
const result1Mapped = result1.reduce((r, v) => ({ ...r, [v.ItemName]: v }), {});
const allKeys = [...new Set([...result1.map((e) => e.ItemName), ...result2.map((e) => e.ItemName)])];
const result2Mapped = result2.reduce((r, v) => ({ ...r, [v.ItemName]: v }), {});
const x = {};
allKeys.forEach((key) => {
x[key] = { ...(result1Mapped?.[key] || { ItemName: key }), diff: result2Mapped[key] || {} };
});
ret.result1 = Object.values(x);
console.log(ret);
return { result1: ret.result1 }; // { result1, result2 };
};
// 老客户: 日期对应的数据字段
const dateTypeDataHelper = {
applyDate: 'SumOrder',
startDate: 'ConfirmOrder',
confirmDate: 'ConfirmOrder',
};
/**
* 构建 老客户系列数据
* * 用明细数据计算
* @param {[]} details
* @param {string} pivotByOrder
* @param {string} pivotByDate
* @returns
*/
const buildSeriesDataFromDetails = (details, pivotByOrder, pivotByDate) => {
const dataDetail = (details || []).map((ele) => ({
...ele,
key: ele.COLI_ID,
orderState: ele.OrderState,
applyDate: formatDate(new Date(ele.COLI_ApplyDate)),
startDate: ele.COLI_OrderStartDate,
confirmDate: formatDate(new Date(ele.COLI_ConfirmDate)),
}));
const { data: IsOldData } = pivotBy(
dataDetail.filter((ele) => ele.COLI_IsOld === '是'),
[['COLI_IsOld'], [], pivotByDate]
);
const { data: isCusCommendData } = pivotBy(
dataDetail.filter((ele) => ele.COLI_IsCusCommend === '是'),
[['COLI_IsCusCommend'], [], pivotByDate]
);
// console.log('IsOldData====', IsOldData, '\nisCusCommend', isCusCommendData);
// 合并成两个系列
const seriesData = []
.concat(
IsOldData.map((ele) => ({ ...ele, _ylabel: '老客户' })),
isCusCommendData.map((ele) => ({ ...ele, _ylabel: '老客户推荐' }))
)
.sort(sortBy(pivotByDate));
return seriesData;
};
/**
* --------------------------------------------------------------------------------------------------------
*/
const initialState = {
loading: false,
loading2: false,
searchValues: {
DepartmentList: ['1', '2', '28', '7'].map((kk) => groupsMappedByKey[kk]),
WebCode: ['CHT', 'AH', 'JH', 'GH', 'ZWQD', 'GH_ZWQD_HW', 'GHKYZG', 'GHKYHW', 'HTravel'].map((kk) => sitesMappedByCode[kk]),
DateType: { key: 'applyDate', label: '提交日期' },
IncludeTickets: { key: '0', label: '不含门票' },
},
searchValuesToSub: {},
regular: { data: [], details: [], total_data_tips: '', pivotData: [], pivotY: 'SumOrder', pivotX: '' },
};
const useCustomerRelationsStore = create(
devtools(
immer((set, get) => ({
...initialState,
reset: () => set(initialState),
setLoading: (loading) => set({ loading }),
setLoading2: (loading2) => set({ loading2 }),
setSearchValues: (obj, values) => set((state) => ({ searchValues: values, searchValuesToSub: obj })),
setSearchValuesToSub: (values) => set((state) => ({ searchValuesToSub: values })),
// 获取数据 ---------------------------------------------------------------------------------------------------
// 老客户
getRegularCustomer: async (params) => {
const { setLoading, setLoading2 } = get();
const { IsDetail } = params;
setLoading(true);
setLoading2(IsDetail === 1);
const pivotByOrder = dateTypeDataHelper[params.DateType];
const pivotByDate = params.DateType;
try {
const {result1, result2} = await fetchRegularCustomer(params);
set((state) => {
if (IsDetail === 1) {
state.regular.details = result1;
const dump_l = (result1 || []).filter((ele) => ele.COLI_IsOld !== '' && ele.COLI_IsCusCommend !== '').length;
state.regular.total_data_tips = dump_l > 0 ? `包含 ${dump_l} 条同时勾选的数据` : '';
/** 使用明细数据画图 */
const seriesData = buildSeriesDataFromDetails(result1, pivotByOrder, pivotByDate);
state.regular.pivotData = seriesData;
state.regular.pivotX = pivotByDate;
state.regular.pivotY = pivotByOrder;
} else {
state.regular.data = result1;
}
});
} catch (error) {
} finally {
setLoading(false);
IsDetail === 1 && setLoading2(false);
}
},
})),
{ name: 'CustomerRelations' }
)
);
export default useCustomerRelationsStore;
Loading…
Cancel
Save