既然el-tree自带复选框有bug,那就自己实现复选框

前言

项目中使用el-tree带复选框的,如果数据中存在禁用的节点 则需要点击多次复选框才能取消或选中

issue地址:github.com/element-plu...

实现效果

预览效果:admin.occtai.com/unclassifie...

实现代码

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

vue 复制代码
<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部分代码

vue 复制代码
<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的部分代码

js 复制代码
// 复选框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~

相关推荐
薛定谔的猫-菜鸟程序员8 分钟前
Electron开发超详细教程(从入门到放弃)
前端·javascript·electron
最强小杰30 分钟前
DeepSeek V4 Flash + Codex CLI 接入教程:原生 Responses API 适配,从配置到跑通收藏这篇就够了(2026)
java·前端·python·ai
静水leo1 小时前
使用 Whistle 拦截接口并模拟后端响应
前端
掘金者阿豪1 小时前
SQL Server数据迁移完成后,凌晨的数据库告警该怎么查?
前端·后端
小郎君6581 小时前
Vue 多标签页的加载态设计:为什么我没有用 Suspense 实现 PageLoading
vue.js·算法
JudithHuang1 小时前
React 嵌套路由与布局
前端·react.js·arcgis·react·react router
htzyl2061 小时前
前端阶梯——第四十三章、实战二:待办清单To-Do
前端·javascript·html
yilaisai1 小时前
前端开发遇到的一些细节问题整理
vue.js
摹客2 小时前
【3D设计】界面设计中的3D元素:应用场景+5款工具横评(含Three.js)
前端