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 { observer } from 'mobx-react';
import SearchForm from './../components/search/SearchForm'; import SearchForm from './../components/search/SearchForm';
import LineWithAvg from '../components/LineWithAvg'; import LineWithAvg from '../components/LineWithAvg';
import { flow } from 'mobx'; import { toJS } from 'mobx';
import { TableExportBtn } from '../components/Data'; 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 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 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 = [ const columns = [
{ {
title: '订单号', title: '订单号',
@ -205,13 +216,14 @@ const Customer_care_regular = () => {
return ( return (
<div> <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}> <Col className="gutter-row" span={24}>
<SearchForm <SearchForm
defaultValue={{ defaultValue={{
initialValue: { initialValue: {
...date_picker_store.formValues, ...toJS(date_picker_store.formValues),
...regular_data.searchValues, // ...toJS(regular_data.searchValues),
...searchValues,
}, },
shows: ['DateType', 'DepartmentList', 'WebCode', 'dates', 'IncludeTickets'], shows: ['DateType', 'DepartmentList', 'WebCode', 'dates', 'IncludeTickets'],
fieldProps: { fieldProps: {
@ -222,21 +234,25 @@ const Customer_care_regular = () => {
}} }}
onSubmit={async (_err, obj, form, str) => { onSubmit={async (_err, obj, form, str) => {
customer_store.setSearchValues(obj, form, 'regular_data'); customer_store.setSearchValues(obj, form, 'regular_data');
regular_data.data_compare=[]; setSearchValues(obj, form);
if (obj.DateDiff1 && obj.DateDiff2){ getRegularCustomer({ ...obj, IsDetail: 0 });
regular_data.isCompareLine=true; getRegularCustomer({ ...obj, IsDetail: 1 });
regular_data.showCompareSum=true;
await customer_store.regular_customer_order(); // regular_data.data_compare=[];
customer_store.regular_customer_order(false,true); // if (obj.DateDiff1 && obj.DateDiff2){
// customer_store.regular_customer_order(true,false,true); // regular_data.isCompareLine=true;
// customer_store.regular_customer_order(true,true,true); // regular_data.showCompareSum=true;
} // await customer_store.regular_customer_order();
else{ // customer_store.regular_customer_order(false,true);
regular_data.isCompareLine=false; // // customer_store.regular_customer_order(true,false,true);
regular_data.showCompareSum=false; // customer_store.regular_customer_order(true,true,true);
customer_store.regular_customer_order(); // }
// customer_store.regular_customer_order(true); // else{
} // regular_data.isCompareLine=false;
// regular_data.showCompareSum=false;
// customer_store.regular_customer_order();
// customer_store.regular_customer_order(true);
// }
}} }}
/> />
</Col> </Col>
@ -247,8 +263,8 @@ const Customer_care_regular = () => {
</Col> </Col>
<Col span={24}> <Col span={24}>
<Table <Table
dataSource={regular_data.data} dataSource={regular.data}
loading={regular_data.loading} loading={loading}
columns={[ columns={[
{ {
title: '统计条目', title: '统计条目',
@ -259,7 +275,7 @@ const Customer_care_regular = () => {
title: () => ( title: () => (
<> <>
订单数{' '} 订单数{' '}
<Tooltip key='total_data_tips_title' title="总订单: 当同时勾选老客户和推荐时, 将重复计数"> <Tooltip key="total_data_tips_title" title="总订单: 当同时勾选老客户和推荐时, 将重复计数">
<InfoCircleOutlined /> <InfoCircleOutlined />
</Tooltip> </Tooltip>
</> </>
@ -268,10 +284,14 @@ const Customer_care_regular = () => {
key: 'OrderNum', key: 'OrderNum',
render: (text, record, index) => ( render: (text, record, index) => (
<> <>
<span>{text}</span>&nbsp;&nbsp; <RenderVSDataCell showDiffData={true} data1={record.OrderNum} data2={record.diff?.OrderNum} />
{<Tooltip key='total_data_tips' title={regular_data.total_data_tips}> {/* <span>{text}</span> */}
{index === 0 && regular_data.total_data_tips!=='' && <InfoCircleOutlined className='ant-tag-gold' />} &nbsp;&nbsp;
</Tooltip>} {
<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: '订单数占比', title: '订单数占比',
dataIndex: 'OrderRate', dataIndex: 'OrderRate',
key: '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: '订单数占比(市场)', title: '订单数占比(市场)',
dataIndex: 'OrderRate2', dataIndex: 'OrderRate2',
key: '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: '成行数', title: '成行数',
dataIndex: 'SUCOrderNum', dataIndex: 'SUCOrderNum',
key: 'SUCOrderNum', key: 'SUCOrderNum',
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={(record.SUCOrderNum)} data2={(record.diff?.SUCOrderNum)} />
}, },
{ {
title: '成行率', title: '成行率',
dataIndex: 'SUCRate', dataIndex: 'SUCRate',
key: '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: '毛利', title: '毛利',
dataIndex: 'ML', dataIndex: 'ML',
key: 'ML', key: 'ML',
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={(record.ML)} data2={(record.diff?.ML)} />
}, },
{ {
title: '毛利占比', title: '毛利占比',
dataIndex: 'OrderMLRate', dataIndex: 'OrderMLRate',
key: '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: '毛利占比(市场)', title: '毛利占比(市场)',
dataIndex: 'OrderMLRate2', dataIndex: 'OrderMLRate2',
key: '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: '人数(含成人+儿童)', title: '人数(含成人+儿童)',
dataIndex: 'PersonNum', dataIndex: 'PersonNum',
key: 'PersonNum', key: 'PersonNum',
render: (text, record) => <RenderVSDataCell showDiffData={true} data1={(record.PersonNum)} data2={(record.diff?.PersonNum)} />
}, },
]} ]}
size="small" size="small"
@ -327,65 +350,38 @@ const Customer_care_regular = () => {
/> />
</Col> </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}> <Col span={24}>
<LineWithAvg dataSource={regular_data.pivotData} loading={regular_data.detail_loading} xField={regular_data.pivotX} yField={regular_data.pivotY} <div style={{ height: '100%' }}>
seriesField='_ylabel' showCompareSum={regular_data.showCompareSum} solidLineTime={regular_data.solidLineTime} solidLineCompareTime={regular_data.solidLineCompareTime} <Col span={24}>
solidLineDash={regular_data.solidLineDash} isCompareLine={regular_data.isCompareLine}/> <LineWithAvg
</Col> 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}> <Col span={24}>
<Divider orientation="right" plain> <Divider orientation="right" plain>
<a <a
onClick={() => { onClick={() => {
const wb = utils.table_to_book(document.getElementById('table_to_xlsx').getElementsByTagName('table')[0]); const wb = utils.table_to_book(document.getElementById('table_to_xlsx').getElementsByTagName('table')[0]);
writeFileXLSX(wb, '老客户.xlsx'); writeFileXLSX(wb, '老客户.xlsx');
}} }}
> >
导出下表 导出下表
</a> </a>
<TableExportBtn btnTxt='导出详情' label={'老客户-详情'} columns={export_columns} dataSource={regular_data.data_detail} style={{ marginLeft: '10px' }} /> <TableExportBtn btnTxt="导出详情" label={'老客户-详情'} columns={export_columns} dataSource={regular.details} style={{ marginLeft: '10px' }} />
</Divider> </Divider>
<Table <Table id="table_to_xlsx" pagination={false} loading={loading2} dataSource={regular.details} scroll={{ x: 1200 }} columns={columns} size="small" rowKey={(record) => record.COLI_ID} />
id="table_to_xlsx" </Col>
pagination={false} </div>
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> </Col>
</Row> </Row>
</div> </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 * 导出表格数据存为xlsx
* @property label 文件名字 * @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