antd treeSelect多选实现勾父不勾子

有一个需求要实现,勾选父节点时不勾选子节点勾选子节点则勾选父节点,取消勾选父节点则取消勾选子节点,在网上真没找到合适的解决方案,所以就自己写了一个。非常好用。开箱即用。

最终效果如下

用之前antd的级联选择器,但是级联选择器多选时无法只选父节点,所以我跟换成了antd的树型组件。

开箱即用,代码如下

组建目录

组件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' },
]
  1. 需要设置isTreeData={true}

欢迎大家尝试使用!!自己写的,随问随答

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值