首页 > 其他分享 >关于为element Tree组件实现仿文件夹效果及右键菜单

关于为element Tree组件实现仿文件夹效果及右键菜单

时间:2023-04-28 10:00:25浏览次数:48  
标签:const name pid Tree value element 右键 null id

<template>
    <div class="custom-tree-container" @contextmenu.native="handlePaste($event)">
      <!-- <el-tree :data="dataSource" show-checkbox node-key="id" default-expand-all :expand-on-click-node="false"
        :render-content="renderContent" @node-contextmenu="" /> -->
      <div class="search">
        <el-input placeholder="输入关键字进行搜索" v-model="filterText" @keyup.enter.native="searchUp" @blur="searchUp" clearable
          style="margin-right: 8px;"></el-input>
        <el-button type="primary" @click="setAllNode">
          <el-icon>
            <Expand v-if="allNode" />
            <Fold v-else />
          </el-icon>
        </el-button>
      </div>
      <el-tree ref="treeRefs" :data="dataSource" node-key="id" default-expand-all expand-on-click-node
        @node-contextmenu="nodeContextmenu" :props="{ value: 'id', label: 'name', children: 'children' }"
        :filter-node-method="filterMethod" draggable @node-drop="dropSuccess">
        <template #default="{ node, data }">
          <div class="custom-tree-node">
            <div v-if="data.id !== reNameId">{{ data.name }}</div>
  
            <el-input v-else size="small" v-model="data.name" @blur="inputBlur(data.name)" v-focus />
            <!-- <span>
              //自定义节点内容
              <a @click="append(data)"> Add </a>
              <a style="margin-left: 8px" @click="remove(node, data)"> Delete </a>
              </span> -->
          </div>
        </template>
      </el-tree>
      <!-- Dialog -->
      <el-dialog v-model="removeVisible" title="确认删除" width="30%" :before-close="handleClose">
        <span>此操作将删除当前节点及全部子节点,确认是否删除?</span>
        <template #footer>
          <span class="dialog-footer">
            <el-button type="primary" @click="remove(true)">
              确认
            </el-button>
            <el-button @click="remove(false)">取消</el-button>
          </span>
        </template>
      </el-dialog>
      <!-- Menu -->
      <div class="menu" v-show="menuState">
        <div class="menu-item menu-rename" @click="rename">重命名</div>
        <div class="menu-item menu-add-peer" @click="addPeer">添加同级节点</div>
        <div class="menu-item menu-add-sublevel" @click="addSublevel">添加子级节点</div>
        <div class="menu-item menu-set-property" @click="setProperty">设置属性</div>
        <div class="menu-item menu-del-node" @click="delNode">删除</div>
      </div>
      <div class="blur" @click="unBlur('blur')" v-show="menuState" @contextmenu.native="handlePaste($event)"></div>
    </div>
  </template>
    
  <script setup>
  import { getCurrentInstance, ref } from 'vue'
  
  const menuState = ref(false)
  const contextMenuRow = ref(null) //右键存储数据
  const parentMenuRow = ref(null) //右键节点父级
  const reNameId = ref(null) //重命名记录
  const reName = ref(null) //重命名记录
  const removeVisible = ref(false)
  
  const filterText = ref('')
  const allNode = ref(false)
  const treeRefs = ref(null);
  const { proxy } = getCurrentInstance();
  
  const dataSource = ref([
    {
      id: 1,
      name: '一级 1',
      pid: null,
      children: [
        {
          id: 4,
          name: '二级 1-1',
          pid: 1,
          children: [
            {
              id: 9,
              name: '三级 1-1-1',
              pid: 4,
            },
            {
              id: 10,
              name: '三级 1-1-2',
              pid: 4,
            },
          ],
        }, {
          id: 36,
          name: '二级 2-1',
          pid: 1,
          children: [
            {
              id: 202,
              name: '三级 1-2-1',
              pid: 36,
            },
            {
              id: 109,
              name: '三级 1-2-2',
              pid: 36,
            },
          ],
        },
      ],
    },
    {
      id: 2,
      name: '一级 2',
      pid: null,
      children: [
        {
          id: 5,
          name: '二级 2-1',
          pid: 2,
        },
        {
          id: 6,
          name: '二级 2-2',
          pid: 2,
        },
      ],
    },
    {
      id: 3,
      name: '一级 3',
      pid: null,
      children: [
        {
          id: 7,
          name: '二级 3-1',
          pid: 3,
        },
        {
          id: 8,
          name: '二级 3-2',
          pid: 3,
        },
      ],
    },
  ])
  
  const handlePaste = (event) => {
    
    // 禁用鼠标右键
    event.preventDefault()
    //关闭菜单
    
    return false
  }
  
  //设置新数据 
  const updateKeyChildren = (key, value) => {
    console.log('为节点设置新数据', key, value)
  }
  
  //右击事件
  const nodeContextmenu = (event, data, node) => {
    console.log('nodeContextmenu', data, node)
    contextMenuRow.value = data
    if (data.pid) {
      parentMenuRow.value = node.parent.data
    }
  
  
    //show menu
    let menu = document.querySelector('.menu')
    menu.style.left = event.x + 8 + 'px'
    menu.style.top = event.y + 'px'
    menuState.value = true
  }
  
  
  
  const rename = () => {
    reNameId.value = contextMenuRow.value.id
    reName.value = contextMenuRow.value.name
    //关闭菜单
    menuState.value = false
  }
  
  const unBlur = (state) => {
    console.log('取消操作菜单')
    if (state == 'inputName') {
      reName.value = null
      reNameId.value = null
      contextMenuRow.value = null
      parentMenuRow.value = null
      return
    }
    if (state == 'addChild') {
      menuState.value = false
      contextMenuRow.value = null
      parentMenuRow.value = null
    }
    if (state == 'remove') {
      menuState.value = false
      reName.value = null
      reNameId.value = null
      contextMenuRow.value = null
      parentMenuRow.value = null
    }
    if(state == 'blur'){
      menuState.value = false
      reName.value = null
      reNameId.value = null
      contextMenuRow.value = null
      parentMenuRow.value = null
    }
  }
  
  const inputBlur = (name) => {
    console.log('更新命名', name)
    if (reName.value !== name) {
      console.log('调用接口,更新list')
    }
    unBlur('inputName')
  }
  
  const addSublevel = () => {
    const addId = +new Date()
    const newChild = { id: addId, name: '未命名', children: [] }
    reName.value = '未命名'
    reNameId.value = addId
    if (!contextMenuRow.value.children) {
      contextMenuRow.value.children = []
    }
    contextMenuRow.value.children.push(newChild)
    dataSource.value = [...dataSource.value]
    unBlur('addChild')
  }
  
  const addPeer = () => {
    const addId = +new Date()
    const newChild = { id: addId, name: '未命名', children: [] }
    reName.value = '未命名'
    reNameId.value = addId
    //静态添加
    if (parentMenuRow.value) {
      if (!parentMenuRow.value.children) {
        parentMenuRow.value.children = []
      }
      parentMenuRow.value.children.push(newChild)
      dataSource.value = [...dataSource.value]
    } else {
      console.log('没有父级节点')
      dataSource.value.push(newChild)
    }
    unBlur('addChild')
  }
  
  const delNode = (node, data) => {
    console.log('删除,弹窗', node, data, removeVisible)
    removeVisible.value = true
  }
  const handleClose = () => {
    console.log('handleClose')
    removeVisible.value = false
    unBlur('remove')
  }
  const remove = (bool) => {
    if (bool) {
      console.log('调用删除')
    }
    removeVisible.value = false
    unBlur('remove')
  }
  
  // 展开收起所有节点
  const setAllNode = () => {
    //一级展开不关注child
    let treeList = dataSource.value;
    for (let i = 0; i < treeList.length; i++) {
      treeRefs.value.store.nodesMap[treeList[i].id].expanded = allNode.value;
    }
    allNode.value = !allNode.value
    //全部展开,但会出现卡顿
    // const nodes = treeRefs.value.store._getAllNodes();
    // nodes.forEach(item => {
    //   item.expanded = allNode.value;
    // })
  }
  
  watch(filterText, (val) => {
    proxy.$refs.treeRefs.filter(val);
  })
  
  const searchUp = () => {
    if (filterText.value && filterText.value.length) {
      proxy.$refs.treeRefs.filter(filterText.value);
      allNode.value = false
    } else {
      let treeList = dataSource.value;
      for (let i = 0; i < treeList.length; i++) {
        treeRefs.value.store.nodesMap[treeList[i].id].expanded = false;
      }
      allNode.value = false
    }
  }
  
  const filterMethod = (query, data) => {
    return data.name.includes(query)
  }
  
  const dropSuccess = (node, toItem, position, event) => {
    console.log('拖拽', node, toItem, position, event)
  }
  
  </script>
    
  <style>
  .custom-tree-node {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 14px;
    padding-right: 8px;
  }
  
  .menu {
    width: 120px;
    z-index: 999;
    background-color: #ffffff;
    position: fixed;
    text-align: left;
    box-shadow: 0px 0px 5px 1px rgba(102, 102, 102, 0.35);
  }
  
  .blur {
    width: 100%;
    height: 100%;
    z-index: 99;
    position: absolute;
    top: 0;
    left: 0;
  }
  
  .menu-item {
    padding-left: 10px;
    color: #333333;
    font-size: 13px;
    line-height: 24px;
    margin: 8px 0;
  }
  
  .menu-item:hover {
    background-color: #EBF5FF;
  }
  
  .search {
    margin-bottom: 10px;
    display: flex;
  }
  </style>
    
  
  
  
  
  
  //树形结构接口需求
  {
    "id": 4, //节点id
    "name": "二级 1-1", //节点name
    "children": [ //子节点数组
        {
            "id": 9,
            "name": "三级 1-1-1",
            "children":[],
            "pid":4
        },
        {
            "id": 10,
            "name": "三级 1-1-2",
            "children":[],
            "pid":4
        }
    ],
    "pid":2, //父级id
    //setAttribute或attributeList
    "setAttribute":'TY', //节点身份属性,取决于二级菜单展示内容
    "attributeList":[ //节点二级菜单内容
      {
        "id": 129,
        "name": "属性1",
        ...
      },
      {
        "id": 103,
        "name": "属性2",
        ...
      } 
    ]
  }
  

