diff --git a/src/components/LineWithKPI.jsx b/src/components/LineWithKPI.jsx new file mode 100644 index 0000000..afdb70d --- /dev/null +++ b/src/components/LineWithKPI.jsx @@ -0,0 +1,71 @@ +import { observer } from 'mobx-react'; +import { DualAxes } from '@ant-design/plots'; +import { merge, pick, 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 { config, dataSource, ...extProps } = props; + const kpiKey = dataFieldAlias[config.yField]?.nestkey?.v; + const _data = uniqueByKey(dataSource, config.xField, true); // debug: + const mergeConfig = merge( + // color: ['#1979C9', '#F4664A', '#FAAD14'], + + // padding: 'auto', + // xField: 'groupDateVal', + // yField: 'SumML', + // seriesField: 'groupsLabel', + { + ...pick(config, ['padding', 'xField', 'seriesField']), + yField: [config.yField, kpiKey], + legend: false, + xAxis: { + type: 'cat', + }, + meta: { ...cloneDeep(dataFieldAlias) }, + geometryOptions: [ + { + geometry: 'line', + smooth: true, + point: { + size: 4, + shape: 'cicle', + }, + }, + { + geometry: 'line', + smooth: true, + point: { + size: 4, + shape: 'cicle', + }, + lineStyle: { + lineWidth: 1, + lineDash: [5, 5], + }, + color: '#F4664A', + }, + ], + } + ); + return ; +}); diff --git a/src/libs/ht.js b/src/libs/ht.js index b8b1cbf..2687c07 100644 --- a/src/libs/ht.js +++ b/src/libs/ht.js @@ -107,7 +107,7 @@ export const dataFieldAlias = dataFieldOptions.reduce( (a, c) => ({ ...a, [c.value]: { ...c, alias: c.label, formatter: (v) => c.formatter(v) }, - [c.nestkey.v]: { ...c, value: c.nestkey.v, alias: `${c.label}目标`, formatter: (v) => c.formatter(v) }, + [c.nestkey.v]: { ...c, value: c.nestkey.v, alias: `${c.label}目标`, label: `${c.label}目标`, formatter: (v) => c.formatter(v) }, }), {} ); diff --git a/src/utils/commons.js b/src/utils/commons.js index 6a9adff..a90a0f2 100644 --- a/src/utils/commons.js +++ b/src/utils/commons.js @@ -289,7 +289,7 @@ export const sortBy = (key) => { export function merge(...objects) { const isDeep = objects.some(obj => obj !== null && typeof obj === 'object'); - const result = objects[0] ?? {}; + const result = objects[0] || (isDeep ? {} : objects[0]); for (let i = 1; i < objects.length; i++) { const obj = objects[i]; @@ -301,7 +301,7 @@ export function merge(...objects) { if (isDeep) { if (Array.isArray(val)) { - result[key] = [...result[key] || [], ...val]; + result[key] = [].concat(Array.isArray(result[key]) ? result[key] : [result[key]], val); } else if (typeof val === 'object') { result[key] = merge(result[key], val); } else { diff --git a/src/views/Detail.jsx b/src/views/Detail.jsx index dc97d35..8ba1c80 100644 --- a/src/views/Detail.jsx +++ b/src/views/Detail.jsx @@ -1,11 +1,12 @@ import { useContext, useEffect, useMemo } from 'react'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; import { stores_Context } from '../config'; import { Row, Col, Spin, Space, Radio, Tabs, Table } from 'antd'; import { empty } from '../utils/commons'; import { dataFieldAlias } from '../libs/ht'; import Scatter from './../components/Scatter'; import SearchForm from './../components/search/SearchForm'; +import { Histogram } from '@ant-design/plots'; export default observer((props) => { const { date_picker_store: searchFormStore, DistributionStore } = useContext(stores_Context); @@ -46,35 +47,44 @@ export default observer((props) => { position: 'right-top', }, }; - return ( - <> - - {/* style={{ margin: '-16px -8px', padding: 0 }} */} - - { - detailRefresh(obj); - }} - /> - - + const HistogramConfig = { + binField: 'personNum', + binWidth: 1, + }; + return ( + <> + + {/* style={{ margin: '-16px -8px', padding: 0 }} */} + + { + detailRefresh(obj); + }} + /> + + -
- - - -
- - ); +
+ + + +
+ {/*
+ + + +
*/} + + ); }); diff --git a/src/views/Home.jsx b/src/views/Home.jsx index 409955b..92fe06c 100644 --- a/src/views/Home.jsx +++ b/src/views/Home.jsx @@ -1,12 +1,13 @@ import { useContext, useEffect, useState } from 'react'; import { observer } from 'mobx-react'; import { Row, Col, Spin, Space, Radio } from 'antd'; -import { CheckCircleTwoTone, MoneyCollectTwoTone, FlagTwoTone, SmileTwoTone, } from '@ant-design/icons'; +import { CheckCircleTwoTone, MoneyCollectTwoTone, FlagTwoTone, SmileTwoTone } from '@ant-design/icons'; import { stores_Context } from '../config'; import { useNavigate } from 'react-router-dom'; import StatisticCard from '../components/StatisticCard'; import Bullet from '../components/BulletWithSort'; import Waterfall from '../components/Waterfall'; +import LineMix from '../components/LineWithKPI'; import DataFieldRadio from '../components/DataFieldRadio'; import { datePartOptions } from './../components/DateGroupRadio/date'; import SearchForm from './../components/search/SearchForm'; @@ -30,7 +31,7 @@ export default observer(() => { // const navigate = useNavigate(); const { TradeStore, date_picker_store: searchFormStore } = useContext(stores_Context); const { sideData, summaryData, BuData, topData, timeData, timeLineKey } = TradeStore; - const { formValues, } = searchFormStore; + const { formValues } = searchFormStore; useEffect(() => { if (empty(summaryData.dataSource)) { @@ -92,7 +93,7 @@ export default observer(() => { }, }, label: { - formatter: (v) => summaryData.kpi.value === 0 ? (dataFieldAlias.SumML?.formatter(v.SumML) || v.SumML) : ((v.SumML / summaryData.kpi.value) * 100).toFixed(2) + '%', + formatter: (v) => (summaryData.kpi.value === 0 ? dataFieldAlias.SumML?.formatter(v.SumML) || v.SumML : ((v.SumML / summaryData.kpi.value) * 100).toFixed(2) + '%'), }, }; @@ -108,7 +109,8 @@ export default observer(() => { autoHide: true, autoRotate: false, }, - } + }, + legend: false, }; const lineConfigSet = { @@ -123,10 +125,11 @@ export default observer(() => { smooth: true, point: { size: 4, - shape: "cicle", + shape: 'cicle', }, legend: false, - meta: { ...cloneDeep(dataFieldAlias) + meta: { + ...cloneDeep(dataFieldAlias), // [extProps.yField]: { // alias: dataFieldAlias[extProps.yField]?.alias || extProps.yField, // formatter: (v) => dataFieldAlias[extProps.yField]?.formatter(v) || v, @@ -139,7 +142,7 @@ export default observer(() => { const handleChangetimeDataField = (key) => { setTimeDataField(key); setLineConfig({ - ...lineConfig, + ...cloneDeep(lineConfig), yField: key, tooltip: { customItems: (originalItems) => { @@ -151,7 +154,7 @@ export default observer(() => { }); }; const [dateField, setDateField] = useState(timeLineKey); - const handleChangeDateType = ({target: {value}}) => { + const handleChangeDateType = ({ target: { value } }) => { setDateField(value); TradeStore.setTimeLineKey(value); if (!isEmpty(TradeStore.searchPayloadHome)) { @@ -204,6 +207,7 @@ export default observer(() => { + {/* */}
@@ -212,7 +216,7 @@ export default observer(() => { -

{`各事业部总业绩`}

+

{`各事业部总业绩`}

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