有一个需求要实现,勾选父节点时不勾选子节点,勾选子节点则勾选父节点,取消勾选父节点则取消勾选子节点,在网上真没找到合适的解决方案,所以就自己写了一个。非常好用。开箱即用。
最终效果如下
![](https://img-blog.csdnimg.cn/img_convert/e2f646e2d5b80ec9a12388717b9f2ffd.jpeg)
用之前antd的级联选择器,但是级联选择器多选时无法只选父节点,所以我跟换成了antd的树型组件。
开箱即用,代码如下
组建目录
![](https://img-blog.csdnimg.cn/img_convert/450268bac9c3ad16a437eef6c8cef91a.png)
组件index.tsx代码
import React, { useState, useImperativeHandle, forwardRef } from 'react'
import { Select, Switch, Divider, Checkbox, TreeSelect, Form, Tooltip } from 'antd'
import { TreeProps } from './data'
import css from './index.module.less'
const { Option } = Select
const { SHOW_ALL, SHOW_CHILD, SHOW_PARENT, TreeNode } = TreeSelect
const CheckboxSelect: React.FC<TreeProps> = forwardRef((props, ref) => {
const {
maxTagCount,
placeholder,
data,
onChange,
showArrow,
allowClear,
name,
treeDisabled,
label = null,
onAllCheck,
hideAllCheck,
form,
showSwitch,
onSwitchChange,
checkSwitchWhenCheckAll,
switchDisabled,
switchName,
switchFormItemName,
switchTooltip = false,
switchTooltipTitle = '',
formRules,
width = '100%', //宽度
isTreeData,
treeSelectStyle,
} = props
const [indeterminate, setIndeterminate] = useState(false)
const [checkAll, setcheckAll] = useState(false)
const [switchChecked, setSwitchChecked] = useState(false)
const [value, setValue] = useState([] as any)
const hasChild = data.findIndex((i) => i?.children && i?.children.length > 0) !== -1
// data的结构必须满足以下结构, 可带children实现树形。子级的value必须为 父级id-子级id
const tData = [
{
title: '一级集团',
value: '1',
key: '1',
children: [
{
title: '二级公司1',
value: '1-3',
key: '1-3',
children: [{ title: '三级公司', value: '1-3-1', key: '1-3-1' }],
},
],
},
{ title: '二级公司2', value: '2', key: '2' },
]
useImperativeHandle(ref, () => ({
onChangeSwitch,
setSwitchChecked,
}))
const tProps = {
//open: true,
disabled: treeDisabled,
allowClear: !allowClear ? allowClear : true,
showArrow: !showArrow ? showArrow : true,
treeData: data,
multiple: true,
treeCheckable: true,
showCheckedStrategy: SHOW_ALL,
treeCheckStrictly: isTreeData,
placeholder: placeholder ? placeholder : '请选择',
treeNodeFilterProp: 'title',
style: { width: '100%' },
//value: value,
maxTagCount: maxTagCount || 'responsive',
onChange: (newValue: string[], label, extra) => {
console.log('onChange触发', newValue)
// console.log('onChange触发label', label)
// console.log('onChange触发extra', extra)
if (isTreeData) {
//树形结构时,勾选子级,父级被勾选
if (extra.checked) {
const parent: any = checkParent(newValue, extra.triggerValue)
form.setFieldsValue({ [name]: [...newValue, ...parent] })
} else {
//树形结构时,父级q取消勾选 ,则子级取消勾选
const d: any = cancelChildren(newValue, extra.triggerValue)
form.setFieldsValue({ [name]: d })
}
}
//切换switch
if (extra.checked) {
const num = loopCount(data)
if (newValue.length === num && checkSwitchWhenCheckAll) {
setSwitchChecked(true)
form.setFieldsValue({ [switchFormItemName || '']: true })
}
} else {
setSwitchChecked(false)
form.setFieldsValue({ [switchFormItemName || '']: false })
}
setIndeterminate(!!newValue.length && newValue.length < data.length)
setcheckAll(newValue.length === data.length)
onChange && onChange(newValue)
//setValue(newValue)
},
dropdownRender: (menu) => (
<div className={hasChild ? css.treeSelectRender : css.selectRender}>
{hideAllCheck ? undefined : (
<div>
<div style={{ padding: '8px 8px 8px 8px' }}>
<Checkbox
checked={checkAll}
indeterminate={indeterminate}
onChange={(e) => {
const checked = e.target.checked
onAllCheck && onAllCheck(checked)
setcheckAll(checked)
setIndeterminate(false)
if (checked) {
form.setFieldsValue({ [name]: data.map((it: any) => it.value) })
} else {
form.setFieldsValue({ [name]: [] })
setSwitchChecked(false)
form.setFieldsValue({ [switchFormItemName || '']: false })
}
}}
>
全部
</Checkbox>
</div>
<Divider style={{ margin: '2px 0' }} />
</div>
)}
{menu}
</div>
),
}
const onChangeSwitch = (vl) => {
if (vl) {
if (isTreeData) {
const arr = loop(data)
form.setFieldsValue({
[name]: arr,
})
} else {
form.setFieldsValue({ [name]: data.map((item) => item.value) })
}
setSwitchChecked(true)
setcheckAll(true)
setIndeterminate(false)
} else {
setSwitchChecked(false)
setcheckAll(false)
form.setFieldsValue({ [name]: [] })
}
onSwitchChange && onSwitchChange(vl)
}
const loop = (data) => {
let arr = [] as any[]
data.forEach((item) => {
arr.push({ label: item.title, value: item.value })
if (item.children && item.children.length > 0) {
arr = arr.concat(loop(item.children))
}
})
return arr
}
//返回data的数量
const loopCount = (data) => {
let num = 0
data.forEach((item, i) => {
num++
if (item.children && item.children.length > 0) {
num += loopCount(item.children)
}
})
return num
}
//勾选子则勾选父
const checkParent = (newValue, triggerValue) => {
let arr = [] as any[]
if (triggerValue.indexOf('-') !== -1) {
const parentId = triggerValue.split('').reverse().join('').replace(/\d+\-/, '').split('').reverse().join('')
let parentName = ''
//父级未被勾选
if (newValue.findIndex((i) => i.value === parentId) == -1) {
data.forEach((i) => {
if (i.value === parentId) {
parentName = i.title
}
})
arr.push({ label: parentName, value: parentId })
}
arr = arr.concat(checkParent(newValue, parentId))
}
return arr
}
//取消勾选父则取消勾选子
const cancelChildren = (newValue, triggerValue) => {
let arr = [] as any[]
const reg = new RegExp(`^${triggerValue}-`)
arr = newValue.filter((i) => {
if (!reg.test(i.value)) {
return i
}
})
return arr
}
const buildTree = () => {
return name ? (
<Form.Item
colon={false}
name={name}
style={{ width: width, margin: '0' }}
rules={formRules}
label={label}
//
shouldUpdate={(a, b) => {
//解决恢复默认设置时,全选没勾上的问题
if (switchFormItemName) {
if (b[switchFormItemName]) {
setcheckAll(true)
}
}
return true
}}
// noStyle={noStyle}
>
<TreeSelect {...tProps} />
</Form.Item>
) : (
<TreeSelect {...tProps} />
)
}
const buildSwitchTree = () => {
return (
<div className={`${css.treeSelect}`} style={treeSelectStyle}>
{buildTree()}
<div className={css.treeSelectRight}>
<Tooltip
visible={switchTooltip ? undefined : false}
placement="top"
title={<div style={{ color: '#13273A' }}>{switchTooltipTitle}</div>}
color="#ffffff"
>
<Form.Item
colon={false}
name={switchFormItemName}
initialValue={false}
valuePropName="checked"
style={{ width: '100%', margin: '0' }}
>
<Switch
disabled={switchDisabled}
style={{ marginLeft: '12px', marginRight: '8px' }}
checked={switchChecked}
onChange={onChangeSwitch}
></Switch>
</Form.Item>
</Tooltip>
<span className={css.treeSelectSpan}>{switchName || '全部'}</span>
</div>
</div>
)
}
return showSwitch ? buildSwitchTree() : buildTree()
})
export default CheckboxSelect
组件index.module.less代码
.treeSelect {
display: flex;
.treeSelectRight {
display: flex;
align-items: center;
height: 32px;
}
.treeSelectSpan {
width: 80px;
white-space: nowrap;
}
}
.treeSelectRender {
:global {
.ant-select-tree {
.ant-select-tree-list {
.ant-select-tree-list-holder {
div {
.ant-select-tree-list-holder-inner {
.ant-select-tree-treenode {
.ant-select-tree-switcher {
width: 30px !important;
}
}
.ant-select-tree-treenode-switcher-close:hover {
background-color: #ebf1fd;
}
.ant-select-tree-treenode:hover {
background-color: #ebf1fd;
}
}
}
}
}
.ant-select-tree-node-content-wrapper:hover {
background-color: #ebf1fd;
}
}
}
}
.selectRender {
:global {
.ant-select-tree {
.ant-select-tree-list {
.ant-select-tree-list-holder {
div {
.ant-select-tree-list-holder-inner {
.ant-select-tree-treenode {
.ant-select-tree-switcher {
width: 8px !important;
}
}
.ant-select-tree-treenode-switcher-close:hover {
background-color: #ebf1fd;
}
.ant-select-tree-treenode:hover {
background-color: #ebf1fd;
}
}
}
}
}
.ant-select-tree-node-content-wrapper:hover {
background-color: #ebf1fd;
}
}
}
}
组件的类型文件data.d.ts
import { ReactNode } from 'react'
export interface TreeProps {
//name: string | string[] //兼容FormModal
name: any
label?: string
form: any //表单form
formRules?: any
ref?: any
treeDisabled?: boolean
switchDisabled?: boolean
showSwitch?: boolean //是否启用switch
switchName?: string
switchFormItemName?: string
switchTooltip?: boolean
switchTooltipTitle?: string
maxTagCount?: number | 'responsive'
placeholder?: string
data: any[]
onChange?: (v: any) => void
onSwitchChange?: (v: any) => void
checkSwitchWhenCheckAll?: boolean //勾选所有数据时是否将switch勾上
onAllCheck?: (v: any) => void
hideAllCheck?: boolean
showArrow?: boolean
allowClear?: boolean
width?: any //宽度
isTreeData?: boolean
treeSelectStyle?: any
}
重头戏!组件使用方法
其中备注的代码是此组件的额外功能,有兴趣可以研究。
<CheckboxSelect
treeDisabled={false}
switchDisabled={false}
//ref={scopeRef}
isTreeData={true}
data={[
{
title: '一级集团',
value: '1',
key: '1',
children: [
{
title: '二级公司1',
value: '1-3',
key: '1-3',
children: [{ title: '三级公司', value: '1-3-1', key: '1-3-1' }],
},
],
},
{ title: '二级公司2', value: '2', key: '2' },
]}
label="适用企业"
form={form}
width={630}
//onChange={(v) => scopeChange()}
//onSwitchChange={(v) => scopeChange()}
//checkSwitchWhenCheckAll={true}
//showSwitch={true} //可以设置成false关掉switch滑块
hideAllCheck={true} //是否关闭下拉列表全选功能
name="companyScopeList"
formRules={[{ required: true, message: '请选择适用企业' }]}
switchName="适用全部企业"
placeholder="请选择适用企业"
treeSelectStyle={{ margin: '0 0 24px 4px' }}
/>
注意!!!!若传入的data是树形
1.使用时,则必须满足以下格式,这是为了避免子选项和父选项出现key相同的尴尬情况
[
{
title: '一级集团',
value: '1',
key: '1',
children: [
{
title: '二级公司1',
value: '1-3',
key: '1-3',
children: [{ title: '三级公司', value: '1-3-1', key: '1-3-1' }],
},
],
},
{ title: '二级公司2', value: '2', key: '2' },
]
需要设置isTreeData={true}
欢迎大家尝试使用!!自己写的,随问随答