fear: CRM统计: 个人看板; 违规明细

main
Lei OT 2 months ago
parent ccbd0be1cd
commit a02fa37f17

@ -52,9 +52,9 @@ import SalesCustomerCareRegular from './views/SalesCustomerCareRegular';
import { stores_Context, APP_VERSION } from './config'; import { stores_Context, APP_VERSION } from './config';
import { WaterMark } from '@ant-design/pro-components'; import { WaterMark } from '@ant-design/pro-components';
import CooperationIcon from './components/icons/CooperationIcon'; import CooperationIcon from './components/icons/CooperationIcon';
import OPDashboard from './views/OPDashboard'; import OPDashboard from './views/sales-crm/Dashboard';
import OPProcess from './views/OPProcess'; import OPProcess from './views/sales-crm/Process';
import OPRisk from './views/OPRisk'; import OPRisk from './views/sales-crm/Risk';
import Cruise from './views/Cruise'; import Cruise from './views/Cruise';
import Hotel from './views/Hotel'; import Hotel from './views/Hotel';
@ -280,6 +280,7 @@ const App = () => {
<Route path="/op_dashboard" element={<OPDashboard />} /> <Route path="/op_dashboard" element={<OPDashboard />} />
<Route path="/op_process" element={<OPProcess />} /> <Route path="/op_process" element={<OPProcess />} />
<Route path="/op_risk" element={<OPRisk />} /> <Route path="/op_risk" element={<OPRisk />} />
<Route path="/op_risk/sales/:opisn" element={<OPRisk />} />
</Route> </Route>
</Routes> </Routes>
</Content> </Content>

@ -1,7 +1,7 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx'; import { makeAutoObservable, runInAction } from 'mobx';
import { fetchJSON } from '../utils/request'; import { fetchJSON } from '../utils/request';
import { isEmpty, sortDescBy, objectMapper, groupBy, pick, unique, cloneDeep } from '../utils/commons'; import { isEmpty, sortDescBy, groupBy, pick, unique } from '../utils/commons';
import { groupsMappedByCode, dataFieldAlias } from './../libs/ht'; import { groupsMappedByCode } from './../libs/ht';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config'; import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config';
import moment from 'moment'; import moment from 'moment';
@ -33,6 +33,20 @@ const fetchProcessData = async (param) => {
return json.errcode === 0 ? json.result : []; return json.errcode === 0 ? json.result : [];
}; };
const fetchRiskDetailData = async (param) => {
const defaultParam = {
opisn: -1,
DateType: '',
WebCode: 'All',
DepartmentList: '',
Date1: '',
Date2: '',
IncludeTickets: '1',
};
const json = await fetchJSON('/service-Analyse2/sales_crm_process_detail', {...defaultParam, ...param});
return json.errcode === 0 ? json.result : [];
};
class SalesCRMData { class SalesCRMData {
constructor(appStore) { constructor(appStore) {
this.appStore = appStore; this.appStore = appStore;
@ -42,7 +56,18 @@ class SalesCRMData {
async get90n180Data(param = {}) { async get90n180Data(param = {}) {
const retProps = param?.retLabel || ''; const retProps = param?.retLabel || '';
const retKey = param.groupDateType === '' ? (param.groupType === 'overview' ? 'dataSource' : 'details') : 'byDate'; let retKey = param.groupDateType === '' ? (param.groupType === 'overview' ? 'dataSource' : 'details') : 'byDate';
retKey = param.opisn ? `operator_${param.opisn}`: retKey;
if (param.opisn ) {
if (!isEmpty(this.results.details)) {
const _this_opi_row = this.results.details.filter(ele => ele.groupsKey === param.opisn);
this.results[retKey] = _this_opi_row;
return;
}
if (!isEmpty(this.results[retKey])) {
return;
}
}
this.results.loading = true; this.results.loading = true;
const date90=this.searchValues.date90; const date90=this.searchValues.date90;
const date180=this.searchValues.date180; const date180=this.searchValues.date180;
@ -62,17 +87,21 @@ class SalesCRMData {
}; };
}); });
// console.log(result2, '+++++ +++', retKey); // console.log(result2, '+++++ +++', retKey);
// console.log(this.results[retKey].length); // console.log(this.results[retKey]?.length);
runInAction(() => { runInAction(() => {
this.results.loading = false; this.results.loading = false;
this.results[retKey] = [].concat(this.results[retKey], result2); this.results[retKey] = [].concat((this.results[retKey] || []), result2);
}); });
return this.results; return this.results;
} }
async getResultData(param = {}) { async getResultData(param = {}) {
const retKey = param.groupDateType === '' ? 'byOperator' : 'byDate'; let retKey = param.groupDateType === '' ? 'byOperator' : 'byDate';
retKey = param.opisn ? `operator_byDate_${param.opisn}`: retKey;
if (!isEmpty(this.results[retKey])) {
return;
}
this.results.loading = true; this.results.loading = true;
this.results[retKey] = []; this.results[retKey] = [];
const res = await fetchResultsData({ ...this.searchValuesToSub, ...param }); const res = await fetchResultsData({ ...this.searchValuesToSub, ...param });
@ -85,19 +114,42 @@ class SalesCRMData {
async getProcessData(param = {}) { async getProcessData(param = {}) {
// const retKey = param.groupDateType === '' ? 'byOperator' : 'byDate'; // const retKey = param.groupDateType === '' ? 'byOperator' : 'byDate';
const retKey = param.groupDateType === '' ? (param.groupType !== 'operator' ? 'dataSource' : 'details') : 'byDate'; let retKey = param.groupDateType === '' ? (param.groupType !== 'operator' ? 'dataSource' : 'details') : 'byDate';
retKey = param.opisn ? `operator_${param.opisn}`: retKey;
if (param.opisn) {
if (!isEmpty(this.process.details)) {
const _this_opi_row = this.process.details.filter(ele => ele.groupsKey === param.opisn);
this.process[retKey] = _this_opi_row;
return;
}
if (!isEmpty(this.process[retKey])) {
return;
}
}
this.process.loading = true; this.process.loading = true;
this.process[retKey] = []; this.process[retKey] = [];
const res = await fetchProcessData({ ...this.searchValuesToSub, ...param }); const res = await fetchProcessData({ ...this.searchValuesToSub, ...param });
runInAction(() => { runInAction(() => {
this.process.loading = false; this.process.loading = false;
this.process[retKey] = [].concat(this.process[retKey], res); this.process[retKey] = [].concat(this.process[retKey], res);
// this.process[retKey] =retKey === 'byOperator' ? res.filter(ele => ele.) });
}
async getRiskDetailData(param = {}) {
this.risk.loading = true;
this.risk.dataSource = [];
const res = await fetchRiskDetailData({ ...this.searchValuesToSub, ...param });
runInAction(() => {
this.risk.loading = false;
this.risk.dataSource = res;
this.risk.byLostType = groupBy(res, 'lost_type');
}); });
} }
searchValues = { searchValues = {
date: moment(), date: moment(),
Date1: moment().startOf("week").subtract(7, "days"),
Date2: moment().endOf("week").subtract(7, "days"),
DateType: { key: 'applyDate', label: '提交日期'}, DateType: { key: 'applyDate', label: '提交日期'},
WebCode: { key: 'All', label: '所有来源' }, WebCode: { key: 'All', label: '所有来源' },
// IncludeTickets: { key: '1', label: '含门票'}, // IncludeTickets: { key: '1', label: '含门票'},
@ -114,7 +166,16 @@ class SalesCRMData {
} }
}; };
searchValuesToSub = {}; searchValuesToSub = {
date: moment().format(DATE_FORMAT),
Date1: moment().startOf("week").subtract(7, "days").format(DATE_FORMAT),
Date2: moment().endOf("week").subtract(7, "days").format(SMALL_DATETIME_FORMAT),
DateType: 'applyDate',
DepartmentList: groupsMappedByCode.GH.value,
WebCode: 'All',
operator: '-1',
opisn: '-1',
};
setSearchValues(obj, values) { setSearchValues(obj, values) {
this.searchValues = { ...this.searchValues, ...values }; this.searchValues = { ...this.searchValues, ...values };
@ -128,21 +189,20 @@ class SalesCRMData {
Date2: (values.date.clone()).subtract(50, 'days').format(SMALL_DATETIME_FORMAT), Date2: (values.date.clone()).subtract(50, 'days').format(SMALL_DATETIME_FORMAT),
}; };
} }
this.searchValuesToSub = obj; this.searchValuesToSub = {...this.searchValuesToSub, ...obj};
} }
results = { loading: false, dataSource: [], details: [], byDate: [], byOperator: [] }; results = { loading: false, dataSource: [], details: [], byDate: [], byOperator: [] };
process = { loading: false, dataSource: [], details: [], byDate: [], byOperator: [] }; process = { loading: false, dataSource: [], details: [], byDate: [], byOperator: [] };
resetData = () => { risk = { loading: false, dataSource: [], byLostType: {}, };
this.results.loading = false; resetData = (rootKey = '') => {
for (const key of Object.keys(this.results)) { if (rootKey === '') {
if (key !== 'loading') { return false;
this.results[key] = [];
}
} }
for (const key of Object.keys(this.process)) { this[rootKey].loading = false;
for (const key of Object.keys(this[rootKey])) {
if (key !== 'loading') { if (key !== 'loading') {
this.process[key] = []; this[rootKey][key] = [];
} }
} }
}; };

@ -1,69 +0,0 @@
import React, { useContext, useState } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import moment from 'moment';
import { SlackOutlined, SketchOutlined, AntCloudOutlined, RedditOutlined, GithubOutlined } from '@ant-design/icons';
import { Row, Col, Table, Select, Space, Typography, Progress, Spin, Divider, Button, Switch } from 'antd';
import SearchForm from './../components/search/SearchForm';
export default observer((props) => {
const { sale_store, date_picker_store: searchFormStore } = useContext(stores_Context);
const { formValues, siderBroken } = searchFormStore;
const riskTableProps = {
loading: false,
// sticky: true,
// scroll: { x: 1000, y: 400 },
pagination: false,
columns: [
{ title: '客人姓名', dataIndex: 'WebCode', key: 'WebCode' },
{ title: '团号', dataIndex: 'o_id', key: 'o_id' },
{ title: '表单内容', dataIndex: 'COLI_LineClass', key: 'COLI_LineClass' },
{ title: '顾问', dataIndex: 'SourceType', key: 'SourceType' },
{ title: '预定时间', dataIndex: 'applyDate', key: 'applyDate' },
],
};
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col md={24} lg={24} xxl={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...sale_store.searchValues,
},
shows: ['DepartmentList', 'WebCode', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple', col: 5 },
WebCode: { show_all: false, mode: 'multiple', col: 5 },
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
sale_store.setSearchValues(obj, form);
// pageRefresh(obj);
}}
/>
</Col>
</Row>
<section>
<h3>&gt;24H回复</h3>
<Table {...riskTableProps} bordered />
</section>
<section>
<h3>首次报价周期&gt;48h</h3>
<Table {...riskTableProps} bordered />
</section>
<section>
<h3>报价次数&lt;1</h3>
<Table {...riskTableProps} bordered />
</section>
<section>
<h3>报价次数&lt;2</h3>
<Table {...riskTableProps} bordered />
</section>
</>
);
});

