diff --git a/src/components/LineWithKPI.jsx b/src/components/LineWithKPI.jsx index 0271fae..3eb4294 100644 --- a/src/components/LineWithKPI.jsx +++ b/src/components/LineWithKPI.jsx @@ -4,44 +4,90 @@ import { merge, isEmpty, groupBy, sortBy } from '../utils/commons'; import { dataFieldAlias } from '../libs/ht'; export default observer((props) => { - const { dataSource, ...config } = props; + const { dataSource, showKPI, ...config } = props; const kpiKey = dataFieldAlias[config.yField]?.nestkey?.v; const seriesData = groupBy(dataSource, ele => ele[config.seriesField]); - const splitData = dataSource.reduce((r, v) => { + const splitData = showKPI ? dataSource.reduce((r, v) => { r.push(v); if ( ! isEmpty(v[kpiKey])) { // 有设目标才多显示一条虚线, 颜色和数据线一致 r.push({...v, [config.yField]: v[kpiKey], [config.seriesField]: `${v[config.seriesField]} ${dataFieldAlias[kpiKey].label}`, extraLine: true,}); } return r; - }, []).sort(sortBy(config.xField)); + }, []).sort(sortBy(config.xField)) : dataSource; const dataColors = [ - "#5B8FF9","#5AD8A6","#5D7092","#F6BD16","#6F5EF9","#6DC8EC","#945FB9","#FF9845","#1E9493", - "#FF99C3","#FF6B3B","#626681","#FFC100","#9FB40F","#76523B","#DAD5B5","#0E8E89","#E19348", - "#F383A2","#247FEA", + "#5D7092","#F6BD16","#6F5EF9","#6DC8EC","#945FB9","#FF9845","#1E9493", + "#FF99C3","#FF6B3B","#626681","#FFC100","#9FB40F","#76523B","#DAD5B5", + "#0E8E89","#E19348","#F383A2","#247FEA","#5B8FF9","#5AD8A6", ]; - const colorSets = Object.keys(seriesData).sort().reduce((obj, k, i) => ({...obj, [k]: dataColors[i]}), {}); - const mergeLineConfig = merge({ - color: (item) => { - const thisSeries = item[config.seriesField]?.split(' ')?.[0]; - return colorSets[thisSeries]; - // return thisSeries.includes('目标') ? '#F4664A' : colorSets[thisSeries]; - }, - lineStyle: (data) => { - if (data[config.seriesField].includes('目标')) { + const colorSets = Object.keys(seriesData) + .sort() + .filter((ele) => !ele.includes(' ')) + .reduce((obj, k, i) => ({ ...obj, [k]: dataColors[i] || dataColors[i % 20] }), {}); + // console.log('colorSets', colorSets); + const mergeLineConfig = merge( + { + color: (item) => { + const thisSeries = item[config.seriesField]?.split(' ')?.[0]; + return colorSets[thisSeries]; + }, + lineStyle: (data) => { + if (data[config.seriesField].includes('目标')) { + return { + lineDash: [8, 20], + opacity: 0.5, + }; + } + + if (data[config.seriesField].includes('@')) { + return { + lineDash: [4, 8], + opacity: 0.6, + lineWidth: 1.5, + }; + } + return { - lineDash: [4, 8], - opacity: 0.7, + opacity: 1, }; - } - - return { - opacity: 1, - }; - }, - legend: { - custom: true, - items: Object.keys(seriesData).map((ele) => ({ id: ele, name: ele, value: ele, marker: { symbol: 'circle', style: { fill: colorSets[ele], color: colorSets[ele] } } })), + }, + legend: { + custom: true, + items: Object.keys(seriesData).map((ele) => ({ + id: ele, + name: ele, + value: ele, + marker: { symbol: ele.includes(' ') ? 'hyphen' : 'circle', style: { fill: colorSets[ele], stroke: colorSets[ele?.split(' ')?.[0]], r: 3, lineWidth: 2, color: colorSets[ele] } }, + })), + }, + // annotations: [ + // // 低于 0 颜色变化 + // { + // type: 'regionFilter', + // start: ['min', 0], + // end: ['max', 0], + // color: '#F4664A', + // }, + // { + // type: 'text', + // position: ['min', 0], + // content: '0', + // offsetY: -4, + // style: { + // textBaseline: 'bottom', + // }, + // }, + // { + // type: 'line', + // start: ['min', 0], + // end: ['max', 0], + // style: { + // stroke: '#F4664A', + // lineDash: [2, 2], + // }, + // }, + // ], }, - }, config); + config + ); return ; }); diff --git a/src/components/MixTBWithKPI.jsx b/src/components/MixTBWithKPI.jsx index 37dce81..1737a57 100644 --- a/src/components/MixTBWithKPI.jsx +++ b/src/components/MixTBWithKPI.jsx @@ -3,26 +3,6 @@ import { Mix } from '@ant-design/plots'; import { merge, isEmpty, groupBy, 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 { dataSource, summaryData: areaData, ...config } = props; const kpiKey = dataFieldAlias[config.yField]?.nestkey?.v; @@ -61,7 +41,7 @@ export default observer((props) => { meta: { [yField]: { sync: true, - } + }, }, // color: ['#598cf3', '#69deae', '#F4664A', '#FAAD14'], color: (item) => { @@ -117,22 +97,39 @@ export default observer((props) => { }, { [xField]: { sync: true }, [yField]: { sync: true } } ), - // color: '#b32b19', color: '#f58269', - smooth: true, + // color: (datum) => { + // console.log('color', datum, String(datum[seriesField]).includes('对比')); + // return String(datum[seriesField]).includes('对比') ? '#f7a593' : '#f58269'; + // }, // '#f58269', + // smooth: true, line: { - size: 0.1, - }, - areaStyle: () => { - return { - fill: 'l(270) 0:#ffffff 0.25:#f8e8e7 0.5:#fac9bd 0.75:#f7a593', - // lineWidth: 0.1, - // lineOpacity: 0.5, - }; + size: 1, + style: (datum) => { + return String(datum[seriesField]).includes('对比') + ? { + // lineWidth: 0.1, + lineDash: [4, 5], + stroke: '#f7a593', + } + : { + stroke: '#f58269', + }; + }, }, - label: { - offsetY: -8, + areaStyle: (datum) => { + // console.log('areaStyle', datum); + return String(datum[seriesField]).includes(' ') + ? { + fill: 'l(270) 0:#ffffff 0.25:#f8e8e7 0.5:#fac9bd 0.75:#fac9bd', + } + : { + fill: 'l(270) 0:#ffffff 0.25:#f8e8e7 0.5:#fac9bd 0.75:#f7a593', + // lineWidth: 0.1, + // lineOpacity: 0.5, + }; }, + label: (datum) => ({ offsetY: -8 }), annotations: areaData.map((d) => { return { type: 'dataMarker', diff --git a/src/components/MixYnQ.jsx b/src/components/MixYnQ.jsx index d990e1d..26c456c 100644 --- a/src/components/MixYnQ.jsx +++ b/src/components/MixYnQ.jsx @@ -257,6 +257,13 @@ export default observer((props) => { legend: false, // {}, color: COLOR_SETS, annotations: diffLine, + + minColumnWidth: 5, + maxColumnWidth: 5, + // 分组柱状图 组内柱子间的间距 (像素级别) + dodgePadding: 1, + // 分组柱状图 组间的间距 (像素级别) + // intervalPadding: 20, }, }, ], diff --git a/src/components/StatisticCard2.jsx b/src/components/StatisticCard2.jsx index cc764ad..14bc2ad 100644 --- a/src/components/StatisticCard2.jsx +++ b/src/components/StatisticCard2.jsx @@ -75,7 +75,7 @@ export default observer((props) => { description: diff ? ( - {diff.VSrate && 0 ? 'up' : 'down'} />} + {diff.VSrate && 0 ? 'up' : 'down'} />} ) : null, }} diff --git a/src/components/search/YearPickerCharts.jsx b/src/components/search/YearPickerCharts.jsx index 1a5a6f7..191afcb 100644 --- a/src/components/search/YearPickerCharts.jsx +++ b/src/components/search/YearPickerCharts.jsx @@ -51,7 +51,7 @@ class DatePickerCharts extends Component { locale={locale} placeholder={"对比 Year"} onChange={(value) => { - const fullYear = [value.clone().set('month', 0).set('date', 1), value.clone().set('month', 11).set('date', 31)]; + const fullYear = value ? [value.clone().set('month', 0).set('date', 1), value.clone().set('month', 11).set('date', 31)] : undefined; if (typeof this.props.onChange === 'function') { this.props.onChange(fullYear); } diff --git a/src/stores/Trade.js b/src/stores/Trade.js index 05e3c17..db58474 100644 --- a/src/stores/Trade.js +++ b/src/stores/Trade.js @@ -19,6 +19,10 @@ class Trade { const curQueryData = cloneDeep(queryData); curQueryData.groupType = curQueryData?.groupType || 'overview'; curQueryData.groupDateType = 'year'; + if (isEmpty(curQueryData.DateDiff1)) { + curQueryData.DateDiff1 = moment(curQueryData.Date1).subtract(1, 'year').format(DATE_FORMAT); + curQueryData.DateDiff2 = moment(curQueryData.Date2).subtract(1, 'year').format(SMALL_DATETIME_FORMAT); + } const multiData = await this.fetchTradeDataAll((curQueryData)); const { summary, traditional, biz } = multiData.result1; const { summary: summary2, traditional: traditional2, biz: biz2 } = multiData.result2; @@ -137,16 +141,59 @@ class Trade { queryData.groupType = queryData?.groupType || 'overview'; Object.assign(queryData, { groupDateType: this.timeLineKey }); const multiData = await this.fetchTradeDataAll(cloneDeep(queryData)); - const { traditional, biz } = multiData.result1; - const { summary: summary2, traditional: traditional2, biz: biz2 } = multiData.result2; - console.log(biz, 'mmmmmmmm', queryData, multiData); + const { traditional, biz, summaryRows: summaryRows1, } = multiData.result1; + // const { summaryRows: summaryRows2, mergeRows: mergeRows2 } = multiData.result2; + // console.log(biz, 'mmmmmmmm', queryData, multiData); const mergeData = [].concat(traditional, biz); const dateKeyData = groupBy(mergeData, ele => ele.groupDateVal); const sortByDateKey = Object.values(sortKeys(dateKeyData)).reduce( (a, b) => a.concat(b), []); + runInAction(() => { this.timeData.loading = false; this.timeData.dataSource = sortByDateKey; - this.timeData.origin = multiData.result1; + this.timeData.origin = { summaryRows: summaryRows1 || [] }; // multiData.result1; + }); + } + + /** + * 有对比的时间轴 + */ + async fetchTradeDataDiffByDate(queryData = {}) { + this.timeDiffData.loading = true; + queryData = Object.assign({}, this.searchPayloadHome, queryData); // queryData || this.searchPayloadHome; + queryData.groupType = queryData?.groupType || 'overview'; + Object.assign(queryData, { groupDateType: this.timeLineKey }); + const multiData = await this.fetchTradeDataAll(cloneDeep(queryData)); + const { mergeRows: mergeRows1 } = multiData.result1; + const { mergeRows: mergeRows2 } = multiData.result2; + // console.log(biz, 'mmmmmmmm', queryData, multiData); + + // 为了图表的X轴一致 + const allDateKey1 = [...new Set(mergeRows1.reduce((rv, vk) => { + rv.push(vk.groupDateVal); + return rv; + }, []))].sort(); + const allDateKey2 = [...new Set(mergeRows2.reduce((rv, vk) => { + rv.push(vk.groupDateVal); + return rv; + }, []))].sort(); + const allLabelDateKeyMapped = { + ...allDateKey2.reduce((obj, k, i) => ({...obj, [k]: allDateKey1[i] || `_${k}`}), {}) + }; + const mergeKeyDateRows = [].concat( + mergeRows1 || [], + (mergeRows2 || []).map((row, ri) => { + return { + ...row, + groupsLabel: `${row.groupsLabel} @${moment(queryData.DateDiff1).year()}`, + groupDateVal: allLabelDateKeyMapped[row.groupDateVal], + rawGroupDateVal: row.groupDateVal, + }; + }) + ).sort(sortBy('groupDateVal')); + runInAction(() => { + this.timeDiffData.loading = false; + this.timeDiffData.dataSource = mergeKeyDateRows; }); } @@ -291,18 +338,26 @@ class Trade { this.targetTableProps.dataSource = [].concat(Object.values(finalTargetData.targetGuest), Object.values(finalTargetData.targetCountry)); // [finalTargetData.targetTotal], // todo: 总数是重复的 }; - setStateSearch(body) { + searchValues = {}; + setSearch(body, form) { this.searchPayloadHome = body; + this.searchValues = form; } - timeLineKey = 'week'; + timeLineKey = 'month'; setTimeLineKey(v) { this.timeLineKey = v; } + groupKey = 'overview'; + setGroupKey(v) { + this.groupKey = v; + } + resetData = () => { this.summaryData = { loading: false, dataSource: [], kpi: {}, }; - this.timeData = { loading: false, dataSource: [], origin: {} }; + this.timeData = { loading: false, dataSource: [], origin: {}, diff: {} }; + this.timeDiffData = { loading: false, dataSource: [], origin: {}, }; this.BuData = { loading: false, dataSource: [] }; this.sideData = { loading: false, dataSource: {}, monthData: [], yearData: [] }; this.topData = {}; @@ -312,7 +367,8 @@ class Trade { searchPayloadHome = {}; summaryData = { loading: false, dataSource: [], kpi: {}, }; - timeData = { loading: false, dataSource: [], origin: {} }; + timeData = { loading: false, dataSource: [], origin: {}, diff: {} }; + timeDiffData = { loading: false, dataSource: [], origin: {}, }; BuData = { loading: false, dataSource: [] }; sideData = { loading: false, dataSource: {}, monthData: [], yearData: [] }; topData = {}; diff --git a/src/views/Home.jsx b/src/views/Home.jsx index f9b3ecb..1ecb837 100644 --- a/src/views/Home.jsx +++ b/src/views/Home.jsx @@ -1,6 +1,6 @@ import { useContext, useEffect, useState } from 'react'; import { observer } from 'mobx-react'; -import { Row, Col, Spin, Space, Radio, Table } from 'antd'; +import { Row, Col, Spin, Space, Radio, Table, Button } from 'antd'; import { CheckCircleTwoTone, MoneyCollectTwoTone, FlagTwoTone, SmileTwoTone } from '@ant-design/icons'; import { stores_Context } from '../config'; import StatisticCard2 from '../components/StatisticCard2'; @@ -9,6 +9,7 @@ import Waterfall from '../components/Waterfall'; import MixTBWithKPI from './../components/MixTBWithKPI'; import Donut from './../components/Donut'; import MapCountry from './../components/MapCountry'; +import LineWithKPI from '../components/LineWithKPI'; import DataFieldRadio from '../components/DataFieldRadio'; import { datePartOptions } from './../components/DateGroupRadio/date'; import SearchForm from './../components/search/SearchForm'; @@ -17,11 +18,16 @@ import { dataFieldAlias } from './../libs/ht'; import './home.css'; const topSeries = [ - { key: 'dept', label: '小组', graphVisible: true }, - { key: 'operator', label: '顾问', graphVisible: true }, - { key: 'destination', label: '目的地', graphVisible: true }, - { key: 'GuestGroupType', label: '客群类别', graphVisible: false }, - { key: 'country', label: '国籍', graphVisible: true }, + { key: 'dept', value: 'dept', label: '小组', graphVisible: true }, + { key: 'operator', value: 'operator', label: '顾问', graphVisible: true }, + { key: 'destination', value: 'destination', label: '目的地', graphVisible: true }, + { key: 'GuestGroupType', value: 'GuestGroupType', label: '客群类别', graphVisible: false }, + { key: 'country', value: 'country', label: '国籍', graphVisible: true }, +]; + +const allGroupTypes = [ + { key: 'overview', value: 'overview', label: '总额' }, + ...topSeries, ]; // const iconSets = [CheckCircleTwoTone, , , , ,,]; @@ -30,7 +36,7 @@ const iconSets = [CheckCircleTwoTone, MoneyCollectTwoTone, FlagTwoTone, SmileTwo export default observer(() => { // const navigate = useNavigate(); const { TradeStore, date_picker_store: searchFormStore } = useContext(stores_Context); - const { sideData, summaryData, BuData, topData, timeData, timeLineKey, targetTableProps } = TradeStore; + const { searchValues, sideData, summaryData, BuData, topData, timeData, timeLineKey, targetTableProps, timeDiffData, groupKey } = TradeStore; const { formValues } = searchFormStore; useEffect(() => { @@ -51,6 +57,7 @@ export default observer(() => { TradeStore.resetData(); TradeStore.fetchSummaryData(Object.assign({}, queryData, { groupType })); TradeStore.fetchTradeDataByDate(queryData); + TradeStore.fetchTradeDataDiffByDate(queryData); // // TradeStore.fetchTradeDataByBU(queryData); TradeStore.fetchTradeDataByMonth(queryData); const topSeriesF = _overviewFlag ? topSeries : topSeries.filter((ele) => ele.key !== 'dept'); @@ -133,7 +140,6 @@ export default observer(() => { xAxis: { type: 'cat', }, - // smooth: true, point: { size: 4, shape: 'cicle', @@ -158,26 +164,38 @@ export default observer(() => { TradeStore.setTimeLineKey(value); if (!isEmpty(TradeStore.searchPayloadHome)) { TradeStore.fetchTradeDataByDate({ groupType: groupTypeVal }); + TradeStore.fetchTradeDataDiffByDate({ groupType: diffGroupKey }); } }; + const [diffGroupKey, setDiffGroupKey] = useState(groupKey); + const handleChangeDiffType = ({ target: { value } }) => { + console.log('diffGroupKey', diffGroupKey, value); + setDiffGroupKey(value); + TradeStore.setGroupKey(value); + if (!isEmpty(TradeStore.searchPayloadHome)) { + TradeStore.fetchTradeDataDiffByDate({ groupType: value }); + } + }; + const [showDiff, setShowDiff] = useState(false); return ( <> - + { - TradeStore.setStateSearch(obj); + TradeStore.setSearch(obj, form); pageRefresh(obj); }} /> @@ -185,7 +203,9 @@ export default observer(() => {
-

年度业绩 =传统+商务

+

+ 年度业绩 =传统+商务 +

@@ -195,7 +215,7 @@ export default observer(() => { ))} */} {summaryData.dataSource.map((item, i) => ( - + ))} @@ -203,22 +223,41 @@ export default observer(() => {
- -

走势

+ +

{showDiff === false ? '走势' : '对比'}

+ {showDiff && } + {searchValues.yearDiff && ( + + )}
- - {/* */} - - + {showDiff === false ? ( + + + + ) : ( + + + + )}
+

市场 (仅传统订单)

- + - <> + <> + + {/* {overviewFlag ? ( <> @@ -228,12 +267,14 @@ export default observer(() => { <> )} */} - {Object.keys(sideData.dataSource).sort().map((key) => ( - - -

{`${key}每月业绩`}

- - ))} + {Object.keys(sideData.dataSource) + .sort() + .map((key) => ( + + +

{`${key}每月业绩`}

+ + ))}
@@ -265,9 +306,9 @@ export default observer(() => { )} -
- -
+
+ +
diff --git a/src/views/Sale_KPI.jsx b/src/views/Sale_KPI.jsx index 5e5800b..b57b6e3 100644 --- a/src/views/Sale_KPI.jsx +++ b/src/views/Sale_KPI.jsx @@ -212,7 +212,7 @@ const Sale_KPI = () => { - +