mh-sms-web/src/feui/search/SearchGroupField.js

531 lines
17 KiB
JavaScript
Raw Normal View History

2024-01-22 09:18:38 +08:00
// 核心库
import React, { Component } from 'react'
import { connect } from 'dva'
// 组件库
import { Row, Col, Select, TreeSelect, Button, Collapse, Icon } from 'antd'
import IFComponent from '../common/IFComponent'
// 工具库
import { getDataFieldValue, setDataFieldValue, initFilter, guid } from '../utils/common'
import getControl from '../utils/getControl'
import { getFieldConfigs, getGroupByGroupConfigs } from '../utils/getFieldConfigs'
import { isEqual } from 'lodash'
const Option = Select.Option
const Panel = Collapse.Panel
const TreeSelectNode = TreeSelect.TreeNode
// dateType 对应 controlType
const dataMapControl = { 2: 7, 3: 3, 4: 4, 5: 10, 6: 12, 8: 17, 9: 18, 10: 19, 11: 9, 12: 20, 13: 21, 14: 11, 15: 22, 16: 23, 17: 2 }
const digGroupData = (arr, target) => {
for (let i = 0, j = arr.length; i < j; i++) {
if (arr[i].id === target) return arr[i]
if (Array.isArray(arr[i].childGroups) && arr[i].childGroups.length) {
const result = digGroupData(arr[i].childGroups, target)
if (result) return result
}
}
}
const operator1 = [
{ value: 1, label: '等于' },
{ value: 2, label: '不等于' },
{ value: 7, label: '开始于' },
{ value: 8, label: '结束于' },
{ value: 9, label: '包含' }
]
const operator3 = [
{ value: 1, label: '等于' },
{ value: 2, label: '不等于' }
]
const operator7 = [
{ value: 1, label: '等于' },
{ value: 2, label: '不等于' },
{ value: 3, label: '小于' },
{ value: 4, label: '小于或等于' },
{ value: 5, label: '大于' },
{ value: 6, label: '大于或等于' }
]
function PanelContent (props) {
// 新增一组字段
const addFieldConfig = () => {
const { code, config, customConfigId, onChange, onPressEnter } = props
!config.fieldConfigs && (config.fieldConfigs = [])
config.fieldConfigs.push({
id: guid(),
searchGroupId: config.id,
isSysQueryField: false,
userCCQueryId: customConfigId || null,
code,
onChange,
data: {
onPressEnter
}
})
props.onPanelContentChange instanceof Function && props.onPanelContentChange()
}
// 删除一组字段
const deleteFieldConfig = (id) => {
const { config } = props
const findIndex = config.fieldConfigs.findIndex(item => item.id === id)
if (findIndex !== -1) {
config.fieldConfigs.splice(findIndex, 1)
props.onPanelContentChange instanceof Function && props.onPanelContentChange()
}
}
// 字段选择
const onSelectChange = (id, nodeData) => {
const dataType = getDataFieldValue(nodeData, 'DataType')
const controlType = dataMapControl[dataType] ? dataMapControl[dataType] : 1
// 配置出选中的配置
const fieldConfig = {
field: getDataFieldValue(nodeData, 'FieldName'),
label: getDataFieldValue(nodeData, 'ShowLabel'),
controlType,
dataType,
operator: controlType === 1 ? '9' : '1',
isCustom: getDataFieldValue(nodeData, 'IsCustom'),
isSysParam: getDataFieldValue(nodeData, 'IsSysParam'),
defaultValue: null,
isSysQueryField: getDataFieldValue(nodeData, 'IsSysField', false),
caseType: getDataFieldValue(nodeData, 'CaseType'),
isRequire: getDataFieldValue(nodeData, 'IsRequire')
}
// 获取到当前选中项
const { config } = props
const find = config.fieldConfigs.find(item => item.id === id)
if (find) {
Object.keys(fieldConfig).forEach(key => {
// 重写当前选中项各项值为选中的配置
setDataFieldValue(find, key, getDataFieldValue(fieldConfig, key))
!find.data && (find.data = {})
find.data.enumName = getDataFieldValue(nodeData, 'EnumName')
})
props.onPanelContentChange instanceof Function && props.onPanelContentChange()
}
}
// 操作关系选择
const onOperatorChange = (id, value) => {
const { config } = props
const find = config.fieldConfigs.find(item => item.id === id)
if (find) {
find.operator = value ? value.toString() : ''
props.onPanelContentChange instanceof Function && props.onPanelContentChange()
}
}
// 构造树节点
const getFieldTreeNode = (data) => {
return data.filter(item => !item.IsCustom).map(item => {
if (item.Children) {
return (
<TreeSelectNode
title={item.Label}
key={item.FieldName}
value={item.FieldName}
data={item}
isLeaf={item.IsLeaf}
>
{getFieldTreeNode(item.Children)}
</TreeSelectNode>
)
}
return (
<TreeSelectNode
title={<>{item.IsRequire ? <span style={{ color: 'red' }}>*</span> : null}{item.Label}</>}
key={item.FieldName}
value={item.FieldName}
data={item}
isLeaf={item.IsLeaf}
/>
)
})
}
// TreeSelect 远程数据加载
const handleLoadChildTreeNode = (treeNode) => {
const { login, formId, dispatch } = props
const { data } = treeNode.props
if (data.Children) return Promise.resolve()
const json = initFilter(login.OrgId, formId, 'CREATE_TIME', 0, 1, data.FieldName, data.TypeName, data.Label)
return dispatch({
type: 'search/getQueryFields',
payload: json
}).then(ret => {
treeNode.props.data.Children = ret
})
}
const { data, config, queryFields } = props
const { fieldConfigs = [], childGroups = [] } = config
return (
<>
<IFComponent
IF={fieldConfigs.length}
ELSE={
<Button
icon='plus'
type='dashed'
shape='circle'
title='新增条件'
onClick={addFieldConfig}
style={{ marginLeft: 12 }}
/>
}
>
{
fieldConfigs.map((item, index) => {
item.value = getDataFieldValue(data, item.id)
const options = item.controlType === 1 ? operator1 : item.controlType === 3 ? operator3 : item.controlType === 7 ? operator7 : []
return (
<Row gutter={16} key={item.id} style={{ marginBottom: 6 }}>
<Col span={6}>
<TreeSelect
allowClear
showSearch
value={item.field}
placeholder='请选择字段'
treeNodeFilterProp='title'
loadData={handleLoadChildTreeNode}
onSelect={(value, node) => onSelectChange(item.id, node.props.data)}
onChange={value => !value && onSelectChange(item.id, null)}
dropdownStyle={{
maxHeight: 400,
overflow: 'auto'
}}
style={{ width: '100%' }}
>
{getFieldTreeNode(queryFields)}
</TreeSelect>
</Col>
<IFComponent IF={!!item.field}>
<Col span={3}>
<IFComponent IF={options.length}>
<Select
allowClear
value={item.operator ? +item.operator : null}
onChange={val => onOperatorChange(item.id, val)}
>
{options.map(option => <Option value={option.value} key={option.id + option.value}>{option.label}</Option>)}
</Select>
</IFComponent>
</Col>
<Col span={6}>{getControl(item)}</Col>
</IFComponent>
<Col span={3}>
<Button
icon='delete'
type='danger'
shape='circle'
title='删除'
onClick={() => deleteFieldConfig(item.id)}
/>
<IFComponent IF={index === fieldConfigs.length - 1}>
<Button
icon='plus'
type='dashed'
shape='circle'
title='新增条件'
onClick={addFieldConfig}
style={{ marginLeft: 12 }}
/>
</IFComponent>
</Col>
</Row>
)
})
}
</IFComponent>
<IFComponent IF={childGroups.length}>
<SearchGroupField
{...props}
key={childGroups.map(item => item.id).join(';') || guid()}
childField
groups={childGroups}
/>
</IFComponent>
</>
)
}
function PanelHeader (props) {
const { app, config, index, onSelectChange, deleteGroupConfig, addGroupConfig } = props
const enumOptions = app.enums && app.enums['FMUserCCQueryGroupRelationTypeEnum'] && app.enums['FMUserCCQueryGroupRelationTypeEnum'].options
return (
<div className='opt-search__collapseHeader'>
<span>条件组{index + 1}</span>
<div className='opt-search__collapseHeaderSelect'>
<span>分组关系</span>
<Select
value={config.relationType}
onChange={onSelectChange}
size='small'
style={{ width: 60 }}
>
{(enumOptions || []).map(option => <Option key={option.value} value={option.value}>{option.label}</Option>)}
</Select>
</div>
<div className='opt-search__collapseHeaderAction'>
<Icon
type='delete'
title='删除该分组'
style={{ color: '#f00', marginRight: 15 }}
onClick={deleteGroupConfig}
/>
<Icon
type='plus'
title='新增分组'
onClick={addGroupConfig}
/>
</div>
</div>
)
}
const CPanelHeader = connect(({ app }) => ({ app }))(PanelHeader)
class SearchGroupField extends Component {
// 注意该组件作为循环嵌套使用的子组件时state 值均为父组件 state 的值的引用,无需 cloneDeep
constructor (props) {
super(props)
this.state = {
data: {},
groupConfigs: [],
queryFields: []
}
}
componentDidMount () {
// 如果是 PanelContent 中渲染出来的该组件,那么可以直接拿 groups 属性作为 groupConfigs
if (this.props.childField) {
this.setState({
data: this.props.data,
groupConfigs: this.props.groups,
queryFields: this.props.queryFields
})
return
}
this.getQueryFields()
const { onRef, fields, groups, presetValue } = this.props
this.setGroupConfigs({ fields, groups, presetValue })
onRef instanceof Function && onRef(this)
}
UNSAFE_componentWillReceiveProps (nextProps) {
// 如果是 PanelContent 中渲染出来的该组件,那么共享 queryFields
// 放在 UNSAFE_componentWillReceiveProps 生命周期中是因为父组件的 queryFields 是通过接口获取的
if (this.props.childField && !isEqual(nextProps.queryFields, this.state.queryFields)) {
this.setState({ queryFields: this.props.queryFields })
}
}
getQueryFields = () => {
const { code, formId, customConfigId, login, dispatch } = this.props
const json = initFilter(login.OrgId, formId, '', 0, 1, customConfigId, login.userId, code)
dispatch({
type: 'search/getUserConfig',
payload: json
}).then(ret => {
if (ret && ret.Nav_Fields) {
this.setState({
queryFields: ret.Nav_Fields.filter(item => !item.IsCustom)
})
}
})
}
getGroupConfigs = (fields, groups, parentId, parent, list, fieldConfigs) => {
const tempGroups = groups.filter(item => item.PARENT_ID === parentId)
if (tempGroups && tempGroups.length) {
tempGroups.sort((x, y) => x.NUM - y.NUM)
tempGroups.forEach(group => {
const config = {
id: group.ID || guid(),
title: group.TITLE,
parentId: group.PARENT_ID,
code: group.CODE,
relationType: group.RELATION_TYPE,
isDisplay: group.IS_DISPLAY,
userCCQueryId: group.USER_C_C_QUERY_ID
}
const tempFields = fields.filter(item => item.USER_C_C_QUERY_GROUP_ID === config.id)
if (tempFields && tempFields.length) {
config.fieldConfigs = []
tempFields.forEach(item => {
const fieldConfig = getFieldConfigs({
field: item,
onPressEnter: this.onPressEnter,
onChange: this.onChange
})
config.fieldConfigs.push(fieldConfig)
fieldConfigs.push(fieldConfig)
})
}
if (parent) {
!parent.childGroups && (parent.childGroups = [])
parent.childGroups.push(config)
} else {
list.push(config)
}
this.getGroupConfigs(fields, groups, config.id, config, list, fieldConfigs)
})
}
}
setGroupConfigs = ({ fields, groups, presetValue }) => {
const fieldConfigs = []
const groupConfigs = []
if (groups && groups.length) {
groups.sort((x, y) => x.NUM - y.NUM)
this.getGroupConfigs(fields, groups, null, null, groupConfigs, fieldConfigs)
}
// 赋值 data { field: value }
const data = {}
fieldConfigs.forEach(item => {
if (presetValue !== undefined && typeof presetValue === 'object' && Object.keys(presetValue).length) {
if (Object.prototype.hasOwnProperty.call(presetValue, item.field)) {
data[item.id] = presetValue[item.field]
return
}
}
if (item.defaultValue !== undefined) {
data[item.id] = item.defaultValue
}
})
this.setState({
data,
groupConfigs
})
}
// 新增一组配置 提供给 ref 调用
addGroupConfig = (id) => {
const { code, customConfigId } = this.props
const config = {
id: guid(),
parentId: id,
relationType: 1,
isDisplay: true,
code,
userCCQueryId: customConfigId || null
}
const find = digGroupData(this.state.groupConfigs, id)
if (find) {
!find.childGroups && (find.childGroups = [])
find.childGroups.push(config)
} else if (!id) {
// 无 id代表新增根配置
this.state.groupConfigs.push(config)
}
// 直接修改 state并使用 forceUpdate 更新,目的是为了保持对父组件 state 值的引用
this.forceUpdate()
}
// 删除一组配置
deleteGroupConfig = (id) => {
const findIndex = this.state.groupConfigs.findIndex(item => item.id === id)
if (findIndex !== -1) {
this.state.groupConfigs.splice(findIndex, 1)
// 直接修改 state并使用 forceUpdate 更新,目的是为了保持对父组件 state 值的引用
this.forceUpdate()
}
}
// 分组关系选择
onSelectChange = (id, value) => {
const find = digGroupData(this.state.groupConfigs, id)
if (find) {
find.relationType = value
// 直接修改 state并使用 forceUpdate 更新,目的是为了保持对父组件 state 值的引用
this.forceUpdate()
}
}
// PanelContent 内容更新
onPanelContentChange = () => {
this.forceUpdate()
}
// 回车搜索
onPressEnter = ({ e: evt }) => {
evt.stopPropagation()
this.handleSearch()
}
// field change 时候赋值给 data
onChange = (params) => {
const { value, colConfig } = params
setDataFieldValue(this.state.data, colConfig.id, value)
// 直接修改 state并使用 forceUpdate 更新,目的是为了保持对父组件 state 值的引用
this.forceUpdate()
}
/**
* 执行搜索搜索流程为
* 调用 Search 组件 onSearch 方法
* 然后 Search 分别通过 ref 分别调用 AdvanceSearch SearchGroupField getSearchParams 方法获取到搜索参数
* 最后再由 Search 组件完成搜索操作这样搜索就统一在 Search 中做了
*/
handleSearch = () => {
const { onSearch } = this.props
onSearch instanceof Function && onSearch()
}
// 获取搜索参数 提供给 ref 调用
getSearchParams = (groups = []) => {
return getGroupByGroupConfigs(this.state.data, this.state.groupConfigs, null, groups,this.props.user)
}
render () {
const { data, groupConfigs, queryFields } = this.state
return (
<IFComponent IF={groupConfigs.length}>
<Collapse activeKey={groupConfigs.map(item => item.id)} className='opt-search__collapseWrap'>
{
groupConfigs.map((config, index) => {
return (
<Panel
header={
<CPanelHeader
config={config}
index={index}
onSelectChange={val => this.onSelectChange(config.id, val)}
deleteGroupConfig={() => this.deleteGroupConfig(config.id)}
addGroupConfig={() => this.addGroupConfig(config.id)}
/>
}
key={config.id}
showArrow={false}
style={{ background: '#eee' }}
>
<PanelContent
{...this.props}
data={data}
queryFields={queryFields}
config={config}
onPanelContentChange={this.onPanelContentChange}
onChange={this.onChange}
onPressEnter={this.onPressEnter}
/>
</Panel>
)
})
}
</Collapse>
</IFComponent>
)
}
}
export default connect(({ login }) => ({ login }))(SearchGroupField)