Commit 48ac1217 authored by 陈龙's avatar 陈龙

feat: 提交报表代码

parent e7a194d1
# Change Log
All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.2](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.8.0...@wisdom-components/basicreport@1.8.2) (2022-12-13)
### Bug Fixes
- 修复函数未引用导致的 bug ([3f3eb57](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/3f3eb57d01d9d97a0e1c5b836cef09e93d1193b1))
- 修复函数未引用导致的 bug ([07038a2](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/07038a22afd57b8935ea6cdb6c3fd761f14f3ff0))
## [1.8.1](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.8.0...@wisdom-components/basicreport@1.8.1) (2022-12-13)
### Bug Fixes
- 修复函数未引用导致的 bug ([07038a2](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/07038a22afd57b8935ea6cdb6c3fd761f14f3ff0))
# [1.8.0](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.7.0...@wisdom-components/basicreport@1.8.0) (2022-12-07)
### Features
- 更新轻量化报表 ([3985ad9](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/3985ad9af1a9d3130ad07eab0cb959a820a73fad))
# [1.7.0](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.6.0...@wisdom-components/basicreport@1.7.0) (2022-12-07)
### Features
- 更新轻量化报表功能 ([d73f0e6](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/d73f0e6c84d5633af7cfe3d4bf3c234bcaa8f2c9))
# [1.6.0](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.5.0...@wisdom-components/basicreport@1.6.0) (2022-09-13)
### Features
- 更新 baiscreport 版本 ([fa63f24](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/fa63f244ddcc23f4fe11dd78acd8d9fcc8b3cc8a))
- 优化默认筛选值的逻辑,现在可以配置多个筛选值 ([62f7fed](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/62f7fed834d31526b8fb33868fad905bf45de4fc))
# [1.5.0](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.4.0...@wisdom-components/basicreport@1.5.0) (2022-09-13)
### Features
- 实现选择器 ([9df6ec8](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/9df6ec87a429fe9c8d321a6f7bc4a5677b852b7f))
# [1.4.0](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.3.1...@wisdom-components/basicreport@1.4.0) (2022-09-07)
### Bug Fixes
- 修复组件引入错误 ([31facdf](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/31facdf92b6200c3ceac31fb549a77faa4a68434))
### Features
- 新增 demo ([0ffab69](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/0ffab69ab677d377ce861e1ab9205ab8b78464a4))
## [1.3.1](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.3.0...@wisdom-components/basicreport@1.3.1) (2022-09-06)
### Bug Fixes
- 修复路径错误 ([c7d8fa8](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/c7d8fa8230f1f47478010e8e1263b1aa46703311))
# [1.3.0](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.2.3...@wisdom-components/basicreport@1.3.0) (2022-09-05)
### Features
- 新增大量功能 ([437dd7c](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/437dd7ca8cb83552b88e11585bb9b7040ee6b07e))
## [1.2.3](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.2.2...@wisdom-components/basicreport@1.2.3) (2022-08-29)
### Bug Fixes
- 兼容日期为 null 的情况 ([0ef2dfb](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/0ef2dfb65a938134d8ecea589479ccc99c7c7b93))
## [1.2.2](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.2.1...@wisdom-components/basicreport@1.2.2) (2022-08-18)
### Bug Fixes
- fix ([bc49887](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/bc498875cbd3deb3104ff5d61c99cb3c318ba355))
## [1.2.1](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.2.0...@wisdom-components/basicreport@1.2.1) (2022-08-18)
### Bug Fixes
- 修复数据格式导致的报错;修复路径引入导致的报错 ([6014c40](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/6014c40b6b19f9060b69e95ce729dcdfc2935521))
# [1.2.0](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.1.3...@wisdom-components/basicreport@1.2.0) (2022-08-18)
### Features
- 重新处理报表逻辑 ([3ebe719](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/3ebe71907d0b60988a53c7b641189f748490826b))
## [1.1.3](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.1.2...@wisdom-components/basicreport@1.1.3) (2022-08-17)
### Bug Fixes
- 重新发布 ([2776cf9](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/2776cf9dd8f7520a84c00fad5500da38ec42dc8f))
## [1.1.2](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.1.1...@wisdom-components/basicreport@1.1.2) (2022-08-17)
### Bug Fixes
- 删除无效引用 ([372c06f](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/372c06f3233ab5e48d6b104addfb6ba95e43d847))
## [1.1.1](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/compare/@wisdom-components/basicreport@1.1.0...@wisdom-components/basicreport@1.1.1) (2022-08-17)
### Bug Fixes
- 修复文件引用错误 ([7e486a8](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/7e486a8a6c321fd36d89e78975c4e65002f7eaa2))
# 1.1.0 (2022-08-17)
### Features
- 新增报表组件 ([561f975](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/561f975db85081d678854c94e859406903f71438))
- 新增 BasicReport ([fb2e80f](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/fb2e80f00b130f48e743de5dd4781913e7ff4c01))
- 增加 package.json ([e10973a](https://g.civnet.cn:8443/ReactWeb5/wisdom-components/commits/e10973a31059ceab4b2e01dda56fdd54ade270bc))
{
"name": "@wisdom-components/basicreport",
"version": "1.8.2",
"description": "> TODO: description",
"author": "chenlong <857265978@163.com>",
"homepage": "",
"license": "ISC",
"sideEffects": [
"*.less"
],
"module": "es/index.js",
"main": "es/index.js",
"files": [
"lib",
"es",
"dist"
],
"directories": {
"lib": "lib",
"es": "es",
"dist": "dist",
"test": "__tests__"
},
"publishConfig": {
"registry": "https://g.civnet.cn:4873/"
},
"repository": {
"type": "git",
"url": "https://g.civnet.cn:8443/ReactWeb5/wisdom-components.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@babel/runtime": "^7.17.9",
"@wisdom-components/basictable": "^1.5.25"
}
}
#### 常规使用
<code src="./demos/Basic.tsx">
// 文本/下拉/多选/时间
/**
* @description:
* @params:
* onChange: 需要传入onChange,接收值的变更
*/
import React, { useEffect, useState } from 'react';
import { Input, Select } from 'antd';
import { reportService } from '../../api';
import { returnDefaultValueOrConfigs } from '../utils/utils';
const { Option } = Select;
const { Search } = Input;
const USER_ID = window.globalConfig.userInfo.OID;
const TextSearchComponent = ({ onChange, style, onSearch, placeholder }) => {
return (
<Search
title={placeholder}
style={style}
placeholder={placeholder}
onChange={onChange}
onSearch={onSearch}
/>
);
};
/**
* Data = ['选项1','选项2'...]
*
* @props:
* 正常选项:武汉
* 附带统计数值: 武汉 (20)
*/
const SelectSearchComponent = ({
onChange,
style,
data,
mode,
reportName,
fieldAlias,
configItems,
filterFields,
filterValues,
filterObject,
}) => {
const [value, setValue] = useState('');
const [options, setOptions] = useState([]);
const defaultConfigs = returnDefaultValueOrConfigs(configItems, ['defaultValue']);
const { defaultValue } = defaultConfigs;
const getData = () => {
reportService
.getReportFilterValues({
fieldAlias,
...filterObject,
})
.then((res) => {
if (res.code === 0) {
let _options = res.data
.find((item) => item.fieldAlias === fieldAlias)
.filterValues.filter((item) => item.filterValue);
if (filterValues && filterFields) {
let _filterFields = filterFields.split('|');
let _filterValues = filterValues.split('|');
let _index = _filterFields.findIndex((item) => item === fieldAlias);
if (_index > -1) {
let _needToFilterValues = _filterValues[_index]?.split(',') || [];
_options = _options.filter((item) => _needToFilterValues.includes(item.filterValue));
}
}
setOptions(_options);
}
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
getData();
setValue(defaultValue);
}, []);
return (
<Select
value={value}
style={style}
onChange={(e) => {
onChange(e);
setValue(e);
}}
mode={mode}
defaultValue={mode === 'multiple' && defaultValue ? defaultValue.split(',') : defaultValue}
allowClear
maxTagCount={1}
placeholder={`请选择${fieldAlias}`}
>
{options && options.length
? options.map((item) => (
<Option key={item.filterValue} value={item.filterValue}>
{item.filterValue} <span style={{ color: 'rgba(0,0,0,.65)' }}>({item.count})</span>
</Option>
))
: ''}
</Select>
);
};
const ReturnControlComponent = ({
type,
onChange,
style,
data,
onSearch,
reportName,
fieldAlias,
placeholder,
configItems,
filterValues,
filterFields,
filterObject,
}) => {
let _component = '';
switch (type) {
case '文本':
_component = (
<TextSearchComponent
style={style}
onChange={onChange}
onSearch={onSearch}
placeholder={placeholder}
configItems={configItems}
/>
);
break;
case '下拉':
case '多选':
_component = (
<SelectSearchComponent
mode={type === '多选' ? 'multiple' : ''}
style={style}
onChange={onChange}
reportName={reportName}
fieldAlias={fieldAlias}
configItems={configItems}
filterFields={filterFields}
filterValues={filterValues}
filterObject={filterObject}
/>
);
break;
default:
break;
}
return _component;
};
export default ReturnControlComponent;
/*
** 自定义取色板
** create by ChenLong on 2022/8/23
** 功能路径:src\pages\product\ReportsManage\Components\CustomerColorPicker.js
** 菜单参数列表:*变量名*(变量说明,数据类型,是否必填,取值范围)
**/
import React from 'react';
import style from './CustomerColorPicker.less';
import { tagColors, tagBackgroundColors } from '../../utils/constant';
import classnames from 'classnames';
const CustomerColorPicker = (props) => {
return (
<div className={style.colorPickerWrapper}>
{tagColors.map((color, index) => (
<div
className={classnames(style.colorCard)}
onClick={() => props.clickColorPicker(tagBackgroundColors[index])}
style={{
color: color,
backgroundColor: tagBackgroundColors[index],
}}
>
A
</div>
))}
</div>
);
};
export default CustomerColorPicker;
@import '~antd/es/style/themes/default.less';
.colorPickerWrapper {
display: flex;
gap: 5px;
width: 330px;
//height: 40px;
padding: 10px;
background: #ffffff;
border: 1px solid rgba(200, 200, 200, 0.85);
border-radius: 4px;
.colorCard {
display: flex;
flex: 0 0 30px;
align-items: center;
justify-content: center;
height: 30px;
font-size: 14px;
&:hover {
font-weight: bold;
font-size: 16px;
box-shadow: #1685ff;
cursor: pointer;
}
}
}
/** @tips: 约定:该控件,获取文件路径字符串或上传了文件(即对应编辑时从接口获取的文件链接和新增时获取的对象),都会将符合要求的对象(上传的文件数据或自行拼接的,必须包含status、type、uid等必要字段)转化为字符串 */
/**
* @description: 合并老版的文件、可预览文件、录音、视频
* @params: 接收参数 renderTo: Recording/Video/File
* @return: {Object} 文件类型返回需要另外处理
* @date: 2021/12/1
* @author: ChenLong
* @待解决问题: 为什么list不直接返回url,而要把file对象变成对象字符串?
*/
/** @tips: 上传业务为:1.通过上传接口,上传文件,获取上传的链接;2.将文件链接存储到服务器,获取存储的地址;3.提交地址 */
/**
* @description: 完成 1.文件名校验;2.编辑界面的文件加载及显示的能力;3.不同类型文件的判断
* @date: 2021/12/2
*/
/**
* @description: 合并图片/可预览图片到文件上传中
* @date: 2021/12/6
*/
/**
* @description: 文件类型单独处理。文件类型返回的是文件对象的字符串,使用JSON.parse解析之后,取url值即可,拼成用逗号分割的字符串即可。这部分修改在入口文件内
* @description: 需要判断值是文件对象的字符串,约定:文件类必须会有 status: 'done'
* @date: 2021/12/8
*/
/** @tips: 裁剪功能在前端框架内无法正常工作,暂不提供裁剪功能 */
// 自定义Form Render组件
import React, { useState, useEffect } from 'react';
import { Upload, Button, Modal, message } from 'antd';
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons';
import './fileUpload.less';
import { downloadFunc, filenameVerification } from '../../utils/utils';
import { uploadFileUrl, downloadFileUrl } from '../../../api/service/workflow';
const videoTypeArray = ['.mp4'];
const audioTypeArray = ['.mp4'];
const fileTypeArray = [];
const imageTypeArray = ['.bmp', '.gif', '.jpeg', 'tiff', '.png', '.svg', '.jpg'];
const FileUpload = ({ value, onChange, schema }) => {
const _isRecordingOrVideo = ['Recording', 'Video', 'Image'].includes(schema.renderTo);
const _isVideo = schema.renderTo === 'Video';
const _isAudio = schema.renderTo === 'Recording';
const _isImage = schema.renderTo === 'Image';
const [previewTitle, setPreviewTitle] = useState('');
const [previewVisible, setPreviewVisible] = useState(false);
const [previewUrl, setPreviewUrl] = useState('');
const [showList, setShowList] = useState('');
const file = value || schema.default;
const option = {
name: 'file',
action: `${window.location.origin}${uploadFileUrl}`,
listType: _isRecordingOrVideo ? 'picture-card' : 'picture',
withCredentials: true,
beforeUpload(file, fileList) {
/** @tips: 解决提交文件中存在特殊字符的问题 */
let _continueUpload = true;
let _msg = {
type: 'success',
content: '上传成功!',
};
fileList.forEach((item) => {
let _msgObject = filenameVerification(item);
if (_msgObject.type === 'error') {
_continueUpload = false;
_msg = {
type: 'error',
content: '上传失败!文件名不符合规则!',
};
}
});
_msg.type === 'error' ? message[_msg.type](_msg.content) : '';
return _continueUpload;
},
onChange: ({ file, fileList, event }) => {
// 检验名字,名字不通过不允许显示
if (filenameVerification(file).type === 'error') return false;
// 返回的链接在file.response内;不设置url,预览图表不可点击
if (file.status === 'done' && file.response.code === 0) {
file.url = `${downloadFileUrl}?filePath=${file.response.data}`;
file.sourcePath = file.response.data;
message.success('上传成功!');
} else if (file.status === 'done' && file.response.code !== 0) {
file.status = 'error';
message.error('上传失败!');
}
onChange((fileList && fileList.length && JSON.stringify(fileList)) || '');
setShowList(JSON.stringify(fileList));
},
onPreview(file) {
if (_isRecordingOrVideo) {
setPreviewVisible(true);
setPreviewUrl(file.url);
}
},
previewFile(file) {},
onDownload(file) {
downloadFunc(file.url, file.name, '_self');
},
};
const handleCancel = () => {
setPreviewVisible(false);
setPreviewTitle('');
};
/**
* @description: 返回文件类型限定值
* @params: {Array} typeArray: Video | Recording | File
* @date: 2021/12/2
* @author: ChenLong
*/
const returnFileTypeString = (type) => {
let _obj = {
Video: videoTypeArray,
Recording: audioTypeArray,
File: fileTypeArray,
Image: imageTypeArray,
};
return _obj[type].join(',');
};
useEffect(() => {
let fileList = [];
(file || '').split(',').forEach((item, index) => {
if (item && filenameVerification({ name: item }, true).type !== 'error') {
// @Tips: 直接过滤掉名字中有异常字符的文件
let _obj = {
uid: index + '_' + Math.random(),
value: item,
name: item.split('\\').reverse()[0],
type: schema.renderTo === 'Image' ? 'image' : 'file',
status: 'done',
url: `${downloadFileUrl}?filePath=${item}`,
sourcePath: item,
};
if (schema.renderTo === 'Image') _obj.thumbUrl = `${downloadFileUrl}?filePath=${item}`;
fileList.push(_obj);
}
});
// onChange(fileList.length && JSON.stringify(fileList) || '');
setShowList(JSON.stringify(fileList));
}, []);
useEffect(() => {
if (value) {
let fileList = [];
(file || '').split(',').forEach((item, index) => {
if (item && filenameVerification({ name: item }, true).type !== 'error') {
// @Tips: 直接过滤掉名字中有异常字符的文件
let _obj = {
uid: index + '_' + Math.random(),
value: item,
name: item.split('\\').reverse()[0],
type: schema.renderTo === 'Image' ? 'image' : 'file',
status: 'done',
url: `${downloadFileUrl}?filePath=${item}`,
sourcePath: item,
};
if (schema.renderTo === 'Image') _obj.thumbUrl = `${downloadFileUrl}?filePath=${item}`;
fileList.push(_obj);
}
});
// onChange(fileList.length && JSON.stringify(fileList) || '');
setShowList(JSON.stringify(fileList));
}
}, [value]);
return (
<>
{/** @tips: 裁剪功能无法正常工作,暂不提供 */}
{/* <ImgCrop beforeCrop={(file) => {
let _returnObj = filenameVerification(file);
if (_returnObj.type === 'error') {
message.error(_returnObj.content);
return false;
}
return schema.renderTo === 'Image';
}} rotate locale={'zh-cn'} modalTitle={'编辑图片'} modalOk={'确定'}
modalCancel={'取消'}>*/}
<Upload
disabled={schema.disabled}
{...option}
fileList={showList ? JSON.parse(showList) : []}
multiple
style={{ width: '100%' }}
className={_isRecordingOrVideo ? 'formUploadVideoOrRecording' : 'formUpload'}
showUploadList={{
showPreviewIcon: _isRecordingOrVideo,
showDownloadIcon: true,
downloadIcon: <DownloadOutlined />,
}}
accept={returnFileTypeString(schema.renderTo)}
>
{!_isRecordingOrVideo ? (
<Button disabled={schema.disabled} icon={<UploadOutlined />}>
Upload
</Button>
) : (
'+ Upload'
)}
</Upload>
{/* </ImgCrop>*/}
<Modal
style={{ width: '30%' }}
bodyStyle={{ textAlign: 'center' }}
visible={previewVisible}
title={previewTitle}
footer={null}
onCancel={handleCancel}
>
{_isVideo ? (
<video width={'100%'} height={'100%'} controls autoPlay src={previewUrl} />
) : (
''
)}
{_isAudio ? <audio controls autoPlay src={previewUrl} /> : ''}
{_isImage ? <img width={'100%'} height={'100%'} src={previewUrl} alt="缩略图" /> : ''}
</Modal>
</>
);
};
export default FileUpload;
@import '~antd/es/style/themes/default.less';
@parse-form-prefix-cls: ~'@{ant-prefix}-parse-form';
.@{parse-form-prefix-cls} {
.formUpload {
width: 100%;
.@{ant-prefix}-upload-list-item {
float: left;
width: 49%;
max-width: 300px; // 增加了最大宽度,防止样式失效导致布局错误的问题
margin-right: 1%;
}
}
.@{ant-prefix}-animate {
}
.formUploadVideoOrRecording {
width: 100%;
.@{ant-prefix}-upload-list-item {
float: left;
width: 99%;
margin-right: 1%;
}
}
}
/*
** 报表的编辑、新增表单
** create by ChenLong on 2022/8/10
** 功能路径:src\pages\product\ReportsManage\ReportEditForm.js
** 菜单参数列表:*变量名*(变量说明,数据类型,是否必填,取值范围)
**/
import React, { useEffect, useState, useContext } from 'react';
import {
Form,
Input,
DatePicker,
InputNumber,
Row,
Col,
Button,
message,
Select,
ConfigProvider,
} from 'antd';
import moment from 'moment';
import { submitReportData } from '../api/service/report';
import FileUpload from './Components/fileUpload/fileUpload';
import { reportService } from '../api';
import { isSelect, returnOptions, returnRows, returnCols } from './utils/utils';
import style from './ReportEditForm.less';
const { Option } = Select;
const { TextArea } = Input;
// 类型
const USER_ID = window.globalConfig.userInfo.OID;
const TEXT_ARRAY = ['文本', '标签'];
const TEXTAREA_ARRAY = ['多行文本'];
const DATE_PICKER_ARRAY = ['日期'];
const DATE_TIME_PICKER_ARRAY = ['日期时刻'];
const DATE_TYPE = ['日期', '日期时刻']; // 用来匹配是否需要转为日期对象;
const NUMBER_ARRAY = ['数值', '数值标签'];
const FILE_ARRAY = ['附件'];
// 形态对应组件
// 对应关系
/**
* @description: 函数描述
* @date: 2022/8/10
* @author: ChenLong
* @params: reportDetails 各个字段的配置列表
* data 与reportDetails对应的值
*/
const ReportEditForm = ({ reportDetails, reportData, onCancel, reportName, modalType }) => {
// if (!reportData || Object.keys(reportData).length === 0) return <>未传递表单数据</>;
const [fileAlias, setFileAlias] = useState([]); // 附件需要单独处理提交的数据
const [form] = Form.useForm();
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls();
/* const formItemLayout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};*/
// 数值类的后端会处理再返回,故无序处理精度问题
const handleDate = (reportDetails, data) => {
let _data = { ...data };
reportDetails.forEach((item) => {
if (DATE_TYPE.includes(item.type)) {
_data[item.fieldAlias] = data[item.fieldAlias] ? moment(data[item.fieldAlias]) : moment();
}
});
return _data;
};
const componentMap = (config) => {
if (DATE_TIME_PICKER_ARRAY.includes(config.type)) {
return <DatePicker showTime readonly={config.isReadOnly} />;
} else if (DATE_PICKER_ARRAY.includes(config.type)) {
return <DatePicker disabled={config.isReadOnly} />;
} else if (NUMBER_ARRAY.includes(config.type)) {
return <InputNumber disabled={config.isReadOnly} />;
} else if (FILE_ARRAY.includes(config.type)) {
return <FileUpload schema={{ renderTo: 'File' }} disabled={config.isReadOnly} />;
} else if (TEXTAREA_ARRAY.includes(config.type)) {
let _rows = returnRows(config.configItems);
return <TextArea rows={_rows} disabled={config.isReadOnly} />;
} else {
if (isSelect(config.configItems)) {
let options = returnOptions(config.configItems);
if (options) {
return (
<Select disabled={config.isReadOnly}>
{options.map((item) => (
<Option value={item}>{item}</Option>
))}
</Select>
);
}
}
return <Input disabled={config.isReadOnly} />;
}
};
const submitReportForm = () => {
form.validateFields().then((values) => {
let _data = values;
let final = [];
Object.keys(_data).forEach((key) => {
let value = reportData[key];
let _value = _data[key];
if (moment.isMoment(_data[key])) {
_value = moment(_data[key]).format('YYYY-MM-DD HH:mm:ss');
}
if (value !== _value) {
if (fileAlias.includes(key)) {
_value = _value
? JSON.parse(_value)
.reduce((final, curr) => {
final.push(curr.sourcePath);
return final;
}, [])
.join(',')
: '';
}
let _finalData = {
fieldAlias: key,
fieldValue: _value,
};
if (modalType === '编辑') {
_finalData.key = reportData.Key;
}
final.push(_finalData);
}
});
if (modalType === '新增') {
reportService
.addReportData({
reportName: reportName,
userId: USER_ID,
reportDatas: final,
})
.then((res) => {
if (res.code === 0) {
message.success('保存成功!');
onCancel();
} else if (res.code === -1) {
message.error(res.msg);
} else if (res.code === -2) {
message.error('系统故障,请找管理员查看故障!');
}
});
}
if (modalType === '编辑') {
submitReportData(
{},
{
editDatas: final,
reportName: reportName,
userId: USER_ID,
},
).then((res) => {
if (res.code === 0) {
message.success('保存成功!');
onCancel();
} else if (res.code === -1) {
message.error(res.msg);
} else if (res.code === -2) {
message.error('系统故障,请找管理员查看故障!');
}
});
}
});
};
useEffect(() => {
if (reportDetails && reportDetails.length) {
let _fileAlias = [...fileAlias];
reportDetails.forEach((item) => {
if (item.type === '附件') _fileAlias.push(item.fieldAlias);
});
setFileAlias(_fileAlias);
}
}, [reportDetails]);
useEffect(() => {
if (reportData && Object.keys(reportData).length)
form.setFieldsValue(handleDate(reportDetails, reportData));
}, [reportData]);
return (
<div className={style.reportEditForm} style={{ position: 'relative' }}>
<div>
<Form form={form}>
<Row style={{ overflowY: 'scroll', maxHeight: 'calc(100vh - 300px)' }}>
{reportDetails &&
reportDetails
.filter((config) => {
if (modalType === '新增' && config.isReadOnly) return false;
return config;
})
.map((config) => {
// return <Col span={returnCols(config.configItems) * 8} key={config.fieldAlias}>
return (
<div
style={{ width: `${returnCols(config.configItems) * 33.3}%` }}
key={config.fieldAlias}
>
<Form.Item
label={config.fieldAlias}
name={config.fieldAlias}
rules={[
{
required: config.isRequired,
message: `${config.fieldAlias}必填`,
},
]}
>
{componentMap(config)}
</Form.Item>
</div>
);
})}
</Row>
<Row>
<Col span={24} style={{ textAlign: 'right' }}>
{/*<Form.Item style={{textAlign:'right'}}>*/}
<Button
style={{ position: 'sticky', bottom: 0 }}
type={'primary'}
onClick={submitReportForm}
>
提交
</Button>
{/*</Form.Item>*/}
</Col>
</Row>
</Form>
</div>
</div>
);
};
export default ReportEditForm;
@import '~antd/es/style/themes/default.less';
.reportEditForm {
:global {
.@{ant-prefix}-form-item-label {
width: 120px !important;
}
input[disabled] {
background-color: rgb(250, 250, 250);
cursor: default;
}
// 多行文本框
.@{ant-prefix}-input[disabled] {
color: rgba(0, 0, 0, 0.85);
background-color: rgb(250, 250, 250);
border-color: #d9d9d9;
box-shadow: none;
cursor: default;
opacity: 1;
}
// 时间选择器的disabled样式
.@{ant-prefix}-picker-disabled {
color: rgba(0, 0, 0, 0.85);
background-color: rgb(250, 250, 250);
cursor: default;
& + span {
cursor: default;
}
}
// radio的disabled样式
.@{ant-prefix}-radio-disabled {
cursor: default;
& + span {
cursor: default;
}
.@{ant-prefix}-radio-inner {
cursor: default;
}
}
// 下拉选已选的disabled样式
.@{ant-prefix}-cascader-picker-disabled {
color: rgba(0, 0, 0, 0.85);
background-color: rgb(250, 250, 250);
cursor: default;
}
// 按钮的disabled样式
.@{ant-prefix}-btn[disabled] {
text-shadow: none;
background: #ffffff;
border-color: #d9d9d9;
box-shadow: none;
cursor: default;
&:hover,
&:focus,
&:active {
color: rgba(0, 0, 0, 0.25);
text-shadow: none;
background: #ffffff;
border-color: #d9d9d9;
box-shadow: none;
}
}
// 多选下的tag的disabled样式
.@{ant-prefix}-select-disabled.@{ant-prefix}-select-multiple {
.@{ant-prefix}-select-selection-item {
color: #bfbfbf;
border-color: #d9d9d9;
cursor: default;
}
}
.@{ant-prefix}-select-disabled.@{ant-prefix}-select:not(.@{ant-prefix}-select-customize-input) {
.@{ant-prefix}-select-selector input {
cursor: default;
}
}
// 时间选择框
.@{ant-prefix}-picker-input > input[disabled] {
color: rgba(0, 0, 0, 0.85);
}
}
}
import React, { useEffect, useState } from 'react';
import style from './ReportsDataSourceSetting.less';
import {
Row,
Space,
Form,
Button,
Card,
Input,
Avatar,
Modal,
Select,
InputNumber,
Spin,
message,
} from 'antd';
import { sourceIconMap } from './utils/constant';
import { EditOutlined, DeleteOutlined, EyeTwoTone, EyeInvisibleOutlined } from '@ant-design/icons';
import { reportService } from '../api';
import classnames from 'classnames';
const { Search, TextArea } = Input;
const { Option } = Select;
const { Meta } = Card;
const ReportsDataSourceSetting = () => {
const [sourceList, setSourceList] = useState([]);
const [modalVisible, setModalVisible] = useState(false);
const [currentData, setCurrentData] = useState(null);
const [type, setType] = useState('');
const [dataSourceType, setDataSourceType] = useState('sqlserver');
const [searchStr, setSearchStr] = useState(null);
const [form] = Form.useForm();
const [dbList, setDBList] = useState([]);
const [listLoading, setListLoading] = useState(false);
const [testLoading, setTestLoading] = useState(false);
const [submitLoading, setSubmitLoading] = useState(false);
const initValues = {
db_sourcesname: '',
db_type: 'sqlserver',
db_url: '',
db_username: '',
db_password: '',
db_port: 1433,
db_name: '',
webapiRType: '',
webapiRParam: '',
};
const getSourceList = () => {
setListLoading(true);
let _temp = searchStr ? { sourceName: searchStr } : {};
reportService
.getDataSources(_temp)
.then((res) => {
if (res.code === 0) {
setSourceList(res.data);
} else {
setSourceList([]);
}
setListLoading(false);
})
.catch((err) => {
setListLoading(false);
});
};
const cancelVisible = () => {
setCurrentData(null);
setModalVisible(false);
form.resetFields();
setDataSourceType('sqlserver');
};
const checkCard = (data) => {
setModalVisible(true);
setCurrentData(data);
setType('detail');
};
const editCard = (data) => {
setModalVisible(true);
setCurrentData(data);
setType('edit');
form.setFieldsValue(data);
setDataSourceType(data.db_type);
};
const addCard = () => {
setModalVisible(true);
setCurrentData(null);
form.resetFields();
};
const deleteCard = (source) => {
Modal.confirm({
title: `当前有${source.db_name}正在使用该数据源,删除后会导致该报表无法使用。请确认,是否删除【${source.db_sourcesname}】数据源配置?`,
onOk: () => {
setSubmitLoading(true);
reportService
.deleteDbConnection({ sourcesId: source.id })
.then((res) => {
if (res.code === 0) {
message.success('删除成功!');
getSourceList();
} else {
message.error(`删除失败!${res.msg}`);
}
setSubmitLoading(false);
})
.catch((err) => {
console.log(err);
setSubmitLoading(false);
});
},
});
};
const selectType = (e) => {
setDataSourceType(e);
if (e === 'mysql') form.setFieldsValue({ db_port: 3306 });
if (e === 'sqlserver') form.setFieldsValue({ db_port: 1433 });
};
const onSearch = (e) => {
setSearchStr(e);
};
const getLoginInfo = () => {
let list = form.getFieldsValue([
'db_url',
'db_username',
'db_password',
'db_port',
'db_sourcesname',
'db_type',
]);
let _final = Object.values(list).reduce((final, curr) => {
if (curr === undefined) final = false;
return final;
}, true);
return _final ? list : false;
};
const getDBList = () => {
let info = getLoginInfo();
if (info) {
let { db_url, db_username, db_password, db_port, db_sourcesname, db_type } = info;
reportService
.getDbSources({
db_url,
db_username,
db_password,
db_port,
db_sourcesname,
db_type,
})
.then((res) => {
if (res.code === 0) {
setDBList(res.data);
} else {
setDBList([]);
message.error(res.msg);
}
});
} else {
message.error(`请完善名称、IP、端口、账号、密码`);
}
};
const test = () => {
form.validateFields().then((value) => {
setTestLoading(true);
let _value = !currentData ? value : { ...value, id: currentData.id };
reportService
.testConnection(_value)
.then((res) => {
if (res.code === 0) {
message.success('测试通过!');
} else {
message.error(res.msg);
}
setTestLoading(false);
})
.catch((err) => {
setTestLoading(false);
});
});
};
const submit = () => {
form.validateFields().then((value) => {
setSubmitLoading(true);
let _value = !currentData ? value : { ...value, id: currentData.id };
reportService
.addDbConnection(_value)
.then((res) => {
if (res.code === 0) {
message.success('保存成功!');
setModalVisible(false);
getSourceList();
} else {
message.error(res.msg);
}
setSubmitLoading(false);
})
.catch((err) => {
setSubmitLoading(false);
});
});
};
useEffect(() => {
getSourceList();
}, []);
useEffect(() => {
if (searchStr !== null) getSourceList();
}, [searchStr]);
return (
<div className={style.reportsDataSourceSetting}>
<div className={style.contentWrapper}>
<Row className={style.controlRow}>
<Space>
{/*控制栏左侧部分*/}
<Form.Item>
<Search onSearch={onSearch} />
</Form.Item>
<Form.Item>
<Button onClick={addCard} type={'primary'}>
添加
</Button>
</Form.Item>
</Space>
<div>{/* 控制栏右侧部分 */}</div>
</Row>
<div className={style.content}>
<Spin
style={{
width: '100%',
height: '100%',
position: 'absolute',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
background: 'rgba(255,255,255,0.2)',
}}
spinning={listLoading}
/>
{sourceList
.filter((item) => {
return !searchStr ? true : item.db_sourcesname.includes(searchStr);
})
.map((item) => {
return (
<Card
className={style.card}
actions={[
// <SettingOutlined onClick={checkCard} key='setting' />,
<EditOutlined onClick={() => editCard(item)} key="edit" />,
<DeleteOutlined onClick={() => deleteCard(item)} key="delete" />,
]}
>
<Meta
avatar={<Avatar src={sourceIconMap[item.db_type]} />}
title={item.db_sourcesname}
/>
<div className={style.cardInfo}>
<p className={style.cardInfoItem}>
类型:{' '}
<span className={classnames(style.item, style.blue)}>
{item.db_type || '无'}
</span>
</p>
{item.db_type === 'webapi' ? (
<p className={style.cardInfoItem}>
请求类型:{' '}
<span className={style.item}>
{item.webapiRType === 'post' ? 'POST' : 'GET'}
</span>
</p>
) : (
<p className={style.cardInfoItem}>
数据库: <span className={style.item}>{item.db_name || '无'}</span>
</p>
)}
<p className={style.cardInfoItem}>
请求地址:{' '}
<span title={item.db_url} className={classnames(style.item, style.ellipsis)}>
{item.db_url}
</span>
</p>
</div>
</Card>
);
})}
</div>
</div>
<Modal
title={currentData?.id ? '编辑' : '新增'}
visible={modalVisible}
onCancel={cancelVisible}
footer={null}
width={600}
destroyOnClose
>
<Form
autocomplete={'off'}
form={form}
initialValues={initValues}
labelCol={{
span: 6,
}}
wrapperCol={{
span: 16,
}}
>
<Form.Item
label={'名称'}
name={'db_sourcesname'}
rules={[
{
required: true,
message: '名称必填',
},
]}
>
<Input placeholder={'请输入数据源名称'} />
</Form.Item>
<Form.Item
label={'类型'}
name={'db_type'}
rules={[
{
required: true,
message: '名称必填',
},
]}
>
<Select onChange={selectType} disabled={!!currentData?.id}>
<Option value="mysql">MySQL</Option>
<Option value="sqlserver">SQLServer</Option>
<Option value="webapi">WebAPI</Option>
</Select>
</Form.Item>
<Form.Item
label={['mysql', 'sqlserver'].includes(dataSourceType) ? 'IP' : '请求地址'}
name={'db_url'}
rules={[
{
required: true,
message: '名称必填',
},
]}
>
{['mysql', 'sqlserver'].includes(dataSourceType) ? (
<Input placeholder={'请输入要链接的服务器IP'} />
) : (
<TextArea placeholder={'请输入API链接'} rows={3} />
)}
</Form.Item>
{['mysql', 'sqlserver'].includes(dataSourceType) ? (
<>
<Form.Item
label={'账号'}
name={'db_username'}
rules={[
{
required: true,
message: '名称必填',
},
]}
>
<Input placeholder={'请输入链接账号'} />
</Form.Item>
<Form.Item
label={'密码'}
name={'db_password'}
rules={[
{
required: true,
message: '名称必填',
},
]}
>
<Input.Password
placeholder="请输入密码"
iconRender={(visible) => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)}
/>
</Form.Item>
<Form.Item
label={'端口'}
name={'db_port'}
rules={[
{
required: true,
message: '名称必填',
},
]}
>
<InputNumber min={0} max={65535} step={1} />
</Form.Item>
<Form.Item
label={'数据库'}
name={'db_name'}
rules={[
{
required: true,
message: '请选择要连接的数据库',
},
]}
>
<Select
onFocus={getDBList}
filterOption={(input, option) => option.children.includes(input)}
showSearch
>
{dbList.map((item) => {
return <Option value={item}>{item}</Option>;
})}
</Select>
</Form.Item>
</>
) : (
''
)}
{
// webapiRType
// webapiRParam
['webapi'].includes(dataSourceType) ? (
<>
<Form.Item
label={'请求方式'}
name={'webapiRType'}
rules={[
{
required: true,
message: '请选择请求方式',
},
]}
>
<Select>
<Option value={'post'}>POST</Option>
<Option value={'get'}>GET</Option>
</Select>
</Form.Item>
<Form.Item label={'请求头参数'} name={'requestHeader'}>
<TextArea rows={3} placeholder={'请配置请求头参数'} />
</Form.Item>
<Form.Item label={'请求参数'} name={'webapiRParam'}>
<TextArea
rows={3}
placeholder={'请填写请求参数,使用&拼接,例:id=0&type=二供泵房'}
/>
</Form.Item>
<Form.Item
name={'webapiDataNode'}
label={'数据路径'}
rules={[{ required: true, message: '数据路径必填!' }]}
>
<Input placeholder={'仅获取该层级数据'} />
</Form.Item>
</>
) : (
''
)
}
<Form.Item label={' '} colon={false}>
<Space>
<Button onClick={test} loading={testLoading}>
测试链接
</Button>
<Button onClick={cancelVisible}>取消</Button>
<Button onClick={submit} type={'primary'} loading={submitLoading}>
确定
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default ReportsDataSourceSetting;
@import '~antd/es/style/themes/default.less';
.reportsDataSourceSetting {
height: 100%;
overflow: hidden;
.contentWrapper {
display: flex;
flex-direction: column;
height: calc(100% - 16px);
margin: 8px;
@media screen and (min-width: 1680px) {
.content {
grid-template-columns: repeat(5, 1fr);
}
}
@media screen and (max-width: 1680px) {
.content {
grid-template-columns: repeat(4, 1fr);
}
}
@media screen and (max-width: 1320px) {
.content {
grid-template-columns: repeat(3, 1fr);
}
}
@media screen and (max-width: 960px) {
.content {
grid-template-columns: repeat(2, 1fr);
}
}
.controlRow {
display: flex;
flex-direction: column;
margin-bottom: 8px;
padding: 8px;
background: #ffffff;
:global {
.@{ant-prefix}-form-item {
margin-bottom: 0;
}
}
}
.content {
display: grid;
grid-gap: 10px;
padding-bottom: 8px;
overflow: scroll;
.card {
height: 198px;
.cardInfo {
padding-left: 48px;
.cardInfoItem {
margin-bottom: 4px;
color: rgb(100, 100, 100, 0.65);
.item {
color: rgba(58, 58, 58, 0.55);
font-weight: bold;
}
.blue {
color: rgba(23, 130, 252, 0.65);
}
.ellipsis {
display: inline-block;
max-width: 120px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
vertical-align: top;
}
}
}
}
}
}
}
/**
* * 轻量化报表通用配置页面 * create by ChenLong on 2022/6/22 *
* 功能路径:src\pages\product\ReportsManage\ReportsManage.js * 菜单参数列表:*变量名*(变量说明,数据类型,是否必填,取值范围)
*
* @changelog:
* editComponentVisible && detailsComponentVisible 共同作用组件的显示
*/
/**
* @description: 功能描述:参考台账概念,重新定义
* @tips: 1. 如果需要对字段进行处理,增加功能之类的。需要提前确定返回值的类型.
* 2. 如果要用customerState来控制页面按钮等,需要按照给定的权限值进行配置
* @params: <ReportManage
* params={{reportName,pageSize,filterFields,filterValues,state,customerState}}>
*
*
*
* reportName: 报表名称;
* pageSize: 按需配置,默认加载100;
*
*
*
* ***************filterFields/filterValues多用于以组件的形式嵌入,将需要过滤的条件传入********************
*
*
*
* filterFields: 需要默认加载的过滤条件的field,用|分割,与filterValues一一对应;
* filterValues:
*
*
* 需要默认加载的过滤条件的value,用|分割,与filterFields一一对应;当某个字段的filterValues为多个时,控件必须配置成多选才可生效;
*
*
*
* *************************************************************************************************
*
*
*
* state: delete|edit|scan 各种权限;
* customerState:
*
*
* ['filters','sortBtn','exportBtn','editBtn','deleteBtn','pagination'];
*
* sortFields:
*
* '排序字段1,排序字段2,排序字段3'
*
*
*
* ---------------------------------------权限过滤-----------------------------------------
*
*
*
* permissionType: 部门|站点|用户
* permissionField: 【字段名】
*
*
*
* ---------------------------------------------------------------------------------------
* @config:
* 【数值】 [prefix]_d%|0.00|_d%[suffix]|金额 = 前缀|精度|后缀|金额类的数据(千分位),可分别设置。
* 【标签】 split=,
*
* 分隔符。
*
* 【功能】 功能配置框内,配置需要跳转功能所需参数,type、url是必须功能,需要type判断类型,需要通过url去解析加载组件。
* @type:
* 【文本】普通文本
* 【数值】数值类型的文本
* 【标签】文本渲染成标签,具有不同颜色;
* 【功能】“功能”会在当前页内去展示,会卸载掉列表页,加载功能组件。配置 type +
*
*
* url + 自定义字段 的配置项,自行解析加载即可;
* -------------------- 待需求提出后开发 -----------------
*
*
*
* 【链接】内链外链,点击可跳转;配置规则:配置链接即可;
* 【弹窗】modal弹窗弹出,弹窗内的具体业务自行配置;配置规则:[function_name];[...params];
*
*
*
* ------------------------------------------------------
* 【附件】
* @table:
* 表头:表头需要支持多级表头、合并;
* 列:列支持设置筛选;
* 固定行、固定列:可根据配置生成固定行、列;
* @control:
* 固定筛选:拥有固定筛选框,根据配置显示可搜索字段;
*
*
*
* 可配置筛选框:根据字段配置,将字段设置成筛选条件,枚举出该字段所有值,提供用户进行选择,然后进行筛选;筛选框具体形态可根据配置字段来渲染;
* 导出功能:各类导出功能按钮;
*
*
*
* 时间筛选框:单表唯一;需要变更,支持多时间参数的筛选
* @state: 参考台账权限 delete 全部权限
* edit 除删除外的权限
* scan 查看权限
*/
import React, { useState, useEffect, useRef } from 'react';
import {
Row,
Button,
Tag,
message,
Form,
Space,
Modal,
Select,
Table,
Dropdown,
Menu,
Spin,
Popconfirm,
Tooltip,
} from 'antd';
import {
SortAscendingOutlined,
MinusCircleOutlined,
ExportOutlined,
FormOutlined,
PlusOutlined,
DeleteOutlined,
QuestionCircleOutlined,
HeartTwoTone,
HeartOutlined,
DownOutlined,
} from '@ant-design/icons';
import BasicTable from '@wisdom-components/basictable';
import ReturnControlComponent from './Components/Control';
import { reportService } from '../api/index';
import style from './ReportsManage.less';
import { exportAccountData } from '../api/service/report';
import extraComponents from './extra/extraComponents';
import DatePickerGroup from '../components/DatePickerGroup';
import moment from 'moment';
import DetailsComponent from './extra/detailsComponent';
import {
handleNumber,
handleTag,
handleText,
handleLink,
handleWidget,
handleModal,
handleDateString,
handleDateTimeString,
handlePageSize,
handleSortFields,
handleNumberTag,
} from './utils/handlers';
import { hasMoney, isArray, isNumber, isString, returnHandledNumber } from './utils/utils';
import { connect } from 'react-redux';
import ReportEditForm from './ReportEditForm';
import { exportJPG } from '../api/service/report';
const ControlsType = ['下拉', '多选', '日期'];
const fieldSplitStr = '-'; // fieldGroup用来分割
const { Option } = Select;
const dateFormat = 'YYYY-MM-DD'; // 日期格式化
const initDateModel = 'all';
let timer = null;
const PERMISSION = {
delete: [
'addBtn',
'filters',
'pagination',
// 操作条按钮
'sortBtn',
'exportBtn',
// 操作列
'editBtn',
'deleteBtn',
],
edit: [
'addBtn',
'filters',
'pagination',
'sortBtn',
'exportBtn',
// 操作列
'editBtn',
],
scan: ['filters', 'pagination', 'sortBtn'],
};
const USER_ID = window.globalConfig.userInfo.OID;
const MODEL = ['all', 'year', 'quarter', 'month', 'week', 'day'];
const ReportsManage = (props) => {
const {
reportName,
pageSize,
filterFields,
filterValues,
state,
customState,
sortFields,
permissionType,
permissionField,
} = props.params;
const handleCustomerState = (customState) => {
if (isArray(customState)) {
return customState;
} else if (isString(customState)) {
return customState.split(',');
}
};
const permission = customState ? handleCustomerState(customState) : PERMISSION[state || 'delete'];
const tableWrapperRef = useRef();
const controlRef = useRef();
if (!reportName)
return (
<div className={style.lackParams}>未配置reportName,请完善配置并重新登陆后查看页面!</div>
);
const [isInit, setIsInit] = useState(true);
const [firstToGetData, setFirstToGetData] = useState(false);
const [tableStruct, setTableStruct] = useState([]); // 临时使用,看后续是否需要保留
const [columns, setColumns] = useState([]); // 表头设置
const [tableData, setTableData] = useState([]); // 表数据
const [reportConfigs, setReportConfigs] = useState([]); // 表设置
const [pagination, setPagination] = useState({
current: 1,
total: 0,
pageSize: handlePageSize(pageSize) || 100,
pageSizeOptions: [...new Set([20, 50, 100].concat(handlePageSize(pageSize)))]
.filter((item) => Number(item))
.sort((a, b) => Number(a) - Number(b)),
showQuickJumper: true,
showSizeChanger: true,
onShowSizeChange: (current, size) => {
let _pagination = { ...pagination };
_pagination.current = current;
_pagination.pageSize = size;
setPagination(_pagination);
getData(_pagination);
},
onChange: (current, size) => {
let _pagination = { ...pagination };
_pagination.current = current;
_pagination.pageSize = size;
setPagination(_pagination);
getData(_pagination);
},
});
const [controls, setControls] = useState([]); // 用来存储操作控件
const [searchContent, setSearchContent] = useState(''); // 搜索框内的值
const [searchPlaceholder, setSearchPlaceholder] = useState([]); // 搜索框的placeholder
const [filterObject, setFilterObject] = useState({}); // 存控制条中,选了筛选条件的值
const [modalVisible, setModalVisible] = useState(false);
const [allSortFields, setAllSortFields] = useState([]); // 设置所有列表
const [selectedSortFields, setSelectedSortFields] = useState([
{
label: '主要排序',
value: '',
sort: 'asc',
},
]);
const [summaryArray, setSummaryArray] = useState([]); // 总结栏,包括小计、总计
const [tableY, setTableY] = useState(500);
const [tableX, setTableX] = useState(1820);
const [listHeight, setListHeight] = useState(0);
const [tableHeaderLevel, setTableHeaderLevel] = useState(null);
const [exportLoading, setExportLoading] = useState(false);
const [extraModal, setExtraModal] = useState(false);
const [hasTotal, setHasTotal] = useState(false);
const [hasSinglePage, setHasSinglePage] = useState(false);
const [tableLoading, setTableLoading] = useState(false);
const [mergeObject, setMergeObject] = useState({});
// const
const [timeFrom, setTimeFrom] = useState(moment().startOf(initDateModel).format(dateFormat));
const [timeTo, setTimeTo] = useState(moment().endOf(initDateModel).format(dateFormat));
const [extra, setExtra] = useState(<></>);
const [sortModalVisible, setSortModalVisible] = useState(false);
const [currentReportId, setCurrentReportId] = useState(null);
const [hasDatePicker, setHasDatePicker] = useState('');
const [defaultDateConfig, setDefaultDateConfig] = useState({
defaultModel: 'year',
defaultDate: null,
});
const [dateModel, setDateMode] = useState('all');
const [detailsComponentVisible, setDetailsComponentVisible] = useState(false); // 是否显示详情组件
const [modalType, setModalType] = useState('');
const [currentData, setCurrentData] = useState({}); // 设置当前编辑数据
const [sorterObject, setSorterObject] = useState({});
const [detailConfig, setDetailConfig] = useState({ url: '', type: '', params: {} });
const [controlsHeight, setControlsHeight] = useState(44);
const [getNewData, setGetNewData] = useState(false);
const [isLocalDataSource, setIsLocalDataSource] = useState(0); // 如果是本地数据源,那么值为0;反之,则是外部数据源
const menu = () => {
const _item = [
{
label: (
<Button
size="middle"
loading={exportLoading}
type="text"
onClick={() => exportModule('pdf', 'pdf')}
icon={<ExportOutlined />}
>
导出pdf
</Button>
),
key: 'exportPdf',
},
{
label: (
<Button
size="middle"
loading={exportLoading}
type="text"
onClick={() => exportModule('excel', 'xls')}
icon={<ExportOutlined />}
>
导出Excel
</Button>
),
key: 'exportExcel',
},
{
label: (
<Button
size="middle"
loading={exportLoading}
type="text"
onClick={() => exportImage()}
icon={<ExportOutlined />}
>
导出JPG
</Button>
),
key: 'excelPdf',
},
];
return <Menu items={_item} />;
};
const exportModule = (type, extension) => {
setExportLoading(true);
let _data = addFilterAndSearchParams({
reportName,
pageIndex: 0,
pageSize: 0,
userId: USER_ID,
});
exportAccountData({ responseType: 'blob' }, { exportType: type }, _data)
.then((res) => {
if (res && res.code === -1) return message.error(res.msg);
const url = window.URL.createObjectURL(
new Blob([res], { type: 'application/octet-stream;charset=UTF-8' }),
);
const a = document.createElement('a');
a.href = url;
a.target = '_blank';
a.download =
`${reportName}${moment().format('YYYY-MM-DD-HH-mm-ss').replaceAll('-', '')}.` + extension;
a.click();
a.remove();
setExportLoading(false);
})
.catch((err) => {
setExportLoading(false);
});
};
const exportImage = () => {
let _data = addFilterAndSearchParams({
reportName,
pageIndex: 0,
pageSize: 0,
userId: USER_ID,
});
exportJPG({ responseType: 'blob' }, _data).then((res) => {
if (res && res.code === -1) return message.error(res.msg);
const url = window.URL.createObjectURL(
new Blob([res], { type: 'application/zip;charset=UTF-8' }),
);
const a = document.createElement('a');
a.href = url;
a.target = '_blank';
a.download = `${reportName}${moment().format('YYYY-MM-DD-HH-mm-ss').replaceAll('-', '')}.zip`;
a.click();
a.remove();
setExportLoading(false);
});
};
const searchData = (e) => {
getData(pagination);
};
const controlSelectChange = (fieldAlias, e) => {
let _filterArray = { ...filterObject };
_filterArray[fieldAlias] = e;
setFilterObject(_filterArray);
};
const searchInputChange = (e) => {
setSearchContent(e.target.value);
};
const setConfig = (config) => {
getControlsBarConfig(config);
getTableLevel(config);
};
const addFilterAndSearchParams = (data) => {
let _data = { ...data };
// 搜索框是否有值
if (searchContent) _data.content = searchContent;
// filterObject是存起来的控制栏的过滤条件
let _filters = Object.keys(filterObject)
.filter((key) => {
let _value = filterObject[key];
return (isString(_value) && _value) || (isArray(_value) && _value.length);
})
.map((key) => {
let _value = filterObject[key];
if (isString(_value) && _value)
return {
fieldAlias: key,
fieldValue: _value,
};
if (isArray(_value) && _value.length)
return {
fieldAlias: key,
fieldValue: _value.join(','),
};
return false;
});
// 加上时间过滤参数
if (dateModel !== 'all' && hasDatePicker && timeFrom && timeTo) {
_filters.push({
fieldAlias: hasDatePicker,
fieldValue: `${timeFrom} 00:00:00,${timeTo} 23:59:59`,
});
}
// 合并手动传入的filters;当配置为筛选框时,筛选框选择值后,会优先取筛选框值
if (filterFields && !_filters.find((item) => item.fieldAlias === filterFields)) {
let _customerFilterArray = filterFields.split('|');
let _customerValueArray = filterValues.split('|');
_customerFilterArray.forEach((item, index) => {
_filters.push({
fieldAlias: item,
fieldValue: _customerValueArray[index] || '',
});
});
}
// 表格上的自定义排序的按钮
if (sorterObject && sorterObject.order) {
_data.sortFields = `${sorterObject.columnKey} ${
sorterObject.order === 'ascend' ? 'asc' : 'desc'
}`;
}
// 增加权限过滤的参数
if (permissionType && permissionField) {
_data.filterType = permissionType;
_data.filterField = permissionField;
}
// 并入 _data
if (_filters.length) _data.filters = _filters;
return _data;
};
// 排序字符串处理成数组
const handleSortString = (sortString) => {
// selectedSortFields
let _sortStringArray = sortString.split(',').map((item, index) => {
let _item = item.split(' ');
if (index === 0) {
return {
label: '主要排序',
value: _item[0].replace(/\[|\]/g, ''),
sort: _item[1],
};
} else {
return {
label: '次要排序',
value: _item[0].replace(/\[|\]/g, ''),
sort: _item[1],
};
}
});
setSelectedSortFields(_sortStringArray);
};
/** @description: 根据是否向下合并处理数据,返回合并的key的数组 */
const returnMergeArray = (config) => {
return config.filter((item) => item.isMerge).map((item) => item.fieldAlias);
};
/** @description: 根据配置和数据,计算出该合并的字段和每一行是否合并 */
const handleDataToGetRowSpanArray = (config, data) => {
let _arr = returnMergeArray(config);
let _merge = {};
// _merge:{爱好:[[0,3],[3,5]]}
_arr.forEach((key) => {
_merge[key] = {};
let _currentIndex = 0;
data.forEach((item, index) => {
if (index > 0) {
if (item[key] === data[index - 1][key]) {
_merge[key][_currentIndex] += 1;
_merge[key][index] = 0;
} else {
_currentIndex = index;
_merge[key][index] = 1;
}
} else {
_merge[key][0] = 1;
}
});
});
return _merge;
};
const handleData = () => {};
const getData = (pagination) => {
setTableLoading(true);
const { pageSize, current } = pagination;
// 搜索条件附加到params
let _data = addFilterAndSearchParams({
reportName: reportName,
pageIndex: current,
pageSize: pageSize,
userId: USER_ID,
});
// sortFields
reportService
.getReportInfo(_data)
.then((res) => {
if (res.code === 0) {
let _reportDetails = res.data?.reportDetails;
let _statisticalValues = res.data?.statisticalValues;
// webAPIData 存在值时使用webAPIData,否则使用原始数据
let _tableData = res.data.webAPIData?.webAPIData
? JSON.parse(res.data.webAPIData.webAPIData)
: res.data?.data?.list.map((item) => {
if (Object.prototype.toString.call(item) === '[object String]') {
return JSON.parse(item);
}
return item;
}) || [];
let _sortString = res.data.sortString;
if (isInit) {
setIsInit(false);
}
setIsLocalDataSource(res.data.sourcesId);
getTableSummaryConfig(_reportDetails, _statisticalValues);
getTableHeaderConfig(_reportDetails, _tableData);
let _pagination = { ...pagination };
_pagination.total = res.data.webAPIData?.webAPIData
? res.data.webAPIData.totalCount
: res.data.data?.totalCount;
setPagination(_pagination);
setTableData(_tableData);
// 处理排序字段
handleSortString(_sortString);
if (_tableData) {
setSortModalVisible(true);
setCurrentReportId(_reportDetails[0]?.reportId);
} else {
setSortModalVisible(false);
setCurrentReportId(null);
}
} else {
setSortModalVisible(false);
setCurrentReportId(null);
message.error('未能查询到报表数据!');
let _pagination = { ...pagination };
_pagination.total = 0;
_pagination.current = 1;
setPagination(_pagination);
setTableData([]);
}
if (isInit) setIsInit(false);
setTableLoading(false);
})
.catch((err) => {
console.log(err);
setTableLoading(false);
});
};
const getConfigs = () => {
reportService.getReportDetails({ reportName }).then((res) => {
if (res.code === 0) {
setReportConfigs(res.data);
setConfig(res.data);
setColumns(returnColumn(res.data));
setTableStruct(res.data);
setFirstToGetData(true);
} else {
message.error(res.msg);
}
});
};
/** @description: 在配置项中,isFilter: true 用来渲染控制框;filterRule: 下拉/文本/多选 */
const getControlsBarConfig = (config) => {
let _data = config.filter((item) => item.isFilter);
let _searchPlaceholder = [];
_data
.filter((item) => item.filterRule === '文本')
.forEach((item) => _searchPlaceholder.push(item.fieldAlias));
setSearchPlaceholder(_searchPlaceholder);
let _controls = _data.filter((item) => ControlsType.includes(item.filterRule));
setControls(_controls);
handleControls(_controls); // 处理控制条,设定默认值
handleDate(_controls); // 处理日期, 设定默认值
};
const getTableHeaderConfig = (config, data) => {
// setTableStruct(config);
// setColumns(returnColumn(config, data));
setAllSortFields(returnSortFields(config));
};
const getTableSummaryConfig = (config, summary) => {
if (summary.length === 0) {
setSummaryArray([]);
return false;
}
let _summaryConfig = {};
let _configLength = config.length; // 需要判断
// 合并列
let _colSpan = -1;
let _index = 0;
config
.filter((item) => item.isShow)
.forEach((item, index) => {
if (item.isStatistics) {
_index += 1;
_colSpan = -1;
_summaryConfig[item.fieldAlias] = {
fieldName: item.fieldAlias,
index,
alignType: item.alignType,
type: item.statisticsRule,
isMoney: item.type === '数值' && !!hasMoney(item.configItems),
configItems: item.configItems,
};
} else {
let _name = `空值行${_index}`;
if (_colSpan === -1) {
_colSpan = 1;
} else {
_colSpan += 1;
}
_summaryConfig[_name] = {
fieldName: _name,
colSpan: _colSpan,
};
}
});
summary.forEach((item) => {
switch (item.totalType) {
case '全部':
setHasTotal(true);
break;
case '单页':
setHasSinglePage(true);
break;
default:
break;
}
if (_summaryConfig[item.fieldName]) {
_summaryConfig[item.fieldName][item.totalType] = item.fieldValue;
}
});
// 增加操作列,总结栏最后一个单元格需要延伸一格
let _sumArr = Object.values(_summaryConfig);
if (_sumArr && _sumArr.length)
_sumArr = _sumArr.map((item, index) => {
let _item = { ...item };
if (index === _sumArr.length - 1) _item.colSpan += 1;
return _item;
});
setSummaryArray(_sumArr);
};
const changeDate = ({ dateFrom, dateTo }, mode) => {
setTimeFrom(dateFrom);
setTimeTo(dateTo);
setDateMode(mode);
};
const mapHandleType = (type) => {
const _map = {
文本: handleText,
多行文本: handleText,
数值: handleNumber,
数值标签: handleNumberTag,
标签: handleTag,
链接: handleLink,
功能: handleWidget,
弹窗: handleModal,
日期: handleDateString,
日期时间: handleDateTimeString,
};
return _map[type] || _map['文本'];
};
const returnSortFields = (data) => {
return data.map((item) => item.fieldAlias);
};
// 处理表格数据,生成表头
const returnColumn = (config, data) => {
//0. 常规数据
//1. 合并表头;
//2. 四类形态的渲染处理;
//3. 多列表头排序;剔除掉原有图标,需要自己实现排序的按钮
let _config = [...config].filter((item) => item.isShow);
function dataStruct(keyArray, dataIndex, obj, dataObj) {
if (dataIndex < keyArray.length - 1) {
if (!obj[keyArray[dataIndex]]) {
obj[keyArray[dataIndex]] = {};
}
let _dataIndex = dataIndex + 1;
dataStruct(keyArray, _dataIndex, obj[keyArray[dataIndex]], dataObj);
} else if (dataIndex === keyArray.length - 1) {
obj[keyArray[dataIndex]] = dataObj;
}
}
let _tempObj = {};
let _fieldAliasArray = data ? handleDataToGetRowSpanArray(_config, data) : false; // 需要向下合并的字段
_config.forEach((item) => {
let _item = {
title: item.fieldAlias,
dataIndex: item.fieldAlias,
key: item.fieldAlias,
ellipsis: true,
onCell: (record, rowIndex) => {
// console.log('Record: ', record); // record是这条记录,index是rowIndex
// 1. 如果该字段是需要向下合并的,则进入判断
let _obj = {};
if (_fieldAliasArray && _fieldAliasArray[item.fieldAlias]) {
_obj.rowSpan = _fieldAliasArray[item.fieldAlias][rowIndex];
}
return _obj;
},
render: (value, record) => {
// 文本、标签、链接、数值
// @params: item 当前的config数据,提供该字段的各类配置
// value 当前单元格的值
// record 点击单元格的整行数据
let rest = [];
if (item.type === '功能') {
/* rest = {
showComponent: setDetailsComponentVisible,
setDetailsConfig: setDetailConfig,
}; */
rest = [setDetailsComponentVisible, setDetailConfig];
} else if (item.type === '弹窗') {
/* rest = {
showModal: setModalVisible,
setExtra: setExtra,
};*/
rest = [setModalVisible, setExtra];
}
return mapHandleType(item.type)(
item,
[undefined, null].includes(value) ? '' : value,
record,
...rest,
);
},
};
_item.width = (!isNaN(Number(item.columnWidth)) && item.columnWidth) || 200; // 列宽,不设置时默认给200;
if (item.fixedColumn) _item.fixed = item.fixedColumn; // 固定位置
_item.align = item.alignType || 'left'; // 单元格内对齐方式
let _keyArray = (item.fieldGroup || item.fieldAlias || item.fieldName).split(fieldSplitStr);
// 自定义排序
let _sortFields = handleSortFields(sortFields);
if (_sortFields.includes(item.fieldAlias)) {
_item.sorter = true;
}
dataStruct(_keyArray, 0, _tempObj, _item);
return _item;
});
let _tempArray = [];
function handleObject2Array(obj, arr) {
Object.keys(obj).forEach((key, index) => {
if (obj[key].title && obj[key].title === key) {
arr.push(obj[key]);
} else {
arr.push({ title: key, children: [] });
handleObject2Array(obj[key], arr[index].children);
}
});
}
handleObject2Array(_tempObj, _tempArray);
// 增加序号
_tempArray.unshift({
title: '序号',
dataIndex: 'r',
key: 'r',
width: 60,
fixed: 'left',
});
// 增加操作列
if (permission.includes('editBtn') || permission.includes('deleteBtn')) {
_tempArray.push({
title: '操作',
align: 'center',
width: permission.reduce((final, curr) => {
if (['editBtn', 'deleteBtn'].includes(curr)) {
final += 30;
}
return final;
}, 30),
fixed: 'right',
render: (text, record) => {
return (
<Space className={style.handleColumnWrapper}>
{record.IsAllow ? (
<Popconfirm
placement="topRight"
title={'确认取消关注吗'}
icon={<QuestionCircleOutlined />}
onConfirm={() => focusProject(record)}
>
<Tooltip title={'点击取消关注'}>
<HeartTwoTone twoToneColor="#eb2f96" />
</Tooltip>
</Popconfirm>
) : (
<Popconfirm
placement="topRight"
title={'确认关注项目吗'}
icon={<QuestionCircleOutlined />}
onConfirm={() => focusProject(record)}
>
<Tooltip title={'点击添加关注'}>
<HeartOutlined />
</Tooltip>
</Popconfirm>
)}
{permission.includes('editBtn') && !isLocalDataSource ? (
<FormOutlined
className={style.editButton}
onClick={() => {
// setEditComponentVisible(true);
setModalType('编辑');
setCurrentData(record);
}}
/>
) : (
''
)}
{permission.includes('deleteBtn') && !isLocalDataSource ? (
<DeleteOutlined
disabled
className={style.deleteButton}
onClick={() => {
Modal.confirm({
content: '你确定要删除该数据吗?',
onOk: () => {
reportService
.delReportData({
reportName: reportName,
userId: USER_ID,
key: record.Key,
})
.then((res) => {
if (res.code === 0) {
message.success('删除成功!');
setGetNewData(!getNewData);
// getData(pagination); // 在onOk的回调函数内,打包后的代码会形成闭包
}
});
},
});
}}
/>
) : (
''
)}
</Space>
);
},
});
}
// 统计宽度
let _x = _tempArray.reduce((final, curr) => {
return (final += curr.width);
}, 0);
setTableX(_x);
return _tempArray;
};
const changeSortField = (value, index, key) => {
let _selectedSortFields = [...selectedSortFields];
_selectedSortFields[index][key] = value;
setSelectedSortFields(_selectedSortFields);
};
const addOtherSortFields = () => {
let _selectedSortFields = [...selectedSortFields];
_selectedSortFields.push({
label: '次要排序',
value: '',
sort: 'asc',
});
setSelectedSortFields(_selectedSortFields);
};
const deleteSortField = (index) => {
let _selectedSortFields = [...selectedSortFields];
_selectedSortFields.splice(index, 1);
setSelectedSortFields(_selectedSortFields);
};
const setTableHeight = () => {
if (!tableWrapperRef.current) return;
const clientHeight = tableWrapperRef.current?.clientHeight || 0;
const clientWidth = tableWrapperRef.current?.clientWidth || 0;
const _height = controlRef.current?.clientHeight; // 控制条的高度
const paginationHeight = 75; // 分页部分的高度
const tableHeaderHeight = tableHeaderLevel * 40; // 表头高度
const summaryHeight = summaryArray.length ? 40 * (Number(hasTotal) + Number(hasSinglePage)) : 0; // 总结栏的高度
const _minus =
clientHeight - _height - 16 - 4 - tableHeaderHeight - paginationHeight - summaryHeight - 10;
setListHeight(clientHeight - _height - paginationHeight - 4 - 6 - 16 - 2);
setTableY(_minus);
};
const getTableLevel = (config) => {
let _level =
config.reduce((final, curr) => {
return (final = curr.level > final ? curr.level : final);
}, 1) || 1;
setTableHeaderLevel(_level);
};
const saveReportListSortFields = (callback) => {
reportService
.saveReportListSortFields({
reportId: currentReportId,
sortFields: selectedSortFields
.filter((item) => item.value)
.map((item) => ({
fieldAlias: item.value,
sortType: item.sort,
})),
})
.then((res) => {
if (res.code === 0) {
message.success('排序保存成功!');
callback();
} else {
message.error(res.msg);
}
});
};
/** @description: 判断是否存在【时间】类型的选择,并返回组件;并记录默认值 */
const handleControls = (controls) => {
// 过滤出非日期的字段,存储默认值
let _controls = controls.filter((item) => item.type !== '日期');
_controls.forEach((item) => {
let _configItems = item.configItems.split('|');
_configItems.forEach((str) => {
if (str.includes('defaultValue=')) {
controlSelectChange(item.fieldAlias, str.replace('defaultValue=', ''));
}
});
});
};
const handleDate = (obj) => {
let _typeObj = obj.find((item) => item.filterRule === '日期');
setHasDatePicker(_typeObj ? _typeObj.fieldAlias : '');
const _configItems = _typeObj?.configItems.split('|') || [''];
let _defaultDate = _configItems
.find((item) => item.includes('defaultDate='))
?.replace('defaultDate=', '')
?.split(',');
let _defaultModel =
_configItems.find((item) => item.includes('defaultModel='))?.replace('defaultModel=', '') ||
'year';
_defaultDate = MODEL.includes(_defaultModel) ? _defaultDate : 'year'; // 确保值符合要求
if (_defaultDate && _defaultDate.length > 1) {
_defaultDate = { dateFrom: moment(_defaultDate[0]), dateTo: moment(_defaultDate[1]) };
} else if (_defaultDate && _defaultDate.length === 1) {
_defaultDate = { dateFrom: moment(_defaultDate[0]), dateTo: moment(_defaultDate[0]) };
} else {
_defaultDate = { dateFrom: moment(), dateTo: moment() };
}
// 给定默认值,初始化时可以加载
changeDate(
{
dateFrom: _defaultDate?.dateFrom.clone().startOf(_defaultModel).format(dateFormat),
dateTo: _defaultDate?.dateTo.clone().endOf(_defaultModel).format(dateFormat),
},
_defaultModel,
);
setDefaultDateConfig({
defaultDate: _defaultDate?.dateFrom,
defaultModel: _defaultModel,
});
};
const focusProject = (record) => {
reportService
.setReportAllow({
userId: USER_ID,
reportName: reportName,
reportKey: record.Key,
})
.then((res) => {
if (res.code === 0) {
message.success(`${record.IsAllow ? '取消' : '关注'}成功!`);
getData(pagination);
} else {
message.error(`${record.IsAllow ? '取消' : '关注'}失败!`);
}
});
};
function getRefHeight() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
let _height = controlRef?.current.clientHeight;
setControlsHeight(_height);
}, 100);
}
useEffect(() => {
getConfigs();
}, []);
useEffect(() => {
if (firstToGetData) getData(pagination);
getRefHeight();
}, [firstToGetData]);
useEffect(() => {
if (tableHeaderLevel) setTableHeight();
}, [tableHeaderLevel]);
useEffect(() => {
if (!isInit) getData(pagination);
}, [timeFrom, timeTo, sorterObject, filterObject, getNewData]);
useEffect(() => {
window.addEventListener('resize', getRefHeight);
return () => window.removeEventListener('resize', getRefHeight);
});
return (
<div className={style.reportManage} ref={tableWrapperRef}>
{/* 预留容器,提供给点击后的功能显示 */}
{detailsComponentVisible ? (
<div
className={style.contentWrapper}
style={{
position: 'absolute',
zIndex: 100,
width: 'calc(100% - 16px)',
height: 'calc(100% - 16px)',
}}
>
<DetailsComponent
url={detailConfig.url}
params={detailConfig.params}
onCancel={() => setDetailsComponentVisible(false)}
/>
</div>
) : (
''
)}
{/* 为方便阅读,分开两部分代码 */}
<div
className={style.contentWrapper}
style={{ zIndex: detailsComponentVisible ? -1 : 'auto' }}
>
<Row className={style.controlRow} ref={controlRef}>
<Space style={{ flex: 1 }} size={8} wrap={true}>
{/*时间搜索控件,确保时间搜索控件在第一个,单独匹配*/}
{hasDatePicker &&
defaultDateConfig.defaultDate !== null &&
permission.includes('filters') ? (
<DatePickerGroup
showModels={['all', 'month', 'quarter', 'year', 'custom']}
onChange={changeDate}
format={dateFormat}
defaultModel={defaultDateConfig.defaultModel}
defaultDate={defaultDateConfig.defaultDate}
/>
) : (
''
)}
{controls && controls.length && permission.includes('filters')
? controls
.filter((control) => ['下拉', '多选'].includes(control.filterRule))
.map((control) => {
return (
<Form.Item label={control.fieldAlias} key={control.fieldAlias}>
<ReturnControlComponent
style={{ width: 240 }}
type={control.filterRule}
reportName={reportName}
fieldAlias={control.fieldAlias}
configItems={control.configItems}
onChange={(e) => controlSelectChange(control.fieldAlias, e)}
filterFields={filterFields}
filterValues={filterValues}
filterObject={addFilterAndSearchParams({
reportName: reportName,
userId: USER_ID,
})}
/>
</Form.Item>
);
})
: ''}
{permission.includes('filters') ? (
<Form.Item label="快速索引" key={'快速搜索控件'}>
<ReturnControlComponent
placeholder={`请输入${
searchPlaceholder.length ? searchPlaceholder.join(',') : '关键字'
}搜索`}
style={{ width: 240 }}
type={'文本'}
onChange={(e) => {
searchInputChange(e);
}}
onSearch={searchData}
/>
</Form.Item>
) : (
''
)}
</Space>
{permission.includes('sortBtn') ||
permission.includes('exportBtn') ||
permission.includes('addBtn') ? (
<div style={{ width: 270, textAlign: 'end' }}>
<Space size={8} nowrap>
{permission.includes('addBtn') && !isLocalDataSource ? (
<Form.Item>
<Button
type={'primary'}
title={'自定义排序'}
icon={<PlusOutlined />}
onClick={() => {
setModalType('新增');
setCurrentData({});
}}
>
添加
</Button>
</Form.Item>
) : (
''
)}
{sortModalVisible && permission.includes('sortBtn') ? (
<Form.Item>
<Button
type={'primary'}
title={'自定义排序'}
icon={<SortAscendingOutlined />}
onClick={() => setModalVisible(true)}
>
排序
</Button>
</Form.Item>
) : (
''
)}
{permission.includes('exportBtn') ? (
<Form.Item>
<Dropdown style={{ float: 'right' }} overlay={menu}>
<Button>
<Space>
导出
<DownOutlined />
</Space>
</Button>
</Dropdown>
</Form.Item>
) : (
''
)}
</Space>
</div>
) : (
''
)}
</Row>
<div
className={style.tableContent}
style={{ height: `calc(100% - ${controlsHeight || 0}px)` }}
>
{columns && columns.length ? (
<BasicTable
rowKey={'Key'}
bordered
loading={tableLoading}
dataSource={tableData}
columns={columns}
onChange={(pagination, filters, sorter, extra) => {
setSorterObject(sorter);
}}
pagination={permission.includes('pagination') ? pagination : false}
// 237是内置图片高度
scroll={{ y: tableData && tableData.length ? `calc(100% - 44px)` : 237, x: tableX }}
summary={(pageData) => {
if (summaryArray.length && tableData && tableData.length)
return (
<Table.Summary fixed>
<Table.Summary.Row>
{hasSinglePage
? summaryArray.map((item, index) => {
if (item.fieldName === '空值行0') {
return (
<Table.Summary.Cell
key={`summary_${index}`}
index={0}
colSpan={item.colSpan + 1}
>
<span
style={{
display: 'inline-block',
width: '100%',
textAlign: 'center',
}}
>
小计
</span>
</Table.Summary.Cell>
);
} else if (item.fieldName.includes('空值行')) {
return (
<Table.Summary.Cell
key={`summary_${index}`}
index={0}
colSpan={item.colSpan}
/>
);
} else {
return (
<Table.Summary.Cell key={`summary_${index}`} index={0}>
<span
style={{
display: 'inline-block',
width: '100%',
textAlign: item.alignType,
}}
>
{item.type.replace('求', '')}:{' '}
{returnHandledNumber(item.configItems, item['单页'], true)}
</span>
</Table.Summary.Cell>
);
}
})
: ''}
</Table.Summary.Row>
<Table.Summary.Row>
{hasTotal
? summaryArray.map((item) => {
if (item.fieldName === '空值行0') {
return (
<Table.Summary.Cell index={0} colSpan={item.colSpan + 1}>
<span
style={{
display: 'inline-block',
width: '100%',
textAlign: 'center',
}}
>
总计
</span>
</Table.Summary.Cell>
);
} else if (item.fieldName.includes('空值行')) {
return <Table.Summary.Cell index={0} colSpan={item.colSpan} />;
} else {
return (
<Table.Summary.Cell index={0}>
<span
style={{
display: 'inline-block',
width: '100%',
textAlign: item.alignType,
}}
>
{item.type.replace('求', '')}:{' '}
{returnHandledNumber(item.configItems, item['全部'], true)}
</span>
</Table.Summary.Cell>
);
}
})
: ''}
</Table.Summary.Row>
</Table.Summary>
);
}}
/>
) : (
<div className={style.spinWrapper}>
<Spin />
</div>
)}
</div>
</div>
<Modal
title={'自定义排序字段'}
visible={modalVisible}
onCancel={() => setModalVisible(false)}
footer={
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>
<Button type={'link'} onClick={() => addOtherSortFields()}>
增加次要排序
</Button>
</div>
<div>
<Button onClick={() => setModalVisible(false)}>取消</Button>
<Button
type={'primary'}
onClick={() => {
saveReportListSortFields(() => getData(pagination));
setModalVisible(false);
}}
>
确认
</Button>
</div>
</div>
}
>
{selectedSortFields.map((item, index) => (
<Row key={'label'} className={style.controlRow}>
<Space size={8} wrap={true}>
<Form.Item label={item.label}>
<Select
style={{ width: 240 }}
defaultValue={item.value}
value={item.value}
onChange={(e) => changeSortField(e, index, 'value')}
>
<Option value="">未选择</Option>
{allSortFields.map((item) => (
<Option value={item}>{item}</Option>
))}
</Select>
</Form.Item>
<Form.Item>
<Select
style={{ width: 120 }}
defaultValue={item.sort}
value={item.sort}
onChange={(e) => changeSortField(e, index, 'sort')}
>
<Option value={'asc'}>升序</Option>
<Option value={'desc'}>降序</Option>
</Select>
</Form.Item>
{index !== 0 ? (
<Form.Item>
<MinusCircleOutlined
style={{ color: 'rgba(0,0,0,.65)' }}
onClick={() => deleteSortField(index)}
/>
</Form.Item>
) : (
''
)}
</Space>
</Row>
))}
</Modal>
<Modal visible={extraModal} onCancel={() => setExtraModal(false)} destroyOnClose width={800}>
{extra}
</Modal>
{/* 编辑表单 */}
<Modal
title={`${modalType}报表信息`}
visible={!!modalType}
width={'80%'}
footer={null}
// visible={true}
destroyOnClose
onCancel={() => setModalType('')}
>
<ReportEditForm
modalType={modalType}
reportDetails={tableStruct}
reportData={currentData}
onCancel={() => {
setModalType('');
getData(pagination);
}}
reportName={reportName}
/>
</Modal>
</div>
);
};
const mapStateToProps = (state) => {
const allWidgets = state.getIn(['global', 'globalConfig', 'allWidgets']);
let _flatWidgets = [];
const flatWidgets = (arr) => {
arr.forEach((item) => {
if (item.widgets && item.widgets.length) {
flatWidgets(item.widgets);
} else {
_flatWidgets.push(item);
}
});
};
flatWidgets(allWidgets);
return {
allWidgets: _flatWidgets,
};
};
export default connect(mapStateToProps, null)(ReportsManage);
@import '~antd/es/style/themes/default.less';
.lackParams {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.reportManage {
display: flex;
flex-direction: column;
height: 100%;
padding: 8px;
.contentWrapper {
display: flex;
flex-direction: column;
height: 100%;
.controlRow {
display: flex;
flex-direction: row;
margin-bottom: 4px;
padding: 6px;
background: #ffffff;
border-radius: 4px;
}
.tableContent {
flex: 1;
padding: 6px;
background: #ffffff;
.handleColumnWrapper {
display: flex;
justify-content: space-around;
.editButton {
cursor: pointer;
&:hover {
color: rgb(24, 144, 255);
}
}
.deleteButton {
color: rgb(255, 0, 0);
cursor: pointer;
&:hover {
//color: rgb(24, 144, 255);
}
}
}
.spinWrapper {
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 60vh;
}
:global {
.@{ant-prefix}-table-container {
height: 100%;
& > .@{ant-prefix}-table-body {
border-right: 1px solid #dbe7fb;
border-bottom: 1px solid #dbe7fb;
}
.@{ant-prefix}-table-body {
flex: 1;
}
.@{ant-prefix}-table-summary > table > tfoot > tr > td {
border-right: none;
}
.@{ant-prefix}-table-summary > table > tfoot > tr:not(:last-child) > td {
border-bottom: none;
}
.@{ant-prefix}-table-summary > table > tfoot > tr:last-child > td {
border-bottom: 1px solid #dbe7fb;
}
}
.@{ant-prefix}-basic-table .@{ant-prefix}-pagination {
border-top: none;
}
}
}
}
.link {
cursor: pointer;
&:hover {
//color: #1685FF;
font-weight: bold;
text-decoration: underline;
}
}
:global {
.@{ant-prefix}-form-item {
margin-bottom: 0 !important;
}
}
.prefixOrSuffix {
color: rgba(0, 0, 0, 0.65);
font-size: 10px;
}
:global {
::-webkit-scrollbar,
*::-webkit-scrollbar {
width: 0px;
height: 6px;
}
::-webkit-scrollbar-track,
*::-webkit-scrollbar-track {
background: #f2f2f2;
border-radius: 10px;
}
::-webkit-scrollbar-thumb,
*::-webkit-scrollbar-thumb {
background: #dcdcdc;
border-radius: 5px;
}
}
}
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,
ExportOutlined,
ImportOutlined,
} from '@ant-design/icons';
import {
Button,
Checkbox,
Divider,
Form,
Input,
InputNumber,
message,
Modal,
Row,
Select,
Space,
Switch,
Tooltip,
Table,
Upload,
} 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,
API,
exportReportConfig,
importReportConfig,
} from '../api/service/report';
import { isNumber, isString } from './utils/utils';
import moment from 'moment';
import CustomerColorPicker from './Components/customerColorPicker/CustomerColorPicker';
const { Option } = Select;
const { TextArea } = Input;
const { Search } = Input;
const typeArray = ['文本', '标签', '数值', '链接', '功能', '弹窗', '日期', '日期时间', '附件'];
const filterRule = ['文本', '下拉', '多选', '日期'];
const publicSplit = '&split;';
const USER_ID = window.globalConfig.userInfo.OID;
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);
}}
/>
<ExportOutlined title={'导出配置'} onClick={() => exportConfigs(record)} />
<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 [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [selectedRows, setSelectedRows] = useState([]);
const SortableItem = SortableElement((props) => <tr {...props} />);
const SortableBody = SortableContainer((props) => <tbody {...props} />);
// 导入配置
const uploadProps = {
action: `${window.location.origin}${API.IMPORT_REPORT_DATA}`,
multiple: false,
showUploadList: false,
headers: {
'Content-Type': 'multipart/form-data',
},
withCredentials: true,
customRequest({ action, file, headers, withCredentials }) {
const formData = new FormData();
formData.append('file', file);
const _data = { userId: USER_ID };
importReportConfig(
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
{ ..._data },
formData,
).then((res) => {
if (res.code === 0) {
message.success('导入成功!');
getData();
} else {
message.info(res.msg);
}
});
},
};
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: '是否必填',
dataIndex: 'isRequired',
key: 'isRequired',
width: 100,
render: (text, record, index) => {
return isEditing ? (
<Form.Item name={[index, 'isRequired']} valuePropName="checked">
<Switch
onChange={(e) => modifyDetailData('isRequired', e, record, index)}
checkedChildren="是"
unCheckedChildren="否"
defaultChecked={false}
className={returnEqual(index, 'isRequired') ? style.boxShadowOfSwitch : ''}
/>
</Form.Item>
) : text ? (
'是'
) : (
'否'
);
},
},
{
title: '操作',
width: 80,
render: (text, record) => {
return (
<div style={{ display: 'flex', justifyContent: 'space-around' }}>
<FormOutlined
onClick={() => {
setActiveID(record.id);
if (record.color) setBackgroundColor(record.color);
form.setFieldsValue(setDefaultValue(record));
setCurrentField(setDefaultValue(record));
setDetailVisible(true);
}}
/>
<DeleteOutlined
onClick={() => deleteReportDetail(record.id)}
style={{ color: 'red' }}
/>
</div>
);
},
},
];
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRowKeys(selectedRowKeys);
setSelectedRows(selectedRows);
},
};
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();
});
};
// 切换字段的同时,如果数据有修改,那需要保存之前的数据 edit by ChenLong 2022年8月29日
const changeField = (record) => {
// submitReportDetails('changeField').then(res => {
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;
};
// @params: {type:String} 不传入type,默认提交表单、关闭表单;传入type时
const submitReportDetails = (type) => {
// 1.表单内的值;2.标签值
return 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);
if (!type) {
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 = () => {
setDetailTableLoading(true);
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('提交成功!');
} else {
message.error(res.msg);
}
setDetailTableLoading(false);
setSubmitFieldLoading(false);
setDetailVisible(false);
clickReport(currentReport.id);
})
.catch((err) => {
setDetailTableLoading(false);
});
});
};
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];
if (currentColorPicker.key === 'tagColor') {
_data[index].color = e;
} else {
_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];
};
// 拖拽
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);
};
const batchModify = (key, value) => {
console.log(key, ':', value);
// 1.取出勾选的字段;2.批量修改值
if (selectedRowKeys.length === 0) return message.info('未勾选任何字段');
let _detailData = detailData.map((item) => {
let _item = { ...item };
if (selectedRowKeys.includes(_item.id)) {
_item[key] = value;
}
return _item;
});
setDetailData(_detailData);
editDetailForm.setFieldsValue(_detailData);
};
const exportConfigs = (record) => {
setTableLoading(true);
exportReportConfig(
{ responseType: 'blob' },
{
reportId: record.id,
},
).then((res) => {
setTableLoading(false);
if (res && res.code === -1) return message.error(res.msg);
const url = window.URL.createObjectURL(
new Blob([res], { type: 'application/json;charset=UTF-8' }),
);
const a = document.createElement('a');
a.href = url;
a.target = '_blank';
a.download = `${record.reportName}.json`;
a.click();
a.remove();
});
};
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,
}}
>
{currentColorPicker.key === 'tagColor' ? (
<CustomerColorPicker
clickColorPicker={(backgroundColor) => {
changeBackgroundColor(backgroundColor);
}}
/>
) : (
<SketchPicker
width={240}
presetColors={[
'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.fieldAlias}
</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>
{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: 'tagColor',
});
}}
/>
</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 style={{ marginRight: 8 }} 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'}
loading={detailTableLoading}
style={{ marginRight: 8 }}
onClick={submitDetailFromTable}
>
保存字段
</Button>
</>
) : (
''
)}
{isEditing ? (
<Button type={'primary'} onClick={() => setIsEditing(false)}>
拖动排序
</Button>
) : (
<Button type={'primary'} onClick={() => setIsEditing(true)}>
关闭排序
</Button>
)}
</Form.Item>
</Form>
</Row>
<div className={style.tableContent}>
<Form className={style.tableForm} form={editDetailForm}>
<BasicTable
loading={detailTableLoading}
pagination={false}
bordered
scroll={{ y: `calc(100% - ${selectedRowKeys.length ? 90 : 40}px)` }} // 需要考虑总结栏的高度
rowKey={'id'}
columns={detailColumns}
rowSelection={
isEditing
? {
type: 'checkbox',
...rowSelection,
}
: null
}
dataSource={detailData.filter((item) => item.visible)}
summary={(pageData) => {
if (detailData && detailData.length > 1 && isEditing && selectedRows.length) {
return (
<Table.Summary fixed>
<Table.Summary.Row>
<Table.Summary.Cell colSpan={7}>批量操作</Table.Summary.Cell>
<Table.Summary.Cell>
<Form.Item>
<InputNumber onChange={(e) => batchModify('columnWidth', e)} />
</Form.Item>
</Table.Summary.Cell>
<Table.Summary.Cell>
<Form.Item>
<Select onChange={(e) => batchModify('alignType', e)}>
<Option value={'left'}></Option>
<Option value={'center'}></Option>
<Option value={'right'}></Option>
</Select>
</Form.Item>
</Table.Summary.Cell>
<Table.Summary.Cell>
<Form.Item>
<Select onChange={(e) => batchModify('fixedColumn', e)}>
<Option value={''}>不固定</Option>
<Option value={'left'}></Option>
<Option value={'right'}></Option>
</Select>
</Form.Item>
</Table.Summary.Cell>
<Table.Summary.Cell>
<Form.Item>
<Switch
checkedChildren="显示"
unCheckedChildren="不显示"
defaultChecked={false}
onChange={(e) => batchModify('isShow', e)}
/>
</Form.Item>
</Table.Summary.Cell>
<Table.Summary.Cell>
<Form.Item>
<Switch
checkedChildren="开启"
unCheckedChildren="关闭"
defaultChecked={false}
onChange={(e) => batchModify('isFilter', e)}
/>
</Form.Item>
</Table.Summary.Cell>
<Table.Summary.Cell>
<Form.Item>
<Select onChange={(e) => batchModify('isFilter', e)}>
<Option value={''}>不过滤</Option>
<Option value={'文本'}>文本</Option>
<Option value={'下拉'}>下拉</Option>
<Option value={'多选'}>多选</Option>
<Option value={'日期'}>日期</Option>
</Select>
</Form.Item>
</Table.Summary.Cell>
<Table.Summary.Cell>
<Form.Item>
<Switch
checkedChildren="开启"
unCheckedChildren="关闭"
defaultChecked={false}
onChange={(e) => batchModify('isMerge', e)}
/>
</Form.Item>
</Table.Summary.Cell>
<Table.Summary.Cell>
<Form.Item>
<DeleteOutlined
onClick={() => batchModify('delete')}
title={'批量删除'}
style={{ color: 'red' }}
/>
</Form.Item>
</Table.Summary.Cell>
</Table.Summary.Row>
</Table.Summary>
);
}
}}
components={
isEditing
? ''
: {
body: {
wrapper: DraggableContainer,
row: DraggableBodyRow,
},
}
}
onRow={(record) => {
return {
onDoubleClick: (e) => {
return false;
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'}
style={{ marginRight: 8 }}
onClick={() =>
openCreateModal(() => {
setCurrentReport({});
setAllFields([]);
setIsCreatingMainTable(true);
createMainTableForm.setFieldsValue({
tableName: '',
reportName: '',
reportFields: [],
});
})
}
>
新增
</Button>
<Upload {...uploadProps}>
<Button size="middle" icon={<ImportOutlined />}>
导入
</Button>
</Upload>
</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 MySQLIcon from '../assets/mysql.png';
import SQLServerIcon from '../assets/sqlserver.png';
import WebAPIIcon from '../assets/webapi.png';
const tagColors = [
'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(189, 160, 20)',
'rgb(154, 56, 215)',
'rgb(221, 64, 151)',
];
const tagBackgroundColors = [
'rgb(220, 223, 228)',
'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)',
];
const sourceIconMap = {
mysql: MySQLIcon,
sqlserver: SQLServerIcon,
webapi: WebAPIIcon,
};
export { tagColors, tagBackgroundColors, sourceIconMap };
import style from '../ReportsManage.less';
import extraComponents from '../extra/extraComponents';
import moment from 'moment';
import { Tag } from 'antd';
import { hasMoney, isNumber, returnHandledNumber } from './utils';
import { tagColors, tagBackgroundColors } from './constant';
/**
* @params: config下的数值的configRule结构如下,[{最大值: 10,最小值: 0,颜色:'#AAAAAA'}];
* @business: configRule有值,则按照configRule设置;没有,按照color设置; 有最大值,无最小值;['', 1]
* 有最大值,有最小值;[2,
*
* 10]
* 有最小值,无最大值;[11,'']
*/
// 链接 功能 弹窗功能待提出需求
// 函数名、属性、...
const clickLink = (config) => {
return window.open(config.configItems, '_blank');
};
const handleUrl = (allWidgets, config) => {};
const handleConfigUrl = (config, record) => {
let _configs = config.configItems.split('|');
// type = 替换|跳转;
let _type = '';
//url=project/pandawork/contractDetails/index?ageSize=100&accountName=软件订单台账|contractCode=${订单编号}|company=${分公司}
let _url = '';
let _params = {};
_configs.forEach((item) => {
if (item.includes('type=')) _type = item.replace('type=', '');
if (item.includes('url=')) {
let _tempUrl = item.replace('url=', '');
let _urlArray = _tempUrl.split('?');
if (_urlArray[1]) {
_urlArray[1].split('&').forEach((param) => {
let _paramArr = param.split('=');
if (_paramArr[1] && _paramArr[1].includes('${') && _paramArr[1].includes('}')) {
let _key = _paramArr[1].replace(/\$\{|\}/g, '');
_params[_paramArr[0]] = record[_key];
} else {
_params[_paramArr[0]] = _paramArr[1];
}
});
}
_url = _urlArray[0];
}
});
return { url: _url, type: _type, params: _params };
};
const clickWidget = (config, text, record, showComponents, setDetailsConfig) => {
// 功能跳转接口
// type=替换|url=product/ReportsManage/ReportsSetting?accountName=${ID}&pageSize=100&title=${name}
if (!config.configItems) return message.error('未配置跳转信息');
// setDetailsComponentVisible(true);
showComponents(true);
let urlConfig = handleConfigUrl(config, record);
// setDetailConfig(urlConfig);
setDetailsConfig(urlConfig);
};
const clickModal = (config, showModal, setExtra) => {
let _splitArr = config.configItems.split('&');
let _fn = _splitArr[0] || 'default';
_splitArr.splice(0, 1);
let _data = {};
_splitArr.forEach((item) => {
let _item = item.split('=');
_data[_item[0]] = _item[1];
});
// setExtraModal(true);
showModal(true);
return setExtra(extraComponents[_fn](_data));
};
const returnOpacity = (rgba) => {
if (!rgba) rgba = 'rgba(0,0,0,.65)';
let _str = rgba.replace('rgba(', '').replace(')', '');
let _splitStr = _str.split(',');
return `rgba(${_splitStr[0]},${_splitStr[1]},${_splitStr[2]},.25)`;
};
export const handleNumber = (config, number) => {
let _color = '';
let _number;
if (number) _number = Number(number); // 当设置精度后,会被转成字符串
if (config.type === '数值' && config.numericalConfigs && config.numericalConfigs.length) {
config.numericalConfigs.forEach((item) => {
// 接口对于数值类型的返回为null
if (!_color) {
let _max = isNumber(item.maxValue) ? item.maxValue : '';
let _min = isNumber(item.minValue) ? item.minValue : '';
if (_max !== '' && _min === '') {
_color = _number <= Number(_max) ? '' : item.color;
} else if (_min !== '' && _max === '') {
_color = _number > Number(_min) ? item.color : '';
} else {
_color = _number > Number(_min) && _number <= Number(_max) ? item.color : '';
}
}
});
} else if (config.type === '数值标签' && config.labelConfigs && config.labelConfigs.length) {
console.log('数值标签');
_color = 'red';
} else if (config.color) {
_color = config.color;
}
return number ? (
<span>
<span
className={style.prefixOrSuffix}
style={{
color: _color,
}}
>
{config.prefix || ''}
</span>
<span
style={{
color: _color,
margin: '0 5px',
}}
>
{returnHandledNumber(config.configItems, number)}
</span>
<span className={style.prefixOrSuffix} style={{ color: _color }}>
{config.suffix || ''}
</span>
</span>
) : (
'-'
);
};
export const handleNumberTag = (config, number) => {
let _color = '';
let _map = {};
// 标签需要设置分隔符 2022年7月13日 ChenLong
let _configItems = config.configItems.split('|');
let _configMap = {};
_configItems.forEach((item) => {
let _arr = item.split('=');
_configMap[_arr[0]] = _arr[1];
});
// 处理label的颜色
if (config.labelConfigs && config.labelConfigs.length) {
config.labelConfigs.forEach((item) => {
_map[item.labelValue] = item.color;
});
}
_color = config.color || 'rgba(0,0,0,.85)';
// String(text)为了解决可能存在数值类型的数据需要设置成标签的需求
// return String(text).split(_configMap['分隔符']).map(item => <Tag color={_map[item]}>{item}</Tag>);
// let a = 'abc' a.split(undefined); a = ['abc']
return (
<Tag
style={{
background: mapTag(_map, number) || _color,
border: `1px solid ${mapTag(_map, number) || _color}`,
borderRadius: 2,
color: mapTag(_map, number)
? tagColors[tagBackgroundColors.findIndex((c) => c === mapTag(_map, number))]
: `rgba(0, 0, 0, .65)`,
}}
>
{(config.prefix || '') +
returnHandledNumber(config.configItems, number) +
(config.suffix || '')}
</Tag>
);
};
/** @params: 标签形态的configRule,[{标签值:'字符串',颜色: '#AAAAAA'}]; */
/* 支持正则配置 RegExp */
export const handleTag = (config, text) => {
let _color = '';
let _map = {};
// 标签需要设置分隔符 2022年7月13日 ChenLong
let _configItems = config.configItems.split('|');
let _configMap = {};
_configItems.forEach((item) => {
let _arr = item.split('=');
_configMap[_arr[0]] = _arr[1];
});
// 处理label的颜色
if (config.labelConfigs && config.labelConfigs.length) {
config.labelConfigs.forEach((item) => {
_map[item.labelValue] = item.color;
});
}
_color = config.color || 'rgba(0,0,0,.85)';
// String(text)为了解决可能存在数值类型的数据需要设置成标签的需求
// return String(text).split(_configMap['分隔符']).map(item => <Tag color={_map[item]}>{item}</Tag>);
// let a = 'abc' a.split(undefined); a = ['abc']
return String(text)
.split(_configMap['分隔符'])
.map((item) => (
<Tag
style={{
background: mapTag(_map, item) || _color,
border: `1px solid ${mapTag(_map, item) || _color}`,
borderRadius: 2,
color: mapTag(_map, item)
? tagColors[tagBackgroundColors.findIndex((c) => c === mapTag(_map, item))]
: `rgba(0, 0, 0, .65)`,
}}
>
{item}
</Tag>
));
};
const mapTag = (map, key) => {
if (map[key]) {
return map[key];
} else {
let _keys = Object.keys(map);
return _keys.reduce((final, _key) => {
let isRegExp = _key.includes('RegExp=');
let reg = new RegExp(_key.replace('RegExp=', ''));
if (isRegExp && reg.test(key)) {
final = map[_key];
}
return final;
}, '');
}
};
export const handleText = (config, text) => {
return <span style={{ color: config.color || 'rgba(0,0,0,.85)' }}>{text}</span>;
};
export const handleLink = (config, text) => {
return (
<span
className={style.link}
style={{
color: config.color || 'rgba(0,0,0,.85)',
}}
onClick={() => clickLink(config)}
>
{text}
</span>
);
};
export const handleWidget = (config, text, record, showComponents, setDetailsConfig) => {
return (
<span
style={{ color: config.color }}
className={style.link}
onClick={() => clickWidget(config, text, record, showComponents, setDetailsConfig)}
>
{text}
</span>
);
};
export const handleModal = (config, text, showModal, setExtra) => {
return (
<span
className={style.link}
style={{ color: config.color || 'rgba(0,0,0,.85)' }}
onClick={() => clickModal(config, showModal, setExtra)}
>
{text}
</span>
);
};
// 日期
export const handleDateString = (config, text) => {
return text ? moment(text).format('YYYY-MM-DD') : '-';
};
// 时刻
export const handleDateTimeString = (config, text) => {
return text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : '-';
};
// 处理pageSize
export const handlePageSize = (numStr) => {
return numStr && isNumber(Number(numStr)) ? Number(numStr) : false;
};
// 处理默认排序
export const handleSortFields = (sortFields) => {
return (sortFields && sortFields.split(',')) || [];
};
const isObject = (obj) => {
return Object.prototype.toString.call(obj) === '[object Object]';
};
const isString = (obj) => {
return Object.prototype.toString.call(obj) === '[object String]';
};
const isNumber = (num) => {
return Object.prototype.toString.call(num) === '[object Number]';
};
const isArray = (arr) => {
return Object.prototype.toString.call(arr) === '[object Array]';
};
const hasMoney = (configItems) => {
if (!configItems) return false;
let _items = configItems.split('|');
return !!_items.find((item) => item === '金额');
};
const isSelect = (configItems) => {
if (!configItems) return false;
let _items = configItems.split('|');
return !!_items.find((item) => item === 'renderAsSelect');
};
// options=name1[.value1],name2[.value2],name3[.value3];
const returnOptions = (configItems) => {
if (!configItems) return false;
let _items = configItems.split('|');
let _options = _items.find((item) => item.includes('options='));
if (!_options) return false;
return _options.replace('options=', '').split(',');
};
const returnRows = (configItems) => {
if (!configItems) return 3;
let _items = configItems.split('|');
let _options = _items.find((item) => item.includes('rows='));
if (!_options) return 3;
let _rows = Number(_options.replace('rows=', ''));
return !isNaN(_rows) && _rows >= 1 ? _rows : 3;
};
const returnCols = (configItems) => {
if (!configItems) return 1;
let _items = configItems.split('|');
let _options = _items.find((item) => item.includes('cols='));
if (!_options) return 1;
let _cols = Number(_options.replace('cols=', ''));
return !isNaN(_cols) && _cols <= 3 && _cols >= 0 ? _cols : 1;
};
/**
* @description: 用来在summary中处理数值的配置
* @params: 参数描述
* @date: 2022/8/10
* @author: ChenLong
*/
const returnHandledNumber = (configItems, num, isSummary) => {
// 精度、前缀、后缀、倍率
// $_d|_d%|_d*0.0001|金额|0.00
if (isNaN(num)) return '-';
if (!configItems) return num;
num = Number(num);
let _items = configItems.split('|');
/* let prefix = '';
let suffix = '';*/
let template = '_d';
let precision = 0;
let rate = 1;
_items.forEach((item) => {
let _arr = [];
if (item.match(/_d[^\*]/)) {
// 后缀
template = item;
} else if (item.match(/^_d\*/)) {
// 倍率
let _rate = item.replace(/_d\*/, '');
rate = _rate && isSummary ? Number(_rate) : 1; // 总结栏计算需要计算倍率,其他不用
} else if (item.match(/^0\./)) {
// 精度
precision = item.replace('0.', '').length;
}
});
// 可能存在NaN的问题
let final = _items.includes('金额')
? Number((num * rate).toFixed(precision)).toLocaleString()
: Number((num * rate).toFixed(precision)).toLocaleString();
return template.replace(/_d/, isString(final) ? final : '-');
};
/**
* @description: 返回configItems内配置的默认值、默认模式等等
* @params: 参数描述
* @date: 2022/8/12
* @author: ChenLong
* @params:
* configItems 报表字段的配置 例如 defaultValue=智慧水务
*
* defaultDateModel=customer|defaultDateValue=2022-01-01,2022-12-31;
* keysArray
*
* 所需要返回的值的key的集合,比如你需要获取configItems中的’defaultValue‘,那么keysArray=['defaultValue'];
* @returns:
* defaultValue 通用参数 默认值
* defaultDateModel 时间参数 默认模式
* defaultDateValue 时间参数 默认时间
*/
const returnDefaultValueOrConfigs = (configItems = '', keysArray = []) => {
let _map = {};
let _configItemsArray = configItems.split('|');
keysArray.forEach((key) => {
_map[key] = _configItemsArray.find((item) => item.includes(`${key}=`))?.replace(`${key}=`, '');
});
return _map;
};
function downloadFunc(url, name, target = '_self') {
const a = document.createElement('a');
a.href = url;
a.target = target;
a.download = name;
a.click();
a.remove();
}
/**
* @description: 校验文件的名称是否包含特殊字符
* @params: {Object: File} file file对象 { special:Boolean } 是否去除/的匹配
* @date: 2021/12/8
* @author: ChenLong
* @returns {Object} {type: error | success ,content: 提示...}
*/
function filenameVerification(file, special) {
// 文件名含有特殊字符 提示不能上传 {+,:/?#[]@!$&\\*+;=}
// 规则对象(flag)
var flag = !special
? new RegExp("[`~!@#$^&*=|{}':;',\\[\\]/?~!@#¥&*——|{}【】‘;:”“'。,、?]")
: new RegExp("[`~!@#$^&*=|{}':;',[\\]?~!@#¥&*——|{}【】‘;:”“'。,、?]");
if (flag.test(file.name)) {
return {
type: 'error',
content: `文件名格式错误,请检查文件名是否含有特殊字符${"~!@#$^&*=|{}':;',\\[\\]/?~!@#¥&*——|{}【】‘;:”“'。,、?"}`,
};
}
return {
type: 'success',
content: `上传成功!`,
};
}
export {
isObject,
isString,
isNumber,
hasMoney,
isArray,
returnHandledNumber,
returnDefaultValueOrConfigs,
downloadFunc,
filenameVerification,
isSelect,
returnOptions,
returnRows,
returnCols,
};
export const SERVICE_APP_GET_UI_META = 'app.getUIMeta';
export const SERVICE_APP_LOGIN_MODE = {
password: 'password',
dingding: 'dingding',
weixin: 'weixin',
phone: 'phone',
};
export const LOGIN_DISPLAY = {
Account: 'Account',
WeChart: 'WeChart',
Mobile: 'Mobile',
};
export const LOGIN_WAY = {
Account: 'pdw',
WeChart: 'iotWechat',
Mobile: 'iotPhone',
};
export const WX_REDIRECT_URI = 'https://panda-water.com/civbase/user/login';
export const SERVICE_INTERFACE_SUCCESS_CODE = 0;
export const SERVICE_INTERFACE_PARAMS_EXCEPTION_CODE = -1; // 服务参数异常
export const SERVICE_INTERFACE_HANDLE_EXCEPTION_CODE = -2; // 服务处理异常
export const SERVICE_APP_CLOSE_ALL_TABS = 'app.close.tabs';
export const REQUEST_HTTP = 'http';
export const REQUEST_POP = 'pop';
export const REQUEST_METHOD_GET = 'get';
export const REQUEST_METHOD_POST = 'post';
export const REQUEST_METHOD_PUT = 'put';
export const REQUEST_METHOD_DELETE = 'delete';
export const RESPONSE_TYPE = 'blob';
import { Modal, notification } from 'antd';
import { instanceRequest, service } from '@wisdom-utils/utils';
import ReportService from './service/report';
const { warning } = Modal;
// eslint-disable-next-line no-return-await
instanceRequest.reportCodeError = true;
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误), 或当前的会话已超时,请重新登录',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
let instance = null;
instanceRequest.setErrorHandler((error) => {
const { response } = error;
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, config } = response;
if (status === 401) {
if (!instance) {
instance = warning({
title: '错误信息',
content: `${codeMessage[status]}`,
centered: true,
onOk(close) {
window.share && window.share.event && window.share.event.emit('triggerLoginout');
close();
},
});
}
} else {
notification.error({
message: `请求错误 ${status}: ${config.url}`,
description: errorText,
});
}
} else if (!response) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
}
return response;
});
const reportService = service(ReportService);
export { reportService };
/*
** 轻量化报表平台接口
** create by ChenLong on 2022/6/24
** 功能路径:src\api\service\report.js
** 菜单参数列表:*变量名*(变量说明,数据类型,是否必填,取值范围)
**/
import { request } from '@wisdom-utils/utils';
import * as constants from '../constants';
const BASEURL = '/PandaAssets/Assets/ReportManager';
export const API = {
GET_REPORT_INFO: `${BASEURL}/GetReportInfo`, // 获取报表信息
GET_REPORT_FILTER_VALUE: `${BASEURL}/GetReportFilterValue`, // 获取过滤字段的值的枚举
GET_REPORT_FILTER_VALUES: `${BASEURL}/GetReportFilterValues`, // 获取过滤字段的值的枚举
GET_REPORT_CONFIG_LIST: `${BASEURL}/GetReportConfigList`, // 获取配置列表
GET_REPORT_DETAILS_INFO: `${BASEURL}/GetReportDetailsInfo`, // 获取配置详情
GET_TABLES: `${BASEURL}/GetTables`, // 查询表
GET_TABLE_FIELDS: `${BASEURL}/GetTableFields`, // 查询表内的字段
ADD_REPORT_INFO: `${BASEURL}/AddReportInfo`, // 创建报表主表、关联报表子表
EDIT_REPORT_INFO: `${BASEURL}/AddReportInfo`,
ADD_REPORT_DETAIL_INFO: `${BASEURL}/AddReportDetailInfo`, // 附加子表字段到主表
DELETE_REPORT_INFO: `${BASEURL}/DeleteReportInfo`, // 删除报表
DELETE_REPORT_DETAIL_INFO: `${BASEURL}/DeleteReportDetailInfo`, // 删除字段
EXPORT_ACCOUNT_DATA: `${BASEURL}/ExportAccountData`, // 导出数据
SAVE_REPORT_LIST_SORT_FIELDS: `${BASEURL}/SaveReportListSortFields`, // 保存排序
ADD_REPORT_DETAIL_INFO_INDEX: `${BASEURL}/AddReportDetailInfoIndex`, // 变更接口顺序
UPDATE_REPORT_DATA: `${BASEURL}/UpdateReportData`, // 更新报表数据
GET_REPORT_DETAILS: `${BASEURL}/GetReportDetails`, // 获取报表配置
DEL_REPORT_DATA: `${BASEURL}/DelReportData`, // 删除报表数据
SET_REPORT_ALLOW: `${BASEURL}/SetReportAllow`, // 设置关注
ADD_REPORT_DATA: `${BASEURL}/AddReportData`, // 添加报表数据
EXPORT_JPG: `${BASEURL}/ExportJPGAccountData`,
EXPORT_REPORT_CONFIG: `${BASEURL}/ExportReportConfig`,
IMPORT_REPORT_CONFIG: `${BASEURL}/ImportReportConfig`,
// 多数据源
GET_DATA_SOURCES: `${BASEURL}/GetDataSources`, // 获取数据源列表
ADD_DB_CONNECTION: `${BASEURL}/AddDbConnection`, // 新增/编辑数据源
DELETE_DB_CONNECTION: `${BASEURL}/DeleteDbConnection`, // 删除数据源
GET_DB_SOURCES: `${BASEURL}/GetDBSources`, // 获取数据库
TEST_CONNECTION: `${BASEURL}/TestConnection`, // 测试链接
DEL_REPORT_CHILD: `${BASEURL}/DelReportChlid`, // 删除挂接关系
};
const reportService = {
getReportInfo: {
url: API.GET_REPORT_INFO,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
getReportFilterValue: {
url: API.GET_REPORT_FILTER_VALUE,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
getReportFilterValues: {
url: API.GET_REPORT_FILTER_VALUES,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
getReportConfigList: {
url: API.GET_REPORT_CONFIG_LIST,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
getReportDetailsInfo: {
url: API.GET_REPORT_DETAILS_INFO,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
getTables: {
url: API.GET_TABLES,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
getTableFields: {
url: API.GET_TABLE_FIELDS,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
addReportInfo: {
url: API.ADD_REPORT_INFO,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
editReportInfo: {
url: API.EDIT_REPORT_INFO,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
addReportDetailInfo: {
url: API.ADD_REPORT_DETAIL_INFO,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
deleteReportInfo: {
url: API.DELETE_REPORT_INFO,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
deleteReportDetailInfo: {
url: API.DELETE_REPORT_DETAIL_INFO,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
saveReportListSortFields: {
url: API.SAVE_REPORT_LIST_SORT_FIELDS,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
addReportDetailInfoIndex: {
url: API.ADD_REPORT_DETAIL_INFO_INDEX,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
updateReportData: {
url: API.UPDATE_REPORT_DATA,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
getReportDetails: {
url: API.GET_REPORT_DETAILS,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
delReportData: {
url: API.DEL_REPORT_DATA,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
setReportAllow: {
url: API.SET_REPORT_ALLOW,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
addReportData: {
url: API.ADD_REPORT_DATA,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
exportJPG: {
url: API.EXPORT_JPG,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
exportConfig: {
url: API.EXPORT_REPORT_CONFIG,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
importConfig: {
url: API.IMPORT_REPORT_CONFIG,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
getDataSources: {
url: API.GET_DATA_SOURCES,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
addDbConnection: {
url: API.ADD_DB_CONNECTION,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
deleteDbConnection: {
url: API.DELETE_DB_CONNECTION,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
getDbSources: {
url: API.GET_DB_SOURCES,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
testConnection: {
url: API.TEST_CONNECTION,
method: constants.REQUEST_METHOD_POST,
type: constants.REQUEST_METHOD_POST,
},
delReportChild: {
url: API.DEL_REPORT_CHILD,
method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_METHOD_GET,
},
};
export const submitReportData = (params, data) =>
request({
url: API.UPDATE_REPORT_DATA,
method: 'post',
params,
data,
});
export const exportAccountData = (options, params, data) =>
request({
url: API.EXPORT_ACCOUNT_DATA,
method: 'post',
...options,
params,
data,
});
export const exportJPG = (options, data) => {
return request({
url: API.EXPORT_JPG,
method: 'post',
...options,
data,
});
};
export const addReportDetailInfoIndex = (data) =>
request({
url: API.ADD_REPORT_DETAIL_INFO_INDEX,
method: 'post',
data,
});
export const importReportConfig = (options, params, data) =>
request({
url: API.IMPORT_REPORT_CONFIG,
method: 'post',
...options,
params,
data,
});
export const exportReportConfig = (options, params) =>
request({
url: API.EXPORT_REPORT_CONFIG,
method: 'get',
...options,
params,
});
export default reportService;
const AccountBaseURL = '/PandaWorkFlow/WorkFlow/AccountManage';
export const uploadFileUrl = `${AccountBaseURL}/UploaderFiles`; // 上传文件链接
export const downloadFileUrl = `${AccountBaseURL}/DownloadFiles`; // 下载/播放文件链接
/**
* 时间组选择:支持 全部,日,月,年,自定义 类型 props:
* onChange: ({dateFrom, dateTo}, model) => {}。切换时间类型或时间会触发onChange,并传递选择的时间范围和类型。
* 注:dateFrom和dateTo都是格式化后的字符串
* format: 格式化字符串格式,默认"YYYY-MM-DD HH:mm:ss",参看moment格式化支持的
*/
import React, { useState, useEffect } from 'react';
import { Radio, Space, DatePicker } from 'antd';
import moment from 'moment';
const dateForModel = (model, date = moment()) => {
if (!date) {
result = { dateFrom: null, dateTo: null };
return result;
}
let result = { dateFrom: null, dateTo: null };
switch (model) {
case 'day':
result = { dateFrom: date.clone().startOf('day'), dateTo: date.clone().endOf('day') };
break;
case 'week':
result = { dateFrom: date.clone().startOf('week'), dateTo: date.clone().endOf('week') };
break;
case 'month':
result = { dateFrom: date.clone().startOf('month'), dateTo: date.clone().endOf('month') };
break;
case 'quarter':
result = { dateFrom: date.clone().startOf('quarter'), dateTo: date.clone().endOf('quarter') };
break;
case 'year':
result = { dateFrom: date.clone().startOf('year'), dateTo: date.clone().endOf('year') };
break;
case 'all':
default:
result = { dateFrom: null, dateTo: null };
break;
}
return result;
};
const textForModel = (model) => {
switch (model) {
case 'day':
return '日';
case 'week':
return '周';
case 'month':
return '月';
case 'quarter':
return '季度';
case 'year':
return '年';
case 'all':
return '全部';
case 'custom':
return '自定义';
default:
return model;
}
};
const useDataGroup = (defaultValue = { dateFrom: null, dateTo: null }, defaultModel = 'all') => {
const [model, setModel] = useState(defaultModel);
const [value, setValue] = useState(defaultValue);
const updateValeu = (_value, _model) => {
setModel(_model);
setValue(_value);
};
return [value, model, updateValeu];
};
const defaultFormat = 'YYYY-MM-DD HH:mm:ss';
const defaultShowModels = ['all', 'week', 'month', 'quarter', 'year', 'custom'];
const DatePickerGroup = ({
onChange,
format = defaultFormat,
showModels = defaultShowModels,
defaultModel = 'all',
defaultDate,
value,
dateModel,
}) => {
const [model, setModel] = useState(defaultModel);
const [dateValue, setDateValue] = useState(() => dateForModel(defaultModel, defaultDate));
useEffect(() => {
if (value || dateModel) {
setModel(dateModel);
setDateValue(value);
}
}, [value, dateModel]);
// 切换类型
const changeModel = (e) => {
const _model = e.target.value;
const _dateValue = dateForModel(_model);
if (!value && !dateModel) {
setModel(_model);
setDateValue(_dateValue);
if (_model === 'custom') return;
}
const _dateFrom = _dateValue && _dateValue.dateFrom ? _dateValue.dateFrom.format(format) : '';
const _dateTo = _dateValue && _dateValue.dateTo ? _dateValue.dateTo.format(format) : '';
onChange &&
onChange(
{
dateFrom: _dateFrom,
dateTo: _dateTo,
},
_model,
);
};
// 切换时间
const changeDate = (date, dateString) => {
const _dateValue = dateForModel(model, date);
const _dateFrom = _dateValue && _dateValue.dateFrom ? _dateValue.dateFrom.format(format) : '';
const _dateTo = _dateValue && _dateValue.dateTo ? _dateValue.dateTo.format(format) : '';
if (!value && !dateModel) {
setDateValue(_dateValue);
}
onChange &&
onChange(
{
dateFrom: _dateFrom,
dateTo: _dateTo,
},
model,
);
};
// 切换范围时间
const changeRangeDate = (dates, dateStrings) => {
const _dateValue = { dateFrom: dates?.[0], dateTo: dates?.[1] };
const _dateFrom = _dateValue && _dateValue.dateFrom ? _dateValue.dateFrom.format(format) : '';
const _dateTo = _dateValue && _dateValue.dateTo ? _dateValue.dateTo.format(format) : '';
if (!value && !dateModel) {
setDateValue(_dateValue);
}
onChange &&
onChange(
{
dateFrom: _dateFrom,
dateTo: _dateTo,
},
model,
);
};
// 渲染时间选择器
const renderDatePicker = () => {
let _result = null;
switch (model) {
case 'day':
_result = <DatePicker onChange={changeDate} value={dateValue.dateFrom} />;
break;
case 'week':
_result = <DatePicker picker="week" onChange={changeDate} value={dateValue.dateFrom} />;
break;
case 'month':
_result = <DatePicker picker="month" onChange={changeDate} value={dateValue.dateFrom} />;
break;
case 'quarter':
_result = <DatePicker picker="quarter" onChange={changeDate} value={dateValue.dateFrom} />;
break;
case 'year':
_result = <DatePicker picker="year" onChange={changeDate} value={dateValue.dateFrom} />;
break;
case 'custom':
_result = (
<DatePicker.RangePicker
onChange={changeRangeDate}
value={[dateValue.dateFrom, dateValue.dateTo]}
/>
);
break;
case 'all':
default:
_result = null;
break;
}
return _result;
};
return (
<Space size={8}>
<Radio.Group value={model} onChange={changeModel} style={{ whiteSpace: 'nowrap' }}>
{showModels.map((item) => (
<Radio.Button value={item} key={item}>
{textForModel(item)}
</Radio.Button>
))}
</Radio.Group>
{renderDatePicker()}
</Space>
);
};
export { useDataGroup };
export default DatePickerGroup;
import React from 'react';
import BasicReport from '../index';
const Demo = () => {
return (
<div>
<h3>报表</h3>
<div style={{ height: '600px' }}>
<BasicReport
params={{
reportName: '订单合同',
}}
/>
</div>
</div>
);
};
export default Demo;
import React from 'react';
import ReportsManage from './ReportsManage/ReportsManage';
const BasicReport = (props) => {
return <ReportsManage {...props} />;
};
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