531 lines
17 KiB
JavaScript
531 lines
17 KiB
JavaScript
// 核心库
|
||
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)
|