Element Plus Tree tree control nested Dropdown drop-down menu silent refresh

Although the tree component of element plus is relatively easy to use, it cannot satisfy the tree operation of traditional OA systems. After browsing the entire element plus, the Tree tree control has a nested Dropdown menu. Of course, if it is simply nested, it doesn't seem to be difficult, so I made it a little more difficult for myself, not only to achieve the perfect effect, but also to achieve a seamless refresh.

The old rule is to first move the sample code of the element tree component and run it, you can have the following effects

Insert image description here

Next, we need to implement nesting. We also need to take the Dropdown code and nest it.

first look at the effect

Insert image description here

Go to the code and talk about liberating the code comments.

<template>
	<el-input
      v-model="filterText"
      placeholder="请输入关键字"
      class="mb-24"
    />
    <el-tree
      ref="treeRef" 
      class="filter-tree"
      :data="data"
      :props="defaultProps"
      :expand-on-click-node="false"
      default-expand-all
      :filter-node-method="filterNode"
      @node-click="handleNodeClick"
    >
      <template #default="{ node }">
        <span>{
    
    {
    
     node.label }}</span>
        <el-dropdown
          class="tree-dropdown"
          :style="[activeNode === node.data.$treeNodeId && 'visibility:visible']"  // 保证点击的时候能够让操作图标显示
          :hide-on-click="false"
          placement="right-start"
          trigger="click"
        >
          <span class="inline-block rotate-90">
            <el-icon
              :size="16"
            ><Histogram /></el-icon>
          </span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item @click="handleEdit">
                重命名
              </el-dropdown-item>
              <el-dropdown-item>
                <el-dropdown
                  placement="right-start"
                >
                  <span class="flex justify-between">
                    添加<el-icon
                      :size="16"
                    ><ArrowRight /></el-icon>
                  </span>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item @click="handleInsertBefore">
                        上方添加目录
                      </el-dropdown-item>
                      <el-dropdown-item @click="handleInsertAfter">
                        下方添加目录
                      </el-dropdown-item>
                      <el-dropdown-item
                        :disabled="node.level===3"  // 只有第一第二级才能添加子目录
                        @click="handleInsertChild"
                      >
                        添加子目录
                      </el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </el-dropdown-item>
              <el-dropdown-item>
                <el-dropdown
                  placement="right-start"
                >
                  <span class="flex justify-between">
                    移动<el-icon size="16"><ArrowRight /></el-icon>
                  </span>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item
                        :disabled="!node.previousSibling"  // 第一个不能上移
                        @click="handleMoveUp(node)"
                      >
                        上移
                      </el-dropdown-item>
                      <el-dropdown-item
                        :disabled="!node.nextSibling"  // 最后一个不能下移
                        @click="handleMoveDown(node)"
                      >
                        下移
                      </el-dropdown-item>
                      <!-- <el-dropdown-item          // 升降级,可以做,但是没必要,暂时屏蔽,有需要的要自己实现
                        :disabled="node.level===3"    // 第三级不能降级
                        @click="handleTierDown(node)"
                      >
                        降级
                      </el-dropdown-item>
                      <el-dropdown-item
                        :disabled="node.level===1"    // 第一级不能升级
                        @click="handleUpgrades(node)"
                      >
                        升级
                      </el-dropdown-item> -->
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </el-dropdown-item>
              <el-dropdown-item @click="handleClone(node)">
                复制
              </el-dropdown-item>
              <el-dropdown-item @click="handleDelete">
                删除
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </template>
    </el-tree>
    
		//  弹窗自己写,我的是组件,就不放代码了
		
 </template>

<script lang="ts" setup>
import {
    
     toRefs, onMounted, ref, Ref, watch } from 'vue'
import {
    
     ElTree, ElMessage, ElMessageBox } from 'element-plus'
import {
    
     Histogram, ArrowRight } from '@element-plus/icons'
import {
    
     v4 as uuidV4 } from 'uuid'

interface Tree {
    
    
  [key: string]: any
}

const emits = defineEmits(['changeScale'])

const filterText = ref('')
const treeRef = ref<InstanceType<typeof ElTree>>()

const defaultProps = {
    
    
  children: 'children',
  label: 'label',
}

const name = ref('')              // 重命名
const activeNode = ref()          // 选中的虚拟树节点id,不是自己定义的
const activeNodeName = ref('')    // 选中树的名字,方便重命名/删除
const level = ref()               // 选中层级
const action = ref('insertBefore') as Ref<'insertBefore'|'insertAfter'|'append'>   // 当前选中的操作,因为下拉菜单是嵌套的,只能够记录当前操作。

