diff --git a/src/charts/Bullet.jsx b/src/charts/Bullet.jsx deleted file mode 100644 index 985093c..0000000 --- a/src/charts/Bullet.jsx +++ /dev/null @@ -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 ( - <> - - - ); -}); diff --git a/src/components/Bullet.jsx b/src/components/Bullet.jsx new file mode 100644 index 0000000..e2cc0dd --- /dev/null +++ b/src/components/Bullet.jsx @@ -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 ( + <> + + + ); +}); diff --git a/src/charts/StatisticCard.jsx b/src/components/StatisticCard.jsx similarity index 88% rename from src/charts/StatisticCard.jsx rename to src/components/StatisticCard.jsx index 0de1fcc..9a771f8 100644 --- a/src/charts/StatisticCard.jsx +++ b/src/components/StatisticCard.jsx @@ -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 ? : ); return ( diff --git a/src/components/Waterfall.jsx b/src/components/Waterfall.jsx new file mode 100644 index 0000000..b23c2d1 --- /dev/null +++ b/src/components/Waterfall.jsx @@ -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 ( + <> + + + ); +}); diff --git a/src/mock/2.0/trade.json b/src/mock/2.0/trade.json index 69b9bf8..d78ed3d 100644 --- a/src/mock/2.0/trade.json +++ b/src/mock/2.0/trade.json @@ -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": [ + {} + ] } } diff --git a/src/stores/Trade.js b/src/stores/Trade.js index eaffc90..db40f98 100644 --- a/src/stores/Trade.js +++ b/src/stores/Trade.js @@ -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 }; - topData = { }; + sideData = { loading: false, dataSource: {}, kpi: {}, }; + topData = {}; + defaultDataSubject = 'CJCount'; } export default Trade; diff --git a/src/utils/commons.js b/src/utils/commons.js index 14c35c5..5cde768 100644 --- a/src/utils/commons.js +++ b/src/utils/commons.js @@ -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); +}; diff --git a/src/views/Home.jsx b/src/views/Home.jsx index 16d138a..09de37a 100644 --- a/src/views/Home.jsx +++ b/src/views/Home.jsx @@ -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'; @@ -16,10 +19,10 @@ export default observer(() => { const { sideData, summaryData, topData } = TradeStore; const topSeries = [ - { key: 'Country', label: '国籍'}, - { key: 'Area', label: '目的地'}, - { key: 'Sales', label: '顾问'}, - { key: 'GuestGroupType', label: '客群类别'}, + { key: 'Country', label: '国籍' }, + { key: 'Area', label: '目的地' }, + { key: 'Sales', label: '顾问' }, + { key: 'GuestGroupType', label: '客群类别' }, ]; useEffect(() => { @@ -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 ( <> -

年度业绩

+ +

年度业绩

+ +
{summaryData.dataSource.map((item) => ( @@ -62,18 +97,30 @@ export default observer(() => {
-

市场进度

-
-

TOP -
- -

+

市场进度

+ + + {Object.keys(sideData.dataSource).map((key) => ( + + + + ))} + + +
+
+ +

TOP

+
+ +
+
{topSeries.map((item) => ( -

{item.label}

+

{item.label}