Vue3 el-tree:全选时只返回父节点,半选只返回勾选中的节点(省-市区-县-镇-乡-村-街道)

发布于:2025-05-09 ⋅ 阅读:(15) ⋅ 点赞:(0)
  1. 需求原因:全选时,传给接口的code数据太多了;
    如果加上 check-strictly 父节点与子节点无关联,可以初步满足需求
    效果如下使用了check-strictly的话,tree就没有了半选效果 不好的地方:用户体验感不好,按道理你勾选中了南昌市的话,下面的子节点都需要全部勾选上的,但由于传参数据太多,不得已使用了 check-strictly,问题来了,此时需要满足俩个要求:
    1、全选时只返回父节点
    2、半选只返回所有勾选中的节点
    3、举列子:勾选南昌市和景德镇市只传(3601,3602);勾选南昌市和景德镇市下面的俩个区只传(3601,360201,360202);
    在这里插入图片描述
  2. 具体实现思路:
    1、使用 @check=“handleChange” 组件自带的方法
    在这里插入图片描述
    2、拿到checkedInfo里面的checkedKeys(选中的key)和 halfCheckedKeys(半选的key:父节点)
    3、创建一个tempKeys来存当前半选的父节点,存上一次点击时halfCheckedKeys的值,目的是找出半选变为全选的值,通过这个值,在
    checkedKeys里面去除下面的子节点
    4、去除子节点保留自己的节点,这里用的正则,判断前缀(因为数据格式比较统一)
    5、特别需要注意:这里需要注意当没有半选变为全选的节点时,那么就只需要返回父节点的值,就是tempKeys和halfCheckedKeys 值一样的情况
  3. 代码实现
<el-tree
    :load="loadNode"
    v-loading="loading"
    highlight-current
    auto-expand-parent
    show-checkbox
    ref="treeRef"
    node-key="gridCode"
    lazy
    :props="defaultProps"
    :default-expanded-keys="expandCodes"
    :default-checked-keys="modelValue"
    @check="handleChange"
  ></el-tree>
const handleChange = (node: any, checkedInfo: { checkedKeys: any; halfCheckedKeys: any }) => {
  const { checkedKeys, halfCheckedKeys } = checkedInfo;
  const result = new Set<string>(checkedKeys);
  // 找出"半选变为全选"的父节点
  const becameFullChecked = tempKeys.value.filter(
    key => !halfCheckedKeys.includes(key) && checkedKeys.includes(key)
  );

  if (becameFullChecked.length === 0) {
    // 如果没有半选变为全选的节点,则只返回子节点全部选中的父节点值
    const parentNodes = new Set<string>();
    checkedKeys.forEach((key: string) => {
      // 检查这个key是否是某个父节点的子节点
      const isChild = checkedKeys.some((parentKey: string) => {
        if (parentKey === key) return false;
        return key.startsWith(parentKey);
      });
      if (!isChild) {
        // 如果不是任何节点的子节点,说明它是父节点
        parentNodes.add(key);
      }
    });
    const finalResult = Array.from(parentNodes);
    emit('update:modelValue', finalResult);
    return;
  }

  // 对于这些父节点,去除所有"包含该父节点值的子节点值",但保留父节点本身
  becameFullChecked.forEach(parentKey => {
    // 正则匹配:以parentKey开头,且不是parentKey本身
    const reg = new RegExp(`^${parentKey}(?!$)`);
    Array.from(result).forEach(val => {
      if (reg.test(val) && val !== parentKey) {
        result.delete(val);
      }
    });
  });

  // 更新tempKeys为当前半选的父节点
  tempKeys.value = [...halfCheckedKeys];
  const finalResult = Array.from(result);
  emit('update:modelValue', finalResult);
};
const props = defineProps({
  modelValue: {
    type: Array,
    default: () => []
  },
  onlyMirco: {
    // 是否微网格:这个可以不用
    type: Boolean,
    default: () => true
  }
})
const defaultProps = {
  label: 'gridName',
  isLeaf: 'leaf',
  disabled: (node: { level: any }) => {
    return Number(node.level) < 6 && props.onlyMirco
  },
  lazy: true
}
const loading = ref(false)
const expandCodes = ref<string[]>([])
const tempKeys = ref<string[]>([])
const treeRef = ref()

