/** * * 轻量化报表通用配置页面 * create by ChenLong on 2022/6/22 * * 功能路径:src\pages\product\ReportsManage\ReportsManage.js * 菜单参数列表:*变量名*(变量说明,数据类型,是否必填,取值范围) * * @changelog: * editComponentVisible && detailsComponentVisible 共同作用组件的显示 */ /** * @description: 功能描述:参考台账概念,重新定义 * @tips: 1. 如果需要对字段进行处理,增加功能之类的。需要提前确定返回值的类型. * 2. 如果要用customerState来控制页面按钮等,需要按照给定的权限值进行配置 * @params: <ReportManage * params={{reportName,pageSize,filterFields,filterValues,state,customerState}}> * * * * reportName: 报表名称; * pageSize: 按需配置,默认加载100; * * * * ***************filterFields/filterValues多用于以组件的形式嵌入,将需要过滤的条件传入******************** * * * * filterFields: 需要默认加载的过滤条件的field,用|分割,与filterValues一一对应; * filterValues: * * * 需要默认加载的过滤条件的value,用|分割,与filterFields一一对应;当某个字段的filterValues为多个时,控件必须配置成多选才可生效; * * * * ************************************************************************************************* * * * * state: delete|edit|scan 各种权限; * customerState: * * * ['filters','sortBtn','exportBtn','editBtn','deleteBtn','pagination']; * * sortFields: * * '排序字段1,排序字段2,排序字段3' * * * * ---------------------------------------权限过滤----------------------------------------- * * * * permissionType: 部门|站点|用户 * permissionField: 【字段名】 * * * * --------------------------------------------------------------------------------------- * @config: * 【数值】 [prefix]_d%|0.00|_d%[suffix]|金额 = 前缀|精度|后缀|金额类的数据(千分位),可分别设置。 * 【标签】 split=, * * 分隔符。 * * 【功能】 功能配置框内,配置需要跳转功能所需参数,type、url是必须功能,需要type判断类型,需要通过url去解析加载组件。 * @type: * 【文本】普通文本 * 【数值】数值类型的文本 * 【标签】文本渲染成标签,具有不同颜色; * 【功能】“功能”会在当前页内去展示,会卸载掉列表页,加载功能组件。配置 type + * * * url + 自定义字段 的配置项,自行解析加载即可; * -------------------- 待需求提出后开发 ----------------- * * * * 【链接】内链外链,点击可跳转;配置规则:配置链接即可; * 【弹窗】modal弹窗弹出,弹窗内的具体业务自行配置;配置规则:[function_name];[...params]; * * * * ------------------------------------------------------ * 【附件】 * @table: * 表头:表头需要支持多级表头、合并; * 列:列支持设置筛选; * 固定行、固定列:可根据配置生成固定行、列; * @control: * 固定筛选:拥有固定筛选框,根据配置显示可搜索字段; * * * * 可配置筛选框:根据字段配置,将字段设置成筛选条件,枚举出该字段所有值,提供用户进行选择,然后进行筛选;筛选框具体形态可根据配置字段来渲染; * 导出功能:各类导出功能按钮; * * * * 时间筛选框:单表唯一;需要变更,支持多时间参数的筛选 * @state: 参考台账权限 delete 全部权限 * edit 除删除外的权限 * scan 查看权限 */ import React, { useState, useEffect, useRef } from 'react'; import { Row, Button, Tag, message, Form, Space, Modal, Select, Table, Dropdown, Menu, Spin, Popconfirm, Tooltip, } from 'antd'; import { SortAscendingOutlined, MinusCircleOutlined, ExportOutlined, FormOutlined, PlusOutlined, DeleteOutlined, QuestionCircleOutlined, HeartTwoTone, HeartOutlined, DownOutlined, } from '@ant-design/icons'; import BasicTable from '@wisdom-components/basictable'; import ReturnControlComponent from './Components/Control'; import { reportService } from '../api/index'; import style from './ReportsManage.less'; import { exportAccountData } from '../api/service/report'; import extraComponents from './extra/extraComponents'; import DatePickerGroup from '../components/DatePickerGroup'; import moment from 'moment'; import DetailsComponent from './extra/detailsComponent'; import { handleNumber, handleTag, handleText, handleLink, handleWidget, handleModal, handleDateString, handleDateTimeString, handlePageSize, handleSortFields, handleNumberTag, } from './utils/handlers'; import { hasMoney, isArray, isNumber, isString, returnHandledNumber } from './utils/utils'; import { connect } from 'react-redux'; import ReportEditForm from './ReportEditForm'; import { exportJPG } from '../api/service/report'; const ControlsType = ['下拉', '多选', '日期']; const fieldSplitStr = '-'; // fieldGroup用来分割 const { Option } = Select; const dateFormat = 'YYYY-MM-DD'; // 日期格式化 const initDateModel = 'all'; let timer = null; const PERMISSION = { delete: [ 'addBtn', 'filters', 'pagination', // 操作条按钮 'sortBtn', 'exportBtn', // 操作列 'editBtn', 'deleteBtn', ], edit: [ 'addBtn', 'filters', 'pagination', 'sortBtn', 'exportBtn', // 操作列 'editBtn', ], scan: ['filters', 'pagination', 'sortBtn'], }; const USER_ID = window.globalConfig.userInfo.OID; const MODEL = ['all', 'year', 'quarter', 'month', 'week', 'day']; const ReportsManage = (props) => { const { reportName, pageSize, filterFields, filterValues, state, customState, sortFields, permissionType, permissionField, } = props.params; const handleCustomerState = (customState) => { if (isArray(customState)) { return customState; } else if (isString(customState)) { return customState.split(','); } }; const permission = customState ? handleCustomerState(customState) : PERMISSION[state || 'delete']; const tableWrapperRef = useRef(); const controlRef = useRef(); if (!reportName) return ( <div className={style.lackParams}>未配置reportName,请完善配置并重新登陆后查看页面!</div> ); const [isInit, setIsInit] = useState(true); const [firstToGetData, setFirstToGetData] = useState(false); const [tableStruct, setTableStruct] = useState([]); // 临时使用,看后续是否需要保留 const [columns, setColumns] = useState([]); // 表头设置 const [tableData, setTableData] = useState([]); // 表数据 const [reportConfigs, setReportConfigs] = useState([]); // 表设置 const [pagination, setPagination] = useState({ current: 1, total: 0, pageSize: handlePageSize(pageSize) || 100, pageSizeOptions: [...new Set([20, 50, 100].concat(handlePageSize(pageSize)))] .filter((item) => Number(item)) .sort((a, b) => Number(a) - Number(b)), showQuickJumper: true, showSizeChanger: true, onShowSizeChange: (current, size) => { let _pagination = { ...pagination }; _pagination.current = current; _pagination.pageSize = size; setPagination(_pagination); getData(_pagination); }, onChange: (current, size) => { let _pagination = { ...pagination }; _pagination.current = current; _pagination.pageSize = size; setPagination(_pagination); getData(_pagination); }, }); const [controls, setControls] = useState([]); // 用来存储操作控件 const [searchContent, setSearchContent] = useState(''); // 搜索框内的值 const [searchPlaceholder, setSearchPlaceholder] = useState([]); // 搜索框的placeholder const [filterObject, setFilterObject] = useState({}); // 存控制条中,选了筛选条件的值 const [modalVisible, setModalVisible] = useState(false); const [allSortFields, setAllSortFields] = useState([]); // 设置所有列表 const [selectedSortFields, setSelectedSortFields] = useState([ { label: '主要排序', value: '', sort: 'asc', }, ]); const [summaryArray, setSummaryArray] = useState([]); // 总结栏,包括小计、总计 const [tableY, setTableY] = useState(500); const [tableX, setTableX] = useState(1820); const [listHeight, setListHeight] = useState(0); const [tableHeaderLevel, setTableHeaderLevel] = useState(null); const [exportLoading, setExportLoading] = useState(false); const [extraModal, setExtraModal] = useState(false); const [hasTotal, setHasTotal] = useState(false); const [hasSinglePage, setHasSinglePage] = useState(false); const [tableLoading, setTableLoading] = useState(false); const [mergeObject, setMergeObject] = useState({}); // const const [timeFrom, setTimeFrom] = useState(moment().startOf(initDateModel).format(dateFormat)); const [timeTo, setTimeTo] = useState(moment().endOf(initDateModel).format(dateFormat)); const [extra, setExtra] = useState(<></>); const [sortModalVisible, setSortModalVisible] = useState(false); const [currentReportId, setCurrentReportId] = useState(null); const [hasDatePicker, setHasDatePicker] = useState(''); const [defaultDateConfig, setDefaultDateConfig] = useState({ defaultModel: 'year', defaultDate: null, }); const [dateModel, setDateMode] = useState('all'); const [detailsComponentVisible, setDetailsComponentVisible] = useState(false); // 是否显示详情组件 const [modalType, setModalType] = useState(''); const [currentData, setCurrentData] = useState({}); // 设置当前编辑数据 const [sorterObject, setSorterObject] = useState({}); const [detailConfig, setDetailConfig] = useState({ url: '', type: '', params: {} }); const [controlsHeight, setControlsHeight] = useState(44); const [getNewData, setGetNewData] = useState(false); const [isLocalDataSource, setIsLocalDataSource] = useState(0); // 如果是本地数据源,那么值为0;反之,则是外部数据源 const menu = () => { const _item = [ { label: ( <Button size="middle" loading={exportLoading} type="text" onClick={() => exportModule('pdf', 'pdf')} icon={<ExportOutlined />} > 导出pdf </Button> ), key: 'exportPdf', }, { label: ( <Button size="middle" loading={exportLoading} type="text" onClick={() => exportModule('excel', 'xls')} icon={<ExportOutlined />} > 导出Excel </Button> ), key: 'exportExcel', }, { label: ( <Button size="middle" loading={exportLoading} type="text" onClick={() => exportImage()} icon={<ExportOutlined />} > 导出JPG </Button> ), key: 'excelPdf', }, ]; return <Menu items={_item} />; }; const exportModule = (type, extension) => { setExportLoading(true); let _data = addFilterAndSearchParams({ reportName, pageIndex: 0, pageSize: 0, userId: USER_ID, }); exportAccountData({ responseType: 'blob' }, { exportType: type }, _data) .then((res) => { if (res && res.code === -1) return message.error(res.msg); const url = window.URL.createObjectURL( new Blob([res], { type: 'application/octet-stream;charset=UTF-8' }), ); const a = document.createElement('a'); a.href = url; a.target = '_blank'; a.download = `${reportName}${moment().format('YYYY-MM-DD-HH-mm-ss').replaceAll('-', '')}.` + extension; a.click(); a.remove(); setExportLoading(false); }) .catch((err) => { setExportLoading(false); }); }; const exportImage = () => { let _data = addFilterAndSearchParams({ reportName, pageIndex: 0, pageSize: 0, userId: USER_ID, }); exportJPG({ responseType: 'blob' }, _data).then((res) => { if (res && res.code === -1) return message.error(res.msg); const url = window.URL.createObjectURL( new Blob([res], { type: 'application/zip;charset=UTF-8' }), ); const a = document.createElement('a'); a.href = url; a.target = '_blank'; a.download = `${reportName}${moment().format('YYYY-MM-DD-HH-mm-ss').replaceAll('-', '')}.zip`; a.click(); a.remove(); setExportLoading(false); }); }; const searchData = (e) => { getData(pagination); }; const controlSelectChange = (fieldAlias, e) => { let _filterArray = { ...filterObject }; _filterArray[fieldAlias] = e; setFilterObject(_filterArray); }; const searchInputChange = (e) => { setSearchContent(e.target.value); }; const setConfig = (config) => { getControlsBarConfig(config); getTableLevel(config); }; const addFilterAndSearchParams = (data) => { let _data = { ...data }; // 搜索框是否有值 if (searchContent) _data.content = searchContent; // filterObject是存起来的控制栏的过滤条件 let _filters = Object.keys(filterObject) .filter((key) => { let _value = filterObject[key]; return (isString(_value) && _value) || (isArray(_value) && _value.length); }) .map((key) => { let _value = filterObject[key]; if (isString(_value) && _value) return { fieldAlias: key, fieldValue: _value, }; if (isArray(_value) && _value.length) return { fieldAlias: key, fieldValue: _value.join(','), }; return false; }); // 加上时间过滤参数 if (dateModel !== 'all' && hasDatePicker && timeFrom && timeTo) { _filters.push({ fieldAlias: hasDatePicker, fieldValue: `${timeFrom} 00:00:00,${timeTo} 23:59:59`, }); } // 合并手动传入的filters;当配置为筛选框时,筛选框选择值后,会优先取筛选框值 if (filterFields && !_filters.find((item) => item.fieldAlias === filterFields)) { let _customerFilterArray = filterFields.split('|'); let _customerValueArray = filterValues.split('|'); _customerFilterArray.forEach((item, index) => { _filters.push({ fieldAlias: item, fieldValue: _customerValueArray[index] || '', }); }); } // 表格上的自定义排序的按钮 if (sorterObject && sorterObject.order) { _data.sortFields = `${sorterObject.columnKey} ${ sorterObject.order === 'ascend' ? 'asc' : 'desc' }`; } // 增加权限过滤的参数 if (permissionType && permissionField) { _data.filterType = permissionType; _data.filterField = permissionField; } // 并入 _data if (_filters.length) _data.filters = _filters; return _data; }; // 排序字符串处理成数组 const handleSortString = (sortString) => { // selectedSortFields let _sortStringArray = sortString.split(',').map((item, index) => { let _item = item.split(' '); if (index === 0) { return { label: '主要排序', value: _item[0].replace(/\[|\]/g, ''), sort: _item[1], }; } else { return { label: '次要排序', value: _item[0].replace(/\[|\]/g, ''), sort: _item[1], }; } }); setSelectedSortFields(_sortStringArray); }; /** @description: 根据是否向下合并处理数据,返回合并的key的数组 */ const returnMergeArray = (config) => { return config.filter((item) => item.isMerge).map((item) => item.fieldAlias); }; /** @description: 根据配置和数据,计算出该合并的字段和每一行是否合并 */ const handleDataToGetRowSpanArray = (config, data) => { let _arr = returnMergeArray(config); let _merge = {}; // _merge:{爱好:[[0,3],[3,5]]} _arr.forEach((key) => { _merge[key] = {}; let _currentIndex = 0; data.forEach((item, index) => { if (index > 0) { if (item[key] === data[index - 1][key]) { _merge[key][_currentIndex] += 1; _merge[key][index] = 0; } else { _currentIndex = index; _merge[key][index] = 1; } } else { _merge[key][0] = 1; } }); }); return _merge; }; const handleData = () => {}; const getData = (pagination) => { setTableLoading(true); const { pageSize, current } = pagination; // 搜索条件附加到params let _data = addFilterAndSearchParams({ reportName: reportName, pageIndex: current, pageSize: pageSize, userId: USER_ID, }); // sortFields reportService .getReportInfo(_data) .then((res) => { if (res.code === 0) { let _reportDetails = res.data?.reportDetails; let _statisticalValues = res.data?.statisticalValues; // webAPIData 存在值时使用webAPIData,否则使用原始数据 let _tableData = res.data.webAPIData?.webAPIData ? JSON.parse(res.data.webAPIData.webAPIData) : res.data?.data?.list.map((item) => { if (Object.prototype.toString.call(item) === '[object String]') { return JSON.parse(item); } return item; }) || []; let _sortString = res.data.sortString; if (isInit) { setIsInit(false); } setIsLocalDataSource(res.data.sourcesId); getTableSummaryConfig(_reportDetails, _statisticalValues); getTableHeaderConfig(_reportDetails, _tableData); let _pagination = { ...pagination }; _pagination.total = res.data.webAPIData?.webAPIData ? res.data.webAPIData.totalCount : res.data.data?.totalCount; setPagination(_pagination); setTableData(_tableData); // 处理排序字段 handleSortString(_sortString); if (_tableData) { setSortModalVisible(true); setCurrentReportId(_reportDetails[0]?.reportId); } else { setSortModalVisible(false); setCurrentReportId(null); } } else { setSortModalVisible(false); setCurrentReportId(null); message.error('未能查询到报表数据!'); let _pagination = { ...pagination }; _pagination.total = 0; _pagination.current = 1; setPagination(_pagination); setTableData([]); } if (isInit) setIsInit(false); setTableLoading(false); }) .catch((err) => { console.log(err); setTableLoading(false); }); }; const getConfigs = () => { reportService.getReportDetails({ reportName }).then((res) => { if (res.code === 0) { setReportConfigs(res.data); setConfig(res.data); setColumns(returnColumn(res.data)); setTableStruct(res.data); setFirstToGetData(true); } else { message.error(res.msg); } }); }; /** @description: 在配置项中,isFilter: true 用来渲染控制框;filterRule: 下拉/文本/多选 */ const getControlsBarConfig = (config) => { let _data = config.filter((item) => item.isFilter); let _searchPlaceholder = []; _data .filter((item) => item.filterRule === '文本') .forEach((item) => _searchPlaceholder.push(item.fieldAlias)); setSearchPlaceholder(_searchPlaceholder); let _controls = _data.filter((item) => ControlsType.includes(item.filterRule)); setControls(_controls); handleControls(_controls); // 处理控制条,设定默认值 handleDate(_controls); // 处理日期, 设定默认值 }; const getTableHeaderConfig = (config, data) => { // setTableStruct(config); // setColumns(returnColumn(config, data)); setAllSortFields(returnSortFields(config)); }; const getTableSummaryConfig = (config, summary) => { if (summary.length === 0) { setSummaryArray([]); return false; } let _summaryConfig = {}; let _configLength = config.length; // 需要判断 // 合并列 let _colSpan = -1; let _index = 0; config .filter((item) => item.isShow) .forEach((item, index) => { if (item.isStatistics) { _index += 1; _colSpan = -1; _summaryConfig[item.fieldAlias] = { fieldName: item.fieldAlias, index, alignType: item.alignType, type: item.statisticsRule, isMoney: item.type === '数值' && !!hasMoney(item.configItems), configItems: item.configItems, }; } else { let _name = `空值行${_index}`; if (_colSpan === -1) { _colSpan = 1; } else { _colSpan += 1; } _summaryConfig[_name] = { fieldName: _name, colSpan: _colSpan, }; } }); summary.forEach((item) => { switch (item.totalType) { case '全部': setHasTotal(true); break; case '单页': setHasSinglePage(true); break; default: break; } if (_summaryConfig[item.fieldName]) { _summaryConfig[item.fieldName][item.totalType] = item.fieldValue; } }); // 增加操作列,总结栏最后一个单元格需要延伸一格 let _sumArr = Object.values(_summaryConfig); if (_sumArr && _sumArr.length) _sumArr = _sumArr.map((item, index) => { let _item = { ...item }; if (index === _sumArr.length - 1) _item.colSpan += 1; return _item; }); setSummaryArray(_sumArr); }; const changeDate = ({ dateFrom, dateTo }, mode) => { setTimeFrom(dateFrom); setTimeTo(dateTo); setDateMode(mode); }; const mapHandleType = (type) => { const _map = { 文本: handleText, 多行文本: handleText, 数值: handleNumber, 数值标签: handleNumberTag, 标签: handleTag, 链接: handleLink, 功能: handleWidget, 弹窗: handleModal, 日期: handleDateString, 日期时间: handleDateTimeString, }; return _map[type] || _map['文本']; }; const returnSortFields = (data) => { return data.map((item) => item.fieldAlias); }; // 处理表格数据,生成表头 const returnColumn = (config, data) => { //0. 常规数据 //1. 合并表头; //2. 四类形态的渲染处理; //3. 多列表头排序;剔除掉原有图标,需要自己实现排序的按钮 let _config = [...config].filter((item) => item.isShow); function dataStruct(keyArray, dataIndex, obj, dataObj) { if (dataIndex < keyArray.length - 1) { if (!obj[keyArray[dataIndex]]) { obj[keyArray[dataIndex]] = {}; } let _dataIndex = dataIndex + 1; dataStruct(keyArray, _dataIndex, obj[keyArray[dataIndex]], dataObj); } else if (dataIndex === keyArray.length - 1) { obj[keyArray[dataIndex]] = dataObj; } } let _tempObj = {}; let _fieldAliasArray = data ? handleDataToGetRowSpanArray(_config, data) : false; // 需要向下合并的字段 _config.forEach((item) => { let _item = { title: item.fieldAlias, dataIndex: item.fieldAlias, key: item.fieldAlias, ellipsis: true, onCell: (record, rowIndex) => { // console.log('Record: ', record); // record是这条记录,index是rowIndex // 1. 如果该字段是需要向下合并的,则进入判断 let _obj = {}; if (_fieldAliasArray && _fieldAliasArray[item.fieldAlias]) { _obj.rowSpan = _fieldAliasArray[item.fieldAlias][rowIndex]; } return _obj; }, render: (value, record) => { // 文本、标签、链接、数值 // @params: item 当前的config数据,提供该字段的各类配置 // value 当前单元格的值 // record 点击单元格的整行数据 let rest = []; if (item.type === '功能') { /* rest = { showComponent: setDetailsComponentVisible, setDetailsConfig: setDetailConfig, }; */ rest = [setDetailsComponentVisible, setDetailConfig]; } else if (item.type === '弹窗') { /* rest = { showModal: setModalVisible, setExtra: setExtra, };*/ rest = [setModalVisible, setExtra]; } return mapHandleType(item.type)( item, [undefined, null].includes(value) ? '' : value, record, ...rest, ); }, }; _item.width = (!isNaN(Number(item.columnWidth)) && item.columnWidth) || 200; // 列宽,不设置时默认给200; if (item.fixedColumn) _item.fixed = item.fixedColumn; // 固定位置 _item.align = item.alignType || 'left'; // 单元格内对齐方式 let _keyArray = (item.fieldGroup || item.fieldAlias || item.fieldName).split(fieldSplitStr); // 自定义排序 let _sortFields = handleSortFields(sortFields); if (_sortFields.includes(item.fieldAlias)) { _item.sorter = true; } dataStruct(_keyArray, 0, _tempObj, _item); return _item; }); let _tempArray = []; function handleObject2Array(obj, arr) { Object.keys(obj).forEach((key, index) => { if (obj[key].title && obj[key].title === key) { arr.push(obj[key]); } else { arr.push({ title: key, children: [] }); handleObject2Array(obj[key], arr[index].children); } }); } handleObject2Array(_tempObj, _tempArray); // 增加序号 _tempArray.unshift({ title: '序号', dataIndex: 'r', key: 'r', width: 60, fixed: 'left', }); // 增加操作列 if (permission.includes('editBtn') || permission.includes('deleteBtn')) { _tempArray.push({ title: '操作', align: 'center', width: permission.reduce((final, curr) => { if (['editBtn', 'deleteBtn'].includes(curr)) { final += 30; } return final; }, 30), fixed: 'right', render: (text, record) => { return ( <Space className={style.handleColumnWrapper}> {record.IsAllow ? ( <Popconfirm placement="topRight" title={'确认取消关注吗'} icon={<QuestionCircleOutlined />} onConfirm={() => focusProject(record)} > <Tooltip title={'点击取消关注'}> <HeartTwoTone twoToneColor="#eb2f96" /> </Tooltip> </Popconfirm> ) : ( <Popconfirm placement="topRight" title={'确认关注项目吗'} icon={<QuestionCircleOutlined />} onConfirm={() => focusProject(record)} > <Tooltip title={'点击添加关注'}> <HeartOutlined /> </Tooltip> </Popconfirm> )} {permission.includes('editBtn') && !isLocalDataSource ? ( <FormOutlined className={style.editButton} onClick={() => { // setEditComponentVisible(true); setModalType('编辑'); setCurrentData(record); }} /> ) : ( '' )} {permission.includes('deleteBtn') && !isLocalDataSource ? ( <DeleteOutlined disabled className={style.deleteButton} onClick={() => { Modal.confirm({ content: '你确定要删除该数据吗?', onOk: () => { reportService .delReportData({ reportName: reportName, userId: USER_ID, key: record.Key, }) .then((res) => { if (res.code === 0) { message.success('删除成功!'); setGetNewData(!getNewData); // getData(pagination); // 在onOk的回调函数内,打包后的代码会形成闭包 } }); }, }); }} /> ) : ( '' )} </Space> ); }, }); } // 统计宽度 let _x = _tempArray.reduce((final, curr) => { return (final += curr.width); }, 0); setTableX(_x); return _tempArray; }; const changeSortField = (value, index, key) => { let _selectedSortFields = [...selectedSortFields]; _selectedSortFields[index][key] = value; setSelectedSortFields(_selectedSortFields); }; const addOtherSortFields = () => { let _selectedSortFields = [...selectedSortFields]; _selectedSortFields.push({ label: '次要排序', value: '', sort: 'asc', }); setSelectedSortFields(_selectedSortFields); }; const deleteSortField = (index) => { let _selectedSortFields = [...selectedSortFields]; _selectedSortFields.splice(index, 1); setSelectedSortFields(_selectedSortFields); }; const setTableHeight = () => { if (!tableWrapperRef.current) return; const clientHeight = tableWrapperRef.current?.clientHeight || 0; const clientWidth = tableWrapperRef.current?.clientWidth || 0; const _height = controlRef.current?.clientHeight; // 控制条的高度 const paginationHeight = 75; // 分页部分的高度 const tableHeaderHeight = tableHeaderLevel * 40; // 表头高度 const summaryHeight = summaryArray.length ? 40 * (Number(hasTotal) + Number(hasSinglePage)) : 0; // 总结栏的高度 const _minus = clientHeight - _height - 16 - 4 - tableHeaderHeight - paginationHeight - summaryHeight - 10; setListHeight(clientHeight - _height - paginationHeight - 4 - 6 - 16 - 2); setTableY(_minus); }; const getTableLevel = (config) => { let _level = config.reduce((final, curr) => { return (final = curr.level > final ? curr.level : final); }, 1) || 1; setTableHeaderLevel(_level); }; const saveReportListSortFields = (callback) => { reportService .saveReportListSortFields({ reportId: currentReportId, sortFields: selectedSortFields .filter((item) => item.value) .map((item) => ({ fieldAlias: item.value, sortType: item.sort, })), }) .then((res) => { if (res.code === 0) { message.success('排序保存成功!'); callback(); } else { message.error(res.msg); } }); }; /** @description: 判断是否存在【时间】类型的选择,并返回组件;并记录默认值 */ const handleControls = (controls) => { // 过滤出非日期的字段,存储默认值 let _controls = controls.filter((item) => item.type !== '日期'); _controls.forEach((item) => { let _configItems = item.configItems.split('|'); _configItems.forEach((str) => { if (str.includes('defaultValue=')) { controlSelectChange(item.fieldAlias, str.replace('defaultValue=', '')); } }); }); }; const handleDate = (obj) => { let _typeObj = obj.find((item) => item.filterRule === '日期'); setHasDatePicker(_typeObj ? _typeObj.fieldAlias : ''); const _configItems = _typeObj?.configItems.split('|') || ['']; let _defaultDate = _configItems .find((item) => item.includes('defaultDate=')) ?.replace('defaultDate=', '') ?.split(','); let _defaultModel = _configItems.find((item) => item.includes('defaultModel='))?.replace('defaultModel=', '') || 'year'; _defaultDate = MODEL.includes(_defaultModel) ? _defaultDate : 'year'; // 确保值符合要求 if (_defaultDate && _defaultDate.length > 1) { _defaultDate = { dateFrom: moment(_defaultDate[0]), dateTo: moment(_defaultDate[1]) }; } else if (_defaultDate && _defaultDate.length === 1) { _defaultDate = { dateFrom: moment(_defaultDate[0]), dateTo: moment(_defaultDate[0]) }; } else { _defaultDate = { dateFrom: moment(), dateTo: moment() }; } // 给定默认值,初始化时可以加载 changeDate( { dateFrom: _defaultDate?.dateFrom.clone().startOf(_defaultModel).format(dateFormat), dateTo: _defaultDate?.dateTo.clone().endOf(_defaultModel).format(dateFormat), }, _defaultModel, ); setDefaultDateConfig({ defaultDate: _defaultDate?.dateFrom, defaultModel: _defaultModel, }); }; const focusProject = (record) => { reportService .setReportAllow({ userId: USER_ID, reportName: reportName, reportKey: record.Key, }) .then((res) => { if (res.code === 0) { message.success(`${record.IsAllow ? '取消' : '关注'}成功!`); getData(pagination); } else { message.error(`${record.IsAllow ? '取消' : '关注'}失败!`); } }); }; function getRefHeight() { if (timer) clearTimeout(timer); timer = setTimeout(() => { let _height = controlRef?.current.clientHeight; setControlsHeight(_height); }, 100); } useEffect(() => { getConfigs(); }, []); useEffect(() => { if (firstToGetData) getData(pagination); getRefHeight(); }, [firstToGetData]); useEffect(() => { if (tableHeaderLevel) setTableHeight(); }, [tableHeaderLevel]); useEffect(() => { if (!isInit) getData(pagination); }, [timeFrom, timeTo, sorterObject, filterObject, getNewData]); useEffect(() => { window.addEventListener('resize', getRefHeight); return () => window.removeEventListener('resize', getRefHeight); }); return ( <div className={style.reportManage} ref={tableWrapperRef}> {/* 预留容器,提供给点击后的功能显示 */} {detailsComponentVisible ? ( <div className={style.contentWrapper} style={{ position: 'absolute', zIndex: 100, width: 'calc(100% - 16px)', height: 'calc(100% - 16px)', }} > <DetailsComponent url={detailConfig.url} params={detailConfig.params} onCancel={() => setDetailsComponentVisible(false)} /> </div> ) : ( '' )} {/* 为方便阅读,分开两部分代码 */} <div className={style.contentWrapper} style={{ zIndex: detailsComponentVisible ? -1 : 'auto' }} > <Row className={style.controlRow} ref={controlRef}> <Space style={{ flex: 1 }} size={8} wrap={true}> {/*时间搜索控件,确保时间搜索控件在第一个,单独匹配*/} {hasDatePicker && defaultDateConfig.defaultDate !== null && permission.includes('filters') ? ( <DatePickerGroup showModels={['all', 'month', 'quarter', 'year', 'custom']} onChange={changeDate} format={dateFormat} defaultModel={defaultDateConfig.defaultModel} defaultDate={defaultDateConfig.defaultDate} /> ) : ( '' )} {controls && controls.length && permission.includes('filters') ? controls .filter((control) => ['下拉', '多选'].includes(control.filterRule)) .map((control) => { return ( <Form.Item label={control.fieldAlias} key={control.fieldAlias}> <ReturnControlComponent style={{ width: 240 }} type={control.filterRule} reportName={reportName} fieldAlias={control.fieldAlias} configItems={control.configItems} onChange={(e) => controlSelectChange(control.fieldAlias, e)} filterFields={filterFields} filterValues={filterValues} filterObject={addFilterAndSearchParams({ reportName: reportName, userId: USER_ID, })} /> </Form.Item> ); }) : ''} {permission.includes('filters') ? ( <Form.Item label="快速索引" key={'快速搜索控件'}> <ReturnControlComponent placeholder={`请输入${ searchPlaceholder.length ? searchPlaceholder.join(',') : '关键字' }搜索`} style={{ width: 240 }} type={'文本'} onChange={(e) => { searchInputChange(e); }} onSearch={searchData} /> </Form.Item> ) : ( '' )} </Space> {permission.includes('sortBtn') || permission.includes('exportBtn') || permission.includes('addBtn') ? ( <div style={{ width: 270, textAlign: 'end' }}> <Space size={8} nowrap> {permission.includes('addBtn') && !isLocalDataSource ? ( <Form.Item> <Button type={'primary'} title={'自定义排序'} icon={<PlusOutlined />} onClick={() => { setModalType('新增'); setCurrentData({}); }} > 添加 </Button> </Form.Item> ) : ( '' )} {sortModalVisible && permission.includes('sortBtn') ? ( <Form.Item> <Button type={'primary'} title={'自定义排序'} icon={<SortAscendingOutlined />} onClick={() => setModalVisible(true)} > 排序 </Button> </Form.Item> ) : ( '' )} {permission.includes('exportBtn') ? ( <Form.Item> <Dropdown style={{ float: 'right' }} overlay={menu}> <Button> <Space> 导出 <DownOutlined /> </Space> </Button> </Dropdown> </Form.Item> ) : ( '' )} </Space> </div> ) : ( '' )} </Row> <div className={style.tableContent} style={{ height: `calc(100% - ${controlsHeight || 0}px)` }} > {columns && columns.length ? ( <BasicTable rowKey={'Key'} bordered loading={tableLoading} dataSource={tableData} columns={columns} onChange={(pagination, filters, sorter, extra) => { setSorterObject(sorter); }} pagination={permission.includes('pagination') ? pagination : false} // 237是内置图片高度 scroll={{ y: tableData && tableData.length ? `calc(100% - 44px)` : 237, x: tableX }} summary={(pageData) => { if (summaryArray.length && tableData && tableData.length) return ( <Table.Summary fixed> <Table.Summary.Row> {hasSinglePage ? summaryArray.map((item, index) => { if (item.fieldName === '空值行0') { return ( <Table.Summary.Cell key={`summary_${index}`} index={0} colSpan={item.colSpan + 1} > <span style={{ display: 'inline-block', width: '100%', textAlign: 'center', }} > 小计 </span> </Table.Summary.Cell> ); } else if (item.fieldName.includes('空值行')) { return ( <Table.Summary.Cell key={`summary_${index}`} index={0} colSpan={item.colSpan} /> ); } else { return ( <Table.Summary.Cell key={`summary_${index}`} index={0}> <span style={{ display: 'inline-block', width: '100%', textAlign: item.alignType, }} > {item.type.replace('求', '')}:{' '} {returnHandledNumber(item.configItems, item['单页'], true)} </span> </Table.Summary.Cell> ); } }) : ''} </Table.Summary.Row> <Table.Summary.Row> {hasTotal ? summaryArray.map((item) => { if (item.fieldName === '空值行0') { return ( <Table.Summary.Cell index={0} colSpan={item.colSpan + 1}> <span style={{ display: 'inline-block', width: '100%', textAlign: 'center', }} > 总计 </span> </Table.Summary.Cell> ); } else if (item.fieldName.includes('空值行')) { return <Table.Summary.Cell index={0} colSpan={item.colSpan} />; } else { return ( <Table.Summary.Cell index={0}> <span style={{ display: 'inline-block', width: '100%', textAlign: item.alignType, }} > {item.type.replace('求', '')}:{' '} {returnHandledNumber(item.configItems, item['全部'], true)} </span> </Table.Summary.Cell> ); } }) : ''} </Table.Summary.Row> </Table.Summary> ); }} /> ) : ( <div className={style.spinWrapper}> <Spin /> </div> )} </div> </div> <Modal title={'自定义排序字段'} visible={modalVisible} onCancel={() => setModalVisible(false)} footer={ <div style={{ display: 'flex', justifyContent: 'space-between' }}> <div> <Button type={'link'} onClick={() => addOtherSortFields()}> 增加次要排序 </Button> </div> <div> <Button onClick={() => setModalVisible(false)}>取消</Button> <Button type={'primary'} onClick={() => { saveReportListSortFields(() => getData(pagination)); setModalVisible(false); }} > 确认 </Button> </div> </div> } > {selectedSortFields.map((item, index) => ( <Row key={'label'} className={style.controlRow}> <Space size={8} wrap={true}> <Form.Item label={item.label}> <Select style={{ width: 240 }} defaultValue={item.value} value={item.value} onChange={(e) => changeSortField(e, index, 'value')} > <Option value="">未选择</Option> {allSortFields.map((item) => ( <Option value={item}>{item}</Option> ))} </Select> </Form.Item> <Form.Item> <Select style={{ width: 120 }} defaultValue={item.sort} value={item.sort} onChange={(e) => changeSortField(e, index, 'sort')} > <Option value={'asc'}>升序</Option> <Option value={'desc'}>降序</Option> </Select> </Form.Item> {index !== 0 ? ( <Form.Item> <MinusCircleOutlined style={{ color: 'rgba(0,0,0,.65)' }} onClick={() => deleteSortField(index)} /> </Form.Item> ) : ( '' )} </Space> </Row> ))} </Modal> <Modal visible={extraModal} onCancel={() => setExtraModal(false)} destroyOnClose width={800}> {extra} </Modal> {/* 编辑表单 */} <Modal title={`${modalType}报表信息`} visible={!!modalType} width={'80%'} footer={null} // visible={true} destroyOnClose onCancel={() => setModalType('')} > <ReportEditForm modalType={modalType} reportDetails={tableStruct} reportData={currentData} onCancel={() => { setModalType(''); getData(pagination); }} reportName={reportName} /> </Modal> </div> ); }; const mapStateToProps = (state) => { const allWidgets = state.getIn(['global', 'globalConfig', 'allWidgets']); let _flatWidgets = []; const flatWidgets = (arr) => { arr.forEach((item) => { if (item.widgets && item.widgets.length) { flatWidgets(item.widgets); } else { _flatWidgets.push(item); } }); }; flatWidgets(allWidgets); return { allWidgets: _flatWidgets, }; }; export default connect(mapStateToProps, null)(ReportsManage);