复制即用,根据需求自行修改

效果图

 

 

 

 

 

<template>     <div class="custom-tree-container" @contextmenu.native="handlePaste($event)">       <!-- <el-tree :data="dataSource" show-checkbox node-key="id" default-expand-all :expand-on-click-node="false"         :render-content="renderContent" @node-contextmenu="" /> -->       <div class="search">         <el-input placeholder="输入关键字进行搜索" v-model="filterText" @keyup.enter.native="searchUp" @blur="searchUp" clearable           style="margin-right: 8px;"></el-input>         <el-button type="primary" @click="setAllNode">           <el-icon>             <Expand v-if="allNode" />             <Fold v-else />           </el-icon>         </el-button>       </div>       <el-tree ref="treeRefs" :data="dataSource" node-key="id" default-expand-all expand-on-click-node         @node-contextmenu="nodeContextmenu" :props="{ value: 'id', label: 'name', children: 'children' }"         :filter-node-method="filterMethod" draggable @node-drop="dropSuccess">         <template #default="{ node, data }">           <div class="custom-tree-node">             <div v-if="data.id !== reNameId">{{ data.name }}</div>               <el-input v-else size="small" v-model="data.name" @blur="inputBlur(data.name)" v-focus />             <!-- <span>               //自定义节点内容               <a @click="append(data)"> Add </a>               <a style="margin-left: 8px" @click="remove(node, data)"> Delete </a>               </span> -->           </div>         </template>       </el-tree>       <!-- Dialog -->       <el-dialog v-model="removeVisible" title="确认删除" width="30%" :before-close="handleClose">         <span>此操作将删除当前节点及全部子节点,确认是否删除?</span>         <template #footer>           <span class="dialog-footer">             <el-button type="primary" @click="remove(true)">               确认             </el-button>             <el-button @click="remove(false)">取消</el-button>           </span>         </template>       </el-dialog>       <!-- Menu -->       <div class="menu" v-show="menuState">         <div class="menu-item menu-rename" @click="rename">重命名</div>         <div class="menu-item menu-add-peer" @click="addPeer">添加同级节点</div>         <div class="menu-item menu-add-sublevel" @click="addSublevel">添加子级节点</div>         <div class="menu-item menu-set-property" @click="setProperty">设置属性</div>         <div class="menu-item menu-del-node" @click="delNode">删除</div>       </div>       <div class="blur" @click="unBlur('blur')" v-show="menuState" @contextmenu.native="handlePaste($event)"></div>     </div>   </template>       <script setup>   import { getCurrentInstance, ref } from 'vue'     const menuState = ref(false)   const contextMenuRow = ref(null) //右键存储数据   const parentMenuRow = ref(null) //右键节点父级   const reNameId = ref(null) //重命名记录   const reName = ref(null) //重命名记录   const removeVisible = ref(false)     const filterText = ref('')   const allNode = ref(false)   const treeRefs = ref(null);   const { proxy } = getCurrentInstance();     const dataSource = ref([     {       id: 1,       name: '一级 1',       pid: null,       children: [         {           id: 4,           name: '二级 1-1',           pid: 1,           children: [             {               id: 9,               name: '三级 1-1-1',               pid: 4,             },             {               id: 10,               name: '三级 1-1-2',               pid: 4,             },           ],         }, {           id: 36,           name: '二级 2-1',           pid: 1,           children: [             {               id: 202,               name: '三级 1-2-1',               pid: 36,             },             {               id: 109,               name: '三级 1-2-2',               pid: 36,             },           ],         },       ],     },     {       id: 2,       name: '一级 2',       pid: null,       children: [         {           id: 5,           name: '二级 2-1',           pid: 2,         },         {           id: 6,           name: '二级 2-2',           pid: 2,         },       ],     },     {       id: 3,       name: '一级 3',       pid: null,       children: [         {           id: 7,           name: '二级 3-1',           pid: 3,         },         {           id: 8,           name: '二级 3-2',           pid: 3,         },       ],     },   ])     const handlePaste = (event) => {         // 禁用鼠标右键     event.preventDefault()     //关闭菜单         return false   }     //设置新数据   const updateKeyChildren = (key, value) => {     console.log('为节点设置新数据', key, value)   }     //右击事件   const nodeContextmenu = (event, data, node) => {     console.log('nodeContextmenu', data, node)     contextMenuRow.value = data     if (data.pid) {       parentMenuRow.value = node.parent.data     }         //show menu     let menu = document.querySelector('.menu')     menu.style.left = event.x + 8 + 'px'     menu.style.top = event.y + 'px'     menuState.value = true   }         const rename = () => {     reNameId.value = contextMenuRow.value.id     reName.value = contextMenuRow.value.name     //关闭菜单     menuState.value = false   }     const unBlur = (state) => {     console.log('取消操作菜单')     if (state == 'inputName') {       reName.value = null       reNameId.value = null       contextMenuRow.value = null       parentMenuRow.value = null       return     }     if (state == 'addChild') {       menuState.value = false       contextMenuRow.value = null       parentMenuRow.value = null     }     if (state == 'remove') {       menuState.value = false       reName.value = null       reNameId.value = null       contextMenuRow.value = null       parentMenuRow.value = null     }     if(state == 'blur'){       menuState.value = false       reName.value = null       reNameId.value = null       contextMenuRow.value = null       parentMenuRow.value = null     }   }     const inputBlur = (name) => {     console.log('更新命名', name)     if (reName.value !== name) {       console.log('调用接口,更新list')     }     unBlur('inputName')   }     const addSublevel = () => {     const addId = +new Date()     const newChild = { id: addId, name: '未命名', children: [] }     reName.value = '未命名'     reNameId.value = addId     if (!contextMenuRow.value.children) {       contextMenuRow.value.children = []     }     contextMenuRow.value.children.push(newChild)     dataSource.value = [...dataSource.value]     unBlur('addChild')   }     const addPeer = () => {     const addId = +new Date()     const newChild = { id: addId, name: '未命名', children: [] }     reName.value = '未命名'     reNameId.value = addId     //静态添加     if (parentMenuRow.value) {       if (!parentMenuRow.value.children) {         parentMenuRow.value.children = []       }       parentMenuRow.value.children.push(newChild)       dataSource.value = [...dataSource.value]     } else {       console.log('没有父级节点')       dataSource.value.push(newChild)     }     unBlur('addChild')   }     const delNode = (node, data) => {     console.log('删除,弹窗', node, data, removeVisible)     removeVisible.value = true   }   const handleClose = () => {     console.log('handleClose')     removeVisible.value = false     unBlur('remove')   }   const remove = (bool) => {     if (bool) {       console.log('调用删除')     }     removeVisible.value = false     unBlur('remove')   }     // 展开收起所有节点   const setAllNode = () => {     //一级展开不关注child     let treeList = dataSource.value;     for (let i = 0; i < treeList.length; i++) {       treeRefs.value.store.nodesMap[treeList[i].id].expanded = allNode.value;     }     allNode.value = !allNode.value     //全部展开,但会出现卡顿     // const nodes = treeRefs.value.store._getAllNodes();     // nodes.forEach(item => {     //   item.expanded = allNode.value;     // })   }     watch(filterText, (val) => {     proxy.$refs.treeRefs.filter(val);   })     const searchUp = () => {     if (filterText.value && filterText.value.length) {       proxy.$refs.treeRefs.filter(filterText.value);       allNode.value = false     } else {       let treeList = dataSource.value;       for (let i = 0; i < treeList.length; i++) {         treeRefs.value.store.nodesMap[treeList[i].id].expanded = false;       }       allNode.value = false     }   }     const filterMethod = (query, data) => {     return data.name.includes(query)   }     const dropSuccess = (node, toItem, position, event) => {     console.log('拖拽', node, toItem, position, event)   }     </script>       <style>   .custom-tree-node {     flex: 1;     display: flex;     align-items: center;     justify-content: space-between;     font-size: 14px;     padding-right: 8px;   }     .menu {     width: 120px;     z-index: 999;     background-color: #ffffff;     position: fixed;     text-align: left;     box-shadow: 0px 0px 5px 1px rgba(102, 102, 102, 0.35);   }     .blur {     width: 100%;     height: 100%;     z-index: 99;     position: absolute;     top: 0;     left: 0;   }     .menu-item {     padding-left: 10px;     color: #333333;     font-size: 13px;     line-height: 24px;     margin: 8px 0;   }     .menu-item:hover {     background-color: #EBF5FF;   }     .search {     margin-bottom: 10px;     display: flex;   }   </style>                 //树形结构接口需求   {     "id": 4, //节点id     "name": "二级 1-1", //节点name     "children": [ //子节点数组         {             "id": 9,             "name": "三级 1-1-1",             "children":[],             "pid":4         },         {             "id": 10,             "name": "三级 1-1-2",             "children":[],             "pid":4         }     ],     "pid":2, //父级id     //setAttribute或attributeList     "setAttribute":'TY', //节点身份属性,取决于二级菜单展示内容     "attributeList":[ //节点二级菜单内容       {         "id": 129,         "name": "属性1",         ...       },       {         "id": 103,         "name": "属性2",         ...       }     ]   }  

