iview tree ,解决子节点禁用状态下,父节点勾选依旧可以操作子节点

开篇之前特别想吐槽iview tree是真的难用,tui~~

问题

针对子节点禁用状态下,父节点勾选依旧可以操作子节点。翻阅了源码,发现实现逻辑有问题。
tree的勾选更新逻辑:勾选一个节点后向上节点和下节点传递更新的,此次修改涉及的源代码如下:

kotlin 复制代码
// 向下更新
updateTreeDown(node, changes = {}) {
            if (this.checkStrictly) return;

            for (let key in changes) {
                this.$set(node, key, changes[key]);
            }
            if (node[this.childrenKey]) {
                node[this.childrenKey].forEach(child => {
                    this.updateTreeDown(child, changes);
                });
            }
        },
  // 向上更新
  updateTreeUp(nodeKey){
            const parentKey = this.flatState[nodeKey].parent;
            if (typeof parentKey == 'undefined' || this.checkStrictly) return;

            const node = this.flatState[nodeKey].node;
            const parent = this.flatState[parentKey].node;
            if (node.checked == parent.checked && node.indeterminate == parent.indeterminate) return; // no need to update upwards

            if (node.checked == true) {
                this.$set(parent, 'checked', parent[this.childrenKey].every(node => node.checked));
                this.$set(parent, 'indeterminate', !parent.checked);
            } else {
                this.$set(parent, 'checked', false);
                this.$set(parent, 'indeterminate', parent[this.childrenKey].some(node => node.checked || node.indeterminate));
            }
            this.updateTreeUp(parentKey);
        },
        
// 勾选逻辑
handleCheck({ checked, nodeKey }) {
        // 获取节点
        const node = this.flatState[nodeKey].node;
        this.$set(node, "checked", checked);
        this.$set(node, "indeterminate", false);

        this.updateTreeUp(nodeKey); // propagate up
        this.updateTreeDown(node, { checked, indeterminate: false }); // reset `indeterminate` when going down

        this.$emit("on-check-change", this.getCheckedNodes(), node);
    },

可以发现 向下更新的时候压根没判断是否是disabled状态,直接就改变勾选了

vbnet 复制代码
for (let key in changes) {
 this.$set(node, key, changes[key]);
 }
   

解决方案:在main.js里面直接修改源码,判断操作是checked 同时 是禁用状态 checked状态不变

kotlin 复制代码
import ViewUI, { Tree } from "view-design";
// 向下更新数据
Tree.methods.updateTreeDown = function (node, changes = {}) {
  // 是否父子关联
  if (this.checkStrictly) return;
  // ### 调整范围
  // 循环操作的key
  for (let key in changes) {
    // after #6121
    // 操作是checked 同时 是禁用状态  checked状态不变
    if (key === "checked" && node.disabled) {
      this.$set(node, key, node.checked);
    } else {
      // 否则 赋值传递的值
      this.$set(node, key, changes[key]);
    }
  }
  // ###
  
  // 判断是否有子节点 继续上续操作
  if (node[this.childrenKey]) {
    node[this.childrenKey].forEach((child) => {
      this.updateTreeDown(child, changes);
    });
  }
};

但是随后会引发两个问题:

1、iview tree 勾选逻辑是,勾选 -> 不勾选,半勾选 -> 不勾选

其他组件库 tree 勾选逻辑是,勾选 -> 不勾选,半勾选 -> 全选

需要调整组件库勾选逻辑,按照iview tree勾选逻辑,一旦有一个子节点 checked:true,disabled:true父节点完全无法交互了,因为半勾选 -> 不勾选,但是代码逻辑里面父节点勾选是不操作disabled:true的子节点
解决方案: 勾选逻辑改为,勾选 -> 不勾选,半勾选 -> 全选

kotlin 复制代码
import ViewUI, { Tree } from "view-design";
// 勾选逻辑
Tree.methods.updateTreeDown =function ({ nodeKey }) {
  // 获取节点
  const node = this.flatState[nodeKey].node;
  // ### 调整范围
  let checked = false;
  if (node.checked) {
    checked = false;
  } else if (node.indeterminate) {
    checked = true;
  } else {
    checked = true;
  }
  // ###
    
  this.$set(node, "checked", checked);
  this.$set(node, "indeterminate", false);
  this.updateTreeUp(nodeKey);
  this.updateTreeDown(node, { checked, indeterminate: false });
  this.$emit("on-check-change", this.getCheckedNodes(), node);
};

