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); + }} + /> + + +
+
+ +
+

未成行订单 数量

+
+ + + ); +});