vue3 实现对el-tree的增删改查

2023-10-26 17:27:17 浏览数 (2)

el-tree说简单很简单,说难也难,毕竟里面很多属性需要灵活运用。最近项目开发中运用到el-tree的相关操作,整理如下:

1. 先把实现的页面展示:

鼠标划入时的状态

点击新增时的状态

点击编辑时的状态

2. 需求介绍:

代码语言:javascript复制
1)点击新增一级在el-tree的最底部出现输入框
2)鼠标划入树形节点时出现`...`,鼠标划入`...`时出现新增修改删除
3)点击新增时,输入框出现在当前节点的子节点的最下方,且输入框聚焦
4)现在el-tree的层级最多为5级,在第5级时只能出现编辑和删除,不可出现新增。

3.思路讲解

3.1出现...

鼠标划入的时候出现...,在鼠标未输入时设置标签为visibility: hidden;当鼠标划入时设置visibility: visible;

3.2给树节点添加属性

代码语言:javascript复制
const filterAddParms = (tree, paramsName) => {
    if (!tree || !Array.isArray(tree)) return null; // 出口 3-1
    return tree.map((item) => {
      // 2-4 1-4
      item[paramsName] = false; // 1-1, 2-1
      item.children = filterAddParms(item.children, paramsName); // 1-2 2-2
      return item; // 2-3 1-3
    });
  };

3.3添加完节点后树形结构不自动收起

这块遇到了一个坑,起初default-expand-all来控制节点的展开与收缩,但是添加完节点后改变default-expand-all绑定的值使节点打开却一直是自动关闭,后查阅文档需通过default-expanded-keys来实现默认展开的key数组

4.完整代码

html

代码语言:javascript复制
<div class="add-folder" @click="addNodeTreeList">
   <span class="add-folder-span">
      <svg-icon icon-class="addIcon"></svg-icon>
         新增一级
   </span>
</div>
<div class="all">全部分类</div>
<div class="tree-show">
    <el-tree
    ref="treeRef"
    :data="treeList"
    node-key="id"
    :props="defaultProps"
    @node-click="handleNodeClick"
    @node-collapse="nodeCollapse"
    :default-expand-all="nodeShow"
    :default-expanded-keys="defaultExpandedkeys"
    accordion
    >
      <template #default="{ node, data }">
         <div class="custom-tree-node">
         <span v-if="!data.isAddNode">{{ node.label }}</span>
         <el-dropdown class="edit-tree-dropdown" v-if="!data.isAddNode">
            <span>
               <a class="showEllipsis"> ... </a>
            </span>
            <template #dropdown>
               <el-dropdown-menu>
                 <el-dropdown-item
                   ><span @click.stop="addAllNode(node, data)"
                     >新增</span
                    ></el-dropdown-item
                >
                <el-dropdown-item @click.stop="editAllNode(node, data)"
                   >编辑</el-dropdown-item
                >
                <el-dropdown-item @click.stop="delAllNode(node, data)"
                   >删除</el-dropdown-item
                >
                </el-dropdown-menu>
             </template>
          </el-dropdown>
          //点击新增时的输入框
          <el-input
           v-model="newChildNode"
           v-if="data.isAddNode"
           @keyup.enter.stop.native="handleAddEnter(node, data)"
           @blur="removeTreeNode(node, data)"
           @change="handleAddNode(node, data)"
           ref="addRef"
           class="add-new-child-node">
          </el-input>
          //点击修改时的输入框
          <el-input
             v-model="data.name"
             v-show="data.isEditNode"
             @change="handleEditNode(node, data)"
             @keyup.enter.stop.native="handleEditEnter(node.data)"
             class="edit-child-node">
           </el-input>
         </div>
       </template>
     </el-tree>
     
     //点击新增一级显示的输入框
     <div class="add-input" v-if="inputShow">
        <el-input
           v-model="addNodeTree"
           placeholder="输入中"
           @change="addClassification"
           @keyup.enter.stop.native="handleEnter"
           ref="newNodeRef"
           ></el-input>
        </div>
  </div>

js