2、向上更新时出现问题:当有一个子节点checked:false,disabled:true,其他子节点全部都checked:true,disabled:false。这个时候事实上应该父节点是勾选状态,毕竟子节点禁用了就不应该参与勾选了。否则的话,如果父节点是半勾选状态,再次点击应该是 半勾选 -> 全选,但是由于上面的调整导致向下传递失败,无法到达勾选状态(在查看源码中发现,有些版本的源码是解决了这个问题的,根据实际情况调整)
解决方案: 将checked:true,disabled:false。状态的子节点不纳入父节点交互

kotlin 复制代码
 import ViewUI, { Tree } from "view-design";
// 向上更新
Tree.methods.updateTreeUp = function (nodeKey) {
  // 获取父级key
  const parentKey = this.flatState[nodeKey].parent;
  // 判断是否继续向上更新
  if (typeof parentKey == "undefined" || this.checkStrictly) return;
  // 获取当前节点 和 父级节点
  const node = this.flatState[nodeKey].node;
  const parent = this.flatState[parentKey].node;
  // 判断当前节点 勾选状态和 显示状态是否完全一致
  if (
    node.checked == parent.checked &&
    node.indeterminate == parent.indeterminate
  )
    return; // ##无需向上更新
  // 节点为勾选状态
  // ### 调整范围
  if (node.checked == true) {
    // #6121
    // 操作了一个子节点后判断所有子节点的状态后反向更新父级
    this.$set(
      parent,
      "checked",
      // 所有子节点都勾选 或者 子节点disabled都存在
      parent[this.childrenKey].every(
        (node) => node.checked || node.disabled !== undefined
      )
    );
    // 因为子节点必然勾选一个 所以样式根据父节点勾选状态展示
    this.$set(parent, "indeterminate", !parent.checked);
  } else {
    // 以为子节点必然有一个没勾选,父级必然不勾选
    this.$set(parent, "checked", false);

    this.$set(
      parent,
      "indeterminate",
      // 所有子节点但凡有一个勾选或者半勾选就向上更新 半勾选状态
      parent[this.childrenKey].some(
        (node) => node.checked || node.indeterminate
      )
    );
  }
  // ### 
  
  // 继续向上更新
  this.updateTreeUp(parentKey);
};

到此全局性的解决了这个问题

但是如果在已经稳定运行的项目中想解决这个问题,不一定能直接全局性修改,毕竟不知道其他模块的同事是否有其他调整

局部解决方案:

在局部解决的思路是一样的, this.$refs.tree.updateTreeUp 直接修改,但是在 handleCheck 方法中失效了。可恶 再次查看源码发现,代码中子组件向父组件传递是封装一个混入方法 this.dispatch

ini 复制代码
 // 向指定祖先节点传递
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root; // 从父组件或根开始搜索
 let name = parent.$options.name; // 获取当前父组件的名称

 // 遍历父组件链
 while (parent && (!name || name !== componentName)) {
  parent = parent.$parent; // 向上移动到下一个父组件
  if (parent) {
      name = parent.$options.name; // 更新当前父组件的名称
  }
  }
  // 找到目标组件后触发事件
   if (parent) {
 }
},

 

tree handleCheck 调用方式

kotlin 复制代码
mounted () {
     this.$on('on-check', this.handleCheck);
 }

结合上面的代码 发现 子组件调用 dispatch ,拿到当前组件去实例找父组件,然后循环往上找,一直找到组件name是目标name的组件,然后在目标组件(也就是父组件)调用 emit,然后使用this.on()捕获 。

由于是 this.$on() 的方式截取自身的方法,所以需要改变一下策略(在看源码才知道 , emit,on 不只是父子组件可以用,本组件内也可以使用)

