vue-JsMind思维导图实现(包含鼠标右键自定义菜单)

项目需要 思维导图这个功能,暂时还不知道具体需要什么功能,就自己先搞了一下,简单实现节点添加 编辑  删除 拖拽 及下载导图等功能,还有鼠标右键点击节点出现自定义菜单的功能,看了好多的自己以及其他人写的,发现很多都是有缺失的,不能拿来直接使用,下面我总结了一下,可以直接复制使用。

(注:jsmind.menu.js 引用一定要放到 jsmind后面)

按钮事件  或者 鼠标右键按钮事件 可二选一选用,废话不多说 直接上代码

<template>
    <div class="brain_content">
        <div class="header_btn">
            <el-button @click='zoomOut' ref="zoomOut">缩小</el-button>
            <el-button @click='zoomIn' ref="zoomIn">放大</el-button>
            <el-button @click="addNode">添加节点</el-button>
            <el-button @click="onRemoveNode">删除节点</el-button>
        </div>
        <js-mind class="js_mind" :options="options" :values="mind" v-show="isShow" ref="jsMind" height="100%"></js-mind>
    </div>
</template>
 
<script>
import Vue from 'vue'
import jm from "vue-jsmind"
import '../../../../../public/static/js/jsmind.menu.js'

Vue.use(jm)
if (window.jsMind) {
  console.log('wind')
  Vue.prototype.jsMind = window.jsMind
}
export default {
    data () {
        return {
            drawer: false,
            direction: 'rtl',
            theme_value: '',
            mind: {
                meta: {/* 元数据,定义思维导图的名称、作者、版本等信息 */
                    name: 'ztengluo',
                    author: 'hizzgdev@163.com',
                    version: '0.2'
                },
                format: 'node_tree',
                data: {
                id: 'root',
                topic: '三国',
                children: [
                    {
                        id: 'easy', // [必选] ID, 所有节点的ID不应有重复,否则ID重复的结节将被忽略
                        topic: '魏', // [必选] 节点上显示的内容
                        direction: 'left', // [可选] 节点的方向,此数据仅在第一层节点上有效,目前仅支持 left 和 right 两种,默认为 right
                        expanded: true, // [可选] 该节点是否是展开状态,默认为 true
                        children: [
                            { id: 'easy1', topic: '曹操' },
                            { id: 'easy2', topic: '张辽' },
                            { id: 'easy3', topic: '司马懿' },
                            { id: 'easy4', topic: '司马昭' },
                            { id: 'easy5', topic: '邓艾' },
                            { id: 'easy6', topic: '夏侯淳' },
                        ]
                    },
                    {
                        id: 'open',
                        topic: '蜀',
                        direction: 'right',
                        expanded: true,
                        children: [
                            { 
                                id: 'open1', 
                                topic: '刘备',
                                children: [
                                    { 
                                        id: 'open2', 
                                        topic: '文',
                                        children: [
                                            { id: 'open3', topic: '诸葛亮' },
                                            { id: 'open4', topic: '徐庶' },
                                        ]
                                    },
                                    { 
                                        id: 'open5', 
                                        topic: '武',
                                        children: [
                                            { id: 'open6', topic: '黄忠' },
                                            { id: 'open7', topic: '马超' },
                                            { id: 'open8', topic: '姜维' },
                                            { id: 'open9', topic: '张飞' },
                                            { id: 'open10', topic: '赵云' },
                                        ]
                                    },
                                ]
                            },
                            
                        ]
                    },
                    {
                        id: 'powerful',
                        topic: '吴',
                        direction: 'right',
                        children: [
                            { id: 'powerful1', topic: '孙策' },
                            { id: 'powerful2', topic: '孙权' },
                            { id: 'powerful3', topic: '周瑜' },
                            { id: 'powerful4', topic: '鲁肃' },
                            { id: 'powerful5', topic: '吕蒙' },
                            { id: 'powerful6', topic: '黄盖' },
                        ]
                    }
                ]
                }
            },
            options: {
                container: 'jsmind_container', // [必选] 容器的ID
                editable: false, // [可选] 是否启用编辑
                theme: 'clouds', // [可选] 主题
                support_html : true,    // 是否支持节点里的HTML元素
                view:{
                    engine: 'canvas',   // 思维导图各节点之间线条的绘制引擎
                    hmargin:100,        // 思维导图距容器外框的最小水平距离
                    vmargin:50,         // 思维导图距容器外框的最小垂直距离
                    line_width:1,       // 思维导图线条的粗细
                    line_color:'#C18AFB'   // 思维导图线条的颜色
                },
                menuOpts:{
                    showMenu: true,
                    injectionList: [
                        {target:'edit',text: '编辑节点',
                            callback: function (node) {
                            console.log(node)
                            }
                        },
                        {target:'addChild',text: '添加子节点',
                            callback: function (node) {
                                console.log(node)
                            }
                        },
                        {target:'addBrother',text: '添加兄弟节点',
                            callback: function (node) {
                                console.log(node)
                            }
                        },
                        {target:'delete',text: '删除节点',
                            callback: function (node,next) {
                                console.log(node)
                            }
                        },
                        {target:'screenshot',text: '下载导图',
                            callback: function (node,next) {
                                console.log(node)
                            }
                        },
                        {target:'showAll',text: '展开全部节点',
                            callback: function (node,next) {
                                console.log(node)
                            }
                        },
                        {target:'hideAll',text: '收起全部节点',
                            callback: function (node,next) {
                                console.log(node)
                            }
                        },
                    ],
                },
            },
            isShow: true,
            isLoad: false,
        }
    },
    methods: {
        // 缩小
        zoomOut () {
            if (this.jm.view.zoomOut()) {
                this.$refs.zoomOut.disabled = false
            } else {
                this.$refs.zoomOut.disabled = true
            }
        },
        // 放大
        zoomIn () {
            if (this.jm.view.zoomIn()) {
                this.$refs.zoomIn.disabled = false
            } else {
                this.$refs.zoomIn.disabled = true
            }
        },
        // 新增节点
        addNode () {
            var selectedNode = this.jm.get_selected_node() // as parent of new node
            if (!selectedNode) { alert('请先选择一个节点'); return }
            var nodeid = this.jsMind.util.uuid.newid()
            var topic = '请输入子节点名称'
            this.jm.add_node(selectedNode, nodeid, topic)
        },
        // 删除节点 
        onRemoveNode () {
            var selectedId = this.get_selected_nodeid()
            if (!selectedId) { alert('请先选择一个节点'); return }
            this.jm.remove_node(selectedId)
        },
        // 获取选中标签的 ID
        get_selected_nodeid () {
            var selectedNode = this.jm.get_selected_node()
            if (selectedNode) {
                return selectedNode.id
            } else {
                return null
            }
        }
    },
    mounted () {
        // 阻止浏览器默认右键事件
        document.oncontextmenu = function () {
            return false;
        };
        this.jm = this.$refs.jsMind.jm
        this.jm.enable_edit()
    },
}
</script>
 
