Commit fb2e80f0 authored by 陈龙's avatar 陈龙

feat: 新增BasicReport

parent 8b5c5f14
Pipeline #57933 failed with stages
in 13 seconds
/*
** 报表的编辑、新增表单
** create by ChenLong on 2022/8/10
** 功能路径:src\pages\product\ReportsManage\ReportEditForm.js
** 菜单参数列表:*变量名*(变量说明,数据类型,是否必填,取值范围)
**/
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';
// 类型
const DATE_PICKER_ARRAY = ['日期'];
const DATE_TIME_PICKER_ARRAY = ['日期时刻'];
const DATE_TYPE = ['日期', '日期时刻']; // 用来匹配是否需要转为日期对象;
const NUMBER_ARRAY = ['数值', '金额'];
// 形态对应组件
// 对应关系
/**
* @Description: 函数描述
* @Date: 2022/8/10
* @Author: ChenLong
* @Params: reportDetails 各个字段的配置列表
* data 与reportDetails对应的值
* */
const ReportEditForm = ({ reportDetails, reportData, onCancel, reportName }) => {
if (!reportData || Object.keys(reportData).length === 0) return <>未传递表单数据</>;
const [form] = Form.useForm();
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] = moment(data[item.fieldAlias]);
}
});
return _data;
};
const componentMap = (config) => {
if (DATE_TIME_PICKER_ARRAY.includes(config.type)) {
return <DatePicker showTime />;
} else if (DATE_PICKER_ARRAY.includes(config.type)) {
return <DatePicker />;
} else if (NUMBER_ARRAY.includes(config.type)) {
return <InputNumber />;
} 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');
}
if (value !== _value) {
final.push({
key: reportData.Key,
fieldName: key,
fieldValue: _value,
});
}
});
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 (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>
);
};
export default ReportEditForm;
import { Modal, notification } from 'antd';
import { instanceRequest, service } from '@wisdom-utils/utils';
import AppService from './service/base';
import notificationService from './service/notification';
import AccountService from './service/account';
import AssetService from './service/asset';
import ReportService from './service/report';
// import WorkflowService from './service/workflow';
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 appService = service(AppService);
const noticeService = service(notificationService);
const accountService = service(AccountService);
const assetService = service(AssetService);
// const workflowService = service(WorkflowService);
const reportService = service(ReportService);
export {
appService,
noticeService,
accountService,
assetService,
// workflowService
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_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`, // 更新报表数据
};
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,
},
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,
},
};
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 addReportDetailInfoIndex = (data) => request({
url: API.ADD_REPORT_DETAIL_INFO_INDEX,
method: 'post',
data,
});
export default reportService;
// 文本/下拉/多选/时间
/**
* @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 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 }) => {
const [value,setValue] = useState('');
const [options, setOptions] = useState([]);
const defaultConfigs = returnDefaultValueOrConfigs(configItems, ['defaultValue']);
const { defaultValue } = defaultConfigs;
const getData = () => {
reportService.getReportFilterValue({
reportName,
fieldAlias,
}).then(res => {
if (res.code === 0) {
setOptions(res.data);
}
}).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.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,
}) => {
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} />;
break;
default:
break;
}
return _component;
};
export default ReturnControlComponent;
/**
* 时间组选择:支持 全部,日,月,年,自定义 类型
* 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;
/**
** 用来动态加载配置的组件功能函数
** 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} />;
};
export default DetailsComponent;
import AccountPermissionScan from '../../AccountManage/AccountPermissionScan';
import pic from '../assets/pic.webp';
const extraComponents = {
renderAccount: (props) => {
return <AccountPermissionScan accountName={props.accountName} />;
},
returnImage: (data) => {
return <img width={200} height={200} src={pic} alt={data.alt} />;
},
default: () => <span>若需要使用弹窗功能,请开发或配置功能函数</span>
};
export default extraComponents;
/**
** 轻量化报表通用配置页面
** create by ChenLong on 2022/6/22
** 功能路径:src\pages\product\ReportsManage\ReportsManage.js
** 菜单参数列表:*变量名*(变量说明,数据类型,是否必填,取值范围)
* @Changelog:
* editComponentVisible && detailsComponentVisible 共同作用组件的显示
*
**/
/**
* @Description: 功能描述:参考台账概念,重新定义
* @Tips: 1. 如果需要对字段进行处理,增加功能之类的。需要提前确定返回值的类型.
* 2. 如果要用customerState来控制页面按钮等,需要按照给定的权限值进行配置
* @Params: <ReportManage params={{reportName,pageSize,filterFields,filterValues,state,customerState}}>
* reportName: 报表名称;
* pageSize: 按需配置,默认加载100;
* filterFields: 需要默认加载的过滤条件的field,英文逗号分割,与filterValues一一对应;
* filterValues: 需要默认加载的过滤条件的value,英文逗号分割,与filterFields一一对应;
* state: delete|edit|scan 各种权限;
* customerState: ['filters','sortBtn','exportBtn','editBtn','deleteBtn','pagination'];
* sortFields: '排序字段1,排序字段2,排序字段3'
* @Config:
* 【数值】 [prefix]_d%|0.00|_d%[suffix]|金额 = 前缀|精度|后缀|金额类的数据(千分位),可分别设置。
* 【标签】 split=, 分隔符。
* 【功能】 功能配置框内,配置需要跳转功能所需参数,type、url是必须功能,需要type判断类型,需要通过url去解析加载组件。
* @Type:
* 【文本】普通文本
* 【数值】数值类型的文本
* 【标签】文本渲染成标签,具有不同颜色;
* 【功能】“功能”会在当前页内去展示,会卸载掉列表页,加载功能组件。配置 type + url + 自定义字段 的配置项,自行解析加载即可;
* -------------------- 待需求提出后开发 -----------------
* 【链接】内链外链,点击可跳转;配置规则:配置链接即可;
* 【弹窗】modal弹窗弹出,弹窗内的具体业务自行配置;配置规则:[function_name];[...params];
* ------------------------------------------------------
* @Table:
* 表头:表头需要支持多级表头、合并;
* 列:列支持设置筛选;
* 固定行、固定列:可根据配置生成固定行、列;
* @Control:
* 固定筛选:拥有固定筛选框,根据配置显示可搜索字段;
* 可配置筛选框:根据字段配置,将字段设置成筛选条件,枚举出该字段所有值,提供用户进行选择,然后进行筛选;筛选框具体形态可根据配置字段来渲染;
* 导出功能:各类导出功能按钮;
* 时间筛选框:单表唯一;需要变更,支持多时间参数的筛选
* @State: 参考台账权限
* delete 全部权限
* edit 除删除外的权限
* scan 查看权限
* */
import React, { useState, useEffect, useRef } from 'react';
import { Row, Button, Tag, message, Form, Space, Modal, Select, Table, Dropdown, Menu, Spin } from 'antd';
import {
SortAscendingOutlined, MinusCircleOutlined, ExportOutlined, FormOutlined,
} from '@ant-design/icons';
import BasicTable from '@wisdom-components/basictable';
import ReturnControlComponent from './components/Control';
import { reportService } from './api/index';
import style from './index.less';
import { exportAccountData } from './api/service/report';
import extraComponents from './extra/extraComponents';
import DatePickerGroup from './components/DatePickerGroup';
import moment from 'moment';
import DetailsComponent from './extra/detailsComponent';
import {
handleNumber,
handleTag,
handleText,
handleLink,
handleWidget,
handleModal,
handleDateString,
handleDateTimeString, handlePageSize, handleSortFields,
} from './utils/handlers';
import { hasMoney, isArray, isNumber, isString, returnHandledNumber } from './utils/utils';
import { connect } from 'react-redux';
import ReportEditForm from './ReportEditForm';
const ControlsType = ['下拉', '多选', '日期'];
const fieldSplitStr = '-'; // fieldGroup用来分割
const { Option } = Select;
const dateFormat = 'YYYY-MM-DD'; // 日期格式化
const initDateModel = 'all';
let timer = null;
const PERMISSION = {
delete: [
'filters',
'pagination',
// 操作条按钮
'sortBtn',
'exportBtn',
// 操作列
'editBtn',
'deleteBtn',
],
edit: [
'filters',
'pagination',
'sortBtn',
'exportBtn',
// 操作列
'editBtn',
],
scan: [
'filters',
'pagination',
],
};
const BasicReport = (props) => {
const { reportName, pageSize, filterFields, filterValues, state, customerState, sortFields } = props.params;
const permission = customerState && isArray(customerState) ? customerState : PERMISSION[(state || 'delete')];
const tableWrapperRef = useRef();
const controlRef = useRef();
if (!reportName) return <div className={style.lackParams}>
未配置reportName,请完善配置并重新登陆后查看页面!
</div>;
const [tableStruct, setTableStruct] = useState([]); // 临时使用,看后续是否需要保留
const [columns, setColumns] = useState([]); // 表头设置
const [tableData, setTableData] = useState([]); // 表数据
const [pagination, setPagination] = useState({
current: 1,
total: 0,
pageSize: handlePageSize(pageSize) || 100,
pageSizeOptions: [...new Set([20, 50, 100].concat(handlePageSize(pageSize)))].filter(item => Number(item)).sort((a, b) => Number(a) - Number(b)),
showQuickJumper: true,
showSizeChanger: true,
onShowSizeChange: (current, size) => {
let _pagination = { ...pagination };
_pagination.current = current;
_pagination.pageSize = size;
setPagination(_pagination);
getData(false, _pagination);
},
onChange: (current, size) => {
let _pagination = { ...pagination };
_pagination.current = current;
_pagination.pageSize = size;
setPagination(_pagination);
getData(false, _pagination);
},
});
const [controls, setControls] = useState([]); // 用来存储操作控件
const [searchContent, setSearchContent] = useState(''); // 搜索框内的值
const [searchPlaceholder, setSearchPlaceholder] = useState([]); // 搜索框的placeholder
const [filterObject, setFilterObject] = useState({}); // 存控制条中,选了筛选条件的值
const [modalVisible, setModalVisible] = useState(false);
const [allSortFields, setAllSortFields] = useState([]); // 设置所有列表
const [selectedSortFields, setSelectedSortFields] = useState([{
label: '主要排序',
value: '',
sort: 'asc',
}]);
const [summaryArray, setSummaryArray] = useState([]); // 总结栏,包括小计、总计
const [tableY, setTableY] = useState(500);
const [tableX, setTableX] = useState(1820);
const [listHeight, setListHeight] = useState(0);
const [tableHeaderLevel, setTableHeaderLevel] = useState(null);
const [exportLoading, setExportLoading] = useState(false);
const [extraModal, setExtraModal] = useState(false);
const [hasTotal, setHasTotal] = useState(false);
const [hasSinglePage, setHasSinglePage] = useState(false);
const [tableLoading, setTableLoading] = useState(false);
const [mergeObject, setMergeObject] = useState({});
// const
const [timeFrom, setTimeFrom] = useState(
moment()
.startOf(initDateModel)
.format(dateFormat),
);
const [timeTo, setTimeTo] = useState(
moment()
.endOf(initDateModel)
.format(dateFormat),
);
const [extra, setExtra] = useState(<></>);
const [sortModalVisible, setSortModalVisible] = useState(false);
const [currentReportId, setCurrentReportId] = useState(null);
const [hasDatePicker, setHasDatePicker] = useState('');
const [defaultDateConfig, setDefaultDateConfig] = useState({
defaultModel: 'year',
defaultDate: null,
});
const [dateModel, setDateMode] = useState('all');
// const [detailsComponent, setDetailsComponent] = useState(<></>); // 详情组件
const [detailsComponentVisible, setDetailsComponentVisible] = useState(false); // 是否显示详情组件
const [editComponentVisible, setEditComponentVisible] = useState(false); // 是否显示编辑组件
const [currentData, setCurrentData] = useState({});
const [detailConfig, setDetailConfig] = useState({ url: '', type: '', params: {} });
const [controlsHeight, setControlsHeight] = useState(44);
const menu = () => {
const _item = [
{
label: (
<Button
size='middle'
loading={exportLoading}
type='text'
onClick={() => exportModule('pdf', 'pdf')}
icon={<ExportOutlined />}
>
导出pdf
</Button>
),
key: 'exportPdf',
},
{
label: (
<Button
size='middle'
loading={exportLoading}
type='text'
onClick={() => exportModule('excel', 'xls')}
icon={<ExportOutlined />}
>
导出Excel
</Button>
),
key: 'exportExcel',
},
];
return <Menu items={_item} />;
};
const exportModule = (type, extension) => {
setExportLoading(true);
exportAccountData({ responseType: 'blob' }, { exportType: type }, {
reportName,
pageIndex: 0,
pageSize: 0,
sortFields: '',
content: '',
filters: [
{
fieldAlias: '',
fieldValue: '',
},
],
})
.then(res => {
if (res && res.code === -1) return message.error(res.msg);
const url = window.URL.createObjectURL(
new Blob([res], { type: 'application/octet-stream;charset=UTF-8' }),
);
const a = document.createElement('a');
a.href = url;
a.target = '_blank';
a.download = `${reportName}${moment().format('YYYY-MM-DD-HH-mm-ss').replaceAll('-', '')}.` + extension;
a.click();
a.remove();
setExportLoading(false);
})
.catch(err => {
setExportLoading(false);
});
};
const searchData = (e) => {
getData(false, pagination);
};
const controlSelectChange = (fieldAlias, e) => {
let _filterArray = { ...filterObject };
_filterArray[fieldAlias] = e;
setFilterObject(_filterArray);
};
const searchInputChange = (e) => {
setSearchContent(e.target.value);
};
const setConfig = (config, summary, data) => {
getControlsBarConfig(config);
getTableSummaryConfig(config, summary);
getTableLevel(config);
};
const addFilterAndSearchParams = (data) => {
let _data = { ...data };
// 搜索框是否有值
if (searchContent) _data.content = searchContent;
// filterObject是存起来的控制栏的过滤条件
let _filters = Object.keys(filterObject).filter(key => {
let _value = filterObject[key];
return (isString(_value) && _value) || (isArray(_value) && _value.length);
}).map(key => {
let _value = filterObject[key];
if (isString(_value) && _value) return {
fieldAlias: key,
fieldValue: _value,
};
if (isArray(_value) && _value.length) return {
fieldAlias: key,
fieldValue: _value.join(','),
};
return false;
});
// 加上时间过滤参数
if (dateModel !== 'all' && hasDatePicker && timeFrom && timeTo) {
_filters.push({
fieldAlias: hasDatePicker,
fieldValue: `${timeFrom} 00:00:00,${timeTo} 23:59:59`,
});
}
// 合并手动传入的filters
if (filterFields) {
let _customerFilterArray = filterFields.split(',');
let _customerValueArray = filterValues.split(',');
_customerFilterArray.forEach((item, index) => {
_filters.push({
fieldAlias: item,
fieldValue: _customerValueArray[index] || '',
});
});
}
// 并入 _data
if (_filters.length) _data.filters = _filters;
return _data;
};
// 排序字符串处理成数组
const handleSortString = (sortString) => {
// selectedSortFields
let _sortStringArray = sortString.split(',').map((item, index) => {
let _item = item.split(' ');
if (index === 0) {
return {
label: '主要排序',
value: _item[0].replace(/\[|\]/g, ''),
sort: _item[1],
};
} else {
return {
label: '次要排序',
value: _item[0].replace(/\[|\]/g, ''),
sort: _item[1],
};
}
});
setSelectedSortFields(_sortStringArray);
};
/**
* @Description: 根据是否向下合并处理数据,返回合并的key的数组
* */
const returnMergeArray = (config) => {
return config.filter(item => item.isMerge).map(item => item.fieldAlias);
};
/**
* @Description: 根据配置和数据,计算出该合并的字段和每一行是否合并
* */
const handleDataToGetRowSpanArray = (config, data) => {
let _arr = returnMergeArray(config);
let _merge = {};
// _merge:{爱好:[[0,3],[3,5]]}
_arr.forEach(key => {
_merge[key] = {};
let _currentIndex = 0;
data.forEach((item, index) => {
if (index > 0) {
if (item[key] === data[index - 1][key]) {
_merge[key][_currentIndex] += 1;
_merge[key][index] = 0;
} else {
_currentIndex = index;
_merge[key][index] = 1;
}
} else {
_merge[key][0] = 1;
}
});
});
return _merge;
};
const getData = (isInit, pagination) => {
setTableLoading(true);
const { pageSize, current } = pagination;
// 搜索条件附加到params
let _data = addFilterAndSearchParams({
reportName: reportName,
pageIndex: current,
pageSize: pageSize,
});
// sortFields
reportService.getReportInfo(_data).then(res => {
if (res.code === 0) {
let _reportDetails = res.data.reportDetails;
let _statisticalValues = res.data.statisticalValues;
let _tableData = res.data.data.list;
let _sortString = res.data.sortString;
if (isInit) {
setConfig(_reportDetails, _statisticalValues, _tableData);
}
getTableHeaderConfig(_reportDetails, _tableData);
let _pagination = { ...pagination };
_pagination.total = res.data.data.totalCount;
setPagination(_pagination);
setTableData(_tableData);
// 处理排序字段
handleSortString(_sortString);
if (_tableData) {
setSortModalVisible(true);
setCurrentReportId(_reportDetails[0]?.reportId);
} else {
setSortModalVisible(false);
setCurrentReportId(null);
}
} else {
setSortModalVisible(false);
setCurrentReportId(null);
message.error('未能查询到报表数据!');
let _pagination = { ...pagination };
_pagination.total = 0;
_pagination.current = 1;
setPagination(_pagination);
setTableData([]);
}
setTableLoading(false);
}).catch(err => {
console.log(err);
setTableLoading(false);
});
};
/**
* @Description: 在配置项中,isFilter: true 用来渲染控制框;filterRule: 下拉/文本/多选
* */
const getControlsBarConfig = (config) => {
let _data = config.filter(item => item.isFilter);
let _searchPlaceholder = [];
_data.filter(item => item.filterRule === '文本').forEach(item => _searchPlaceholder.push(item.fieldAlias));
setSearchPlaceholder(_searchPlaceholder);
let _controls = _data.filter(item => ControlsType.includes(item.filterRule));
setControls(_controls);
handleDate(_controls);
};
const getTableHeaderConfig = (config, data) => {
setTableStruct(config);
setColumns(returnColumn(config, data));
setAllSortFields(returnSortFields(config));
};
const getTableSummaryConfig = (config, summary) => {
if (summary.length === 0) {
setSummaryArray([]);
return false;
}
let _summaryConfig = {};
let _configLength = config.length; // 需要判断
// 合并列
let _colSpan = -1;
let _index = 0;
config.filter(item => item.isShow).forEach((item, index) => {
if (item.isStatistics) {
_index += 1;
_colSpan = -1;
_summaryConfig[item.fieldAlias] = {
fieldName: item.fieldAlias,
index,
alignType: item.alignType,
type: item.statisticsRule,
isMoney: item.type === '数值' && !!hasMoney(item.configItems),
configItems: item.configItems,
};
} else {
let _name = `空值行${_index}`;
if (_colSpan === -1) {
_colSpan = 1;
} else {
_colSpan += 1;
}
_summaryConfig[_name] = {
fieldName: _name,
colSpan: _colSpan,
};
}
});
summary.forEach(item => {
switch (item.totalType) {
case '全部':
setHasTotal(true);
break;
case '单页':
setHasSinglePage(true);
break;
default:
break;
}
if (_summaryConfig[item.fieldName]) {
_summaryConfig[item.fieldName][item.totalType] = item.fieldValue;
}
});
// 增加操作列,总结栏最后一个单元格需要延伸一格
let _sumArr = Object.values(_summaryConfig);
if (_sumArr && _sumArr.length) _sumArr = _sumArr.map((item, index) => {
let _item = { ...item };
if (index === _sumArr.length - 1) _item.colSpan += 1;
return _item;
});
setSummaryArray(_sumArr);
};
const changeDate = ({ dateFrom, dateTo }, mode) => {
setTimeFrom(dateFrom);
setTimeTo(dateTo);
setDateMode(mode);
};
const mapHandleType = (type) => {
const _map = {
文本: handleText,
数值: handleNumber,
标签: handleTag,
链接: handleLink,
功能: handleWidget,
弹窗: handleModal,
日期: handleDateString,
日期时间: handleDateTimeString,
};
return _map[type] || _map['文本'];
};
const returnSortFields = (data) => {
return data.map(item => item.fieldAlias);
};
// 处理表格数据,生成表头
const returnColumn = (config, data) => {
//0. 常规数据
//1. 合并表头;
//2. 四类形态的渲染处理;
//3. 多列表头排序;剔除掉原有图标,需要自己实现排序的按钮
let _config = [...config].filter(item => item.isShow);
function dataStruct(keyArray, dataIndex, obj, dataObj) {
if (dataIndex < keyArray.length - 1) {
if (!obj[keyArray[dataIndex]]) {
obj[keyArray[dataIndex]] = {};
}
let _dataIndex = dataIndex + 1;
dataStruct(keyArray, _dataIndex, obj[keyArray[dataIndex]], dataObj);
} else if (dataIndex === keyArray.length - 1) {
obj[keyArray[dataIndex]] = dataObj;
}
}
let _tempObj = {};
let _fieldAliasArray = handleDataToGetRowSpanArray(_config, data); // 需要向下合并的字段
_config.forEach(item => {
let _item = {
title: item.fieldAlias,
dataIndex: item.fieldAlias,
key: item.fieldAlias,
ellipsis: true,
onCell: (record, rowIndex) => {
// console.log('Record: ', record); // record是这条记录,index是rowIndex
// 1. 如果该字段是需要向下合并的,则进入判断
let _obj = {};
if (_fieldAliasArray[item.fieldAlias]) {
_obj.rowSpan = _fieldAliasArray[item.fieldAlias][rowIndex];
}
return _obj;
},
render: (value, record) => {
// 文本、标签、链接、数值
// @params: item 当前的config数据,提供该字段的各类配置
// value 当前单元格的值
// record 点击单元格的整行数据
let rest = [];
if (item.type === '功能') {
/* rest = {
showComponent: setDetailsComponentVisible,
setDetailsConfig: setDetailConfig,
}; */
rest = [setDetailsComponentVisible, setDetailConfig];
} else if (item.type === '弹窗') {
/* rest = {
showModal: setModalVisible,
setExtra: setExtra,
};*/
rest = [setModalVisible, setExtra];
}
return mapHandleType(item.type)(item, value || '', record, ...rest);
},
};
_item.width = item.columnWidth || 200; // 列宽,不设置时默认给200;
if (item.fixedColumn) _item.fixed = item.fixedColumn; // 固定位置
_item.align = item.alignType || 'left'; // 单元格内对齐方式
let _keyArray = (item.fieldGroup || item.fieldAlias || item.fieldName).split(fieldSplitStr);
// 自定义排序
let _sortFields = handleSortFields(sortFields);
if (_sortFields.includes(item.fieldAlias)) {
_item.sorter = true;
}
dataStruct(_keyArray, 0, _tempObj, _item);
return _item;
});
let _tempArray = [];
function handleObject2Array(obj, arr) {
Object.keys(obj).forEach((key, index) => {
if (obj[key].title && obj[key].title === key) {
arr.push(obj[key]);
} else {
arr.push({ title: key, children: [] });
handleObject2Array(obj[key], arr[index].children);
}
});
}
handleObject2Array(_tempObj, _tempArray);
// 增加序号
_tempArray.unshift({
title: '序号',
dataIndex: 'r',
key: 'r',
width: 60,
fixed: 'left',
});
// 增加操作列
if (permission.includes('editBtn') || permission.includes('deleteBtn')) {
_tempArray.push({
title: '操作',
width: 60,
fixed: 'right',
render: (text, record) => {
return <Space className={style.handleColumnWrapper}>
{
permission.includes('editBtn') ?
<FormOutlined className={style.editButton} onClick={() => {
setEditComponentVisible(true);
setCurrentData(record);
}} /> : ''
}
{/* {
permission.includes('deleteBtn') ?
<DeleteOutlined disabled className={style.deleteButton} onClick={() => {
Modal.confirm({
content: '你确定要删除改数据吗?',
onOK: () => message.error('您无法删除数据!'),
});
}} /> : ''
}*/}
</Space>;
},
});
}
// 统计宽度
let _x = _tempArray.reduce((final, curr) => {
return final += curr.width;
}, 0);
setTableX(_x);
return _tempArray;
};
const changeSortField = (value, index, key) => {
let _selectedSortFields = [...selectedSortFields];
_selectedSortFields[index][key] = value;
setSelectedSortFields(_selectedSortFields);
};
const addOtherSortFields = () => {
let _selectedSortFields = [...selectedSortFields];
_selectedSortFields.push({
label: '次要排序',
value: '',
sort: 'asc',
});
setSelectedSortFields(_selectedSortFields);
};
const deleteSortField = (index) => {
let _selectedSortFields = [...selectedSortFields];
_selectedSortFields.splice(index, 1);
setSelectedSortFields(_selectedSortFields);
};
const setTableHeight = () => {
if (!tableWrapperRef.current) return;
const clientHeight = tableWrapperRef.current?.clientHeight || 0;
const clientWidth = tableWrapperRef.current?.clientWidth || 0;
const _height = controlRef.current?.clientHeight; // 控制条的高度
const paginationHeight = 75; // 分页部分的高度
const tableHeaderHeight = tableHeaderLevel * 40; // 表头高度
const summaryHeight = summaryArray.length ? 40 * (Number(hasTotal) + Number(hasSinglePage)) : 0; // 总结栏的高度
const _minus = clientHeight - _height - 16 - 4 - tableHeaderHeight - paginationHeight - summaryHeight - 10;
setListHeight(clientHeight - _height - paginationHeight - 4 - 6 - 16 - 2);
setTableY(_minus);
};
const getTableLevel = (config) => {
let _level = config.reduce((final, curr) => {
return final = curr.level > final ? curr.level : final;
}, 1) || 1;
setTableHeaderLevel(_level);
};
const saveReportListSortFields = (callback) => {
reportService.saveReportListSortFields({
reportId: currentReportId,
sortFields: selectedSortFields.filter(item => item.value).map(item => ({
fieldAlias: item.value,
sortType: item.sort,
})),
}).then(res => {
if (res.code === 0) {
message.success('排序保存成功!');
callback();
} else {
message.error(res.msg);
}
});
};
/**
* @Description: 判断是否存在【时间】类型的选择,并返回组件
* */
const handleDate = (obj) => {
let _typeObj = obj.find(item => item.filterRule === '日期');
setHasDatePicker(_typeObj ? _typeObj.fieldAlias : '');
const _configItems = _typeObj.configItems.split('|');
let _defaultDate = _configItems.find(item => item.includes('defaultDate='))?.replace('defaultDate=', '')?.split(',');
if (_defaultDate && _defaultDate.length > 1) {
_defaultDate = { dateFrom: moment(_defaultDate[0]), dateTo: moment(_defaultDate[1]) };
} else if (_defaultDate && _defaultDate.length === 1) {
_defaultDate = { dateFrom: moment(_defaultDate[0]), dateTo: moment(_defaultDate[0]) };
} else {
_defaultDate = { dateFrom: moment(), dateTo: moment() };
}
setDefaultDateConfig({
defaultDate: _defaultDate?.dateFrom,
defaultModel: _configItems.find(item => item.includes('defaultModel='))?.replace('defaultModel=', '') || 'year',
});
};
useEffect(() => {
getData(true, pagination);
}, []);
useEffect(() => {
if (tableHeaderLevel) setTableHeight();
}, [tableHeaderLevel]);
useEffect(() => {
getData(false, pagination);
}, [timeFrom, timeTo, filterObject]);
useEffect(() => {
function getRefHeight() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
let _height = controlRef?.current.clientHeight;
setControlsHeight(_height);
}, 100);
}
window.addEventListener('resize', getRefHeight);
return () => window.removeEventListener('resize', getRefHeight);
});
return <div className={style.reportManage} ref={tableWrapperRef}>
{/* 预留容器,提供给点击后的功能显示 */}
{
detailsComponentVisible ? <DetailsComponent url={detailConfig.url} params={detailConfig.params}
onCancel={() => setDetailsComponentVisible(false)} /> : ''
}
{/* 为方便阅读,分开两部分代码 */}
{
!detailsComponentVisible ? <div className={style.contentWrapper}>
{
state !== 'scan' ? <Row className={style.controlRow} ref={controlRef}>
<Space style={{ flex: 1 }} size={8} wrap={true}>
{/*时间搜索控件,确保时间搜索控件在第一个,单独匹配*/}
{
hasDatePicker && defaultDateConfig.defaultDate !== null && permission.includes('filters') ?
<DatePickerGroup
showModels={['all', 'month', 'quarter', 'year', 'custom']}
onChange={changeDate}
format={dateFormat}
defaultModel={defaultDateConfig.defaultModel}
defaultDate={defaultDateConfig.defaultDate}
/> : ''
}
{
controls && controls.length && permission.includes('filters') ?
controls.filter(control => ['下拉', '多选'].includes(control.filterRule)).map(control => {
return <Form.Item label={control.fieldAlias} key={control.fieldAlias}>
<ReturnControlComponent style={{ width: 240 }} type={control.filterRule} reportName={reportName}
fieldAlias={control.fieldAlias}
configItems={control.configItems}
onChange={(e) => controlSelectChange(control.fieldAlias, e)} />
</Form.Item>;
}) : ''
}
{
permission.includes('filters') ? <Form.Item label='快速索引' key={'快速搜索控件'}>
<ReturnControlComponent
placeholder={`请输入${searchPlaceholder.length ? searchPlaceholder.join(',') : '关键字'}搜索`}
style={{ width: 240 }} type={'文本'} onChange={(e) => {
searchInputChange(e);
}} onSearch={searchData} />
</Form.Item> : ''
}
</Space>
{
permission.includes('sortBtn') ? <div style={{ width: 200, textAlign: 'end' }}>
<Space size={8} nowrap={true}>
{
sortModalVisible && permission.includes('sortBtn') ? <Form.Item>
<Button type={'primary'} title={'自定义排序'} icon={<SortAscendingOutlined />}
onClick={() => setModalVisible(true)}>排序</Button>
</Form.Item> : ''
}
{
permission.includes('exportBtn') ? <Form.Item>
<Dropdown.Button style={{ float: 'right' }} overlay={menu}>导出</Dropdown.Button>
</Form.Item> : ''
}
</Space>
</div> : ''
}
</Row> : ''
}
<div className={style.tableContent}
style={{ height: `calc(100% - ${controlsHeight || 0}px)` }}>
{
columns && columns.length ? <BasicTable
rowKey={'Key'}
bordered
loading={tableLoading}
dataSource={tableData}
columns={columns}
onChange={(pagination, filters, sorter, extra)=>{
console.log(sorter)
}}
pagination={permission.includes('pagination') ? pagination : false}
// 237是内置图片高度
scroll={{ y: (tableData && tableData.length) ? `calc(100% - 44px)` : 237, x: tableX }}
summary={(pageData) => {
if (summaryArray.length && tableData && tableData.length) return <Table.Summary fixed>
<Table.Summary.Row>
{
hasSinglePage ?
summaryArray.map((item, index) => {
if (item.fieldName === '空值行0') {
return <Table.Summary.Cell key={`summary_${index}`} index={0}
colSpan={item.colSpan + 1}><span
style={{
display: 'inline-block',
width: '100%',
textAlign: 'center',
}}>小计</span></Table.Summary.Cell>;
} else if (item.fieldName.includes('空值行')) {
return <Table.Summary.Cell key={`summary_${index}`} index={0} colSpan={item.colSpan} />;
} else {
return <Table.Summary.Cell key={`summary_${index}`} index={0}><span style={{
display: 'inline-block',
width: '100%',
textAlign: item.alignType,
}}>{item.type.replace('求', '')}: {returnHandledNumber(item.configItems, item['单页'])}</span></Table.Summary.Cell>;
}
}) : ''
}
</Table.Summary.Row>
<Table.Summary.Row>
{
hasTotal ?
summaryArray.map(item => {
if (item.fieldName === '空值行0') {
return <Table.Summary.Cell index={0} colSpan={item.colSpan + 1}><span style={{
display: 'inline-block',
width: '100%',
textAlign: 'center',
}}>总计</span></Table.Summary.Cell>;
} else if (item.fieldName.includes('空值行')) {
return <Table.Summary.Cell index={0} colSpan={item.colSpan} />;
} else {
return <Table.Summary.Cell index={0}><span style={{
display: 'inline-block',
width: '100%',
textAlign: item.alignType,
}}>{item.type.replace('求', '')}: {returnHandledNumber(item.configItems, item['全部'])}</span></Table.Summary.Cell>;
}
}) : ''
}
</Table.Summary.Row>
</Table.Summary>;
}}
/> : <div className={style.spinWrapper}>
<Spin />
</div>
}
</div>
</div> : ''
}
<Modal
title={'自定义排序字段'}
visible={modalVisible}
onCancel={() => setModalVisible(false)}
footer={
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>
<Button type={'link'} onClick={() => addOtherSortFields()}>增加次要排序</Button>
</div>
<div>
<Button onClick={() => setModalVisible(false)}>取消</Button>
<Button type={'primary'} onClick={() => {
saveReportListSortFields(() => getData(false, pagination));
setModalVisible(false);
}}>确认</Button>
</div>
</div>
}
>
{
selectedSortFields.map((item, index) => <Row key={'label'} className={style.controlRow}>
<Space size={8} wrap={true}>
<Form.Item label={item.label}>
<Select style={{ width: 240 }} defaultValue={item.value} value={item.value}
onChange={(e) => changeSortField(e, index, 'value')}>
<Option value=''>未选择</Option>
{
allSortFields.map(item => <Option value={item}>{item}</Option>)
}
</Select>
</Form.Item>
<Form.Item>
<Select style={{ width: 120 }} defaultValue={item.sort} value={item.sort}
onChange={(e) => changeSortField(e, index, 'sort')}>
<Option value={'asc'}>升序</Option>
<Option value={'desc'}>降序</Option>
</Select>
</Form.Item>
{
index !== 0 ?
<Form.Item>
<MinusCircleOutlined style={{ color: 'rgba(0,0,0,.65)' }} onClick={() => deleteSortField(index)} />
</Form.Item> : ''
}
</Space>
</Row>)
}
</Modal>
<Modal
visible={extraModal}
onCancel={() => setExtraModal(false)}
destroyOnClose
width={800}
>
{extra}
</Modal>
{/* 编辑表单 */}
<Modal
title={'编辑报表信息'}
visible={editComponentVisible}
width={'80%'}
footer={null}
// visible={true}
onCancel={() => setEditComponentVisible(false)}
>
<ReportEditForm reportDetails={tableStruct} reportData={currentData} onCancel={() => {
setEditComponentVisible(false);
getData(false, pagination);
}} reportName={reportName} />
</Modal>
</div>;
};
const mapStateToProps = state => {
const allWidgets = state.getIn(['global', 'globalConfig', 'allWidgets']);
let _flatWidgets = [];
const flatWidgets = (arr) => {
arr.forEach(item => {
if (item.widgets && item.widgets.length) {
flatWidgets(item.widgets);
} else {
_flatWidgets.push(item);
}
});
};
flatWidgets(allWidgets);
return {
allWidgets: _flatWidgets,
};
};
export default connect(mapStateToProps, null)(BasicReport);
@import "~antd/es/style/themes/default.less";
.lackParams {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.reportManage {
padding: 8px;
height: 100%;
display: flex;
flex-direction: column;
.contentWrapper {
display: flex;
flex-direction: column;
height: 100%;
.controlRow {
background: #ffffff;
padding: 6px;
border-radius: 4px;
margin-bottom: 4px;
display: flex;
flex-direction: row;
}
.tableContent {
flex: 1;
padding: 6px;
background: #ffffff;
.handleColumnWrapper {
display: flex;
justify-content: space-around;
.editButton {
cursor: pointer;
&:hover {
color: rgb(24, 144, 255);
}
}
.deleteButton {
cursor: pointer;
color: rgb(255, 0, 0);
&:hover {
//color: rgb(24, 144, 255);
}
}
}
.spinWrapper {
width: 100vw;
height: 60vh;
display: flex;
justify-content: center;
align-items: center
}
:global {
.@{ant-prefix}-table-container {
height: 100%;
& > .@{ant-prefix}-table-body {
border-bottom: 1px solid #dbe7fb;
border-right: 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, .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 {
border-radius: 5px;
background: #DCDCDC;
}
}
}
import style from '../ReportsManage.less';
import extraComponents from '../extra/extraComponents';
import moment from 'moment';
import { Tag } from 'antd';
import { hasMoney, isNumber } from './utils';
/**
* @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 = '';
if (number) number = Number(number); // 当设置精度后,会被转成字符串
if (config.numericalConfigs && config.numericalConfigs.length) {
config.numericalConfigs.forEach(item => { // 接口对于数值类型的返回为null
if (!_color) {
let _max = item.maxValue || '';
let _min = 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.color) {
_color = config.color;
}
return number ? <span>
<span className={style.prefixOrSuffix} style={{
color: _color,
}}>{config.prefix || ''}</span>
<span style={{
color: _color,
margin: '0 5px',
}}>{hasMoney(config?.configItems) ? (number ? Number(number)?.toLocaleString() : number) : number}</span>
<span className={style.prefixOrSuffix} style={{ color: _color }}>{config.suffix || ''}</span>
</span> : '-';
};
/**
* @Params: 标签形态的configRule,[{标签值:'字符串',颜色: '#AAAAAA'}];
* */
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>);
/* 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>);*/
};
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 === '金额');
};
/**
* @Description: 用来在summary中处理数值的配置
* @Params: 参数描述
* @Date: 2022/8/10
* @Author: ChenLong
* */
const returnHandledNumber = (configItems, num) => {
// 精度、前缀、后缀、倍率
// $_d|_d%|_d*0.0001|金额|0.00
if (!configItems) return 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 ? 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));
return template.replace(/_d/, 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;
};
export {
isObject, isString, isNumber, hasMoney, isArray, returnHandledNumber, returnDefaultValueOrConfigs,
};
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