yanjinhui
10 天以前 c5de0d98241f8c8349fa38851b77efcfc61e4d26
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<script lang="tsx" setup>
import { defineProps, defineEmits, ref, CSSProperties } from 'vue'
import { ElTree } from 'element-plus'
 
interface TreeProps {
  data: any[]
  treeProps?: Record<string, any>
  width?: string
  height?: string
}
const props = defineProps<TreeProps>()
 
const emit = defineEmits<{
  (e: 'node-click', nodeData: any): void
  (e: 'node-expand', nodeData: any): void
  (e: 'node-collapse', nodeData: any): void
}>()
 
const treeContainer = ref<any>(null)
const showTreeMenu = ref(false)
const contextNode = ref<any>(null)
 
const menuStyle = ref<any>({})
 
const defaultWidth = '300px'
const defaultHeight = '400px'
 
// 关闭菜单
const closeTreeMenu = () => {
  showTreeMenu.value = false
  document.removeEventListener('click', closeTreeMenu)
  document.removeEventListener('contextmenu', closeTreeMenu)
}
 
// 右键菜单事件处理函数
const openTreeMenu = (event: MouseEvent, data: any, _node: any, _target: HTMLElement) => {
  contextNode.value = data
  if (!treeContainer.value) return
 
  const containerRect = treeContainer.value.getBoundingClientRect()
  const nodeRect = (event.target as HTMLElement).getBoundingClientRect()
 
  // 计算菜单相对于父容器定位的坐标
  const top = nodeRect.top - containerRect.top + treeContainer.value.scrollTop
  const left = nodeRect.left - containerRect.left + treeContainer.value.scrollLeft
 
  menuStyle.value = {
    position: 'absolute',
    top: `${top + 20}px`,
    left: `${left + 20}px`
  }
 
  showTreeMenu.value = true
 
  // 点击其他地方或再次右键关闭菜单
  document.addEventListener('click', closeTreeMenu)
  document.addEventListener('contextmenu', closeTreeMenu)
}
 
// 节点点击事件
const handleNodeClick = (data: any) => {
  emit('node-click', data)
  closeTreeMenu()
}
 
// 节点展开事件
const handleNodeExpand = (data: any) => {
  emit('node-expand', data)
  closeTreeMenu()
}
 
// 节点关闭事件
const handleNodeCollapse = (data: any) => {
  emit('node-collapse', data)
  closeTreeMenu()
}
 
// 计算容器样式
const containerStyle: CSSProperties = {
  position: 'relative',
  overflow: 'auto',
  width: props.width ?? defaultWidth,
  height: props.height ?? defaultHeight
}
</script>
<template>
  <div class="tree-container" ref="treeContainer" :style="containerStyle">
    <ElTree
      v-bind="treeProps"
      :data="data"
      @node-click="handleNodeClick"
      @node-expand="handleNodeExpand"
      @node-collapse="handleNodeCollapse"
      @node-contextmenu="openTreeMenu"
    >
      <template #default="{ node }">
        <!-- 如果使用者提供了 render-node slot,则渲染使用者的内容 -->
        <template v-if="$slots['render-node']">
          <slot name="render-node" :node="node"></slot>
        </template>
        <!-- 否则使用默认节点显示(比如使用 node.label )-->
        <template v-else>
          <span>{{ node.label }}</span>
        </template>
      </template>
    </ElTree>
    <div class="treeMenu" v-show="showTreeMenu" :style="menuStyle">
      <!-- 用户通过 context-menu slot 来自定义菜单内容 -->
      <slot name="context-menu" :node="contextNode" :data="contextNode">
        <!-- 如果用户不提供 context-menu slot,可给一个默认内容 -->
        <div style="padding: 8px">No menu defined</div>
      </slot>
    </div>
    <slot></slot>
  </div>
</template>
<style scoped lang="less">
.treeMenu {
  position: absolute;
  padding: 5px;
  font-size: 14px;
  color: #606266;
  background-color: rgb(255 255 255 / 90%);
  border: 1px solid #dcdcdc;
  border-radius: 5px;
  box-shadow: 0 4px 10px rgb(0 0 0 / 40%);
 
  /* 移除 overflow: hidden; 或尝试不使用负的 top 值 */
 
  /* overflow: hidden; */
 
  &::after {
    position: absolute;
 
    /* 将箭头向上移动到菜单外部 */
    top: -6px;
    left: 50%;
    border-right: 6px solid transparent;
    border-bottom: 6px solid rgb(206 194 194);
 
    /* 创建一个向上的箭头 */
    border-left: 6px solid transparent;
    content: '';
    transform: translateX(-50%);
  }
}
</style>