代码语言:javascript复制
<script setup>
import { reactive, ref, onMounted, nextTick } from "vue";
// 获取列表
const treeList = ref([]);
const getTreeList = () => {
  http.get("/获取树列表接口").then((res) => {
    treeList.value = res.data.children;
    filterAddParms(treeList.value, "isOper");
  });
};

//点击树节点时触发的方法
const input = ref("");
const parentId = ref("");
const handleNodeClick = (node, data) => {
  parentId.value = node.parentId;
};


// 点击新增一级(新增一级时parentid默认为0)
const newNodeRef = ref(null);
const addNodeTree = ref("");
const inputShow = ref(false);
const addNodeTreeList = () => {
  inputShow.value = true;
  setTimeout(() => {
    newNodeRef.value && newNodeRef.value.focus();
  }, 800);
};

//新增一级出现输入框时通过change事件输入内容请求方法添加节点
const addClassification = () => {
  const data = {
    name: addNodeTree.value,
    parentId: 0,
  };
  console.log(addNodeTree.value === "");
  if (addNodeTree.value != "") {
    http.post("新增一级保存接口", data).then((res) => {
      console.log(res);
      inputShow.value = false;
      getTreeList();
      addNodeTree.value = "";
    });
  } else {
    inputShow.value = false;
  }
};
//此方法为用户在输入框不输入任何东西时回车输入框不显示
const handleEnter = () => {
  inputShow.value = false;
};

//节点被关闭时触发的事件,节点关闭时输入框也消失
const nodeCollapse = (data) => {
  console.log("data: ", data);
  // 如果有input框, 删除节点
  if (nodeShow.value) {
    data.children.pop();
    nodeShow.value = false;
  }
};


//点击新增,出现输入框
const nodeShow = ref(false);
const addRef = ref(null);
const newChildNode = ref("");
const addAllNode = (node, data) => {
  if (nodeShow.value) return;
  nodeShow.value = true;
  if (!data.children || !Array.isArray(data.children)) {
    data.children = [];
  }
  // 展开
  console.log("treeRef.value: ", data.id, treeRef.value);
  //使树形结构图展开
  node.expanded = true;
  //为了使输入框出现在最下层。为树结构添加节点
  nextTick(() => {
    data.children.push({
      isAddNode: true,
      isOper: null,
      name: "",
      parentId: data.id,
    });
    setTimeout(() => {
      addRef.value && addRef.value.focus();
    }, 800);
  });
  console.log("data: ", data);
};
// 删除节点。添加完节点后删除节点
function removeTreeNode(node, data) {
  const parent = node.parent;
  const children = parent.data.children || parent.data;
  const index = children.findIndex((d) => d.id === data.id);
  children.splice(index, 1);
  treeList.value = [...treeList.value];
  nodeShow.value = false;
}
//输入框输入内容添加数据
const defaultExpandedkeys = ref([]);
const handleAddNode = (node, data) => {
  defaultExpandedkeys.value.push(node.data.parentId);
  const params = {
    parentId: data.parentId,
    name: newChildNode.value,
  };
  console.log("params: ", params);
  http.post("点击新增接口", params).then((res) => {
    console.log(addRef.value);
    nextTick(() => {
      data.isAddNode = false;
      addRef.value && addRef.value.focus();
    });
    getTreeList();
    newChildNode.value = "";
  });
};
//此方法为用户在输入框不输入任何东西时回车输入框不显示
const handleAddEnter = (node, data) => {
  // if(data.isAddNode) return
  nextTick(() => {
    data.isAddNode = false;
    addRef.value && addRef.value.focus();
  });
};


//点击编辑,出现输入框
const editAllNode = (node, data) => {
  nextTick(() => {
    data.isEditNode = true;
  });
};
//编辑完之后请求接口保存编辑完的数据
const handleEditNode = (node, data) => {
  console.log("node: ", node);
  console.log(data);
  const editParams = {
    name: data.name,
    id: data.id,
    parentId: data.parentId,
  };
  console.log(editParams);
  http.put("点击编辑接口", editParams).then((res) => {
    nextTick(() => {
      data.isEditNode = false;
    });
    getTreeList();
  });
};
//此方法为用户在输入框不输入任何东西时回车输入框不显示
const handleEditEnter = (node, data) => {
  nextTick(() => {
    data.isEditNode = false;
  });
};
</script>