标签:const,name,pid,Tree,value,element,右键,null,id
From: https://www.cnblogs.com/awench/p/17361022.html

相关文章

  • ElementPlus 组件全局配置
    友链:语雀,在线文档协同平台官方提供的全局配置:ConfigProvider本文只做简单的模板参考,具体的配置请根据自己的业务灵活设置,如果你使用的是其它的ui框架,原理应该都差不多入口文件的配置注意事项需要设置全局属性的组件先导入进来,比如:ElTable、ElForm、ElInput一定要在这......
  • import treeTransfer from "el-tree-transfer"; 全量树去除 选中的
    <template><div><tree-transfer:title="['源列表','目标列表']":from_data="fromData":to_data="toData":defaultProps="{label:'label'}"@add......
  • elementUI表格默认多选
    this.multipleSelection=row.mer_type_idthis.$nextTick(()=>{this.multipleSelection.forEach(id=>{constrow=this.categoryMerTypeList.find(item=>item.mer_type_id==id);this.$refs.multipleTable.toggleRowSelection(row,true);});})......
  • html文件中使用vue3+element-plus开发模版
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"/><metahttp-equiv="X-UA-Compatible"content="IE=edge"/><metaname="viewport"content="w......
  • element-ui中多个表单el-form进行显示/隐藏切换时校验失效
    问题描述:当一个弹窗或页面中含有多个表单(都需要校验),且需要进行显示/隐藏切换时,容易造成切换后的表单元素的校验失效。如下: 第一个表单的输入框都触发blur提示语后,切换至第二个表单第二个表单的输入框触发blur后,第一个输入框的校验失效了。返回后,第一个输入框触发blur,校验......
  • Codeforces Round 866 (Div. 1) C. The Fox and the Complete Tree Traversal (构造)
    传送门题目大意:  给定一个有n个点的树,我们可以任意选择一个点作为起点,这个点可以跳到跟自己距离为1或者距离为2的点,已经到达的点不能再到达(最终必须回到起点),询问是否存在一种方案可以从一个点出发经过所有的点最终再回到这个点来。  输入一个整数n。紧接着输入n-1条边。大......
  • element 合并单元格方法
    你的数据是需要排列好的,把所有一样的数据都排序到一起//获取需要合并的位置constgetSpanNumber=(data:User[],prop:string)=>{constlength=data.lengthif(length>0){letposition=0lettemp=data[0][prop]constresult=[1]fo......
  • element-ui el-dialog中引用组件,为何组件只加载一次
    最近开发项目,页面中引入组件,2次展示,组件中生命周期都不调取,导致网组件中传的值不更新;<el-dialogv-dialogDragtitle="巡检记录":visible.sync="patrolItemVisible":show-close="true":close-on-press-escape="true":close-on-click-modal="true":appen......
  • 遇到 element-ui 框架的警告 `[Element Warn]please pass correct props!`
     第二行发现是校验表单项报错,到代码中查看原来是没有给 el-form 表单中的 el-form-item 传prop参数,这个prop参数是表单域 model字段,在使用 validate、validateField、resetFields 等方法的情况下,该属性是必填的......
  • Vue3 + element-plus使用注意
    1.给组件设置ref="xxx"例如:<el-tableref="tableRef"定义tableRef时,需要注意尽量使用ref()而非ref(null)consttableRef=ref();因为使用ref(null)会得不到$el的相关属性,即undefined例如:表格自适应高度consttableRef=ref();constsetTableHeight=()=>......