feat: home: 瀑布图-百分比; 排名图-进度

feature/2.0-sales-trade
Lei OT 2 years ago
parent d83e7dec92
commit 230ec4be81

@ -1,100 +0,0 @@
import { observer } from 'mobx-react';
import { Bullet } from '@ant-design/plots';
export default observer((props) => {
const { dataSource, ...extProps } = props;
// todo: , number -> array
const parseData = dataSource?.map(ele => ({...ele, CJCountRange: [0,1000], [extProps.measureField]: [ele[extProps.measureField]]}));
const config = {
// data,
// measureField: 'measures',
// rangeField: 'ranges',
// targetField: 'target',
// xField: 'title',
color: {
range: ['#FFbcb8', '#FFe0b0', '#bfeec8'],
measure: '#5B8FF9',
target: '#39a3f4',
},
label: {
// target: true,
// measure: {
// position: 'middle',
// style: {
// fill: '#fff',
// },
// },
},
xAxis: {
line: null,
},
yAxis: false,
// legend
legend: {
custom: true,
position: 'bottom',
items: [
// {
// value: '',
// name: '',
// marker: {
// symbol: 'square',
// style: {
// fill: '#FFbcb8',
// r: 5,
// },
// },
// },
// {
// value: '',
// name: '',
// marker: {
// symbol: 'square',
// style: {
// fill: '#FFe0b0',
// r: 5,
// },
// },
// },
// {
// value: '',
// name: '',
// marker: {
// symbol: 'square',
// style: {
// fill: '#bfeec8',
// r: 5,
// },
// },
// },
{
value: '实际',
name: '实际',
marker: {
symbol: 'square',
style: {
fill: '#5B8FF9',
r: 5,
},
},
},
{
value: '目标',
name: '目标',
marker: {
symbol: 'line',
style: {
stroke: '#39a3f4',
r: 5,
},
},
},
],
},
};
return (
<>
<Bullet {...config} {...extProps} data={parseData} />
</>
);
});

