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、效果图