<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",
        ...
      }
    ]
  }
 

关于为element Tree组件实现仿文件夹效果及右键菜单的更多相关文章

  1. Linux下的tree命令 --Linux下文件夹树查看

    Linux下的tree命令 --Linux下文件夹树查看 有时我们须要生成文件夹树结构,能够使用的有ls -R,可是实际效果并不好 这时须要用到tree命令,可是大部分Linux系统是默认不安装该命令 ...

  2. Windows 10文件夹Shirt+鼠标右键出现“在此处打开命令窗口”

    Windows 10文件夹Shirt+鼠标右键出现“在此处打开命令窗口” Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\Directo ...

  3. win10无法新建文件夹怎么办 win10右键新建菜单设置方法

    有朋友安装了win10系统后发现右键菜单中没有新建项,而平时使用新建 - 文件夹项的机率很高.如何才能恢复桌面右键菜单中的新建项呢? 右键点击桌面空白处,在右键菜单中发现没有新建项: 桌面右键菜单没有 ...

  4. win7在某个盘或文件夹中出现右键只能新建文件夹的情况 (2012-12-28-bd 写的日志迁移

    至于只能新建文件夹的情况如图: 解决方法是在运行中输入msconfig进入如图: 在系统设置选工具项在选中更改UAC设置点击启动如图: 如图: 直接把通知栏拉到最低确定即可(如果已经是最低了那就随便改 ...

  5. windows10升级更新1709版本 在桌面和文件夹中点击右键刷新,会引起卡顿反应慢

    win10,升级更新,1709,右键,卡机,刷新,反应慢,桌面,文件夹 windows自动升级到1709版本后出现的问题,而之前是没有这种问题的. 最终解决办法:(需要设置注册表) 运行:快捷键Win ...

  6. 【技术博客】使用iview的Tree组件写一棵文件树

    本次项目的前端部分使用vue框架+iview组件构建,其中IDE的文件树部分使用了iview的Tree组件,但是Tree组件本身的接口功能极其有限,网上的相关资料也不多,在使用时费了一番功夫才摸索清楚 ...

  7. [置顶] Flex中Tree组件无刷新删除节点

    在Tree组件中经常要删除某个节点,而删除之后重新刷新加载该Tree组件会影响整个操作效果和效率,因此,无刷新删除就比较好,既删除了节点也没有刷新tree,而使Tree的状态处于删除之前的状态. 无刷 ...

  8. 使用dos的tree命令输出文件夹树

    用dos的tree命令就可以实现文件夹树状图的输出,不过目前仅能输出为.txt文件 方法如下: 开始->运行 输入cmd打开命令控制台 切换到你要显示的列表文件夹 比如 输入 d: 切换到d盘 ...

  9. 文件夹管理工具(MVC+zTree+layer)(附源码)

    写在前 之前写了一篇关于 文件夹与文件的操作的文章  操作文件方法简单总结(File,Directory,StreamReader,StreamWrite )  把常用的对于文件与文件夹的操作总结了一 ...

  10. ASP.NET中App_Code,App_Data等文件夹的作用

    http://www.cnblogs.com/shiyu007/archive/2007/12/04/982264.html 1.  Bin文件夹 Bin文件夹包含应用程序所需的,用于控件.组件或者需 ...

随机推荐

  1. ASP.NET WEBAPI oken验证

    看了下网上关于.net webAPI 的案例全是坑 验证成功了不被微信服务器接收 微信客服有找不到,提问也没人回 自己测试好几个小时 终于发现返回结果只要个string 双引号都不用加 public ...

  2. 快速乘_c/c++

    快速乘的使用主要是这种情形:要计算(a * b) % p时,发现a * b爆 long long 了,而a, b, p没有爆 long long   快速乘的原理:   比如当我们需要要计算3 * 2 ...

  3. 什么是5G垂直行业?

    什么是垂直行业呢? 感觉"垂直行业"这个词在太多地方遇到,但是这个词的涵盖范围到底是什么呢? 垂直这一概念源于两条直线(或平面)的直角交叉,两条直线是相互作为参照物的.比如,我们可 ...

  4. 一个小数据库SQLite

    参考 https://blog.csdn.net/csdnhsh/article/details/93376733 https://www.runoob.com/sqlite/sqlite-creat ...

  5. python字符操作超全总结

    在python中,字符串是数据类型之一,属于不可变序列. 转义字符的使用 转义字符是指使用反斜杠"\"对一些特殊字符进行转义.几个常用的转义字符如下: \   -续行 \n -换行 ...

  6. Linux & 标准C语言学习 <DAY1>

    Linux系统简单介绍:     BCPL->New B->C->UNIX->Minix->Linux->gcc     美国贝尔实验室 1968     Linu ...

  7. 声网王浩宇:RTE 场景下的 Serverless 架构挑战【RTE 2022】

    前言 在「RTE2022 实时互联网大会」中,声网云原生边缘计算团队的负责人 @王浩宇 Dylan 以<RTE 场景下的 Serverless 架构挑战 -- 声网如何兼顾后端服务的可靠.高效和 ...

  8. 【读书笔记】Young Tableau_Calculus of tableaux_bumping and sliding

    目录 bumping Schensted bumping algorithm 举例 sliding/digging a hole 一些定义 Schiitzenberger sliding algori ...

  9. Shell脚本监控Centos 7系统运行状态

    #!/usr/bin/bash ## @date: 2021-08-17 ## This is a script for security operation indicator monitoring ...

  10. Gateway 网关

    Spring Cloud Gateway 作为 Spring Cloud框架的第二代网关,在功能上要比 Zuul更加的强大,性能也更好.随着 Spring Cloud的版本迭代,Spring Clou ...