赞
踩
在开发过程中,我们往往会使用到权限映射这样的架构图,也就是我们常说的组织架构,废话不多说,直接上代码…
Vue实现流程图,借鉴vue-tree-color
npm install vue-tree-color
同时查看项目中是否已安装less和less-loader,因为该组件使用到less
npm install --save-dev less less-loader
如果这里启动项目报错,有可能是less和less-loader的版本过高,可以降低版本,或者指定版本号
npm i less@3.9.0 less-loader@4.1.0 -D
import Vue2OrgTree from 'vue-tree-color'
Vue.use(Vue2OrgTree)
创建components文件夹,在components下加入下面两个文件
<template> <div ref='container' class='container'> <div v-if='isShow' class='spin'> <a-spin tip='数据加载中...' /> </div> <vueOrgTree v-if='!isShow' class='vueOrgTree' :data='data' :horizontal='true' :collapsable='true' @on-expand='onExpand' @on-node-click='NodeClick' @on-node-mouseover='onMouseover' @on-node-mouseout='onMouseout' :renderContent='renderContent' /> </div> </template> <script> import vueOrgTree from './Components/vueOrgTree.vue' import throttle from 'lodash/throttle' //导入数据 import { data, img, className } from './index' export default { name: 'permissionMap', components: { vueOrgTree }, data() { return { win: '', container: '', handler: () => { }, isShow: true, data: {} } }, created() { }, mounted() { this.win = window this.container = this.$refs.container this.container.style.minHeight = `${window.innerHeight - (52 + 65 + 12)}px` // 节流监听窗口大小 this.handler = throttle(this.resize, 800) this.win.addEventListener('resize', this.handler) //当设置完container宽高后获取数据 this.isShow=true setTimeout(()=>{ this.data = data if (this.data){ this.isShow = false this.toggleExpand(this.data, true) } },3000) //默认展开所有节点 }, methods: { // 监听自定义事件 resize() { this.container.style.minHeight = `${window.innerHeight - (52 + 65 + 24)}px` if (typeof this.win !== 'undefined') { if (!this.container || !this.width || !this.height) return } }, //渲染节点 renderContent(h, data) { // 通过data中的className属性来对div元素进行注入class // 每个节点渲染必然会走这个函数 //这里对应的不同的className 需要在上面的tree.less中写入样式 const result = this.changeRender(data) return result }, //判断相对等级渲染的dom changeRender(data) { if ([0, 1, '0', '1'].includes(data.level)) { data.img = img data.className = className return ( <div style='position:relative;display:flex;align-items: center;'> <div style='position: absolute;display: flex;width: 72px;height: 72px;border-radius: 50%;justify-content: center;align-items: center;'> <img src={(data.level == 0 && data.type == 0) ? data.img.userUrl : (data.level == 1 && data.type == 1) ? data.img.teacherUrl : (data.level == 1 && data.type == 2) ? data.img.adminUrl : (data.level == 1 && data.type == 3) ? data.img.directorUrl : data.img.postUrl} /> </div> <div class={(data.level == 0 && data.type == 0) ? data.className.user : (data.level == 1 && data.type == 1) ? data.className.teacher : (data.level == 1 && data.type == 2) ? data.className.admin : (data.level == 1 && data.type == 3) ? data.className.director : data.className.post} > <span> {data.label} </span> <span style='font-size: 11px;'>{data.jobNum}</span> </div> </div> ) } if ([2, '2'].includes(data.level)) { data.img = img data.className = className return ( <div style='position:relative;display:flex;align-items: center;'> <div class={data.type == 1 ? data.className.teacherChild : data.type == 2 ? data.className.adminChild : data.type == 3 ? data.className.directorChild : data.className.postChild} >{data.label} </div> </div> ) } if ([3, '3'].includes(data.level)) { data.className = className return ( <div style='position:relative;display:flex;align-items: center;'> <div class={data.type == 1 ? data.className.tecListChild : data.type == 2 ? data.className.adminListChild : data.type == 3 ? data.className.directorListChild : data.className.postListChild} > <div style='width: 24px;height: 24px;border-radius: 50%;color: #fff;display: block;justify-content: normal;align-items: normal;padding: 0;min-width: 0;font-weight: normal;' class={data.type == 1 ? data.className.teacherChild : data.type == 2 ? data.className.adminChild : data.type == 3 ? data.className.directorChild : data.className.postChild}> <i class={data.mark == 'app' ? 'action-icon actionzhuomianyingyongshezhi' : data.mark == 'server' ? 'action-icon actionfuwu1' : 'action-icon actioncaidan'} style=''></i> </div> <div style='display: flex;flex: 1;justify-content: center;height: 24px;'>{data.label}</div> </div> </div> ) } else { data.className = className return ( <div style='position:relative;display:flex;align-items: center;'> <div style='display: flex;justify-content: center;align-items: center;font-weight: 400;min-width: 90px;font-size: 13px;' class={data.type == 1 ? data.className.tecListChild : data.type == 2 ? data.className.adminListChild : data.type == 3 ? data.className.directorListChild : data.className.postListChild}> {data.label} </div> </div> ) } }, //鼠标移出 onMouseout(e, data) { console.log('onMouseout', data) }, //鼠标移入 onMouseover(e, data) { console.log('onMouseover', data) }, //点击节点 NodeClick(e, data) { console.log(e, data) }, //默认展开 toggleExpand(data, val) { if (Array.isArray(data)) { data.forEach(item => { this.$set(item, 'expand', val) if (item.children) { this.toggleExpand(item.children, val) } }) } else { this.$set(data, 'expand', val) if (data.children) { this.toggleExpand(data.children, val) } } }, collapse(list) { list.forEach(child => { if (child.expand) { child.expand = false } child.children && this.collapse(child.children) }) }, //展开 onExpand(e, data) { if ('expand' in data) { data.expand = !data.expand if (!data.expand && data.children) { this.collapse(data.children) } } else { this.$set(data, 'expand', true) } } }, beforeDestroy() { this.win.removeEventListener('resize', this.handler) } } </script> <style scoped lang='less'> @import "./index"; </style>
// 判断是否叶子节点 const isLeaf = (data, prop) => { return !(Array.isArray(data[prop]) && data[prop].length > 0) } // console.info('Thank you for using vue-tree-color \nIf you have any questions about this plug-in, please contact me in the following ways \nWeChat: yanjiahui12345 \nWeChat official number: Web_Miao') // 创建 node 节点 export const renderNode = (h, data, context) => { const { props } = context const cls = ['org-tree-node'] const childNodes = [] const children = data[props.props.children] if (isLeaf(data, props.props.children)) { cls.push('is-leaf') } else if (props.collapsable && !data[props.props.expand]) { cls.push('collapsed') } childNodes.push(renderLabel(h, data, context)) if (!props.collapsable || data[props.props.expand]) { childNodes.push(renderChildren(h, children, context)) } return h('div', { domProps: { className: cls.join(' ') } }, childNodes) } // 创建展开折叠按钮 export const renderBtn = (h, data, { props, listeners }) => { const expandHandler = listeners['on-expand'] let cls = ['org-tree-node-btn'] if (data[props.props.expand]) { cls.push('expanded') } return h('span', { domProps: { className: cls.join(' ') }, on: { click: e => expandHandler && expandHandler(e,data) } }) } // 创建 label 节点 export const renderLabel = (h, data, context) => { const { props, listeners } = context const label = data[props.props.label] const renderContent = props.renderContent // event handlers const clickHandler = listeners['on-node-click'] const mouseOverHandler = listeners['on-node-mouseover'] const mouseOutHandler = listeners['on-node-mouseout'] const childNodes = [] if (typeof renderContent === 'function') { let vnode = renderContent(h, data) vnode && childNodes.push(vnode) } else { childNodes.push(label) } if (props.collapsable && !isLeaf(data, props.props.children)) { childNodes.push(renderBtn(h, data, context)) } const cls = ['org-tree-node-label-inner'] let { labelWidth, labelClassName, selectedClassName, selectedKey ,judge,NodeClass} = props if (typeof labelWidth === 'number') { labelWidth += 'px' } if (typeof labelClassName === 'function') { labelClassName = labelClassName(data) } labelClassName && cls.push(labelClassName) // add selected class and key from props if (typeof selectedClassName === 'function') { selectedClassName = selectedClassName(data) } selectedClassName && selectedKey && data[selectedKey] && cls.push(selectedClassName) return h('div', { domProps: { className: 'org-tree-node-label' } }, [h('div', { domProps: { className:ChangeTheColor(data,judge,NodeClass) + " org-tree-node-label-inner" }, style: { width: labelWidth }, on: { 'click': e => clickHandler && clickHandler(e, data), 'mouseover': e => mouseOverHandler && mouseOverHandler(e, data), 'mouseout': e => mouseOutHandler && mouseOutHandler(e, data) } }, childNodes)]) } function ChangeTheColor(e,judge,NodeClass){ if(judge !== "" && judge !== undefined && judge !== null && judge.swtich !== false){ for(var k in judge) { var a = (eval("e."+k)) if(NodeClass){ for(let c =0 ;c<NodeClass.length;c++){ if( a === NodeClass[c]) return NodeClass[c] else if(NodeClass.length-1==c) return "" } }else{ return "" } } }else{ return "" } } // 创建 node 子节点 export const renderChildren = (h, list, context) => { if (Array.isArray(list) && list.length) { const children = list.map(item => { return renderNode(h, item, context) }) return h('div', { domProps: { className: 'org-tree-node-children' } }, children) } return '' } export const render = (h, context) => { const {props} = context return renderNode(h, props.data, context) } export default render
<template> <div ref='container' class='container'> <div v-if='isShow' class='spin'> <a-spin tip='数据加载中...' /> </div> <vueOrgTree v-if='!isShow' class='vueOrgTree' :data='data' :horizontal='true' :collapsable='true' @on-expand='onExpand' @on-node-click='NodeClick' @on-node-mouseover='onMouseover' @on-node-mouseout='onMouseout' :renderContent='renderContent' /> </div> </template> <script> import vueOrgTree from './Components/vueOrgTree.vue' import throttle from 'lodash/throttle' //导入数据 import { data, img, className } from './index' export default { name: 'permissionMap', components: { vueOrgTree }, data() { return { win: '', container: '', handler: () => { }, isShow: true, data: {} } }, created() { }, mounted() { this.win = window this.container = this.$refs.container this.container.style.minHeight = `${window.innerHeight - (52 + 65 + 12)}px` // 节流监听窗口大小 this.handler = throttle(this.resize, 800) this.win.addEventListener('resize', this.handler) //当设置完container宽高后获取数据 this.isShow=true setTimeout(()=>{ this.data = data if (this.data){ this.isShow = false this.toggleExpand(this.data, true) } },3000) //默认展开所有节点 }, methods: { // 监听自定义事件 resize() { this.container.style.minHeight = `${window.innerHeight - (52 + 65 + 24)}px` if (typeof this.win !== 'undefined') { if (!this.container || !this.width || !this.height) return } }, //渲染节点 renderContent(h, data) { // 通过data中的className属性来对div元素进行注入class // 每个节点渲染必然会走这个函数 //这里对应的不同的className 需要在上面的tree.less中写入样式 const result = this.changeRender(data) return result }, //判断相对等级渲染的dom changeRender(data) { if ([0, 1, '0', '1'].includes(data.level)) { data.img = img data.className = className return ( <div style='position:relative;display:flex;align-items: center;'> <div style='position: absolute;display: flex;width: 72px;height: 72px;border-radius: 50%;justify-content: center;align-items: center;'> <img src={(data.level == 0 && data.type == 0) ? data.img.userUrl : (data.level == 1 && data.type == 1) ? data.img.teacherUrl : (data.level == 1 && data.type == 2) ? data.img.adminUrl : (data.level == 1 && data.type == 3) ? data.img.directorUrl : data.img.postUrl} /> </div> <div class={(data.level == 0 && data.type == 0) ? data.className.user : (data.level == 1 && data.type == 1) ? data.className.teacher : (data.level == 1 && data.type == 2) ? data.className.admin : (data.level == 1 && data.type == 3) ? data.className.director : data.className.post} > <span> {data.label} </span> <span style='font-size: 11px;'>{data.jobNum}</span> </div> </div> ) } if ([2, '2'].includes(data.level)) { data.img = img data.className = className return ( <div style='position:relative;display:flex;align-items: center;'> <div class={data.type == 1 ? data.className.teacherChild : data.type == 2 ? data.className.adminChild : data.type == 3 ? data.className.directorChild : data.className.postChild} >{data.label} </div> </div> ) } if ([3, '3'].includes(data.level)) { data.className = className return ( <div style='position:relative;display:flex;align-items: center;'> <div class={data.type == 1 ? data.className.tecListChild : data.type == 2 ? data.className.adminListChild : data.type == 3 ? data.className.directorListChild : data.className.postListChild} > <div style='width: 24px;height: 24px;border-radius: 50%;color: #fff;display: block;justify-content: normal;align-items: normal;padding: 0;min-width: 0;font-weight: normal;' class={data.type == 1 ? data.className.teacherChild : data.type == 2 ? data.className.adminChild : data.type == 3 ? data.className.directorChild : data.className.postChild}> <i class={data.mark == 'app' ? 'action-icon actionzhuomianyingyongshezhi' : data.mark == 'server' ? 'action-icon actionfuwu1' : 'action-icon actioncaidan'} style=''></i> </div> <div style='display: flex;flex: 1;justify-content: center;height: 24px;'>{data.label}</div> </div> </div> ) } else { data.className = className return ( <div style='position:relative;display:flex;align-items: center;'> <div style='display: flex;justify-content: center;align-items: center;font-weight: 400;min-width: 90px;font-size: 13px;' class={data.type == 1 ? data.className.tecListChild : data.type == 2 ? data.className.adminListChild : data.type == 3 ? data.className.directorListChild : data.className.postListChild}> {data.label} </div> </div> ) } }, //鼠标移出 onMouseout(e, data) { console.log('onMouseout', data) }, //鼠标移入 onMouseover(e, data) { console.log('onMouseover', data) }, //点击节点 NodeClick(e, data) { console.log(e, data) }, //默认展开 toggleExpand(data, val) { if (Array.isArray(data)) { data.forEach(item => { this.$set(item, 'expand', val) if (item.children) { this.toggleExpand(item.children, val) } }) } else { this.$set(data, 'expand', val) if (data.children) { this.toggleExpand(data.children, val) } } }, collapse(list) { list.forEach(child => { if (child.expand) { child.expand = false } child.children && this.collapse(child.children) }) }, //展开 onExpand(e, data) { if ('expand' in data) { data.expand = !data.expand if (!data.expand && data.children) { this.collapse(data.children) } } else { this.$set(data, 'expand', true) } } }, beforeDestroy() { this.win.removeEventListener('resize', this.handler) } } </script> <style scoped lang='less'> @import "./index"; </style>
//数据 export const data = { id: 0, label: '用户角色', level: 0,//层级 type: 0,//类型(教师以及下面的子集为同一类型) jobNum: '123456', children: [ { id: 1, label: '教师', level: 1, type: 1, jobNum: '角色', children: [ { id: 55, level: 2, type: 1, label: '办事大厅', children: [ { id: 61, level: 3, type: 1, label: '应用', mark: 'app',//应用层标识 children: [ { id: 63, level: 4, type: 1, label: '信息站群', children: [ { id: 63, level: 5, type: 1, label: '信息站群1' }, { id: 63, level: 5, type: 1, label: '信息站群2' } ] }, { id: 63, level: 4, type: 1, label: 'AI助教' } ] }, { id: 62, level: 3, type: 1, label: '服务', mark: 'server'//服务层标识 }, { id: 63, level: 3, type: 1, label: '菜单', mark: 'menu'//菜单层标识 } ] }, { id: 56, level: 2, type: 1, label: '离校系统', children: [ { id: 61, level: 3, type: 1, label: '应用' }, { id: 62, level: 3, type: 1, label: '服务' }, { id: 63, level: 3, type: 1, label: '菜单' } ] }, { id: 57, level: 2, type: 1, label: '用户中心', children: [ { id: 61, level: 3, type: 1, label: '应用' }, { id: 62, level: 3, type: 1, label: '服务' }, { id: 63, level: 3, type: 1, label: '菜单' } ] } ] }, { id: 2, label: '管理员', level: 1, type: 2, jobNum: '角色', children: [ { id: 55, level: 2, type: 2, label: '办事大厅', children: [ { id: 61, level: 3, type: 2, label: '应用', children: [ { id: 63, level: 4, type: 2, label: '信息站群', children: [ { id: 63, level: 5, type: 2, label: '信息站群1' }, { id: 63, level: 5, type: 2, label: '信息站群2' } ] }, { id: 63, level: 4, type: 2, label: 'AI助教' } ] }, { id: 62, level: 3, type: 2, label: '服务' }, { id: 63, level: 3, type: 2, label: '菜单' } ] }, { id: 56, level: 2, type: 2, label: '离校系统' }, { id: 57, level: 2, type: 2, label: '用户中心' } ] }, { id: 3, label: '学院主任', level: 1, type: 3, jobNum: '角色', children: [ { id: 58, level: 2, type: 3, label: '办事大厅', children: [ { id: 61, level: 3, type: 3, label: '应用', children: [ { id: 63, level: 4, type: 3, label: '信息站群', children: [ { id: 63, level: 5, type: 3, label: '信息站群1' }, { id: 63, level: 5, type: 3, label: '信息站群2' } ] }, { id: 63, level: 4, type: 3, label: 'AI助教' } ] }, { id: 62, level: 3, type: 3, label: '服务' }, { id: 63, level: 3, type: 3, label: '菜单' } ] }, { id: 59, level: 2, type: 3, label: '离校系统' }, { id: 60, level: 2, type: 3, label: '用户中心' } ] }, { id: 4, label: '岗位', level: 1, type: 4, jobNum: '角色', children: [ { id: 61, level: 2, type: 4, label: '办事大厅', children: [ { id: 61, level: 3, type: 4, label: '应用', children: [ { id: 63, level: 4, type: 4, label: '信息站群' , children: [ { id: 63, level: 5, type: 4, label: '信息站群1' }, { id: 63, level: 5, type: 4, label: '信息站群2' } ] }, { id: 63, level: 4, type: 4, label: 'AI助教' } ] }, { id: 62, level: 3, type: 4, label: '服务' }, { id: 63, level: 3, type: 4, label: '菜单' } ] }, { id: 62, level: 2, type: 4, label: '离校系统' }, { id: 63, level: 2, type: 4, label: '用户中心' } ] } ] } //前两级渲染时需要的图片 export const img = { userUrl: require('@assets/permissionMap/user.png'),//用户角色 teacherUrl: require('@assets/permissionMap/teacher.png'),//教师 adminUrl: require('@assets/permissionMap/administrators.png'),//管理员 directorUrl: require('@assets/permissionMap/director.png'),//学院主任 postUrl: require('@assets/permissionMap/post.png')//岗位 } //渲染中所需要的样式 export const className = { user: 'userBgc',//用户角色层样式 teacher: 'teacherBgc',//教师样式 admin: 'adminBgc',//管理员层样式 director: 'directorBgc',//学院主任样式 post: 'postBgc',//岗位样式 sys: 'sysStyle',//系统层样式 teacherChild: 'teaC',//教师子集样式 adminChild: 'adminC',//管理员子集样式 directorChild: 'directC',//学院主任样式子集 postChild: 'postC',//岗位样式子集 tecListChild: 'tecListC',//教师应用,服务,菜单样式 adminListChild: 'adminListC',//管理员应用,服务,菜单样式 directorListChild: 'directorListC',//学院主任应用,服务,菜单样式 postListChild: 'postListC'//岗位应用,服务,菜单样式 }
.container { display: flex; .vueOrgTree { display: flex; flex: 1; align-items: center; box-sizing: border-box; overflow: auto; } .spin { display: flex; flex: 1; justify-content: center; align-items: center; background: #fff; } } .userBgc, .teacherBgc, .adminBgc, .directorBgc, .postBgc, .sysStyle { display: flex; //justify-content: center; align-items: center; flex-direction: column; padding: 0px 15px 0px 74px; min-width: 157px; height: 50px; box-shadow: 0px 0px 13px 0px rgba(0, 0, 0, 0.09); border-radius: 31px !important; border: 4px solid #FFFFFF; font-size: 16px; font-weight: bold; color: #FFFFFF; } .teaC, .adminC, .directC, .postC { display: flex; justify-content: center; align-items: center; padding: 10px 20px; min-width: 80px; height: 30px; border-radius: 16px; font-size: 13px; font-weight: bold; color: #FFFFFF; } .userBgc { background: #88A3C7; } .teacherBgc, .teaC { background: #FFA66F; } .adminBgc, .adminC { background: #7BA1FF; } .directorBgc, .directC { background: #A382EF; } .postBgc, .postC { background: #60CFD9; } .tecListC, .adminListC, .directorListC, .postListC { display: flex; justify-content: space-between; align-items: center; min-width: 80px; height: 30px; border-radius: 21px; border: 1px solid #FFA66F; padding: 3px; font-size: 14px; font-weight: 500; color: #333333; } .tecListC { border: 1px solid #FFA66F; } .adminListC { border: 1px solid #7BA1FF; } .directorListC { border: 1px solid #A382EF; } .postListC { border: 1px solid #60CFD9; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。