Compare commits
101 Commits
feature/ho
...
main
Author | SHA1 | Date |
---|---|---|
|
0ddd1e2824 | 1 day ago |
|
c0c45dd955 | 1 day ago |
|
c1003334e4 | 6 days ago |
|
b3b406e379 | 6 days ago |
|
1c0ae97122 | 1 week ago |
|
137cf6d5ec | 1 week ago |
|
e3aa9c4299 | 2 weeks ago |
|
685c4373ef | 2 weeks ago |
|
8d4200c8d3 | 2 weeks ago |
|
d978f1e280 | 2 weeks ago |
|
12022a455c | 2 weeks ago |
|
cb7105648b | 2 weeks ago |
|
0ad5252f46 | 3 weeks ago |
|
ca9aab3e3a | 3 weeks ago |
|
cb08973db4 | 3 weeks ago |
|
d232fd5f7d | 3 weeks ago |
|
51ce3b11c2 | 3 weeks ago |
|
b35bf72128 | 1 month ago |
|
00c1eed1ba | 1 month ago |
|
f633c9b1eb | 2 months ago |
|
71f5207940 | 2 months ago |
|
36a14acf59 | 2 months ago |
|
c510545651 | 2 months ago |
|
0706df5d93 | 2 months ago |
|
9f6915286a | 2 months ago |
|
fbd3f49bb6 | 2 months ago |
|
47ead2316e | 3 months ago |
|
021fb429ce | 3 months ago |
|
a9f45e334d | 4 months ago |
|
488b33ae50 | 4 months ago |
|
6dbf9cfb56 | 4 months ago |
|
108becd2cd | 4 months ago |
|
73e1dcdb4c | 4 months ago |
|
baae505831 | 4 months ago |
|
16b08311fa | 4 months ago |
|
e0e7c3c84f | 5 months ago |
|
87f24c40fa | 5 months ago |
|
bed6b81753 | 5 months ago |
|
171dd7514a | 5 months ago |
|
a0ea6507a3 | 5 months ago |
|
40cd81f9a7 | 5 months ago |
|
876216458e | 5 months ago |
|
8a0f919a42 | 5 months ago |
|
020c9fc3e9 | 5 months ago |
|
b2e539a4d2 | 5 months ago |
|
fee1185406 | 5 months ago |
|
e297d36256 | 5 months ago |
|
667574ec34 | 5 months ago |
|
185db1ec80 | 5 months ago |
|
6dc82a0a01 | 5 months ago |
|
4724b13a2d | 5 months ago |
|
f532f138ff | 5 months ago |
|
3b51ae8d08 | 5 months ago |
|
a6f7884e6e | 5 months ago |
|
928e27913d | 5 months ago |
|
94363df0fc | 5 months ago |
|
621d3a2446 | 5 months ago |
|
8f12fc8747 | 5 months ago |
|
397c886aea | 5 months ago |
|
459db55453 | 5 months ago |
|
6317c24a75 | 5 months ago |
|
c9e13385dc | 5 months ago |
|
a02fa37f17 | 5 months ago |
|
ccbd0be1cd | 5 months ago |
|
c7d3cf5746 | 5 months ago |
|
23b1d2c81b | 5 months ago |
|
7c08e8bfe6 | 5 months ago |
|
1e46cf6e63 | 5 months ago |
|
17cedf14d9 | 5 months ago |
|
2fae92fa2a | 5 months ago |
|
08aa01e33c | 5 months ago |
|
17a85122b1 | 5 months ago |
|
1b86ab1192 | 5 months ago |
|
be31c3983d | 6 months ago |
|
5551b2e362 | 6 months ago |
|
7cfd79a0d2 | 6 months ago |
|
dcb185be5a | 6 months ago |
|
c3547538fd | 6 months ago |
|
cb598ecba1 | 6 months ago |
|
a32943fffb | 6 months ago |
|
a7b24d206d | 6 months ago |
|
f8eef38944 | 6 months ago |
|
89d151f388 | 6 months ago |
|
6ea43a58e2 | 6 months ago |
|
12e99bb816 | 6 months ago |
|
d29dab5824 | 6 months ago |
|
f1e0c1200b | 6 months ago |
|
6f640bac4d | 6 months ago |
|
e822b9bb3a | 6 months ago |
|
ac11babdab | 6 months ago |
|
04431635b5 | 7 months ago |
|
80d4050527 | 7 months ago |
|
6d6a7d98ee | 7 months ago |
|
b583bfcd2f | 7 months ago |
|
539f57e16d | 7 months ago |
|
e5ff51ae63 | 7 months ago |
|
cc3d0c19ad | 7 months ago |
|
00e22a6f08 | 7 months ago |
|
02c09c9821 | 7 months ago |
|
bd67f6ca4e | 7 months ago |
|
04d1cc2b39 | 7 months ago |
@ -0,0 +1,240 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Row, Col, Spin } from 'antd';
|
||||
import { Line } from '@ant-design/plots';
|
||||
import { observer } from 'mobx-react';
|
||||
import { dataFieldAlias } from '../libs/ht';
|
||||
import DateGroupRadio from '../components/DateGroupRadio';
|
||||
import { cloneDeep, groupBy, sortBy } from '../utils/commons';
|
||||
|
||||
export default observer((props) => {
|
||||
const { dataSource: rawData, showAVG, showCompareSum, loading, solidLineTime,
|
||||
solidLineDash, isCompareLine,solidLineCompareTime, ...config } = props;
|
||||
const { xField, yField, yFieldAlias, seriesField } = config;
|
||||
|
||||
const [dataBeforeXChange, setDataBeforeXChange] = useState([]);
|
||||
|
||||
const [dataSource, setDataSource] = useState([]);
|
||||
const [sumSeries, setSumSeries] = useState([]);
|
||||
|
||||
const line_config = {
|
||||
// data: dataSource,
|
||||
padding: 'auto',
|
||||
xField,
|
||||
yField,
|
||||
seriesField,
|
||||
// seriesField: 'rowLabel',
|
||||
// xAxis: {
|
||||
// type: 'timeCat',
|
||||
// },
|
||||
color: isCompareLine?['#17f485', '#1890ff','#17f485', '#1890ff',"#181414","#181414"]:undefined,
|
||||
point: {
|
||||
size: 4,
|
||||
shape: "cicle",
|
||||
},
|
||||
lineStyle: (datum) => {
|
||||
return {
|
||||
stroke: isCompareLine?datum._ylabel.includes("总计")?"#181414":datum._ylabel.includes(solidLineDash) ? '#1890ff':'#17f485' : undefined, // 设置颜色
|
||||
lineDash: isCompareLine?datum._ylabel.includes(solidLineTime) ? undefined:[4, 4] : undefined, // 设置虚线
|
||||
};
|
||||
},
|
||||
yAxis: {
|
||||
min: 0,
|
||||
maxTickInterval: 5,
|
||||
},
|
||||
meta: {
|
||||
...cloneDeep(dataFieldAlias),
|
||||
},
|
||||
// smooth: true,
|
||||
label: {}, // 显示标签
|
||||
legend: {
|
||||
position: 'right-top',
|
||||
// title: {
|
||||
// text: '总合计 ' + dataSource.reduce((a, b) => a + b.SumOrder, 0),
|
||||
// },
|
||||
itemMarginBottom: 12, // 垂直间距
|
||||
},
|
||||
tooltip: {
|
||||
customItems: (originalItems) => {
|
||||
return originalItems
|
||||
.map((ele) => ({ ...ele, valueR: ele.data[yField] }))
|
||||
.sort(sortBy('valueR'))
|
||||
.reverse();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const [lineConfig, setLineConfig] = useState(cloneDeep(line_config));
|
||||
|
||||
useEffect(() => {
|
||||
resetX();
|
||||
|
||||
return () => {};
|
||||
}, [rawData]);
|
||||
|
||||
useEffect(() => {
|
||||
setLineConfig(cloneDeep(line_config));
|
||||
|
||||
return () => {};
|
||||
}, [isCompareLine,solidLineTime]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lineChartX === 'day') {
|
||||
setDataBeforeXChange(dataSource);
|
||||
}
|
||||
|
||||
return () => {};
|
||||
}, [dataSource]);
|
||||
|
||||
// 日月年切换
|
||||
const [lineChartX, setLineChartX] = useState('day');
|
||||
const orderCountDataMapper = { data1: 'data1', data2: undefined };
|
||||
const orderCountDataFieldMapper = { 'dateKey': xField, 'valueKey': yField, 'seriesKey': seriesField, _f: 'sum' };
|
||||
const resetX = () => {
|
||||
setLineChartX('day');
|
||||
setDataSource(rawData);
|
||||
setDataBeforeXChange(rawData);
|
||||
// 初始化`平均`线, `总计`线
|
||||
const byDays = groupBy(rawData, xField);
|
||||
const sumY = rawData.reduce((a, b) => a + b[yField], 0);
|
||||
// const avgVal = Math.round(sumY / (Object.keys(byDays).length));
|
||||
// const avgLine = [
|
||||
// { type: 'text', position: ['start', avgVal], content: avgVal, offsetX: -15, style: { fill: '#F4664A', textBaseline: 'bottom' } },
|
||||
// { type: 'line', start: [-10, avgVal], end: ['max', avgVal], style: { stroke: '#F4664A', lineDash: [2, 2] } },
|
||||
// ];
|
||||
// setLineConfig({ ...lineConfig, yField, xField, annotations: avgLine });
|
||||
setLineConfig({ ...lineConfig, yField, xField,});
|
||||
if (showCompareSum) {
|
||||
const _sumLine = Object.keys(byDays).reduce((r, _d) => {
|
||||
const summaryVal = byDays[_d].reduce((rows, row) =>
|
||||
{
|
||||
if (row[seriesField].includes(solidLineTime)){
|
||||
return rows + row[yField];
|
||||
}
|
||||
else{
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
, 0);
|
||||
const summaryCompareVal = byDays[_d].reduce((rows, row) =>
|
||||
{
|
||||
if (row[seriesField].includes(solidLineCompareTime)){
|
||||
return rows + row[yField];
|
||||
}
|
||||
else{
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
, 0);
|
||||
r.push({ ...byDays[_d][0], [yField]: summaryVal, [seriesField]: solidLineTime+'总计' });
|
||||
r.push({ ...byDays[_d][0], [yField]: summaryCompareVal, [seriesField]: solidLineCompareTime+'总计' });
|
||||
return r;
|
||||
}, []);
|
||||
setSumSeries(_sumLine);
|
||||
}
|
||||
else{
|
||||
const _sumLine = Object.keys(byDays).reduce((r, _d) => {
|
||||
const summaryVal = byDays[_d].reduce((rows, row) => rows + row[yField], 0);
|
||||
r.push({ ...byDays[_d][0], [yField]: summaryVal, [seriesField]: '总计' });
|
||||
return r;
|
||||
}, []);
|
||||
// console.log(_sumLine.map((ele) => ele[yField]));
|
||||
setSumSeries(_sumLine);
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeXDateFieldGroup = (value, data, avg1) => {
|
||||
// console.log(value, data, avg1);
|
||||
const _sumLine = [];
|
||||
const { xField, yField, seriesField } = lineConfig;
|
||||
const groupByDate = data.reduce((r, v) => {
|
||||
(r[v[xField]] || (r[v[xField]] = [])).push(v);
|
||||
return r;
|
||||
}, {});
|
||||
// console.log(groupByDate);
|
||||
const _data = Object.keys(groupByDate).reduce((r, _d) => {
|
||||
if (showCompareSum) {
|
||||
const summaryVal = groupByDate[_d].reduce((rows, row) =>
|
||||
{
|
||||
if (row[seriesField].includes(solidLineTime)){
|
||||
return rows + row[yField];
|
||||
}
|
||||
else{
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
, 0);
|
||||
const summaryCompareVal = groupByDate[_d].reduce((rows, row) =>
|
||||
{
|
||||
if (row[seriesField].includes(solidLineCompareTime)){
|
||||
return rows + row[yField];
|
||||
}
|
||||
else{
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
, 0);
|
||||
_sumLine.push({ ...groupByDate[_d][0], [yField]: summaryVal, [seriesField]: solidLineTime+'总计' });
|
||||
_sumLine.push({ ...groupByDate[_d][0], [yField]: summaryCompareVal, [seriesField]: solidLineCompareTime+'总计' });
|
||||
}
|
||||
else{
|
||||
const summaryVal = groupByDate[_d].reduce((rows, row) => rows + row[yField], 0);
|
||||
_sumLine.push({ ...groupByDate[_d][0], [yField]: summaryVal, [seriesField]: '总计' });
|
||||
}
|
||||
|
||||
|
||||
const xAxisGroup = groupByDate[_d].reduce((a, v) => {
|
||||
(a[v[seriesField]] || (a[v[seriesField]] = [])).push(v);
|
||||
return a;
|
||||
}, {});
|
||||
// console.log(xAxisGroup);
|
||||
Object.keys(xAxisGroup).map((_group) => {
|
||||
const summaryVal = xAxisGroup[_group].reduce((rows, row) => rows + row[yField], 0);
|
||||
r.push({ ...xAxisGroup[_group][0], [yField]: summaryVal, });
|
||||
return _group;
|
||||
});
|
||||
return r;
|
||||
}, []);
|
||||
// const _sum = Object.values(groupBy(_data, 'dateGroup')).reduce((ac, b) => ({...b, [yField]: 0}), {});
|
||||
// console.log(xField, avg1);
|
||||
// console.log('date source=====', _data);
|
||||
setLineChartX(value);
|
||||
setDataSource(_data);
|
||||
setSumSeries(_sumLine);
|
||||
|
||||
// setAvgLine1(avg1);
|
||||
// const avg1Int = Math.round(avg1);
|
||||
// const mergedConfig = { ...lineConfig,
|
||||
// annotations: [
|
||||
// { type: 'text', position: ['start', avg1Int], content: avg1Int, offsetX: -15, style: { fill: '#F4664A', textBaseline: 'bottom' } },
|
||||
// { type: 'line', start: [-10, avg1Int], end: ['max', avg1Int], style: { stroke: '#F4664A', lineDash: [2, 2] } },
|
||||
// ],
|
||||
// };
|
||||
// console.log(mergedConfig);
|
||||
// setLineConfig(mergedConfig);
|
||||
setLineConfig(cloneDeep(line_config));
|
||||
};
|
||||
return (
|
||||
<section>
|
||||
<Row gutter={16} justify={'space-between'} className="mb-1">
|
||||
<Col flex={'auto'}>
|
||||
<h3>
|
||||
走势: <span style={{ fontSize: 'smaller' }}>{dataFieldAlias[lineConfig.yField].label}</span>
|
||||
</h3>
|
||||
</Col>
|
||||
<Col style={{ textAlign: 'right' }} align={'end'}>
|
||||
<DateGroupRadio
|
||||
visible={true}
|
||||
dataRaw={{ data1: dataBeforeXChange }}
|
||||
onChange={onChangeXDateFieldGroup}
|
||||
value={lineChartX}
|
||||
dataMapper={orderCountDataMapper}
|
||||
fieldMapper={orderCountDataFieldMapper}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Spin spinning={loading}>
|
||||
<Line {...lineConfig} data={[].concat(dataSource, sumSeries)} />
|
||||
</Spin>
|
||||
</section>
|
||||
);
|
||||
});
|
@ -0,0 +1,147 @@
|
||||
import { useContext } from 'react';
|
||||
import { Row, Col, Typography, Space, Table, Divider } from 'antd';
|
||||
import { stores_Context } from '../config';
|
||||
import { observer } from 'mobx-react';
|
||||
import 'moment/locale/zh-cn';
|
||||
import SearchForm from '../components/search/SearchForm';
|
||||
import { TableExportBtn } from '../components/Data';
|
||||
import * as comm from '../utils/commons';
|
||||
|
||||
const HostCaseCount = () => {
|
||||
const { customer_store, date_picker_store } = useContext(stores_Context);
|
||||
const host_case_data = customer_store.host_case_data;
|
||||
|
||||
const columnsList = [
|
||||
{
|
||||
title: '团数',
|
||||
dataIndex: 'TotalGroupNum',
|
||||
key: 'TotalGroupNum',
|
||||
sorter: (a, b) => parseInt(a.TotalGroupNum) - parseInt(b.TotalGroupNum),
|
||||
},
|
||||
{
|
||||
title: '人数',
|
||||
dataIndex: 'TotalPersonNum',
|
||||
key: 'TotalPersonNum',
|
||||
sorter: (a, b) => parseInt(a.TotalPersonNum) - parseInt(b.TotalPersonNum),
|
||||
},
|
||||
{
|
||||
title: '计费团天数',
|
||||
dataIndex: 'TotalDays',
|
||||
key: 'TotalDays',
|
||||
sorter: (a, b) => parseInt(a.TotalDays) - parseInt(b.TotalDays),
|
||||
},
|
||||
{
|
||||
title: '交易额',
|
||||
dataIndex: 'TotalPrice',
|
||||
key: 'TotalPrice',
|
||||
sorter: (a, b) => parseInt(a.TotalPrice) - parseInt(b.TotalPrice),
|
||||
},
|
||||
];
|
||||
// 筛选函数
|
||||
const getFiltersData=(filterData,filterKey)=>{
|
||||
return comm.uniqWith(filterData.map(rr => ({ text: rr[filterKey], value: rr[filterKey] })),
|
||||
(a, b) => JSON.stringify(a) === JSON.stringify(b)).sort((a, b) => a.text.localeCompare(b.text));
|
||||
};
|
||||
// 小组筛选菜单项
|
||||
const allOPIGroup = getFiltersData(host_case_data.summaryData,"GroupBy");
|
||||
// 顾问筛选菜单项
|
||||
const allOPIConsultant = getFiltersData(host_case_data.counselorData,"GroupBy");
|
||||
// 明细表顾问名筛选菜单项
|
||||
const allOPIDetailConsultant = getFiltersData(host_case_data.singleDetailData,"OPI_Name");
|
||||
const summaryColumnsList = [{
|
||||
title: '',
|
||||
dataIndex: 'GroupBy',
|
||||
key: 'GroupBy',
|
||||
}, ...columnsList];
|
||||
const groupColumnsList = [{
|
||||
title: '组名',
|
||||
dataIndex: 'GroupBy',
|
||||
key: 'GroupBy',
|
||||
filters: allOPIGroup,
|
||||
onFilter: (value, record) => record.GroupBy === value,
|
||||
filterSearch: true,
|
||||
}, ...columnsList];
|
||||
const counselorColumnsList = [{
|
||||
title: '顾问名',
|
||||
dataIndex: 'GroupBy',
|
||||
key: 'GroupBy',
|
||||
filters: allOPIConsultant,
|
||||
onFilter: (value, record) => record.GroupBy === value,
|
||||
filterSearch: true,
|
||||
}, ...columnsList];
|
||||
const singleDetailColumnsList = [{
|
||||
title: '团名',
|
||||
dataIndex: 'GroupBy',
|
||||
key: 'GroupBy',
|
||||
},
|
||||
{
|
||||
title: '顾问名',
|
||||
dataIndex: 'OPI_Name',
|
||||
key: 'OPI_Name',
|
||||
filters: allOPIDetailConsultant,
|
||||
onFilter: (value, record) => record.OPI_Name === value,
|
||||
filterSearch: true,
|
||||
},
|
||||
...columnsList.slice(1)];
|
||||
|
||||
const renderRow=(rowColumns,rowDataSource,title)=>{
|
||||
return(
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Typography.Title level={3}>{title}</Typography.Title>
|
||||
<Divider orientation="right" plain>
|
||||
<TableExportBtn label={title} {...{ columns: rowColumns, dataSource: rowDataSource }} />
|
||||
</Divider>
|
||||
<Table
|
||||
sticky
|
||||
id={`${rowColumns}`}
|
||||
dataSource={rowDataSource}
|
||||
columns={rowColumns}
|
||||
size="small"
|
||||
rowKey={(record) => record.key}
|
||||
loading={host_case_data.loading}
|
||||
pagination={false}
|
||||
scroll={{ x: 1000 }}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Row gutter={16} className={date_picker_store.siderBroken ? "" : "sticky-top"} >
|
||||
<Col className="gutter-row" span={24}>
|
||||
<SearchForm
|
||||
defaultValue={{
|
||||
initialValue: {
|
||||
...date_picker_store.formValues,
|
||||
...host_case_data.searchValues,
|
||||
},
|
||||
shows: ['DepartmentList', 'dates'],
|
||||
fieldProps: {
|
||||
DepartmentList: { show_all: false, mode: 'multiple' },
|
||||
dates: { hide_vs: true },
|
||||
},
|
||||
}}
|
||||
onSubmit={(_err, obj, form) => {
|
||||
customer_store.setSearchValues(obj, form,"host_case_data");
|
||||
customer_store.getHostCaseData("1");
|
||||
customer_store.getHostCaseData("2");
|
||||
customer_store.getHostCaseData("3");
|
||||
customer_store.getHostCaseData("4");
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
{renderRow(summaryColumnsList,host_case_data.summaryData,'东道主项目汇总')}
|
||||
{renderRow(groupColumnsList,host_case_data.groupData,'东道主项目小组统计')}
|
||||
{renderRow(counselorColumnsList,host_case_data.counselorData,'东道主项目顾问统计')}
|
||||
{renderRow(singleDetailColumnsList,host_case_data.singleDetailData,'单团明细')}
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(HostCaseCount);
|
@ -1,85 +0,0 @@
|
||||
import React, { Children, 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 activityTableProps = {
|
||||
columns: [
|
||||
{ title: '', dataIndex: 'op', key: 'op' }, // 维度: 顾问
|
||||
{
|
||||
title: '顾问动作',
|
||||
key: 'date',
|
||||
children: [
|
||||
{ title: '首次响应率24H', dataIndex: 'action', key: 'action' },
|
||||
{ title: '48H内报价率', dataIndex: 'action', key: 'action' },
|
||||
{ title: '一次报价率', dataIndex: 'action', key: 'action' },
|
||||
{ title: '二次报价率', dataIndex: 'action', key: 'action' },
|
||||
{ title: '>50条会话', dataIndex: 'action', key: 'action' },
|
||||
{ title: '违规数', dataIndex: 'action', key: 'action' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '客人回复',
|
||||
key: 'department',
|
||||
children: [
|
||||
{ title: '首次回复率24H', dataIndex: 'action', key: 'action' },
|
||||
{ title: '48H内报价回复率', dataIndex: 'action', key: 'action' },
|
||||
{ title: '一次报价回复率', dataIndex: 'action', key: 'action' },
|
||||
{ title: '二次报价回复率', dataIndex: 'action', key: 'action' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const riskTableProps = {
|
||||
columns: [
|
||||
{ title: '', dataIndex: 'date', key: 'date' }, // 维度
|
||||
{ title: '>24H回复', dataIndex: 'action', key: 'action' },
|
||||
{ title: '首次报价周期>48h', dataIndex: 'action', key: 'action' },
|
||||
{ title: '报价次数<1次', dataIndex: 'action' },
|
||||
{ title: '报价次数<2次', dataIndex: 'action' },
|
||||
],
|
||||
};
|
||||
|
||||
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>
|
||||
<Table {...activityTableProps} bordered />
|
||||
</section>
|
||||
<section>
|
||||
<h3>未成行订单</h3>
|
||||
<Table {...riskTableProps} bordered />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
});
|
@ -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>>24H回复</h3>
|
||||
<Table {...riskTableProps} bordered />
|
||||
</section>
|
||||
<section>
|
||||
<h3>首次报价周期>48h</h3>
|
||||
<Table {...riskTableProps} bordered />
|
||||
</section>
|
||||
<section>
|
||||
<h3>报价次数<1</h3>
|
||||
<Table {...riskTableProps} bordered />
|
||||
</section>
|
||||
<section>
|
||||
<h3>报价次数<2</h3>
|
||||
<Table {...riskTableProps} bordered />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
});
|
@ -0,0 +1,259 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { observer } from 'mobx-react';
|
||||
import { stores_Context } from '../../config';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Row, Col, Table, Tooltip } from 'antd';
|
||||
import SearchForm from '../../components/search/SearchForm';
|
||||
import { pick } from '../../utils/commons';
|
||||
|
||||
const COLOR_SETS = [
|
||||
"#FFFFFF",
|
||||
// "#5B8FF9",
|
||||
// "#FF6B3B",
|
||||
"#9FB40F",
|
||||
"#76523B",
|
||||
"#DAD5B5",
|
||||
"#E19348",
|
||||
"#F383A2",
|
||||
];
|
||||
|
||||
const transparentHex = '1A';
|
||||
|
||||
export default observer((props) => {
|
||||
const { SalesCRMDataStore, date_picker_store: searchFormStore } = useContext(stores_Context);
|
||||
|
||||
// const { formValues, siderBroken } = searchFormStore;
|
||||
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
|
||||
const _formValuesToSub = pick(formValuesToSub, ['DepartmentList', 'WebCode']);
|
||||
const { resetData, process } = SalesCRMDataStore;
|
||||
const operatorObjects = process.details.map((v) => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel, text: v.groupsLabel }));
|
||||
|
||||
const pageRefresh = async (obj) => {
|
||||
resetData('process');
|
||||
Promise.allSettled([
|
||||
SalesCRMDataStore.getProcessData({
|
||||
...(obj || _formValuesToSub),
|
||||
// ...subParam,
|
||||
// groupType: 'overview',
|
||||
groupType: 'dept',
|
||||
groupDateType: '',
|
||||
}),
|
||||
SalesCRMDataStore.getProcessData({
|
||||
...(obj || _formValuesToSub),
|
||||
// ...subParam,
|
||||
groupType: 'operator',
|
||||
groupDateType: '',
|
||||
}),
|
||||
]);
|
||||
};
|
||||
|
||||
const tableSorter = (a, b, colName) => (a.groupType !== 'operator' ? -1 : b.groupType !== 'operator' ? 0 : a[colName] - b[colName]);
|
||||
const percentageRender = val => val ? `${val}%` : '';
|
||||
|
||||
const activityTableProps = {
|
||||
rowKey: 'groupsKey',
|
||||
pagination: { pageSize: 10, showSizeChanger: false },
|
||||
size: 'small',
|
||||
rowClassName: (record, rowIndex) => {
|
||||
return record.groupType === 'operator' ? '': 'ant-tag-blue';
|
||||
},
|
||||
showSorterTooltip: false,
|
||||
columns: [
|
||||
{ title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem',
|
||||
filterSearch: true,
|
||||
filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)),
|
||||
onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator',
|
||||
render: (text, record) => (record.groupType !== 'operator' ? text : <Link to={`/sales-crm/risk/sales/${record.groupsKey}`}>{text}</Link>),
|
||||
}, // 维度: 顾问
|
||||
{
|
||||
title: '顾问动作',
|
||||
key: 'date',
|
||||
children: [
|
||||
{ title: () => (
|
||||
<>
|
||||
首次响应率24H{' '}
|
||||
<Tooltip title="达到24H内回复的订单数/总订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
), dataIndex: 'firstTouch24', key: 'firstTouch24', render: percentageRender,
|
||||
sorter: (a, b) => tableSorter(a, b, 'firstTouch24'),
|
||||
},
|
||||
{ title: () => (
|
||||
<>
|
||||
48H内报价率{' '}
|
||||
<Tooltip title="达到48H内报价的订单数/总订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
), dataIndex: 'firstQuote48', key: 'firstQuote48',render: percentageRender ,
|
||||
sorter: (a, b) => tableSorter(a, b, 'firstQuote48'), },
|
||||
{ title: () => (
|
||||
<>
|
||||
一次报价率{' '}
|
||||
<Tooltip title="首次报价的订单数/总订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
), dataIndex: 'quote1', key: 'quote1',render: percentageRender ,
|
||||
sorter: (a, b) => tableSorter(a, b, 'quote1'), },
|
||||
{ title: () => (
|
||||
<>
|
||||
二次报价率{' '}
|
||||
<Tooltip title="二次报价的订单数/一次报价后回复数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
), dataIndex: 'quote2', key: 'quote2',render: percentageRender ,
|
||||
sorter: (a, b) => tableSorter(a, b, 'quote2'), },
|
||||
{ title: () => (
|
||||
<>
|
||||
>50条会话{' '}
|
||||
<Tooltip title=">50条会话的订单数/总订单">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
), dataIndex: 'turnsGT50', key: 'turnsGT50', render: percentageRender ,
|
||||
sorter: (a, b) => tableSorter(a, b, 'turnsGT50'), },
|
||||
{ title: () => (
|
||||
<>
|
||||
违规数{' '}
|
||||
<Tooltip title="未遵循24H回复的订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
), dataIndex: 'violations', key: 'violations',
|
||||
sorter: (a, b) => tableSorter(a, b, '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(_),
|
||||
}),
|
||||
sorter: (a, b) => tableSorter(a, b, 'firstReply24'), },
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
48H内报价回复率{' '}
|
||||
<Tooltip title="48H内报价后的回复数/报价的订单数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
), dataIndex: 'replyQuote48', key: 'replyQuote48',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
sorter: (a, b) => tableSorter(a, b, 'replyQuote48'), },
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
一次报价回复率{' '}
|
||||
<Tooltip title="一次报价后回复率/订单总数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
), dataIndex: 'replyQuote1', key: 'replyQuote1',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
sorter: (a, b) => tableSorter(a, b, 'replyQuote1'), },
|
||||
{
|
||||
title: () => (
|
||||
<>
|
||||
二次报价回复率{' '}
|
||||
<Tooltip title="二次报价后回复数/一次报价后回复数">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</>
|
||||
), dataIndex: 'replyQuote2', key: 'replyQuote2',
|
||||
render: (_, r) => ({
|
||||
props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
|
||||
children: percentageRender(_),
|
||||
}),
|
||||
sorter: (a, b) => tableSorter(a, b, 'replyQuote2'), },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const riskTableProps = {
|
||||
rowKey: 'groupsKey',
|
||||
pagination: { pageSize: 10, showSizeChanger: false },
|
||||
size: 'small',
|
||||
rowClassName: (record, rowIndex) => {
|
||||
return record.groupType === 'operator' ? '': 'ant-tag-blue';
|
||||
},
|
||||
showSorterTooltip: false,
|
||||
columns: [
|
||||
{ title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem',
|
||||
filterSearch: true,
|
||||
filters: operatorObjects.sort((a, b) => a.text.localeCompare(b.text)),
|
||||
onFilter: (value, record) => record.groupsKey === value || record.groupType !== 'operator',
|
||||
render: (text, record) => (record.groupType !== 'operator' ? text : <Link to={`/sales-crm/risk/sales/${record.groupsKey}`}>{text}</Link>),
|
||||
}, // 维度: 顾问
|
||||
{ title: '>24H回复', dataIndex: 'lostTouch24', key: 'lostTouch24',
|
||||
sorter: (a, b) => tableSorter(a, b, 'lostTouch24'),
|
||||
},
|
||||
{ title: '首次报价周期>48h', dataIndex: 'lostQuote48', key: 'lostQuote48',
|
||||
sorter: (a, b) => tableSorter(a, b, 'lostQuote48'),
|
||||
},
|
||||
{ title: '报价次数<1次', dataIndex: 'lostQuote1', key: 'lostQuote1',
|
||||
sorter: (a, b) => tableSorter(a, b, 'lostQuote1'),
|
||||
},
|
||||
{ title: '报价次数<2次', dataIndex: 'lostQuote2', key: 'lostQuote2',
|
||||
sorter: (a, b) => tableSorter(a, b, 'lostQuote2'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
|
||||
<Col md={24} lg={24} xxl={24}>
|
||||
<SearchForm
|
||||
defaultValue={{
|
||||
initialValue: {
|
||||
...formValues,
|
||||
...SalesCRMDataStore.searchValues,
|
||||
},
|
||||
shows: ['DepartmentList', 'WebCode', 'DateType', 'dates', 'IncludeTickets', 'country'],
|
||||
fieldProps: {
|
||||
DepartmentList: { show_all: false, mode: 'multiple', col: 5 },
|
||||
WebCode: { show_all: false, mode: 'multiple', col: 5 },
|
||||
dates: { hide_vs: true },
|
||||
country: { show_all: false, mode: 'multiple', col: 5 },
|
||||
},
|
||||
}}
|
||||
onSubmit={(_err, obj, form, str) => {
|
||||
SalesCRMDataStore.setSearchValues(obj, form);
|
||||
pageRefresh(obj);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<section>
|
||||
<Table {...activityTableProps} bordered dataSource={[...process.dataSource, ...process.details]} loading={process.loading} sticky />
|
||||
</section>
|
||||
<section>
|
||||
<h3>未成行订单 数量</h3>
|
||||
<Table {...riskTableProps} bordered dataSource={[...process.dataSource, ...process.details]} loading={process.loading} sticky />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
});
|
@ -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: () => (
|
||||
<>
|
||||
>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>>24H回复</h3>
|
||||
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostTouch24 || []} />
|
||||
</section>
|
||||
<section>
|
||||
<h3>首次报价周期>48h</h3>
|
||||
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote48 || []} />
|
||||
</section>
|
||||
<section>
|
||||
<h3>报价次数<1</h3>
|
||||
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote1 || []} />
|
||||
</section>
|
||||
<section>
|
||||
<h3>报价次数<2</h3>
|
||||
<Table {...riskTableProps} bordered dataSource={risk.byLostType?.lostQuote2 || []} />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
});
|
Loading…
Reference in New Issue