diff --git a/src/stores/CustomerStore.js b/src/stores/CustomerStore.js index 2a53605..c5a98d0 100644 --- a/src/stores/CustomerStore.js +++ b/src/stores/CustomerStore.js @@ -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 } from "../utils/commons"; +import { sortBy, show_vs_tag, formatPercent, groupBy, isEmpty } from "../utils/commons"; import moment from 'moment'; /** @@ -168,13 +168,13 @@ class CustomerStore { if (isCompare){ return { ...item, - _ylabel: date_picker_store.start_date_cp.format(config.DATE_FORMAT)+ '-' +date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT) + item._ylabel + _ylabel: date_picker_store.start_date_cp.format(config.DATE_FORMAT)+ '-' +date_picker_store.end_date_cp.format(config.SMALL_DATETIME_FORMAT) + item._ylabel }; } else{ return { ...item, - _ylabel: date_picker_store.start_date.format(config.DATE_FORMAT)+ '-' +date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT) + item._ylabel + _ylabel: date_picker_store.start_date.format(config.DATE_FORMAT)+ '-' +date_picker_store.end_date.format(config.SMALL_DATETIME_FORMAT) + item._ylabel }; } }); @@ -203,7 +203,7 @@ class CustomerStore { this.regular_data.pivotData = seriesCompareData; // { IsOldData, isCusCommendData, }; this.regular_data.pivotY = pivotByOrder; this.regular_data.pivotX = pivotByDate; - } + } } else{ this.regular_data.detail_loading = false; @@ -226,7 +226,7 @@ class CustomerStore { this.regular_data.pivotData = seriesData; // { IsOldData, isCusCommendData, }; this.regular_data.pivotY = pivotByOrder; this.regular_data.pivotX = pivotByDate; - } + } } else { if (isCompare){ const result = []; @@ -237,15 +237,15 @@ class CustomerStore { result.push({ ItemName: item1.ItemName, OrderNum: show_vs_tag(formatPercent((item1.OrderNum-item2.OrderNum)/(item2.OrderNum===0?1:item2.OrderNum)), - item1.OrderNum-item2.OrderNum,item1.OrderNum,item2.OrderNum), + item1.OrderNum-item2.OrderNum,item1.OrderNum,item2.OrderNum), SUCOrderNum: show_vs_tag(formatPercent((item1.SUCOrderNum-item2.SUCOrderNum)/(item2.SUCOrderNum===0?1:item2.SUCOrderNum)), - item1.SUCOrderNum-item2.SUCOrderNum,item1.SUCOrderNum,item2.SUCOrderNum), + item1.SUCOrderNum-item2.SUCOrderNum,item1.SUCOrderNum,item2.SUCOrderNum), SUCRate: show_vs_tag(formatPercent((item1.SUCRate-item2.SUCRate)/(item2.SUCRate===0?1:item2.SUCRate)), - formatPercent(item1.SUCRate-item2.SUCRate),formatPercent(item1.SUCRate),formatPercent(item2.SUCRate)), + formatPercent(item1.SUCRate-item2.SUCRate),formatPercent(item1.SUCRate),formatPercent(item2.SUCRate)), ML: show_vs_tag(formatPercent((item1.ML-item2.ML)/(item2.ML===0?1:item2.ML)), - (item1.ML-item2.ML).toFixed(2),item1.ML,item2.ML), + (item1.ML-item2.ML).toFixed(2),item1.ML,item2.ML), PersonNum: show_vs_tag(formatPercent((item1.PersonNum-item2.PersonNum)/(item2.PersonNum===0?1:item2.PersonNum)), - item1.PersonNum-item2.PersonNum,item1.PersonNum,item2.PersonNum), + item1.PersonNum-item2.PersonNum,item1.PersonNum,item2.PersonNum), }); } } @@ -410,10 +410,70 @@ class CustomerStore { }, }; - get_sales_regular_data = async (param) => { + // 销售-老客户 + get_sales_regular_data_vs = async (param) => { this.sales_regular_data.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: []}, + ]); + + 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 mergeDataBySales = allSales.reduce((r, sale) => { + const _default = { operatorName: 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) || {}; + 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}); + }, []); + 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); + + }; + + get_sales_regular_data = async (param) => { + const seriesKey = `${param.Date1}至${param.Date2}`; const rawData = await getDetailData({...param, IncludeTickets: 1}); - const filterHasOld = rawData.filter(ele => (ele.IsOld === '1' || ele.isCusCommend === '1')).map(e => ({...e, operatorNameB: e.operatorName.replace(/\([^)]*\)/gi, '').toLowerCase()})); + const filterHasOld = rawData.filter(ele => (ele.IsOld === '1' || ele.isCusCommend === '1')).map(e => ({ + ...e, + seriesKey, + 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', ], [], []]); @@ -422,9 +482,10 @@ class CustomerStore { // 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})), - isCusCommendData.filter((ele2) => ele2.operatorName === ele.operatorName).map(o => ({...o, operatorName: o.isCusCommend_txt, key: o.rowLabel})) + 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,})) ), })); @@ -435,18 +496,19 @@ class CustomerStore { 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})), - isCusCommendDataSales.filter((ele2) => ele2.operatorNameB === ele.operatorNameB).map(o => ({...o, operatorName: o.isCusCommend_txt, key: o.rowLabel})) + 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); - - this.sales_regular_data.loading = false; - this.sales_regular_data.data = mergeDataBySales; - this.sales_regular_data.mergedData = mergeDataBySalesAccount; - this.sales_regular_data.rawData = filterHasOld; + 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) { diff --git a/src/views/SalesCustomerCareRegular.jsx b/src/views/SalesCustomerCareRegular.jsx index ebc44c8..33b8c6a 100644 --- a/src/views/SalesCustomerCareRegular.jsx +++ b/src/views/SalesCustomerCareRegular.jsx @@ -1,10 +1,10 @@ import { createContext, useContext, useEffect, useState } from 'react'; import { observer } from 'mobx-react'; import { stores_Context } from '../config'; -import { Table, Row, Col, Divider, Button, Switch } from 'antd'; +import { Table, Row, Col, Divider, Button, Switch, Space } from 'antd'; import SearchForm from '../components/search/SearchForm'; -import { TableExportBtn } from './../components/Data'; -import { uniqWith, groupBy } from './../utils/commons'; +import { TableExportBtn, VSTag } from './../components/Data'; +import { uniqWith, groupBy, fixTo2Decimals, isEmpty } from './../utils/commons'; // 注意TdCell要提到DataTable作用域外声明 const TdCell = (tdprops) => { @@ -37,8 +37,19 @@ const SalesCustomerCareRegular = (props) => { // ).sort((a, b) => a.text.localeCompare(b.text)); // setOPIFilters(allOPI1); setDataSource(pageData.data); - setDataForExport(pageData.data.reduce((r, c) => r.concat([{...c, children: undefined}], c.children.map(ele => ({...ele, operatorName: ele.rowLabel}))), [])); - setDataForExportS(pageData.data.reduce((r, c) => r.concat([{...c, children: undefined}]), [])); + setDataForExport( + pageData.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: {} }]), []) + .filter((ele) => ele.SumOrder !== undefined) + ), + [] + ) + ); + setDataForExportS(pageData.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 })), @@ -46,8 +57,18 @@ const SalesCustomerCareRegular = (props) => { // ).sort((a, b) => a.text.localeCompare(b.text)); // setOPIFilters(allOPI1); setDataSource(pageData.mergedData); - setDataForExport(pageData.mergedData.reduce((r, c) => r.concat([{...c, children: undefined}], c.children.map(ele => ({...ele, operatorName: ele.rowLabel}))), [])); - setDataForExportS(pageData.mergedData.reduce((r, c) => r.concat([{...c, children: undefined}]), [])); + setDataForExport( + pageData.mergedData.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: {} }]), []) + .filter((ele) => ele.SumOrder !== undefined) + ), + [] + ) + ); + setDataForExportS(pageData.mergedData.reduce((r, c) => r.concat([{...c, children: undefined}], [{ ...c.vsData, vsData: {} }]), []).filter((ele) => ele.SumOrder !== undefined)); } return () => { @@ -57,6 +78,7 @@ const SalesCustomerCareRegular = (props) => { const rowColumns = [ + { title: '日期区间', dataIndex: 'seriesKey', key: 'seriesKey' }, { title: '顾问', dataIndex: 'operatorName', key: 'operatorName' }, { title: '订单号', dataIndex: 'o_id', key: 'o_id' }, { title: '预定日期', dataIndex: 'applyDate', key: 'applyDate' }, @@ -73,17 +95,30 @@ const SalesCustomerCareRegular = (props) => { { title: '来源', dataIndex: 'SourceType', key: 'SourceType' }, { title: '页面类型', dataIndex: 'COLI_LineClass', key: 'COLI_LineClass' }, ]; + const calcDelta = (r, key) => !isEmpty(Number(r.vsData[key])) ? fixTo2Decimals((Number(r[key] || 0) - Number(r.vsData[key]))/Number(r.vsData[key]) *100) : null; + const renderVS = (v, r, key) => { + const delta = calcDelta(r, key); + return <> + + + {v || 0} + {r.vsData[key] ? VS {r.vsData[key]} : null} + + {delta && } + + ; + }; 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' }, - { key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em' }, - { key: 'ConfirmPersonNum', title: '✔人数(SUM)', dataIndex: 'ConfirmPersonNum', width: '5em' }, - { key: 'confirmTourdays', title: '✔团天数(AVG)', dataIndex: 'confirmTourdays', width: '5em' }, - { key: 'SumML', title: '预计毛利', dataIndex: 'SumML', width: '5em' }, // SumML_txt - { key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' }, - { key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em' }, + { 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') }, + { key: 'confirmTourdays', title: '✔团天数(AVG)', dataIndex: 'confirmTourdays', width: '5em', render: (v, r) => renderVS(v, r, 'confirmTourdays') }, + { 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 = pageData.rawData.filter(ele => ele.operatorName === r.operatorName); + const rowChildren = ifmerge ? pageData.rawData.filter(ele => ele.operatorName.includes(r.operatorName)) : pageData.rawData.filter(ele => ele.operatorName === r.operatorName); return r.hasOld ? : null; } }, ]; @@ -101,12 +136,12 @@ const SalesCustomerCareRegular = (props) => { fieldProps: { DepartmentList: { show_all: false, mode: 'multiple' }, WebCode: { show_all: false, mode: 'multiple' }, - dates: { hide_vs: true }, + dates: { hide_vs: false }, }, }} onSubmit={(_err, obj, form, str) => { customer_store.setSearchValues(obj, form, 'sales_regular_data'); - customer_store.get_sales_regular_data(obj); + customer_store.get_sales_regular_data_vs(obj); // MeetingDataStore.setSearchValues(form); // dataRefresh(obj); }} @@ -129,9 +164,9 @@ const SalesCustomerCareRegular = (props) => { - + - +