From cb598ecba150f87bfdfc8b8ef712a904c6c022ba Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 11 Apr 2025 11:08:31 +0800 Subject: [PATCH 1/2] =?UTF-8?q?perf:=20CRM=E7=BB=9F=E8=AE=A1:=20tips?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/OPDashboard.jsx | 7 +-- src/views/OPProcess.jsx | 101 ++++++++++++++++++++++++++++++++------ 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/views/OPDashboard.jsx b/src/views/OPDashboard.jsx index 68a5eb5..6fedf58 100644 --- a/src/views/OPDashboard.jsx +++ b/src/views/OPDashboard.jsx @@ -33,7 +33,7 @@ export default observer((props) => { const { formValues, formValuesToSub, siderBroken } = searchFormStore; const _formValuesToSub = pick(formValuesToSub, ['DepartmentList', 'WebCode']); - const { resetData, results } = SalesCRMDataStore; + const { searchValues, resetData, results } = SalesCRMDataStore; const operatorObjects = results.details.map((v) => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel, text: v.groupsLabel })); // console.log(operatorObjects); @@ -189,6 +189,7 @@ export default observer((props) => { const dashboardTableProps = { pagination: false, size: 'small', + showSorterTooltip: false, columns: [ { title: '', @@ -200,12 +201,12 @@ export default observer((props) => { onFilter: (value, record) => record.groupsKey === value || record.groupType === 'overview', }, { - title: '前90 -30天', + title: () => (<>前90 -30天
{searchValues.date90.Date1} 至 {searchValues.date90.Date2}), key: 'date', children: dataFields('result90', 0), }, { - title: '前180 -50天', + title: () => (<>前180 -50天
{searchValues.date180.Date1} 至 {searchValues.date180.Date2}), key: 'department', children: dataFields('result180', 1), }, diff --git a/src/views/OPProcess.jsx b/src/views/OPProcess.jsx index 7303aca..78d2bee 100644 --- a/src/views/OPProcess.jsx +++ b/src/views/OPProcess.jsx @@ -1,11 +1,10 @@ -import React, { Children, useContext, useState } from 'react'; +import React, { useContext } 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 { InfoCircleOutlined } from '@ant-design/icons'; +import { Row, Col, Table, Tooltip } from 'antd'; import SearchForm from '../components/search/SearchForm'; -import { cloneDeep, fixTo2Decimals, sortBy, isEmpty, pick } from '../utils/commons'; +import { pick } from '../utils/commons'; const COLOR_SETS = [ "#FFFFFF", @@ -58,6 +57,7 @@ export default observer((props) => { rowClassName: (record, rowIndex) => { return record.groupType === 'operator' ? '': 'ant-tag-blue'; }, + showSorterTooltip: false, columns: [ { title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem', filterSearch: true, @@ -68,18 +68,60 @@ export default observer((props) => { title: '顾问动作', key: 'date', children: [ - { title: '首次响应率24H', dataIndex: 'firstTouch24', key: 'firstTouch24', render: percentageRender, + { title: () => ( + <> + 首次响应率24H{' '} + + + + + ), dataIndex: 'firstTouch24', key: 'firstTouch24', render: percentageRender, sorter: (a, b) => tableSorter(a, b, 'firstTouch24'), }, - { title: '48H内报价率', dataIndex: 'firstQuote48', key: 'firstQuote48',render: percentageRender , + { title: () => ( + <> + 48H内报价率{' '} + + + + + ), dataIndex: 'firstQuote48', key: 'firstQuote48',render: percentageRender , sorter: (a, b) => tableSorter(a, b, 'firstQuote48'), }, - { title: '一次报价率', dataIndex: 'quote1', key: 'quote1',render: percentageRender , + { title: () => ( + <> + 一次报价率{' '} + + + + + ), dataIndex: 'quote1', key: 'quote1',render: percentageRender , sorter: (a, b) => tableSorter(a, b, 'quote1'), }, - { title: '二次报价率', dataIndex: 'quote2', key: 'quote2',render: percentageRender , + { title: () => ( + <> + 二次报价率{' '} + + + + + ), dataIndex: 'quote2', key: 'quote2',render: percentageRender , sorter: (a, b) => tableSorter(a, b, 'quote2'), }, - { title: '>50条会话', dataIndex: 'turnsGT50', key: 'turnsGT50', render: percentageRender , + { title: () => ( + <> + >50条会话{' '} + + + + + ), dataIndex: 'turnsGT50', key: 'turnsGT50', render: percentageRender , sorter: (a, b) => tableSorter(a, b, 'turnsGT50'), }, - { title: '违规数', dataIndex: 'violations', key: 'violations', + { title: () => ( + <> + 违规数{' '} + + + + + ), dataIndex: 'violations', key: 'violations', sorter: (a, b) => tableSorter(a, b, 'violations'), }, ], }, @@ -88,28 +130,56 @@ export default observer((props) => { key: 'department', children: [ { - title: '首次回复率24H', dataIndex: 'firstReply24', key: 'firstReply24', + 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', + 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', + 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', + title: () => ( + <> + 二次报价回复率{' '} + + + + + ), dataIndex: 'replyQuote2', key: 'replyQuote2', render: (_, r) => ({ props: { style: { backgroundColor: COLOR_SETS[1]+transparentHex } }, children: percentageRender(_), @@ -127,6 +197,7 @@ export default observer((props) => { rowClassName: (record, rowIndex) => { return record.groupType === 'operator' ? '': 'ant-tag-blue'; }, + showSorterTooltip: false, columns: [ { title: '', dataIndex: 'groupsLabel', key: 'groupsLabel', width: '6rem', filterSearch: true, From c3547538fd9b5bd7405bd0cb10c4ba86486cf361 Mon Sep 17 00:00:00 2001 From: ZJYHX Date: Mon, 14 Apr 2025 11:18:56 +0800 Subject: [PATCH 2/2] =?UTF-8?q?perf:=E5=AE=A2=E6=9C=8D:=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9=E6=AF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/CustomerServices.js | 363 +++++++++++++++++++++++---------- src/utils/commons.js | 4 + src/views/AgentGroupCount.jsx | 2 +- 3 files changed, 262 insertions(+), 107 deletions(-) diff --git a/src/stores/CustomerServices.js b/src/stores/CustomerServices.js index 1e05bc3..e8fb2aa 100644 --- a/src/stores/CustomerServices.js +++ b/src/stores/CustomerServices.js @@ -3,7 +3,7 @@ import moment from "moment"; import { NavLink } from "react-router-dom"; import * as config from "../config"; import * as req from '../utils/request'; -import { groupBy, prepareUrl } from '../utils/commons'; +import { groupBy, prepareUrl, isEmpty, show_vs_tag, formatPercent, percentToDecimal } from '../utils/commons'; class CustomerServices { @@ -53,113 +53,264 @@ class CustomerServices { .then(json => { if (json.errcode === 0) { runInAction(() => { - const splitTotalList = groupBy(json.result1, row => row.EOI_ObjSN === -1 ? '0' : '1'); - this.agentGroupList = splitTotalList['1']; - const total1 = splitTotalList['0']?.[0] || {}; // json.total1; - this.agentGroupListColumns = [ - { - title: '地接社名称', - dataIndex: 'VendorName', - fixed: 'left', - children: [{ - // title: this.startDate.format(config.DATE_FORMAT) + '~' + this.endDate.format(config.DATE_FORMAT), - dataIndex: 'VendorName', - fixed: 'left', - render: (text, record) => {record.VendorName} - } - ] - }, - { - title: '团数', - dataIndex: 'GroupCount', - sorter: (a, b) => a.GroupCount - b.GroupCount, - children: [{ - title: total1.GroupCount, - dataIndex: 'GroupCount' - } - ] - }, - { - title: '人数', - dataIndex: 'PersonNum', - sorter: (a, b) => a.PersonNum - b.PersonNum, - children: [{ - title: total1.PersonNum, - dataIndex: 'PersonNum' - } - ] - }, - { - title: '团天数', - dataIndex: 'GroupDays', - sorter: (a, b) => a.GroupDays - b.GroupDays, - children: [{ - title: total1.GroupDays, - dataIndex: 'GroupDays' - } - ] - }, - { - title: '交易额', - dataIndex: 'totalcost', - sorter: (a, b) => a.totalcost - b.totalcost, - children: [{ - title: total1.totalcost, - dataIndex: 'totalcost' - } - ] - }, - { - title: '前勤分', - dataIndex: 'qianqin', - sorter: (a, b) => a.qianqin - b.qianqin, - children: [{ - title: total1.qianqin, - dataIndex: 'qianqin' - } - ] - }, - { - title: '好评数', - dataIndex: 'GoodCount', - sorter: (a, b) => a.GoodCount - b.GoodCount, - children: [{ - title: total1.GoodCount, - dataIndex: 'GoodCount' - } - ] - }, - { - title: '好评率', - dataIndex: 'GoodRate', - sorter: (a, b) => parseInt(a.GoodRate) - parseInt(b.GoodRate), - children: [{ - title: total1.GoodRate, - dataIndex: 'GoodRate' - } - ] - }, - { - title: '差评数', - dataIndex: 'BadCount', - sorter: (a, b) => a.BadCount - b.BadCount, - children: [{ - title: total1.BadCount, - dataIndex: 'BadCount' - } - ] - }, - { - title: '差评率', - dataIndex: 'BadRate', - sorter: (a, b) => parseInt(a.BadRate) - parseInt(b.BadRate), - children: [{ - title: total1.BadRate, - dataIndex: 'BadRate' + if (isEmpty(json.result2)){ + const splitTotalList = groupBy(json.result1, row => row.EOI_ObjSN === -1 ? '0' : '1'); + this.agentGroupList = splitTotalList['1']; + const total1 = splitTotalList['0']?.[0] || {}; // json.total1; + this.agentGroupListColumns = [ + { + title: '地接社名称', + dataIndex: 'VendorName', + fixed: 'left', + children: [{ + // title: this.startDate.format(config.DATE_FORMAT) + '~' + this.endDate.format(config.DATE_FORMAT), + dataIndex: 'VendorName', + fixed: 'left', + render: (text, record) => {record.VendorName} + } + ] + }, + { + title: '团数', + dataIndex: 'GroupCount', + sorter: (a, b) => a.GroupCount - b.GroupCount, + children: [{ + title: total1.GroupCount, + dataIndex: 'GroupCount' + } + ] + }, + { + title: '人数', + dataIndex: 'PersonNum', + sorter: (a, b) => a.PersonNum - b.PersonNum, + children: [{ + title: total1.PersonNum, + dataIndex: 'PersonNum' + } + ] + }, + { + title: '团天数', + dataIndex: 'GroupDays', + sorter: (a, b) => a.GroupDays - b.GroupDays, + children: [{ + title: total1.GroupDays, + dataIndex: 'GroupDays' + } + ] + }, + { + title: '交易额', + dataIndex: 'totalcost', + sorter: (a, b) => a.totalcost - b.totalcost, + children: [{ + title: total1.totalcost, + dataIndex: 'totalcost' + } + ] + }, + { + title: '前勤分', + dataIndex: 'qianqin', + sorter: (a, b) => a.qianqin - b.qianqin, + children: [{ + title: total1.qianqin, + dataIndex: 'qianqin' + } + ] + }, + { + title: '好评数', + dataIndex: 'GoodCount', + sorter: (a, b) => a.GoodCount - b.GoodCount, + children: [{ + title: total1.GoodCount, + dataIndex: 'GoodCount' + } + ] + }, + { + title: '好评率', + dataIndex: 'GoodRate', + sorter: (a, b) => parseInt(a.GoodRate) - parseInt(b.GoodRate), + children: [{ + title: total1.GoodRate, + dataIndex: 'GoodRate' + } + ] + }, + { + title: '差评数', + dataIndex: 'BadCount', + sorter: (a, b) => a.BadCount - b.BadCount, + children: [{ + title: total1.BadCount, + dataIndex: 'BadCount' + } + ] + }, + { + title: '差评率', + dataIndex: 'BadRate', + sorter: (a, b) => parseInt(a.BadRate) - parseInt(b.BadRate), + children: [{ + title: total1.BadRate, + dataIndex: 'BadRate' + } + ] + } + ]; + + } + else{ + const splitTotalList1 = groupBy(json.result1, row => row.EOI_ObjSN === -1 ? '0' : '1'); + const splitTotalList2 = groupBy(json.result2, row => row.EOI_ObjSN === -1 ? '0' : '1'); + const result = []; + for (const item1 of splitTotalList1['1']) { + for (const item2 of splitTotalList2['1']) { + if (item1.EOI_ObjSN === item2.EOI_ObjSN) { + const goodRate1 = percentToDecimal(item1.GoodRate); + const goodRate2 = percentToDecimal(item2.GoodRate); + const badRate1 = percentToDecimal(item1.BadRate); + const badRate2 = percentToDecimal(item2.BadRate); + result.push({ + EOI_ObjSN: item1.EOI_ObjSN, + VendorName: item1.VendorName, + GroupCount: show_vs_tag(formatPercent((item1.GroupCount-item2.GroupCount)/item2.GroupCount),item1.GroupCount-item2.GroupCount,item1.GroupCount,item2.GroupCount), + PersonNum: show_vs_tag(formatPercent((item1.PersonNum-item2.PersonNum)/item2.PersonNum),item1.PersonNum-item2.PersonNum,item1.PersonNum,item2.PersonNum), + GroupDays: show_vs_tag(formatPercent((item1.GroupDays-item2.GroupDays)/item2.GroupDays),item1.GroupDays-item2.GroupDays,item1.GroupDays,item2.GroupDays), + totalcost: show_vs_tag(formatPercent((item1.totalcost-item2.totalcost)/item2.totalcost), + (item1.totalcost-item2.totalcost).toFixed(2),item1.totalcost,item2.totalcost), + GoodCount: show_vs_tag(formatPercent((item1.GoodCount-item2.GoodCount)/item2.GoodCount),item1.GoodCount-item2.GoodCount,item1.GoodCount,item2.GoodCount), + GoodRate: show_vs_tag(formatPercent((goodRate1-goodRate2)/goodRate2),formatPercent(goodRate1-goodRate2),item1.GoodRate,item2.GoodRate), + BadCount: show_vs_tag(formatPercent((item1.BadCount-item2.BadCount)/item2.BadCount),item1.BadCount-item2.BadCount,item1.BadCount,item2.BadCount), + BadRate: show_vs_tag(formatPercent((badRate1-badRate2)/badRate2),formatPercent(badRate1-badRate2),item1.BadRate,item2.BadRate), + qianqin: show_vs_tag(formatPercent((item1.qianqin-item2.qianqin)/item2.qianqin),(item1.qianqin-item2.qianqin).toFixed(2),item1.qianqin,item2.qianqin), + key:item1.key, + }); } - ] + } } - ]; + this.agentGroupList = result; + const total1 = splitTotalList1['0']?.[0] || {}; + const total2 = splitTotalList2['0']?.[0] || {}; + this.agentGroupListColumns = [ + { + title: '地接社名称', + dataIndex: 'VendorName', + fixed: 'left', + children: [{ + // title: this.startDate.format(config.DATE_FORMAT) + '~' + this.endDate.format(config.DATE_FORMAT), + dataIndex: 'VendorName', + fixed: 'left', + render: (text, record) => {record.VendorName} + } + ] + }, + { + title: '团数', + dataIndex: 'GroupCount', + sorter: (a, b) => a.GroupCount - b.GroupCount, + children: [{ + title: show_vs_tag(formatPercent((total1.GroupCount-total2.GroupCount)/total2.GroupCount),total1.GroupCount-total2.GroupCount,total1.GroupCount,total2.GroupCount), + titleX: [total1.GroupCount, total2.GroupCount].join(' vs '), + dataIndex: 'GroupCount' + } + ] + }, + { + title: '人数', + dataIndex: 'PersonNum', + sorter: (a, b) => a.PersonNum - b.PersonNum, + children: [{ + title: show_vs_tag(formatPercent((total1.PersonNum-total2.PersonNum)/total2.PersonNum),total1.PersonNum-total2.PersonNum,total1.PersonNum,total2.PersonNum), + titleX: [total1.PersonNum, total2.PersonNum].join(' vs '), + dataIndex: 'PersonNum' + } + ] + }, + { + title: '团天数', + dataIndex: 'GroupDays', + sorter: (a, b) => a.GroupDays - b.GroupDays, + children: [{ + title: show_vs_tag(formatPercent((total1.GroupDays-total2.GroupDays)/total2.GroupDays),total1.GroupDays-total2.GroupDays,total1.GroupDays,total2.GroupDays), + titleX: [total1.GroupDays, total2.GroupDays].join(' vs '), + dataIndex: 'GroupDays' + } + ] + }, + { + title: '交易额', + dataIndex: 'totalcost', + sorter: (a, b) => a.totalcost - b.totalcost, + children: [{ + title: show_vs_tag(formatPercent((total1.totalcost-total2.totalcost)/total2.totalcost),(total1.totalcost-total2.totalcost).toFixed(2),total1.totalcost,total2.totalcost), + titleX: [total1.totalcost, total2.totalcost].join(' vs '), + dataIndex: 'totalcost' + } + ] + }, + { + title: '前勤分', + dataIndex: 'qianqin', + sorter: (a, b) => a.qianqin - b.qianqin, + children: [{ + title: show_vs_tag(formatPercent((total1.qianqin-total2.qianqin)/total2.qianqin),(total1.qianqin-total2.qianqin).toFixed(2),total1.qianqin,total2.qianqin), + titleX: [total1.qianqin, total2.qianqin].join(' vs '), + dataIndex: 'qianqin' + } + ] + }, + { + title: '好评数', + dataIndex: 'GoodCount', + sorter: (a, b) => a.GoodCount - b.GoodCount, + children: [{ + title: show_vs_tag(formatPercent((total1.GoodCount-total2.GoodCount)/total2.GoodCount),total1.GoodCount-total2.GoodCount,total1.GoodCount,total2.GoodCount), + titleX: [total1.GoodCount, total2.GoodCount].join(' vs '), + dataIndex: 'GoodCount' + } + ] + }, + { + title: '好评率', + dataIndex: 'GoodRate', + sorter: (a, b) => parseInt(a.GoodRate) - parseInt(b.GoodRate), + children: [{ + title: show_vs_tag(formatPercent((percentToDecimal(total1.GoodRate)-percentToDecimal(total2.GoodRate))/percentToDecimal(total2.GoodRate)), + formatPercent(percentToDecimal(total1.GoodRate)-percentToDecimal(total2.GoodRate)),total1.GoodRate,total2.GoodRate), + titleX: [total1.GoodRate, total2.GoodRate].join(' vs '), + dataIndex: 'GoodRate' + } + ] + }, + { + title: '差评数', + dataIndex: 'BadCount', + sorter: (a, b) => a.BadCount - b.BadCount, + children: [{ + title: show_vs_tag(formatPercent((total1.BadCount-total2.BadCount)/total2.BadCount),total1.BadCount-total2.BadCount,total1.BadCount,total2.BadCount), + titleX: [total1.BadCount, total2.BadCount].join(' vs '), + dataIndex: 'BadCount' + } + ] + }, + { + title: '差评率', + dataIndex: 'BadRate', + sorter: (a, b) => parseInt(a.BadRate) - parseInt(b.BadRate), + children: [{ + title: show_vs_tag(formatPercent((percentToDecimal(total1.BadRate)-percentToDecimal(total2.BadRate))/percentToDecimal(total2.GoodRate)), + formatPercent(percentToDecimal(total1.BadRate)-percentToDecimal(total2.BadRate)),total1.BadRate,total2.BadRate), + titleX: [total1.BadRate, total2.BadRate].join(' vs '), + dataIndex: 'BadRate' + } + ] + } + ]; + } }); } }) diff --git a/src/utils/commons.js b/src/utils/commons.js index 2849681..86b4b7a 100644 --- a/src/utils/commons.js +++ b/src/utils/commons.js @@ -72,6 +72,10 @@ export function formatPercent(number) { return Math.round(number * 100) + "%"; } +export function percentToDecimal(number) { + return parseFloat(number) / 100; +} + export function formatDate(date) { if (isEmpty(date)) { return "NaN"; diff --git a/src/views/AgentGroupCount.jsx b/src/views/AgentGroupCount.jsx index 211243a..8d7dc5d 100644 --- a/src/views/AgentGroupCount.jsx +++ b/src/views/AgentGroupCount.jsx @@ -32,7 +32,7 @@ const AgentGroupCount = () => { fieldProps: { DepartmentList: { show_all: true, mode: 'multiple' }, WebCode: { show_all: false, mode: 'multiple' }, - dates: { hide_vs: true }, + years: { hide_vs: true }, departureDateType: { disabledKeys: ['applyDate'] }, }, }}