diff --git a/src/components/Donut.jsx b/src/components/Donut.jsx index ae3bccc..e7e5f2f 100644 --- a/src/components/Donut.jsx +++ b/src/components/Donut.jsx @@ -30,7 +30,7 @@ export default observer((props) => { offset: '-50%', autoRotate: false, // content: '{value}', - content: ({ percent }) => `${fixTo2Decimals(percent * 100)}%`, + content: ({ percent }) => percent > 0.02 ? `${fixTo2Decimals(percent * 100)}%`: '', style: { textAlign: 'center', fontSize: 14, diff --git a/src/components/LineWithKPI.jsx b/src/components/LineWithKPI.jsx index f2d6c43..d06bee3 100644 --- a/src/components/LineWithKPI.jsx +++ b/src/components/LineWithKPI.jsx @@ -1,53 +1,36 @@ import { observer } from 'mobx-react'; import { Line } from '@ant-design/plots'; -import { merge, isEmpty, groupBy } from '../utils/commons'; +import { merge, isEmpty, groupBy, sortBy } from '../utils/commons'; import { dataFieldAlias } from '../libs/ht'; -const uniqueByKey = (array, key, pickLast) => { - const seen = new Map(); - const isPickLast = pickLast === true; - - return array.filter(item => { - const k = item[key]; - const storedItem = seen.get(k); - - if(storedItem) { - if(isPickLast) { - seen.set(k, item); // update with last item - } - return false; - } - - seen.set(k, item); - return true; - }); -}; - export default observer((props) => { const { dataSource, ...config } = props; const kpiKey = dataFieldAlias[config.yField]?.nestkey?.v; const seriesData = groupBy(dataSource, ele => ele[config.seriesField]); - const pickKey4KPI = Object.keys(seriesData)[0]; - const KPIData = (seriesData?.[pickKey4KPI] || []).reduce((r, v) => { - if ( ! isEmpty(v[kpiKey])) { // 有设目标才多显示一条虚线 颜色: #F4664A - r.push({...v, [config.yField]: v[kpiKey], [config.seriesField]: dataFieldAlias[kpiKey].label, extraLine: true,}); + const splitData = dataSource.reduce((r, v) => { + r.push(v); + if ( ! isEmpty(v[kpiKey])) { // 有设目标才多显示一条虚线, 颜色和数据线一致 + r.push({...v, [config.yField]: v[kpiKey], [config.seriesField]: `${v[config.seriesField]} ${dataFieldAlias[kpiKey].label}`, extraLine: true,}); } return r; - }, []); - const dataColors = ['#598cf3', '#69deae', '#FAAD14']; + }, []).sort(sortBy(config.xField)); + const dataColors = [ + "#5B8FF9","#5AD8A6","#5D7092","#F6BD16","#6F5EF9","#6DC8EC","#945FB9","#FF9845","#1E9493", + "#FF99C3","#FF6B3B","#626681","#FFC100","#9FB40F","#76523B","#DAD5B5","#0E8E89","#E19348", + "#F383A2","#247FEA", + ]; const colorSets = Object.keys(seriesData).sort().reduce((obj, k, i) => ({...obj, [k]: dataColors[i]}), {}); const mergeLineConfig = merge({ - // color: ['#598cf3', '#69deae', '#F4664A', '#FAAD14'], color: (item) => { - const thisSeries = item[config.seriesField]; - return thisSeries.includes('目标') ? '#F4664A' : colorSets[thisSeries]; + const thisSeries = item[config.seriesField].split(' ')?.[0]; + return colorSets[thisSeries]; + // return thisSeries.includes('目标') ? '#F4664A' : colorSets[thisSeries]; }, lineStyle: (data) => { - // console.log(data); if (data[config.seriesField].includes('目标')) { return { - lineDash: [4, 4], - opacity: 0.5, + lineDash: [4, 8], + opacity: 0.7, }; } @@ -55,6 +38,10 @@ export default observer((props) => { opacity: 1, }; }, + legend: { + custom: true, + items: Object.keys(seriesData).map((ele) => ({ id: ele, name: ele, value: ele, marker: { symbol: 'circle', style: { fill: colorSets[ele], color: colorSets[ele] } } })), + }, }, config); - return ; + return ; }); diff --git a/src/stores/SaleStore.js b/src/stores/SaleStore.js index 4987574..cfdf8bd 100644 --- a/src/stores/SaleStore.js +++ b/src/stores/SaleStore.js @@ -30,11 +30,23 @@ class SaleStore { type_data_sub = []; // 类型的子维度数据 date_title = 'date_title'; // 日期段,只用于显示,防止日期选择控件的变化导致页面刷新 - salesTrade = { groupType: 'dept', loading: false, operator: [], dept: [], overview: [] }; + salesTrade = { + groupType: 'dept', loading: false, operator: [], dept: [], overview: [], + operatorMapped: {}, pickSales: [], pickSalesData: [], + }; + setGroupType(v) { this.salesTrade.groupType = v; } + setPickSales = (v) => { + this.salesTrade.pickSales = v; + setTimeout(() => { + const data = v.reduce((r, ele) => r.concat(this.salesTrade.operatorMapped[ele.key]), []); + this.salesTrade.pickSalesData = data; + }, 500); + }; + // 是否包含门票 handleChange_include_tickets = (value) => { this.include_tickets = value; @@ -514,6 +526,7 @@ class SaleStore { runInAction(() => { this.salesTrade.loading = false; this.salesTrade[groupType] = mergeYearMonth; + this.salesTrade[`${groupType}Mapped`] = mergeYearMonth.reduce((r, v) => ({...r, [v.groupsKey]: Object.values(v.mData)}), {}); }); } diff --git a/src/views/Sale_KPI.jsx b/src/views/Sale_KPI.jsx index 2790c08..e565c93 100644 --- a/src/views/Sale_KPI.jsx +++ b/src/views/Sale_KPI.jsx @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useState } from 'react'; -import { Row, Col, Button, Tabs, Table, Divider, Radio, Select, Space, Typography, Progress } from 'antd'; +import { Row, Col, Button, Tabs, Table, Divider, Radio, Select, Space, Typography, Progress, Spin } from 'antd'; import { ContainerOutlined, SearchOutlined, UserSwitchOutlined } from '@ant-design/icons'; import { stores_Context } from '../config'; import { observer } from 'mobx-react'; @@ -9,6 +9,9 @@ import * as config from '../config'; import { utils, writeFileXLSX } from 'xlsx'; import SearchForm from './../components/search/SearchForm'; import { dataFieldAlias } from '../libs/ht'; +import Donut from './../components/Donut'; +import LineWithKPI from '../components/LineWithKPI'; +import { Line, DualAxes } from '@ant-design/plots'; const { Text } = Typography; @@ -18,13 +21,15 @@ const Sale_KPI = () => { const { groupType, loading, operator } = sale_store.salesTrade; const dataSource = [].concat(sale_store.salesTrade[groupType], operator); - + const yearData = Object.values(sale_store.salesTrade[groupType]?.[0]?.mData || {}); + const operatorObjects = operator.map((v) => ({ key: v.groupsKey, value: v.groupsKey, label: v.groupsLabel })); const pageRefresh = (queryData) => { const overviewFlag = queryData.DepartmentList.toLowerCase() === 'all' || queryData.DepartmentList.toLowerCase().includes(','); const _groupType = overviewFlag ? 'overview' : 'dept'; sale_store.setGroupType(_groupType); sale_store.fetchOperatorTradeData(_groupType, { ...queryData, groupDateType: 'year' }); sale_store.fetchOperatorTradeData('operator', { ...queryData, groupDateType: 'year' }); + sale_store.setPickSales([]); }; const monthCol = new Array(12).fill(1).map((_, index) => { return { @@ -98,6 +103,7 @@ const Sale_KPI = () => { }, ...monthCol, ]; + const lineConfig = { appendPadding: 10, xField: 'groupDateVal', yField: 'SumML', seriesField: 'groupsLabel', isGroup: true, smooth: true, meta: comm.cloneDeep(dataFieldAlias), }; return (
@@ -120,22 +126,63 @@ const Sale_KPI = () => { /> + +

年度业绩组成和走势

+ + + ({ ...row, SumML: row.yearML }))} + /> + + + + + + +

顾问业绩走势

+ +
+ + + + +
- - - - - + + +
+ + + ); };