首页 年度

feature/2.0-sales-trade
Lei OT 2 years ago
parent 76a3c3c94d
commit 2b279718c0

@ -2,19 +2,24 @@ import { useEffect, useState } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Bullet } from '@ant-design/plots'; import { Bullet } from '@ant-design/plots';
import { sortBy, merge } from '../utils/commons'; import { sortBy, merge } from '../utils/commons';
import { dataFieldAlias } from './../libs/ht'; import { dataFieldAlias } from '../libs/ht';
// const layoutLabel = {
// 'vertical':
// };
export default observer((props) => { export default observer((props) => {
const { dataSource, ...extProps } = props; const { dataSource, itemLength, ...extProps } = props;
// , number -> array // , number -> array
const dataParser = (origin) => { const dataParser = (origin) => {
const { measureField, rangeField, targetField } = extProps; const { measureField, rangeField, targetField } = extProps;
const maxKPI = Math.max(...(origin || []).map((ele) => ele[targetField])); const maxKPI = Math.max(...(origin || []).map((ele) => ele[targetField]));
const maxValue = Math.max(...(origin || []).map((ele) => ele[measureField])); const maxValue = Math.max(...(origin || []).map((ele) => ele[measureField]));
const _max = Math.max(maxKPI, maxValue); const _max = Math.max(maxKPI, maxValue);
const sortData = origin.sort(sortBy(measureField)); const sortData = origin.sort(sortBy(measureField)).slice(-itemLength);
// //
const _parseData = sortData?.map((ele) => ({ ...ele, [rangeField]: [0, Math.ceil(_max / 0.9)], [measureField]: [ele[measureField]] })); const _parseData = sortData?.map((ele) => ({ ...ele, [rangeField]: [0, Math.ceil(_max / 0.9)], [measureField]: [ele[measureField]] }));
console.log(_parseData, 'vvvvvvvvvvvvvv');
return _parseData; return _parseData;
}; };
@ -26,16 +31,16 @@ export default observer((props) => {
const config = merge({ const config = merge({
color: { color: {
range: ['#FFbcb8', '#FFe0b0', '#bfeec8'], range: [ '#FFF3E1', '#FFe0b0', '#bfeec8'], // '#FFbcb8', '#FFe0b0',
measure: '#5B8FF9', measure: '#5B8FF9',
target: '#FF9845', target: '#FF9845',
}, },
label: { label: {
// target: true, // target: true,
// measure: { // measure: {
// position: 'middle', // position: extProps?.vertical === 'vertical' ? 'top' : 'right',
// style: { // style: {
// fill: '#fff', // fill: '#063CAA',
// }, // },
// }, // },
}, },

@ -5,7 +5,8 @@ import { merge } from '../utils/commons';
export default observer((props) => { export default observer((props) => {
const { dataSource, line, title, ...extProps } = props; const { dataSource, line, title, ...extProps } = props;
const yMax = Math.max(line?.value || 0, ...dataSource.map((ele) => ele[extProps.yField])); const yMax = (Math.max(line?.value || 0, ...dataSource.map((ele) => ele[extProps.yField])))*10;
console.log(title, 'title waterfall', yMax);
const annotationsLine = line const annotationsLine = line
? [ ? [
{ {
@ -36,6 +37,7 @@ export default observer((props) => {
/** 展示总计 */ /** 展示总计 */
total: { total: {
// label: `${title}`,
label: `${title}`, label: `${title}`,
style: { style: {
fill: '#96a6a6', fill: '#96a6a6',
@ -57,6 +59,17 @@ export default observer((props) => {
formatter: (v) => dataFieldAlias[extProps.yField]?.formatter(v) || v, formatter: (v) => dataFieldAlias[extProps.yField]?.formatter(v) || v,
}, },
}, },
xAxis: {
type: 'cat',
},
tooltip: {
customItems: (originalItems) => {
// process originalItems,
const items = originalItems.map((ele) => ({ ...ele, title: `${ele.title} ${ele.data.groupsLabel}`, name: dataFieldAlias[ele.name]?.alias || ele.name }));
console.log(originalItems, items, 'llll');
return items;
},
},
}, extProps); }, extProps);
return ( return (
<> <>

@ -82,10 +82,10 @@ export const dateTypes = [
*/ */
export const dataFieldOptions = [ export const dataFieldOptions = [
{ label: '毛利', value: 'SumML', formatter: (v) => `${v / 1000} K`, nestkey: { p: 'MLKPIrates', v: 'MLKPIvalue' } }, { label: '毛利', value: 'SumML', formatter: (v) => `${v / 1000} K`, nestkey: { p: 'MLKPIrates', v: 'MLKPIvalue' } },
{ label: '订单数', value: 'OrderCount', formatter: (v) => v }, { label: '订单数', value: 'SumOrder', formatter: (v) => v, nestkey: { p: 'OrderKPIrates', v: 'OrderKPIvalue' } },
{ label: '成交数', value: 'CJCount', formatter: (v) => v }, { label: '成交数', value: 'ConfirmOrder', formatter: (v) => v, nestkey: { p: 'ConfirmOrderKPIrates', v: 'ConfirmOrderKPIvalue' } },
// { label: '成交人数', value: 'CJPersonNum', formatter: (v) => v }, { label: '成交率', value: 'ConfirmRates', formatter: (v) => v, nestkey: { p: 'ConfirmRatesKPIrates', v: 'ConfirmRatesKPIvalue' } },
{ label: '成交率', value: 'CJrate', formatter: (v) => v }, // { label: '人数', value: 'CJPersonNum', formatter: (v) => v },
// todo: more... // todo: more...
]; ];
/** /**
@ -94,8 +94,8 @@ export const dataFieldOptions = [
export const dataFieldAlias = dataFieldOptions.reduce( export const dataFieldAlias = dataFieldOptions.reduce(
(a, c) => ({ (a, c) => ({
...a, ...a,
[c.value]: { alias: c.label, formatter: (v) => c.formatter(v) }, [c.value]: { ...c, alias: c.label, formatter: (v) => c.formatter(v) },
[`${c.value}KPI`]: { alias: `${c.label}目标`, formatter: (v) => c.formatter(v) }, [c.nestkey.v]: { ...c, alias: `${c.label}目标`, formatter: (v) => c.formatter(v) },
}), }),
{} {}
); );

@ -87,6 +87,7 @@
"result1|24": [ "result1|24": [
{ {
"groups|1": "@pick([\"inside\",\"outside\"])", "groups|1": "@pick([\"inside\",\"outside\"])",
"groupsLabel|1": "@pick([\"inside\",\"outside\"])",
"groupDateVal": "@date('2023-MM')", "groupDateVal": "@date('2023-MM')",
"SumML": "@increment(1000)", "SumML": "@increment(1000)",
"SumMLVSrate": "@float(0,70,2,2)", "SumMLVSrate": "@float(0,70,2,2)",

@ -1,6 +1,7 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx'; import { makeAutoObservable, runInAction, toJS } from 'mobx';
import * as req from '../utils/request'; import * as req from '../utils/request';
import { isEmpty, sortBy } from '../utils/commons'; import { isEmpty, sortBy, pick } from '../utils/commons';
import { dataFieldAlias } from './../libs/ht';
class Trade { class Trade {
constructor(rootStore) { constructor(rootStore) {
@ -8,76 +9,151 @@ class Trade {
makeAutoObservable(this); makeAutoObservable(this);
} }
/**
* 年度总额
*/
fetchSummaryData(queryData) { fetchSummaryData(queryData) {
this.summaryData.loading = true; this.summaryData.loading = true;
queryData.groupType = 'overview'; queryData.groupType = 'overview';
queryData.groupDateType = 'year'; // queryData.groupDateType = 'year';
req.fetchJSON('/service-Analyse2/GetTradeProcess/test', queryData).then((json) => { this.fetchTradeData(queryData).then((json) => {
if (json.errcode === 0) { if (json.errcode === 0) {
runInAction(() => { runInAction(() => {
const summary = json.result1?.[0] || {};
const summaryData = { const summaryData = {
loading: false, loading: false,
dataSource: [ dataSource: [
{ title: '成团', value: json.result1?.SumOrder, VSrate: json.result1?.SumOrderrate, KPIrate: json.result1?.SumOrderKPIrate, hasKPI: !isEmpty(json.result1?.SumOrderKPIrate) }, {
{ title: '毛利', value: json.result1?.SumML, VSrate: json.result1?.SumMLrate, KPIrate: json.result1?.SumMLKPIrate, hasKPI: !isEmpty(json.result1?.SumMLKPIrate) }, title: '成团',
{ title: '完成率', value: `${json.result1?.SumMLKPIrate || ''}%`, hasKPI: false }, value: summary?.ConfirmOrder,
// VSrate: summary?.ConfirmOrderrate,
KPIrate: summary?.[dataFieldAlias.ConfirmOrder.nestkey.p],
hasKPI: !isEmpty(summary?.[dataFieldAlias.ConfirmOrder.nestkey.p]),
},
{ title: '毛利', value: summary?.SumML, KPIrate: summary?.[dataFieldAlias.SumML.nestkey.p], hasKPI: !isEmpty(summary?.[dataFieldAlias.SumML.nestkey.p]) },
{ title: '完成率', value: `${summary?.[dataFieldAlias.SumML.nestkey.p] || ''}%`, hasKPI: false },
{ {
title: '人数', title: '人数',
value: json.result1?.SumPersonNum, value: summary?.SumPersonNum,
VSrate: json.result1?.SumPersonNumrate, // VSrate: summary?.SumPersonNumrate,
KPIrate: json.result1?.SumPersonNumKPIrate, // KPIrate: summary?.[dataFieldAlias.SumPersonNum.nestkey.p],
hasKPI: !isEmpty(json.result1?.SumPersonNumKPIrate), hasKPI: false, // // !isEmpty(summary?.[dataFieldAlias.SumPersonNum.nestkey.p]),
}, },
], ],
}; };
this.summaryData = summaryData; this.summaryData = summaryData;
const kpi = { label: '', value: summary.MLKPIvalue };
this.summaryData.kpi = kpi;
});
}
});
}
/**
* 时间轴
*/
fetchTradeDataByDate(queryData) {
this.timeData.loading = true;
Object.assign(queryData, { groupType: 'overview', groupDateType: 'month' });
this.fetchTradeData(queryData).then((json) => {
if (json.errcode === 0) {
runInAction(() => {
const data = json.result1;
// 标注KPI
this.timeData.loading = false;
this.timeData.dataSource = data;
}); });
} }
}); });
} }
/**
* 事业部年度
*/
fetchTradeDataByBU(queryData) {
this.BuData.loading = true;
Object.assign(queryData, { groupType: 'bu', groupDateType: 'year' });
this.fetchTradeData(queryData).then((json) => {
if (json.errcode === 0) {
runInAction(() => {
const data = json.result1;
// 标注KPI
this.BuData.loading = false;
this.BuData.dataSource = data;
});
}
});
}
/**
* 业务区域, 按月
*/
fetchTradeDataByMonth(queryData) { fetchTradeDataByMonth(queryData) {
this.sideData.loading = true; this.sideData.loading = true;
// Object.assign(queryData, { groupType: 'bizarea', groupDateType: 'month' });
// todo: groupType: bizarea
Object.assign(queryData, { groupType: 'bu', groupDateType: 'month' }); Object.assign(queryData, { groupType: 'bu', groupDateType: 'month' });
req.fetchJSON('/service-Analyse2/GetTradeProcess/test', queryData).then((json) => { this.fetchTradeData(queryData).then((json) => {
if (json.errcode === 0) { if (json.errcode === 0) {
runInAction(() => { runInAction(() => {
const sortResult = json.result1.sort(sortBy('groupDateVal')); const sortResult = json.result1.sort(sortBy('groupDateVal'));
/**
* test: '91006'
*/
const groupsData = sortResult.reduce((r, v) => { const groupsData = sortResult.reduce((r, v) => {
(r[v.groups] || (r[v.groups] = [])).push(v); if (v.groupsLabel && ['91001', '91006'].includes(v.groupsKey)) { // , '91006'
(r[v.groupsLabel] || (r[v.groupsLabel] = [])).push(v);
}
return r; return r;
}, {}); }, {});
console.log(groupsData, 'groupsData', queryData);
const kpi = { label: '', value: 1200000 }; // todo: 标注KPI
this.sideData.loading = false; this.sideData.loading = false;
this.sideData.dataSource = groupsData; this.sideData.dataSource = groupsData;
this.sideData.monthData = sortResult; this.sideData.monthData = sortResult;
this.sideData.kpi = kpi; // const kpi = { label: '', value: 1200000 }; // 标注KPI
// this.sideData.kpi = kpi;
}); });
} }
}); });
} }
/**
* TOP
*/
fetchTradeDataByType(orderType, queryData) { fetchTradeDataByType(orderType, queryData) {
this.topData[orderType] = { loading: true, dataSource: [] }; this.topData[orderType] = { loading: true, dataSource: [], originData: [] };
Object.assign(queryData, { groupType: 'orderType', groupDateType: 'year' }); Object.assign(queryData, { groupType: orderType, groupDateType: 'year' });
req.fetchJSON('/service-web/QueryData/GetTradeOrderByType', queryData).then((json) => { this.fetchTradeData(queryData).then((json) => {
if (json.errcode === 0) { if (json.errcode === 0) {
runInAction(() => { runInAction(() => {
this.topData[orderType].loading = false; this.topData[orderType].loading = false;
this.topData[orderType].dataSource = json.result1; this.topData[orderType].dataSource = json.result1;
console.log({ loading: false, ...json }, orderType, 'topData');
}); });
} }
}); });
} }
/**
* 获取业绩数据
*/
async fetchTradeData(queryData) {
const json = await req.fetchJSON('/service-Analyse2/GetTradeProcess', queryData);
if (json.errcode === 0) {
return json;
}
return null;
}
setStateSearch(body) { setStateSearch(body) {
this.searchPayloadHome = body; this.searchPayloadHome = body;
} }
summaryData = { loading: false, dataSource: [] }; summaryData = { loading: false, dataSource: [], kpi: {}, };
sideData = { loading: false, dataSource: {}, kpi: {}, monthData: [] }; timeData = { loading: false, dataSource: [] };
BuData = { loading: false, dataSource: [] };
sideData = { loading: false, dataSource: {}, monthData: [] };
dataForSort = {};
topData = {}; topData = {};
defaultDataSubject = 'CJCount'; defaultDataSubject = 'CJCount';
searchPayloadHome = {}; searchPayloadHome = {};

@ -3,27 +3,26 @@ import { observer } from 'mobx-react';
import { Row, Col, Spin, Space } from 'antd'; import { Row, Col, Spin, Space } from 'antd';
import { stores_Context } from '../config'; import { stores_Context } from '../config';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
// import { SlackOutlined, SketchOutlined, AntCloudOutlined, RedditOutlined, GithubOutlined, ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import StatisticCard from '../components/StatisticCard'; import StatisticCard from '../components/StatisticCard';
import Bullet from '../components/Bullet'; import Bullet from '../components/BulletWithSort';
import Waterfall from '../components/Waterfall'; import Waterfall from '../components/Waterfall';
import Column from '../components/Column';
import DataFieldRadio from './../components/DateFieldRadio'; import DataFieldRadio from './../components/DateFieldRadio';
import SearchForm from './../components/search/SearchForm'; import SearchForm from './../components/search/SearchForm';
import { empty } from './../utils/commons'; import { empty } from './../utils/commons';
import { dataFieldAlias } from './../libs/ht';
import './home.css'; import './home.css';
export default observer(() => { export default observer(() => {
const navigate = useNavigate(); const navigate = useNavigate();
const { TradeStore } = useContext(stores_Context); const { TradeStore } = useContext(stores_Context);
const { searchPayloadHome, sideData, summaryData, topData } = TradeStore; const { searchPayloadHome, sideData, summaryData, BuData, topData } = TradeStore;
const topSeries = [ const topSeries = [
{ key: 'Country', label: '国籍' }, { key: 'country', label: '国籍' },
{ key: 'Area', label: '目的地' }, { key: 'dept', label: '小组' },
{ key: 'Sales', label: '顾问' }, { key: 'operator', label: '顾问' },
{ key: 'GuestGroupType', label: '客群类别' }, { key: 'destination', label: '目的地' },
// { key: 'GuestGroupType', label: '' },
]; ];
useEffect(() => { useEffect(() => {
@ -35,6 +34,7 @@ export default observer(() => {
const pageRefresh = (queryData) => { const pageRefresh = (queryData) => {
TradeStore.fetchSummaryData(queryData); TradeStore.fetchSummaryData(queryData);
TradeStore.fetchTradeDataByBU(queryData);
TradeStore.fetchTradeDataByMonth(queryData); TradeStore.fetchTradeDataByMonth(queryData);
for (const iterator of topSeries) { for (const iterator of topSeries) {
TradeStore.fetchTradeDataByType(iterator.key, queryData); TradeStore.fetchTradeDataByType(iterator.key, queryData);
@ -59,55 +59,42 @@ export default observer(() => {
const [BulletConfig, setBulletConfig] = useState({ const [BulletConfig, setBulletConfig] = useState({
measureField: 'SumML', // measureField: 'SumML', //
rangeField: 'SumMLRange', // rangeField: 'SumMLRange', //
targetField: 'SumMLKPI', // targetField: 'MLKPIvalue', //
xField: 'OrderType', xField: 'groupsLabel',
}); });
const handleChangeValueKey = (key) => { const handleChangeValueKey = (key) => {
setValueKey(key); setValueKey(key);
setBulletConfig({ setBulletConfig({
measureField: key, measureField: key,
rangeField: `${key}Range`, rangeField: `${key}Range`,
targetField: `${key}KPI`, // targetField: `${key}KPI`,
xField: 'OrderType', targetField: dataFieldAlias[key].nestkey.p,
xField: 'groupsLabel',
}); });
}; };
const WaterfallConfig = { const WaterfallConfig = {
xField: 'groupDateVal', xField: 'groupDateVal',
yField: 'SumML', yField: 'SumML',
seriesField: 'groupsLabel',
meta: { meta: {
groupDateVal: { groupDateVal: {
alias: '月份', alias: '月份',
// type: 'cat',
}, },
}, },
label: { label: {
formatter: (v) => ((v.SumML / sideData.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) + '%',
}, },
}; };
const ColumnConfig = { const BUConfig = {
xField: 'groupDateVal', measureField: 'SumML', //
yField: 'SumML', rangeField: 'SumMLRange', //
seriesField: 'groups', targetField: 'MLKPIvalue', //
label: { xField: 'groupsLabel',
formatter: (v) => ((v.SumML / sideData.kpi.value) * 100).toFixed(2) + '%', layout: 'vertical',
},
legend: false,
// annotations: sideData.monthData.map((d, ...args) => {
// console.log('aaa', d, args);
// return {
// type: 'dataMarker',
// position: d,
// point: {
// style: {
// stroke: '#FF6B3B',
// lineWidth: 1.5,
// },
// },
// };
// }),
}; };
return ( return (
<> <>
<Row gutter={16} style={{margin: '-16px -8px'}}> <Row gutter={16} style={{margin: '-16px -8px'}}>
@ -115,14 +102,14 @@ export default observer(() => {
<Col className="gutter-row" span={24} > <Col className="gutter-row" span={24} >
<SearchForm <SearchForm
defaultValue={{ defaultValue={{
'initialValue': { initialValue: {
...searchPayloadHome, ...searchPayloadHome,
}, },
hides: ['businessUnits', 'dates', 'months'], shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'years'],
// shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'applyDate'], fieldProps: {
'fieldProps': {
DepartmentList: { show_all: true, }, DepartmentList: { show_all: true, },
WebCode: { show_all: true, }, WebCode: { show_all: true, },
years: { hide_vs: true },
}, },
}} }}
onSubmit={(_err, obj, form, str) => { onSubmit={(_err, obj, form, str) => {
@ -148,14 +135,15 @@ export default observer(() => {
</section> </section>
<section> <section>
<h3>市场进度</h3> <h3>市场进度</h3>
<Spin spinning={sideData.loading}> <Spin spinning={BuData.loading}>
<Row gutter={layoutProps3.gutter}> <Row gutter={layoutProps3.gutter}>
<Col {...layoutProps3}> <Col {...layoutProps3}>
<Column {...ColumnConfig} dataSource={sideData.monthData} line={sideData.kpi} /> <Bullet {...BUConfig} dataSource={BuData?.dataSource || []} />
</Col> </Col>
{Object.keys(sideData.dataSource).map((key) => ( {Object.keys(sideData.dataSource).map((key) => (
<Col {...layoutProps3} key={key}> <Col {...layoutProps3} key={key}>
<Waterfall {...WaterfallConfig} title={key} dataSource={sideData.dataSource[key]} line={sideData.kpi} /> <Waterfall key={key} {...WaterfallConfig} title={key} dataSource={sideData.dataSource[key]} line={summaryData.kpi} />
<h3 style={{ textAlign: 'center' }}>{key}</h3>
</Col> </Col>
))} ))}
</Row> </Row>
@ -173,7 +161,7 @@ export default observer(() => {
<Col {...layoutProps} key={item.key}> <Col {...layoutProps} key={item.key}>
<Spin spinning={topData[item.key]?.loading || false}> <Spin spinning={topData[item.key]?.loading || false}>
<h3 style={{ textAlign: 'center' }}>{item.label}</h3> <h3 style={{ textAlign: 'center' }}>{item.label}</h3>
<Bullet {...BulletConfig} dataSource={topData[item.key]?.dataSource || []} /> <Bullet {...BulletConfig} dataSource={topData[item.key]?.dataSource || []} itemLength={10} />
</Spin> </Spin>
</Col> </Col>
))} ))}

Loading…
Cancel
Save