From be2298c5bc855005cbcb02d71ea218eed09764ac Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 20 Oct 2023 11:28:44 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BB=E9=A1=B5:=20=E8=B5=B0=E5=8A=BF:=20?= =?UTF-8?q?=E4=BC=A0=E7=BB=9F+=E5=95=86=E5=8A=A1=E7=9A=84=E6=80=BB?= =?UTF-8?q?=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MixTBWithKPI.jsx | 139 ++++++++++++++++++++++++++++++ src/components/StatisticCard2.jsx | 2 +- src/stores/Trade.js | 41 ++++++--- src/views/Home.jsx | 12 +-- 4 files changed, 173 insertions(+), 21 deletions(-) create mode 100644 src/components/MixTBWithKPI.jsx diff --git a/src/components/MixTBWithKPI.jsx b/src/components/MixTBWithKPI.jsx new file mode 100644 index 0000000..1500eee --- /dev/null +++ b/src/components/MixTBWithKPI.jsx @@ -0,0 +1,139 @@ +import { observer } from 'mobx-react'; +import { Mix } from '@ant-design/plots'; +import { merge, isEmpty, groupBy, cloneDeep } 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, summaryData: areaData, ...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 }); + } + return r; + }, []); + const dataColors = ['#598cf3', '#69deae', '#FAAD14']; + const colorSets = Object.keys(seriesData) + .sort() + .reduce((obj, k, i) => ({ ...obj, [k]: dataColors[i] }), {}); + const { xField, yField, seriesField, tooltip, ...extConfig } = config; + const lineConfig = merge( + { + data: [...dataSource, ...KPIData], + xField, + yField, + seriesField, + point: { + size: 4, + shape: 'cicle', + }, + xAxis: false, + yAxis: false, + meta: { + [yField]: { + sync: true, + } + }, + // color: ['#598cf3', '#69deae', '#F4664A', '#FAAD14'], + color: (item) => { + const thisSeries = item[config.seriesField]; + return thisSeries.includes('目标') ? '#F4664A' : colorSets[thisSeries]; + }, + lineStyle: (data) => { + // console.log(data); + if (data[config.seriesField].includes('目标')) { + return { + lineDash: [4, 4], + opacity: 0.5, + }; + } + + return { + opacity: 1, + }; + }, + }, + extConfig + ); + const MixConfig = { + appendPadding: 8, + syncViewPadding: true, + tooltip: { + shared: true, + customItems: (originalItems) => { + // process originalItems, + const items = originalItems.map((ele) => ({ ...ele, name: ele.data?.extraLine ? ele.name : `${ele.name} ${dataFieldAlias[yField]?.alias || yField}` })); + return items; + }, + }, + plots: [ + { + type: 'area', + options: { + data: areaData, + xField, + yField, + seriesField, + xAxis: false, + meta: merge( + { + ...cloneDeep(dataFieldAlias), + }, + { [xField]: { sync: true }, [yField]: { sync: true } } + ), + // color: '#b32b19', + color: '#f58269', + smooth: true, + areaStyle: () => { + return { + fill: 'l(270) 0:#ffffff 0.25:#f8e8e7 0.5:#fac9bd 0.75:#f7a593', + }; + }, + label: { + offsetY: -8, + }, + annotations: areaData.map((d) => { + return { + type: 'dataMarker', + position: d, + point: { + style: { + stroke: '#F4664A', + lineWidth: 1.5, + }, + }, + }; + }), + }, + }, + { + type: 'line', + options: lineConfig, + }, + ], + }; + return ; +}); diff --git a/src/components/StatisticCard2.jsx b/src/components/StatisticCard2.jsx index a625377..849f3d8 100644 --- a/src/components/StatisticCard2.jsx +++ b/src/components/StatisticCard2.jsx @@ -57,7 +57,7 @@ export default observer((props) => { range: [ '#E8EDF3', '#FFF3E1'], // range: ['#FFbcb8', '#FFe0b0', '#bfeec8'], measure: ['#5B8FF9', '#61DDAA'], - target: '#39a3f4', + target: kpiVal ? '#FF9845' : '#5B8FF9', }, label: { measure: { diff --git a/src/stores/Trade.js b/src/stores/Trade.js index c386d90..037b5d3 100644 --- a/src/stores/Trade.js +++ b/src/stores/Trade.js @@ -1,6 +1,6 @@ import { makeAutoObservable, runInAction, toJS } from 'mobx'; import * as req from '../utils/request'; -import { isEmpty, sortBy, pick, merge, fixTo2Decimals, groupBy, sortKeys } from '../utils/commons'; +import { isEmpty, sortBy, pick, merge, fixTo2Decimals, groupBy, sortKeys, fixToInt } from '../utils/commons'; import { dataFieldAlias } from './../libs/ht'; class Trade { @@ -88,15 +88,14 @@ class Trade { Object.assign(queryData, { groupDateType: this.timeLineKey }); const multiData = await this.fetchTradeDataAll(queryData); const { traditional, biz } = multiData.result1; - // console.log(biz, 'mmmmmmmm', queryData); - const dataT = traditional.map(ele => ({...ele, groupsLabel: '传统'})); - const dataB = biz.map(ele => ({...ele, groupsLabel: '商务'})); - const mergeData = [].concat(dataT, dataB); + // console.log(biz, 'mmmmmmmm', queryData, multiData); + const mergeData = [].concat(traditional, biz); const dateKeyData = groupBy(mergeData, ele => ele.groupDateVal); const sortByDateKey = Object.values(sortKeys(dateKeyData)).reduce( (a, b) => a.concat(b), []); runInAction(() => { this.timeData.loading = false; this.timeData.dataSource = sortByDateKey; + this.timeData.origin = multiData.result1; }); } @@ -214,15 +213,34 @@ class Trade { const traditional = await this.fetchTradeData(queryData); const biz = await this.fetchTradeDataBiz(queryData); const rr = ['result1', 'result2'].reduce((res, resKey) => { - const mergeItem = { traditional: traditional[resKey], biz: biz[resKey] }; + const mergeItem = { + traditional: traditional[resKey].map(ele => ({...ele, groupsLabel: `传统 `})), // ${ele.groupsLabel} + biz: biz[resKey].map(ele => ({...ele, groupsLabel: `商务 `})), // ${ele.groupsLabel} + }; const mergeRes = [].concat(traditional[resKey], biz[resKey]); - // const kpiKeys = []; const groupsData = mergeRes.reduce((r, v) => { if (v.groupsLabel ) { (r[v.groupsLabel] || (r[v.groupsLabel] = [])).push(v); } return r; }, {}); + const ByDate = sortKeys(groupBy(mergeRes, ele => ele.groupDateVal)); + const summaryRows = Object.keys(ByDate).map(dateVal => { + const sumFields = ['ConfirmOrder', 'SumOrder', 'SumML', 'transactions', 'SumPersonNum'].reduce( + (r, skey) => ({ ...r, [skey]: ByDate[dateVal].reduce((a, c) => a + c[skey], 0) }), + ByDate[dateVal]?.[0] || {} + ); + const pickFields = pick(ByDate[dateVal], Object.keys(ByDate[dateVal]).filter(_k => _k.endsWith('KPIvalue') )); + return {...sumFields, ...pickFields}; + }, {}).map(row => ({...row, + ConfirmRates: row.SumOrder ? fixTo2Decimals((row.ConfirmOrder/row.SumOrder)*100) : 0, + MLKPIrates: row.MLKPIvalue ? fixTo2Decimals((row.SumML/row.MLKPIvalue)*100) : 0, + OrderValue: row.SumOrder ? fixToInt((row.SumML/row.SumOrder)) : 0, + groupsLabel: `总 `, // ${row.groupsLabel} + ConfirmOrderKPIrates: row.ConfirmOrderKPIvalue ? fixTo2Decimals((row.ConfirmOrder/row.ConfirmOrderKPIvalue)*100) : 0, + OrderKPIrates: row.OrderKPIvalue ? fixTo2Decimals((row.SumOrder/row.OrderKPIvalue)*100) : 0, + // ConfirmRatesKPIrates: 0, // todo: + })); const summary = Object.keys(groupsData).map(groupsKey => { return ['ConfirmOrder', 'SumOrder', 'SumML', 'transactions', 'SumPersonNum'].reduce( (r, skey) => ({ ...r, [skey]: groupsData[groupsKey].reduce((a, c) => a + c[skey], 0) }), @@ -231,9 +249,10 @@ class Trade { }).map(row => ({...row, ConfirmRates: row.SumOrder ? fixTo2Decimals((row.ConfirmOrder/row.SumOrder)*100) : 0, MLKPIrates: row.MLKPIvalue ? fixTo2Decimals((row.SumML/row.MLKPIvalue)*100) : 0, - // todo: + OrderValue: row.SumOrder ? fixToInt((row.SumML/row.SumOrder)) : 0, + groupsLabel: `总 `, // ${row.groupsLabel} })); - return Object.assign(res, { [resKey]: Object.assign({}, mergeItem, { summary }) }); + return Object.assign(res, { [resKey]: Object.assign({}, mergeItem, { summary, summaryRows }) }); }, {}); return rr; } @@ -273,7 +292,7 @@ class Trade { resetData = () => { this.summaryData = { loading: false, dataSource: [], kpi: {}, }; - this.timeData = { loading: false, dataSource: [] }; + this.timeData = { loading: false, dataSource: [], origin: {} }; this.BuData = { loading: false, dataSource: [] }; this.sideData = { loading: false, dataSource: {}, monthData: [], yearData: [] }; this.topData = {}; @@ -283,7 +302,7 @@ class Trade { searchPayloadHome = {}; summaryData = { loading: false, dataSource: [], kpi: {}, }; - timeData = { loading: false, dataSource: [] }; + timeData = { loading: false, dataSource: [], origin: {} }; BuData = { loading: false, dataSource: [] }; sideData = { loading: false, dataSource: {}, monthData: [], yearData: [] }; topData = {}; diff --git a/src/views/Home.jsx b/src/views/Home.jsx index a8f4660..0c174c9 100644 --- a/src/views/Home.jsx +++ b/src/views/Home.jsx @@ -9,6 +9,7 @@ import StatisticCard2 from '../components/StatisticCard2'; import Bullet from '../components/BulletWithSort'; import Waterfall from '../components/Waterfall'; import LineWithKPI from '../components/LineWithKPI'; +import MixTBWithKPI from './../components/MixTBWithKPI'; import Donut from './../components/Donut'; import DataFieldRadio from '../components/DataFieldRadio'; import { datePartOptions } from './../components/DateGroupRadio/date'; @@ -152,13 +153,6 @@ export default observer(() => { setLineConfig({ ...cloneDeep(lineConfig), yField: key, - tooltip: { - customItems: (originalItems) => { - // process originalItems, - const items = originalItems.map((ele) => ({ ...ele, name: ele.data?.extraLine ? ele.name : `${ele.name} ${dataFieldAlias[key]?.alias || key}` })); - return items; - }, - }, }); }; const [dateField, setDateField] = useState(timeLineKey); @@ -219,8 +213,8 @@ export default observer(() => { - {/* */} - + {/* */} +