You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dashboard/src/views/DataPivot.jsx

869 lines
37 KiB
React

import { useContext, useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { useParams, useLocation } from 'react-router-dom';
import { stores_Context } from '../config';
import { Row, Col, Spin, Table, Select, Typography, Card, Button, Space, Divider, Alert } from 'antd';
import { cloneDeep, groupBy, isEmpty, omit, pick, sortBy, unique, cartesianProductArray, fixTo2Decimals } from '@haina/utils-commons';
import { dataFieldAlias, pivotBy } from '../libs/ht';
import SearchForm from '../components/search/SearchForm';
import { Line } from '@ant-design/plots';
import DateGroupRadio from '../components/DateGroupRadio';
import { TableExportBtn, VSTag } from './../components/Data';
const { Text } = Typography;
const filterFields = [
{
label: '渠道',
options: [
{ key: 'SourceType', value: 'SourceType', label: '来源类型' },
{ key: 'guestGroupType', value: 'guestGroupType', label: '客群类别' },
{ key: 'campaign', value: 'campaign', label: 'PPC广告' },
{ key: 'WebCode', value: 'WebCode', label: '来源站点' },
{ key: 'COLI_LineClass', value: 'COLI_LineClass', label: '页面渠道' }
],
},
{
label: '产品',
options: [
{ key: 'productType', value: 'productType', label: '产品类型' },
{ key: 'CLI_NO', value: 'CLI_NO', label: '线路' },
{ key: 'destinationCountry', value: 'destinationCountry', label: '目的地国家' },
{ key: 'destinations', value: 'destinations', label: '目的地城市' },
{ key: 'HotelStar', value: 'HotelStar', label: '酒店星级' },
],
},
{
label: '客户',
options: [
{ key: 'country', value: 'country', label: '国籍' },
{ key: 'travelMotivation', value: 'travelMotivation', label: '出行目的' },
{ key: 'IsOld_txt', value: 'IsOld_txt', label: '是否老客户' },
{ key: 'isCusCommend_txt', value: 'isCusCommend_txt', label: '是否老客户推荐' },
{ key: 'hasOld_txt', value: 'hasOld_txt', label: '老客户(含推荐)' },
{ key: 'RTXF_WB_range', value: 'RTXF_WB_range', label: '人天消费(外币)' },
{ key: 'customer_types', value: 'customer_types', label: '分销客户' },
],
},
{
label: '业绩',
options: [
{ key: 'operatorName', value: 'operatorName', label: '顾问' },
{ key: 'SumML_ctxt', value: 'SumML_ctxt', label: '毛利' },
{ key: 'startMonth', value: 'startMonth', label: '出行日期-月份' },
{ key: 'startYearMonth', value: 'startYearMonth', label: '出行日期-年月' },
{ key: 'applyMonth', value: 'applyMonth', label: '预订日期-月份' },
{ key: 'applyYearMonth', value: 'applyYearMonth', label: '预订日期-年月' },
{ key: 'PPPriceRange', value: 'PPPriceRange', label: '人均天/单(外币)' },
{ key: 'dealDays_ctxt', value: 'dealDays_ctxt', label: '成团周期(天)' },
{ key: 'applyDays_ctxt', value: 'applyDays_ctxt', label: '预订周期(天)' },
],
},
// { key: 'destination', label: '目的地' },
// { key: 'unitPPPriceRange', label: '人均天(外币)' },
// { key: 'SumML_ctxt1', label: '毛利范围[1W]' },
// { key: 'SumML_ctxt1_5', label: '毛利范围[1.5W]' },
// { key: 'SumML_ctxt2', label: '毛利范围[2W]' },
// { key: 'SumML_ctxt3', label: '毛利范围[3W]' },
// { key: 'SumML_ctxt4', label: '毛利范围[4W]' },
];
const filterFieldsMapped = filterFields.reduce((a, c) => a.concat(c.options) ,[]).reduce((r, v) => ({ ...r, [v.key]: v }), {});
/** 预设的选项, 只有行 */
const quickOptions = [
{ label: ' 来源站点 ', fields: [['WebCode'], []] },
{ label: '[ 产品×客群 ]', fields: [['productType', 'guestGroupType'], []] },
{ label: '[ 国籍×客群 ]', fields: [['country', 'guestGroupType'], []] },
{ label: '[ 客群×目的地国家 ]', fields: [['guestGroupType', 'destinationCountry'], []] },
// { label: '[ 国籍×客群 ]×[ ]', fields: [['country', 'guestGroupType'], []] },
];
// 注意TdCell要提到DataTable作用域外声明
const TdCell = (tdprops) => {
// onMouseEnter, onMouseLeave在数据量多的时候会严重阻塞表格单元格渲染严重影响性能
const { onMouseEnter, onMouseLeave, ...restProps } = tdprops;
return <td {...restProps} />;
};
const calcDelta = (r, key) => !isEmpty(Number(r?.vsData?.[key])) ? fixTo2Decimals((Number(r[key] || 0) - Number(r.vsData[key]))/Number(r.vsData[key]) *100) : null;
const pageSetting = {
orders: {
xField: 'applyDate',
yField: 'SumOrder',
tableColumns: [
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em', sorter: (a,b) => a.SumOrder - b.SumOrder },
// { key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em' },
// { key: 'ConfirmPersonNum', title: '成交人数', dataIndex: 'ConfirmPersonNum', width: '5em' },
// { key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' },
// { key: 'SumML', title: '毛利', dataIndex: 'SumML_txt', width: '5em' },
],
childrenColumns: [
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em', sorter: (a,b) => a.ConfirmOrder - b.ConfirmOrder },
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' },
{ key: 'SumML', title: '毛利', dataIndex: 'SumML', width: '5em', sorter: (a,b) => a.SumML - b.SumML }, // SumML_txt
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em' },
{ key: 'tourdays', title: '团天数', dataIndex: 'tourdays', width: '5em' },
{ key: 'confirmTourdays', title: '✅团天数', dataIndex: 'confirmTourdays', width: '5em' },
{ key: 'SumPersonNum', title: '人数', dataIndex: 'SumPersonNum', width: '5em' },
{ key: 'ConfirmPersonNum', title: '✅人数', dataIndex: 'ConfirmPersonNum', width: '5em' },
],
searchInitial: { DateType: { key: 'applyDate', value: 'applyDate', label: '提交日期' } },
},
trade: {
xField: 'confirmDate',
yField: 'SumML',
yFieldAlias: 'SumML_txt',
tableColumns: [
{ key: 'SumML', title: '毛利', dataIndex: 'SumML', width: '5em', sorter: (a,b) => a.SumML - b.SumML }, // SumML_txt
],
childrenColumns: [
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em', sorter: (a,b) => a.SumOrder - b.SumOrder },
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em', sorter: (a,b) => a.ConfirmOrder - b.ConfirmOrder },
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' },
// { key: 'ResumeOrder', title: '老客户订单数', dataIndex: 'ResumeOrder', width: '5em', render: (_, r) => `${r.ResumeConfirmOrder}/${r.ResumeOrder}` },
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em' },
{ key: 'OrderValue', title: '单个订单价值', dataIndex: 'OrderValue', width: '5em' },
{ key: 'PPPriceRange', title: '人均天(外币)', dataIndex: 'PPPriceRange', width: '5em' },
{ key: 'AvgPPPrice', title: '人均天/单(外币)-平均', dataIndex: 'AvgPPPrice', width: '5em' },
// { key: 'unitPPPrice', title: '人均天(外币)', dataIndex: 'unitPPPrice', width: '5em' },
// { key: 'unitPPPriceRange', title: '人均天区间(外币)', dataIndex: 'unitPPPriceRange', width: '5em' },
{ key: 'confirmDays', title: '成团周期(天)', dataIndex: 'confirmDays', width: '5em' },
{ key: 'applyDays', title: '预定周期(天)', dataIndex: 'applyDays', width: '5em' },
{ key: 'tourdays', title: '团天数', dataIndex: 'tourdays', width: '5em' },
],
searchInitial: { DateType: { key: 'confirmDate', value: 'confirmDate', label: '确认日期' } },
},
};
export default observer((props) => {
const { page } = useParams();
const { pathname } = useLocation();
const { date_picker_store: searchFormStore, orders_store, DataPivotStore } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const { loading, loading2 } = DataPivotStore;
const { originData, originData2 } = DataPivotStore.detailData[page];
const { xField: defaultDateType, yField: defaultValKey, yFieldAlias, tableColumns, childrenColumns, searchInitial } = pageSetting[page];
const [curXfield, setCurXfield] = useState(defaultDateType);
// const [loading, setLoading] = useState(false);
const [rawData, setRawData] = useState(originData || []);
const [rawData2, setRawData2] = useState(originData2 || []);
const [dataBeforePick, setDataBeforePick] = useState([]);
const [dataBeforeXChange, setDataBeforeXChange] = useState([]);
const [dataSource, setDataSource] = useState([]);
// const [dataSourceMapped, setDataSourceMapped] = useState({});
const [pivotRow, setPivotRow] = useState({});
const [pivotRowDataSource, setPivotRowDataSource] = useState([]);
const [pivotRowDataSource2, setPivotRowDataSource2] = useState([]);
const [pivotDataSource, setPivotDataSource] = useState([]);
const [pivotTableDataSource, setPivotTableDataSource] = useState([]);
const [pivotTableColumnSummary, setPivotTableColumnSummary] = useState({}); // 列单选, 只有一组结果
const [pivotDateColumns, setPivotDateColumns] = useState([[], []]);
const [pivotDateColumnsValues, setPivotDateColumnsValues] = useState([[], []]);
const [showPassCountryTips, setShowPassCountryTips] = useState(false);
useEffect(() => {
calcDataByDate(originData, originData2);
resetX();
resetItemFilter();
return () => {};
}, [pivotDateColumns]);
useEffect(() => {
if (lineChartX === 'day') {
setDataBeforeXChange(dataSource);
}
return () => {};
}, [dataSource]);
useEffect(() => {
setCurXfield(formValuesToSub.DateType);
setLineConfig({...lineConfig, xField: formValuesToSub.DateType});
return () => {};
}, [formValues]);
useEffect(() => {
// console.log('🔰', 1);
if (!isEmpty(originData) && !isEmpty(originData2)) {
// console.log('🔰', 2);
setRawData(originData);
setRawData2(originData2);
calcDataByDate(originData, originData2);
resetX();
resetItemFilter();
} else if (isEmpty(originData2)) {
// console.log('🔰', 3);
setRawData(originData);
calcDataByDate(originData);
resetX();
resetItemFilter();
} else {
//
}
return () => {};
}, [originData, originData2]);
const detailRefresh = async (obj) => {
// setLoading(true);
DataPivotStore.getDetailData({
...(obj || formValuesToSub),
}, page).then((resData) => {
// setLoading(false);
// setRawData(originData);
// calcDataByDate(originData);
// resetX();
// resetItemFilter();
});
};
/**
* - 走势的数据
* - 汇总
*/
const calcDataByDate = (_rawData, _rawData2=[]) => {
// console.log(';;;;;', pivotDateColumns);
const { data, columnValues, summaryRows, summaryColumns, pivotKeys, summaryMix } = pivotBy(_rawData, [].concat(pivotDateColumns, [curXfield]));
// console.log('data====', data, '\ncolumnValues', columnValues, '\nsummaryRows', summaryRows, '\nsummaryColumns', summaryColumns, '\nsummaryMix', summaryMix);
setShowPassCountryTips(pivotKeys.includes('destinationCountry'));
setDataBeforePick(data.sort(sortBy(curXfield)));
// 折线图汇总数据, 排序
// const sortMixData = cloneDeep(summaryMix).sort(sortBy(defaultValKey)).reverse();
// setPivotDataSource(sortMixData);
// const sortRank = sortMixData.map(ele => ele.rowLabel);
// 折线图数据
setDataSource(data.sort(sortBy(curXfield)));
// 排名
// .map(ele => ({...ele, rowLabel: `${sortRank.indexOf(ele.rowLabel)+1}).${ele.rowLabel}` }))
// 表格数据
const sortRowData = cloneDeep(summaryRows).sort(sortBy(defaultValKey)).reverse();
setPivotTableDataSource(sortRowData);
setPivotRow({});
setPivotRowDataSource([]);
// 列汇总
const _col1 = pivotDateColumns[1][0] || '';
const _sortByDateOrVal = (_col1.includes('Month') || _col1.includes('Date')) ? _col1 : defaultValKey;
let sortColData = summaryColumns.sort(sortBy(_sortByDateOrVal));
sortColData = _sortByDateOrVal === defaultValKey ? sortColData.reverse() : sortColData;
const colDataMapped = isEmpty(pivotDateColumns[1]) ? sortColData[0] : sortColData.reduce((r, v) => ({...r, [v[pivotDateColumns[1][0]]]: v}), {});
setPivotTableColumnSummary(colDataMapped);
// 行列的选项值
const _r = (pivotDateColumns[0].map(eleR => unique(sortRowData.map(ele => ele[eleR]))));
const _c = (pivotDateColumns[1].map(eleC => unique(sortColData.map(ele => ele[eleC]))));
// console.log('_r', _r, '_c', _c);
setPivotDateColumnsValues([_r, _c, columnValues[2]]);
if (!isEmpty(_rawData2)) {
const { data: data2, columnValues: columnValues2, summaryRows: summaryRows2, summaryColumns: summaryColumns2, pivotKeys: pivotKeys2, summaryMix: summaryMix2 } = pivotBy(_rawData2, [].concat(pivotDateColumns, [curXfield]));
// console.log('data=22===', data2, '\ncolumnValues2', columnValues2, '\nsummaryRows2', summaryRows2, '\nsummaryColumns2', summaryColumns2, '\nsummaryMix2', summaryMix2);
// 表格数据
const sortRowData2 = cloneDeep(summaryRows2).sort(sortBy(defaultValKey)).reverse();
const sortRowData1 = sortRowData.map(row => {
const _diff = sortRowData2.find(r2 => r2.rowLabel === row.rowLabel);
return {...row, vsData: _diff };
});
// console.log('🟢', sortRowData1);
setPivotTableDataSource(sortRowData1);
}
};
const line_config = {
// data: dataSource,
padding: 'auto',
xField: curXfield,
yField: defaultValKey,
seriesField: 'rowLabel',
// xAxis: {
// type: 'timeCat',
// },
yAxis: {
min: 0,
maxTickInterval: 5,
},
meta: {
...cloneDeep(dataFieldAlias),
},
// smooth: true,
label: {}, // 显示标签
legend: {
position: 'right-top',
// title: {
// text: '总合计 ' + dataSource.reduce((a, b) => a + b.SumOrder, 0),
// },
itemMarginBottom: 12, // 垂直间距
},
tooltip: {
customItems: (originalItems) => {
return originalItems.map(ele => ({...ele, valueR: ele.data[defaultValKey]})).sort(sortBy('valueR')).reverse();
},
},
};
const [lineConfig, setLineConfig] = useState(cloneDeep(line_config));
// 透视配置:行列选项
// const [leftFields, setLeftFields] = useState(filterFields);
const [rightFields, setRightFields] = useState(filterFields);
const [rowFields, setRowFields] = useState([]);
const [columnFields, setColumnFields] = useState([]);
const [rowSelection, setRowSelection] = useState([]);
const [columnSelection, setColumnSelection] = useState();
// 预设的选项
const quickOpt = (i) => {
const { fields: pivotFields } = quickOptions[i];
const [row, col] = pivotFields;
setRowSelection(Object.values(pick(filterFieldsMapped, row)));
!isEmpty(col) ? setColumnSelection(filterFieldsMapped[col[0]]) : setColumnSelection([]);
setRowFields(row);
setColumnFields(col);
resetItemFilter();
setPivotDateColumns(pivotFields);
};
const resetFields = () => {
setRowFields([]);
setColumnFields([]);
setRowSelection([]);
setColumnSelection([]);
resetItemFilter();
setPivotDateColumns([[], []]);
};
const handleRowsPick = (v) => {
setRowSelection(v);
const pickKeys = v.map((ele) => ele.key);
setRowFields(pickKeys);
// const leftFieldsMapped = leftFields.reduce((r, v) => ({ ...r, [v.key]: v }), {});
// const _left = omit(filterFieldsMapped, pickKeys);
const _left = structuredClone(filterFields).map(g => ({...g, options: g.options.filter(ele => !pickKeys.includes( ele.key))}));
// console.log('📙', _left);
setRightFields(isEmpty(v) ? filterFields : _left); // Object.values(_left)
// setPivotDateColumns([].concat(pickKeys, columnFields));
setPivotDateColumns([pickKeys, columnFields]);
resetItemFilter();
};
const handleColsPick = (val) => {
setColumnSelection(val);
const pickKeys = isEmpty(val) ? [] : Array.isArray(val) ? val.map((ele) => ele.key) : [val.key];
setColumnFields(pickKeys);
// 列选项, 不改变
// const afterLeft = Object.values(omit(filterFieldsMapped, rowFields));
// setRightFields(afterLeft); // 单选
// const rightFieldsMapped = rightFields.reduce((r, v) => ({ ...r, [v.key]: v }), {});
// const _left = omit(rightFieldsMapped, pickKeys);
// setRightFields(isEmpty(val) ? afterLeft : Object.values(_left)); // 多选
// setPivotDateColumns([].concat(rowFields, pickKeys));
setPivotDateColumns([rowFields, pickKeys]);
resetItemFilter();
};
// 行列的值选项
const [rowsItemValues, setRowsItemValues] = useState();
const [columnsItemValues, setColumnsItemValues] = useState();
const [rowsFilter, setRowsFilter] = useState();
const [columnsFilter, setColumnsFilter] = useState();
const resetItemFilter = () => {
setRowsItemValues(null);
setColumnsItemValues(null);
setRowsFilter(null);
setColumnsFilter(null);
};
const handleFieldsItemPick = (v, columnsIndex, columnsName, actionSeries) => {
const _curFilter = { [columnsName]: actionSeries === 'row' ? v : [v] };
// console.log('handleFieldsItemPick', v, columnsIndex, columnsName, actionSeries, _curFilter);
const _rowsF = actionSeries === 'row' ? { ...rowsFilter, ..._curFilter } : rowsFilter;
const _columnsF = actionSeries === 'col' ? { ...columnsFilter, ..._curFilter } : columnsFilter;
actionSeries === 'row' ? setRowsFilter(_rowsF) : setColumnsFilter(_columnsF);
const currentFilterMerge = {
rows: _rowsF || {},
columns: _columnsF || {},
};
setRowsItemValues(currentFilterMerge.rows);
setColumnsItemValues(currentFilterMerge.columns);
const rowsFilterFields = Object.keys(currentFilterMerge.rows).filter((ele) => currentFilterMerge.rows[ele].length);
const dataMappedByRows = groupBy(dataBeforePick, (row) => rowsFilterFields.map((kk) => `${row[kk]}`).join('=@='));
const rowsFilterKey = isEmpty(rowsFilterFields)
? []
: cartesianProductArray(
Object.values(currentFilterMerge.rows)
.map((kv) => kv.map((kf) => kf.key))
.filter((s) => s.length),
'=@='
);
const afterRowsFilter = isEmpty(rowsFilterFields) ? dataBeforePick : rowsFilterKey.reduce((r, _key) => r.concat(dataMappedByRows[_key]), []);
const columnsFilterFields = Object.keys(currentFilterMerge.columns).filter((ele) => currentFilterMerge.rows[ele].length);
const allFilterValues = [].concat(
rowsFilterFields.reduce((r, v) => r.concat(currentFilterMerge.rows[v]), []),
columnsFilterFields.reduce((r, v) => r.concat(currentFilterMerge.columns[v]), [])
);
const dataMapped = groupBy(afterRowsFilter, (row) => row[columnsName]);
const pickData = isEmpty(v) ? afterRowsFilter : Array.isArray(v) ? v.reduce((r, v) => r.concat(dataMapped[v.value]), []) : dataMapped[v.value];
setDataSource(allFilterValues.length === 0 ? dataBeforePick : pickData.sort(sortBy(curXfield)));
resetX();
};
// 日月年切换
const [lineChartX, setLineChartX] = useState('day');
const [avgLine1, setAvgLine1] = useState(0);
const orderCountDataMapper = { data1: 'data1', data2: undefined };
const orderCountDataFieldMapper = { 'dateKey': curXfield, 'valueKey': defaultValKey, 'seriesKey': 'rowLabel', _f: 'sum' };
const resetX = () => {
setLineChartX('day');
setAvgLine1(0);
};
const onChangeXDateFieldGroup = (value, data, avg1) => {
const { xField, yField, seriesField } = lineConfig;
const groupByDate = data.reduce((r, v) => {
(r[v[xField]] || (r[v[xField]] = [])).push(v);
return r;
}, {});
const _data = Object.keys(groupByDate).reduce((r, _d) => {
const xAxisGroup = groupByDate[_d].reduce((a, v) => {
(a[v[seriesField]] || (a[v[seriesField]] = [])).push(v);
return a;
}, {});
Object.keys(xAxisGroup).map((_group) => {
const summaryVal = xAxisGroup[_group].reduce((rows, row) => rows + row[yField], 0);
r.push({ ...xAxisGroup[_group][0], [yField]: summaryVal });
return _group;
});
return r;
}, []);
// .map((row) => ({ [xField]: row[xField], [yField]: row[yField], [seriesField]: row[seriesField], rowX: row.dateRange[0] }));
setLineChartX(value);
setDataSource(_data);
setAvgLine1(avg1);
};
const renderVS = (v, r, key) => {
const delta = calcDelta(r, key);
return <>
<Space direction={'vertical'}>
<span>
{v || 0}
{r?.vsData?.[key] ? <span type="secondary"> VS {r.vsData[key]}</span> : null}
</span>
{delta && <VSTag diffPercent={delta} />}
</Space>
</>;
};
const targetTableProps = {
loading: false,
// sticky: true,
scroll: { x: 1000, y: 400 },
pagination: false,
columns: [
{ title: '#', dataIndex: 'index', key: 'index', render: (text, record, index) => index + 1, width: '3rem', fixed: 'left' },
...pivotDateColumns[0].map((ele) => ({ key: ele, title: filterFieldsMapped[ele].label, dataIndex: ele, width: '6em', fixed: 'left', sorter: (a, b) => (a[ele] || '').localeCompare(b[ele]) })),
...(isEmpty(pivotDateColumns[1])
? [].concat(cloneDeep(tableColumns), childrenColumns).map((th) => ({ ...th, render: (v, r) => renderVS(v, r, th.dataIndex) }))
: tableColumns.map((th) => ({ ...th, render: (v, r) => renderVS(v, r, th.dataIndex) }))),
...pivotDateColumns[1].map((ele) => ({
key: ele,
title: filterFieldsMapped[ele].label,
align: 'left',
className: 'p-s1',
children: cloneDeep(pivotDateColumnsValues[1][0] || []).map((col) => ({
key: col,
title: `${col || '(空)'}: ${pivotTableColumnSummary[col]?.[defaultValKey]}`,
dataIndex: ['columns', col, defaultValKey || yFieldAlias],
width: '6em',
})),
})),
],
};
const detailsTableProps = {
loading: false,
// sticky: true,
size: 'small',
scroll: { x: 1000, y: 400 },
pagination: false,
columns: [
{ title: '#', dataIndex: 'index', key: 'index', render: (text, record, index) => index + 1, width: '3rem' },
{
title: '订单号',
dataIndex: 'o_id',
key: 'o_id',
},
{
title: '来源站点',
dataIndex: 'WebCode',
key: 'WebCode',
},
{
title: '页面渠道',
dataIndex: 'COLI_LineClass',
key: 'COLI_LineClass',
},
{
title: '来源类型',
dataIndex: 'SourceType',
key: 'SourceType',
},
{
title: '客群类别',
dataIndex: 'guestGroupType',
key: 'guestGroupType',
},
{
title: '成行',
dataIndex: 'orderState',
key: 'orderState',
render: (text, record) => <span>{text === '1' ? '是' : '否'}</span>,
sorter: (a, b) => b.orderState - a.orderState,
},
// {
// title: "人数(成/童/婴)",
// dataIndex: "COLI_PersonNum",
// key: "COLI_PersonNum",
// render: (text, record) => (
// <span>
// {record.COLI_PersonNum}/{record.COLI_ChildNum}/{record.COLI_BabyNum}
// </span>
// ),
// },
{
title: '预计利润',
dataIndex: 'ML',
key: 'ML',
},
{
title: '预定时间',
dataIndex: 'applyDate',
key: 'applyDate',
},
{
title: '出发日期',
dataIndex: 'startDate',
key: 'startDate',
},
],
};
return (
<div key={pathname}>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...orders_store.searchValues,
...searchInitial,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'dates'], // 'country'
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple', col: 5 },
WebCode: { show_all: false, mode: 'multiple', col: 5 },
dates: { hide_vs: false },
},
}}
onSubmit={(_err, obj, form, str) => {
detailRefresh(obj);
}}
/>
</Col>
</Row>
{/* extra={<Button type="link">重置</Button>} */}
<Card
size={'small'}
title={'透视选项'}
extra={
2 years ago
<Row gutter={3}>
<Col flex={'0 0 auto'}>
<Text type={'secondary'}>预设:</Text>
</Col>
{quickOptions.map((ele, elei) => (
<Col key={ele.label}>
<Button key={ele.label} onClick={() => quickOpt(elei)} type={'primary'} ghost size={'small'}>
{ele.label}
</Button>
</Col>
))}
<Col>
<Button key={'reset-quick'} onClick={resetFields} type={'primary'} ghost size={'small'}>
重置
</Button>
</Col>
2 years ago
</Row>
}
>
<Row gutter={16}>
<Col span={24}>
{/* todo: 拖拽的操作 */}
{/* <div className='p-s1' style={{border: '1px solid #d9d9d9 '}}>
{filterFields.map((tag) => (
<Tag key={tag.key} checked={selectedTags.indexOf(tag.key) > -1} onChange={(checked) => handleChange(tag.key, checked)} color={'orange'}>
{tag.label}
</Tag>
))}
</div> */}
</Col>
2 years ago
{/* 行: */}
<Col md={12} sm={24} xs={24}>
<Row gutter={8} align={'middle'}>
<Col flex={'4em'} align={'end'}>
<Text strong>: </Text>
</Col>
<Col flex={'auto'}>
<Select
labelInValue
mode={'multiple'}
style={{ width: '100%' }}
placeholder={`选择`}
onChange={(v) => handleRowsPick(v)}
value={rowSelection}
maxTagCount={2}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
options={filterFields}
>
{/* {filterFields.map((ele) => (
<Select.Option key={ele.key} value={ele.key}>
{ele.label}
</Select.Option>
))} */}
</Select>
</Col>
<Col span={24}>
{rowFields.length > 0
? cloneDeep(pivotDateColumnsValues)[0]
// .slice(0, rowFields.length)
.map((_colArr, _colIndex) => (
<Row gutter={8} key={filterFieldsMapped[pivotDateColumns[0][_colIndex]]?.key || _colIndex}>
<Col flex={'5em'} align={'end'}>
<Text strong>{filterFieldsMapped[pivotDateColumns[0][_colIndex]]?.label}: </Text>
</Col>
<Col flex={'auto'}>
<Select
size={'small'}
labelInValue
// mode={_colIndex === 0 ? 'multiple' : null}
mode={'multiple'}
style={{ width: '100%' }}
placeholder={`选择`}
onChange={(v) => handleFieldsItemPick(v, _colIndex, pivotDateColumns[0][_colIndex], 'row')}
value={rowsItemValues?.[pivotDateColumns[0][_colIndex]]}
maxTagCount={1}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
>
{pivotDateColumnsValues[0][_colIndex].map((ele) => (
<Select.Option key={ele} value={ele}>
{ele}
</Select.Option>
))}
</Select>
</Col>
</Row>
))
: null}
</Col>
</Row>
</Col>
2 years ago
{/* 列: */}
<Col md={12} sm={24} xs={24} style={{ borderLeft: '1px solid #d9d9d9' }}>
<Row gutter={8} align={'middle'}>
2 years ago
<Col flex={'4em'} align={'end'}>
<Text strong>: </Text>
</Col>
<Col flex={'auto'}>
<Select
labelInValue
// mode={'multiple'}
style={{ width: '100%' }}
placeholder={`选择`}
onChange={(v) => handleColsPick(v)}
value={columnSelection}
maxTagCount={2}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
options={rightFields}
>
{/* {rightFields.map((ele) => (
<Select.Option key={ele.key} value={ele.key}>
{ele.label}
</Select.Option>
))} */}
</Select>
</Col>
<Col span={24}>
<Row gutter={16} align={'bottom'} className="mt-1">
<Col span={24}>{showPassCountryTips && <Alert message="途径的国家将会重复计算" type="warning" showIcon />}</Col>
</Row>
{/* {columnFields.length > 0
? cloneDeep(pivotDateColumnsValues)
.slice(rowFields.length)
.map((_colArr, _colIndex) => (
<>
<Row gutter={8} key={_colArr.join('_')}>
<Col flex={'5em'} align={'end'}>
<Text strong>{filterFieldsMapped[pivotDateColumns[_colIndex + rowFields.length]]?.label || _colIndex + rowFields.length}: </Text>
</Col>
<Col flex={'auto'}>
<Select
size={'small'}
labelInValue
mode={_colIndex + rowFields.length === 0 ? 'multiple' : null}
style={{ width: '100%' }}
placeholder={`选择`}
onChange={(v) => handleFieldsItemPick(v, _colIndex + rowFields.length, pivotDateColumns[_colIndex + rowFields.length], 'col')}
// value={sale_store.salesTrade.pickSales}
maxTagCount={1}
maxTagPlaceholder={(omittedValues) => ` + ${omittedValues.length} 更多...`}
allowClear={true}
>
{pivotDateColumnsValues[_colIndex + rowFields.length].map((ele) => (
<Select.Option key={ele} value={ele}>
{ele}
</Select.Option>
))}
</Select>
</Col>
</Row>
</>
))
: null} */}
</Col>
</Row>
</Col>
</Row>
</Card>
<section>
<Row gutter={16} justify={'space-between'} className="mb-1">
<Col flex={'auto'}>
<h3>
走势: <span style={{ fontSize: 'smaller' }}>{dataFieldAlias[lineConfig.yField].label}</span>
</h3>
</Col>
2 years ago
<Col style={{ textAlign: 'right' }} align={'end'}>
<DateGroupRadio
visible={true}
dataRaw={{ data1: dataBeforeXChange }}
onChange={onChangeXDateFieldGroup}
value={lineChartX}
dataMapper={orderCountDataMapper}
fieldMapper={orderCountDataFieldMapper}
/>
</Col>
</Row>
<Spin spinning={loading}>
<Line {...lineConfig} data={dataSource} />
</Spin>
</section>
<section>
<Spin spinning={loading}>
{/* <h3>
透视汇总表: <span style={{ fontSize: 'smaller' }}>{dataFieldAlias[lineConfig.yField].label}</span>
</h3> */}
<div style={{ display: 'flex' }}>
<Divider orientation="left" style={{ flex: '1', minWidth: 'unset' }}>
透视汇总表: <span style={{ fontSize: 'smaller' }}>{dataFieldAlias[lineConfig.yField].label}</span>
</Divider>
{loading2 && <Spin size="small">正在获取对比数据</Spin>}
<Divider orientation="right" style={{ flex: '1', minWidth: 'unset' }}>
<TableExportBtn
label={'pivot-' + pivotDateColumns[0].map((ele, ri) => `${filterFieldsMapped[ele].label}`).join('×')}
{...{ columns: targetTableProps.columns, dataSource: pivotTableDataSource }}
/>
</Divider>
</div>
<Table
{...targetTableProps}
dataSource={pivotTableDataSource}
components={{ body: { cell: TdCell } }}
onRow={(record) => {
return {
// 点击行
onClick: (event) => {
setPivotRow(record);
const thisDetail = rawData.filter((ele) => {
return record.keys.includes(String(ele.key));
});
setPivotRowDataSource(thisDetail);
const thisDetail2 = rawData2.filter((ele) => {
return (record?.vsData?.keys || []).includes(String(ele.key));
});
setPivotRowDataSource2(thisDetail2);
},
};
}}
/>
</Spin>
</section>
<section>
<Spin spinning={loading}>
{/* <h3>
点击上方表格行查看单行数据的订单明细:{' '}
<span style={{ fontSize: 'smaller' }}>{pivotDateColumns[0].map((ele, ri) => `${filterFieldsMapped[ele].label}: ${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('; ')}</span>
</h3> */}
<div style={{ display: 'flex' }}>
<Divider orientation="left" style={{ flex: '1', minWidth: 'unset' }}>
点击上方表格行查看单行数据的订单明细:{' '}
<span style={{ fontSize: 'smaller' }}>{pivotDateColumns[0].map((ele, ri) => `${filterFieldsMapped[ele].label}: ${pivotRow[pivotDateColumns[0][ri]] || '(空)'}`).join('; ')}</span>
</Divider>
{/* <Divider orientation="right" style={{ flex: '1', minWidth: 'unset' }}>
<TableExportBtn
label={'pivot-' + pivotDateColumns[0].map((ele, ri) => `${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('-')}
{...{ columns: detailsTableProps.columns, dataSource: pivotRowDataSource }}
/>
</Divider> */}
</div>
<Table
{...detailsTableProps}
dataSource={pivotRowDataSource}
components={{ body: { cell: TdCell } }}
title={() => (
<>
{formValuesToSub.Date1} ~ {formValuesToSub.Date2.substring(10, -1)}
<TableExportBtn
label={'pivot-' + pivotDateColumns[0].map((ele, ri) => `${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('-')}
{...{ columns: detailsTableProps.columns, dataSource: pivotRowDataSource }}
/>
</>
)}
/>
{formValuesToSub.DateDiff1 && (
<Table
{...detailsTableProps}
dataSource={pivotRowDataSource2}
components={{ body: { cell: TdCell } }}
title={() => (
<>
{formValuesToSub.DateDiff1} ~ {formValuesToSub.DateDiff2.substring(10, -1)}
<TableExportBtn
label={'pivot2-' + pivotDateColumns[0].map((ele, ri) => `${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('-')}
{...{ columns: detailsTableProps.columns, dataSource: pivotRowDataSource2 }}
/>
</>
)}
/>
)}
</Spin>
</section>
</div>
);
});