From 47609219fbbd10a5b3ea99b64db6de03b29eb577 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 22 Sep 2023 14:42:08 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E5=88=86=E5=B8=83:=20?= =?UTF-8?q?=E4=B8=9A=E7=BB=A9=E7=9A=84=E8=BE=93=E5=87=BA=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Distribution.jsx | 102 +++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 55 deletions(-) diff --git a/src/views/Distribution.jsx b/src/views/Distribution.jsx index 68a07f0..59c6876 100644 --- a/src/views/Distribution.jsx +++ b/src/views/Distribution.jsx @@ -1,31 +1,31 @@ -import { useContext, useEffect, useMemo } from 'react'; +import { useContext, useEffect } from 'react'; import { observer } from 'mobx-react'; import { stores_Context } from '../config'; -import { Row, Col, Spin, Space, Radio, Tabs, Table } from 'antd'; +import { Row, Col, Spin, Tabs, Table } from 'antd'; import { RingProgress } from '@ant-design/plots'; import SearchForm from './../components/search/SearchForm'; -import "./kpi.css"; import { empty } from '../utils/commons'; +import { dataFieldAlias } from '../libs/ht'; + +import './kpi.css'; const apartOptions = [ - { key: 'tourDays', value: 'tourDays', label: '团天数', }, - { key: 'PML', value: 'PML', label: '单团毛利', }, - { key: 'ConfirmDays', value: 'ConfirmDays', label: '成团周期', }, - { key: 'ApplyDays', value: 'ApplyDays', label: '预定周期', }, - { key: 'PersonNum', value: 'PersonNum', label: '人等', }, - { key: 'destination', value: 'destination', label: '国内目的地', }, - { key: 'GlobalDestination', value: 'GlobalDestination', label: '海外目的地', }, + { key: 'tourDays', value: 'tourDays', label: '团天数' }, + { key: 'PML', value: 'PML', label: '单团毛利' }, + { key: 'ConfirmDays', value: 'ConfirmDays', label: '成团周期' }, + { key: 'ApplyDays', value: 'ApplyDays', label: '预定周期' }, + { key: 'PersonNum', value: 'PersonNum', label: '人等' }, + { key: 'destination', value: 'destination', label: '国内目的地' }, + { key: 'GlobalDestination', value: 'GlobalDestination', label: '海外目的地' }, ]; -export default observer((props) => { +export default observer(() => { const { date_picker_store: searchFormStore, DistributionStore } = useContext(stores_Context); const { formValues, formValuesToSub } = searchFormStore; const { curTab } = DistributionStore; + const pageRefresh = (obj) => { - DistributionStore.getData({ - DateType: 'applyDate', - Date1: searchFormStore.start_date.startOf('year').format('YYYY-MM-DD'), - Date2: searchFormStore.end_date.endOf('year').format('YYYY-MM-DD 23:59'), + DistributionStore.getApartData({ ...(obj || formValuesToSub), }); }; @@ -41,7 +41,6 @@ export default observer((props) => { return () => {}; }, [formValuesToSub]); - const onTabsChange = (tab) => { DistributionStore.setCurTab(tab); }; @@ -49,69 +48,62 @@ export default observer((props) => { height: 60, width: 60, autoFit: false, - // percent: Number(_)/100, color: ['#5B8FF9', '#E8EDF3'], }; const columns = [ - { - title: '', - dataIndex: 'label', - }, - { title: '团数', dataIndex: 'ConfirmOrder'}, - { title: '业绩', dataIndex: 'SumML', render1: (v) => `1`}, - { title: '团数占比', dataIndex: 'ConfirmOrderPercent', render: (v) => }, - { title: '业绩占比', dataIndex: 'SumMLPercent', render: (v) => }, + { title: '', dataIndex: 'label' }, + { title: '团数', dataIndex: 'ConfirmOrder' }, + { title: '业绩', dataIndex: 'SumML', render: (v) => dataFieldAlias.SumML.formatter(v) }, + { title: '团数占比', dataIndex: 'ConfirmOrderPercent', render: (v) => }, + { title: '业绩占比', dataIndex: 'SumMLPercent', render: (v) => }, ]; return ( <> - {/* style={{ margin: '-16px -8px', padding: 0 }} */} { + onSubmit={(_err, obj) => { pageRefresh(obj); }} /> -
- { - // const ObjectItemPanel = objectComponents[ele.key]; - return { - ...ele, - children: ( - - {/* */} - record.label} - loading={DistributionStore[curTab].loading} - pagination={false} - scroll={{ x: '100%' }} - /> - - ), - }; - })} - /> +
+ { + return { + ...ele, + children: ( + +
record.label} + loading={DistributionStore[curTab].loading} + pagination={false} + scroll={{ x: '100%' }} + /> + + ), + }; + })} + /> ); From 2587b50c5efa005b5b2cd63ed19e541e2e7c4c29 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 22 Sep 2023 16:49:36 +0800 Subject: [PATCH 2/9] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=98=8E=E7=BB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 15 ++++++- src/components/Scatter.jsx | 42 ++++++++++++++++++++ src/stores/Distribution.js | 35 ++++++++++++++--- src/views/Detail.jsx | 80 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/components/Scatter.jsx create mode 100644 src/views/Detail.jsx diff --git a/src/App.jsx b/src/App.jsx index 1ce1708..718dd76 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,9 +10,9 @@ import { DollarOutlined, AreaChartOutlined, WechatOutlined, - UserOutlined, FlagOutlined, PieChartOutlined + UserOutlined, FlagOutlined, PieChartOutlined, BarChartOutlined } from '@ant-design/icons'; -import { Layout, Menu, Image, Spin } from 'antd'; +import { Layout, Menu, Image, Badge } from 'antd'; import { BrowserRouter, Route, Routes, NavLink } from 'react-router-dom'; import Home from './views/Home'; import Dashboard from './views/Dashboard'; @@ -37,6 +37,7 @@ import { observer } from 'mobx-react'; import ExchangeRate from './charts/ExchangeRate'; import KPI from './views/KPI'; import Distribution from './views/Distribution'; +import Detail from './views/Detail'; const App = () => { const { Content, Footer, Sider } = Layout; @@ -123,6 +124,15 @@ const App = () => { }, { key: 'kpi', label: 目标, icon: }, { key: 'distribution', label: 统计分布, icon: }, + { + key: 'detail', + label: ( + + 统计分析 + + ), + icon: , + }, ]; return ( @@ -165,6 +175,7 @@ const App = () => { } /> } /> } /> + } /> }> } /> } /> diff --git a/src/components/Scatter.jsx b/src/components/Scatter.jsx new file mode 100644 index 0000000..3ef84a2 --- /dev/null +++ b/src/components/Scatter.jsx @@ -0,0 +1,42 @@ +import { useEffect, useState } from 'react'; +import { observer } from 'mobx-react'; +import { sortBy, merge } from '../utils/commons'; +import { dataFieldAlias } from '../libs/ht'; +import { Mix, Scatter } from '@ant-design/plots'; + +export default observer((props) => { + const { dataSource, ...extProps } = props; + const config = merge( + { + appendPadding: 10, + // xField: 'Revenue (Millions)', + // yField: 'Rating', + shape: 'circle', + // colorField: 'Genre', + size: 4, + yAxis: { + nice: true, + line: { + style: { + stroke: '#aaa', + }, + }, + }, + xAxis: { + min: -100, + grid: { + line: { + style: { + stroke: '#eee', + }, + }, + }, + line: { + style: { + stroke: '#aaa', + }, + }, + }, + }, extProps); + return ; +}); diff --git a/src/stores/Distribution.js b/src/stores/Distribution.js index e583983..5f62d6c 100644 --- a/src/stores/Distribution.js +++ b/src/stores/Distribution.js @@ -1,6 +1,6 @@ import { makeAutoObservable, runInAction, toJS } from 'mobx'; import * as req from '../utils/request'; -import { isEmpty, sortBy } from '../utils/commons'; +import { isEmpty, pick, sortBy } from '../utils/commons'; const modelMapper = { 'tourDays': { url: '/service-Analyse2/GetTradeApartByTourDays' }, @@ -17,7 +17,10 @@ class Distribution { makeAutoObservable(this); } - async getData(param){ + /** + * 各个类型的分布 + */ + getApartData = async (param) => { const mkey = this.curTab; this[mkey] = { loading: true, dataSource: [] }; const json = await req.fetchJSON(modelMapper[mkey].url, param); @@ -30,10 +33,29 @@ class Distribution { }); } return this[mkey]; + }; - } + /** + * 明细 + */ + getDetailData = async (param) => { + this.detailData.loading = true; + const json = await req.fetchJSON('/service-Analyse2/GetTradeApartDetail', param); + if (json.errcode === 0) { + runInAction(() => { + this.detailData.loading = false; + this.detailData.dataSource = json.result; + const daysData = json.result.filter(ele => ele.confirmDays).map(row => pick(row, ['o_id', 'tourdays', 'applyDays', 'personNum', 'country', 'startDate'])); + this.scatterDays = daysData; + }); + } + return this.detailData; + }; + + resetData = () => { + // this.detailData = { loading: false, dataSource: [] }; + // this.scatterDays = []; - resetData() { this.tourDays = { loading: false, dataSource: [] }; this.PML = { loading: false, dataSource: [] }; this.ConfirmDays = { loading: false, dataSource: [] }; @@ -41,7 +63,7 @@ class Distribution { this.PersonNum = { loading: false, dataSource: [] }; this.destination = { loading: false, dataSource: [] }; this.GlobalDestination = { loading: false, dataSource: [] }; - } + }; curTab = 'tourDays'; setCurTab(v) { @@ -50,6 +72,9 @@ class Distribution { pageLoading = false; + detailData = { loading: false, dataSource: [], }; + scatterDays = []; + tourDays = { loading: false, dataSource: [] }; PML = { loading: false, dataSource: [] }; ConfirmDays = { loading: false, dataSource: [] }; diff --git a/src/views/Detail.jsx b/src/views/Detail.jsx new file mode 100644 index 0000000..dc97d35 --- /dev/null +++ b/src/views/Detail.jsx @@ -0,0 +1,80 @@ +import { useContext, useEffect, useMemo } from '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'; + +export default observer((props) => { + const { date_picker_store: searchFormStore, DistributionStore } = useContext(stores_Context); + const { formValues, formValuesToSub } = searchFormStore; + const { curTab, scatterDays, detailData } = DistributionStore; + + const detailRefresh = (obj) => { + DistributionStore.getDetailData({ + ...(obj || formValuesToSub), + }); + }; + + useEffect(() => { + if (empty(detailData.dataSource)) { + detailRefresh(); + } + }, []); + + const ScatterConfig = { + xField: 'startDate', + yField: 'tourdays', + colorField: 'country', + size: 4, + // xAxis: { + // min: 0, + // max: 30, + // }, + yAxis: { + min: 0, + max: 10, + }, + // quadrant: { + // xBaseline: 15, + // yBaseline: 5, + // }, + tooltip: false, + legend: { + position: 'right-top', + }, + }; + return ( + <> + + {/* style={{ margin: '-16px -8px', padding: 0 }} */} + + { + detailRefresh(obj); + }} + /> + + + +
+ + + +
+ + ); +}); From e3cbbee6b74a42d2995e9c08fce1b149a7228d25 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 22 Sep 2023 16:50:34 +0800 Subject: [PATCH 3/9] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E9=BB=98=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/search/SearchForm.jsx | 16 ++++++------ src/stores/DatePickerStore.js | 37 +++++++++++++++------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/components/search/SearchForm.jsx b/src/components/search/SearchForm.jsx index 9dca78c..36bccbe 100644 --- a/src/components/search/SearchForm.jsx +++ b/src/components/search/SearchForm.jsx @@ -278,14 +278,6 @@ function getFields(props) { , 2 ), - item( - 'dates', - 99, - - - , - midCol - ), item( 'years', 99, @@ -303,6 +295,14 @@ function getFields(props) { , 2 ), + item( + 'dates', + 99, + + + , + midCol + ), item( 'operator', 99, diff --git a/src/stores/DatePickerStore.js b/src/stores/DatePickerStore.js index d4f24eb..1609148 100644 --- a/src/stores/DatePickerStore.js +++ b/src/stores/DatePickerStore.js @@ -10,23 +10,6 @@ class DatePickerStore { makeAutoObservable(this); } - formValues = { - 'DepartmentList': { 'key': 'ALL', 'label': '所有小组' }, - 'WebCode': { 'key': 'ALL', 'label': '所有来源' }, - 'IncludeTickets': { 'key': '1', 'label': '含门票' }, - 'DateType': { 'key': 'applyDate', 'label': '提交日期' }, - 'year': moment(), - }; - - formValuesToSub = { - DepartmentList: 'ALL', - WebCode: 'ALL', - IncludeTickets: '1', - DateType: 'applyDate', - Date1: moment().startOf('year').format('YYYY-MM-DD'), - Date2: moment().endOf('year').format('YYYY-MM-DD 23:59'), - }; - start_date = moment().startOf('week').subtract(7, 'days'); end_date = moment().endOf('week').subtract(7, 'days'); start_date_cp = false; @@ -57,6 +40,26 @@ class DatePickerStore { return [moment(this.start_date).subtract(1, 'year'), moment(this.end_date).subtract(1, 'year')]; } + + formValues = { + 'DepartmentList': { 'key': 'ALL', 'label': '所有小组' }, + 'WebCode': { 'key': 'ALL', 'label': '所有来源' }, + 'IncludeTickets': { 'key': '1', 'label': '含门票' }, + 'DateType': { 'key': 'applyDate', 'label': '提交日期' }, + 'years': this.start_date, + // 'months': [moment(), moment()], + 'dates': [this.start_date, this.end_date], + }; + + formValuesToSub = { + DepartmentList: 'ALL', + WebCode: 'ALL', + IncludeTickets: '1', + DateType: 'applyDate', + Date1: this.start_date.format('YYYY-MM-DD'), + Date2: this.end_date.format('YYYY-MM-DD 23:59'), + }; + setFormValues(data){ this.formValues = data; } From 1c78a0c64ffca9be904c8be32f97c766c94da11a Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 25 Sep 2023 14:03:46 +0800 Subject: [PATCH 4/9] =?UTF-8?q?todo:=20=E5=8F=8C=E7=BA=BF=E5=9B=BE:=20KPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LineWithKPI.jsx | 71 +++++++++++++++++++++++++++++++++ src/libs/ht.js | 2 +- src/utils/commons.js | 4 +- src/views/Detail.jsx | 72 +++++++++++++++++++--------------- src/views/Home.jsx | 22 ++++++----- 5 files changed, 128 insertions(+), 43 deletions(-) create mode 100644 src/components/LineWithKPI.jsx diff --git a/src/components/LineWithKPI.jsx b/src/components/LineWithKPI.jsx new file mode 100644 index 0000000..afdb70d --- /dev/null +++ b/src/components/LineWithKPI.jsx @@ -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 ; +}); diff --git a/src/libs/ht.js b/src/libs/ht.js index b8b1cbf..2687c07 100644 --- a/src/libs/ht.js +++ b/src/libs/ht.js @@ -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) }, }), {} ); diff --git a/src/utils/commons.js b/src/utils/commons.js index 6a9adff..a90a0f2 100644 --- a/src/utils/commons.js +++ b/src/utils/commons.js @@ -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 { diff --git a/src/views/Detail.jsx b/src/views/Detail.jsx index dc97d35..8ba1c80 100644 --- a/src/views/Detail.jsx +++ b/src/views/Detail.jsx @@ -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 ( - <> - - {/* style={{ margin: '-16px -8px', padding: 0 }} */} -
- { - detailRefresh(obj); - }} - /> - - + const HistogramConfig = { + binField: 'personNum', + binWidth: 1, + }; + return ( + <> + + {/* style={{ margin: '-16px -8px', padding: 0 }} */} + + { + detailRefresh(obj); + }} + /> + + -
- - - -
- - ); +
+ + + +
+ {/*
+ + + +
*/} + + ); }); diff --git a/src/views/Home.jsx b/src/views/Home.jsx index 409955b..92fe06c 100644 --- a/src/views/Home.jsx +++ b/src/views/Home.jsx @@ -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(() => { + {/* */}
@@ -212,7 +216,7 @@ export default observer(() => {
-

{`各事业部总业绩`}

+

{`各事业部总业绩`}

{Object.keys(sideData.dataSource).map((key) => (
From a5e9e8aaf0db9506caa2a617062e5e3e3e1c812e Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 25 Sep 2023 17:11:27 +0800 Subject: [PATCH 5/9] =?UTF-8?q?KPI=20=E6=89=B9=E9=87=8F=E8=AE=BE=E7=BD=AE;?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=E6=97=A5=E6=9C=9F=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/kpi/KPISettings.jsx | 10 ++- src/components/kpi/ObjectPanel.jsx | 15 ++-- src/components/kpi/OverviewPanel.jsx | 2 + src/components/kpi/SubjectTable/Profit.jsx | 77 +++++++++++-------- .../kpi/{ => SubjectTable}/SumProfitPanel.jsx | 4 +- src/components/search/GroupSelect.jsx | 7 +- src/components/search/Input.jsx | 16 ++-- src/components/search/SearchForm.jsx | 28 ++++--- src/libs/ht.js | 2 +- src/mock/2.0/baseinfo.json | 6 +- src/stores/DictData.js | 10 ++- src/stores/KPI.js | 2 +- src/views/KPI.jsx | 27 +++++-- 13 files changed, 127 insertions(+), 79 deletions(-) rename src/components/kpi/{ => SubjectTable}/SumProfitPanel.jsx (91%) diff --git a/src/components/kpi/KPISettings.jsx b/src/components/kpi/KPISettings.jsx index ecec5d4..2ae7b3e 100644 --- a/src/components/kpi/KPISettings.jsx +++ b/src/components/kpi/KPISettings.jsx @@ -6,6 +6,7 @@ import SearchForm from './../search/SearchForm'; import { bu, KPIObjects, KPISubjects } from './../../libs/ht'; import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt, merge } from './../../utils/commons'; import ProfitTable from './SubjectTable/Profit'; +import { toJS } from 'mobx'; const Todo = (props) => { return

TODO

; @@ -28,7 +29,7 @@ export default observer((props) => { const { KPIStore, DictDataStore, date_picker_store: searchFormStore } = useContext(stores_Context); const { sort, initialValue, hides, shows, fieldProps: _fieldProps } = { sort: '', - initialValue: searchFormStore.formValues, + initialValue: '', // searchFormStore.formValues, fieldProps: {}, hides: [], shows: ['DateType', 'years'], @@ -43,15 +44,15 @@ export default observer((props) => {
{ - console.log('invoke kpi setting search'); + // console.log('invoke kpi setting search'); if (typeof onSearchSubmit === 'function') { - onSearchSubmit(obj); + onSearchSubmit(obj, form); } }} /> @@ -63,6 +64,7 @@ export default observer((props) => { tabPosition={'left'} onChange={(sub) => { KPIStore.setSettingSubject(sub); + // onSearchSubmit(searchFormStore.formValuesToSub); }} items={KPISubjects.map((ele, i) => { const SubjectTableComponent = subjectComponents[ele.key]; diff --git a/src/components/kpi/ObjectPanel.jsx b/src/components/kpi/ObjectPanel.jsx index a78c231..7eebc39 100644 --- a/src/components/kpi/ObjectPanel.jsx +++ b/src/components/kpi/ObjectPanel.jsx @@ -1,16 +1,15 @@ -import { useContext } from 'react'; import { observer } from 'mobx-react'; -// import { stores_Context } from '../config'; -import { Table } from 'antd'; import KPISettings from './KPISettings'; import { bu, KPISubjects } from '../../libs/ht'; +const sort = { DateType: 10, years: 11 }; +const yearInitial = {}; const searchFormItemSet = { - 'bu': { shows: ['DateType', 'years', 'HTBusinessUnits'] }, - 'dept': { shows: ['DateType', 'years', 'DepartmentList'], fieldProps: { DepartmentList: { allowClear: true } } }, - 'operator': { shows: ['DateType', 'years', 'DepartmentList'] }, // , 'operator' - 'destination': { shows: ['DateType', 'years', 'destination'] }, - 'country': { shows: ['DateType', 'years', 'country'] }, + 'bu': { shows: ['DateType', 'years', 'HTBusinessUnits'], sort }, + 'dept': { shows: ['DateType', 'years', 'DepartmentList'], sort, fieldProps: { DepartmentList: { allowClear: true,isLeaf: true, show_all: false } }, }, + 'operator': { shows: ['DateType', 'years', 'DepartmentList', 'operator'], fieldProps: { DepartmentList: { allowClear: true, isLeaf: true }, operator: { param: { is_assign: 1 } } }, sort }, + 'destination': { shows: ['DateType', 'years', 'destination'], sort }, + 'country': { shows: ['DateType', 'years', 'country'], sort }, }; export default observer((props) => { diff --git a/src/components/kpi/OverviewPanel.jsx b/src/components/kpi/OverviewPanel.jsx index 49f8760..6e7720c 100644 --- a/src/components/kpi/OverviewPanel.jsx +++ b/src/components/kpi/OverviewPanel.jsx @@ -129,6 +129,7 @@ export default observer((props) => { value: mVal, kpi_id: curObj.kpiDataMapped?.[`M${mIndex}`]?.kpi_id || undefined, key: undefined, + group_date_type: 'month', }; }); return r.concat(allMonth); @@ -143,6 +144,7 @@ export default observer((props) => { start_date: moment([KPIStore.settingYear, 0, 1]).format('YYYY-MM-DD'), end_date: moment([KPIStore.settingYear, 11, 1]).endOf('M').format('YYYY-MM-DD HH:mm'), kpi_id: kpiYear?.kpi_id || undefined, + group_date_type: 'year', }))(dataSource?.[0] || {}); tableData.unshift(yearRow); console.log('sub', tableData, delKpiIds); diff --git a/src/components/kpi/SubjectTable/Profit.jsx b/src/components/kpi/SubjectTable/Profit.jsx index 777adb4..be76b4a 100644 --- a/src/components/kpi/SubjectTable/Profit.jsx +++ b/src/components/kpi/SubjectTable/Profit.jsx @@ -24,11 +24,11 @@ export default observer((props) => { const [editableRowsKeys, setEditableRowKeys] = useState([]); useEffect(() => { setDataSource(KPIStore.pageData); + setEditableRowKeys([]); setEditOpen(false); return () => {}; }, [KPIStore.pageData]); - const PercentInput = useMemo( () => // eslint-disable-next-line react/display-name @@ -84,7 +84,7 @@ export default observer((props) => { title: '年度目标', dataIndex: 'yearValue', valueType: 'digit', - fieldProps: { style: { width: '100%' }, step: 10000*100 }, + fieldProps: { style: { width: '100%' }, step: 10000 * 100 }, formItemProps: { style: { width: '100%' }, }, @@ -138,22 +138,23 @@ export default observer((props) => { value: mVal, kpi_id: curObj.kpiDataMapped?.[`M${mIndex}`]?.kpi_id || undefined, key: undefined, + group_date_type: 'month', }; }); - return r.concat(allMonth); + const yearRow = (({ object, object_name, object_id, subject, date_type, yearValue, kpiYear }) => ({ + object, + object_name, + object_id, + subject, + date_type, + value: yearValue, + start_date: moment([KPIStore.settingYear, 0, 1]).format('YYYY-MM-DD'), + end_date: moment([KPIStore.settingYear, 11, 1]).endOf('M').format('YYYY-MM-DD HH:mm'), + kpi_id: kpiYear?.kpi_id || undefined, + group_date_type: 'year', + }))(curObj); + return r.concat(allMonth, yearRow); }, []); - const yearRow = (({ object, object_name, object_id, subject, date_type, yearValue, kpiYear }) => ({ - object, - object_name, - object_id, - subject, - date_type, - value: yearValue, - start_date: moment([KPIStore.settingYear, 0, 1]).format('YYYY-MM-DD'), - end_date: moment([KPIStore.settingYear, 11, 1]).endOf('M').format('YYYY-MM-DD HH:mm'), - kpi_id: kpiYear?.kpi_id || undefined, - }))(dataSource?.[0] || {}); - tableData.unshift(yearRow); console.log('sub', tableData, 'del:', delKpiIds); // return false; // debug: KPIStore.onSubmit(tableData, { delQueue: delKpiIds }).then((res) => { @@ -180,28 +181,37 @@ export default observer((props) => { date_type: searchFormStore.formValuesToSub.DateType, kpiDataMapped: {}, key: Date.now().toString(32), + group_date_type: 'month', }), {} ); // v.formItemProps.initialValue const makeInitialTable = (e) => { setEditOpen(e); - // todo: 单独设置之后, 清空筛选会导致无法批量设置新的 + // test: 单独设置之后, 清空筛选会导致无法批量设置新的 + const _initialRow = Object.assign({}, initialRow, initialPercentKey); + const _objects = isEmpty(objects) ? curObjectItem?.data || [] : objects; + // console.log('ooo', objects, isEmpty(objects), curObjectItem?.data || []); + const _initialTable = _objects.map((obj) => ({ + ...cloneDeep(_initialRow), + object_name: obj.label, + object_id: obj.value, + key: Date.now().toString(32) + obj.value, + })); + // console.log(_initialRow, 'iiiii'); + const mergePageData = Object.values( + Object.assign( + {}, + _initialTable.reduce((r, v) => ({ ...r, [v.object_name]: v }), {}), + dataSource.reduce((r, v) => ({ ...r, [v.object_name]: v }), {}) + ) + ); if (e && isEmpty(dataSource)) { - const _initialRow = Object.assign({}, initialRow, initialPercentKey); - const _objects = isEmpty(objects) ? (curObjectItem?.data || []) : objects; - console.log('ooo', objects, isEmpty(objects), curObjectItem?.data || []); - const _initialTable = _objects.map((obj) => ({ - ...cloneDeep(_initialRow), - object_name: obj.label, - object_id: obj.value, - key: Date.now().toString(32) + obj.value, - })); - console.log(_initialRow, 'iiiii'); setDataSource(_initialTable); setEditableRowKeys(_initialTable.map((ele) => ele.key)); return false; } - setEditableRowKeys(e ? dataSource.map((ele) => ele.key) : []); + setDataSource(mergePageData); + setEditableRowKeys(e ? mergePageData.map((ele) => ele.key) : []); }; const [delKpiIds, setDelKpiIds] = useState([]); return ( @@ -226,13 +236,11 @@ export default observer((props) => { return [defaultDoms.delete]; }, onDelete: (_key, _row) => { - // console.log('del', _key, _row); const rowKpiIds = (_row?.kpiData || []).map((ele) => ele.kpi_id); rowKpiIds.push(_row?.kpiYear?.kpi_id); setDelKpiIds(rowKpiIds); }, onValuesChange: (record, recordList) => { - // console.log('on edit, onValuesChange',record, recordList); onTableChange(recordList); }, onChange: (editableKeys, editableRows) => { @@ -242,9 +250,14 @@ export default observer((props) => { /> - + + + {!editOpen && } + diff --git a/src/components/kpi/SumProfitPanel.jsx b/src/components/kpi/SubjectTable/SumProfitPanel.jsx similarity index 91% rename from src/components/kpi/SumProfitPanel.jsx rename to src/components/kpi/SubjectTable/SumProfitPanel.jsx index 0818a00..1b5666d 100644 --- a/src/components/kpi/SumProfitPanel.jsx +++ b/src/components/kpi/SubjectTable/SumProfitPanel.jsx @@ -2,8 +2,8 @@ import { useContext } from 'react'; import { observer } from 'mobx-react'; // import { stores_Context } from '../config'; import { Button, Table, Switch, Input, Space, Typography, Row, Col, Spin, Radio, Tabs } from 'antd'; -import SearchForm from './../search/SearchForm'; -import { bu, KPIObjects } from './../../libs/ht'; +import SearchForm from '../../search/SearchForm'; +import { bu, KPIObjects } from '../../../libs/ht'; export default observer((props) => { // const { } = useContext(stores_Context); diff --git a/src/components/search/GroupSelect.jsx b/src/components/search/GroupSelect.jsx index 24daa00..edfb5a4 100644 --- a/src/components/search/GroupSelect.jsx +++ b/src/components/search/GroupSelect.jsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { Select } from 'antd'; import { observer } from 'mobx-react'; -import { groups } from '../../libs/ht'; +import { groups, leafGroup } from '../../libs/ht'; class GroupSelect extends Component { constructor(props) { @@ -9,9 +9,10 @@ class GroupSelect extends Component { } render() { - const { store, mode, value, onChange, show_all, ...extProps } = this.props; + const { store, mode, value, onChange, show_all, isLeaf, ...extProps } = this.props; const _mode = mode || store?.group_select_mode || null; const _show_all = ['tags', 'multiple'].includes(_mode) ? false : show_all; + const options = isLeaf===true ? leafGroup : groups; return (
store.onChange_datetype(value)} onChange={this.handleChange} + {...extProps} > {dateTypes.map((ele) => ( diff --git a/src/components/search/GroupSelect.jsx b/src/components/search/GroupSelect.jsx index edfb5a4..ba897bc 100644 --- a/src/components/search/GroupSelect.jsx +++ b/src/components/search/GroupSelect.jsx @@ -26,7 +26,7 @@ class GroupSelect extends Component { } store?.group_handleChange(value); }} - labelInValue={true} + labelInValue={false} maxTagCount={1} maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`} allowClear={_mode != null} diff --git a/src/components/search/SearchForm.jsx b/src/components/search/SearchForm.jsx index e1abb50..e8051c4 100644 --- a/src/components/search/SearchForm.jsx +++ b/src/components/search/SearchForm.jsx @@ -175,7 +175,7 @@ export default observer((props) => { }; const onValuesChange = (...args) => { const [changedValues, allValues] = args; - console.log('form onValuesChange', Object.keys(changedValues), args); + // console.log('form onValuesChange', Object.keys(changedValues), args); const dest = formValuesMapper(allValues); searchFormStore.setFormValues(allValues); @@ -232,28 +232,28 @@ function getFields(props) { 'HTBusinessUnits', 99, - + ), item( 'businessUnits', 99, - + ), item( 'DepartmentList', 99, - + ), item( 'WebCode', 99, - + ), item( @@ -277,7 +277,7 @@ function getFields(props) { 'DateType', 99, - + , 2 ), diff --git a/src/components/search/SiteSelect.jsx b/src/components/search/SiteSelect.jsx index 3d22f93..2e25905 100644 --- a/src/components/search/SiteSelect.jsx +++ b/src/components/search/SiteSelect.jsx @@ -26,7 +26,7 @@ class SiteSelect extends Component { } store?.handleChange_webcode(value); }} - labelInValue={true} + labelInValue={false} maxTagCount={1} maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`} allowClear={_mode != null} From 176463d6a9b2da7066ff3398af2e38a0688be41f Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 26 Sep 2023 16:56:32 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E5=8F=96=E6=B6=88=20=E8=BF=9B=E5=85=A5?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=9A=84=E9=BB=98=E8=AE=A4=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/kpi/OverviewPanel.jsx | 12 ++++++------ src/stores/DatePickerStore.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/kpi/OverviewPanel.jsx b/src/components/kpi/OverviewPanel.jsx index 6e7720c..e0cac54 100644 --- a/src/components/kpi/OverviewPanel.jsx +++ b/src/components/kpi/OverviewPanel.jsx @@ -20,12 +20,12 @@ export default observer((props) => { const { curObject } = props; const [dataSource, setDataSource] = useState([]); useEffect(() => { - onSearchSubmit({ - object: curObject, - date_type: 'applyDate', - start_date: searchFormStore.start_date.startOf('year').format('YYYY-MM-DD'), - end_date: searchFormStore.end_date.endOf('year').format('YYYY-MM-DD 23:59'), - }); + // onSearchSubmit({ + // object: curObject, + // date_type: 'applyDate', + // start_date: searchFormStore.start_date.startOf('year').format('YYYY-MM-DD'), + // end_date: searchFormStore.end_date.endOf('year').format('YYYY-MM-DD 23:59'), + // }); return () => {}; }, []); diff --git a/src/stores/DatePickerStore.js b/src/stores/DatePickerStore.js index 1609148..eba2f83 100644 --- a/src/stores/DatePickerStore.js +++ b/src/stores/DatePickerStore.js @@ -57,7 +57,7 @@ class DatePickerStore { IncludeTickets: '1', DateType: 'applyDate', Date1: this.start_date.format('YYYY-MM-DD'), - Date2: this.end_date.format('YYYY-MM-DD 23:59'), + Date2: this.end_date.format('YYYY-MM-DD 23:59:59'), }; setFormValues(data){ From 8106d22e287b752980943c44dbcdc1985e116e30 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 26 Sep 2023 17:04:41 +0800 Subject: [PATCH 9/9] =?UTF-8?q?KPI=20=E8=AE=BE=E7=BD=AE:=20=E5=B0=8F?= =?UTF-8?q?=E7=BB=84=E5=8F=96=E6=B6=88=E8=AE=BE=E7=BD=AE=E7=BB=84=E7=BB=87?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E7=9A=84=E5=B0=8F=E7=BB=84,=20=E4=BA=8B?= =?UTF-8?q?=E4=B8=9A=E9=83=A8=E6=8C=89HT=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/kpi/ObjectPanel.jsx | 2 +- src/libs/ht.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/kpi/ObjectPanel.jsx b/src/components/kpi/ObjectPanel.jsx index 7eebc39..2caa53a 100644 --- a/src/components/kpi/ObjectPanel.jsx +++ b/src/components/kpi/ObjectPanel.jsx @@ -16,7 +16,7 @@ export default observer((props) => { const searchProps = searchFormItemSet?.[props.curObject] || {}; return ( <> - + ); }); diff --git a/src/libs/ht.js b/src/libs/ht.js index 85827a6..8c8d152 100644 --- a/src/libs/ht.js +++ b/src/libs/ht.js @@ -118,7 +118,7 @@ export const dataFieldAlias = dataFieldOptions.reduce( export const KPIObjects = [ { key: 'overview', value: 'overview', label: '海纳' }, { key: 'bu', value: 'bu', label: '事业部', data: bu }, - { key: 'dept', value: 'dept', label: '小组', data: groups }, + { key: 'dept', value: 'dept', label: '小组', data: leafGroup }, { key: 'du', value: 'du', label: '销售小组', data: deptUnits }, { key: 'operator', value: 'operator', label: '顾问' }, { key: 'destination', value: 'destination', label: '目的地' },