@ -0,0 +1,88 @@
import { useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { Bullet } from '@ant-design/plots';
import { sortBy } from '../utils/commons';
import { dataFieldAlias } from './../libs/ht';
export default observer((props) => {
const { dataSource, ...extProps } = props;
// , number -> array
const dataParser = (origin) => {
const { measureField, rangeField, targetField } = extProps;
const maxKPI = Math.max(...(origin || []).map((ele) => ele[targetField]));
const maxValue = Math.max(...(origin || []).map((ele) => ele[measureField]));
const _max = Math.max(maxKPI, maxValue);
const sortData = origin.sort(sortBy(measureField));
//
const _parseData = sortData?.map((ele) => ({ ...ele, [rangeField]: [0, Math.ceil(_max / 0.9)], [measureField]: [ele[measureField]] }));
return _parseData;
};
const [parseData, setParseData] = useState([]);
useEffect(() => {
setParseData(dataParser(dataSource));
return () => {};
}, [extProps.measureField, dataSource]);
const config = {
color: {
range: ['#FFbcb8', '#FFe0b0', '#bfeec8'],
measure: '#5B8FF9',
target: '#FF9845',
},
label: {
// target: true,
// measure: {
// position: 'middle',
// style: {
// fill: '#fff',
// },
// },
},
xAxis: {
line: null,
},
yAxis: false,
// legend
legend: {
custom: true,
position: 'bottom',
items: [
{
value: '实际',
name: '实际',
marker: {
symbol: 'square',
style: {
fill: '#5B8FF9',
r: 5,
},
},
},
{
value: '目标',
name: '目标',
marker: {
symbol: 'line',
style: {
stroke: '#FF9845', // '#39a3f4',
r: 5,
},
},
},
],
},
// alias
tooltip: {
customItems: (originalItems) => {
// process originalItems,
return originalItems.map((ele) => ({ ...ele, name: dataFieldAlias[ele.name]?.alias || ele.name }));
},
},
};
return (
<>
<Bullet {...config} {...extProps} data={parseData} />
</>
);
});

@ -3,7 +3,7 @@ import { Card, Statistic, Progress } from 'antd';
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
export default observer((props) => {
const valueStyle = { color: props.VSrate < 0 ? '#cf1322' : '#3f8600' };
const valueStyle = { color: props.VSrate < 0 ? '#3f8600' : '#cf1322' };
const VSIcon = () => (props.VSrate < 0 ? <ArrowDownOutlined /> : <ArrowUpOutlined />);
return (
<Card>

@ -0,0 +1,67 @@
import { observer } from 'mobx-react';
import { Waterfall } from '@ant-design/plots';
import { dataFieldAlias } from './../libs/ht';
export default observer((props) => {
const { dataSource, line, title, ...extProps } = props;
const yMax = Math.max(line?.value || 0, ...dataSource.map((ele) => ele[extProps.yField]));
const annotationsLine = line
? [
{
type: 'text',
position: ['start', line.value],
content: `${line.label} ${line.value / 1000} K`,
// offsetX: -15,
style: {
fill: '#F4664A',
textBaseline: 'bottom',
},
},
{
type: 'line',
start: [-10, line.value],
end: ['max', line.value],
style: {
stroke: '#F4664A',
lineDash: [2, 2],
},
},
]
: [];
const config = {
padding: 'auto',
appendPadding: [20, 0, 0, 0],
/** 展示总计 */
total: {
label: `${title}`,
style: {
fill: '#96a6a6',
},
},
legend: false,
/** 数据标签展示模式:绝对值 */
labelMode: 'absolute',
label: {
style: {
fontSize: 12,
},
},
annotations: [...annotationsLine],
meta: {
...extProps.mergeMeta,
[extProps.yField]: {
...extProps.mergeMeta[extProps.yField],
max: Math.ceil(yMax / 0.95),
alias: dataFieldAlias[extProps.yField]?.alias || extProps.yField,
formatter: (v) => dataFieldAlias[extProps.yField]?.formatter(v) || v,
},
},
};
return (
<>
<Waterfall {...config} {...extProps} data={dataSource} />
</>
);
});

@ -41,27 +41,9 @@
"month": "@date('MM')",
"key": "@increment"
}
],
"inside|12": [
{
"SumML": "@float(999,9999,2,2)",
"SumMLKPIrate": "@float(0,70,2,2)",
"COLI_Department": "1,2,28,7",
"month|+1": -11,
"key": "@increment"
}
],
"outside|12": [
{
"SumML": "@float(999,9999,2,2)",
"SumMLKPIrate": "@float(0,70,2,2)",
"COLI_Department": "1,2,28,7",
"month|+1": -11,
"key": "@increment"
}
]
},
"get|/service-web/QueryData/GetTradeByType": {
"get|/service-web/QueryData/GetTradeOrderByType": {
"errcode": 0,
"errmsg": "",
"loading": false,
@ -71,18 +53,21 @@
"OrderTypeSN": "@integer(1,10)",
"OrderType": "@cname",
"OrderCount": "@integer(9,999)",
"OrderCountKPI": "@integer(100,1000)",
"CJCount": "@integer(9,999)",
"CJCountKPI": "@integer(100,1000)",
"CJPersonNum": "@integer(9,999)",
"YJLY": "@integer(99,9999)",
"CJrate": "@float(1,99,0,2)",
"CJrateKPI": "@float(1,99,0,2)",
"Ordervalue": "@integer(9,999)",
"SumML": "@integer(99,9999)",
"SumMLKPI": "@integer(99,9999)",
"SumMLrate": "@float(-20,100,0,2)",
"SumMLKPIrate": "@float(20,100,0,2)",
"SumOrder": "@integer(9,999)",
"SumOrderrate": "@float(-20,20,0,2)",
"SumOrderKPIrate": "@float(20,100,0,2)",
"OrderCountVSrate": "@float(-20,20,0,2)",
"OrderCountKPIrate": "@float(20,100,0,2)",
"SumPersonNum": "@integer(99,9999)",
"SumPersonNumrate": "@float(-50,1,0,2)",
"SumPersonNumKPIrate": "@float(20,100,0,2)",
@ -93,5 +78,30 @@
"result2": [
{}
]
},
"get|/service-web/QueryData/GetTradeProcess": {
"errcode": 0,
"errmsg": "",
"loading": false,
"data": null,
"result1|24": [
{
"groups|1": "@pick([\"inside\",\"outside\"])",
"SumML": "@increment(1000)",
"SumMLVSrate": "@float(0,70,2,2)",
"SumMLKPI": "@integer(1000,10000)",
"SumMLKPIrate": "@float(0,70,2,2)",
"month": "@date('2023-MM')",
"SumOrder": "@integer(9,999)",
"SumOrderVSrate": "@float(-20,20,0,2)",
"SumOrderKPIrate": "@float(20,100,0,2)",
"SumPersonNum": "@integer(99,9999)",
"SumPersonNumrate": "@float(-50,1,0,2)",
"SumPersonNumKPIrate": "@float(20,100,0,2)",
"key": "@increment"
}],
"result2": [
{}
]
}
}

@ -1,12 +1,12 @@
import { makeAutoObservable, runInAction } from 'mobx';
import * as req from '../utils/request';
import { isEmpty } from '../utils/commons';
import { isEmpty, sortBy } from '../utils/commons';
/**
* 计算变化值
*/
const calcRate = (r1, r2) => {
//
//
};
class Trade {
@ -43,15 +43,20 @@ class Trade {
fetchTradeDataByMonth() {
this.sideData.loading = true;
req.fetchJSON('/service-web/QueryData/GetTradeByMonth').then((json) => {
req.fetchJSON('/service-web/QueryData/GetTradeProcess').then((json) => {
// req.fetchJSON('/service-web/QueryData/GetTradeByMonth').then((json) => {
if (json.errcode === 0) {
runInAction(() => {
const _sideData = json.data.reduce((r, v) => {
(r[v.biz_side] || (r[v.biz_side] = [])).push(v);
const sortResult = json.result1.sort(sortBy('month'));
const groupsData = sortResult.reduce((r, v) => {
(r[v.groups] || (r[v.groups] = [])).push(v);
return r;
}, {});
console.log(_sideData, '_sideData');
this.sideData = { loading: false, ...json };
console.log(groupsData, 'groupsData');
const kpi = { label: '', value: 1200000 }; // 标注KPI
this.sideData.loading = false;
this.sideData.dataSource = groupsData;
this.sideData.kpi = kpi;
});
}
});
@ -59,7 +64,7 @@ class Trade {
fetchTradeDataByType(orderType) {
this.topData[orderType] = { loading: true, dataSource: [] };
req.fetchJSON('/service-web/QueryData/GetTradeByType').then((json) => {
req.fetchJSON('/service-web/QueryData/GetTradeOrderByType').then((json) => {
if (json.errcode === 0) {
runInAction(() => {
this.topData[orderType].loading = false;
@ -71,8 +76,9 @@ class Trade {
}
summaryData = { loading: false, dataSource: [] };
sideData = { loading: false };
sideData = { loading: false, dataSource: {}, kpi: {}, };
topData = {};
defaultDataSubject = 'CJCount';
}
export default Trade;

@ -275,3 +275,10 @@ export function set_array_index(result) {
}
return result_array;
}
/**
* 排序
*/
export const sortBy = (key) => {
return (a, b) => (a[key] > b[key]) ? 1 : ((b[key] > a[key]) ? -1 : 0);
};

@ -1,12 +1,15 @@
import { useContext, useEffect } from 'react';
import { useContext, useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { Row, Col, Spin } from 'antd';
import { Row, Col, Spin, Space } from 'antd';
import { stores_Context } from '../config';
import { useNavigate } from 'react-router-dom';
import { SlackOutlined, SketchOutlined, AntCloudOutlined, RedditOutlined, GithubOutlined, ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import StatisticCard from './../charts/StatisticCard';
import Bullet from './../charts/Bullet';
import StatisticCard from '../components/StatisticCard';
import Bullet from '../components/Bullet';
import Waterfall from '../components/Waterfall';
import DataFieldRadio from './../components/DateFieldRadio';
import DatePickerCharts from './../components/search/DatePickerCharts';
import { empty } from './../utils/commons';
import './home.css';
@ -28,7 +31,6 @@ export default observer(() => {
TradeStore.fetchTradeDataByMonth();
for (const iterator of topSeries) {
TradeStore.fetchTradeDataByType(iterator.key);
// TradeStore.fetchTradeDataByType('Area');
}
}
return () => {};
@ -41,17 +43,50 @@ export default observer(() => {
xs: { span: 24 },
};
const BulletConfig = {
measureField: 'CJCount', //
rangeField: 'CJCountRange', //
targetField: 'CJCountKPI', //
const layoutProps3 = {
gutter: { xs: 8, sm: 8, lg: 16 },
lg: { span: 8 },
sm: { span: 12 },
xs: { span: 24 },
};
const [valueKey, setValueKey] = useState('SumML');
const [BulletConfig, setBulletConfig] = useState({
measureField: 'SumML', //
rangeField: 'SumMLRange', //
targetField: 'SumMLKPI', //
xField: 'OrderType',
});
const handleChangeValueKey = (key) => {
setValueKey(key);
setBulletConfig({
measureField: key,
rangeField: `${key}Range`,
targetField: `${key}KPI`,
xField: 'OrderType',
});
};
const WaterfallConfig = {
xField: 'month',
yField: 'SumML',
mergeMeta: {
month: {
alias: '月份',
},
},
label: {
formatter: (v) => ((v.SumML / sideData.kpi.value) * 100).toFixed(2) + '%',
},
};
return (
<>
<h2>年度业绩</h2>
<section>
<Space>
<h2>年度业绩</h2>
<DatePickerCharts hide_vs={true} />
</Space>
<Spin spinning={summaryData.loading}>
<Row gutter={layoutProps.gutter}>
{summaryData.dataSource.map((item) => (
@ -62,13 +97,25 @@ export default observer(() => {
</Row>
</Spin>
</section>
<section>
<h3>市场进度</h3>
<section></section>
<h3>TOP
<div style={{float: 'right', display: 'inline-block'}}>
<DataFieldRadio />
</div></h3>
<Spin spinning={sideData.loading}>
<Row gutter={layoutProps3.gutter}>
{Object.keys(sideData.dataSource).map((key) => (
<Col {...layoutProps3} key={key}>
<Waterfall {...WaterfallConfig} title={key} dataSource={sideData.dataSource[key]} line={sideData.kpi} />
</Col>
))}
</Row>
</Spin>
</section>
<section>
<Space>
<h3>TOP</h3>
<div>
<DataFieldRadio value={valueKey} onChange={handleChangeValueKey} />
</div>
</Space>
<Row gutter={layoutProps.gutter}>
{topSeries.map((item) => (
<Col {...layoutProps} key={item.key}>

Loading…
Cancel
Save