todo: 双线图: KPI

feature/2.0-sales-trade
Lei OT 2 years ago
parent e3cbbee6b7
commit 1c78a0c64f

@ -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 <DualAxes {...mergeConfig} data={[_data, _data]} />;
});

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

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

@ -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 (
<>
<Row gutter={16} style={{ margin: '-16px -8px' }}>
{/* style={{ margin: '-16px -8px', padding: 0 }} */}
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'dates', 'country'],
fieldProps: {
DepartmentList: { show_all: true },
WebCode: { show_all: true },
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
detailRefresh(obj);
}}
/>
</Col>
</Row>
const HistogramConfig = {
binField: 'personNum',
binWidth: 1,
};
return (
<>
<Row gutter={16} style={{ margin: '-16px -8px' }}>
{/* style={{ margin: '-16px -8px', padding: 0 }} */}
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'dates', 'country'],
fieldProps: {
DepartmentList: { show_all: true },
WebCode: { show_all: true },
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
detailRefresh(obj);
}}
/>
</Col>
</Row>
<section>
<Spin spinning={detailData.loading}>
<Scatter {...ScatterConfig} dataSource={scatterDays} />
</Spin>
</section>
</>
);
<section>
<Spin spinning={detailData.loading}>
<Scatter {...ScatterConfig} dataSource={scatterDays} />
</Spin>
</section>
{/* <section>
<Spin spinning={detailData.loading}>
<Histogram {...HistogramConfig} data={scatterDays} />
</Spin>
</section> */}
</>
);
});

@ -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(() => {
</Space>
<Spin spinning={timeData.loading}>
<Line {...lineConfig} data={timeData.dataSource} />
{/* <LineMix dataSource={timeData.dataSource} config={lineConfig} /> */}
</Spin>
</section>
<section>
@ -212,7 +216,7 @@ export default observer(() => {
<Row gutter={layoutProps3.gutter}>
<Col {...layoutProps3}>
<Bullet {...BUConfig} dataSource={BuData?.dataSource || []} />
<h3 style={{ textAlign: 'center' }}>{`各事业部总业绩`}</h3>
<h3 style={{ textAlign: 'center' }}>{`各事业部总业绩`}</h3>
</Col>
{Object.keys(sideData.dataSource).map((key) => (
<Col {...layoutProps3} key={key}>

Loading…
Cancel
Save