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多級樹形菜單內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: