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 && }
+
+ >
+ ),
+ },
+ ],
},
],
};