kotlin 复制代码
// 先删除再装填方法
this.$refs.tree.$off("on-check", this.handleCheck);
const handleCheck = function ({ nodeKey }) {
  // 获取节点
  const node = this.flatState[nodeKey].node;
  let checked = false;
  if (node.checked) {
    checked = false;
  } else if (node.indeterminate) {
    checked = true;
  } else {
    checked = true;
  }
  this.$set(node, "checked", checked);
  this.$set(node, "indeterminate", false);
  this.updateTreeUp(nodeKey);
  this.updateTreeDown(node, { checked, indeterminate: false });
  this.$emit("on-check-change", this.getCheckedNodes(), node);
};
this.$refs.tree.$on("on-check", handleCheck);

完整局部解决代码:

kotlin 复制代码
this.$refs.tree.$off("on-check", this.handleCheck);
const handleCheck = function ({ nodeKey }) {
  // 获取节点
  const node = this.flatState[nodeKey].node;
  let checked = false;
  if (node.checked) {
    checked = false;
  } else if (node.indeterminate) {
    checked = true;
  } else {
    checked = true;
  }
  this.$set(node, "checked", checked);
  this.$set(node, "indeterminate", false);
  this.updateTreeUp(nodeKey);
  this.updateTreeDown(node, { checked, indeterminate: false });
  this.$emit("on-check-change", this.getCheckedNodes(), node);
};
this.$refs.tree.$on("on-check", handleCheck);
// 向上更新树
this.$refs.tree.updateTreeUp = function (nodeKey) {
  // 获取父级key
  const parentKey = this.flatState[nodeKey].parent;
  // 判断是否继续向上更新
  if (typeof parentKey == "undefined" || this.checkStrictly) return;
  // 获取当前节点 和 父级节点
  const node = this.flatState[nodeKey].node;
  const parent = this.flatState[parentKey].node;
  // 判断当前节点 勾选状态和 显示状态是否完全一致
  if (
    node.checked == parent.checked &&
    node.indeterminate == parent.indeterminate
  )
    return; // ##无需向上更新
  // 节点为勾选状态
  if (node.checked == true) {
    // #6121
    // 操作了一个子节点后判断所有子节点的状态后反向更新父级
    this.$set(
      parent,
      "checked",
      // 所有子节点都勾选 或者 子节点disabled都存在
      parent[this.childrenKey].every(
        (node) => node.checked || node.disabled !== undefined
      )
    );
    // 因为子节点必然勾选一个 所以样式根据父节点勾选状态展示
    this.$set(parent, "indeterminate", !parent.checked);
  } else {
    // 以为子节点必然有一个没勾选,父级必然不勾选
    this.$set(parent, "checked", false);

    this.$set(
      parent,
      "indeterminate",
      // 所有子节点但凡有一个勾选或者半勾选就向上更新 半勾选状态
      parent[this.childrenKey].some(
        (node) => node.checked || node.indeterminate
      )
    );
  }
  // 继续向上更新
  this.updateTreeUp(parentKey);
};
// 向下更新数据
this.$refs.tree.updateTreeDown = function (node, changes = {}) {
  // 是否父子关联
  if (this.checkStrictly) return;
  // 循环操作的key
  for (let key in changes) {
    // after #6121
    // 操作是checked 同时 是禁用状态  checked状态不变
    if (key === "checked" && node.disabled) {
      this.$set(node, key, node.checked);
    } else {
      // 否则 赋值传递的值
      this.$set(node, key, changes[key]);
    }
  }

  // 判断是否有子节点 继续上续操作
  if (node[this.childrenKey]) {
    node[this.childrenKey].forEach((child) => {
      this.updateTreeDown(child, changes);
    });
  }
};

完结撒花~~~~

相关推荐
200不是二百3 分钟前
Vuex详解
前端·javascript·vue.js
滔滔不绝tao7 分钟前
自动化测试常用函数
前端·css·html5
码爸35 分钟前
flink doris批量sink
java·前端·flink
深情废杨杨36 分钟前
前端vue-父传子
前端·javascript·vue.js
J不A秃V头A2 小时前
Vue3:编写一个插件(进阶)
前端·vue.js
司篂篂2 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客2 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹3 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码3 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
天下无贼!4 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue