首页 > 编程语言 > SpringBoot mybatis 实现多级树形菜单的示例代码
2021
06-12

SpringBoot mybatis 实现多级树形菜单的示例代码

一、前言

iview-admin中提供了 v-org-tree 这么一个vue组件可以实现树形菜单,下面小编来提供一下在element-ui中的使用教程(项目见:https://github.com/lison16/v-org-tree)

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

二、使用教程

(1)安装依赖

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

(2)引入组件

在main.js文件中引入

import TreeTable from 'tree-table-vue'
import VOrgTree from 'v-org-tree'

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

在项目目录下 -> src -> directive文件夹中引入如下4个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.success
    el.__error_callback__ = binding.value.error
    clipboard.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.value
    el.__success_callback__ = binding.value.success
    el.__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 '@/libs/tools'
export default {
  inserted: (el, binding, vnode) => {
    const triggerDom = document.querySelector(binding.value.trigger)
    triggerDom.style.cursor = 'move'
    const bodyDom = document.querySelector(binding.value.body)
    let pageX = 0
    let pageY = 0
    let transformX = 0
    let transformY = 0
    let canMove = false
    const handleMousedown = e => {
      let transform = /\(.*\)/.exec(bodyDom.style.transform)
      if (transform) {
        transform = transform[0].slice(1, transform[0].length - 1)
        const splitxy = transform.split('px, ')
        transformX = parseFloat(splitxy[0])
        transformY = parseFloat(splitxy[1].split('px')[0])
      }
      pageX = e.pageX
      pageY = e.pageY
      canMove = true
    }
    const handleMousemove = e => {
      const xOffset = e.pageX - pageX + transformX
      const yOffset = e.pageY - pageY + transformY
      if (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) return
    const bodyDom = document.querySelector(binding.value.body)
    bodyDom.style.transform = ''
  }
}

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组件

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

项目\src\components\org-view下面建立二个文件:

index.js

import OrgView from './org-view.vue'
export default OrgView

org-view.vue

<template>
  <div
    ref="dragWrapper"
    class="org-tree-drag-wrapper"
    @mousedown="mousedownView"
    @contextmenu="handleDocumentContextmenu"
  >
    <div class="org-tree-wrapper" :style="orgTreeStyle">
      <v-org-tree
        v-if="data"
        :data="data"
        :node-render="nodeRender"
        :expand-all="true"
        @on-node-click="handleNodeClick"
        collapsable
      ></v-org-tree>
    </div>
  </div>
</template>
 
<script>
import { on, off } from '@/directive/module/tools'
 
export default {
  name: 'OrgView',
  props: {
    zoomHandled: {
      type: Number,
      default: 1
    },
    data: Object,
    menuList: {
      type: Array,
      default: function () {
        return [
          {
            key: 'edit',
            label: '编辑公司'
          },
          {
            key: 'detail',
            label: '查看公司'
          },
          {
            key: 'add',
            label: '新增子公司'
          },
          {
            key: 'delete',
            label: '删除公司'
          }
        ]
      }
    }
  },
  data () {
    return {
      currentContextMenuId: '',
      orgTreeOffsetLeft: 0,
      orgTreeOffsetTop: 0,
      initPageX: 0,
      initPageY: 0,
      oldMarginLeft: 0,
      oldMarginTop: 0,
      canMove: false
    }
  },
  computed: {
    orgTreeStyle () {
      return {
        transform: `translate(-50%, -50%) scale(${this.zoomHandled}, ${
          this.zoomHandled
        })`,
        marginLeft: `${this.orgTreeOffsetLeft}px`,
        marginTop: `${this.orgTreeOffsetTop}px`
      }
    }
  },
  methods: {
    handleNodeClick (e, data, expand) {
      expand()
    },
    closeMenu () {
      this.currentContextMenuId = ''
    },
    getBgColor (data) {
      return this.currentContextMenuId === data.id
        ? data.isRoot
          ? '#0d7fe8'
          : '#5d6c7b'
        : ''
    },
    nodeRender (h, data) {
      return (
        <div
          class={[
            'custom-org-node',
            data.children && data.children.length ? 'has-children-label' : ''
          ]}
          on-mousedown={event => event.stopPropagation()}
          on-contextmenu={this.contextmenu.bind(this, data)}
        >
          {data.label}
          <dropdown
            trigger="custom"
            class="context-menu"
            visible={this.currentContextMenuId === data.id}
            nativeOn-click={this.handleDropdownClick}
            on-on-click={this.handleContextMenuClick.bind(this, data)}
            style={{
              transform: `scale(${1 / this.zoomHandled}, ${1 /
                this.zoomHandled})`
            }}
            v-click-outside={this.closeMenu}
          >
            <dropdown-menu slot="list">
              {this.menuList.map(item => {
                return (
                  <dropdown-item name={item.key}>{item.label}</dropdown-item>
                )
              })}
            </dropdown-menu>
          </dropdown>
        </div>
      )
    },
    contextmenu (data, $event) {
      const event = $event || window.event
      event.preventDefault
        ? event.preventDefault()
        : (event.returnValue = false)
      this.currentContextMenuId = data.id
    },
    setDepartmentData (data) {
      data.isRoot = true
      this.departmentData = data
    },
    mousedownView (event) {
      this.canMove = true
      this.initPageX = event.pageX
      this.initPageY = event.pageY
      this.oldMarginLeft = this.orgTreeOffsetLeft
      this.oldMarginTop = this.orgTreeOffsetTop
      on(document, 'mousemove', this.mousemoveView)
      on(document, 'mouseup', this.mouseupView)
    },
    mousemoveView (event) {
      if (!this.canMove) return
      const { pageX, pageY } = event
      this.orgTreeOffsetLeft = this.oldMarginLeft + pageX - this.initPageX
      this.orgTreeOffsetTop = this.oldMarginTop + pageY - this.initPageY
    },
    mouseupView () {
      this.canMove = false
      off(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>
</style>

项目\src\components\zoom-controller下面建立二个文件:

index.js

import ZoomController from './zoom-controller'
export default ZoomController

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>

 项目\src\view下面建立org.vue文件

<template>
  <Card shadow style="height: 100%;width: 100%;overflow:hidden">
    <div class="department-outer">
      <div class="zoom-box">
        <zoom-controller v-model="zoom" :min="20" :max="200"></zoom-controller>
      </div>
      <div class="view-box">
        <org-view
            v-if="data"
            :data="data"
            :zoom-handled="zoomHandled"
            :menuList="menuList"
            @on-menu-click="handleMenuClick"
        ></org-view>
      </div>
    </div>
  </Card>
</template>
 
<script>
import { orgList } from '@/api/org'
import { layerDialog } from '@/libs/Diaglog'
import './org.less'
 
const OrgView = Vue.component('OrgView', function (resolve) {
  require(['/user/org-view'], resolve)
})
const ZoomController = Vue.component('ZoomController', function (resolve) {
  require(['/user/zoom-controller'], resolve)
})
export default {
  name: 'org_tree_page',
  components: {
    OrgView,
    ZoomController
  },
  data () {
    return {
      data: null,
      zoom: 100,
      menuList: [
        {
          key: 'add',
          label: '新增子公司'
        },
        {
          key: 'edit',
          label: '编辑公司'
        },
        {
          key: 'delete',
          label: '删除公司'
        }
      ]
    }
  },
  computed: {
    zoomHandled () {
      return this.zoom / 100
    }
  },
  methods: {
    setDepartmentData (data) {
      data.isRoot = true
      return data
    },
    handleMenuClick ({ data, key }) {
      switch (key) {
        case 'add':
        case 'edit':
          console.log(data)
          this.showDialog(data, key)
          break
        case 'delete':
          break
      }
    },
    showDialog (data, key) {
      data.key = key
      const option = {
        id: key + 'SaveDialog',
        title: this.$i18n.t(key),
        width: '600px',
        height: '550px',
        url: '/api/org/' + Qs.stringify(data, { arrayFormat: 'brackets' })
      }
      layerDialog(option)
    },
    getDepartmentData () {
      const entity = {}
      const levels = '0,1,2,3,4,5,6'
      entity.status = 1
      getOrgList(entity, 1, 20, levels).then(result => {
        if (result.data.code === 10000) {
          let len = 0
          const list = result.data.data.list
          if (list) {
            len = list.length
          }
          var data = { id: 0, label: '阿里巴巴集团', level: 0 }
          if (len > 0) {
            data.children = this.formatData(list, 1, 0)
          }
          this.data = data
        }
      })
    },
    formatData (list, level, pid, pname) {
      const childrenData = []
      for (let i = 0; i < list.length; i++) {
        const data = {}
        const item = list[i]
        if (level === item.level) {
          data.id = item.id
          data.label = item.name
          data.parentName = pname
          data.level = item.level
          data.children = this.formatData(list, level + 1, item.id, item.name)
          childrenData.push(data)
        }
      }
      return childrenData
    }
  },
  mounted () {
    this.getDepartmentData()
  }
}
</script>

接口中核心代码:

List<Station> stations = stationService.listByEntity(station,levelList);
        List<StationVo> stationVos = new ArrayList();
        for(Station s : stations) {
            StationVo vo = new StationVo();
            vo.setId(s.getId());
            vo.setLevel(s.getLevel());
            vo.setName(s.getName());
            stationVos.add(vo);
        }

数据库中四个字段

mybatis  配置:

返回的json如下:

{
  "data" : {
    "page" : 1,
    "pageSize" : 20,
    "total" : 6,
    "pages" : 1,
    "list" : [ {
      "id" : 1,
      "name" : "天猫科技服务有限公司",
      "level" : 1,
      "status" : 1,
    }, {
      "id" : 2,
      "name" : "淘宝技术服务有限公司",
      "level" : 1,
      "status" : 1,
    }, {
      "id" : 3,
      "name" : "聚划算科技服务有限公司",
      "level" : 1,
      "status" : 1,
    }, {
      "id" : 4,
      "name" : "菜鸟金服",
      "level" : 2,
      "status" : 1,
    }, {
      "id" : 5,
      "name" : "黑鸟网络",
      "level" : 3,
      "status" : 1,
    }, {
      "id" : 6,
      "name" : "白鸟 网络",
      "level" : 3,
      "status" : 1,
    } ]
  },
  "message" : "获取成功",
  "code" : 200
}

到此这篇关于SpringBoot mybatis 实现多级树形菜单的示例代码的文章就介绍到这了,更多相关SpringBoot mybatis多级树形菜单内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!

编程技巧