@ -1,14 +1,14 @@
import React, { useContext, useState, useEffect } from 'react'; import React, { useContext, useState, useEffect } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { stores_Context, DATE_FORMAT, SMALL_DATETIME_FORMAT } from '../config'; import { Link } from 'react-router-dom';
import { stores_Context, DATE_FORMAT, SMALL_DATETIME_FORMAT } from '../../config';
import moment from 'moment'; import moment from 'moment';
import { SlackOutlined, SketchOutlined, AntCloudOutlined, RedditOutlined, GithubOutlined } from '@ant-design/icons'; import { Row, Col, Table, Select, Spin, Tag } from 'antd';
import { Row, Col, Table, Select, Space, Typography, Progress, Spin, Divider, Button, Switch, Tag } from 'antd'; import SearchForm from '../../components/search/SearchForm';
import SearchForm from './../components/search/SearchForm'; import MixFieldsDetail from '../../components/MixFieldsDetail';
import MixFieldsDetail from '../components/MixFieldsDetail'; import { fixTo2Decimals, isEmpty, pick } from '../../utils/commons';
import { cloneDeep, fixTo2Decimals, sortBy, isEmpty, pick } from '../utils/commons'; import Column from '../../components/Column';
import Column from '../components/Column'; import { groupsMappedByKey } from '../../libs/ht';
import { groupsMappedByKey } from './../libs/ht';
const COLOR_SETS = [ const COLOR_SETS = [
"#FFFFFF", "#FFFFFF",
@ -38,7 +38,7 @@ export default observer((props) => {
// console.log(operatorObjects); // console.log(operatorObjects);
const pageRefresh = async (obj) => { const pageRefresh = async (obj) => {
resetData(); resetData('results');
const deptList = obj.DepartmentList.split(','); const deptList = obj.DepartmentList.split(',');
const includeCH = ['1', '2', '7'].some(ele => deptList.includes(ele)); const includeCH = ['1', '2', '7'].some(ele => deptList.includes(ele));
const includeAH = ['28'].every(ele => deptList.includes(ele)); const includeAH = ['28'].every(ele => deptList.includes(ele));
@ -87,8 +87,8 @@ export default observer((props) => {
// console.log('formValuesToSub --- getFullYearDiagramData', formValuesToSub.DepartmentList); // console.log('formValuesToSub --- getFullYearDiagramData', formValuesToSub.DepartmentList);
await SalesCRMDataStore.getResultData({ await SalesCRMDataStore.getResultData({
..._formValuesToSub, ..._formValuesToSub,
Date1: moment().startOf('year').format(DATE_FORMAT), Date1: moment(obj.date).startOf('year').format(DATE_FORMAT),
Date2: moment().endOf('year').format(SMALL_DATETIME_FORMAT), Date2: moment(obj.date).endOf('year').format(SMALL_DATETIME_FORMAT),
groupType: 'overview', groupType: 'overview',
// groupType: 'operator', // groupType: 'operator',
groupDateType: 'month', groupDateType: 'month',
@ -199,6 +199,7 @@ export default observer((props) => {
filterSearch: true, filterSearch: true,
filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)), filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)),
onFilter: (value, record) => record.groupsKey === value || record.groupType === 'overview', onFilter: (value, record) => record.groupsKey === value || record.groupType === 'overview',
render: (text, record) => (record.groupType !== 'operator' ? text : <Link to={`/op_risk/sales/${record.groupsKey}`}>{text}</Link>),
}, },
{ {
title: () => (<>前90 -30<br/>{searchValues.date90.Date1} {searchValues.date90.Date2}</>), title: () => (<>前90 -30<br/>{searchValues.date90.Date1} {searchValues.date90.Date2}</>),

@ -1,10 +1,11 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Link } from 'react-router-dom';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { stores_Context } from '../config'; import { stores_Context } from '../../config';
import { InfoCircleOutlined } from '@ant-design/icons'; import { InfoCircleOutlined } from '@ant-design/icons';
import { Row, Col, Table, Tooltip } from 'antd'; import { Row, Col, Table, Tooltip } from 'antd';
import SearchForm from '../components/search/SearchForm'; import SearchForm from '../../components/search/SearchForm';
import { pick } from '../utils/commons'; import { pick } from '../../utils/commons';
const COLOR_SETS = [ const COLOR_SETS = [
"#FFFFFF", "#FFFFFF",
@ -29,7 +30,7 @@ export default observer((props) => {
const operatorObjects = process.details.map((v) => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel, text: v.groupsLabel })); const operatorObjects = process.details.map((v) => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel, text: v.groupsLabel }));
const pageRefresh = async (obj) => { const pageRefresh = async (obj) => {
resetData(); resetData('process');
Promise.allSettled([ Promise.allSettled([
SalesCRMDataStore.getProcessData({ SalesCRMDataStore.getProcessData({
...(obj || _formValuesToSub), ...(obj || _formValuesToSub),
@ -63,6 +64,7 @@ export default observer((props) => {
filterSearch: true, filterSearch: true,
filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)), filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)),
onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator', onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator',
render: (text, record) => (record.groupType !== 'operator' ? text : <Link to={`/op_risk/sales/${record.groupsKey}`}>{text}</Link>),
}, // : }, // :
{ {
title: '顾问动作', title: '顾问动作',
@ -203,8 +205,7 @@ export default observer((props) => {
filterSearch: true, filterSearch: true,
filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)), filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)),
onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator', onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator',
// render: (text, record) => { // todo: render: (text, record) => (record.groupType !== 'operator' ? text : <Link to={`/op_risk/sales/${record.groupsKey}`}>{text}</Link>),
// },
}, // : }, // :
{ title: '>24H回复', dataIndex: 'lostTouch24', key: 'lostTouch24', { title: '>24H回复', dataIndex: 'lostTouch24', key: 'lostTouch24',
sorter: (a, b) => tableSorter(a, b, 'lostTouch24'), sorter: (a, b) => tableSorter(a, b, 'lostTouch24'),

@ -0,0 +1,397 @@
import React, { useContext, useEffect } from 'react';
import { NavLink, useParams } from 'react-router-dom';
import { observer } from 'mobx-react';
import { stores_Context, DATE_FORMAT, SMALL_DATETIME_FORMAT } from '../../config';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Row, Col, Table, Divider, Button, Popover, Tooltip } from 'antd';
import { fixTo2Decimals } from '../../utils/commons';
import MixFieldsDetail from '../../components/MixFieldsDetail';
const COLOR_SETS = [
'#FFFFFF',
// "#5B8FF9",
// "#FF6B3B",
'#9FB40F',
'#76523B',
'#DAD5B5',
'#E19348',
'#F383A2',
];
const transparentHex = '1A';
const percentageRender = (val) => (val ? `${val}%` : '');
const numberConvert10K = (number, scale = 10) => {
return fixTo2Decimals((number/(1000*scale)));
};
const retPropsMinRatesSet = { 'CH': 12, 'AH': 8, 'default': 10 }; //
export default observer((props) => {
const { opisn, opi_name } = useParams();
const { sale_store, SalesCRMDataStore, date_picker_store: searchFormStore } = useContext(stores_Context);
const { formValues, siderBroken } = searchFormStore;
const { searchValues, searchValuesToSub, resetData, results, process, risk } = SalesCRMDataStore;
useEffect(() => {
Promise.allSettled([
//
SalesCRMDataStore.get90n180Data({ opisn, groupType: 'operator', groupDateType: '' }),
// :
SalesCRMDataStore.getResultData({
opisn,
Date1: searchValues.date.clone().startOf('year').format(DATE_FORMAT),
Date2: searchValues.date.clone().endOf('year').format(SMALL_DATETIME_FORMAT),
groupType: 'overview',
// groupType: 'operator',
groupDateType: 'month',
}),
//
SalesCRMDataStore.getProcessData({ opisn, groupType: 'operator', groupDateType: '' }),
//
SalesCRMDataStore.getRiskDetailData({ opisn }),
]);
}, [opisn]);
const dataFields = (suffix, colRootIndex) => [
{
key: 'ConfirmRates' + suffix,
title: '成行率',
dataIndex: [suffix, 'ConfirmRates'],
width: '5em',
// CH<12%, AH<8%
render: (val, r) => ({
props: { style: { color: val < (retPropsMinRatesSet?.[r?.retProps || 'default'] || retPropsMinRatesSet.default) ? 'red' : 'green', backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: val ? `${val}%` : '',
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ConfirmRates - b[suffix].ConfirmRates),
},
{
key: 'SumML' + suffix,
title: '业绩/万',
dataIndex: [suffix, 'SumML'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: numberConvert10K(_),
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].SumML - b[suffix].SumML),
},
{
key: 'ConfirmOrder' + suffix,
title: '团数',
dataIndex: [suffix, 'ConfirmOrder'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: _,
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ConfirmOrder - b[suffix].ConfirmOrder),
},
{
key: 'SumOrder' + suffix,
title: '订单数',
dataIndex: [suffix, 'SumOrder'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: _,
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].SumOrder - b[suffix].SumOrder),
},
{
key: 'ResumeOrder' + suffix,
title: '老客户团数',
dataIndex: [suffix, 'ResumeOrder'],
width: '5em',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: _,
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ResumeOrder - b[suffix].ResumeOrder),
},
{
key: 'ResumeRates' + suffix,
title: '老客户成行率',
dataIndex: [suffix, 'ResumeRates'],
width: '5em',
render: (val, r) => ({
props: { style: { backgroundColor: COLOR_SETS[colRootIndex]+transparentHex } },
children: val ? `${val}%` : ''
}),
sorter: (a, b) => (a.groupType === 'overview' ? -1 : b.groupType === 'overview' ? 0 : a[suffix].ResumeRates - b[suffix].ResumeRates),
},
];
const dashboardTableProps = {
rowKey: 'groupsKey',
pagination: false,
size: 'small',
showSorterTooltip: false,
columns: [
{
title: '',
dataIndex: 'groupsLabel',
key: 'name',
width: '5em',
},
{
title: () => (<>前90 -30<br/>{searchValues.date90.Date1} {searchValues.date90.Date2}</>),
key: 'date',
children: dataFields('result90', 0),
},
{
title: () => (<>前180 -50<br/>{searchValues.date180.Date1} {searchValues.date180.Date2}</>),
key: 'department',
children: dataFields('result180', 1),
},
],
rowClassName: (record, rowIndex) => {
return record.groupType === 'overview' ? 'ant-tag-blue' : '';
},
};
const activityTableProps = {
rowKey: 'groupsKey',
pagination: false,
size: 'small',
rowClassName: (record, rowIndex) => {
return record.groupType === 'operator' ? '' : 'ant-tag-blue';
},
showSorterTooltip: false,
columns: [
{ title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem' }, // :
{
title: '顾问动作',
key: 'date',
children: [
{
title: () => (
<>
首次响应率24H{' '}
<Tooltip title="达到24H内回复的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'firstTouch24',
key: 'firstTouch24',
render: percentageRender,
},
{
title: () => (
<>
48H内报价率{' '}
<Tooltip title="达到48H内报价的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'firstQuote48',
key: 'firstQuote48',
render: percentageRender,
},
{
title: () => (
<>
一次报价率{' '}
<Tooltip title="首次报价的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'quote1',
key: 'quote1',
render: percentageRender,
},
{
title: () => (
<>
二次报价率{' '}
<Tooltip title="二次报价的订单数/一次报价后回复数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'quote2',
key: 'quote2',
render: percentageRender,
},
{
title: () => (
<>
&gt;50条会话{' '}
<Tooltip title=">50条会话的订单数/总订单">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'turnsGT50',
key: 'turnsGT50',
render: percentageRender,
},
{
title: () => (
<>
违规数{' '}
<Tooltip title="未遵循24H回复的订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'violations',
key: 'violations',
},
],
},
{
title: '客人回复',
key: 'department',
children: [
{
title: () => (
<>
首次回复率24H{' '}
<Tooltip title="达到24H内回复的订单数/总订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'firstReply24',
key: 'firstReply24',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
children: percentageRender(_),
}),
},
{
title: () => (
<>
48H内报价回复率{' '}
<Tooltip title="48H内报价后的回复数/报价的订单数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'replyQuote48',
key: 'replyQuote48',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
children: percentageRender(_),
}),
},
{
title: () => (
<>
一次报价回复率{' '}
<Tooltip title="一次报价后回复率/订单总数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'replyQuote1',
key: 'replyQuote1',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
children: percentageRender(_),
}),
},
{
title: () => (
<>
二次报价回复率{' '}
<Tooltip title="二次报价后回复数/一次报价后回复数">
<InfoCircleOutlined />
</Tooltip>
</>
),
dataIndex: 'replyQuote2',
key: 'replyQuote2',
render: (_, r) => ({
props: { style: { backgroundColor: COLOR_SETS[1] + transparentHex } },
children: percentageRender(_),
}),
},
],
},
],
};
const riskTableProps = {
loading: risk.loading,
sticky: true,
// scroll: { x: 1000, y: 400 },
pagination: false,
rowKey: (row) => row.coli_id,
columns: [
{ title: '客人姓名', dataIndex: 'guest_name', key: 'guest_name', width: '6rem' },
{ title: '团号', dataIndex: 'coli_id', key: 'coli_id', width: '6rem' },
{
title: '表单内容',
dataIndex: 'coli_contents',
key: 'coli_contents',
width: '6rem',
render: (text, record) => (
<Popover
title={`${record.coli_id} ${record.guest_name}`}
content={<pre dangerouslySetInnerHTML={{ __html: text }} style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word' }} />}
trigger={['click']}
placement="right"
overlayStyle={{ width: '500px', maxHeight: '500px' }}
autoAdjustOverflow={false}
>
<Button type="link" size="small">
表单内容
</Button>
</Popover>
),
},
{ title: '顾问', dataIndex: 'opi_name', key: 'opi_name', width: '6rem' },
{ title: '预定时间', dataIndex: 'coli_applydate', key: 'coli_applydate', width: '6rem' },
],
};
const chartsConfig = {
colFields: ['ConfirmOrder', 'SumOrder'],
lineFields: ['SumML', 'ConfirmRates'],
seriesField: null,
xField: 'groupDateVal',
};
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col md={24} lg={12} xxl={14}>
<NavLink to={-1}>返回</NavLink>
</Col>
</Row>
<section>
<h2>结果指标 @ {searchValues.date.format(DATE_FORMAT)}</h2>
<Table {...dashboardTableProps} bordered dataSource={results[`operator_${opisn}`]} loading={results.loading} sticky />
</section>
<section>
<h3>全年每月业绩 @ {searchValues.date.format('YYYY')}</h3>
<MixFieldsDetail {...chartsConfig} dataSource={results[`operator_byDate_${opisn}`] || []} />
</section>
<Divider />
<section>
<h2>过程指标 @ {searchValuesToSub.Date1} {searchValuesToSub.Date2}</h2>
<Table {...activityTableProps} bordered dataSource={process[`operator_${opisn}`]} loading={process.loading} sticky />
</section>
<section>
<h2>违规明细</h2>
<h3>&gt;24H回复</h3>
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostTouch24 || []} />
</section>
<section>
<h3>首次报价周期&gt;48h</h3>
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote48 || []} />
</section>
<section>
<h3>报价次数&lt;1</h3>
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote1 || []} />
</section>
<section>
<h3>报价次数&lt;2</h3>
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote2 || []} />
</section>
</>
);
});
Loading…
Cancel
Save