From f532f138ff12ad7e1ccc97ff08ea79d304e3a9f5 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 25 Apr 2025 10:50:41 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=B8=89=E5=B3=A1,=E9=85=92=E5=BA=97:?= =?UTF-8?q?=20=E5=90=88=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Data.jsx | 20 +++-- src/stores/HotelCruise.js | 51 +++++++++--- src/views/Cruise.jsx | 161 ++++++++++++++++++++++++++------------ src/views/Hotel.jsx | 133 ++++++++++++++++++++----------- 4 files changed, 252 insertions(+), 113 deletions(-) diff --git a/src/components/Data.jsx b/src/components/Data.jsx index 7b433f0..68def75 100644 --- a/src/components/Data.jsx +++ b/src/components/Data.jsx @@ -30,20 +30,24 @@ export const VSTag = (props) => { /** * 导出表格数据存为xlsx + * @property label 文件名字 + * @property columns 表格列 + * @property dataSource 表格数据 + * @property btnTxt 按钮文字 */ -export const TableExportBtn = (props) => { - const output_name = `${props.label}`; +export const TableExportBtn = ({label, columns, dataSource, btnTxt, ...props}) => { + const output_name = `${label}`; const [columnsMap, setColumnsMap] = useState([]); const [summaryRow, setSummaryRow] = useState({}); useEffect(() => { - const r1 = props.columns.reduce((r, v) => ({ + const r1 = columns.reduce((r, v) => ({ ...r, ...(v.children ? v.children.reduce((rc, vc, ci) => ({ ...rc, ...(vc?.titleX ? {[`${v?.titleX || v.title},${vc.titleX}`]: vc.titleX } : {[(v?.titleX || v.title) + (ci || '')]: `${vc?.titleX || vc?.title || ''}`}), }), {}) : {}) }), {}); - const flatCols = props.columns.flatMap((v, k) => + const flatCols = columns.flatMap((v, k) => v.children ? v.children.map((vc, ci) => ({ ...vc, title: `${v?.titleX || v.title}` + (vc?.titleX ? `,${vc.titleX}` : (ci || '')) })) : {...v, title: `${v?.titleX || v.title}`} ); // .filter((c) => c.dataIndex) @@ -56,14 +60,14 @@ export const TableExportBtn = (props) => { // console.log('summaryRow', r1); return () => {}; - }, [props.columns]); + }, [columns]); const onExport = () => { - if (isEmpty(props.dataSource)) { + if (isEmpty(dataSource)) { message.warning('无结果.'); return false; } - const data = props.dataSource.map((item) => { + const data = dataSource.map((item) => { const itemMapped = columnsMap.reduce((sv, kset) => { const render_val = typeof kset?.render === 'function' ? kset.render('', item) : null; const data_val = kset?.dataIndex ? (Array.isArray(kset.dataIndex) ? getNestedValue(item, kset.dataIndex) : item[kset.dataIndex]) : undefined; @@ -88,7 +92,7 @@ export const TableExportBtn = (props) => { disabled={false} onClick={onExport} > - {props.btnTxt || '导出excel'} + {btnTxt || '导出excel'} ); }; diff --git a/src/stores/HotelCruise.js b/src/stores/HotelCruise.js index 0e4894c..3b1ea38 100644 --- a/src/stores/HotelCruise.js +++ b/src/stores/HotelCruise.js @@ -84,10 +84,7 @@ class HotelCruise { this.cruise.loading = true; this.cruise.dataSource = []; const _queryParam = objectMapper(param, paramKeyMapped); - const queryParam = omit({ ...this.searchValuesToSub, ..._queryParam }, [ - 'DepartmentList', 'orderStatus', 'keyword', 'Date1', 'Date2', 'DateDiff1', 'DateDiff2', - 'cruiseDirection', 'cruiseBookType', 'country', 'agency', - ]); + const queryParam = omit({ ...this.searchValuesToSub, ..._queryParam }, Object.keys(paramKeyMapped)); queryParam.Compare = isEmpty(param.DateDiff1) ? '' : '1'; const res = await fetchCruiseData(queryParam); const resCP = @@ -95,13 +92,29 @@ class HotelCruise { ? res : (res || []).map((ele) => ({ ...ele, + // 计算 增长率 = (当前值 - 上次值) / 上次值 * 100 TotalNumPercent: ele.CPTotalNum ? fixTo2Decimals(((ele.TotalNum - ele.CPTotalNum) / ele.CPTotalNum) * 100) : '-', TotalPersonNumPercent: ele.CPTotalPersonNum ? fixTo2Decimals(((ele.TotalPersonNum - ele.CPTotalPersonNum) / ele.CPTotalPersonNum) * 100) : '-', TotalProfitPercent: ele.CPTotalProfit ? fixTo2Decimals(((ele.TotalProfit - ele.CPTotalProfit) / ele.CPTotalProfit) * 100) : '-', })); + const summaryRow = ['TotalNum', 'TotalPersonNum', 'TotalProfit', 'CPTotalNum', 'CPTotalPersonNum', 'CPTotalProfit'].reduce( + (r, skey) => ({ + ...r, + [skey]: resCP.reduce((a, c) => a + c[skey], 0), + }), + { ProductName: '合计' } + ); + const summaryDelta = ['TotalNum', 'TotalPersonNum', 'TotalProfit'].reduce( + (r, skey) => ({ + ...r, + [`${skey}Percent`]: queryParam.Compare === '' ? null : fixTo2Decimals(((summaryRow[skey] - summaryRow[`CP${skey}`]) / summaryRow[`CP${skey}`]) * 100), + }), + {} + ); runInAction(() => { this.cruise.loading = false; this.cruise.dataSource = resCP; + this.cruise.summaryRow = { ...summaryRow, ...summaryDelta }; }); return this.cruise; } @@ -110,10 +123,7 @@ class HotelCruise { this.hotel.loading = true; this.hotel.dataSource = []; const _queryParam = objectMapper(param, paramKeyMapped); - const queryParam = omit({ ...this.searchValuesToSub, ..._queryParam }, [ - 'DepartmentList','orderStatus','keyword','Date1','Date2','DateDiff1','DateDiff2','DateType', - 'hotelStar','hotelRecommandRate','hotelBookType','countryArea', - ]); + const queryParam = omit({ ...this.searchValuesToSub, ..._queryParam }, Object.keys(paramKeyMapped)); queryParam.Compare = isEmpty(param.DateDiff1) ? '0' : '1'; const _res = await fetchHotelData(queryParam); const res = (_res || []).map((ele) => ({ ...ele, RecommendRate_100: fixTo2Decimals(ele.RecommendRate * 100) + '%' })); @@ -127,9 +137,30 @@ class HotelCruise { RecommendRateDelta: fixTo2Decimals((ele.RecommendRate - (ele.CPRecommendRate || 0)) * 100), CPRecommendRate_100: fixTo2Decimals(ele.CPRecommendRate * 100) + '%', })); + const summaryRow = ['TotalNum', 'RecomendNum', ].reduce( + (r, skey) => ({ + ...r, + [skey]: resCP.reduce((a, c) => a + c[skey], 0), + [`CP${skey}`]: resCP.reduce((a, c) => a + c[`CP${skey}`], 0), + }), + { CityName: '合计' } + ); + summaryRow.RecommendRate = fixTo2Decimals(summaryRow.RecomendNum/summaryRow.TotalNum); + summaryRow.RecommendRate_100 = fixTo2Decimals(summaryRow.RecommendRate * 100) + '%'; + summaryRow.CPRecommendRate = fixTo2Decimals(summaryRow.CPRecomendNum/summaryRow.CPTotalNum); + summaryRow.CPRecommendRate_100 = fixTo2Decimals(summaryRow.CPRecommendRate * 100) + '%'; + const summaryDelta = ['TotalNum', 'RecomendNum', ].reduce( + (r, skey) => ({ + ...r, + [`${skey}Percent`]: queryParam.Compare === '0' ? null : fixTo2Decimals(((summaryRow[skey] - summaryRow[`CP${skey}`]) / summaryRow[`CP${skey}`]) * 100), + }), + {} + ); + summaryDelta.RecommendRateDelta = queryParam.Compare === '0' ? undefined : fixTo2Decimals((summaryRow.RecommendRate - (summaryRow.CPRecommendRate || 0)) * 100); runInAction(() => { this.hotel.loading = false; this.hotel.dataSource = resCP; + this.hotel.summaryRow = { ...summaryRow, ...summaryDelta }; }); } @@ -150,8 +181,8 @@ class HotelCruise { this.searchValuesToSub = obj; } - cruise = { loading: false, dataSource: [] }; - hotel = { loading: false, dataSource: [] }; + cruise = { loading: false, dataSource: [], summaryRow: {} }; + hotel = { loading: false, dataSource: [], summaryRow: {} }; resetData = () => { this.results.loading = false; for (const key of Object.keys(this.results)) { diff --git a/src/views/Cruise.jsx b/src/views/Cruise.jsx index 901c94a..58f1db2 100644 --- a/src/views/Cruise.jsx +++ b/src/views/Cruise.jsx @@ -1,70 +1,123 @@ -import React, { Children, useContext, useState } from 'react'; +import React, { useContext } from 'react'; import { observer } from 'mobx-react'; import { stores_Context } from '../config'; -import moment from 'moment'; -import { Row, Col, Table, Select, Space, Typography, Progress, Spin, Divider, Button, Switch } from 'antd'; +import { Row, Col, Table, Space, Typography } from 'antd'; import SearchForm from './../components/search/SearchForm'; -import { VSTag, TableExportBtn } from './../components/Data'; +import { VSTag } from './../components/Data'; import { CruiseAgency } from './../libs/ht'; -const {Text} = Typography; +const { Text } = Typography; export default observer((props) => { - const { sale_store, date_picker_store: searchFormStore } = useContext(stores_Context); - const { customerServicesStore, HotelCruiseStore, date_picker_store } = useContext(stores_Context); - const { loading, dataSource } = HotelCruiseStore.cruise; + const { date_picker_store: searchFormStore } = useContext(stores_Context); + const { HotelCruiseStore, date_picker_store } = useContext(stores_Context); + const { loading, dataSource, summaryRow } = HotelCruiseStore.cruise; const { formValues, siderBroken } = searchFormStore; - const tableSorter = (a, b, colName) => (a[colName] - b[colName]); + const tableSorter = (a, b, colName) => a[colName] - b[colName]; const tableProps = { size: 'small', - bordered: true, pagination: false, + bordered: true, + pagination: false, columns: [ - { title: '产品', dataIndex: 'ProductName', key: 'ProductName' }, + { title: '产品', children: [{ title: summaryRow.ProductName, dataIndex: 'ProductName', key: 'ProductName' }] }, { title: '房间数', - dataIndex: 'TotalNum', - key: 'TotalNum', sorter: (a, b) => tableSorter(a, b, 'TotalNum'), - render: (v, r) => ( - <> - - - {v} - {r.CPTotalNum ? VS {r.CPTotalNum} : null} - - {r.CPTotalNum && } - - - ), + children: [ + { + title: ( + <> + + + {summaryRow.TotalNum} + {summaryRow.TotalNumPercent ? VS {summaryRow.CPTotalNum} : null} + + {summaryRow.TotalNumPercent && } + + + ), + dataIndex: 'TotalNum', + key: 'TotalNum', + render: (v, r) => ( + <> + + + {v} + {r.CPTotalNum ? VS {r.CPTotalNum} : null} + + {r.CPTotalNum && } + + + ), + }, + ], }, - { title: '人数', dataIndex: 'TotalPersonNum', key: 'TotalPersonNum', + { + title: '人数', sorter: (a, b) => tableSorter(a, b, 'TotalPersonNum'), - render: (v, r) => ( - <> - - - {v} - {r.CPTotalPersonNum ? VS {r.CPTotalPersonNum} : null} - - {r.CPTotalNum && } - - - ),}, - { title: '总利润', dataIndex: 'TotalProfit', key: 'TotalProfit', + children: [ + { + title: ( + <> + + + {summaryRow.TotalPersonNum} + {summaryRow.TotalPersonNumPercent ? VS {summaryRow.CPTotalPersonNum} : null} + + {summaryRow.TotalPersonNumPercent && } + + + ), + dataIndex: 'TotalPersonNum', + key: 'TotalPersonNum', + render: (v, r) => ( + <> + + + {v} + {r.CPTotalPersonNum ? VS {r.CPTotalPersonNum} : null} + + {r.CPTotalNum && } + + + ), + }, + ], + }, + { + title: '总利润', sorter: (a, b) => tableSorter(a, b, 'TotalProfit'), - render: (v, r) => ( - <> - - - {v} - {r.CPTotalProfit ? VS {r.CPTotalProfit} : null} - - {r.CPTotalNum && } - - - ), }, + children: [ + { + title: ( + <> + + + {summaryRow.TotalProfit} + {summaryRow.TotalProfitPercent ? VS {summaryRow.CPTotalProfit} : null} + + {summaryRow.TotalProfitPercent && } + + + ), + dataIndex: 'TotalProfit', + key: 'TotalProfit', + render: (v, r) => ( + <> + + + {v} + {r.CPTotalProfit ? VS {r.CPTotalProfit} : null} + + {r.CPTotalNum && } + + + ), + }, + ], + }, // { title: '单订船', dataIndex: '', key: '' }, // { title: '订单含行程', dataIndex: '', key: '' }, // { title: '国籍', dataIndex: '', key: '' }, @@ -83,8 +136,14 @@ export default observer((props) => { }, // 'countryArea', shows: [ - 'DepartmentList', 'orderStatus', 'dates', 'keyword', 'cruiseDirection', 'agency', - 'cruiseBookType', 'country', // 'roomsRange', 'personRange' + 'DepartmentList', + 'orderStatus', + 'dates', + 'keyword', + 'cruiseDirection', + 'agency', + 'cruiseBookType', + 'country', // 'roomsRange', 'personRange' ], sort: { keyword: 101, agency: 110, cruiseDirection: 102, country: 104 }, fieldProps: { @@ -104,7 +163,7 @@ export default observer((props) => {
- record.ProductName} /> +
record.ProductName} /> ); diff --git a/src/views/Hotel.jsx b/src/views/Hotel.jsx index 0c3b8ff..e41e1a6 100644 --- a/src/views/Hotel.jsx +++ b/src/views/Hotel.jsx @@ -7,69 +7,114 @@ import { VSTag, TableExportBtn } from './../components/Data'; const { Text } = Typography; export default observer((props) => { - const { sale_store, date_picker_store: searchFormStore } = useContext(stores_Context); - const { customerServicesStore, date_picker_store, HotelCruiseStore } = useContext(stores_Context); - const { loading, dataSource } = HotelCruiseStore.hotel; + const { date_picker_store: searchFormStore } = useContext(stores_Context); + const { date_picker_store, HotelCruiseStore } = useContext(stores_Context); + const { loading, dataSource, summaryRow } = HotelCruiseStore.hotel; const { formValues, siderBroken } = searchFormStore; - const tableSorter = (a, b, colName) => (a[colName] - b[colName]); + const tableSorter = (a, b, colName) => a[colName] - b[colName]; const tableProps = { size: 'small', bordered: true, pagination: false, columns: [ - { title: '目的地', dataIndex: 'CityName', key: 'CityName' }, + { title: '目的地', children: [{ title: summaryRow.CityName, dataIndex: 'CityName', key: 'CityName' }] }, { title: '总间夜', - dataIndex: 'TotalNum', - key: 'TotalNum', sorter: (a, b) => tableSorter(a, b, 'TotalNum'), - render: (v, r) => ( - <> - - - {v} - {r.CPTotalNum ? VS {r.CPTotalNum} : null} - - {r.CPTotalNum && } - - - ), + children: [ + { + title: ( + <> + + + {summaryRow.TotalNum} + {summaryRow.TotalNumPercent ? VS {summaryRow.CPTotalNum} : null} + + {summaryRow.TotalNumPercent && } + + + ), + dataIndex: 'TotalNum', + key: 'TotalNum', + render: (v, r) => ( + <> + + + {v} + {r.CPTotalNum ? VS {r.CPTotalNum} : null} + + {r.CPTotalNum && } + + + ), + }, + ], }, { title: '主推', - dataIndex: 'RecomendNum', - key: 'RecomendNum', sorter: (a, b) => tableSorter(a, b, 'RecomendNum'), - render: (v, r) => ( - <> - - - {v} - {r.CPRecomendNum ? VS {r.CPRecomendNum} : null} - - {r.CPRecomendNum && } - - - ), + children: [ + { + title: ( + <> + + + {summaryRow.RecomendNum} + {summaryRow.RecomendNumPercent ? VS {summaryRow.CPRecomendNum} : null} + + {summaryRow.RecomendNumPercent && } + + + ), + dataIndex: 'RecomendNum', + key: 'RecomendNum', + render: (v, r) => ( + <> + + + {v} + {r.CPRecomendNum ? VS {r.CPRecomendNum} : null} + + {r.CPRecomendNum && } + + + ), + }, + ], }, { title: '使用比例', - dataIndex: 'RecommendRate_100', - key: 'RecommendRate_100', - render: (v, r) => ( - <> - - - {v} - {r.RecommendRateDelta !== undefined && VS {r.CPRecommendRate_100}} - - {r.RecommendRateDelta !== undefined && } - - - ), + children: [ + { + title: ( + <> + + + {summaryRow.RecommendRate_100} + {summaryRow.RecommendRateDelta ? VS {summaryRow.CPRecommendRate_100} : null} + + {summaryRow.RecommendRateDelta && } + + + ), + dataIndex: 'RecommendRate_100', + key: 'RecommendRate_100', + render: (v, r) => ( + <> + + + {v} + {r.RecommendRateDelta !== undefined && VS {r.CPRecommendRate_100}} + + {r.RecommendRateDelta !== undefined && } + + + ), + }, + ], }, ], };