feat: 老客户-分析

main
Lei OT 2 months ago
parent 40cd81f9a7
commit a0ea6507a3

@ -128,7 +128,7 @@ const App = () => {
key: 32,
label: <NavLink to="/customer_care_regular">老客户</NavLink>,
},
{ key: 'customer_care_regular_sales', label: <NavLink to="/customer_care_regular_sales">销售-老客户</NavLink> },
{ key: 'customer_care_regular_sales', label: <NavLink to="/customer_care_regular_sales">老客户-分析</NavLink> },
{
key: 33,
label: <NavLink to="/customer_care_inchina">在华客户</NavLink>,

@ -2,7 +2,7 @@ import {makeAutoObservable, runInAction} from "mobx";
import { fetchJSON } from '../utils/request';
import * as config from "../config";
import { groupsMappedByKey, sitesMappedByCode, pivotBy } from './../libs/ht';
import { sortBy, show_vs_tag, formatPercent, groupBy, isEmpty } from "../utils/commons";
import { sortBy, show_vs_tag, formatPercent, groupBy, isEmpty, uniqWith } from "../utils/commons";
import moment from 'moment';
/**
@ -458,64 +458,78 @@ class CustomerStore {
DateType: { key: 'applyDate', label: '提交日期'},
IncludeTickets: { key: '0', label: '不含门票' },
},
pivotData: {
operatorName: { loading: false, data: [], rawData: [], filterColValues: [] },
country: { loading: false, data: [], rawData: [], filterColValues: [] },
},
};
get_sales_regular_data_vs = async (param) => {
this.sales_regular_data.loading = true;
get_sales_regular_data_vs = async (param, pivotRow = 'operatorName') => {
this.sales_regular_data.pivotData[pivotRow].loading = true;
const hasCompare = !isEmpty(param.DateDiff1);
const [result1, result2] = await Promise.all([
this.get_sales_regular_data(param),
hasCompare ? this.get_sales_regular_data({...param, Date1: param.DateDiff1, Date2: param.DateDiff2}) : { mergeDataBySales: [], mergeDataBySalesAccount: [], filterHasOld: []},
this.get_sales_regular_data(param, pivotRow),
hasCompare ? this.get_sales_regular_data({...param, Date1: param.DateDiff1, Date2: param.DateDiff2}, pivotRow) : { mergeDataBySales: [], mergeDataBySalesAccount: [], filterHasOld: []},
]);
const allTypes = ['老客户', '老客户推荐'];
// 独立的账户
const allSales = Array.from(new Set([...result1.mergeDataBySales.map(row=>row.operatorName), ...result2.mergeDataBySales.map(row=>row.operatorName)]));
const sales1 = groupBy(result1.mergeDataBySales, 'operatorName');
const sales2 = groupBy(result2.mergeDataBySales, 'operatorName');
const allSales = Array.from(new Set([...result1.mergeDataBySales.map(row=>row[pivotRow]), ...result2.mergeDataBySales.map(row=>row[pivotRow])]));
const sales1 = groupBy(result1.mergeDataBySales, pivotRow);
const sales2 = groupBy(result2.mergeDataBySales, pivotRow);
const mergeDataBySales = allSales.reduce((r, sale) => {
const _default = { operatorName: sale, rowLabel: sales1?.[sale]?.rowLabel || sales2?.[sale]?.rowLabel, children: sales1?.[sale]?.children || [], key: sale};
const _default = { [pivotRow]: sale, rowLabel: sales1?.[sale]?.rowLabel || sales2?.[sale]?.rowLabel, children: sales1?.[sale]?.children || [], key: sale};
const operatorRow = {...(sales1?.[sale]?.[0] || _default), vsData: sales2?.[sale]?.[0] || {}};
// 展开的两项: '老客户', '老客户推荐'
const series1Children = sales1?.[sale]?.[0]?.children || [];
const series2Children = sales2?.[sale]?.[0]?.children || [];
const children = allTypes.reduce((r, type) => {
const _default = { operatorName: type, rowLabel: type, key: type};
const _typeRow = series1Children.find(sc => sc.operatorName === type) || _default;
const _typeVSRow = series2Children.find(sc => sc.operatorName === type) || {};
const _default = { [pivotRow]: type, rowLabel: type, key: type};
const _typeRow = series1Children.find(sc => sc[pivotRow] === type) || _default;
const _typeVSRow = series2Children.find(sc => sc[pivotRow] === type) || {};
return r.concat({..._typeRow, vsData: _typeVSRow});
}, []);
operatorRow.children = children;
return r.concat(operatorRow);
}, []);
// 合并顾问的账户
const allSalesMerged = Array.from(new Set([...result1.mergeDataBySalesAccount.map(row=>row.operatorName), ...result2.mergeDataBySalesAccount.map(row=>row.operatorName)]));
const salesM1 = groupBy(result1.mergeDataBySalesAccount, 'operatorName');
const salesM2 = groupBy(result2.mergeDataBySalesAccount, 'operatorName');
const mergeDataBySalesAccount = allSalesMerged.reduce((r, sale) => {
const _default = { operatorName: sale, rowLabel: salesM1?.[sale]?.rowLabel || salesM2?.[sale]?.rowLabel, children: salesM1?.[sale]?.children || [], key: sale};
const operatorRow = {...(salesM1?.[sale]?.[0] || _default), vsData: salesM2?.[sale]?.[0] || {}};
// 展开的两项: '老客户', '老客户推荐'
const series1Children = salesM1?.[sale]?.[0]?.children || [];
const series2Children = salesM2?.[sale]?.[0]?.children || [];
const children = allTypes.reduce((r, type) => {
const _default = { operatorName: type, rowLabel: type, key: type};
const _typeRow = series1Children.find(sc => sc.operatorName === type) || _default;
const _typeVSRow = series2Children.find(sc => sc.operatorName === type) || {};
return r.concat({..._typeRow, vsData: _typeVSRow});
let mergeDataBySalesAccount = [];
if (pivotRow === 'operatorName') {
const allSalesMerged = Array.from(new Set([...result1.mergeDataBySalesAccount.map(row=>row.operatorName), ...result2.mergeDataBySalesAccount.map(row=>row.operatorName)]));
const salesM1 = groupBy(result1.mergeDataBySalesAccount, 'operatorName');
const salesM2 = groupBy(result2.mergeDataBySalesAccount, 'operatorName');
mergeDataBySalesAccount = allSalesMerged.reduce((r, sale) => {
const _default = { operatorName: sale, rowLabel: salesM1?.[sale]?.rowLabel || salesM2?.[sale]?.rowLabel, children: salesM1?.[sale]?.children || [], key: sale};
const operatorRow = {...(salesM1?.[sale]?.[0] || _default), vsData: salesM2?.[sale]?.[0] || {}};
// 展开的两项: '老客户', '老客户推荐'
const series1Children = salesM1?.[sale]?.[0]?.children || [];
const series2Children = salesM2?.[sale]?.[0]?.children || [];
const children = allTypes.reduce((r, type) => {
const _default = { operatorName: type, rowLabel: type, key: type};
const _typeRow = series1Children.find(sc => sc.operatorName === type) || _default;
const _typeVSRow = series2Children.find(sc => sc.operatorName === type) || {};
return r.concat({..._typeRow, vsData: _typeVSRow});
}, []);
operatorRow.children = children;
return r.concat(operatorRow);
}, []);
operatorRow.children = children;
return r.concat(operatorRow);
}, []);
}
this.sales_regular_data.loading = false;
this.sales_regular_data.data = mergeDataBySales;
this.sales_regular_data.mergedData = mergeDataBySalesAccount;
this.sales_regular_data.rawData = [].concat(result1.filterHasOld, result2.filterHasOld);
const filterColValues = uniqWith(
mergeDataBySales.map((rr) => ({ text: rr[pivotRow], value: rr[pivotRow] })),
(a, b) => JSON.stringify(a) === JSON.stringify(b)
).sort((a, b) => a.text.localeCompare(b.text, 'zh-CN'));
this.sales_regular_data.pivotData[pivotRow].loading = false;
this.sales_regular_data.pivotData[pivotRow].rawData = [].concat(result1.filterHasOld, result2.filterHasOld);
this.sales_regular_data.pivotData[pivotRow].data = mergeDataBySales;
this.sales_regular_data.pivotData[pivotRow].mergedData = isEmpty(mergeDataBySalesAccount) ? mergeDataBySales : mergeDataBySalesAccount;
this.sales_regular_data.pivotData[pivotRow].filterColValues = filterColValues;
};
get_sales_regular_data = async (param) => {
get_sales_regular_data = async (param, pivotRow = 'operatorName') => {
const seriesKey = `${param.Date1}${param.Date2}`;
const rawData = await getDetailData({...param, });
const filterHasOld = rawData.filter(ele => (ele.IsOld === '1' || ele.isCusCommend === '1')).map(e => ({
@ -524,40 +538,38 @@ class CustomerStore {
operatorNameB: e.operatorName.replace(/\([^)]*\)/gi, '').toLowerCase(),
}));
const { data: hasOldData, } = pivotBy(filterHasOld, [['hasOld', 'operatorName'], [], []]);
const { data: IsOldData, } = pivotBy(filterHasOld.filter(ele => ele.IsOld === '1'), [['operatorName', 'IsOld_txt', ], [], []]);
const { data: isCusCommendData, } = pivotBy(filterHasOld.filter(ele => ele.isCusCommend === '1'), [['operatorName', 'isCusCommend_txt', ], [], []]);
const { data: hasOldData, } = pivotBy(filterHasOld, [['hasOld', pivotRow], [], []]);
const { data: IsOldData, } = pivotBy(filterHasOld.filter(ele => ele.IsOld === '1'), [[pivotRow, 'IsOld_txt', ], [], []]);
const { data: isCusCommendData, } = pivotBy(filterHasOld.filter(ele => ele.isCusCommend === '1'), [[pivotRow, 'isCusCommend_txt', ], [], []]);
// console.log('IsOldData====', IsOldData, '\nisCusCommend', isCusCommendData);
// console.log('data====', rawData, '\ncolumnValues', columnValues, '\nsummaryRows', summaryRows, '\nsummaryColumns', summaryColumns, '\nsummaryMix', summaryMix, '\nhasOld',filterHasOld);
const mergeDataBySales = hasOldData.map((ele) => ({
...ele,
seriesKey,
children: [].concat(
IsOldData.filter((ele1) => ele1.operatorName === ele.operatorName).map(o => ({...o, operatorName: o.IsOld_txt, key: o.rowLabel, seriesKey,})),
isCusCommendData.filter((ele2) => ele2.operatorName === ele.operatorName).map(o => ({...o, operatorName: o.isCusCommend_txt, key: o.rowLabel, seriesKey,}))
IsOldData.filter((ele1) => ele1[pivotRow] === ele[pivotRow]).map(o => ({...o, [pivotRow]: o.IsOld_txt, key: o.rowLabel, seriesKey,})),
isCusCommendData.filter((ele2) => ele2[pivotRow] === ele[pivotRow]).map(o => ({...o, [pivotRow]: o.isCusCommend_txt, key: o.rowLabel, seriesKey,}))
),
}));
// 合并顾问的账户
const { data: hasOldDataSales, } = pivotBy(filterHasOld, [['hasOld', 'operatorNameB'], [], []]);
const { data: IsOldDataSales, } = pivotBy(filterHasOld.filter(ele => ele.IsOld === '1'), [['operatorNameB', 'IsOld_txt', ], [], []]);
const { data: isCusCommendDataSales, } = pivotBy(filterHasOld.filter(ele => ele.isCusCommend === '1'), [['operatorNameB', 'isCusCommend_txt', ], [], []]);
const mergeDataBySalesAccount = hasOldDataSales.map((ele) => ({
...ele,
operatorName: ele.operatorNameB,
seriesKey,
children: [].concat(
IsOldDataSales.filter((ele1) => ele1.operatorNameB === ele.operatorNameB).map(o => ({...o, operatorName: o.IsOld_txt, key: o.rowLabel, seriesKey,})),
isCusCommendDataSales.filter((ele2) => ele2.operatorNameB === ele.operatorNameB).map(o => ({...o, operatorName: o.isCusCommend_txt, key: o.rowLabel, seriesKey,}))
),
}));
let mergeDataBySalesAccount = [];
if (pivotRow === 'operatorName') {
const { data: hasOldDataSales, } = pivotBy(filterHasOld, [['hasOld', 'operatorNameB'], [], []]);
const { data: IsOldDataSales, } = pivotBy(filterHasOld.filter(ele => ele.IsOld === '1'), [['operatorNameB', 'IsOld_txt', ], [], []]);
const { data: isCusCommendDataSales, } = pivotBy(filterHasOld.filter(ele => ele.isCusCommend === '1'), [['operatorNameB', 'isCusCommend_txt', ], [], []]);
mergeDataBySalesAccount = hasOldDataSales.map((ele) => ({
...ele,
operatorName: ele.operatorNameB,
seriesKey,
children: [].concat(
IsOldDataSales.filter((ele1) => ele1.operatorNameB === ele.operatorNameB).map(o => ({...o, operatorName: o.IsOld_txt, key: o.rowLabel, seriesKey,})),
isCusCommendDataSales.filter((ele2) => ele2.operatorNameB === ele.operatorNameB).map(o => ({...o, operatorName: o.isCusCommend_txt, key: o.rowLabel, seriesKey,}))
),
}));
}
// console.log('IsOldDataSales====', IsOldDataSales, '\nisCusCommendDataSales', isCusCommendDataSales);
// console.log('mergeDataBySalesAccount====', mergeDataBySalesAccount);
return { mergeDataBySales, mergeDataBySalesAccount, filterHasOld };
// this.sales_regular_data.loading = false;
// this.sales_regular_data.data = mergeDataBySales;
// this.sales_regular_data.mergedData = mergeDataBySalesAccount;
// this.sales_regular_data.rawData = filterHasOld;
};
setSearchValues(obj, values, target) {

@ -38,7 +38,7 @@ const HostCaseCount = () => {
},
];
//
console.log(host_case_data.groupData);
// console.log(host_case_data.groupData);
const allOPI1 = comm.uniqWith(host_case_data.groupData.map(rr => ({ text: rr.GroupBy, value: rr.GroupBy })),
(a, b) => JSON.stringify(a) === JSON.stringify(b)).sort((a, b) => a.text.localeCompare(b.text));
//

@ -1,10 +1,10 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { useContext, useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Table, Row, Col, Divider, Button, Switch, Space } from 'antd';
import { Table, Row, Col, Divider, Switch, Space, Tabs } from 'antd';
import SearchForm from '../components/search/SearchForm';
import { TableExportBtn, VSTag } from './../components/Data';
import { uniqWith, groupBy, fixTo2Decimals, isEmpty } from './../utils/commons';
import { fixTo2Decimals, isEmpty } from './../utils/commons';
// TdCellDataTable
const TdCell = (tdprops) => {
@ -16,14 +16,15 @@ const TdCell = (tdprops) => {
const SalesCustomerCareRegular = (props) => {
const { date_picker_store: searchFormStore, customer_store } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const { sales_regular_data: pageData } = customer_store;
const { pivotData: pageData } = customer_store.sales_regular_data;
const allOPI1 = uniqWith(
pageData.data.map((rr) => ({ text: rr.operatorName, value: rr.operatorName })),
(a, b) => JSON.stringify(a) === JSON.stringify(b)
).sort((a, b) => a.text.localeCompare(b.text));
const pivotOptions = [{key: 'operatorName', label: '顾问'}, {key: 'country', label: '国家'}];
const [pivotRow, setPivotRow] = useState('operatorName');
const onTabsChange = (key) => {
setPivotRow(key);
};
const [OPIFilters, setOPIFilters] = useState([]);
const [dataSource, setDataSource] = useState([]);
const [ifmerge, setIfmerge] = useState(false);
const [dataForExport, setDataForExport] = useState([]);
@ -31,34 +32,24 @@ const SalesCustomerCareRegular = (props) => {
useEffect(() => {
if ( ! ifmerge) {
// const allOPI1 = uniqWith(
// pageData.data.map((rr) => ({ text: rr.operatorName, value: rr.operatorName })),
// (a, b) => JSON.stringify(a) === JSON.stringify(b)
// ).sort((a, b) => a.text.localeCompare(b.text));
// setOPIFilters(allOPI1);
setDataSource(pageData.data);
setDataSource(pageData[pivotRow].data);
setDataForExport(
pageData.data.reduce(
pageData[pivotRow].data.reduce(
(r, c) =>
r.concat(
[{ ...c, children: undefined }],
c.children
.reduce((rc, ele) => rc.concat([{ ...ele, operatorName: ele.rowLabel }], [{ ...ele.vsData, operatorName: ele.vsData.rowLabel, vsData: {} }]), [])
.reduce((rc, ele) => rc.concat([{ ...ele, [pivotRow]: ele.rowLabel }], [{ ...ele.vsData, [pivotRow]: ele.vsData.rowLabel, vsData: {} }]), [])
.filter((ele) => ele.SumOrder !== undefined)
),
[]
)
);
setDataForExportS(pageData.data.reduce((r, c) => r.concat([{...c, children: undefined}], [{ ...c.vsData, vsData: {} }]), []).filter((ele) => ele.SumOrder !== undefined));
setDataForExportS(pageData[pivotRow].data.reduce((r, c) => r.concat([{...c, children: undefined}], [{ ...c.vsData, vsData: {} }]), []).filter((ele) => ele.SumOrder !== undefined));
} else {
// const allOPI1 = uniqWith(
// pageData.mergedData.map((rr) => ({ text: rr.operatorName, value: rr.operatorName })),
// (a, b) => JSON.stringify(a) === JSON.stringify(b)
// ).sort((a, b) => a.text.localeCompare(b.text));
// setOPIFilters(allOPI1);
setDataSource(pageData.mergedData);
setDataSource(pageData[pivotRow].mergedData);
setDataForExport(
pageData.mergedData.reduce(
pageData[pivotRow].mergedData.reduce(
(r, c) =>
r.concat(
[{ ...c, children: undefined }],
@ -68,13 +59,11 @@ const SalesCustomerCareRegular = (props) => {
[]
)
);
setDataForExportS(pageData.mergedData.reduce((r, c) => r.concat([{...c, children: undefined}], [{ ...c.vsData, vsData: {} }]), []).filter((ele) => ele.SumOrder !== undefined));
setDataForExportS(pageData[pivotRow].mergedData.reduce((r, c) => r.concat([{...c, children: undefined}], [{ ...c.vsData, vsData: {} }]), []).filter((ele) => ele.SumOrder !== undefined));
}
return () => {
};
}, [ifmerge, pageData.data]);
return () => {};
}, [ifmerge, pageData[pivotRow].data]);
const rowColumns = [
@ -109,7 +98,6 @@ const SalesCustomerCareRegular = (props) => {
</>;
};
const columns = [
{ key: 'operatorName', title: '顾问', dataIndex: 'operatorName', width: '6em', filters: allOPI1, onFilter: (value, record) => value.includes(record.operatorName), filterSearch: true },
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em', render: (v, r) => renderVS(v, r, 'SumOrder') },
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em', render: (v, r) => renderVS(v, r, 'ConfirmOrder') },
{ key: 'ConfirmPersonNum', title: '✔人数(SUM)', dataIndex: 'ConfirmPersonNum', width: '5em', render: (v, r) => renderVS(v, r, 'ConfirmPersonNum') },
@ -117,10 +105,6 @@ const SalesCustomerCareRegular = (props) => {
{ key: 'SumML', title: '预计毛利', dataIndex: 'SumML', width: '5em', render: (v, r) => renderVS(v, r, 'SumML') }, // SumML_txt
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em', render: (v, r) => renderVS(v, r, 'ConfirmRates') },
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em', render: (v, r) => renderVS(v, r, 'SingleML') },
{ key: 'action', title: '', width: '5em', render: (_, r) => {
const rowChildren = ifmerge ? pageData.rawData.filter(ele => ele.operatorName.includes(r.operatorName)) : pageData.rawData.filter(ele => ele.operatorName === r.operatorName);
return r.hasOld ? <TableExportBtn btnTxt='明细' label={`老客户明细-${r.operatorName}`} {...{ columns: rowColumns, dataSource: rowChildren }} /> : null;
} },
];
return (
<>
@ -130,7 +114,7 @@ const SalesCustomerCareRegular = (props) => {
defaultValue={{
initialValue: {
...formValues,
...pageData.searchValues,
...customer_store.sales_regular_data.searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'dates', 'IncludeTickets'],
fieldProps: {
@ -141,41 +125,78 @@ const SalesCustomerCareRegular = (props) => {
}}
onSubmit={(_err, obj, form, str) => {
customer_store.setSearchValues(obj, form, 'sales_regular_data');
customer_store.get_sales_regular_data_vs(obj);
// MeetingDataStore.setSearchValues(form);
// dataRefresh(obj);
customer_store.get_sales_regular_data_vs(obj, pivotRow);
}}
/>
</Col>
</Row>
<h2>销售-老客户, 含推荐</h2>
<Divider orientation="right" plain>
{pageData.data.length > 0 && (
<Switch
unCheckedChildren="各账户"
checkedChildren="合并"
key={'openOrMerge'}
checked={ifmerge}
onChange={(e) => {
setIfmerge(e);
}}
/>
)}
<Divider type={'vertical'} />
<TableExportBtn btnTxt='导出明细' label={`${formValuesToSub.Date1}-销售.老客户-明细`} {...{ columns: rowColumns, dataSource: pageData.rawData }} />
<Divider type={'vertical'} />
<TableExportBtn btnTxt='导出下表-展开' label={`${formValuesToSub.Date1}-销售.老客户`} {...{ columns: [rowColumns[0], ...columns], dataSource: dataForExport }} />
<Divider type={'vertical'} />
<TableExportBtn btnTxt='导出下表-总' label={`${formValuesToSub.Date1}-销售.老客户`} {...{ columns: [rowColumns[0], ...columns], dataSource: dataForExportS }} />
</Divider>
<Table
sticky
dataSource={dataSource}
loading={pageData.loading}
columns={columns}
pagination={false}
// defaultExpandAllRows
// expandable={{ defaultExpandAllRows: true, expandedRowKeys: pageData.data.map((ele) => ele.key), }}
<Tabs
type={'card'}
activeKey={pivotRow}
onChange={onTabsChange}
items={pivotOptions.map((ele) => {
return {
...ele,
children: (
<>
{/* <h2>{ele.label}-老客户, 含推荐</h2> */}
<Table
sticky
dataSource={dataSource}
loading={pageData[ele.key].loading}
columns={[
{
key: ele.key,
title: ele.label,
dataIndex: ele.key,
width: '6em',
filters: pageData[ele.key].filterColValues,
onFilter: (value, record) => value.includes(record[ele.key]),
filterSearch: true,
},
...columns,
]}
pagination={false}
/>
</>
),
};
})}
tabBarExtraContent={{
right: (
<>
{pageData[pivotRow].data.length > 0 && pivotRow === 'operatorName' && (
<Switch
unCheckedChildren="各账户"
checkedChildren="合并"
key={'openOrMerge'}
checked={ifmerge}
onChange={(e) => {
setIfmerge(e);
}}
/>
)}
<Divider type={'vertical'} />
<TableExportBtn
btnTxt="导出明细"
label={`${formValuesToSub.Date1}-${'ele.label'}.老客户-明细`}
{...{ columns: [{ title: 'ele.label', dataIndex: pivotRow, key: pivotRow }, ...rowColumns], dataSource: pageData[pivotRow].rawData }}
/>
<Divider type={'vertical'} />
<TableExportBtn
btnTxt="导出下表-展开"
label={`${formValuesToSub.Date1}-${'ele.label'}.老客户`}
{...{ columns: [{ title: 'ele.label', dataIndex: pivotRow, key: pivotRow }, ...columns], dataSource: dataForExport }}
/>
<Divider type={'vertical'} />
<TableExportBtn
btnTxt="导出下表-总"
label={`${formValuesToSub.Date1}-${'ele.label'}.老客户`}
{...{ columns: [{ title: 'ele.label', dataIndex: pivotRow, key: pivotRow }, ...columns], dataSource: dataForExportS }}
/>
</>
),
}}
/>
</>
);

Loading…
Cancel
Save