diff --git a/src/App.jsx b/src/App.jsx
index 4db3f01..1039556 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -53,7 +53,7 @@ import { stores_Context, APP_VERSION } from './config';
import { WaterMark } from '@ant-design/pro-components';
import CooperationIcon from './components/icons/CooperationIcon';
import OPDashboard from './views/OPDashboard';
-import OPActivity from './views/OPActivity';
+import OPProcess from './views/OPProcess';
import OPRisk from './views/OPRisk';
const App = () => {
@@ -167,7 +167,7 @@ const App = () => {
children: [
// { key: 'xx', type: 'divider' },
{ key: 'op_dashboard', label: 结果 },
- // { key: 'op_activity', label: 过程 },
+ { key: 'op_process', label: 过程 },
// { key: 'op_risk', label: 提升 },
],
},
@@ -272,7 +272,7 @@ const App = () => {
} />
} />
} />
- } />
+ } />
} />
diff --git a/src/stores/SalesCRMData.js b/src/stores/SalesCRMData.js
index da4cbc6..0dfe535 100644
--- a/src/stores/SalesCRMData.js
+++ b/src/stores/SalesCRMData.js
@@ -19,6 +19,20 @@ const fetchResultsData = async (param) => {
return json.errcode === 0 ? json.result : [];
};
+const fetchProcessData = async (param) => {
+ const defaultParam = {
+ WebCode: 'All',
+ DepartmentList: '',
+ opisn: -1,
+ Date1: '',
+ Date2: '',
+ groupType: '',
+ groupDateType: '',
+ };
+ const json = await fetchJSON('/service-Analyse2/sales_crm_process', { ...defaultParam, ...param });
+ return json.errcode === 0 ? json.result : [];
+};
+
class SalesCRMData {
constructor(appStore) {
this.appStore = appStore;
@@ -63,7 +77,6 @@ class SalesCRMData {
return this.results;
}
-
async getResultData(param = {}) {
const retKey = param.groupDateType === '' ? 'byOperator' : 'byDate';
this.results.loading = true;
@@ -76,8 +89,21 @@ class SalesCRMData {
return this.results;
};
+ async getProcessData(param = {}) {
+ // const retKey = param.groupDateType === '' ? 'byOperator' : 'byDate';
+ const retKey = param.groupDateType === '' ? (param.groupType !== 'operator' ? 'dataSource' : 'details') : 'byDate';
+ this.process.loading = true;
+ this.process[retKey] = [];
+ const res = await fetchProcessData({ ...this.searchValues, ...param });
+ runInAction(() => {
+ this.process.loading = false;
+ this.process[retKey] = [].concat(this.results[retKey], res);
+ // this.process[retKey] =retKey === 'byOperator' ? res.filter(ele => ele.)
+ });
+ }
+
searchValues = {
- // DateType: { key: 'confirmDate', label: '确认日期'},
+ DateType: { key: 'applyDate', label: '提交日期'},
WebCode: { key: 'All', label: '所有来源' },
// IncludeTickets: { key: '1', label: '含门票'},
DepartmentList: [groupsMappedByCode.GH], // test: GH
@@ -89,10 +115,21 @@ class SalesCRMData {
setSearchValues(obj, values) {
this.searchValues = { ...this.searchValues, ...values };
+ if (values.date) {
+ this.searchValues.date90 = {
+ Date1: (values.date.clone()).subtract(90, 'days').format(DATE_FORMAT),
+ Date2: (values.date.clone()).subtract(30, 'days').format(SMALL_DATETIME_FORMAT),
+ };
+ this.searchValues.date180 = {
+ Date1: (values.date.clone()).subtract(180, 'days').format(DATE_FORMAT),
+ Date2: (values.date.clone()).subtract(50, 'days').format(SMALL_DATETIME_FORMAT),
+ };
+ }
this.searchValuesToSub = obj;
}
results = { loading: false, dataSource: [], details: [], byDate: [], byOperator: [] };
+ process = { loading: false, dataSource: [], details: [], byDate: [], byOperator: [] };
resetData = () => {
this.results.loading = false;
for (const key of Object.keys(this.results)) {
@@ -100,6 +137,11 @@ class SalesCRMData {
this.results[key] = [];
}
}
+ for (const key of Object.keys(this.process)) {
+ if (key !== 'loading') {
+ this.process[key] = [];
+ }
+ }
};
}
export default SalesCRMData;
diff --git a/src/views/OPActivity.jsx b/src/views/OPActivity.jsx
deleted file mode 100644
index 9c42e41..0000000
--- a/src/views/OPActivity.jsx
+++ /dev/null
@@ -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 (
- <>
-
-
- {
- sale_store.setSearchValues(obj, form);
- // pageRefresh(obj);
- }}
- />
-
-
-
-
- >
- );
-});
diff --git a/src/views/OPProcess.jsx b/src/views/OPProcess.jsx
new file mode 100644
index 0000000..7303aca
--- /dev/null
+++ b/src/views/OPProcess.jsx
@@ -0,0 +1,186 @@
+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';
+import { cloneDeep, fixTo2Decimals, sortBy, isEmpty, 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();
+ 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';
+ },
+ 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',
+ }, // 维度: 顾问
+ {
+ title: '顾问动作',
+ key: 'date',
+ children: [
+ { title: '首次响应率24H', dataIndex: 'firstTouch24', key: 'firstTouch24', render: percentageRender,
+ sorter: (a, b) => tableSorter(a, b, 'firstTouch24'),
+ },
+ { title: '48H内报价率', dataIndex: 'firstQuote48', key: 'firstQuote48',render: percentageRender ,
+ sorter: (a, b) => tableSorter(a, b, 'firstQuote48'), },
+ { title: '一次报价率', dataIndex: 'quote1', key: 'quote1',render: percentageRender ,
+ sorter: (a, b) => tableSorter(a, b, 'quote1'), },
+ { title: '二次报价率', dataIndex: 'quote2', key: 'quote2',render: percentageRender ,
+ sorter: (a, b) => tableSorter(a, b, 'quote2'), },
+ { title: '>50条会话', dataIndex: 'turnsGT50', key: 'turnsGT50', render: percentageRender ,
+ sorter: (a, b) => tableSorter(a, b, 'turnsGT50'), },
+ { title: '违规数', dataIndex: 'violations', key: 'violations',
+ sorter: (a, b) => tableSorter(a, b, 'violations'), },
+ ],
+ },
+ {
+ title: '客人回复',
+ key: 'department',
+ children: [
+ {
+ title: '首次回复率24H', dataIndex: 'firstReply24', key: 'firstReply24',
+ render: (_, r) => ({
+ props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
+ children: percentageRender(_),
+ }),
+ sorter: (a, b) => tableSorter(a, b, 'firstReply24'), },
+ {
+ title: '48H内报价回复率', dataIndex: 'replyQuote48', key: 'replyQuote48',
+ render: (_, r) => ({
+ props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
+ children: percentageRender(_),
+ }),
+ sorter: (a, b) => tableSorter(a, b, 'replyQuote48'), },
+ {
+ title: '一次报价回复率', dataIndex: 'replyQuote1', key: 'replyQuote1',
+ render: (_, r) => ({
+ props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } },
+ children: percentageRender(_),
+ }),
+ sorter: (a, b) => tableSorter(a, b, 'replyQuote1'), },
+ {
+ title: '二次报价回复率', 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';
+ },
+ 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) => { // todo: 点击查看不成行订单明细
+ // },
+ }, // 维度: 顾问
+ { 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 (
+ <>
+
+
+ {
+ SalesCRMDataStore.setSearchValues(obj, form);
+ pageRefresh(obj);
+ }}
+ />
+
+
+
+
+ >
+ );
+});