一、前言

iview-admin中提供了 v-org-tree 这么一个vue组件可以实现树形菜单,下面小编来提供一下在element-ui中的使用教程

小编集成了el-dropdown下拉菜单(鼠标左击显示菜单),和右击自定义菜单,两种方式,效果图如下:

二、使用教程

(1)安装依赖

npm install clipboard
npm install v-click-outside-x
npm install v-org-tree

(2)引入组件

在main.js文件中引入

温馨小提示:也可在单个页面中进行局部引入哦

// 导入组织树形菜单组件 v-org-tree
import OrgTree from 'v-org-tree'
import 'v-org-tree/dist/v-org-tree.css'
Vue.use(OrgTree)import importDirective from '@/directive'
import { directive as clickOutside } from 'v-click-outside-x'
// 注册指令
importDirective(Vue)
Vue.directive('clickOutside', clickOutside)

(3)引入部分js工具方法

在项目目录下 -> src -> directive文件夹中引入如下5个js文件

clipboard.js

import Clipboard from 'clipboard'
export default {bind: (el, binding) => {const clipboard = new Clipboard(el, {text: () => binding.value.value})el.__success_callback__ = binding.value.successel.__error_callback__ = binding.value.errorclipboard.on('success', e => {const callback = el.__success_callback__callback && callback(e)})clipboard.on('error', e => {const callback = el.__error_callback__callback && callback(e)})el.__clipboard__ = clipboard},update: (el, binding) => {el.__clipboard__.text = () => binding.value.valueel.__success_callback__ = binding.value.successel.__error_callback__ = binding.value.error},unbind: (el, binding) => {delete el.__success_callback__delete el.__error_callback__el.__clipboard__.destroy()delete el.__clipboard__}
}

draggable.js

import { on } from './tools'
export default {inserted: (el, binding, vnode) => {let triggerDom = document.querySelector(binding.value.trigger)triggerDom.style.cursor = 'move'let bodyDom = document.querySelector(binding.value.body)let pageX = 0let pageY = 0let transformX = 0let transformY = 0let canMove = falseconst handleMousedown = e => {let transform = /\(.*\)/.exec(bodyDom.style.transform)if (transform) {transform = transform[0].slice(1, transform[0].length - 1)let splitxy = transform.split('px, ')transformX = parseFloat(splitxy[0])transformY = parseFloat(splitxy[1].split('px')[0])}pageX = e.pageXpageY = e.pageYcanMove = true}const handleMousemove = e => {let xOffset = e.pageX - pageX + transformXlet yOffset = e.pageY - pageY + transformYif (canMove) bodyDom.style.transform = `translate(${xOffset}px, ${yOffset}px)`}const handleMouseup = e => {canMove = false}on(triggerDom, 'mousedown', handleMousedown)on(document, 'mousemove', handleMousemove)on(document, 'mouseup', handleMouseup)},update: (el, binding, vnode) => {if (!binding.value.recover) returnlet bodyDom = document.querySelector(binding.value.body)bodyDom.style.transform = ''}
}

tools.js

/*** @description 绑定事件 on(element, event, handler)*/
export const on = (function () {if (document.addEventListener) {return function (element, event, handler) {if (element && event && handler) {element.addEventListener(event, handler, false)}}} else {return function (element, event, handler) {if (element && event && handler) {element.attachEvent('on' + event, handler)}}}
})()/*** @description 解绑事件 off(element, event, handler)*/
export const off = (function () {if (document.removeEventListener) {return function (element, event, handler) {if (element && event) {element.removeEventListener(event, handler, false)}}} else {return function (element, event, handler) {if (element && event) {element.detachEvent('on' + event, handler)}}}
})()

directives.js

import draggable from './module/draggable'
import clipboard from './module/clipboard'const directives = {draggable,clipboard
}export default directives

index.js

import directive from './directives'const importDirective = Vue => {/*** 拖拽指令 v-draggable="options"* options = {*  trigger: /这里传入作为拖拽触发器的CSS选择器/,*  body:    /这里传入需要移动容器的CSS选择器/,*  recover: /拖动结束之后是否恢复到原来的位置/* }*/Vue.directive('draggable', directive.draggable)/*** clipboard指令 v-draggable="options"* options = {*  value:    /在输入框中使用v-model绑定的值/,*  success:  /复制成功后的回调/,*  error:    /复制失败后的回调/* }*/Vue.directive('clipboard', directive.clipboard)
}export default importDirective

(4)正式使用v-org-tree组件

在所要使用的地方新增如下4个文件,比如我要写在user-group文件夹中

温馨小提示:注意在router.js路由中配置index访问页面哦~


org-view.vue

