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 } from '../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 } from './../components/Data'; const { Text } = Typography; const filterFields = [ { key: 'SourceType', label: '来源类型' }, { key: 'productType', label: '产品类型' }, { key: 'country', label: '国籍' }, { key: 'CLI_NO', label: '线路' }, // { key: 'destination', label: '目的地' }, { key: 'COLI_LineClass', label: '页面渠道' }, { key: 'guestGroupType', label: '客群类别' }, { key: 'travelMotivation', label: '出行目的' }, { key: 'startMonth', label: '出行日期-月份' }, { key: 'startYearMonth', label: '出行日期-年月' }, { key: 'applyMonth', label: '预订日期-月份' }, { key: 'applyYearMonth', label: '预订日期-年月' }, { key: 'operatorName', label: '顾问' }, { key: 'WebCode', label: '来源站点' }, { key: 'IsOld_txt', label: '是否老客户' }, { key: 'isCusCommend_txt', label: '是否老客户推荐' }, { key: 'hasOld_txt', label: '老客户(推荐)' }, { key: 'HotelStar', label: '酒店星级' }, { key: 'destinationCountry_AsJOSN', label: '目的地国籍' }, { key: 'destinations_AsJOSN', label: '目的地城市' }, { key: 'RTXF_WB_range', label: '人天消费(外币)' }, { key: 'PPPriceRange', 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]' }, { key: 'SumML_ctxt', label: '毛利' }, ]; const filterFieldsMapped = filterFields.reduce((r, v) => ({ ...r, [v.key]: v }), {}); /** 预设的选项, 只有行 */ const quickOptions = [ { label: ' 来源站点 ', fields: [['WebCode'], []] }, { label: '[ 产品×客群 ]', fields: [['productType', 'guestGroupType'], []] }, { label: '[ 国籍×客群 ]', fields: [['country', 'guestGroupType'], []] }, { label: '[ 客群×目的地国籍 ]', fields: [['guestGroupType', 'destinationCountry_AsJOSN'], []] }, // { label: '[ 国籍×客群 ]×[ ]', fields: [['country', 'guestGroupType'], []] }, ]; // 注意TdCell要提到DataTable作用域外声明 const TdCell = (tdprops) => { // onMouseEnter, onMouseLeave在数据量多的时候,会严重阻塞表格单元格渲染,严重影响性能 const { onMouseEnter, onMouseLeave, ...restProps } = tdprops; return ; }; const pageSetting = { orders: { xField: 'applyDate', yField: 'SumOrder', tableColumns: [ { key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em' }, // { 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' }, { key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' }, { key: 'SumML', title: '毛利', dataIndex: 'SumML', width: '5em' }, // 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' }, // SumML_txt ], childrenColumns: [ { key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em' }, { key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em' }, { 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 { originData } = 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 [dataBeforePick, setDataBeforePick] = useState([]); const [dataBeforeXChange, setDataBeforeXChange] = useState([]); const [dataSource, setDataSource] = useState([]); // const [dataSourceMapped, setDataSourceMapped] = useState({}); const [pivotRow, setPivotRow] = useState({}); const [pivotRowDataSource, setPivotRowDataSource] = 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(); resetX(); resetItemFilter(); return () => {}; }, [pivotDateColumns]); useEffect(() => { if (lineChartX === 'day') { setDataBeforeXChange(dataSource); } return () => {}; }, [dataSource]); useEffect(() => { setCurXfield(formValuesToSub.DateType); setLineConfig({...lineConfig, xField: formValuesToSub.DateType}); return () => {}; }, [formValues]); const detailRefresh = async (obj) => { setLoading(true); DataPivotStore.getDetailData({ ...(obj || formValuesToSub), }, page).then((resData) => { setLoading(false); setRawData(resData); calcDataByDate(resData); resetX(); resetItemFilter(); }); }; /** * 走势的数据 * 汇总 */ const calcDataByDate = (_rawData) => { // console.log(';;;;;', pivotDateColumns); const { data, columnValues, summaryRows, summaryColumns, pivotKeys, summaryMix } = pivotBy(_rawData || rawData, [].concat(pivotDateColumns, [curXfield])); // console.log('data====', data, '\ncolumnValues', columnValues, '\nsummaryRows', summaryRows, '\nsummaryColumns', summaryColumns, '\nsummaryMix', summaryMix); setShowPassCountryTips(pivotKeys.includes('destinationCountry_AsJOSN')); 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]]); }; 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); setRightFields(isEmpty(v) ? filterFields : 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 targetTableProps = { loading: false, // sticky: true, scroll: { x: 1000, y: 400 }, pagination: false, columns: [ ...pivotDateColumns[0].map((ele) => ({ key: ele, title: filterFieldsMapped[ele].label, dataIndex: ele, width: '6em', fixed: 'left' })), ...(isEmpty(pivotDateColumns[1]) ? [].concat(cloneDeep(tableColumns), childrenColumns) : tableColumns), ...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, scroll: { x: 1000, y: 400 }, pagination: false, columns: [ { 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) => {text === '1' ? '是' : '否'}, sorter: (a, b) => b.orderState - a.orderState, }, // { // title: "人数(成/童/婴)", // dataIndex: "COLI_PersonNum", // key: "COLI_PersonNum", // render: (text, record) => ( // // {record.COLI_PersonNum}/{record.COLI_ChildNum}/{record.COLI_BabyNum} // // ), // }, { title: '预计利润', dataIndex: 'ML', key: 'ML', }, { title: '预定时间', dataIndex: 'applyDate', key: 'applyDate', }, { title: '出发日期', dataIndex: 'startDate', key: 'startDate', }, ], }; return (
{ detailRefresh(obj); }} /> {/* extra={} */} 预设: {quickOptions.map((ele, elei) => ( ))} } > {/* todo: 拖拽的操作 */} {/*
{filterFields.map((tag) => ( -1} onChange={(checked) => handleChange(tag.key, checked)} color={'orange'}> {tag.label} ))}
*/} {/* 行: */} 行: {rowFields.length > 0 ? cloneDeep(pivotDateColumnsValues)[0] // .slice(0, rowFields.length) .map((_colArr, _colIndex) => ( {filterFieldsMapped[pivotDateColumns[0][_colIndex]]?.label}: )) : null} {/* 列: */} 列: {showPassCountryTips && } {/* {columnFields.length > 0 ? cloneDeep(pivotDateColumnsValues) .slice(rowFields.length) .map((_colArr, _colIndex) => ( <> {filterFieldsMapped[pivotDateColumns[_colIndex + rowFields.length]]?.label || _colIndex + rowFields.length}: )) : null} */}

走势: {dataFieldAlias[lineConfig.yField].label}

透视汇总表: {dataFieldAlias[lineConfig.yField].label}

`${filterFieldsMapped[ele].label}`).join('×')} {...{ columns: targetTableProps.columns, dataSource: pivotTableDataSource }} /> { return { // 点击行 onClick: (event) => { setPivotRow(record); const thisDetail = rawData.filter((ele) => { return record.keys.includes(String(ele.key)); }); setPivotRowDataSource(thisDetail); }, }; }} />

点击上方表格行查看单行数据的订单明细:{' '} {pivotDateColumns[0].map((ele, ri) => `${filterFieldsMapped[ele].label}: ${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('; ')}

`${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('-')} {...{ columns: detailsTableProps.columns, dataSource: pivotRowDataSource }} />
); });