顾问: 销售进度: 图表

feature/person-num
Lei OT 2 years ago
parent 64898b7533
commit c3194869fd

@ -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,

@ -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 <Line {...mergeLineConfig} data={[...dataSource, ...KPIData]} />;
return <Line {...mergeLineConfig} data={splitData} />;
});

@ -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)}), {});
});
}

@ -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 (
<div>
<Row gutter={16} style={{ margin: '-16px -8px' }}>
@ -120,22 +126,63 @@ const Sale_KPI = () => {
/>
</Col>
</Row>
<Spin spinning={loading}>
<h2 style={{ marginTop: '.5em' }}>年度业绩组成和走势</h2>
<Row>
<Col className="gutter-row" md={8}>
<Donut
{...{ angleField: 'SumML', colorField: 'groupsLabel', label1: { style: { color: '#000000' }, type: 'spider', content: '{name}\n{percentage}' }, legend: false, label2: false }}
title={formValues.DepartmentList?.label}
dataSource={operator.map((row) => ({ ...row, SumML: row.yearML }))}
/>
</Col>
<Col className="gutter-row" md={16}>
<LineWithKPI dataSource={yearData} {...lineConfig} {...{legend: false}} />
</Col>
<Col className="gutter-row" md={24}>
<Space gutter={16} size={'large'}>
<h2>顾问业绩走势</h2>
<Select
labelInValue
mode={'multiple'}
style={{ width: '400px' }}
placeholder={'选择顾问'}
onChange={sale_store.setPickSales}
value={sale_store.salesTrade.pickSales}
maxTagCount={1}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
>
{operatorObjects.map((ele) => (
<Select.Option key={ele.key} value={ele.value}>
{ele.label}
</Select.Option>
))}
</Select>
</Space>
</Col>
<Col className="gutter-row" md={24}>
<LineWithKPI dataSource={sale_store.salesTrade.pickSalesData} {...lineConfig} />
</Col>
</Row>
<Row>
<Col className="gutter-row" md={24}>
<Table
key={`salesTradeTable`}
loading={loading}
columns={columns}
rowKey="groupsKey"
scroll={{
x: 1000,
}}
dataSource={dataSource}
pagination={false}
/>
</Col>
</Row>
<Row>
<Col className="gutter-row" md={24}>
<Table
sticky
key={`salesTradeTable`}
loading={loading}
columns={columns}
rowKey="groupsKey"
scroll={{
x: 1000,
}}
dataSource={dataSource}
pagination={false}
/>
</Col>
</Row>
</Spin>
</div>
);
};

Loading…
Cancel
Save