//删除节点
const delAllNode = (node, data) => {
  console.log("data: ", data);
  // nextTick(() => {
  //   data.isDelNode = true;
  // });
  http.delete(`删除接口/${data.path}`).then((res) => {
    console.log("res: ", res);
    getTreeList();
  });
};
代码语言:javascript复制
引入的js文件
export default function useTree() {
  // 给树的节点添加属性
  // 递归
  const filterAddParms = (tree, paramsName) => {
    if (!tree || !Array.isArray(tree)) return null; // 出口 3-1
    return tree.map((item) => {
      // 2-4 1-4
      item[paramsName] = false; // 1-1, 2-1
      item.children = filterAddParms(item.children, paramsName); // 1-2 2-2
      return item; // 2-3 1-3
    });
  };
  return {
    filterAddParms
  }
}

css

代码语言:javascript复制
.content-left {
      width: 280px;
      border-radius: 20px;
      margin-right: 20px;
      background: rgba(255, 255, 255, 0.6);
      height: 100%;
      .title {
        padding-top: 15px;
        font-size: 14px;
        font-weight: 500;
        color: #333333;
        padding-left: 20px;
      }
      .el-input {
        padding-left: 20px;
        :deep(.el-input__wrapper) {
          width: 240px;
          height: 28px;
          background: rgba(255, 255, 255, 0.6);
          border-radius: 8px;
          border: 1px solid #e1e1f0;
          margin-right: 20px;
          margin-top: 10px;
        }
        :deep(.el-input__inner) {
          font-size: 12px;
          color: #8894a8;
        }
      }
      .el-divider {
        margin-top: 12px;
        margin-bottom: 15px;
      }
      .tree-show {
        padding-left: 15px;
        overflow-y: auto;
        height: calc(100% - 180px);
        .el-tree {
          position: relative;
          background: none;

          :deep(.el-tree-node) {
            position: relative;
          }

          :deep(.el-tree-node__content) {
            height: 34px;
            .el-input__wrapper {
              margin-top: 0;
            }
            .custom-tree-node {
              position: relative;
              display: flex;
              justify-content: space-between;
              align-items: center;
              padding-right: 20px;
              width: 100%;
              font-size: 14px;
              .edit-tree-dropdown {
                position: relative;
                visibility: hidden;
                .showEllipsis {
                  width: 18px;
                  height: 18px;
                  background: #fff;
                  border-radius: 50%;
                  vertical-align: text-top;
                  line-height: 14px;
                  display: block;
                  text-align: center;
                  color: #5d63da;
                }
              }
            }
            //此处为鼠标输入时显示三个点
            &:hover {
              .custom-tree-node {
                .edit-tree-dropdown {
                  visibility: visible;
                }
              }
            }
          }

          .add-new-child-node {
            z-index: 20;
            width: calc(100% - 10px);
            padding-left: 0px;
            background: rgba(255, 255, 255, 0.6);
            :deep(.el-input__wrapper) {
              background: #fff;
            }
          }
          .edit-child-node {
            width: calc(100% - 10px);
            background: rgba(255, 255, 255, 0.6);
            position: absolute;
            padding-left: 10px;
            left: -15px;
            // top: 0;
            :deep(.el-input__wrapper) {
              background: #fff;
            }
          }
        }
        ::v-deep {
          /* 设置树形最外层的背景颜色和字体颜色 */
          .is-leaf::before {
            display: none;
          }
        }
      }
      .add-folder {
        line-height: 32px;
        height: 32px;
        font-size: 14px;
        cursor: pointer;
        border-left: 3px solid transparent;
        .add-folder-span {
          background-repeat: no-repeat;
          background-position: 0px 50%;
          margin-left: 8px;
          color: #5d63da;
          .svg-icon {
            font-size: 18px;
            margin-left: 6px;
            cursor: pointer;
            vertical-align: -4px;
          }
        }
      }
      .all {
        padding-left: 38px;
        font-size: 14px;
        font-weight: 400;
        color: #666666;
        padding-bottom: 5px;
        font-size: 14px;
      }
      .add-input {
      }
    }

0 人点赞