<style lang="less" scoped>
.brain_content {
    /deep/ jmnodes.theme-clouds jmnode{background-color: #f0fadd;color:#333;}
    /deep/ jmnodes.theme-clouds jmnode:hover{background-color:#D6E3BD;}
    /deep/ jmnodes.theme-clouds jmnode.selected{background-color: #b6d47e;color:#333;}
    height: 100%;
    padding: 10px;
    display: flex;
    flex-direction: column;
    .header_btn {
        height: 40px;
        line-height: 40px;
    }
    .js_mind {
        flex: 1;
    }
}
</style>

下面上一下jsmind.menu.js 文件

/*
 * Released under BSD License
 * Copyright (c) 2019-2020 Allen_sun_js@hotmail.com
 *
 * Project Home:
 *  https://github.com/allensunjian
 */

(function ($w, temp) {
  var Jm = $w[temp]

  var name = 'menu'

  var $d = $w['document']

  var menuEvent = 'oncontextmenu'

  var clickEvent = 'onclick'

  var overEvent = 'mouseover'

  var $c = function (tag) { return $d.createElement(tag) }

  var _noop = function () { }

  var logger = (typeof console === 'undefined') ? {

    log: _noop, debug: _noop, error: _noop, warn: _noop, info: _noop

  } : console

  var $t = function (n, t) { if (n.hasChildNodes()) { n.firstChild.nodeValue = t } else { n.appendChild($d.createTextNode(t)) } }

  var $h = function (n, t) {
    if (t instanceof HTMLElement) {
      t.innerHTML = ''
      n.appendChild(t)
    } else {
      n.innerHTML = t
    }
  }

  if (!Jm || Jm[name]) return

  Jm.menu = function (_jm) {
    this._get_menu_options(_jm, function () {
      this.init(_jm)

      this._mount_events()
    })
  }
  Jm.menu.prototype = {

    defaultDataMap: {
      funcMap: {
        edit: {
          isDepNode: true,
          // defaultFn不受到中台变量的控制,始终会先于fn去执行
          defaultFn: function (node) {
            var f = this._menu_default_mind_methods._menu_begin_edit.call(this.jm)
            f && this._menu_default_mind_methods._menu_edit_node_begin(this.jm.view, node)
          },
          fn: _noop,
          text: 'edit node'
        },
        addChild: {
          isDepNode: true,
          fn: function (nodeid, text) {
            var selected_node = this.get_selected_node()
            if (selected_node) {
              var node = this.add_node(selected_node, nodeid, text)
              if (node) {
                this.select_node(nodeid)
                this.begin_edit(nodeid)
              }
            }
          },
          text: 'append child'
        },
        addBrother: {
          isDepNode: true,
          fn: function (nodeid, text) {
            var selected_node = this.get_selected_node()
            if (!!selected_node && !selected_node.isroot) {
              var node = this.insert_node_after(selected_node, nodeid, text)
              if (node) {
                this.select_node(nodeid)
                this.begin_edit(nodeid)
              }
            }
          },
          text: 'append brother'
        },
        delete: {
          isDepNode: true,
          fn: function () {
            this.shortcut.handle_delnode.call(this.shortcut, this)
          },
          text: 'delete node'
        },
        showAll: {
          sDepNode: false,
          fn: function () {
            this.expand_all(this)
          },
          text: 'show all'
        },
        hideAll: {
          isDepNode: false,
          fn: function () {
            this.collapse_all(this)
          },
          text: 'hide all'
        },
        screenshot: {
          isDepNode: false,
          fn: function () {
            if (!this.screenshot) {
              logger.error('[jsmind] screenshot dependent on jsmind.screenshot.js !')
              return
            }
            this.screenshot.shootDownload()
          },
          text: 'load mind picture'
        },
        showNode: {
          isDepNode: true,
          fn: function (node) {
            this.expand_node(node)
          },
          text: 'show target node'
        },
        hideNode: {
          isDepNode: true,
          fn: function (node) {
            this.collapse_node(node)
          },
          text: 'hide target node'
        }
      },
      menuStl: {
        'width': '150px',
        'padding': '12px 0',
        'position': 'fixed',
        'z-index': '10',
        'background': '#fff',
        'box-shadow': '0 2px 12px 0 rgba(0,0,0,0.1)',
        'border-radius': '5px',
        'font-size': '12px',
        'display': 'none'
      },
      menuItemStl: {
        padding: '5px 15px',
        cursor: 'pointer',
        display: 'block',
        'text-align': 'center',
        'transition': 'all .2s'
      },
      injectionList: ['edit', 'addChild', 'delete']
    },

    init: function (_jm) {
      this._create_menu(_jm)
      this._get_injectionList(_jm)
      this.menuOpts.switchMidStage && Jm.util.dom.add_event(_jm.view.e_editor, 'blur', function (e) {
        this._menu_default_mind_methods._menu_edit_node_end.call(_jm.view)
        if (typeof this.menuOpts.editCaller === 'function') {
          this.menuOpts.editCaller($w.menu._update_node_info, this._menu_default_mind_methods._menu_update_edit_node)
          return
        }
        this._menu_default_mind_methods._menu_update_edit_node()
      }.bind(this))
    },

    _event_contextMenu (e) {
      e.preventDefault()
      this.menu.style.left = e.clientX + 'px'
      this.menu.style.top = e.clientY + 'px'
      this.menu.style.display = 'block'
      this.selected_node = this.jm.get_selected_node()
    },

    _event_hideMenu () {
      this.menu.style.display = 'none'
    },

    _mount_events () {
      $w[menuEvent] = this._event_contextMenu.bind(this)
      $w[clickEvent] = this._event_hideMenu.bind(this)
    },

    _create_menu (_jm) {
      var d = $c('menu')
      this._set_menu_wrap_syl(d)
      this.menu = d
      this.e_panel = _jm.view.e_panel
      this.e_panel.appendChild(d)
    },

    _create_menu_item (j, text, fn, isDepNode, cb, defaultFn) {
      var d = $c('menu-item'); var _this = this
      this._set_menu_item_syl(d)
      d.innerText = text
      d.addEventListener('click', function () {
        if (this.selected_node || !isDepNode) {
          defaultFn.call(_this, this.selected_node)
          if (!_this._get_mid_opts()) {
            cb(this.selected_node, _noop)
            fn.call(j, Jm.util.uuid.newid(), this.menuOpts.newNodeText || '请输入节点名称')
            return
          }
          cb(this.selected_node, _this._mid_stage_next(function () {
            var retArgs = [this.selected_node]
            var argus = Array.prototype.slice.call(arguments[0], 0)
            argus[1] = this.menuOpts.newNodeText || '请输入节点名称'
            if (argus[0]) {
              retArgs = argus
            }
            fn.apply(j, retArgs)
          }.bind(this)))
          return
        }
        alert(this.menuOpts.tipContent || 'Continue with node selected!')
      }.bind(this))
      d.addEventListener('mouseover', function () {
        d.style.background = 'rgb(179, 216, 255)'
      })
      d.addEventListener('mouseleave', function () {
        d.style.background = '#fff'
      })
      return d
    },

    _set_menu_wrap_syl (d) {
      var os = this._get_option_sty('menu', this._get_mixin_sty)
      d.style.cssText = this._format_cssText(os)
    },

    _set_menu_item_syl (d) {
      var os = this._get_option_sty('menuItem', this._get_mixin_sty)
      d.style.cssText = this._format_cssText(os)
    },

    _format_cssText (o) {
      var text = ''
      Object.keys(o).forEach(function (k) {
        text += k + ':' + o[k] + ';'
      })
      return text
    },

    _empty_object (o) {
      return Object.keys(o).length == 0
    },

    _get_option_sty (type, fn) {
      var sty = this.menuOpts.style
      var menu = this.defaultDataMap.menuStl
      var menuItem = this.defaultDataMap.menuItemStl
      var o = {menu, menuItem}
      if (!sty) return o[type]
      if (!sty[type]) return o[type]
      if (!sty[type] || this._empty_object(sty[type])) return o[type]
      return fn(o[type], sty[type])
    },

    _get_mixin_sty (dSty, oSty) {
      var o = {}
      Object.keys(oSty).forEach(function (k) {
        o[k] = oSty[k]
      })
      Object.keys(dSty).forEach(function (k) {
        if (!o[k]) o[k] = dSty[k]
      })
      return o
    },

    _get_menu_options (j, fn) {
      var options = j.options
      if (!options.menuOpts) return
      if (!options.menuOpts.showMenu) return
      this.menuOpts = j.options.menuOpts
      fn.call(this)
    },

    _get_injectionDetail () {
      var iLs = this.menuOpts.injectionList
      var dLs = this.defaultDataMap.injectionList
      if (!iLs) return dLs
      if (!Array.isArray(iLs)) {
        logger.error('[jsmind] injectionList must be a Array')
        return
      }
      if (iLs.length == 0) return dLs
      return iLs
    },

    _get_injectionList (j) {
      var list = this._get_injectionDetail()
      var _this = this
      list.forEach(function (k) {
        var o = null
        var text = ''
        var callback = _noop
        var defaultFn = _noop

        if (typeof k === 'object') {
          o = _this.defaultDataMap.funcMap[k.target]
          text = k.text
          k.callback && (callback = k.callback)
        } else {
          o = _this.defaultDataMap.funcMap[k]
          text = o.text
        }

        if (o.defaultFn) defaultFn = o.defaultFn
        _this.menu.appendChild(_this._create_menu_item(j, text, o.fn, o.isDepNode, callback, defaultFn))
      })
    },

    _get_mid_opts () {
      var b = this.menuOpts.switchMidStage
      if (!b) return false
      if (typeof b !== 'boolean') {
        logger.error('[jsmind] switchMidStage must be Boolean')
        return false
      }
      return b
    },

    _switch_view_db_event (b, jm) {
      Jm.prototype.dblclick_handle = _noop
      Jm.shortcut_provider.prototype.handler = _noop
      Jm.view_provider.prototype.edit_node_end = _noop
    },

    _mid_stage_next (fn) {
      return function () {
        fn(arguments)
      }
    },

    _reset_mind_event_edit () {},

    _menu_default_mind_methods: {
      _menu_begin_edit: function () {
        var f = this.get_editable()
        if (!f) {
          logger.error('fail, this mind map is not editable.')
        };
        return f
      },
      _menu_edit_node_begin (scope, node) {
        if (!node.topic) {
          logger.warn("don't edit image nodes")
          return
        }
        if (scope.editing_node != null) {
          this._menu_default_mind_methods._menu_edit_node_end.call(scope)
        }
        scope.editing_node = node
        var view_data = node._data.view
        var element = view_data.element
        var topic = node.topic
        var ncs = getComputedStyle(element)
        scope.e_editor.value = topic
        scope.e_editor.style.width = (element.clientWidth - parseInt(ncs.getPropertyValue('padding-left')) - parseInt(ncs.getPropertyValue('padding-right'))) + 'px'
        element.innerHTML = ''
        element.appendChild(scope.e_editor)
        element.style.zIndex = 5
        scope.e_editor.focus()
        scope.e_editor.select()
      },
      _menu_edit_node_end: function () {
        if (this.editing_node != null) {
          var node = this.editing_node
          this.editing_node = null
          var view_data = node._data.view
          var element = view_data.element
          var topic = this.e_editor.value
          element.style.zIndex = 'auto'
          element.removeChild(this.e_editor)
          $w.menu._update_node_info = {id: node.id, topic: topic}
          if (Jm.util.text.is_empty(topic) || node.topic === topic) {
            if (this.opts.support_html) {
              $h(element, node.topic)
            } else {
              $t(element, node.topic)
            }
          }
        }
      },
      _menu_update_edit_node: function () {
        var info = $w.menu._update_node_info
        $w.menu.jm.update_node(info.id, info.topic)
      }
    }

  }
  var plugin = new Jm.plugin('menu', function (_jm) {
    $w.menu = new Jm.menu(_jm)

    menu.jm = _jm

    if (menu.menuOpts) _jm.menu = menu
  })

  Jm.register_plugin(plugin)

  function preventMindEventDefault () {
    Jm.menu.prototype._switch_view_db_event()
  }
  Jm.preventMindEventDefault = preventMindEventDefault
})(window, 'jsMind')

备注:(如果要更改 思维导图 如hover节点样式 以及选中样式 或者其他配置中不可配置的样式时, 可以选中引入 jsmind.css 文件,然后在css文件中找到对应主题进行更改, 或者跟我这个一样直接在当前页面中 使用 deep属性直接更改他们的样式,会把你当前设置主题的样式给覆盖掉)

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
要在Vue中实现定义鼠标菜单,你可以借助第三方插件或者自己编写代码来实现。下面是一个使用第三方插件 `vue-contextmenu` 的示例: 首先,安装 `vue-contextmenu` 插件: ```shell npm install vue-contextmenu ``` 然后,在你的Vue项目中,入并注册 `vue-contextmenu` 插件: ```javascript import VueContextmenu from 'vue-contextmenu'; Vue.use(VueContextmenu); ``` 接下来,在需要添加菜单的元素上使用 `v-contextmenu` 指令,并绑定一个方法来定义菜单的内容和行为: ```html <template> <div> <div v-contextmenu="contextMenu"> Right-click me! </div> </div> </template> <script> export default { data() { return { contextMenu: [ { text: '菜单项1', action: 'menuItem1' }, { text: '菜单项2', action: 'menuItem2' }, { text: '菜单项3', action: 'menuItem3' } ] }; }, methods: { menuItem1() { console.log('执行菜单项1的操作'); }, menuItem2() { console.log('执行菜单项2的操作'); }, menuItem3() { console.log('执行菜单项3的操作'); } } }; </script> ``` 在这个例子中,`contextMenu` 数组定义菜单的内容,每个菜单项都有一个 `text` 属性用于显示菜单项的文本,以及一个 `action` 属性用于定义菜单项被点击时要执行的方法。 运行项目后,当你在指定的元素上点击时,就会弹出自定义菜单,并执行相应的方法。 除了使用第三方插件外,你也可以根据需求自己编写代码来实现定义鼠标菜单。这需要使用原生JavaScript事件来捕获点击事件,并自定义菜单的样式和行为。 希望对你有所帮助!如果还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值