el-tree实现自定义复选框

799 阅读2分钟

前言

项目中使用el-tree带复选框的,如果数据中存在禁用的节点 则需要点击多次复选框才能取消或选中
issue地址:github.com/element-plu…

实现效果

动画.gif 预览效果:admin.occtai.com/unclassifie…

实现代码

把官网的示例复制过来自己写个el-checkbox

<el-tree :data="dataList" ref="treeRef" :props="treeProps" v-bind="attrs">
  <template #default="{ data }">
    <el-checkbox
      @click.stop
      :indeterminate="data.indeterminate"
      v-model="data.checked"
      :disabled="data[treeProps.disabled]"
      @change="handlchange($event, data)" />
    <slot :data="data"></slot>
  </template>
</el-tree>

script部分代码

<script setup>
import { computed, ref, useAttrs, watch } from 'vue'
import { ElTree } from 'element-plus'

import useCustomCheckBox from './useCustomCheckBox'
import {
  flattenTree,
  findAllParentNode,
  findAllSubNode,
} from '@/utils/dataProcessing.js'

const props = defineProps({
  props: {
    type: Object,
    default() {
      return {}
    },
  },
  data: {
    type: Array,
    default() {
      return []
    },
  },
})
const emit = defineEmits(['checkBoxChange', 'updNode'])
const attrs = useAttrs()
const dataList = ref([])
const treeRef = ref(null)

const treeProps = computed(() => {
  return {
    ...ElTree.props.props.default(),
    ...props.props,
  }
})

// 扁平数据
const flatDataList = computed(() => {
  return flattenTree(dataList.value)
})


const handlchange = (checked, data) => {
  emit('checkBoxChange', checked, data)
  checkBoxChange(checked, data)
}
const uniqueKey = computed(() => {
  return attrs['node-key'] || attrs['nodeKey']
})
const {
  checkBoxChange,
  getCheckedNodes,
  getCheckedKeys,
  setCheckedKeys,
  setCheckedNodes,
  clearAllSelection,
} = useCustomCheckBox({
  config: treeProps.value,
  attrs,
  uniqueKey,
  dataList,
})

// 这里实现用于给过滤后满足条件的节点包括对应父级打个标识,实现全选时只选择这批数据
const filter = async (val) => {
  flatDataList.value.forEach((item) => {
    if (
      val &&
      item[treeProps.value.label]
        .toLocaleLowerCase()
        .includes(val.toLocaleLowerCase())
    ) {
      item.display = true
    } else {
      item.display = !val || false
    }
  })

  flatDataList.value.forEach((item) => {
    const parents = findAllParentNode(
      dataList.value,
      item[uniqueKey.value],
      false,
      uniqueKey.value,
      treeProps.value.children
    )
    parents.forEach((parent) => {
      parent.display = parent.children.some((n) => n.display)
      parent[treeProps.value.disabled] = parent.children
        .filter((n) => n.display)
        .every((n) => n[treeProps.value.disabled])
    })
  })

  treeRef.value.filter(val)
  emit('updNode')
}

watch(
  () => props.data,
  (val) => {
    dataList.value = val
  },
  {
    immediate: true,
  }
)

defineExpose({
  treeRef,
  filter,
  getCheckedNodes,
  getCheckedKeys,
  setCheckedKeys,
  setCheckedNodes,
  clearAllSelection,
})

defineOptions({
  name: 'CustomElTreeCheckBox',
})
</script>

useCustomCheckBox的部分代码

// 复选框change触发 处理父级选中或半选
const checkBoxChange = (checked, currentData) => {
  if (isCheckStrictlyTrue.value) return
  const parentData = findParentData(
    dataList.value,
    currentData[uniqueKey.value],
    uniqueKey.value,
    config.children
  )
  // 没有父级或有子级
  if (!parentData || currentData?.[config.children]?.length) {
    handleSubNodeCheckedAndIndeterminate(currentData, checked)
    currentData.indeterminate =
      hasAnyChildChecked(currentData) && !isAllChildrenChecked(currentData)
  }

  if (!parentData) return

  const allParentItems = findAllParentNode(
    dataList.value,
    currentData[uniqueKey.value],
    false,
    uniqueKey.value,
    config.children
  )
  allParentItems.reverse().forEach((item) => {
    const anyChecked = hasAnyChildChecked(item)
    item.indeterminate = anyChecked && !isAllChildrenChecked(item)
    item.checked = isAllChildrenChecked(item, anyChecked)
  })
}

// 清空时clearAll为true
const setCheckedKeys = (targetIds, checked = true, clearAll = false) => {
  // 扁平数据然后反转数据 反转主要为了处理对应父级的选中或半选状态
  let data = flattenTree(dataList.value)
  data.reverse().forEach((node) => {
    if (node.display !== false || clearAll) {
      if (checked === false) {
        // 如果targetIds存在则为false 否则为自身的值
        node.checked = targetIds.includes(node[uniqueKey.value])
          ? checked
          : node.checked
      } else {
        node.checked = targetIds.includes(node[uniqueKey.value])
      }

      if (node?.[config.children]?.length) {
        if (!isCheckStrictlyTrue.value) {
          if (node.checked) {
            handleSubNodeCheckedAndIndeterminate(
              node,
              checked,
              checked ? null : targetIds
            )
          }
          const anyChildChecked = hasAnyChildChecked(node)
          node.indeterminate = anyChildChecked && !isAllChildrenChecked(node)
          node.checked = isAllChildrenChecked(node, anyChildChecked)
        }
      }
    }
  })

总结

简单的实现了下自定义复选框,也就是递归处理数据,如有优解方式欢迎讨论学习
End~