Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions mock/role/index.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,14 @@ const adminList = [
meta: {
title: 'router.iAgree'
}
},
{
path: 'tree',
component: 'views/Components/Tree',
name: 'Tree',
meta: {
title: 'router.tree'
}
}
]
},
Expand Down
3 changes: 3 additions & 0 deletions src/components/Tree/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Tree from './src/Tree.vue'

export { Tree }
147 changes: 147 additions & 0 deletions src/components/Tree/src/Tree.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,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>
8 changes: 7 additions & 1 deletion src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ export default {
personalCenter: 'Personal center',
personal: 'Personal',
avatars: 'Avatars',
iAgree: 'I agree'
iAgree: 'I agree',
tree: 'Tree'
},
permission: {
hasPermission: 'Please set the operation permission value'
Expand Down Expand Up @@ -393,6 +394,11 @@ export default {
logoStyle: 'Logo style',
size: 'size config'
},
treeDemo: {
treeTitle: 'Tree control (right-click node to customize menu options)',
message:
'The tree component is based on the secondary packaging of the tree component of ElementPlus'
},
highlightDemo: {
highlight: 'Highlight',
message: 'The best time to plant a tree is ten years ago, followed by now.',
Expand Down
7 changes: 6 additions & 1 deletion src/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ export default {
personalCenter: '个人中心',
personal: '个人',
avatars: '头像列表',
iAgree: '我同意'
iAgree: '我同意',
tree: 'Tree 树形控件'
},
permission: {
hasPermission: '请设置操作权限值'
Expand Down Expand Up @@ -385,6 +386,10 @@ export default {
logoStyle: 'logo样式',
size: '大小配置'
},
treeDemo: {
treeTitle: '树形控件(节点右键可自定义菜单选项)',
message: '基于 ElementPlus 的 Tree 组件二次封装'
},
highlightDemo: {
highlight: '高亮',
message: '种一棵树最好的时间是十年前,其次就是现在。',
Expand Down
Loading
Loading