Commit 437dd7ca authored by 陈龙's avatar 陈龙

feat: 新增大量功能

parent 34c8f109
/*
** 自定义取色板
** 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(() => {
debugger;
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%;
}
}
}
......@@ -8,11 +8,15 @@ import React, { useEffect, useState } from 'react';
import { Form, Input, DatePicker, InputNumber, Space, Row, Col, Button, message } from 'antd';
import moment from 'moment';
import { submitReportData } from '../api/service/report';
import FileUpload from './Components/fileUpload/fileUpload';
import { reportService } from '../api';
// 类型
const USER_ID = window.globalConfig.userInfo.OID;
const DATE_PICKER_ARRAY = ['日期'];
const DATE_TIME_PICKER_ARRAY = ['日期时刻'];
const DATE_TYPE = ['日期', '日期时刻']; // 用来匹配是否需要转为日期对象;
const NUMBER_ARRAY = ['数值', '金额'];
const FILE_ARRAY = ['附件'];
// 形态对应组件
// 对应关系
......@@ -24,8 +28,9 @@ const NUMBER_ARRAY = ['数值', '金额'];
* @params: reportDetails 各个字段的配置列表
* data 与reportDetails对应的值
*/
const ReportEditForm = ({ reportDetails, reportData, onCancel, reportName }) => {
if (!reportData || Object.keys(reportData).length === 0) return <>未传递表单数据</>;
const ReportEditForm = ({ reportDetails, reportData, onCancel, reportName, modalType }) => {
// if (!reportData || Object.keys(reportData).length === 0) return <>未传递表单数据</>;
const [fileAlias, setFileAlias] = useState([]); // 附件需要单独处理提交的数据
const [form] = Form.useForm();
const formItemLayout = {
labelCol: {
......@@ -52,77 +57,135 @@ const ReportEditForm = ({ reportDetails, reportData, onCancel, reportName }) =>
return <DatePicker />;
} else if (NUMBER_ARRAY.includes(config.type)) {
return <InputNumber />;
} else if (FILE_ARRAY.includes(config.type)) {
return <FileUpload schema={{ renderTo: 'File' }} />;
} else {
return <Input />;
}
};
const submitReportForm = () => {
let _data = form.getFieldsValue();
console.log(_data);
// {
// "key": "string",
// "fieldName": "string",
// "fieldValue": "string"
// }
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');
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 (value !== _value) {
final.push({
key: reportData.Key,
fieldName: key,
fieldValue: _value,
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('系统故障,请找管理员查看故障!');
}
});
}
});
console.log(final);
// reportService.updateReportData()
submitReportData(
{
reportName: reportName,
userId: window.globalConfig.userInfo.OID,
},
final,
).then((res) => {
if (res.code === 0) {
message.success('保存成功!');
onCancel();
}
});
};
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>
<Form {...formItemLayout} form={form}>
<Row>
{reportDetails &&
reportDetails.map((config) => {
return (
<Col span={8}>
<Form.Item label={config.fieldAlias} name={config.fieldAlias}>
{componentMap(config)}
</Form.Item>
</Col>
);
})}
</Row>
<Row>
<Col span={24} style={{ textAlign: 'right' }}>
{/*<Form.Item style={{textAlign:'right'}}>*/}
<Button type={'primary'} onClick={submitReportForm}>
提交
</Button>
{/*</Form.Item>*/}
</Col>
</Row>
</Form>
<div style={{ position: 'relative' }}>
<div>
<Form {...formItemLayout} form={form}>
<Row style={{ overflowY: 'scroll', maxHeight: 'calc(100vh - 300px)' }}>
{reportDetails &&
reportDetails.map((config) => {
return (
<Col span={8} key={config.fieldAlias}>
<Form.Item
label={config.fieldAlias}
name={config.fieldAlias}
rules={[
{
required: config.isRequired,
message: `${config.fieldAlias}必填`,
},
]}
>
{componentMap(config)}
</Form.Item>
</Col>
);
})}
</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>
);
};
......
......@@ -12,62 +12,47 @@
* @params: <ReportManage
* params={{reportName,pageSize,filterFields,filterValues,state,customerState}}>
*
*
*
*
* reportName: 报表名称;
* pageSize: 按需配置,默认加载100;
* filterFields:
*
* ***************filterFields/filterValues多用于以组件的形式嵌入,将需要过滤的条件传入********************
*
*
* 需要默认加载的过滤条件的field,英文逗号分割,与filterValues一一对应;
* filterFields: 需要默认加载的过滤条件的field,英文逗号分割,与filterValues一一对应;
* filterValues:
*
*
*
* 需要默认加载的过滤条件的value,英文逗号分割,与filterFields一一对应;
* state: delete|edit|scan 各种权限;
*
*
*
* *************************************************************************************************
*
* customerState: ['filters','sortBtn','exportBtn','editBtn','deleteBtn','pagination'];
* state: delete|edit|scan 各种权限;
* customerState:
* ['filters','sortBtn','exportBtn','editBtn','deleteBtn','pagination'];
* sortFields:
* '排序字段1,排序字段2,排序字段3'
*
* ---------------------------------------权限过滤-----------------------------------------
*
* permissionType: 部门|站点|用户
* permissionField: 【字段名】
*
*
* sortFields: '排序字段1,排序字段2,排序字段3'
* ---------------------------------------------------------------------------------------
* @config:
* 【数值】 [prefix]_d%|0.00|_d%[suffix]|金额 = 前缀|精度|后缀|金额类的数据(千分位),可分别设置。
* 【标签】 split=,
*
*
* 分隔符。
* 【标签】 split=, 分隔符。
*
* 【功能】 功能配置框内,配置需要跳转功能所需参数,type、url是必须功能,需要type判断类型,需要通过url去解析加载组件。
* @type:
* 【文本】普通文本
* 【数值】数值类型的文本
* 【标签】文本渲染成标签,具有不同颜色;
* 【功能】“功能”会在当前页内去展示,会卸载掉列表页,加载功能组件。配置 type
* +
*
*
* url + 自定义字段 的配置项,自行解析加载即可;
* -------------------- 待需求提出后开发
* -----------------
*
*
* 【功能】“功能”会在当前页内去展示,会卸载掉列表页,加载功能组件。配置 type +
* url + 自定义字段 的配置项,自行解析加载即可;
* -------------------- 待需求提出后开发 -----------------
*
* 【链接】内链外链,点击可跳转;配置规则:配置链接即可;
*
* 【弹窗】modal弹窗弹出,弹窗内的具体业务自行配置;配置规则:[function_name];[...params];
*
*
*
* 【弹窗】modal弹窗弹出,弹窗内的具体业务自行配置;配置规则:[function_name];[...params];
*
* ------------------------------------------------------
* 【附件】
* @table:
* 表头:表头需要支持多级表头、合并;
* 列:列支持设置筛选;
......@@ -75,15 +60,9 @@
* @control:
* 固定筛选:拥有固定筛选框,根据配置显示可搜索字段;
*
*
*
*
* 可配置筛选框:根据字段配置,将字段设置成筛选条件,枚举出该字段所有值,提供用户进行选择,然后进行筛选;筛选框具体形态可根据配置字段来渲染;
* 导出功能:各类导出功能按钮;
*
*
*
*
* 时间筛选框:单表唯一;需要变更,支持多时间参数的筛选
* @state: 参考台账权限 delete 全部权限
* edit 除删除外的权限
......@@ -103,13 +82,20 @@ import {
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';
......@@ -135,6 +121,7 @@ import {
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用来分割
......@@ -144,6 +131,7 @@ const initDateModel = 'all';
let timer = null;
const PERMISSION = {
delete: [
'addBtn',
'filters',
'pagination',
// 操作条按钮
......@@ -154,6 +142,7 @@ const PERMISSION = {
'deleteBtn',
],
edit: [
'addBtn',
'filters',
'pagination',
'sortBtn',
......@@ -161,11 +150,22 @@ const PERMISSION = {
// 操作列
'editBtn',
],
scan: ['filters', 'pagination'],
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, customerState, sortFields } =
props.params;
const {
reportName,
pageSize,
filterFields,
filterValues,
state,
customerState,
sortFields,
permissionType,
permissionField,
} = props.params;
const permission =
customerState && isArray(customerState) ? customerState : PERMISSION[state || 'delete'];
const tableWrapperRef = useRef();
......@@ -174,9 +174,12 @@ const ReportsManage = (props) => {
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,
......@@ -191,14 +194,14 @@ const ReportsManage = (props) => {
_pagination.current = current;
_pagination.pageSize = size;
setPagination(_pagination);
getData(false, _pagination);
getData(_pagination);
},
onChange: (current, size) => {
let _pagination = { ...pagination };
_pagination.current = current;
_pagination.pageSize = size;
setPagination(_pagination);
getData(false, _pagination);
getData(_pagination);
},
});
const [controls, setControls] = useState([]); // 用来存储操作控件
......@@ -240,9 +243,10 @@ const ReportsManage = (props) => {
// const [detailsComponent, setDetailsComponent] = useState(<></>); // 详情组件
const [detailsComponentVisible, setDetailsComponentVisible] = useState(false); // 是否显示详情组件
const [editComponentVisible, setEditComponentVisible] = useState(false); // 是否显示编辑组件
const [modalType, setModalType] = useState('');
// const [editComponentVisible, setEditComponentVisible] = useState(false); // 是否显示编辑组件
const [currentData, setCurrentData] = useState({}); // 设置当前编辑数据
const [sroter, setSorter] = useState({});
const [sorterObject, setSorterObject] = useState({});
const [detailConfig, setDetailConfig] = useState({ url: '', type: '', params: {} });
const [controlsHeight, setControlsHeight] = useState(44);
const menu = () => {
......@@ -275,6 +279,20 @@ const ReportsManage = (props) => {
),
key: 'exportExcel',
},
{
label: (
<Button
size="middle"
loading={exportLoading}
type="text"
onClick={() => exportImage()}
icon={<ExportOutlined />}
>
导出JPG
</Button>
),
key: 'excelPdf',
},
];
return <Menu items={_item} />;
};
......@@ -315,8 +333,38 @@ const ReportsManage = (props) => {
setExportLoading(false);
});
};
const exportImage = () => {
exportJPG(
{ responseType: 'blob' },
{
reportName,
pageIndex: 1,
pageSize: 10,
sortFields: '',
content: '',
filters: [
{
fieldAlias: '',
fieldValue: '',
},
],
},
).then((res) => {
if (res && res.code === -1) return message.error(res.msg);
const url = window.URL.createObjectURL(
new Blob([res], { type: 'application/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(false, pagination);
getData(pagination);
};
const controlSelectChange = (fieldAlias, e) => {
let _filterArray = { ...filterObject };
......@@ -326,9 +374,13 @@ const ReportsManage = (props) => {
const searchInputChange = (e) => {
setSearchContent(e.target.value);
};
const setConfig = (config, summary, data) => {
/* const setConfig = (config, summary) => {
getControlsBarConfig(config);
getTableSummaryConfig(config, summary);
getTableLevel(config);
};*/
const setConfig = (config) => {
getControlsBarConfig(config);
getTableSummaryConfig(config, summary);
getTableLevel(config);
};
const addFilterAndSearchParams = (data) => {
......@@ -373,6 +425,17 @@ const ReportsManage = (props) => {
});
});
}
// 表格上的自定义排序的按钮
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;
......@@ -426,7 +489,7 @@ const ReportsManage = (props) => {
});
return _merge;
};
const getData = (isInit, pagination) => {
const getData = (pagination) => {
setTableLoading(true);
const { pageSize, current } = pagination;
// 搜索条件附加到params
......@@ -434,6 +497,7 @@ const ReportsManage = (props) => {
reportName: reportName,
pageIndex: current,
pageSize: pageSize,
userId: USER_ID,
});
// sortFields
reportService
......@@ -444,8 +508,9 @@ const ReportsManage = (props) => {
let _statisticalValues = res.data.statisticalValues;
let _tableData = res.data.data.list;
let _sortString = res.data.sortString;
if (isInit) {
setConfig(_reportDetails, _statisticalValues, _tableData);
if (isInit || !summaryArray.length) {
getTableSummaryConfig(_reportDetails, _statisticalValues);
setIsInit(false);
}
getTableHeaderConfig(_reportDetails, _tableData);
let _pagination = { ...pagination };
......@@ -471,6 +536,7 @@ const ReportsManage = (props) => {
setPagination(_pagination);
setTableData([]);
}
if (isInit) setIsInit(false);
setTableLoading(false);
})
.catch((err) => {
......@@ -478,6 +544,17 @@ const ReportsManage = (props) => {
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);
}
});
};
/** @description: 在配置项中,isFilter: true 用来渲染控制框;filterRule: 下拉/文本/多选 */
const getControlsBarConfig = (config) => {
let _data = config.filter((item) => item.isFilter);
......@@ -488,11 +565,12 @@ const ReportsManage = (props) => {
setSearchPlaceholder(_searchPlaceholder);
let _controls = _data.filter((item) => ControlsType.includes(item.filterRule));
setControls(_controls);
handleDate(_controls);
handleControls(_controls); // 处理控制条,设定默认值
handleDate(_controls); // 处理日期, 设定默认值
};
const getTableHeaderConfig = (config, data) => {
setTableStruct(config);
setColumns(returnColumn(config, data));
// setTableStruct(config);
// setColumns(returnColumn(config, data));
setAllSortFields(returnSortFields(config));
};
const getTableSummaryConfig = (config, summary) => {
......@@ -601,7 +679,7 @@ const ReportsManage = (props) => {
}
let _tempObj = {};
let _fieldAliasArray = handleDataToGetRowSpanArray(_config, data); // 需要向下合并的字段
let _fieldAliasArray = data ? handleDataToGetRowSpanArray(_config, data) : false; // 需要向下合并的字段
_config.forEach((item) => {
let _item = {
title: item.fieldAlias,
......@@ -612,7 +690,7 @@ const ReportsManage = (props) => {
// console.log('Record: ', record); // record是这条记录,index是rowIndex
// 1. 如果该字段是需要向下合并的,则进入判断
let _obj = {};
if (_fieldAliasArray[item.fieldAlias]) {
if (_fieldAliasArray && _fieldAliasArray[item.fieldAlias]) {
_obj.rowSpan = _fieldAliasArray[item.fieldAlias][rowIndex];
}
return _obj;
......@@ -639,7 +717,7 @@ const ReportsManage = (props) => {
return mapHandleType(item.type)(item, value || '', record, ...rest);
},
};
_item.width = item.columnWidth || 200; // 列宽,不设置时默认给200;
_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);
......@@ -682,26 +760,68 @@ const ReportsManage = (props) => {
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') ? (
<FormOutlined
className={style.editButton}
onClick={() => {
setEditComponentVisible(true);
// setEditComponentVisible(true);
setModalType('编辑');
setCurrentData(record);
}}
/>
) : (
''
)}
{/* {
permission.includes('deleteBtn') ?
<DeleteOutlined disabled className={style.deleteButton} onClick={() => {
Modal.confirm({
content: '你确定要删除改数据吗?',
onOK: () => message.error('您无法删除数据!'),
});
}} /> : ''
}*/}
{permission.includes('deleteBtn') ? (
<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('删除成功!');
getData(pagination);
}
});
},
});
}}
/>
) : (
''
)}
</Space>
);
},
......@@ -773,15 +893,31 @@ const ReportsManage = (props) => {
}
});
};
/** @description: 判断是否存在【时间】类型的选择,并返回组件 */
/** @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('|') || [];
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) {
......@@ -789,22 +925,47 @@ const ReportsManage = (props) => {
} 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:
_configItems.find((item) => item.includes('defaultModel='))?.replace('defaultModel=', '') ||
'year',
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 ? '取消' : '关注'}失败!`);
}
});
};
useEffect(() => {
getData(true, pagination);
getConfigs();
}, []);
useEffect(() => {
if (firstToGetData) getData(pagination);
}, [firstToGetData]);
useEffect(() => {
if (tableHeaderLevel) setTableHeight();
}, [tableHeaderLevel]);
useEffect(() => {
getData(false, pagination);
}, [timeFrom, timeTo, filterObject]);
if (!isInit) getData(pagination);
}, [timeFrom, timeTo, sorterObject, filterObject]);
useEffect(() => {
function getRefHeight() {
if (timer) clearTimeout(timer);
......@@ -832,94 +993,112 @@ const ReportsManage = (props) => {
{/* 为方便阅读,分开两部分代码 */}
{!detailsComponentVisible ? (
<div className={style.contentWrapper}>
{state !== 'scan' ? (
<Row className={style.controlRow} ref={controlRef}>
<Space style={{ flex: 1 }} size={8} wrap={true}>
{/*时间搜索控件,确保时间搜索控件在第一个,单独匹配*/}
{hasDatePicker &&
defaultDateConfig.defaultDate !== null &&
permission.includes('filters') ? (
<DatePickerGroup
showModels={['all', 'month', 'quarter', 'year', 'custom']}
onChange={changeDate}
format={dateFormat}
defaultModel={defaultDateConfig.defaultModel}
defaultDate={defaultDateConfig.defaultDate}
<Row className={style.controlRow} ref={controlRef}>
<Space style={{ flex: 1 }} size={8} wrap={true}>
{/*时间搜索控件,确保时间搜索控件在第一个,单独匹配*/}
{hasDatePicker &&
defaultDateConfig.defaultDate !== null &&
permission.includes('filters') ? (
<DatePickerGroup
showModels={['all', 'month', 'quarter', 'year', 'custom']}
onChange={changeDate}
format={dateFormat}
defaultModel={defaultDateConfig.defaultModel}
defaultDate={defaultDateConfig.defaultDate}
/>
) : (
''
)}
{controls && controls.length && permission.includes('filters')
? controls
.filter((control) => ['下拉', '多选'].includes(control.filterRule))
.map((control) => {
return (
<Form.Item label={control.fieldAlias} key={control.fieldAlias}>
<ReturnControlComponent
style={{ width: 240 }}
type={control.filterRule}
reportName={reportName}
fieldAlias={control.fieldAlias}
configItems={control.configItems}
onChange={(e) => controlSelectChange(control.fieldAlias, e)}
/>
</Form.Item>
);
})
: ''}
{permission.includes('filters') ? (
<Form.Item label="快速索引" key={'快速搜索控件'}>
<ReturnControlComponent
placeholder={`请输入${
searchPlaceholder.length ? searchPlaceholder.join(',') : '关键字'
}搜索`}
style={{ width: 240 }}
type={'文本'}
onChange={(e) => {
searchInputChange(e);
}}
onSearch={searchData}
/>
) : (
''
)}
{controls && controls.length && permission.includes('filters')
? controls
.filter((control) => ['下拉', '多选'].includes(control.filterRule))
.map((control) => {
return (
<Form.Item label={control.fieldAlias} key={control.fieldAlias}>
<ReturnControlComponent
style={{ width: 240 }}
type={control.filterRule}
reportName={reportName}
fieldAlias={control.fieldAlias}
configItems={control.configItems}
onChange={(e) => controlSelectChange(control.fieldAlias, e)}
/>
</Form.Item>
);
})
: ''}
{permission.includes('filters') ? (
<Form.Item label="快速索引" key={'快速搜索控件'}>
<ReturnControlComponent
placeholder={`请输入${
searchPlaceholder.length ? searchPlaceholder.join(',') : '关键字'
}搜索`}
style={{ width: 240 }}
type={'文本'}
onChange={(e) => {
searchInputChange(e);
}}
onSearch={searchData}
/>
</Form.Item>
) : (
''
)}
</Space>
{permission.includes('sortBtn') ? (
<div style={{ width: 200, textAlign: 'end' }}>
<Space size={8} nowrap={true}>
{sortModalVisible && permission.includes('sortBtn') ? (
<Form.Item>
<Button
type={'primary'}
title={'自定义排序'}
icon={<SortAscendingOutlined />}
onClick={() => setModalVisible(true)}
>
排序
</Button>
</Form.Item>
) : (
''
)}
{permission.includes('exportBtn') ? (
<Form.Item>
<Dropdown.Button style={{ float: 'right' }} overlay={menu}>
导出
</Dropdown.Button>
</Form.Item>
) : (
''
)}
</Space>
</div>
</Form.Item>
) : (
''
)}
</Row>
) : (
''
)}
</Space>
{permission.includes('sortBtn') || permission.includes('exportBtn') ? (
<div style={{ width: 270, textAlign: 'end' }}>
<Space size={8} nowrap>
{permission.includes('addBtn') ? (
<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)` }}
......@@ -932,8 +1111,7 @@ const ReportsManage = (props) => {
dataSource={tableData}
columns={columns}
onChange={(pagination, filters, sorter, extra) => {
console.log(sorter);
setSorter(sorter);
setSorterObject(sorter);
}}
pagination={permission.includes('pagination') ? pagination : false}
// 237是内置图片高度
......@@ -1058,7 +1236,7 @@ const ReportsManage = (props) => {
<Button
type={'primary'}
onClick={() => {
saveReportListSortFields(() => getData(false, pagination));
saveReportListSortFields(() => getData(pagination));
setModalVisible(false);
}}
>
......@@ -1114,19 +1292,21 @@ const ReportsManage = (props) => {
</Modal>
{/* 编辑表单 */}
<Modal
title={'编辑报表信息'}
visible={editComponentVisible}
title={`${modalType}报表信息`}
visible={!!modalType}
width={'80%'}
footer={null}
// visible={true}
onCancel={() => setEditComponentVisible(false)}
destroyOnClose
onCancel={() => setModalType('')}
>
<ReportEditForm
modalType={modalType}
reportDetails={tableStruct}
reportData={currentData}
onCancel={() => {
setEditComponentVisible(false);
getData(false, pagination);
setModalType('');
getData(pagination);
}}
reportName={reportName}
/>
......
......@@ -15,6 +15,8 @@ import {
MenuOutlined,
SettingOutlined,
LeftOutlined,
ExportOutlined,
ImportOutlined,
} from '@ant-design/icons';
import {
Button,
......@@ -30,6 +32,8 @@ import {
Space,
Switch,
Tooltip,
Table,
Upload,
} from 'antd';
import { SketchPicker } from 'react-color';
import { arrayMoveImmutable } from 'array-move';
......@@ -38,16 +42,23 @@ import { reportService } from '../api';
import style from './ReportsSetting.less';
import classname from 'classnames';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import { addReportDetailInfoIndex } from '../api/service/report';
import {
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 typeArray = ['文本', '标签', '数值', '链接', '功能', '弹窗', '日期', '日期时间', '附件'];
const filterRule = ['文本', '下拉', '多选', '日期'];
const publicSplit = '&split;';
const USER_ID = window.globalConfig.userInfo.OID;
const ReportsSetting = () => {
// 报表列表
const layout = {
......@@ -120,6 +131,7 @@ const ReportsSetting = () => {
clickReport(record.id);
}}
/>
<ExportOutlined title={'导出配置'} onClick={() => exportConfigs(record)} />
<DeleteOutlined
title={'删除报表'}
style={{ color: 'red' }}
......@@ -161,8 +173,41 @@ const ReportsSetting = () => {
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(
......@@ -217,33 +262,19 @@ const ReportsSetting = () => {
dataIndex: 'fieldName',
key: 'fieldName',
},
{
title: '别名',
dataIndex: 'fieldAlias',
key: 'fieldAlias',
render: (text, record, index) => {
return isEditing ? (
<Form.Item
name={[index, 'fieldAlias']}
rules={[
{
required: true,
message: '请输入字段别名',
/* {
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;
},
]}
>
<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',
......@@ -429,15 +460,58 @@ const ReportsSetting = () => {
},
},
{
title: '操作',
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 (
<DeleteOutlined onClick={() => deleteReportDetail(record.id)} style={{ color: 'red' }} />
<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;
......@@ -586,7 +660,9 @@ const ReportsSetting = () => {
if (callback) callback();
});
};
// 切换字段的同时,如果数据有修改,那需要保存之前的数据 edit by ChenLong 2022年8月29日
const changeField = (record) => {
// submitReportDetails('changeField').then(res => {
setActiveID(record.id);
form.setFieldsValue(setDefaultValue(record));
// 手动设置文字的颜色
......@@ -595,6 +671,7 @@ const ReportsSetting = () => {
setNumberColorPickerArray(record.numericalConfigs);
setLabelColorPickerArray(record.labelConfigs);
setCurrentField(setDefaultValue(record)); // 为了确保前后数据统一
// });
};
const setDefaultValue = (record) => {
if (!record) return {};
......@@ -618,9 +695,10 @@ const ReportsSetting = () => {
if (!_record.alignType) _record.alignType = 'left';
return _record;
};
const submitReportDetails = () => {
// @params: {type:String} 不传入type,默认提交表单、关闭表单;传入type时
const submitReportDetails = (type) => {
// 1.表单内的值;2.标签值
form.validateFields().then((value) => {
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);
......@@ -670,7 +748,9 @@ const ReportsSetting = () => {
message.error(res.msg);
}
setSubmitFieldLoading(false);
setDetailVisible(false);
if (!type) {
setDetailVisible(false);
}
clickReport(currentReport.id);
});
});
......@@ -763,6 +843,7 @@ const ReportsSetting = () => {
// setIsEditing(true);
};
const submitDetailFromTable = () => {
setDetailTableLoading(true);
editDetailForm.validateFields().then((res) => {
// 去除掉标签、数字区间内的id
let _detailData = detailData.map((item) => {
......@@ -779,17 +860,22 @@ const ReportsSetting = () => {
});
return _item;
});
reportService.addReportDetailInfo({ reportDetails: _detailData }).then((res) => {
if (res.code === 0) {
message.success('提交成功!');
// setIsEditing(false);
} else {
message.error(res.msg);
}
setSubmitFieldLoading(false);
setDetailVisible(false);
clickReport(currentReport.id);
});
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 = () => {
......@@ -882,7 +968,11 @@ const ReportsSetting = () => {
}
let { data, callback, index, key } = currentColorPicker;
let _data = [...data];
_data[index][key] = `rgba(${_rgb.r},${_rgb.g},${_rgb.b},${_rgb.a})`;
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 = () => {
......@@ -890,67 +980,6 @@ const ReportsSetting = () => {
if (key === 'init') return 'rgba(0,0,0,.65)';
return data[index][key];
};
// 颜色卡片组件
//[
// "rgb(255, 255, 255)",
// "rgb(214, 229, 255)",
// "rgb(214, 241, 255)",
// "rgb(211, 243, 226)",
// "rgb(255, 220, 219)",
// "rgb(255, 236, 219)",
// "rgb(255, 245, 204)",
// "rgb(251, 219, 255)",
// "rgb(255, 219, 234)",
// "rgb(220, 223, 228)",
// "rgb(173, 203, 255)",
// "rgb(173, 228, 255)",
// "rgb(172, 226, 197)",
// "rgb(255, 181, 179)",
// "rgb(255, 206, 163)",
// "rgb(255, 234, 153)",
// "rgb(231, 180, 255)",
// "rgb(255, 179, 220)",
// "rgb(129, 134, 143)",
// "rgb(41, 114, 244)",
// "rgb(0, 163, 245)",
// "rgb(69, 176, 118)",
// "rgb(222, 60, 54)",
// "rgb(248, 136, 37)",
// "rgb(231, 180, 255)",
// "rgb(154, 56, 215)",
// "rgb(221, 64, 151)"
// ]
const ColorBoard = ({ value, onChange }) => {
return (
<>
<div
style={{
padding: 4,
display: 'inline-flex',
borderRadius: '2px',
border: '1px solid rgba(0,0,0,.35)',
}}
>
<div
style={{
width: '36px',
height: '14px',
borderRadius: '2px',
border: '1px solid rgba(0,0,0,.35)',
backgroundColor: value || 'rgba(0,0,0,.65)',
}}
onClick={(e) => {
clickColorCard(e);
setCurrentColorPicker({
key: 'wordColor',
callback: onChange,
});
}}
/>
</div>
</>
);
};
// 拖拽
const DragHandle = SortableHandle(() => (
<MenuOutlined
......@@ -993,6 +1022,42 @@ const ReportsSetting = () => {
_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();
}, []);
......@@ -1026,42 +1091,32 @@ const ReportsSetting = () => {
top: colorCardPosition.top,
}}
>
<SketchPicker
width={240}
presetColors={[
'rgb(255, 255, 255)',
'rgb(214, 229, 255)',
'rgb(214, 241, 255)',
'rgb(211, 243, 226)',
'rgb(255, 220, 219)',
'rgb(255, 236, 219)',
'rgb(255, 245, 204)',
'rgb(251, 219, 255)',
'rgb(255, 219, 234)',
'rgb(220, 223, 228)',
'rgb(173, 203, 255)',
'rgb(173, 228, 255)',
'rgb(172, 226, 197)',
'rgb(255, 181, 179)',
'rgb(255, 206, 163)',
'rgb(255, 234, 153)',
'rgb(231, 180, 255)',
'rgb(255, 179, 220)',
'rgb(129, 134, 143)',
'rgb(41, 114, 244)',
'rgb(0, 163, 245)',
'rgb(69, 176, 118)',
'rgb(222, 60, 54)',
'rgb(248, 136, 37)',
'rgb(216,180,255)',
'rgb(154, 56, 215)',
'rgb(221, 64, 151)',
]}
color={
currentColorPicker.key === 'wordColor' ? backgroundColor : returnCurrentColor()
}
onChange={(e) => changeBackgroundColor(e)}
/>
{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
......@@ -1199,7 +1254,7 @@ const ReportsSetting = () => {
)}
key={item.id}
>
{item.fieldName}
{item.fieldAlias}
</li>
))}
</ul>
......@@ -1228,13 +1283,18 @@ const ReportsSetting = () => {
>
<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={'fieldAlias'}
rules={[
{
required: true,
message: '别名必填且不可重复',
},
]}
>
<Input style={{ width: wordInputWidth }} />
</Form.Item>
<Form.Item
label={'表头级数'}
name={'level'}
......@@ -1314,13 +1374,6 @@ const ReportsSetting = () => {
/>
</div>
</Form.Item>
{/* <Form.Item label={'对齐方式'} name={'alignType'}>
<Radio.Group>
<Radio value={'left'}>左</Radio>
<Radio value={'center'}>中</Radio>
<Radio value={'right'}>右</Radio>
</Radio.Group>
</Form.Item>*/}
{watchType === '数值' ? (
<Form.Item label={'区间设置'}>
{numberColorPickerArray.map((item, index) => {
......@@ -1535,7 +1588,7 @@ const ReportsSetting = () => {
data: labelColorPickerArray,
callback: setLabelColorPickerArray,
index,
key: 'color',
key: 'tagColor',
});
}}
/>
......@@ -1623,7 +1676,9 @@ const ReportsSetting = () => {
/>
</Form.Item>
<Form.Item>
<Button onClick={showPatchSubModal}>附加子表</Button>
<Button style={{ marginRight: 8 }} onClick={showPatchSubModal}>
附加子表
</Button>
<Button onClick={addVirtualColumn}>添加虚拟字段</Button>
</Form.Item>
</div>
......@@ -1641,6 +1696,7 @@ const ReportsSetting = () => {
</Button>
<Button
type={'primary'}
loading={detailTableLoading}
style={{ marginRight: 8 }}
onClick={submitDetailFromTable}
>
......@@ -1661,10 +1717,6 @@ const ReportsSetting = () => {
)}
</Form.Item>
</Form>
{/* <Button type={'primary'} onClick={() => {
setDetailTableVisible(false);
// setIsEditing(false);
}}>返回</Button>*/}
</Row>
<div className={style.tableContent}>
<Form className={style.tableForm} form={editDetailForm}>
......@@ -1672,10 +1724,102 @@ const ReportsSetting = () => {
loading={detailTableLoading}
pagination={false}
bordered
scroll={{ y: 'calc(100% - 40px)' }}
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
? ''
......@@ -1689,6 +1833,7 @@ const ReportsSetting = () => {
onRow={(record) => {
return {
onDoubleClick: (e) => {
return false;
if (
e.target.cellIndex === detailColumns.length - 1 ||
e.target.cellIndex === void 0
......@@ -1794,6 +1939,7 @@ const ReportsSetting = () => {
<Form.Item>
<Button
type={'primary'}
style={{ marginRight: 8 }}
onClick={() =>
openCreateModal(() => {
setCurrentReport({});
......@@ -1809,6 +1955,11 @@ const ReportsSetting = () => {
>
新增
</Button>
<Upload {...uploadProps}>
<Button size="middle" icon={<ImportOutlined />}>
导入
</Button>
</Upload>
</Form.Item>
</Form>
</Row>
......
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)',
];
export { tagColors, tagBackgroundColors };
......@@ -8,7 +8,8 @@ import { hasMoney, isNumber } from './utils';
* @params: config下的数值的configRule结构如下,[{最大值: 10,最小值: 0,颜色:'#AAAAAA'}];
* @business: configRule有值,则按照configRule设置;没有,按照color设置; 有最大值,无最小值;['', 1]
* 有最大值,有最小值;[2,
* 10]
*
* 10]
* 有最小值,无最大值;[11,'']
*/
// 链接 功能 弹窗功能待提出需求
......@@ -76,7 +77,8 @@ const returnOpacity = (rgba) => {
};
export const handleNumber = (config, number) => {
let _color = '';
if (number) number = Number(number); // 当设置精度后,会被转成字符串
let _number;
if (number) _number = Number(number); // 当设置精度后,会被转成字符串
if (config.numericalConfigs && config.numericalConfigs.length) {
config.numericalConfigs.forEach((item) => {
// 接口对于数值类型的返回为null
......@@ -84,11 +86,11 @@ export const handleNumber = (config, number) => {
let _max = item.maxValue || '';
let _min = item.minValue || '';
if (_max !== '' && _min === '') {
_color = number <= Number(_max) ? '' : item.color;
_color = _number <= Number(_max) ? '' : item.color;
} else if (_min !== '' && _max === '') {
_color = number > Number(_min) ? item.color : '';
_color = _number > Number(_min) ? item.color : '';
} else {
_color = number > Number(_min) && number <= Number(_max) ? item.color : '';
_color = _number > Number(_min) && _number <= Number(_max) ? item.color : '';
}
}
});
......@@ -144,15 +146,23 @@ export const handleTag = (config, text) => {
}
_color = config.color || 'rgba(0,0,0,.85)';
// String(text)为了解决可能存在数值类型的数据需要设置成标签的需求
// return String(text).split(_configMap['分隔符']).map(item => <Tag color={_map[item]}>{item}</Tag>);
return String(text)
.split(_configMap['分隔符'])
.map((item) => <Tag color={_map[item]}>{item}</Tag>);
/* return String(text).split(_configMap['分隔符']).map(item => <Tag style={{
background: _map[item] || _color,
border: `1px solid ${_map[item]}`,
borderRadius: 4,
color: `rgba(0, 0, 0, .65)`,
}}>{item}</Tag>);*/
.map((item) => (
<Tag
style={{
background: _map[item] || _color,
border: `1px solid ${_map[item] || _color}`,
borderRadius: 2,
color: _map[item]
? tagColors[tagBackgroundColors.findIndex((c) => c === _map[item])]
: `rgba(0, 0, 0, .65)`,
}}
>
{item}
</Tag>
));
};
export const handleText = (config, text) => {
return <span style={{ color: config.color || 'rgba(0,0,0,.85)' }}>{text}</span>;
......
......@@ -24,7 +24,9 @@ const hasMoney = (configItems) => {
const returnHandledNumber = (configItems, num) => {
// 精度、前缀、后缀、倍率
// $_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 = '';*/
......@@ -58,8 +60,10 @@ const returnHandledNumber = (configItems, num) => {
* @author: ChenLong
* @params:
* configItems 报表字段的配置 例如 defaultValue=智慧水务
*
* defaultDateModel=customer|defaultDateValue=2022-01-01,2022-12-31;
* keysArray
*
* 所需要返回的值的key的集合,比如你需要获取configItems中的’defaultValue‘,那么keysArray=['defaultValue'];
* @returns:
* defaultValue 通用参数 默认值
......@@ -74,6 +78,42 @@ const returnDefaultValueOrConfigs = (configItems = '', keysArray = []) => {
});
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) {
debugger;
// 文件名含有特殊字符 提示不能上传 {+,:/?#[]@!$&\\*+;=}
// 规则对象(flag)
var flag = !special
? new RegExp("[`~!@#$^&*=|{}':;',\\[\\]/?~!@#¥&*——|{}【】‘;:”“'。,、?]")
: new RegExp("[`~!@#$^&*=|{}':;',[\\]?~!@#¥&*——|{}【】‘;:”“'。,、?]");
if (flag.test(file.name)) {
return {
type: 'error',
content: `文件名格式错误,请检查文件名是否含有特殊字符${"~!@#$^&*=|{}':;',\\[\\]/?~!@#¥&*——|{}【】‘;:”“'。,、?"}`,
};
}
return {
type: 'success',
content: `上传成功!`,
};
}
export {
isObject,
isString,
......@@ -82,4 +122,6 @@ export {
isArray,
returnHandledNumber,
returnDefaultValueOrConfigs,
downloadFunc,
filenameVerification,
};
......@@ -24,6 +24,13 @@ export const API = {
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`,
};
const reportService = {
getReportInfo: {
......@@ -96,6 +103,41 @@ const reportService = {
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,
},
};
export const submitReportData = (params, data) =>
request({
......@@ -112,10 +154,33 @@ export const exportAccountData = (options, params, data) =>
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;
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