基于Vue2和jsmind.js实现思维导图

1、安装jsmind

yarn add jsmind

2、源码

<template>
  <div>
    <div>
      <input type="button" value="放大" v-on:click="zoomIn" />
      <input type="button" value="缩小" v-on:click="zoomOut" />
    </div>
    <div id="jsmind_container"></div>
  </div>
</template>
 
<script>
import "jsmind/style/jsmind.css";
import jsMind from "jsmind/js/jsmind.js";
window.jsMind = jsMind;
 
require("jsmind/js/jsmind.draggable.js");
require("jsmind/js/jsmind.screenshot.js");
require('./jsmind.menu.js')
 
export default {
  name: "MyJsmind",
 
  data: function () {
    return {
      jm: null,
    };
  },
 
  mounted: function () {
    this.init_data();
  },
 
  methods: {
    init_data: function () {
      var mind = {
        /* 元数据,定义思维导图的名称、作者、版本等信息 */
        meta: {
          name: "jsMind-demo-tree",
          author: "hizzgdev@163.com",
          version: "0.2",
        },
        /* 数据格式声明 */
        format: "node_tree",
        /* 数据内容 */
        data: {
          id: "root",
          topic: "jsMind",
          children: [
            {
              id: "easy",
              topic: "Easy",
              direction: "left",
              expanded: false,
              children: [
                { id: "easy1", topic: "Easy to show" },
                { id: "easy2", topic: "Easy to edit" },
                { id: "easy3", topic: "Easy to store" },
                { id: "easy4", topic: "Easy to embed" },
              ],
            },
            {
              id: "open",
              topic: "Open Source",
              direction: "right",
              expanded: true,
              children: [
                { id: "open1", topic: "on GitHub" },
                { id: "open2", topic: "BSD License" },
              ],
            },
            {
              id: "powerful",
              topic: "Powerful",
              direction: "right",
              children: [
                { id: "powerful1", topic: "Base on Javascript" },
                { id: "powerful2", topic: "Base on HTML5" },
                { id: "powerful3", topic: "Depends on you" },
              ],
            },
            {
              id: "other",
              topic: "test node",
              direction: "left",
              children: [
                { id: "other1", topic: "I'm from local variable" },
                { id: "other2", topic: "I can do everything" },
              ],
            },
          ],
        },
      };
 
      var options = {
        container: "jsmind_container",
        editable: true,
        theme: "primary",
 
        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) {
                console.log(node);
              },
            },
            {
              target: "screenshot",
              text: "下载导图",
              callback: function (node) {
                console.log(node);
              },
            },
            {
              target: "showAll",
              text: "展开全部节点",
              callback: function (node) {
                console.log(node);
              },
            },
            {
              target: "hideAll",
              text: "收起全部节点",
              callback: function (node) {
                console.log(node);
              },
            },
          ],
        },
      };
 
      this.jm = new jsMind(options);
      this.jm.show(mind);
    },
 
    zoomIn: function () {
      this.jm.view.zoomIn();
    },
 
    zoomOut: function () {
      this.jm.view.zoomOut();
    },
  },
};
</script>
 
<style scoped>
#jsmind_container {
  width: 100%;
  height: 800px;
}
</style>

3、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')

4、效果图

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要使用Vue 3和AntV来实现思维导图,可以按照以下步骤进行操作: 1. 安装Vue 3和AntV:在项目目录下运行命令`npm install vue@next @antv/g6`来安装Vue 3和AntV G6图表库。 2. 创建Vue组件:在Vue项目中创建一个新的组件,命名为"MindMap"。在组件中导入Vue和AntV G6。 3. 初始化G6图表:在组件的生命周期钩子函数`mounted`中,使用AntV G6的`Graph`类初始化一个图表实例,并将其绑定到Vue组件实例的数据属性中。 4. 定义思维导图数据:在Vue组件中定义一个数据属性,用于存储思维导图的节点和边的数据。 5. 绘制思维导图:在Vue组件的`mounted`生命周期钩子函数中,使用G6图表实例的`data`方法将思维导图的数据绑定到图表上,并使用G6图表实例的`render`方法绘制图表。 6. 添加交互能力:通过G6图表实例的事件监听和交互能力,为思维导图添加节点的拖拽、缩放、连线等交互操作。 7. 样式定制和主题设置:通过G6图表实例的配置项,可以定制思维导图的样式和主题,如节点的颜色、形状、连线的样式等。 8. 完善其他功能:根据实际需求,可以实现添加节点、删除节点、编辑节点内容、保存导图等功能,通过Vue的数据双向绑定,更新思维导图的数据并重新渲染图表。 以上是用Vue 3和AntV实现思维导图的基本步骤,具体的实现方式可以根据项目需求进行调整和扩展。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值