onMounted(() => {
  console.log('props.modelValue.length', props.modelValue.length, props.modelValue)
  if (props.modelValue.length) {
    getExpandCodes() //获取展开的code列表
    loading.value = true
    tempKeys.value = props.modelValue as string[]
  }
  else {
    tempKeys.value = []
  }
})
const getExpandCodes = () => {
// multiExpandedTreeByCode 这个是接口:替换成自己的
  multiExpandedTreeByCode({
    codes: props.modelValue + '' || userStore.userInfo?.globalCode
  }).then((data: any[]) => {
    let _expandCodes: Iterable<any> = []
    data.forEach((paths) => {
      paths.forEach((ele: { gridCode: any }) => {
        _expandCodes.push(ele.gridCode)
      })
    })
    expandCodes.value = Array.from(new Set(_expandCodes))
  })
}
// 懒加载节点
const loadNode = (node: { data?: any; level?: any }, resolve: (arg0: any[]) => void) => {
  const { level } = node
  if (level) {
    if (node.data.isLeaf || level > 5) {
      resolve([])
      loading.value = false
    } else {
      getGridChildren({
        gridCode: node.data.gridCode
      }).then((data: { level: any }[]) => {
        resolve(data)
        loading.value = false
      })
    }
  } else if (props.onlyMirco) {
    // 如果只是能选微网格,则查询下一级
    getGridChildren({
      gridCode: userStore.userInfo?.globalCode || '36'
    }).then((data: { level: any }[]) => {
      resolve(data)
    })
  } else {
    // 否则本级也要查
    getGrid({
      gridCode: userStore.userInfo?.globalCode || '36'
    }).then((data: any) => {
      resolve([data])
    })
  }
}

上面用到的接口:都需要换成自己的

优化

  1. 存在一个比较深的bug: 假如我新增的时候勾选的是某个乡镇,然后保存,再进行编辑时,全选中他父级的父级,或者再上一级,此时,收集到的结果将包含所有勾选中的节点值
  2. 原因是因为我的 halfCheckedKeys 值没有变化,并且,没有乡镇父一级的code,导致无法去除掉子节点的值
  3. 列如我勾选的乡镇节点是【3602101,3602102,3602103】,此时我再直接勾选江西省(上一级的上一级),此时我的halfCheckedKeys 里面只有【36】,并没有3602,所以删除不了,估传参就是全部的code
  4. 解决办法:找出所有选中的节点,从最上层节点开始判断是否选中,如果选中则拿到该节点的值,没有则往下继续找,判断检查当前节点是否是其他选中节点的子节点
  5. 改写handleChange方法
const handleChange = (node: any, checkedInfo: { checkedKeys: any; halfCheckedKeys: any }) => {
  const { checkedKeys} = checkedInfo;
  // 找出所有选中的节点
  const allCheckedNodes = checkedKeys.map((key: string) => {
    return treeRef.value?.getNode(key);
  }).filter(Boolean);

  // 找出最上层的选中节点
  const topLevelNodes = allCheckedNodes.filter((node: any) => {
    // 检查当前节点是否是其他选中节点的子节点
    return !allCheckedNodes.some((otherNode: any) => {
      if (otherNode === node) return false;
      // 检查otherNode是否是node的祖先节点
      let parent = node.parent;
      while (parent) {
        if (parent === otherNode) return true;
        parent = parent.parent;
      }
      return false;
    });
  });

  // 获取最上层节点的gridCode
  const finalResult = topLevelNodes.map((node: any) => node.data.gridCode);
  emit('update:modelValue', finalResult);
};

网站公告

今日签到

点亮在社区的每一天
去签到