feat: 增加国内外占比; perf: 首页: 走势图中的KPI线;

feature/2.0-sales-trade
Lei OT 2 years ago
parent 1d76cbcc74
commit 48a4010fc9

@ -0,0 +1,84 @@
import { observer } from 'mobx-react';
import { Pie, measureTextWidth } from '@ant-design/plots';
import { fixTo2Decimals, merge } from '../utils/commons';
import { dataFieldAlias } from './../libs/ht';
export default observer((props) => {
const { dataSource, title, ...extProps } = props;
const renderStatistic = (containerWidth, text, style) => {
const { width: textWidth, height: textHeight } = measureTextWidth(text, style);
const R = containerWidth / 2; // r^2 = (w / 2)^2 + (h - offsetY)^2
let scale = 1;
if (containerWidth < textWidth) {
scale = Math.min(Math.sqrt(Math.abs(Math.pow(R, 2) / (Math.pow(textWidth / 2, 2) + Math.pow(textHeight, 2)))), 1);
}
const textStyleStr = `width:${containerWidth}px;`;
return `<div style="${textStyleStr};font-size:${scale}em;line-height:${scale < 1 ? 1 : 'inherit'};">${text}</div>`;
};
const config = merge(
{
appendPadding: 10,
// angleField: 'value',
// colorField: 'type',
radius: 0.7,
innerRadius: 0.65,
label: {
type: 'inner',
offset: '-50%',
autoRotate: false,
// content: '{value}',
content: ({ percent }) => `${fixTo2Decimals(percent * 100)}%`,
style: {
textAlign: 'center',
fontSize: 14,
},
},
interactions: [{ type: 'element-selected' }, { type: 'element-active' }, { type: 'pie-statistic-active' }],
statistic: {
title: {
offsetY: -4,
customHtml: (container, view, datum) => {
const { width, height } = container.getBoundingClientRect();
const d = Math.sqrt(Math.pow(width / 2, 2) + Math.pow(height / 2, 2));
const text = datum ? datum[extProps.colorField] : (title || '');
return renderStatistic(d, text, {
fontSize: '28px',
});
},
},
content: {
// style: {
// whiteSpace: 'pre-wrap',
// overflow: 'hidden',
// textOverflow: 'ellipsis',
// },
// content: title || '',
offsetY: 4,
style: {
fontSize: '28px',
},
customHtml: (container, view, datum, data) => {
const { width } = container.getBoundingClientRect();
const _sum = data.reduce((r, d) => r + d[extProps.angleField], 0);
const showVal = datum ? datum[extProps.angleField] : _sum;
const text = dataFieldAlias[extProps.angleField].formatter(showVal);
return renderStatistic(width, text, {
fontSize: '28px',
});
},
},
},
meta: {
[extProps.angleField]: {
alias: dataFieldAlias[extProps.angleField]?.alias || extProps.angleField,
formatter: (v) => dataFieldAlias[extProps.angleField]?.formatter(v) || v,
},
},
},
extProps
);
return <Pie {...config} data={dataSource} />;
});