<template><divref="dragWrapper"class="org-tree-drag-wrapper"@mousedown="mousedownView"@contextmenu="handleDocumentContextmenu"><div class="org-tree-wrapper" :style="orgTreeStyle"><v-org-treev-if="data":data="data":node-render="nodeRender":expand-all="true"@on-node-click="handleNodeClick"collapsable></v-org-tree></div><!-- 右击显示的自定义菜单 --><div v-show="menuVisible"><ul id="menu" class="menu"><li class="menu__item" @click="addGroup">添加同级组织</li><li class="menu__item" @click="addGroup">添加下级组织</li><li class="menu__item" @click="updateGroup">修改信息</li><li class="menu__item" @click="deleteGroup" style="color: red;">删除信息</li></ul></div></div>
</template><script>import { on, off } from '@/directive/module/tools'const menuList = [{key: 'edit',label: '编辑'},{key: 'detail',label: '查看'},{key: 'new',label: '新增'},{key: 'delete',label: '删除'}
]export default {name: 'OrgView',props: {zoomHandled: {type: Number,default: 1},data: Object},data () {return {currentContextMenuId: '',orgTreeOffsetLeft: 0,orgTreeOffsetTop: 0,initPageX: 0,initPageY: 0,oldMarginLeft: 0,oldMarginTop: 0,canMove: false,menuVisible: false,// 默认菜单为隐藏状态}},computed: {orgTreeStyle () {return {transform: `translate(-50%, -50%) scale(${this.zoomHandled}, ${this.zoomHandled})`,marginLeft: `${this.orgTreeOffsetLeft}px`,marginTop: `${this.orgTreeOffsetTop}px`}}},methods: {// 处理右击菜单方法addGroup(){alert("add")},deleteGroup(){alert("delete")},updateGroup(){},handleNodeClick (e, data, expand) {expand()},// 监听鼠标点击自定义菜单以外的事件 -> 即点击其他地方隐藏菜单closeMenu () {this.menuVisible = false;// 隐藏菜单this.currentContextMenuId = '';},getBgColor (data) {return this.currentContextMenuId === data.id? data.isRoot? '#0d7fe8': '#5d6c7b': ''},// 组织数据及下拉菜单 【注:事件方法写法  ex:@command变为on-command才会生效】nodeRender (h, data) {return (<div on-mousedown={event => event.stopPropagation()} on-contextmenu={this.contextmenu.bind(this, data)} ><el-dropdowntrigger="click"on-command={this.handleContextMenuClick.bind(this, data)}class="context-menu"nativeOn-click={this.handleDropdownClick}style={{ transform: `scale(${1 / this.zoomHandled}, ${1 / this.zoomHandled})` }}v-click-outside={this.closeMenu}><span class={['custom-org-node', data.children && data.children.length ? 'has-children-label' : '']}>{data.label}</span><el-dropdown-menu slot="dropdown">{menuList.map(item => {return (<el-dropdown-item command={item.key}>{item.label}</el-dropdown-item>)})}</el-dropdown-menu></el-dropdown></div>)},// 处理右击事件 -> 右击覆盖浏览器原生右击事件contextmenu (data, $event) {console.log("右击事件:"+data.label)this.menuVisible = true;// 显示隐藏菜单let event = $event || window.event// console.log(event)event.preventDefault? event.preventDefault(): (event.returnValue = false)this.currentContextMenuId = data.id},setDepartmentData (data) {data.isRoot = truethis.departmentData = data},mousedownView (event) {this.canMove = truethis.initPageX = event.pageXthis.initPageY = event.pageYthis.oldMarginLeft = this.orgTreeOffsetLeftthis.oldMarginTop = this.orgTreeOffsetTopon(document, 'mousemove', this.mousemoveView)on(document, 'mouseup', this.mouseupView)},mousemoveView (event) {if (!this.canMove) returnconst { pageX, pageY } = eventthis.orgTreeOffsetLeft = this.oldMarginLeft + pageX - this.initPageXthis.orgTreeOffsetTop = this.oldMarginTop + pageY - this.initPageY},mouseupView () {this.canMove = falseoff(document, 'mousemove', this.mousemoveView)off(document, 'mouseup', this.mouseupView)},handleDropdownClick (event) {event.stopPropagation()},handleDocumentContextmenu () {this.canMove = false},handleContextMenuClick (data, key) {this.$emit('on-menu-click', { data, key })}},mounted () {on(document, 'contextmenu', this.handleDocumentContextmenu)},beforeDestroy () {off(document, 'contextmenu', this.handleDocumentContextmenu)}
}
</script><style>.menu__item {display: block;line-height: 20px;text-align: center;margin-top: 10px;}.menu {height: 120px;width: 100px;position: absolute;border-radius: 10px;border: 1px solid #999999;background-color: #f4f4f4;}li:hover {background-color: #1790ff;color: white;}
</style>

zoom-controller.vue

<template><div class="zoom-wrapper"><button class="zoom-button" @click="scale('down')"><Icon type="md-remove" :size="14" color="#fff"/></button><span class="zoom-number">{{ value }}%</span><button class="zoom-button" @click="scale('up')"><Icon type="md-add" :size="14" color="#fff"/></button></div>
</template><script>
export default {name: 'ZoomController',props: {value: {type: Number,default: 100},step: {type: Number,default: 20},min: {type: Number,default: 10},max: {type: Number,default: 200}},methods: {scale (type) {const zoom = this.value + (type === 'down' ? -this.step : this.step)if ((zoom < this.min && type === 'down') ||(zoom > this.max && type === 'up')) {return}this.$emit('input', zoom)}}
}
</script><style lang="less">
.trans(@duration) {transition: ~"all @{duration} ease-in";
}
.zoom-wrapper {.zoom-button {width: 20px;height: 20px;line-height: 10px;border-radius: 50%;background: rgba(157, 162, 172, 1);box-shadow: 0px 2px 8px 0px rgba(218, 220, 223, 0.7);border: none;cursor: pointer;outline: none;&:active {box-shadow: 0px 0px 2px 2px rgba(218, 220, 223, 0.2) inset;}.trans(0.1s);&:hover {background: #1890ff;.trans(0.1s);}}.zoom-number {color: #657180;padding: 0 8px;display: inline-block;width: 46px;text-align: center;}
}
</style>

index.less

@wrapper: ~'department';
.percent-100 {width: 100%;height: 100%;
}
.@{wrapper}-outer {.percent-100;overflow: hidden;.tip-box{position: absolute;left: 20px;top: 20px;z-index: 12;}.zoom-box {position: absolute;right: 30px;bottom: 30px;z-index: 2;}.view-box {position: absolute;top: 0;bottom: 0;left: 0;right: 0;z-index: 1;cursor: move;.org-tree-drag-wrapper {width: 100%;height: 100%;}.org-tree-wrapper {display: inline-block;position: absolute;left: 50%;top: 50%;transition: transform 0.2s ease-out;.org-tree-node-label {box-shadow: 0px 2px 12px 0px rgba(143, 154, 165, 0.4);border-radius: 4px;.org-tree-node-label-inner {padding: 0;.custom-org-node {padding: 14px 41px;background: #738699;user-select: none;word-wrap: none;white-space: nowrap;border-radius: 4px;color: #ffffff;font-size: 14px;font-weight: 500;line-height: 20px;transition: background 0.1s ease-in;cursor: default;&:hover {background: #5d6c7b;transition: background 0.1s ease-in;}&.has-children-label {cursor: pointer;}.context-menu{position: absolute;right: -10px;bottom: 20px;z-index: 10;}}}}}}
}

index.vue

<template><div shadow style="height: 100%;width: 100%;overflow:hidden"><div class="department-outer"><div class="view-box"><org-viewv-if="data":data="data":zoom-handled="zoomHandled"@on-menu-click="handleMenuClick"></org-view></div></div></div>
</template><script>
import OrgView from './components/org-view.vue'
import ZoomController from './components/zoom-controller.vue'
import './index.less'
const menuDic = {edit: '编辑按钮',detail: '查看按钮',new: '新增按钮',delete: '删除按钮'
}
export default {name: 'org_tree_page',components: {OrgView,ZoomController},data () {return {// TODO 目前暂时为假数据,可调用接口方法赋予实时数据data: {id: 0,label: 'XX科技有限公司',children: [{id: 2,label: '产品研发部',children: [{id: 5,label: '研发-前端'}, {id: 6,label: '研发-后端'}, {id: 9,label: 'UI设计'}, {id: 10,label: '产品经理'}]},{id: 3,label: '销售部',children: [{id: 7,label: '销售一部'}, {id: 8,label: '销售二部'}]},{id: 4,label: '财务部'}, {id: 11,label: 'HR人事'}]},zoom: 100}},computed: {zoomHandled () {return this.zoom / 100}},methods: {setDepartmentData (data) {data.isRoot = truereturn data},handleMenuClick ({ data, key }) {// this.submitSucc(`点击了《${data.label}》节点的'${menuDic[key]}'菜单`)this.$message(`点击了《${data.label}》节点的'${menuDic[key]}'菜单`);},}
}
</script><style></style>

(5)最终实现效果图

npm run dev 运行

两种菜单中都默认定义了点击菜单弹出消息框,具体的业务根据你们个人情况修改~

三、总结

部分文件说明

  1. org-view.vue:封装v-org-tree组件功能
  2. index.less:样式
  3. index.vue:正式使用组件编写业务

遇坑问题

  1. 注意引入文件位置
  2. 从iview-admin源码中直接拷贝过来修改组件时注意修改标签为elment的标签
  3. 如上图中org-view.vue文件中的事件方法写法
    element-ui中 el-dropdown 点击菜单项触发的事件回调 @command变为on-command才会生效!
    


    调用方法为 { this.方法名 }
    如:

    on-command={this.handleContextMenuClick.bind(this, data)}
    
  4. 由于小编是Java工程师,对前端vue这块不是太熟,部分修改并不是那么完善,还请见谅 ~

Element(3) 使用v-org-tree组件实现组织树形菜单相关推荐

  1. vue 递归组件多级_Vue 递归组件构建一个树形菜单

    原标题:Vue 递归组件构建一个树形菜单 Vue.js 中的递归组件是一个可以调用自己的组件例如: Vue.component('recursive-component', { template: ` ...

  2. vue中element ui 中tree组件怎么自定义前缀图标呢?

    一 问题 饿了么ui 默认的图标样式是: 1. 一个箭头, 展开自动顺时针旋转90°, 以上的条件是该节点有子节点, 2. 如果是没有子节点的节点, 是默认空白图标的(这里我认为他不是没有, 而是有占 ...

  3. jsf tree组件_JSF表单组件示例教程

    jsf tree组件 JSF Form component is a collection of fields along with the data and submit functionality ...

  4. jsf tree组件_JSF UI组件标签属性示例教程

    jsf tree组件 JSF provides a wide variety of ui component tags along with a long range of attributes. T ...

  5. jsf tree组件_JSF文本组件–标签,文本字段,文本区域和密码

    jsf tree组件 The Text components allows the user to add, view and edit data in a form of a web applica ...

  6. elementui tree组件层级过多时可左右滚动

    2019独角兽企业重金招聘Python工程师标准>>> 使用vue+elementui的tree组件,elementui官网elementui的tree组件 问题描述:tree层级过 ...

  7. antd Tree组件中,自定义右键菜单

    最近项目中,有一个需求是自定义antd的Tree组件的右键菜单功能. 直接上代码 class Demo extends Component {state = {rightClickNodeTreeIt ...

  8. jsf tree组件_JSF:在传统组件和时尚性能杀手之间进行选择

    jsf tree组件 这篇博客文章起源于一个大型Web应用程序中的性能问题. 每个人都优化Java代码,但似乎没有人尝试优化JavaScript代码. 奇怪,因为在客户端有很多改进的空间. 我会说,甚 ...

  9. jsf tree组件_JSF和“立即”属性–命令组件

    jsf tree组件 JSF中的即时属性通常被误解. 如果您不相信我,请查看Stack Overflow . 造成混淆的部分原因可能是由于输入(即<h:inputText />)和命令(即 ...

最新文章

  1. VMWare虚拟机下为Ubuntu 12.04.1配置静态IP(NAT连接方式)
  2. Spring Cloud (3) 服务消费者-Ribbon
  3. 通过SCCM部署Office365应用
  4. [每天进步一点 -- 流水账]第1周
  5. Hibernate----Hibernate小配置
  6. 数据结构与算法 | 二分查找
  7. webstorm 2018 破解
  8. java jersey client,如何在Jersey-Client Java中实现重试机制
  9. LeetCode 647. 回文子串 (动态规划)
  10. 最近好闷,什么时候才有需求啊,很郁闷
  11. 堆排序-java实现
  12. Excel 中超链接转换为相应的 URL
  13. 蕉下招股书里提了26次的DTC,到底是啥?
  14. mysql orderby asc_MySQL 数据排序 order by
  15. java输出皮卡丘_使用CSS实现皮卡丘
  16. 2020最新苹果企业级开发者账号续费流程
  17. 什么类型的APP适合推广
  18. 2022年汽车驾驶员(中级)理论题库模拟考试平台操作
  19. excel 宏命令 VB 批量删除word表格的指定行
  20. 组合数求解与(扩展)卢卡斯定理

热门文章

  1. 笔记本宏碁 4750G加装固态硬盘小计
  2. bilibili有linux客户端吗,bilibili2021全新官网版
  3. python读取idx_通过Python从.idx3-ubyte文件或GZIP中提取图像 - python
  4. Python实现员工管理系统GUI
  5. 事件的独立与常见的几种分布
  6. 快速打开控制面板中的功能项
  7. ideaxml文件背景飘黄问题
  8. 基于.net6.0在wpf桌面应用中发布webApi服务示例
  9. 阿里云IOT网关BL110应用之28:实现三菱 PLC FX3U 接入阿里云平台
  10. QQ用户信息管理系统