mh-sms-web/src/feui/search/SearchGroupField.js
2024-01-22 09:18:38 +08:00

531 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 核心库
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)