@ -24,7 +24,7 @@ const uniqueByKey = (array, key, pickLast) => {
};
export default observer((props) => {
const { config, dataSource, ...extProps } = props;
const { dataSource, ...config } = props;
const kpiKey = dataFieldAlias[config.yField]?.nestkey?.v;
const _data = dataSource.reduce((r, v) => {
r.push(v);

@ -1,7 +1,7 @@
import { observer } from 'mobx-react';
import { Waterfall } from '@ant-design/plots';
import { dataFieldAlias } from './../libs/ht';
import { merge } from '../utils/commons';
import { fixTo4Decimals, merge } from '../utils/commons';
export default observer((props) => {
const { dataSource, line, title, ...extProps } = props;
@ -11,7 +11,7 @@ export default observer((props) => {
{
type: 'text',
position: ['start', line.value],
content: `${line.label} ${line.value / 10000}`,
content: `${line.label} ${fixTo4Decimals(line.value / 10000)}`,
// offsetX: -15,
style: {
fill: '#F4664A',

@ -1,3 +1,5 @@
import { fixTo4Decimals } from "../utils/commons";
/**
* 事业部
*/
@ -95,8 +97,8 @@ export const dateTypes = [
* 结果字段
*/
export const dataFieldOptions = [
{ label: '营收', value: 'transactions', formatter: (v) => `${v / 10000}`, nestkey: { p: 'transactionsKPIrates', v: 'transactionsKPIvalue' } },
{ label: '毛利', value: 'SumML', formatter: (v) => `${v / 10000}`, nestkey: { p: 'MLKPIrates', v: 'MLKPIvalue' } },
{ label: '营收', value: 'transactions', formatter: (v) => `${fixTo4Decimals(v / 10000)}`, nestkey: { p: 'transactionsKPIrates', v: 'transactionsKPIvalue' } },
{ label: '毛利', value: 'SumML', formatter: (v) => `${fixTo4Decimals(v / 10000)}`, nestkey: { p: 'MLKPIrates', v: 'MLKPIvalue' } },
{ label: '订单数', value: 'SumOrder', formatter: (v) => v, nestkey: { p: 'OrderKPIrates', v: 'OrderKPIvalue' } },
{ label: '成交数', value: 'ConfirmOrder', formatter: (v) => v, nestkey: { p: 'ConfirmOrderKPIrates', v: 'ConfirmOrderKPIvalue' } },
{ label: '成交率', value: 'ConfirmRates', formatter: (v) => `${v} %`, nestkey: { p: 'ConfirmRatesKPIrates', v: 'ConfirmRatesKPIvalue' } },

@ -54,9 +54,9 @@ class Trade {
/**
* 时间轴
*/
fetchTradeDataByDate(queryData) {
fetchTradeDataByDate(queryData = {}) {
this.timeData.loading = true;
queryData = queryData || this.searchPayloadHome;
queryData = Object.assign({}, this.searchPayloadHome, queryData); // queryData || this.searchPayloadHome;
queryData.groupType = queryData?.groupType || 'overview';
Object.assign(queryData, { groupDateType: this.timeLineKey });
this.fetchTradeData(queryData).then((json) => {
@ -123,10 +123,17 @@ class Trade {
}
return r;
}, {});
const summaryData = 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) }),
groupsData[groupsKey]?.[0] || {}
);
});
runInAction(() => {
this.sideData.loading = false;
this.sideData.dataSource = groupsData;
this.sideData.monthData = sortResult;
this.sideData.yearData = summaryData;
});
}
});
@ -197,7 +204,7 @@ class Trade {
this.summaryData = { loading: false, dataSource: [], kpi: {}, };
this.timeData = { loading: false, dataSource: [] };
this.BuData = { loading: false, dataSource: [] };
this.sideData = { loading: false, dataSource: {}, monthData: [] };
this.sideData = { loading: false, dataSource: {}, monthData: [], yearData: [] };
this.topData = {};
this.targetData = { targetTotal: {}, targetCountry: {}, targetGuest: {} };
this.targetTableProps.dataSource = [];
@ -207,7 +214,7 @@ class Trade {
summaryData = { loading: false, dataSource: [], kpi: {}, };
timeData = { loading: false, dataSource: [] };
BuData = { loading: false, dataSource: [] };
sideData = { loading: false, dataSource: {}, monthData: [] };
sideData = { loading: false, dataSource: {}, monthData: [], yearData: [] };
topData = {};
targetData = { targetTotal: {}, targetCountry: {}, targetGuest: {} };
targetTableProps = { loading: false, columns: [

@ -8,6 +8,7 @@ import StatisticCard from '../components/StatisticCard';
import Bullet from '../components/BulletWithSort';
import Waterfall from '../components/Waterfall';
import LineWithKPI from '../components/LineWithKPI';
import Donut from './../components/Donut';
import DataFieldRadio from '../components/DataFieldRadio';
import { datePartOptions } from './../components/DateGroupRadio/date';
import SearchForm from './../components/search/SearchForm';
@ -17,9 +18,9 @@ import { Line } from '@ant-design/charts';
import './home.css';
const topSeries = [
{ key: 'country', label: '国籍', graphVisible: true },
{ key: 'dept', label: '小组', graphVisible: true },
{ key: 'operator', label: '顾问', graphVisible: true },
{ key: 'country', label: '国籍', graphVisible: true },
{ key: 'GuestGroupType', label: '客群类别', graphVisible: false },
{ key: 'destination', label: '目的地', graphVisible: true },
];
@ -40,15 +41,23 @@ export default observer(() => {
return () => {};
}, []);
const [topSeriesSet, setTopSeriesSet] = useState(topSeries);
const [overviewFlag, setOverviewFlag] = useState(true);
const [groupTypeVal, setGroupTypeVal] = useState('overview');
const pageRefresh = (queryData) => {
const overviewFlag = queryData.DepartmentList.toLowerCase() === 'all' || queryData.DepartmentList.toLowerCase().includes(',');
queryData.groupType = overviewFlag ? 'overview' : 'dept';
const _overviewFlag = queryData.DepartmentList.toLowerCase() === 'all' || queryData.DepartmentList.toLowerCase().includes(',');
const groupType = _overviewFlag ? 'overview' : 'dept';
queryData.groupType = groupType;
setGroupTypeVal(groupType);
TradeStore.resetData();
TradeStore.fetchSummaryData(queryData);
TradeStore.fetchSummaryData(Object.assign({}, queryData, { groupType }));
TradeStore.fetchTradeDataByDate(queryData);
TradeStore.fetchTradeDataByBU(queryData);
TradeStore.fetchTradeDataByMonth(queryData);
for (const iterator of topSeries) {
const topSeriesF = _overviewFlag ? topSeries : topSeries.filter(ele => ele.key !== 'dept');
setTopSeriesSet(topSeriesF);
setOverviewFlag(_overviewFlag);
for (const iterator of topSeriesF) {
TradeStore.fetchTradeDataByType(iterator.key, queryData);
}
};
@ -156,12 +165,12 @@ export default observer(() => {
setDateField(value);
TradeStore.setTimeLineKey(value);
if (!isEmpty(TradeStore.searchPayloadHome)) {
TradeStore.fetchTradeDataByDate();
TradeStore.fetchTradeDataByDate({groupType: groupTypeVal});
}
};
return (
<>
<Row gutter={16} style={{ margin: '-16px -8px', position: 'sticky', top: 0, zIndex: 2, }}>
<Row gutter={16} style={{ margin: '-16px -8px', position: 'sticky', top: 0, zIndex: 10 }}>
{/* style={{ margin: '-16px -8px', padding: 0 }} */}
<Col className="gutter-row" span={24}>
<SearchForm
@ -205,16 +214,22 @@ export default observer(() => {
</Space>
<Spin spinning={timeData.loading}>
{/* <Line {...lineConfig} data={timeData.dataSource} /> */}
<LineWithKPI dataSource={timeData.dataSource} config={lineConfig} />
<LineWithKPI dataSource={timeData.dataSource} {...lineConfig} />
</Spin>
</section>
<section>
<h3>市场进度</h3>
<h3>市场</h3>
<Spin spinning={BuData.loading}>
<Row gutter={layoutProps3.gutter}>
<Col {...layoutProps3}>
{overviewFlag ? (
<>
<Bullet {...BUConfig} dataSource={BuData?.dataSource || []} />
<h3 style={{ textAlign: 'center' }}>{`各事业部总业绩`}</h3>
</>
) : (
<><Donut {...{angleField: 'SumML', colorField: 'groupsLabel'}} title={formValues.DepartmentList?.label} dataSource={sideData.yearData} /></>
)}
</Col>
{Object.keys(sideData.dataSource).map((key) => (
<Col {...layoutProps3} key={key}>
@ -226,7 +241,8 @@ export default observer(() => {
</Spin>
</section>
<section>
<h3>英语区目标客户
<h3>
英语区目标客户
<Spin spinning={topData?.GuestGroupType?.loading || false}>
<Table {...targetTableProps} pagination={false} />
</Spin>
@ -240,14 +256,16 @@ export default observer(() => {
</div>
</Space>
<Row gutter={layoutProps.gutter}>
{topSeries.map((item) => item.graphVisible ? (
{topSeriesSet.map((item) =>
item.graphVisible ? (
<Col {...layoutProps} key={item.key}>
<Spin spinning={topData[item.key]?.loading || false}>
<h3 style={{ textAlign: 'center' }}>{item.label}</h3>
<Bullet {...BulletConfig} dataSource={topData[item.key]?.dataSource || []} itemLength={10} />
<Bullet key={item.key} {...BulletConfig} dataSource={topData[item.key]?.dataSource || []} itemLength={10} />
</Spin>
</Col>
) : null)}
) : null
)}
</Row>
</section>
</>

@ -1,3 +1,6 @@
.__hn-sta-wrapper .ant-statistic-title{
color: rgb(0, 0, 0, .45);
}
.__hn-sta-wrapper .ant-statistic-content-suffix{
float: right;
font-size: 18px;

Loading…
Cancel
Save