const handleInsertBefore = () => {
    
    
  action.value = 'insertBefore'
  // 这里让你的弹窗显示
  // 弹窗.title = "添加目录"
}
const handleInsertAfter = () => {
    
    
  action.value = 'insertAfter'
  // 这里让你的弹窗显示
  // 弹窗.title = "添加目录"
}
const handleInsertChild = () => {
    
    
  action.value = 'append'
  // 这里让你的弹窗显示
  // 弹窗.title = "添加子目录"
}
const handleEdit = () => {
    
    
  action.value = 'insertAfter'
  name.value = activeNodeName.value  // 把原来的名字带到弹窗去
  // 这里让你的弹窗显示
  // 弹窗.title = "修改目录"
}
const handleClone = async (node) => {
    
    
  try {
    
    
    await ElMessageBox.confirm(`确定复制该模板吗?`, '提示', {
    
    
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
      center: true
    })
    const {
    
     $treeNodeId, ...newData } = node.data
    if (treeRef.value) treeRef.value.insertAfter(newData, $treeNodeId)  // 复制的原理就是,在改节点下方复制一个一模一样的,原则来说,包含的id是要替换掉新的,这个就得涉及到结构赋值了。
    activeNode.value = ''  // 清空选中的节点id,让图标隐藏
  } catch (err) {
    
    
    ElMessage({
    
     message: '已取消', showClose: true, type: 'info' })
    return
  }
}
const handleDelete = async () => {
    
    
  try {
    
    
    await ElMessageBox.confirm(`是否删除 ${
      
      activeNodeName.value}`, '提示', {
    
    
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
      center: true
    })
    if (treeRef.value) treeRef.value.remove(activeNode.value)
    activeNode.value = ''  // 清空选中的节点id,让图标隐藏
  } catch (err) {
    
    
    ElMessage({
    
     message: '已取消', showClose: true, type: 'info' })
    return
  }
}
const handleMoveUp = (node) => {
    
      // 上移的原理就是现在选中节点上方复制一个一模一样的节点,然后删掉原来那个
  const {
    
     $treeNodeId, ...newData } = node.data
  if (treeRef.value) treeRef.value.insertBefore(newData, node.previousSibling.data.$treeNodeId)
  if (treeRef.value) treeRef.value.remove($treeNodeId)
}
const handleMoveDown = (node) => {
    
      // 下移的原理就是现在选中节点下方复制一个一模一样的节点,然后删掉原来那个
  const {
    
     $treeNodeId, ...newData } = node.data
  if (treeRef.value) treeRef.value.insertAfter(newData, node.nextSibling.data.$treeNodeId)
  if (treeRef.value) treeRef.value.remove($treeNodeId)
}
// const handleTierDown = (node) => {
    
    
//   console.log(node)
// }
// const handleUpgrades = (node) => {
    
    
//   console.log(node)
// }

watch(filterText, (val) => {
    
    
  treeRef.value!.filter(val)
})

const filterNode = (value: string, data: Tree) => {
    
    
  if (!value) return true
  return data.label.includes(value)
}

const data: Tree[] = [
  {
    
    
    label: 'Level one 1',
    children: [
      {
    
    
        label: 'Level two 1-1',
        children: [
          {
    
    
            label: 'Level three 1-1-1',
          },
        ],
      },
    ],
  },
  {
    
    
    label: 'Level one 2',
    children: [
      {
    
    
        label: 'Level two 2-1',
        children: [
          {
    
    
            label: 'Level three 2-1-1',
          },
        ],
      },
      {
    
    
        label: 'Level two 2-2',
        children: [
          {
    
    
            label: 'Level three 2-2-1',
          },
        ],
      },
    ],
  },
  {
    
    
    label: 'Level one 3',
    children: [
      {
    
    
        label: 'Level two 3-1',
        children: [
          {
    
    
            label: 'Level three 3-1-1',
          },
        ],
      },
      {
    
    
        label: 'Level two 3-2',
        children: [
          {
    
    
            label: 'Level three 3-2-1',
          },
        ],
      },
    ],
  },
]

const handleNodeClick = (event) => {
    
    
  console.log(event)
  activeNode.value = event.$treeNodeId  // 选中的节点树id
  activeNodeName.value = event.label    // 选中的名字
  level.value = event.level             // 选中的层级
}

const handleSubmit = () => {
    
      //  这里是你的弹窗做出正确选择的时候触发
  submitBoxProps.value.visible = false
  const data: object = {
    
    
    id: uuidV4(),        // 定义新的id,这里用的是uuid,你可以先跑跟后端约定好的接口,让他把对应的 data 返回给你
    label: name.value,   // 你弹窗输入的内容(节点名字)
  }
  level.value === 3 ? Object.assign(data, {
    
     // 你的第三级自己的内容 }) : Object.assign(data, {children: []})  // 根据层级,重新重构data
  if (action.value === 'insertBefore' && treeRef.value) treeRef.value.insertBefore(data, activeNode.value)  // 上方插入
  if (action.value === 'insertAfter' && treeRef.value) treeRef.value.insertAfter(data, activeNode.value)   // 下方插入
  if (action.value === 'append' && treeRef.value) treeRef.value.append(data, activeNode.value)        // 子插入
  activeNode.value = ''
}
</script>

<style lang="scss" scoped>
.template-tree {
    
    
  width: 300px;
  height: max-content;
  background: #ffffff;

  :deep(.el-tree-node__content) {
    
    
    .tree-dropdown {
    
    
      visibility: hidden;  // 默认让菜单时隐藏的
    }
  }
  :deep(.el-tree-node__content:active),
  :deep(.el-tree-node__content:hover) {
    
    
      .tree-dropdown {
    
    
        visibility: visible !important;  // 菜单显示
      }
    }
}
</style>

Note: Each method should be implemented in conjunction with the backend. The principle of silent refresh is that the frontend manually changes the tree, instead of refreshing the backend interface to obtain data every time.

Hope this helps you.

Guess you like

Origin blog.csdn.net/weixin_44872023/article/details/131582055