Commit 3ebe7190 authored by 陈龙's avatar 陈龙

feat: 重新处理报表逻辑

parent b8b2275c
......@@ -9,7 +9,7 @@
"*.less"
],
"module": "es/index.js",
"main": "lib/index.js",
"main": "es/index.js",
"files": [
"lib",
"es",
......
......@@ -6,7 +6,7 @@
*/
import React, { useEffect, useState } from 'react';
import { Input, Select } from 'antd';
import { reportService } from '../api';
import { reportService } from '@/api';
import { returnDefaultValueOrConfigs } from '../utils/utils';
const { Option } = Select;
......
......@@ -7,7 +7,7 @@
import React, { useEffect, useState } from 'react';
import { Form, Input, DatePicker, InputNumber, Space, Row, Col, Button, message } from 'antd';
import moment from 'moment';
import { submitReportData } from './api/service/report';
import { submitReportData } from '../api/service/report';
// 类型
const DATE_PICKER_ARRAY = ['日期'];
const DATE_TIME_PICKER_ARRAY = ['日期时刻'];
......
/**
* * 轻量化报表通用配置页面 * 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:
* 需要默认加载的过滤条件的field,英文逗号分割,与filterValues一一对应;
* filterValues:
* 需要默认加载的过滤条件的value,英文逗号分割,与filterFields一一对应;
* state: delete|edit|scan 各种权限;
*
* customerState: ['filters','sortBtn','exportBtn','editBtn','deleteBtn','pagination'];
*
* sortFields: '排序字段1,排序字段2,排序字段3'
* @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,
} from 'antd';
import {
SortAscendingOutlined,
MinusCircleOutlined,
ExportOutlined,
FormOutlined,
DeleteOutlined,
} 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,
} from './utils/handlers';
import { hasMoney, isArray, isNumber, isString, returnHandledNumber } from './utils/utils';
import { connect } from 'react-redux';
import ReportEditForm from './ReportEditForm';
const ControlsType = ['下拉', '多选', '日期'];
const fieldSplitStr = '-'; // fieldGroup用来分割
const { Option } = Select;
const dateFormat = 'YYYY-MM-DD'; // 日期格式化
const initDateModel = 'all';
let timer = null;
const PERMISSION = {
delete: [
'filters',
'pagination',
// 操作条按钮
'sortBtn',
'exportBtn',
// 操作列
'editBtn',
'deleteBtn',
],
edit: [
'filters',
'pagination',
'sortBtn',
'exportBtn',
// 操作列
'editBtn',
],
scan: ['filters', 'pagination'],
};
const ReportsManage = (props) => {
const { reportName, pageSize, filterFields, filterValues, state, customerState, sortFields } =
props.params;
const permission =
customerState && isArray(customerState) ? customerState : PERMISSION[state || 'delete'];
const tableWrapperRef = useRef();
const controlRef = useRef();
if (!reportName)
return (
<div className={style.lackParams}>未配置reportName,请完善配置并重新登陆后查看页面!</div>
);
const [tableStruct, setTableStruct] = useState([]); // 临时使用,看后续是否需要保留
const [columns, setColumns] = useState([]); // 表头设置
const [tableData, setTableData] = 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(false, _pagination);
},
onChange: (current, size) => {
let _pagination = { ...pagination };
_pagination.current = current;
_pagination.pageSize = size;
setPagination(_pagination);
getData(false, _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 [detailsComponent, setDetailsComponent] = useState(<></>); // 详情组件
const [detailsComponentVisible, setDetailsComponentVisible] = useState(false); // 是否显示详情组件
const [editComponentVisible, setEditComponentVisible] = useState(false); // 是否显示编辑组件
const [currentData, setCurrentData] = useState({}); // 设置当前编辑数据
const [sroter, setSorter] = useState({});
const [detailConfig, setDetailConfig] = useState({ url: '', type: '', params: {} });
const [controlsHeight, setControlsHeight] = useState(44);
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',
},
];
return <Menu items={_item} />;
};
const exportModule = (type, extension) => {
setExportLoading(true);
exportAccountData(
{ responseType: 'blob' },
{ exportType: type },
{
reportName,
pageIndex: 0,
pageSize: 0,
sortFields: '',
content: '',
filters: [
{
fieldAlias: '',
fieldValue: '',
},
],
},
)
.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 searchData = (e) => {
getData(false, pagination);
};
const controlSelectChange = (fieldAlias, e) => {
let _filterArray = { ...filterObject };
_filterArray[fieldAlias] = e;
setFilterObject(_filterArray);
};
const searchInputChange = (e) => {
setSearchContent(e.target.value);
};
const setConfig = (config, summary, data) => {
getControlsBarConfig(config);
getTableSummaryConfig(config, summary);
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) {
let _customerFilterArray = filterFields.split(',');
let _customerValueArray = filterValues.split(',');
_customerFilterArray.forEach((item, index) => {
_filters.push({
fieldAlias: item,
fieldValue: _customerValueArray[index] || '',
});
});
}
// 并入 _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 getData = (isInit, pagination) => {
setTableLoading(true);
const { pageSize, current } = pagination;
// 搜索条件附加到params
let _data = addFilterAndSearchParams({
reportName: reportName,
pageIndex: current,
pageSize: pageSize,
});
// sortFields
reportService
.getReportInfo(_data)
.then((res) => {
if (res.code === 0) {
let _reportDetails = res.data.reportDetails;
let _statisticalValues = res.data.statisticalValues;
let _tableData = res.data.data.list;
let _sortString = res.data.sortString;
if (isInit) {
setConfig(_reportDetails, _statisticalValues, _tableData);
}
getTableHeaderConfig(_reportDetails, _tableData);
let _pagination = { ...pagination };
_pagination.total = 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([]);
}
setTableLoading(false);
})
.catch((err) => {
console.log(err);
setTableLoading(false);
});
};
/** @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);
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,
数值: handleNumber,
标签: 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 = handleDataToGetRowSpanArray(_config, data); // 需要向下合并的字段
_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[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, value || '', record, ...rest);
},
};
_item.width = 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: '操作',
width: 60,
fixed: 'right',
render: (text, record) => {
return (
<Space className={style.handleColumnWrapper}>
{permission.includes('editBtn') ? (
<FormOutlined
className={style.editButton}
onClick={() => {
setEditComponentVisible(true);
setCurrentData(record);
}}
/>
) : (
''
)}
{/* {
permission.includes('deleteBtn') ?
<DeleteOutlined disabled className={style.deleteButton} onClick={() => {
Modal.confirm({
content: '你确定要删除改数据吗?',
onOK: () => message.error('您无法删除数据!'),
});
}} /> : ''
}*/}
</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 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(',');
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() };
}
setDefaultDateConfig({
defaultDate: _defaultDate?.dateFrom,
defaultModel:
_configItems.find((item) => item.includes('defaultModel='))?.replace('defaultModel=', '') ||
'year',
});
};
useEffect(() => {
getData(true, pagination);
}, []);
useEffect(() => {
if (tableHeaderLevel) setTableHeight();
}, [tableHeaderLevel]);
useEffect(() => {
getData(false, pagination);
}, [timeFrom, timeTo, filterObject]);
useEffect(() => {
function getRefHeight() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
let _height = controlRef?.current.clientHeight;
setControlsHeight(_height);
}, 100);
}
window.addEventListener('resize', getRefHeight);
return () => window.removeEventListener('resize', getRefHeight);
});
return (
<div className={style.reportManage} ref={tableWrapperRef}>
{/* 预留容器,提供给点击后的功能显示 */}
{detailsComponentVisible ? (
<DetailsComponent
url={detailConfig.url}
params={detailConfig.params}
onCancel={() => setDetailsComponentVisible(false)}
/>
) : (
''
)}
{/* 为方便阅读,分开两部分代码 */}
{!detailsComponentVisible ? (
<div className={style.contentWrapper}>
{state !== 'scan' ? (
<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)}
/>
</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') ? (
<div style={{ width: 200, textAlign: 'end' }}>
<Space size={8} nowrap={true}>
{sortModalVisible && permission.includes('sortBtn') ? (
<Form.Item>
<Button
type={'primary'}
title={'自定义排序'}
icon={<SortAscendingOutlined />}
onClick={() => setModalVisible(true)}
>
排序
</Button>
</Form.Item>
) : (
''
)}
{permission.includes('exportBtn') ? (
<Form.Item>
<Dropdown.Button style={{ float: 'right' }} overlay={menu}>
导出
</Dropdown.Button>
</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) => {
console.log(sorter);
setSorter(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['单页'])}
</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['全部'])}
</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(false, 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={'编辑报表信息'}
visible={editComponentVisible}
width={'80%'}
footer={null}
// visible={true}
onCancel={() => setEditComponentVisible(false)}
>
<ReportEditForm
reportDetails={tableStruct}
reportData={currentData}
onCancel={() => {
setEditComponentVisible(false);
getData(false, 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);
import React from 'react';
import ReportsManage from './ReportsManage';
const ReportsPermissionDelete = (props) => <ReportsManage {...props} state="delete" />;
export default ReportsPermissionDelete;
import React from 'react';
import ReportsManage from './ReportsManage';
const ReportsPermissionScan = (props) => <ReportsManage {...props} state="scan" />;
export default ReportsPermissionScan;
/**
* * 报表设置列表 * create by ChenLong on 2022/6/28 *
* 功能路径:src\pages\product\ReportsManage\Components\ReportsSetting.js *
* 菜单参数列表:*变量名*(变量说明,数据类型,是否必填,取值范围) * @changelog: edit by ChenLong 2022年7月7日
* 增加日期类型的过滤,可以配置日期选择器,只允许存在一个日期类型的过滤;在表单提交时做校验 edit by ChenLong 2022年7月8日 表单的使用方式需要变更,需求为:1.
* 动态表单控件需要封装成组件; 2. 动态列表需要使用Form.List替换】 待执行 edit by ChenLong 2022年7月8日 变更单页、全部统计的规则
*/
import React, { useEffect, useRef, useState } from 'react';
import {
DeleteOutlined,
FormOutlined,
MinusCircleOutlined,
PlusOutlined,
MenuOutlined,
SettingOutlined,
LeftOutlined,
} from '@ant-design/icons';
import {
Button,
Checkbox,
Divider,
Form,
Input,
InputNumber,
message,
Modal,
Row,
Select,
Space,
Switch,
Tooltip,
} from 'antd';
import { SketchPicker } from 'react-color';
import { arrayMoveImmutable } from 'array-move';
import BasicTable from '@wisdom-components/basictable';
import { reportService } from '../api';
import style from './ReportsSetting.less';
import classname from 'classnames';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import { addReportDetailInfoIndex } from '../api/service/report';
import { isNumber, isString } from './utils/utils';
import moment from 'moment';
const { Option } = Select;
const { TextArea } = Input;
const { Search } = Input;
const typeArray = ['文本', '标签', '数值', '链接', '功能', '弹窗', '日期', '日期时间'];
const filterRule = ['文本', '下拉', '多选', '日期'];
const publicSplit = '&split;';
const ReportsSetting = () => {
// 报表列表
const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 20 },
};
const [form] = Form.useForm();
const [createMainTableForm] = Form.useForm();
const [patchSubTableForm] = Form.useForm();
const [editDetailForm] = Form.useForm();
const watchType = Form.useWatch('type', form);
const showFilter = Form.useWatch('isFilter', form);
const fieldGroupLevel = Form.useWatch('level', form);
const isStatistics = Form.useWatch('isStatistics', form);
const [isCreatingMainTable, setIsCreatingMainTable] = useState(false);
const colorPicker = useRef();
const reportDetails = useRef();
const [tableData, setTableData] = useState([]); // 常规使用的数据
const [tableLoading, setTableLoading] = useState(false);
const [detailTableVisible, setDetailTableVisible] = useState(false);
const columns = [
{
title: '报表名称',
dataIndex: 'reportName',
key: 'reportName',
},
{
title: '主表名称',
dataIndex: 'tableName',
key: 'tableName',
},
{
title: '报表字段',
dataIndex: 'reportFields',
key: 'reportFields',
// ellipsis:true,
render: (text) => (
<Tooltip title={text}>
{/* 自动折叠内容时,如果使用Tooltip,Tooltip会无法正确定位;建议手动设置内容折叠 */}
<span className={style.reportFiled}>{text}</span>
</Tooltip>
),
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
},
{
title: '创建人',
dataIndex: 'creator',
key: 'creator',
},
{
title: '操作',
width: 120,
render: (text, record) => {
return (
<div style={{ display: 'flex', justifyContent: 'space-around' }}>
<FormOutlined
title={'编辑字段'}
onClick={() => editReport(record)}
style={{ color: '#1685FF' }}
/>
<SettingOutlined
title={'设置'}
style={{ color: 'rgba(0,0,0,.55)' }}
onClick={() => {
setCurrentReport(record);
clickReport(record.id);
}}
/>
<DeleteOutlined
title={'删除报表'}
style={{ color: 'red' }}
onClick={() => deleteReport(record.id)}
/>
</div>
);
},
},
];
// 详情表
const [detailData, setDetailData] = useState([]); // 详情表的数据
const [tempDetailData, setTempDetailData] = useState([]); // 备份数据
const [detailVisible, setDetailVisible] = useState(false);
const [backgroundColor, setBackgroundColor] = useState('rgba(0,0,0,.85)');
const [showSketchPicker, setShowSketchPicker] = useState(false);
const [createModalVisible, setCreateModalVisible] = useState(false);
const [activeID, setActiveID] = useState(null);
const [currentField, setCurrentField] = useState(null);
const [patchFieldVisible, setPatchFieldVisible] = useState(false);
const [subTableList, setSubTableList] = useState([]);
const [allTableList, setAllTableList] = useState([]); // 数据库表
const [allFields, setAllFields] = useState([]); // 新增主表时的选择的表字段
const [allSubFields, setAllSubFields] = useState([]); // 新增子表字段
const [createBtnLoading, setCreateBtnLoading] = useState(false);
const [currentReport, setCurrentReport] = useState({});
const [colorCardPosition, setColorCardPosition] = useState({});
const [labelColorPickerArray, setLabelColorPickerArray] = useState([]); // 标签色板
const [numberColorPickerArray, setNumberColorPickerArray] = useState([]); // 数值色板
const [currentColorPicker, setCurrentColorPicker] = useState({
// 因为使用单例,需要记录是哪一个色板触发了
data: [],
callback: () => {},
index: 0,
key: 'init', // init 初始化时的key,用来解决初始化报错的问题;wordColor 用来区分是那部分的色板
}); // 记录需要修改的对象
const [submitFieldLoading, setSubmitFieldLoading] = useState(false);
const [relationship, setRelationship] = useState([]);
const [patchSubTableBtnLoading, setPatchSubTableBtnLoading] = useState(false);
const [isEditing, setIsEditing] = useState(true);
const [detailTableLoading, setDetailTableLoading] = useState(false);
const SortableItem = SortableElement((props) => <tr {...props} />);
const SortableBody = SortableContainer((props) => <tbody {...props} />);
const onSortEnd = ({ oldIndex, newIndex }) => {
if (oldIndex !== newIndex) {
const newData = arrayMoveImmutable(detailData.slice(), oldIndex, newIndex).filter(
(el) => !!el,
);
console.log('Sorted items: ', newData);
setDetailData(newData);
setTempDetailData(newData);
editDetailForm.setFieldsValue(newData);
saveOrder(newData);
}
};
const DraggableContainer = (props) => (
<SortableBody
useDragHandle
disableAutoscroll
helperClass="row-dragging"
onSortEnd={onSortEnd}
{...props}
/>
);
const DraggableBodyRow = ({ className, style, ...restProps }) => {
const index = detailData.findIndex((x) => x.id === restProps['data-row-key']);
return <SortableItem index={index} {...restProps} />;
};
const returnEqual = (index, key) => {
// return detailData !== tempDetailData || detailData[index] !== tempDetailData[index] || (detailData[index] && tempDetailData[index] && detailData[index][key] !== tempDetailData[index][key]);
return (
(detailData[index] && !tempDetailData[index]) ||
(detailData[index] &&
tempDetailData[index] &&
detailData[index][key] !== tempDetailData[index][key])
);
};
const detailColumns = [
{
title: '',
dataIndex: 'sort',
width: isEditing ? 0 : 30,
className: 'drag-visible',
render: () => <DragHandle />,
},
{
title: '表名',
dataIndex: 'tableName',
className: 'drag-visible',
key: 'tableName',
},
{
title: '字段名',
dataIndex: 'fieldName',
key: 'fieldName',
},
{
title: '别名',
dataIndex: 'fieldAlias',
key: 'fieldAlias',
render: (text, record, index) => {
return isEditing ? (
<Form.Item
name={[index, 'fieldAlias']}
rules={[
{
required: true,
message: '请输入字段别名',
},
]}
>
<Input
key={`fieldAlias_${index}`}
value={record?.fieldAlias}
className={returnEqual(index, 'fieldAlias') ? style.boxShadow : ''}
onChange={(e) => modifyDetailData('fieldAlias', e.target.value, record, index)}
/>
</Form.Item>
) : (
text
);
},
},
{
title: '字段组',
dataIndex: 'fieldGroup',
key: 'fieldGroup',
},
{
title: '形态',
dataIndex: 'type',
key: 'type',
width: 60,
},
{
title: '列宽',
dataIndex: 'columnWidth',
key: 'columnWidth',
width: 120,
render: (text, record, index) => {
return isEditing ? (
<Form.Item
name={[index, 'columnWidth']}
rules={[
{
required: true,
message: '列宽不能为空',
},
]}
>
<InputNumber
value={record?.columnWidth}
className={returnEqual(index, 'columnWidth') ? style.boxShadow : ''}
onChange={(e) => modifyDetailData('columnWidth', e, record, index)}
/>
</Form.Item>
) : (
`${text}px`
);
},
},
{
title: '对齐方式',
dataIndex: 'alignType',
key: 'alignType',
width: 80,
render: (text, record, index) => {
const _map = {
left: '左',
right: '右',
center: '中',
};
return isEditing ? (
<Form.Item name={[index, 'alignType']}>
<Select
onChange={(e) => modifyDetailData('alignType', e, record, index)}
className={returnEqual(index, 'alignType') ? style.boxShadowOfSelect : ''}
>
<Option value={text ? 'left' : ''}></Option>
<Option value={'center'}></Option>
<Option value={'right'}></Option>
</Select>
</Form.Item>
) : (
_map[text] || '左'
);
},
},
{
title: '是否固定',
dataIndex: 'fixedColumn',
key: 'fixedColumn',
width: 120,
render: (text, record, index) => {
const _map = {
left: '左',
right: '右',
};
return isEditing ? (
<Form.Item name={[index, 'fixedColumn']}>
<Select
onChange={(e) => modifyDetailData('fixedColumn', e, record, index)}
className={returnEqual(index, 'fixedColumn') ? style.boxShadowOfSelect : ''}
>
<Option value={''}>不固定</Option>
<Option value={'left'}></Option>
<Option value={'right'}></Option>
</Select>
</Form.Item>
) : (
_map[text] || '不固定'
);
},
},
{
title: '是否显示',
dataIndex: 'isShow',
key: 'isShow',
width: 100,
render: (text, record, index) => {
return isEditing ? (
<Form.Item name={[index, 'isShow']} valuePropName="checked">
<Switch
onChange={(e) => modifyDetailData('isShow', e, record, index)}
checkedChildren="显示"
unCheckedChildren="不显示"
defaultChecked={false}
className={returnEqual(index, 'isShow') ? style.boxShadowOfSwitch : ''}
/>
</Form.Item>
) : text ? (
'是'
) : (
'否'
);
},
},
{
title: '是否过滤',
dataIndex: 'isFilter',
key: 'isFilter',
width: 120,
render: (text, record, index) => {
return isEditing ? (
<Form.Item name={[index, 'isFilter']} valuePropName="checked">
<Switch
onChange={(e) => modifyDetailData('isFilter', e, record, index)}
checkedChildren="开启"
unCheckedChildren="关闭"
defaultChecked={false}
className={returnEqual(index, 'isFilter') ? style.boxShadowOfSwitch : ''}
/>
</Form.Item>
) : text ? (
'是'
) : (
'否'
);
},
},
{
title: '过滤类型',
dataIndex: 'filterRule',
key: 'filterRule',
width: 120,
render: (text, record, index) => {
return isEditing ? (
<Form.Item name={[index, 'filterRule']}>
<Select
disabled={!record.isFilter}
onChange={(e) => modifyDetailData('filterRule', e, record, index)}
className={returnEqual(index, 'filterRule') ? style.boxShadowOfSelect : ''}
>
<Option value={''}>不过滤</Option>
{filterRule.map((item) => (
<Option value={item}>{item}</Option>
))}
</Select>
</Form.Item>
) : (
text
);
},
},
{
title: '向下合并',
dataIndex: 'isMerge',
key: 'isMerge',
width: 120,
render: (text, record, index) => {
return isEditing ? (
<Form.Item name={[index, 'isMerge']} valuePropName="checked">
<Switch
onChange={(e) => modifyDetailData('isMerge', e, record, index)}
checkedChildren="开启"
unCheckedChildren="关闭"
defaultChecked={false}
className={returnEqual(index, 'isMerge') ? style.boxShadowOfSwitch : ''}
/>
</Form.Item>
) : text ? (
'合并'
) : (
'不合并'
);
},
},
{
title: '操作',
width: 100,
render: (text, record) => {
return (
<DeleteOutlined onClick={() => deleteReportDetail(record.id)} style={{ color: 'red' }} />
);
},
},
];
const wordInputWidth = 300;
const numberInputWidth = 120;
const rangeWidth = 500;
const tagWidth = 400;
const getData = () => {
setTableLoading(true);
reportService
.getReportConfigList()
.then((res) => {
if (res.code === 0) {
// 增加visible属性,后续搜索时需要
let _data = res.data.map((item) => {
let _item = { ...item };
_item.visible = true;
return _item;
});
setTableData(_data);
} else {
message.error(res.msg);
}
setTableLoading(false);
})
.catch((err) => {
setTableLoading(false);
});
};
const clickReport = (reportId) => {
setTableLoading(true);
reportService
.getReportDetailsInfo({ reportId })
.then((res) => {
if (res.code === 0) {
setTableLoading(false);
setDetailTableVisible(true);
// 增加visible属性,方便后续检索的属性变更
let _final = res.data.details.map((item) => {
let _item = { ...item };
_item.visible = true;
return _item;
});
editDetailForm.setFieldsValue(_final);
setDetailData(_final);
setTempDetailData(_final);
// 存储关联关系
setRelationship(res.data.child);
}
})
.catch((err) => {
setTableLoading(false);
});
};
const restCreateReportForm = () => {
createMainTableForm.resetFields();
setAllFields([]);
};
const createReport = () => {
setCreateBtnLoading(true);
createMainTableForm
.validateFields()
.then((values) => {
let _data = {
reportName: values.reportName,
tableName: values.tableName,
reportFields: values.reportFields.join(','),
creator: String(globalConfig.userInfo.OID),
};
if (currentReport && currentReport.id) {
_data.id = currentReport.id;
return editReportOrSubTable({ rM_ReportInfo: [_data] }, () => {
setCreateBtnLoading(false);
setCreateModalVisible(false);
getData();
restCreateReportForm();
});
}
addReportOrPatchSubTable({ rM_ReportInfo: [_data] }, () => {
setCreateBtnLoading(false);
setCreateModalVisible(false);
getData();
restCreateReportForm();
});
})
.catch((err) => {
setCreateBtnLoading(false);
});
};
const deleteReport = (reportId) => {
Modal.confirm({
title: '请确认',
content: '是否删除该报表?',
onOk: () => {
reportService.deleteReportInfo({ reportId }).then((res) => {
if (res.code === 0) {
message.success('删除成功!');
getData();
}
});
},
});
};
const deleteReportDetail = (reportDetailId) => {
Modal.confirm({
title: '请确认',
content: '是否删除该参数?',
onOk: () => {
reportService.deleteReportDetailInfo({ reportDetailId }).then((res) => {
if (res.code === 0) {
clickReport(currentReport.id);
message.success('删除成功!');
} else {
message.error(res.code);
}
});
},
});
};
const deleteField = () => {};
const editReport = (record) => {
openCreateModal();
setCurrentReport(record);
setIsCreatingMainTable(false);
createMainTableForm.setFieldsValue({
tableName: record.tableName,
reportName: record.reportName,
reportFields: record.reportFields.split(','),
});
getFieldsFromTable(record.tableName, 'mainTable');
};
const addReportOrPatchSubTable = (data, callback) => {
reportService.addReportInfo(data).then((res) => {
if (res.code === 0) {
message.success('提交成功!');
} else {
message.error(res.msg);
}
if (callback) callback();
});
};
const editReportOrSubTable = (data, callback) => {
reportService.editReportInfo(data).then((res) => {
if (res.code === 0) {
message.success('提交成功!');
} else {
message.error(res.msg);
}
if (callback) callback();
});
};
const changeField = (record) => {
setActiveID(record.id);
form.setFieldsValue(setDefaultValue(record));
// 手动设置文字的颜色
setBackgroundColor(record.color);
// 手动设置labelConfig和number的设置
setNumberColorPickerArray(record.numericalConfigs);
setLabelColorPickerArray(record.labelConfigs);
setCurrentField(setDefaultValue(record)); // 为了确保前后数据统一
};
const setDefaultValue = (record) => {
if (!record) return {};
let _record = { ...record };
if (!_record.type) _record.type = '文本';
if (isString(_record.fieldGroup)) {
_record.fieldGroup = _record.fieldGroup.split('-').reduce((final, curr, index) => {
final[`fieldGroup_${record.fieldAlias}_${index}`] = curr;
return final;
}, {});
}
if (!_record.filterRule) _record.filterRule = '下拉';
if (!_record.statisticsRule) _record.statisticsRule = '求和';
// 统计规则变更
if (isString(_record.statisticsData))
_record.statisticsData = {
单页: _record.statisticsData.includes('单页'),
全部: _record.statisticsData.includes('全部'),
};
// 对齐方式设置默认值
if (!_record.alignType) _record.alignType = 'left';
return _record;
};
const submitReportDetails = () => {
// 1.表单内的值;2.标签值
form.validateFields().then((value) => {
// 需要将表单内带索引的fieldGroup_[number]处理成后端需要的fieldGroups: a-b-c-d;
value.fieldGroup = Object.values(value.fieldGroup).join('-');
let _value = Object.assign(currentField, value);
// 处理数值颜色和标签颜色
// 后端需求:labelConfig和numberConfig里面都需要设置默认configId并且id设置为0;
if (numberColorPickerArray.length)
_value.numericalConfigs = numberColorPickerArray.map((item) => {
let _item = { ...item };
Object.keys(_item).forEach((key) => {
if (_item[key] === '') _item[key] = null;
});
_item.configId = _value.id;
_item.id = 0;
return _item;
});
if (labelColorPickerArray.length)
_value.labelConfigs = labelColorPickerArray.map((item) => {
let _item = { ...item };
_item.configId = _value.id;
_item.id = 0;
return _item;
});
// 文字的颜色
if (backgroundColor) {
_value.color = backgroundColor;
}
// 过滤规则的设置,需要确保日期类型的过滤规则字段是唯一的
let _isDateArray = detailData.filter((item) => item.filterRule === '日期');
if (
_isDateArray.length > 1 ||
(_isDateArray.length === 1 &&
_value.filterRule === '日期' &&
_isDateArray[0].fieldName !== _value.fieldName)
)
return message.error('已存在日期类型的过滤,请确保日期类型的过滤规则只存在一个!');
// 统计规则的提交变更
let _statisticsData = _value.statisticsData;
_value.statisticsData = Object.keys(_statisticsData)
.filter((key) => _statisticsData[key])
.join(',');
// 提交请求
setSubmitFieldLoading(true);
reportService.addReportDetailInfo({ reportDetails: [_value] }).then((res) => {
if (res.code === 0) {
message.success('提交成功!');
} else {
message.error(res.msg);
}
setSubmitFieldLoading(false);
setDetailVisible(false);
clickReport(currentReport.id);
});
});
};
const searchReportList = (e) => {
let _data = [...tableData];
let final = _data.map((item) => {
let _item = { ...item };
if (!e) {
_item.visible = true;
} else {
_item.visible = _item.reportName.includes(e);
}
return _item;
});
setTableData(final);
};
const openCreateModal = (callback) => {
if (callback) callback();
getTableFromDB((data) => {
setAllTableList(data);
setCreateModalVisible(true);
});
};
const getTableFromDB = (callback) => {
reportService.getTables().then((res) => {
if (res.code === 0) {
callback(res.data);
} else {
message.error(res.msg);
}
});
};
const getFieldsFromTable = (tableName, type) => {
let _data = tableName.split(',').map((name) => ({ name: name }));
reportService.getTableFields({ tableName: _data }).then((res) => {
if (res.code === 0) {
if (type === 'mainTable') {
setAllFields(res.data[0].fieldName);
}
if (type === 'subTable') {
// setTable
setAllSubFields(res.data);
}
}
});
};
const showPatchSubModal = () => {
getTableFromDB((data) => {
setSubTableList(data.filter((item) => item.name !== currentReport.tableName));
});
setPatchFieldVisible(true);
getFieldsFromTable(currentReport.tableName, 'mainTable');
let subTableName = relationship.map((item) => item.tableName);
if (subTableName && subTableName.length) getFieldsFromTable(subTableName.join(','), 'subTable');
let _relationshipObject = {};
relationship.forEach((item) => {
let _arr = item.relation.split('=');
_relationshipObject[item.tableName + publicSplit + 'mainTable'] = _arr[0];
_relationshipObject[item.tableName + publicSplit + 'subTable'] = _arr[1];
});
patchSubTableForm.setFieldsValue({
subTableName,
subTableFields: detailData.map((item) => item.tableName + publicSplit + item.fieldName),
..._relationshipObject,
});
};
const addVirtualColumn = () => {
// 添加虚拟列;1. 增加字段;2.保存
const _detailData = [...detailData];
const _temp = { ..._detailData[0] };
delete _temp.id;
// delete _temp.tableName;
_temp.index = _detailData.length - 1;
_temp.isVirtualColumn = true;
//fieldAlias: "软件开票"
// fieldGroup: "软件开票"
// fieldName: "软件开票"
_temp.fieldAlias =
_temp.fieldGroup =
_temp.fieldName =
`虚拟字段${moment().format('YYYY-MM-DD-HH-mm-ss').replace(/-/g, '')}`;
_detailData.push(_temp);
setDetailData(_detailData);
// setTempDetailData(_detailData);
editDetailForm.setFieldsValue(_detailData);
// submitDetailFromTable();
};
const editDetailInTable = () => {
// setIsEditing(true);
};
const submitDetailFromTable = () => {
editDetailForm.validateFields().then((res) => {
// 去除掉标签、数字区间内的id
let _detailData = detailData.map((item) => {
let _item = { ...item };
_item.numericalConfigs = _item.numericalConfigs.map((obj) => {
let _obj = { ...obj };
_obj.id = 0;
return _obj;
});
_item.labelConfigs = _item.labelConfigs.map((obj) => {
let _obj = { ...obj };
_obj.id = 0;
return _obj;
});
return _item;
});
reportService.addReportDetailInfo({ reportDetails: _detailData }).then((res) => {
if (res.code === 0) {
message.success('提交成功!');
// setIsEditing(false);
} else {
message.error(res.msg);
}
setSubmitFieldLoading(false);
setDetailVisible(false);
clickReport(currentReport.id);
});
});
};
const patchSubTable2MainTable = () => {
patchSubTableForm.validateFields().then(async (value) => {
setPatchSubTableBtnLoading(true);
// 由于是动态表单数据,导致表单对象无法正常监听数据,要过滤掉未能监听的数据。
let _tempArray = value.subTableFields
.filter((item) => value.subTableName.includes(item.split(publicSplit)[0]))
.map((item) => item.split(publicSplit));
let _tempObject = {};
_tempArray.forEach((item) => {
if (_tempObject[item[0]]) {
_tempObject[item[0]].push(item[1]);
} else {
_tempObject[item[0]] = [item[1]];
}
});
let rM_ReportInfo = Object.keys(_tempObject).map((key) => {
return {
parentId: currentReport.id,
tableName: key,
reportFields: _tempObject[key].join(','),
relation: `${value[key + `${publicSplit}mainTable`]}=${
value[key + `${publicSplit}subTable`]
}`,
};
});
await reportService
.addReportInfo({
rM_ReportInfo,
})
.then((res) => {
if (res.code === 0) {
message.success('保存成功!');
setPatchFieldVisible(false);
} else {
message.error(res.msg);
}
})
.catch((err) => {
message.error('保存失败!');
setPatchSubTableBtnLoading(false);
});
setPatchSubTableBtnLoading(false);
clickReport(currentReport.id);
});
};
const clickColorCard = (e) => {
let _cardPosition = e.target.getBoundingClientRect();
let _wrapperPosition = reportDetails.current.getBoundingClientRect();
let _colorPicker = colorPicker.current.getBoundingClientRect();
// 减去最外层的边距之后,还需要减去色块的高度+外边白边的高度+1px的边框+1px间隙 + 8px的最外层容器的padding
let _cardLeft = _cardPosition.left - _wrapperPosition.left - 4 - 8;
let _cardTop = _cardPosition.top - _wrapperPosition.top + 14 + 4 + 1 + 1 - 8;
// 需要考虑超出边距的问题,如果超出边界,则向上,向左平移
let isOuterOfBottom = _cardTop + _colorPicker.height - _wrapperPosition.height;
// let isOuterOfRight = (_colorPicker.left + _colorPicker.width) - (_wrapperPosition.top + _wrapperPosition.height);
if (isOuterOfBottom > 0) {
_cardTop -= isOuterOfBottom + 10; // 向上平移,10px保证显示效果
_cardLeft -= _colorPicker.width + 5; // 向左平移,5px保证显示效果
}
setColorCardPosition({
left: _cardLeft,
top: _cardTop,
});
setShowSketchPicker(true);
};
const addColorPicker = (data, callback, init) => {
let _labels = [...data];
_labels.push(init);
callback(_labels);
};
const removeColorPicker = (data, callback, index) => {
let _labels = [...data];
_labels.splice(index, 1);
callback(_labels);
};
const changeColorLabel = (e, data, callback, key, index) => {
// 接口需要id全部为 0
let _labels = [...data];
_labels[index][key] = isNumber(e) ? e : e.target.value; // e为数值时是InputNumber,e为对象时是Input
callback(_labels);
};
const changeBackgroundColor = (e) => {
// 改变当前的
let _rgb = e.rgb;
if (currentColorPicker.key === 'wordColor') {
setBackgroundColor(`rgba(${_rgb.r},${_rgb.g},${_rgb.b},${_rgb.a})`);
return;
}
let { data, callback, index, key } = currentColorPicker;
let _data = [...data];
_data[index][key] = `rgba(${_rgb.r},${_rgb.g},${_rgb.b},${_rgb.a})`;
callback(_data);
};
const returnCurrentColor = () => {
let { data, index, key } = currentColorPicker;
if (key === 'init') return 'rgba(0,0,0,.65)';
return data[index][key];
};
// 颜色卡片组件
//[
// "rgb(255, 255, 255)",
// "rgb(214, 229, 255)",
// "rgb(214, 241, 255)",
// "rgb(211, 243, 226)",
// "rgb(255, 220, 219)",
// "rgb(255, 236, 219)",
// "rgb(255, 245, 204)",
// "rgb(251, 219, 255)",
// "rgb(255, 219, 234)",
// "rgb(220, 223, 228)",
// "rgb(173, 203, 255)",
// "rgb(173, 228, 255)",
// "rgb(172, 226, 197)",
// "rgb(255, 181, 179)",
// "rgb(255, 206, 163)",
// "rgb(255, 234, 153)",
// "rgb(231, 180, 255)",
// "rgb(255, 179, 220)",
// "rgb(129, 134, 143)",
// "rgb(41, 114, 244)",
// "rgb(0, 163, 245)",
// "rgb(69, 176, 118)",
// "rgb(222, 60, 54)",
// "rgb(248, 136, 37)",
// "rgb(231, 180, 255)",
// "rgb(154, 56, 215)",
// "rgb(221, 64, 151)"
// ]
const ColorBoard = ({ value, onChange }) => {
return (
<>
<div
style={{
padding: 4,
display: 'inline-flex',
borderRadius: '2px',
border: '1px solid rgba(0,0,0,.35)',
}}
>
<div
style={{
width: '36px',
height: '14px',
borderRadius: '2px',
border: '1px solid rgba(0,0,0,.35)',
backgroundColor: value || 'rgba(0,0,0,.65)',
}}
onClick={(e) => {
clickColorCard(e);
setCurrentColorPicker({
key: 'wordColor',
callback: onChange,
});
}}
/>
</div>
</>
);
};
// 拖拽
const DragHandle = SortableHandle(() => (
<MenuOutlined
style={{
cursor: 'grab',
color: '#999',
}}
/>
));
const saveOrder = (data) => {
let _data = data.map((item, index) => {
let _item = {};
_item.index = index;
_item.id = item.id;
return _item;
});
addReportDetailInfoIndex(_data).then((res) => {
if (res.code !== 0) {
message.error(res.msg);
} else {
clickReport(currentReport.id);
}
});
};
const submitDetail = (data) => {
reportService.addReportDetailInfo({ reportDetails: data }).then((res) => {
if (res.code === 0) {
message.success('提交成功!');
} else {
message.error(res.msg);
}
setSubmitFieldLoading(false);
clickReport(currentReport.id);
});
};
const modifyDetailData = (key, value, record, index, data = detailData) => {
let _data = [...data];
let _record = { ...record };
_record[key] = value;
_data.splice(index, 1, _record);
setDetailData(_data);
};
useEffect(() => {
getData();
}, []);
useEffect(() => {
let numericalConfigs = currentField ? currentField.numericalConfigs : [];
let labelConfigs = currentField ? currentField.labelConfigs : [];
setNumberColorPickerArray(numericalConfigs);
setLabelColorPickerArray(labelConfigs);
}, [watchType]);
return (
<div className={style.reportSettings}>
{detailTableVisible ? (
<div ref={reportDetails} className={style.reportDetails}>
{/* 色板容器 */}
<div
onClick={(e) => {
if (e.target.className.includes('colorSketch')) {
setShowSketchPicker(false);
}
}}
style={{
visibility: showSketchPicker ? 'visible' : 'hidden',
}}
className={style.colorSketch}
>
<div
ref={colorPicker}
style={{
position: 'absolute',
left: colorCardPosition.left,
top: colorCardPosition.top,
}}
>
<SketchPicker
width={240}
presetColors={[
'rgb(255, 255, 255)',
'rgb(214, 229, 255)',
'rgb(214, 241, 255)',
'rgb(211, 243, 226)',
'rgb(255, 220, 219)',
'rgb(255, 236, 219)',
'rgb(255, 245, 204)',
'rgb(251, 219, 255)',
'rgb(255, 219, 234)',
'rgb(220, 223, 228)',
'rgb(173, 203, 255)',
'rgb(173, 228, 255)',
'rgb(172, 226, 197)',
'rgb(255, 181, 179)',
'rgb(255, 206, 163)',
'rgb(255, 234, 153)',
'rgb(231, 180, 255)',
'rgb(255, 179, 220)',
'rgb(129, 134, 143)',
'rgb(41, 114, 244)',
'rgb(0, 163, 245)',
'rgb(69, 176, 118)',
'rgb(222, 60, 54)',
'rgb(248, 136, 37)',
'rgb(216,180,255)',
'rgb(154, 56, 215)',
'rgb(221, 64, 151)',
]}
color={
currentColorPicker.key === 'wordColor' ? backgroundColor : returnCurrentColor()
}
onChange={(e) => changeBackgroundColor(e)}
/>
</div>
</div>
<Modal
title={'附加子表'}
width={600}
visible={patchFieldVisible}
onCancel={() => {
setPatchFieldVisible(false);
setAllFields([]);
setAllSubFields([]);
}}
onOk={patchSubTable2MainTable}
okButtonProps={{
loading: patchSubTableBtnLoading,
}}
>
<Form form={patchSubTableForm} {...layout}>
<Form.Item
label={'子表'}
rules={[
{
required: true,
message: '请选择需要附加的子表',
},
]}
name={'subTableName'}
>
<Select
placeholder={'请选择子表'}
maxTagCount={2}
style={{ width: '100%' }}
mode={'multiple'}
onChange={(value) => {
console.log(value);
getFieldsFromTable(value.join(','), 'subTable');
}}
>
{subTableList.map((item) => (
<Option value={item.name}>{item.name}</Option>
))}
</Select>
</Form.Item>
{allSubFields && allSubFields.length ? (
<Form.Item
label={'子表字段'}
rules={[
{
required: true,
message: '请选择字段',
},
]}
name={'subTableFields'}
>
<Checkbox.Group style={{ height: 400, overflowY: 'scroll' }}>
{allSubFields.map((item, index) => {
return (
<div>
<Divider orientation="left">{item.tableName}</Divider>
<Row style={{ display: 'flex' }}>
<Form.Item
label={'主表'}
name={`${item.tableName}${publicSplit}mainTable`}
rules={[
{
required: true,
message: '请选择对应字段',
},
]}
>
<Select style={{ width: 150 }} placeholder={'主表对应字段'}>
{allFields.map((field) => (
<Option value={`${currentReport.tableName}.${field.name}`}>
{field.name}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
label={'子表'}
name={`${item.tableName}${publicSplit}subTable`}
rules={[
{
required: true,
message: '请选择对应字段',
},
]}
>
<Select style={{ width: 150 }} placeholder={'子表关联字段'}>
{item.fieldName.map((field) => (
<Option value={`${item.tableName}.${field.name}`}>
{field.name}
</Option>
))}
</Select>
</Form.Item>
</Row>
{item.fieldName.map((field) => {
return (
<Row>
<Checkbox value={`${item.tableName}${publicSplit}${field.name}`}>
{field.name}
</Checkbox>
</Row>
);
})}
</div>
);
})}
</Checkbox.Group>
</Form.Item>
) : (
''
)}
</Form>
</Modal>
<Modal
title={'编辑字段属性'}
width={920}
visible={detailVisible}
onCancel={() => setDetailVisible(false)}
destroyOnClose
footer={null}
onClick={() => setShowSketchPicker(false)}
>
<div style={{ display: 'flex' }}>
<div style={{ width: 200 }}>
{detailData ? (
<ul>
{detailData.map((item, index) => (
<li
onClick={() => changeField(item)}
className={classname(
style.fieldList,
activeID === item.id ? style.active : '',
)}
key={item.id}
>
{item.fieldName}
</li>
))}
</ul>
) : (
''
)}
</div>
<div
style={{
flex: 1,
}}
>
{/*<Form {...layout} form={form} initialValues={setDefaultValue(currentField)}*/}
<Form {...layout} form={form} onFinish={submitReportDetails}>
<div style={{ height: 500, overflowY: 'scroll' }}>
<Form.Item
disabled
label={'字段'}
name={'fieldName'}
rules={[
{
required: true,
message: '编码名称必填',
},
]}
>
<Input disabled style={{ width: wordInputWidth }} />
</Form.Item>
{/* <Form.Item label={'别名'} name={'fieldAlias'} rules={[
{
required: true, message: '别名必填且不可重复',
},
]}>
<Input style={{ width: wordInputWidth }} />
</Form.Item>*/}
<Form.Item
label={'表头级数'}
name={'level'}
rules={[
{
required: true,
message: '别名必填且不可重复',
},
]}
>
<InputNumber min={1} />
</Form.Item>
{fieldGroupLevel ? (
<Form.Item style={{ marginBottom: 0 }} label={'分组'}>
<Input.Group compact>
{new Array(Number(fieldGroupLevel) * 2 - 1).fill(1).map((item, index) => {
if (index % 2 === 0) {
return (
<Form.Item
key={`fieldGroup_${index / 2}`}
style={{ width: 160 }}
name={[
'fieldGroup',
`fieldGroup_${currentField.fieldAlias}_${index / 2}`,
]}
rules={[
{
required: true,
message: '分组名称必填',
},
]}
>
<Input />
</Form.Item>
);
} else {
return (
<span style={{ color: 'rgba(0,0,0,.25)' }}> &nbsp; _ &nbsp;</span>
);
}
})}
</Input.Group>
</Form.Item>
) : (
''
)}
<Form.Item label={'形态'} name={'type'}>
<Select style={{ width: wordInputWidth }}>
{typeArray.map((item) => (
<Option value={item}>{item}</Option>
))}
</Select>
</Form.Item>
<Form.Item label={'字体颜色'} name={'color'}>
<div
style={{
padding: 4,
display: 'inline-flex',
borderRadius: '2px',
border: '1px solid rgba(0,0,0,.35)',
}}
>
<div
style={{
width: '36px',
height: '14px',
borderRadius: '2px',
border: '1px solid rgba(0,0,0,.35)',
backgroundColor: backgroundColor,
}}
onClick={(e) => {
clickColorCard(e);
setCurrentColorPicker({
key: 'wordColor',
});
}}
/>
</div>
</Form.Item>
{/* <Form.Item label={'对齐方式'} name={'alignType'}>
<Radio.Group>
<Radio value={'left'}>左</Radio>
<Radio value={'center'}>中</Radio>
<Radio value={'right'}>右</Radio>
</Radio.Group>
</Form.Item>*/}
{watchType === '数值' ? (
<Form.Item label={'区间设置'}>
{numberColorPickerArray.map((item, index) => {
return (
<Space
key={item.key}
style={{
display: 'flex',
marginBottom: 8,
width: rangeWidth,
}}
align="baseline"
>
<Form.Item label={'最小值'}>
<InputNumber
value={item.minValue}
onChange={(e) =>
changeColorLabel(
e,
numberColorPickerArray,
setNumberColorPickerArray,
'minValue',
index,
)
}
/>
</Form.Item>
<Form.Item label={'最大值'}>
<InputNumber
value={item.maxValue}
onChange={(e) =>
changeColorLabel(
e,
numberColorPickerArray,
setNumberColorPickerArray,
'maxValue',
index,
)
}
/>
</Form.Item>
<Form.Item label={'颜色'}>
<div
style={{
padding: 4,
display: 'inline-flex',
borderRadius: '2px',
border: '1px solid rgba(0,0,0,.35)',
}}
>
<div
style={{
width: '36px',
height: '14px',
borderRadius: '2px',
border: '1px solid rgba(0,0,0,.35)',
backgroundColor: item.color,
}}
onClick={(e) => {
clickColorCard(e);
setCurrentColorPicker({
data: numberColorPickerArray,
callback: setNumberColorPickerArray,
index,
key: 'color',
});
}}
/>
</div>
</Form.Item>
<MinusCircleOutlined
onClick={() =>
removeColorPicker(
numberColorPickerArray,
setNumberColorPickerArray,
index,
)
}
/>
</Space>
);
})}
<Form.Item>
<Button
type="dashed"
onClick={() =>
addColorPicker(numberColorPickerArray, setNumberColorPickerArray, {
maxValue: '',
minValue: '',
color: 'rgba(0,0,0,.65)',
})
}
block
icon={<PlusOutlined />}
>
增加区间
</Button>
</Form.Item>
</Form.Item>
) : (
''
)}
{watchType === '数值' ? (
<Form.Item label={'统计规则'} style={{ display: 'flex' }}>
<Space
style={{
display: 'flex',
marginBottom: 8,
}}
>
<Form.Item
style={{ marginBottom: 0 }}
name={'isStatistics'}
valuePropName={'checked'}
>
<Switch
checkedChildren="开启"
unCheckedChildren="关闭"
defaultChecked={false}
/>
</Form.Item>
{isStatistics ? (
<>
<Form.Item
label={'统计规则'}
rules={[
{
required: true,
message: '请选择统计规则',
},
]}
style={{ marginBottom: 0 }}
name={'statisticsRule'}
>
<Select style={{ width: 120 }}>
<Option value={'求和'}>求和</Option>
<Option value={'求平均'}>求平均</Option>
<Option value={'求方差'}>求方差</Option>
</Select>
</Form.Item>
<Form.Item
name={['statisticsData', '单页']}
style={{ marginBottom: 0 }}
label={'单页统计'}
valuePropName={'checked'}
>
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
</Form.Item>
<Form.Item
name={['statisticsData', '全部']}
style={{ marginBottom: 0 }}
label={'全部统计'}
valuePropName={'checked'}
>
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
</Form.Item>
</>
) : (
''
)}
</Space>
</Form.Item>
) : (
''
)}
{watchType === '标签' ? (
<Form.Item label={'标签设置'}>
{labelColorPickerArray.map((item, index) => {
return (
<Space
key={item.key}
style={{
display: 'flex',
marginBottom: 8,
}}
align="baseline"
>
<Form.Item label={'标签'}>
<Input
value={item.labelValue}
onChange={(e) =>
changeColorLabel(
e,
labelColorPickerArray,
setLabelColorPickerArray,
'labelValue',
index,
)
}
/>
</Form.Item>
<Form.Item label={'颜色'}>
<div
style={{
padding: 4,
display: 'inline-flex',
borderRadius: '2px',
border: '1px solid rgba(0,0,0,.35)',
}}
>
<div
style={{
width: '36px',
height: '14px',
borderRadius: '2px',
border: '1px solid rgba(0,0,0,.35)',
backgroundColor: item.color,
}}
onClick={(e) => {
clickColorCard(e);
setCurrentColorPicker({
data: labelColorPickerArray,
callback: setLabelColorPickerArray,
index,
key: 'color',
});
}}
/>
</div>
</Form.Item>
<MinusCircleOutlined
onClick={() =>
removeColorPicker(
labelColorPickerArray,
setLabelColorPickerArray,
index,
)
}
/>
</Space>
);
})}
<Form.Item>
<Button
type="dashed"
onClick={() =>
addColorPicker(labelColorPickerArray, setLabelColorPickerArray, {
color: 'rgba(0,0,0,.65)',
labelValue: '',
})
}
block
icon={<PlusOutlined />}
>
增加区间
</Button>
</Form.Item>
</Form.Item>
) : (
''
)}
<Form.Item label={'配置'} name={'configItems'}>
<TextArea rows={6} />
</Form.Item>
</div>
<Form.Item label={' '} colon={false} style={{ marginTop: 10 }}>
<Button onClick={() => setDetailVisible(false)}>取消</Button>
<Button
loading={submitFieldLoading}
style={{ marginLeft: 8 }}
type="primary"
htmlType="submit"
>
提交
</Button>
</Form.Item>
</Form>
</div>
</div>
</Modal>
<div className={style.tableWrapper}>
<Row className={style.controlRow}>
<LeftOutlined
className={style.leftBtn}
onClick={() => {
setDetailTableVisible(false);
}}
/>
<Form
layout={'inline'}
style={{ display: 'flex', flex: 1, justifyContent: 'space-between' }}
>
<div style={{ display: 'flex' }}>
<Form.Item label={'快速索引'}>
<Search
onSearch={(e) => {
let _detailData = detailData.map((item) => {
let _item = { ...item };
if (e) {
_item.visible =
item.fieldName.includes(e) || item.fieldAlias.includes(e);
} else {
_item.visible = true;
}
return _item;
});
setDetailData(_detailData);
setTempDetailData(_detailData);
}}
/>
</Form.Item>
<Form.Item>
<Button onClick={showPatchSubModal}>附加子表</Button>
<Button onClick={addVirtualColumn}>添加虚拟字段</Button>
</Form.Item>
</div>
<Form.Item style={{ marginRight: 0 }}>
{isEditing ? (
<>
<Button
style={{ marginRight: 8 }}
onClick={() => {
setDetailData(tempDetailData);
editDetailForm.setFieldsValue(tempDetailData);
}}
>
重置
</Button>
<Button
type={'primary'}
style={{ marginRight: 8 }}
onClick={submitDetailFromTable}
>
保存字段
</Button>
</>
) : (
''
)}
{isEditing ? (
<Button type={'primary'} onClick={() => setIsEditing(false)}>
拖动排序
</Button>
) : (
<Button type={'primary'} onClick={() => setIsEditing(true)}>
关闭排序
</Button>
)}
</Form.Item>
</Form>
{/* <Button type={'primary'} onClick={() => {
setDetailTableVisible(false);
// setIsEditing(false);
}}>返回</Button>*/}
</Row>
<div className={style.tableContent}>
<Form className={style.tableForm} form={editDetailForm}>
<BasicTable
loading={detailTableLoading}
pagination={false}
bordered
scroll={{ y: 'calc(100% - 40px)' }}
rowKey={'id'}
columns={detailColumns}
dataSource={detailData.filter((item) => item.visible)}
components={
isEditing
? ''
: {
body: {
wrapper: DraggableContainer,
row: DraggableBodyRow,
},
}
}
onRow={(record) => {
return {
onDoubleClick: (e) => {
if (
e.target.cellIndex === detailColumns.length - 1 ||
e.target.cellIndex === void 0
)
return false;
setActiveID(record.id);
if (record.color) setBackgroundColor(record.color);
form.setFieldsValue(setDefaultValue(record));
setCurrentField(setDefaultValue(record));
setDetailVisible(true);
},
};
}}
/>
</Form>
</div>
</div>
</div>
) : (
<div className={style.reportDetails}>
<Modal
onCancel={() => setCreateModalVisible(false)}
closable={false}
visible={createModalVisible}
footer={null}
destroyOnClose
>
<Form form={createMainTableForm} {...layout} onFinish={createReport}>
<Form.Item
label={'主表'}
name={'tableName'}
rules={[
{
required: true,
message: '请选择一张主表',
},
]}
>
<Select
disabled={!isCreatingMainTable}
placeholder={'选择一张表作为主表'}
showSearch
optionFilterProp="children"
filterOption={(input, option) => option.children.includes(input)}
onChange={(e) => {
getFieldsFromTable(e, 'mainTable');
createMainTableForm.setFields([{ reportFields: [] }]);
}}
>
{allTableList.map((item) => (
<Option value={item.name}>{item.name}</Option>
))}
</Select>
</Form.Item>
<Form.Item
label={'报表名称'}
name={'reportName'}
rules={[
{
required: true,
message: '请输入报表名称',
},
]}
>
<Input placeholder={'请输入报表名称'} />
</Form.Item>
{allFields.length ? (
<Form.Item
label={'报表字段'}
name={'reportFields'}
rules={[{ required: true, message: '请选择需要绑定的字段!' }]}
>
<Checkbox.Group style={{ maxHeight: 400, overflowY: 'scroll' }}>
{allFields.map((item) => (
<Row>
<Checkbox value={item.name}>{item.name}</Checkbox>
</Row>
))}
</Checkbox.Group>
</Form.Item>
) : (
''
)}
<Form.Item label={' '} colon={false}>
<Button onClick={() => setCreateModalVisible(false)}>取消</Button>
<Button
loading={createBtnLoading}
style={{ marginLeft: 8 }}
type={'primary'}
htmlType={'submit'}
>
提交
</Button>
</Form.Item>
</Form>
</Modal>
<div className={style.tableWrapper}>
<Row className={style.controlRow}>
<Form layout={'inline'}>
<Form.Item label={'快速索引'}>
<Search onSearch={(e) => searchReportList(e)} />
</Form.Item>
<Form.Item>
<Button
type={'primary'}
onClick={() =>
openCreateModal(() => {
setCurrentReport({});
setAllFields([]);
setIsCreatingMainTable(true);
createMainTableForm.setFieldsValue({
tableName: '',
reportName: '',
reportFields: [],
});
})
}
>
新增
</Button>
</Form.Item>
</Form>
</Row>
<div className={style.tableContent}>
<BasicTable
pagination={false}
bordered
loading={tableLoading}
rowKey={'id'}
columns={columns}
dataSource={tableData.filter((item) => item.visible)}
/>
</div>
</div>
</div>
)}
</div>
);
};
export default ReportsSetting;
@import '~antd/es/style/themes/default.less';
.reportSettings {
//width: 100%;
height: 100%;
.reportFiled {
display: inline-block;
width: 100%;
overflow: hidden;
line-height: 1;
white-space: nowrap;
text-overflow: ellipsis;
&:hover {
color: #1685ff;
}
}
.reportDetails {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 8px;
.colorSketch {
position: absolute;
z-index: 1001;
width: 100%;
height: 100%;
}
:global {
.@{ant-prefix}-form-item {
margin-bottom: 0 !important;
}
}
.tableWrapper {
display: flex;
flex: 1;
flex-direction: column;
overflow: hidden;
.controlRow {
display: flex;
flex: 0 0 44px;
//justify-content: space-between;
align-items: center;
height: 44px;
margin-bottom: 4px;
padding: 6px;
background: #ffffff;
.leftBtn {
margin-right: 10px;
font-weight: bold;
font-size: 18px;
cursor: pointer;
&:hover {
color: #1685ff;
opacity: 0.8;
}
}
}
.tableContent {
flex: 1;
width: 100%;
max-height: calc(100% - 44px);
padding: 6px;
overflow: hidden;
background: #ffffff;
.tableForm {
height: 100%;
}
}
}
}
}
.boxShadow {
border-color: #40a9ff;
border-right-width: 1px;
outline: 0;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.boxShadowOfSwitch {
border-right-width: 1px;
outline: 0;
box-shadow: 0 0 4px 2px rgba(24, 144, 255, 0.6);
}
.boxShadowOfSelect {
border-right-width: 1px;
outline: 0;
box-shadow: 0 0 2px 2px rgba(24, 144, 255, 0.2);
:global {
.@{ant-prefix}-select-selector {
border-color: #40a9ff !important;
}
}
}
.fieldList {
cursor: pointer !important;
&:hover {
color: #1685ff;
}
&.active {
color: #1685ff;
}
}
/**
* * 用来动态加载配置的组件功能函数 * create by ChenLong on 2022/7/28 * @Params: url 功能路径,加载需要的业务组件 * onCancel
* 从详情组件回到当前功能的函数 * params 其他的必要参数 * 功能路径:src\pages\product\ReportsManage\extra\detailsComponent.js
* * 菜单参数列表:*变量名*(变量说明,数据类型,是否必填,取值范围)
*/
import React from 'react';
import { Button } from 'antd';
const DetailsComponent = ({ url, onCancel, params }) => {
/* const BusinessComponent = React.lazy(() => import(`@/pages/${url}`));
return <BusinessComponent onCancel={onCancel} {...params} />;*/
return <div>弹窗组件在组件中未实现</div>;
};
export default DetailsComponent;
// import AccountPermissionScan from '../../AccountManage/AccountPermissionScan';
import React from 'react';
import pic from '../assets/pic.webp';
const extraComponents = {
renderAccount: (props) => {
return <div>台账组件</div>;
},
returnImage: (data) => {
return <img width={200} height={200} src={pic} alt={data.alt} />;
},
default: () => <span>若需要使用弹窗功能,请开发或配置功能函数</span>,
};
export default extraComponents;
import style from '../index.less';
import style from '../ReportsManage.less';
import extraComponents from '../extra/extraComponents';
import moment from 'moment';
import { Tag } from 'antd';
......@@ -206,5 +206,5 @@ export const handlePageSize = (numStr) => {
};
// 处理默认排序
export const handleSortFields = (sortFields) => {
return sortFields && sortFields.split(',');
return (sortFields && sortFields.split(',')) || [];
};
export const RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount';
export const DAEMON = '@@saga-injector/daemon';
export const ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount';
export const FILTER_FOLER_REG =
/(configs|configuration|framework|iframe|Product|product|productex|project|qrcode|threedimensional|video|widgetconfigs)/;
export const FILTER_FOLER_WEB5 = /(pages)/;
export const SERVICE_APP_GET_UI_META = 'app.getUIMeta';
export const SERVICE_APP_LOGIN_MODE = {
......
/**
* * 轻量化报表通用配置页面 * 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:
*
*
* 需要默认加载的过滤条件的field,英文逗号分割,与filterValues一一对应;
* filterValues:
*
*
* 需要默认加载的过滤条件的value,英文逗号分割,与filterFields一一对应;
* state: delete|edit|scan 各种权限;
*
*
*
* customerState: ['filters','sortBtn','exportBtn','editBtn','deleteBtn','pagination'];
*
*
*
* sortFields: '排序字段1,排序字段2,排序字段3'
* @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,
} from 'antd';
import {
SortAscendingOutlined,
MinusCircleOutlined,
ExportOutlined,
FormOutlined,
} from '@ant-design/icons';
import BasicTable from '@wisdom-components/basictable';
import ReturnControlComponent from './components/Control';
import { reportService } from './api/index';
import style from './index.less';
import { exportAccountData } from './api/service/report';
import DatePickerGroup from './components/DatePickerGroup';
import moment from 'moment';
import {
handleNumber,
handleTag,
handleText,
handleLink,
handleWidget,
handleModal,
handleDateString,
handleDateTimeString,
handlePageSize,
handleSortFields,
} from './utils/handlers';
import { hasMoney, isArray, isNumber, isString, returnHandledNumber } from './utils/utils';
import { connect } from 'react-redux';
import ReportEditForm from './ReportEditForm';
import React from 'react';
import ReportsManage from './ReportsManage/ReportsManage';
const ControlsType = ['下拉', '多选', '日期'];
const fieldSplitStr = '-'; // fieldGroup用来分割
const { Option } = Select;
const dateFormat = 'YYYY-MM-DD'; // 日期格式化
const initDateModel = 'all';
let timer = null;
const PERMISSION = {
delete: [
'filters',
'pagination',
// 操作条按钮
'sortBtn',
'exportBtn',
// 操作列
'editBtn',
'deleteBtn',
],
edit: [
'filters',
'pagination',
'sortBtn',
'exportBtn',
// 操作列
'editBtn',
],
scan: ['filters', 'pagination'],
};
const BasicReport = (props) => {
const { reportName, pageSize, filterFields, filterValues, state, customerState, sortFields } =
props.params;
const permission =
customerState && isArray(customerState) ? customerState : PERMISSION[state || 'delete'];
const tableWrapperRef = useRef();
const controlRef = useRef();
if (!reportName)
return (
<div className={style.lackParams}>未配置reportName,请完善配置并重新登陆后查看页面!</div>
);
const [tableStruct, setTableStruct] = useState([]); // 临时使用,看后续是否需要保留
const [columns, setColumns] = useState([]); // 表头设置
const [tableData, setTableData] = 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(false, _pagination);
},
onChange: (current, size) => {
let _pagination = { ...pagination };
_pagination.current = current;
_pagination.pageSize = size;
setPagination(_pagination);
getData(false, _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 [detailsComponent, setDetailsComponent] = useState(<></>); // 详情组件
const [detailsComponentVisible, setDetailsComponentVisible] = useState(false); // 是否显示详情组件
const [editComponentVisible, setEditComponentVisible] = useState(false); // 是否显示编辑组件
const [currentData, setCurrentData] = useState({});
const [detailConfig, setDetailConfig] = useState({ url: '', type: '', params: {} });
const [controlsHeight, setControlsHeight] = useState(44);
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',
},
];
return <Menu items={_item} />;
};
const exportModule = (type, extension) => {
setExportLoading(true);
exportAccountData(
{ responseType: 'blob' },
{ exportType: type },
{
reportName,
pageIndex: 0,
pageSize: 0,
sortFields: '',
content: '',
filters: [
{
fieldAlias: '',
fieldValue: '',
},
],
},
)
.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 searchData = (e) => {
getData(false, pagination);
};
const controlSelectChange = (fieldAlias, e) => {
let _filterArray = { ...filterObject };
_filterArray[fieldAlias] = e;
setFilterObject(_filterArray);
};
const searchInputChange = (e) => {
setSearchContent(e.target.value);
};
const setConfig = (config, summary, data) => {
getControlsBarConfig(config);
getTableSummaryConfig(config, summary);
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) {
let _customerFilterArray = filterFields.split(',');
let _customerValueArray = filterValues.split(',');
_customerFilterArray.forEach((item, index) => {
_filters.push({
fieldAlias: item,
fieldValue: _customerValueArray[index] || '',
});
});
}
// 并入 _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 getData = (isInit, pagination) => {
setTableLoading(true);
const { pageSize, current } = pagination;
// 搜索条件附加到params
let _data = addFilterAndSearchParams({
reportName: reportName,
pageIndex: current,
pageSize: pageSize,
});
// sortFields
reportService
.getReportInfo(_data)
.then((res) => {
if (res.code === 0) {
let _reportDetails = res.data.reportDetails;
let _statisticalValues = res.data.statisticalValues;
let _tableData = res.data.data.list;
let _sortString = res.data.sortString;
if (isInit) {
setConfig(_reportDetails, _statisticalValues, _tableData);
}
getTableHeaderConfig(_reportDetails, _tableData);
let _pagination = { ...pagination };
_pagination.total = 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([]);
}
setTableLoading(false);
})
.catch((err) => {
console.log(err);
setTableLoading(false);
});
};
/** @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);
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,
数值: handleNumber,
标签: 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 = handleDataToGetRowSpanArray(_config, data); // 需要向下合并的字段
_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[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, value || '', record, ...rest);
},
};
_item.width = 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: '操作',
width: 60,
fixed: 'right',
render: (text, record) => {
return (
<Space className={style.handleColumnWrapper}>
{permission.includes('editBtn') ? (
<FormOutlined
className={style.editButton}
onClick={() => {
setEditComponentVisible(true);
setCurrentData(record);
}}
/>
) : (
''
)}
{/* {
permission.includes('deleteBtn') ?
<DeleteOutlined disabled className={style.deleteButton} onClick={() => {
Modal.confirm({
content: '你确定要删除改数据吗?',
onOK: () => message.error('您无法删除数据!'),
});
}} /> : ''
}*/}
</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 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(',');
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() };
}
setDefaultDateConfig({
defaultDate: _defaultDate?.dateFrom,
defaultModel:
_configItems.find((item) => item.includes('defaultModel='))?.replace('defaultModel=', '') ||
'year',
});
};
useEffect(() => {
getData(true, pagination);
}, []);
useEffect(() => {
if (tableHeaderLevel) setTableHeight();
}, [tableHeaderLevel]);
useEffect(() => {
getData(false, pagination);
}, [timeFrom, timeTo, filterObject]);
useEffect(() => {
function getRefHeight() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
let _height = controlRef?.current.clientHeight;
setControlsHeight(_height);
}, 100);
}
window.addEventListener('resize', getRefHeight);
return () => window.removeEventListener('resize', getRefHeight);
});
return (
<div className={style.reportManage} ref={tableWrapperRef}>
{/* 为方便阅读,分开两部分代码 */}
{!detailsComponentVisible ? (
<div className={style.contentWrapper}>
{state !== 'scan' ? (
<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)}
/>
</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') ? (
<div style={{ width: 200, textAlign: 'end' }}>
<Space size={8} nowrap={true}>
{sortModalVisible && permission.includes('sortBtn') ? (
<Form.Item>
<Button
type={'primary'}
title={'自定义排序'}
icon={<SortAscendingOutlined />}
onClick={() => setModalVisible(true)}
>
排序
</Button>
</Form.Item>
) : (
''
)}
{permission.includes('exportBtn') ? (
<Form.Item>
<Dropdown.Button style={{ float: 'right' }} overlay={menu}>
导出
</Dropdown.Button>
</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) => {
console.log(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['单页'])}
</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['全部'])}
</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(false, 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={'编辑报表信息'}
visible={editComponentVisible}
width={'80%'}
footer={null}
// visible={true}
onCancel={() => setEditComponentVisible(false)}
>
<ReportEditForm
reportDetails={tableStruct}
reportData={currentData}
onCancel={() => {
setEditComponentVisible(false);
getData(false, 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,
};
return <ReportsManage {...props} />;
};
export default connect(mapStateToProps, null)(BasicReport);
export default BasicReport;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment