首页 年度

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

@ -5,7 +5,8 @@ import { merge } from '../utils/commons';
export default observer((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
? [
{
@ -36,6 +37,7 @@ export default observer((props) => {
/** 展示总计 */
total: {
// label: `${title}`,
label: `${title}`,
style: {
fill: '#96a6a6',
@ -57,6 +59,17 @@ export default observer((props) => {
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);
return (
<>

@ -82,10 +82,10 @@ export const dateTypes = [
*/
export const dataFieldOptions = [
{ label: '毛利', value: 'SumML', formatter: (v) => `${v / 1000} K`, nestkey: { p: 'MLKPIrates', v: 'MLKPIvalue' } },
{ label: '订单数', value: 'OrderCount', formatter: (v) => v },
{ label: '成交数', value: 'CJCount', formatter: (v) => v },
// { label: '成交人数', value: 'CJPersonNum', formatter: (v) => v },
{ label: '成交率', value: 'CJrate', formatter: (v) => v },
{ 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' } },
// { label: '人数', value: 'CJPersonNum', formatter: (v) => v },
// todo: more...
];
/**
@ -94,8 +94,8 @@ export const dataFieldOptions = [
export const dataFieldAlias = dataFieldOptions.reduce(
(a, c) => ({
...a,
[c.value]: { alias: c.label, formatter: (v) => c.formatter(v) },
[`${c.value}KPI`]: { alias: `${c.label}目标`, formatter: (v) => c.formatter(v) },
[c.value]: { ...c, 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": [
{
"groups|1": "@pick([\"inside\",\"outside\"])",
"groupsLabel|1": "@pick([\"inside\",\"outside\"])",
"groupDateVal": "@date('2023-MM')",
"SumML": "@increment(1000)",
"SumMLVSrate": "@float(0,70,2,2)",

@ -1,6 +1,7 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
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 {
constructor(rootStore) {
@ -8,76 +9,151 @@ class Trade {
makeAutoObservable(this);
}
/**
* 年度总额
*/
fetchSummaryData(queryData) {
this.summaryData.loading = true;
queryData.groupType = 'overview';
queryData.groupDateType = 'year';
req.fetchJSON('/service-Analyse2/GetTradeProcess/test', queryData).then((json) => {
// queryData.groupDateType = 'year';
this.fetchTradeData(queryData).then((json) => {
if (json.errcode === 0) {
runInAction(() => {
const summary = json.result1?.[0] || {};
const summaryData = {
loading: false,
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: '完成率', value: `${json.result1?.SumMLKPIrate || ''}%`, hasKPI: false },
{
title: '成团',
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: '人数',
value: json.result1?.SumPersonNum,
VSrate: json.result1?.SumPersonNumrate,
KPIrate: json.result1?.SumPersonNumKPIrate,
hasKPI: !isEmpty(json.result1?.SumPersonNumKPIrate),
value: summary?.SumPersonNum,
// VSrate: summary?.SumPersonNumrate,
// KPIrate: summary?.[dataFieldAlias.SumPersonNum.nestkey.p],
hasKPI: false, // // !isEmpty(summary?.[dataFieldAlias.SumPersonNum.nestkey.p]),
},
],
};
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) {
this.sideData.loading = true;
// Object.assign(queryData, { groupType: 'bizarea', groupDateType: 'month' });
// todo: groupType: bizarea
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) {
runInAction(() => {
const sortResult = json.result1.sort(sortBy('groupDateVal'));
/**
* test: '91006'
*/
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;
}, {});
console.log(groupsData, 'groupsData', queryData);
const kpi = { label: '', value: 1200000 }; // todo: 标注KPI
this.sideData.loading = false;
this.sideData.dataSource = groupsData;
this.sideData.monthData = sortResult;
this.sideData.kpi = kpi;
// const kpi = { label: '', value: 1200000 }; // 标注KPI
// this.sideData.kpi = kpi;
});
}
});
}
/**
* TOP
*/
fetchTradeDataByType(orderType, queryData) {
this.topData[orderType] = { loading: true, dataSource: [] };
Object.assign(queryData, { groupType: 'orderType', groupDateType: 'year' });
req.fetchJSON('/service-web/QueryData/GetTradeOrderByType', queryData).then((json) => {
this.topData[orderType] = { loading: true, dataSource: [], originData: [] };
Object.assign(queryData, { groupType: orderType, groupDateType: 'year' });
this.fetchTradeData(queryData).then((json) => {
if (json.errcode === 0) {
runInAction(() => {
this.topData[orderType].loading = false;
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) {
this.searchPayloadHome = body;
}
summaryData = { loading: false, dataSource: [] };
sideData = { loading: false, dataSource: {}, kpi: {}, monthData: [] };
summaryData = { loading: false, dataSource: [], kpi: {}, };
timeData = { loading: false, dataSource: [] };
BuData = { loading: false, dataSource: [] };
sideData = { loading: false, dataSource: {}, monthData: [] };
dataForSort = {};
topData = {};
defaultDataSubject = 'CJCount';
searchPayloadHome = {};

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

Loading…
Cancel
Save