在项目中遇到组织架构或思维导图的需求,选的技术是 jsmind
官方文档给的示例,有需要的可以参考:示例
先来看看效果图:
如想查看完整代码:请访问 jsmind_demo
尝试了两种类型:第一张截图是普通菜单类型,第二张截图可以进行右键菜单操作。最终选用了普通菜单类型。
jsmind
可以导入 jm
文件并渲染出来,也可以对编辑后的文件进行保存,也可以下载编辑好的思维导图,可以展开指定层级节点,修改主题,以及对节点进行增删改查等操作。
演示:
接下来如何使用:
npm i jsmind
// 或者
yarn add jsmind
<template>
<div class="jsmind_layout">
<div class="jsmind_toolbar" v-if="showBar">
<el-upload
class="pad"
:multiple="false"
ref="upload"
action="action"
:before-upload="beforeUpload"
:http-request="upload">
<el-button type="primary" size="medium">导入el-button>
el-upload>
<el-button @click="save_nodearray_file" size="medium">保存el-button>
<el-button @click="screen_shot" size="medium">下载导图el-button>
<el-button @click="get_nodearray_data" size="medium">获取数据el-button>
<el-button @click="addNode" size="medium">新增节点el-button>
<el-button @click="addBrotherNode" size="medium">新增兄弟节点el-button>
<el-button @click="editNode" size="medium">编辑节点el-button>
<el-button @click="removeNode" size="medium">删除节点el-button>
<el-button @click="zoomIn" size="medium" :disabled="isZoomIn">放大el-button>
<el-button @click="zoomOut" size="medium" :disabled="isZoomOut" class="pad">缩小el-button>
<span>展开:span>
<el-select v-model="level" placeholder="展开节点" @change="expand_to_level" class="pad pad-left" size="medium">
<el-option
v-for="item in nodeOptions"
:key="item.value"
:label="item.label"
:value="item.value">
el-option>
el-select>
<span>主题:span>
<el-select v-model="localTheme" placeholder="选择主题" @change="set_theme" size="medium">
<el-option
v-for="item in themeOptions"
:key="item.value"
:label="item.label"
:value="item.value">
el-option>
el-select>
div>
<div id="jsmind_container" ref="container">
div>
<el-drawer
title="编辑节点"
:visible.sync="dialogVisible"
size="500px">
<el-form label-width="80px" class="form-con">
<el-form-item label="字体大小">
<el-input-number controls-position="right" v-model.number="nodeOption.fontSize" class="ele-width" :min="1" :max="30" maxLength="2">el-input-number>
el-form-item>
<el-form-item label="字体粗细">
<el-select v-model="nodeOption.fontWeight" class="ele-width">
<el-option value="normal" label="常规">el-option>
<el-option value="bold" label="粗体">el-option>
<el-option value="bolder" label="更粗">el-option>
el-select>
el-form-item>
<el-form-item label="字体样式">
<el-select v-model="nodeOption.fontStyle" class="ele-width">
<el-option value="normal" label="标准">el-option>
<el-option value="italic" label="斜体">el-option>
<el-option value="oblique" label="倾斜">el-option>
el-select>
el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="背景颜色">
<el-color-picker v-model="nodeOption.bgColor" show-alpha size="mini">el-color-picker>
el-form-item>
el-col>
<el-col :span="12">
<el-form-item label="字体颜色">
<el-color-picker v-model="nodeOption.fontColor" show-alpha size="mini">el-color-picker>
el-form-item>
el-col>
el-row>
<el-form-item label="节点内容">
<el-input type="textarea" :rows="2" v-model="nodeOption.content" class="ele-width" maxLength="64">el-input>
el-form-item>
el-form>
<template v-slot:footer>
<div class="right mr-10">
<el-button type="primary" class="common-btn" @click="sureEditNode" size="medium">确 定el-button>
div>
template>
el-drawer>
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')
export default {
props: {
showBar: { // 是否显示工具栏,显示启用编辑
type: Boolean,
default: true
},
theme: { // 主题
type: String,
default: 'info'
},
lineColor: { // 线条颜色
type: String,
default: 'skyblue'
}
},
data() {
return {
mind: {},
jm: null,
isZoomIn: false,
isZoomOut: false,
level: 0,
nodeOptions: [
{ value: 1, label: '展开到一级节点' },
{ value: 2, label: '展开到二级节点' },
{ value: 3, label: '展开到三级节点' },
{ value: 0, label: '展开全部节点' },
{ value: -1, label: '隐藏全部节点' }
],
themeOptions: [
{ value: 'default', label: 'default' },
{ value: 'primary', label: 'primary' },
{ value: 'warning', label: 'warning' },
{ value: 'danger', label: 'danger' },
{ value: 'success', label: 'success' },
{ value: 'info', label: 'info' },
{ value: 'greensea', label: 'greensea' },
{ value: 'nephrite', label: 'nephrite' },
{ value: 'belizehole', label: 'belizehole' },
{ value: 'wisteria', label: 'wisteria' },
{ value: 'asphalt', label: 'asphalt' },
{ value: 'orange', label: 'orange' },
{ value: 'pumpkin', label: 'pumpkin' },
{ value: 'pomegranate', label: 'pomegranate' },
{ value: 'clouds', label: 'clouds' },
{ value: 'asbestos', label: 'asbestos' }
],
localTheme: this.theme,
dialogVisible: false,
nodeOption: {
content: '',
bgColor: '',
fontColor: '',
fontSize: '',
fontWeight: '',
fontStyle: ''
}
}
},
created() {
},
mounted() {
this.getData()
this.mouseWheel()
},
methods: {
beforeUpload (file) { // 上传文件之前钩子
if (file) {
jsMind.util.file.read(file, (jsmindData) => {
const mind = jsMind.util.json.string2json(jsmindData)
if (mind) {
this.jm.show(mind)
this.$message({ type: 'success', message: '打开成功' })
} else {
this.prompt_info('不能打开mindmap文件')
}
})
} else {
this.prompt_info('请先选择文件')
return false
}
},
upload() {},
getData() {
this.$API({
name: 'getMind'
}).then(res => {
this.mind = res.data
this.open_empty()
}).catch(error => {
this.$message.error(error)
})
},
open_empty() {
const options = {
container: 'jsmind_container', // 必选,容器ID
editable: this.showBar, // 可选,是否启用编辑
theme: this.localTheme, // 可选,主题
view: {
line_width: 2, // 思维导图线条的粗细
line_color: this.lineColor // 思维导图线条的颜色
},
shortcut: {
enable: true // 禁用快捷键
},
layout: {
hspace: 50, // 节点之间的水平间距
vspace: 20, // 节点之间的垂直间距
pspace: 13 // 节点与连接线之间的水平间距(用于容纳节点收缩/展开控制器)
},
mode: 'side' // 显示模式,子节点只分布在根节点右侧
}
this.jm = jsMind.show(options, this.mind)
// 改变窗口大小重置画布
window.onresize = () => {
this.jm.resize()
}
},
save_nodearray_file() {
const mindData = this.jm.get_data('node_array')
const mindName = mindData.meta.name
const mindStr = jsMind.util.json.json2string(mindData)
jsMind.util.file.save(mindStr, 'text/jsmind', mindName + '.jm')
},
screen_shot() {
this.jm.screenshot.shootDownload()
},
expand_all() {
this.jm.expand_all()
},
collapse_all() {
this.jm.collapse_all()
},
expand_to_level(num) {
switch (num) {
case -1:
this.collapse_all()
break
case 0:
this.expand_all()
break
default:
this.jm.expand_to_depth(num)
break
}
},
zoomIn() {
if (this.jm.view.zoomIn()) {
this.isZoomOut = false
} else {
this.isZoomIn = true
}
},
zoomOut() {
if (this.jm.view.zoomOut()) {
this.isZoomIn = false
} else {
this.isZoomOut = true
}
},
prompt_info(msg) {
this.$message({ type: 'warning', message: msg})
},
get_nodearray_data() {
const mindData = this.jm.get_data('node_array')
const mindString = jsMind.util.json.json2string(mindData)
this.$message({ type: 'info', message: mindString})
},
set_theme(themeName) {
this.jm.set_theme(themeName)
},
scrollFunc(e) {
e = e || window.event
if (e.wheelDelta) {
if (e.wheelDelta > 0) {
this.zoomIn()
} else {
this.zoomOut()
}
} else if (e.detail) {
if (e.detail > 0) {
this.zoomIn()
} else {
this.zoomOut()
}
}
this.jm.resize()
},
// 鼠标滚轮放大缩小
mouseWheel() {
if (document.addEventListener) {
document.addEventListener('domMouseScroll', this.scrollFunc, false)
}
this.$refs.container.onmousewheel = this.scrollFunc
},
// 新增节点
addNode() {
let selectedNode = this.jm.get_selected_node()
if (!selectedNode) {
this.$message({ type: 'warning', message: '请先选择一个节点!'})
return
}
let nodeid = jsMind.util.uuid.newid()
let topic = 'new Node'
let newNode = this.jm.add_node(selectedNode, nodeid, topic)
if (newNode) {
this.jm.select_node(nodeid)
this.jm.begin_edit(nodeid)
}
},
// 新增兄弟节点
addBrotherNode() {
let selectedNode = this.jm.get_selected_node()
if (!selectedNode) {
this.$message({ type: 'warning', message: '请先选择一个节点!'})
return
} else if (selectedNode.isroot) {
this.$message({ type: 'warning', message: '不能在根节点添加,请重新选择节点!'})
return
}
let nodeid = jsMind.util.uuid.newid()
let topic = 'new Node'
let newNode = this.jm.insert_node_after(selectedNode, nodeid, topic)
if (newNode) {
this.jm.select_node(nodeid)
this.jm.begin_edit(nodeid)
}
},
// 获取选中标签的 ID
get_selected_nodeid () {
let selectedNode = this.jm.get_selected_node()
if (selectedNode) {
return selectedNode.id
} else {
return null
}
},
// 删除节点
removeNode() {
let selectedId = this.get_selected_nodeid()
if (!selectedId) {
this.$message({
type: 'warning',
message: '请先选择一个节点!'
})
return
}
this.jm.remove_node(selectedId)
},
// 编辑节点
editNode () {
let selectedId = this.get_selected_nodeid()
if (!selectedId) {
this.$message({ type: 'warning', message: '请先选择一个节点!'})
return
}
let nodeObj = this.jm.get_node(selectedId)
this.nodeOption.content = nodeObj.topic
this.nodeOption.bgColor = nodeObj.data['background-color']
this.nodeOption.fontColor = nodeObj.data['foreground-color']
this.nodeOption.fontSize = nodeObj.data['font-size']
this.nodeOption.fontWeight = nodeObj.data['font-weight']
this.nodeOption.fontStyle = nodeObj.data['font-style']
this.dialogVisible = true
},
sureEditNode () {
let selectedId = this.get_selected_nodeid()
this.jm.update_node(selectedId, this.nodeOption.content)
this.jm.set_node_font_style(selectedId, this.nodeOption.fontSize, this.nodeOption.fontWeight, this.nodeOption.fontStyle)
this.jm.set_node_color(selectedId, this.nodeOption.bgColor, this.nodeOption.fontColor)
this.nodeOption = {
content: '',
bgColor: '',
fontColor: '',
fontSize: '',
fontWeight: '',
fontStyle: ''
}
this.dialogVisible = false
}
},
beforeDestroy() {
document.removeEventListener('domMouseScroll', this.scrollFunc, false)
}
}
</script>
<template>
<div class="jsmind_layout">
<div class="jsmind_toolbar" v-if="showBar">
<el-upload
class="pad"
:multiple="false"
ref="upload"
action="action"
:before-upload="beforeUpload"
:http-request="upload">
<el-button type="primary" size="medium">导入el-button>
el-upload>
<el-button @click="save_nodearray_file" size="medium">保存el-button>
<el-button @click="screen_shot" size="medium">下载导图el-button>
<el-button @click="get_nodearray_data" size="medium">获取数据el-button>
<span class="pad-left">展开:span>
<el-select v-model="level" placeholder="展开节点" @change="expand_to_level" class="pad pad-left">
<el-option
v-for="item in nodeOptions"
:key="item.value"
:label="item.label"
:value="item.value">
el-option>
el-select>
<span>主题:span>
<el-select v-model="theme" placeholder="选择主题" @change="set_theme">
<el-option
v-for="item in themeOptions"
:key="item.value"
:label="item.label"
:value="item.value">
el-option>
el-select>
div>
<div id="jsmind_container" ref="container">
<div class="zoom_in_out">
<span><i class="zoom-icon el-icon-plus" @click="zoomIn" :class="isZoomIn === true ? 'disabled' : ''">i>span>
<span><i class="zoom-icon el-icon-minus" @click="zoomOut" :class="isZoomOut === true ? 'disabled' : ''">i>span>
div>
div>
div>
template>
<script>
import 'jsmind/style/jsmind.css'
import jsMind from 'jsmind/js/jsmind.js'
window.jsMind = jsMind
const { init, reBuild } = require('@/assets/js/jsmind.menu.js')
require('jsmind/js/jsmind.draggable.js')
require('jsmind/js/jsmind.screenshot.js')
init(jsMind)
export default {
props: {
showBar: { // 是否显示工具栏
type: Boolean,
default: true
},
isEdit: { // 是否启用编辑,启用编辑可以显示右键功能
type: Boolean,
default: true
}
},
data() {
return {
mind: {},
jm: null,
isZoomIn: false,
isZoomOut: false,
level: 0,
nodeOptions: [
{ value: 1, label: '展开到一级节点' },
{ value: 2, label: '展开到二级节点' },
{ value: 3, label: '展开到三级节点' },
{ value: 4, label: '展开到四级节点' },
{ value: 0, label: '展开全部节点' },
{ value: -1, label: '隐藏全部节点' }
],
themeOptions: [
{ value: 'default', label: 'default' },
{ value: 'primary', label: 'primary' },
{ value: 'warning', label: 'warning' },
{ value: 'danger', label: 'danger' },
{ value: 'success', label: 'success' },
{ value: 'info', label: 'info' },
{ value: 'greensea', label: 'greensea' },
{ value: 'nephrite', label: 'nephrite' },
{ value: 'belizehole', label: 'belizehole' },
{ value: 'wisteria', label: 'wisteria' },
{ value: 'asphalt', label: 'asphalt' },
{ value: 'orange', label: 'orange' },
{ value: 'pumpkin', label: 'pumpkin' },
{ value: 'pomegranate', label: 'pomegranate' },
{ value: 'clouds', label: 'clouds' },
{ value: 'asbestos', label: 'asbestos' }
],
theme: 'success'
}
},
created() {
},
mounted() {
this.getData()
this.mouseWheel()
},
methods: {
beforeUpload (file) { // 上传文件之前钩子
if (file) {
jsMind.util.file.read(file,(jsmind_data) => {
const mind = jsMind.util.json.string2json(jsmind_data)
if(mind){
this.jm.show(mind)
reBuild()
}else{
this.prompt_info('can not open this file as mindmap')
}
})
} else {
this.prompt_info('please choose a file first')
return false
}
},
upload() {},
getData() {
this.$API({
name: 'getMind'
}).then(res => {
this.mind = res.data
this.open_empty()
}).catch(error => {
this.$message.error(error)
})
},
open_empty() {
const options = {
container: 'jsmind_container', // 必选,容器ID
editable: this.isEdit, // 可选,是否启用编辑
theme: this.theme, // 可选,主题
view: {
line_width: 2, // 思维导图线条的粗细
line_color: 'skyblue' // 思维导图线条的颜色
},
shortcut: {
enable: false // 禁用快捷键
},
mode: 'side', // 显示模式,子节点只分布在根节点右侧
menuOpts:{ // 这里加入一个专门配置menu的对象
showMenu: this.isEdit, //showMenu 为 true 则打开右键功能 ,反之关闭
injectionList: [
{ target: 'edit', text: '编辑节点' },
{ target: 'delete', text: '删除节点' },
{ target: 'addChild', text: '添加子节点' },
{ target: 'addBrother', text: '添加兄弟节点' }
],
style: {
menuItem:{
'line-height': '28px'
}
}
}
}
this.jm = jsMind.show(options, this.mind)
// 改变窗口大小重置画布
window.onresize = () => {
this.jm.resize()
}
},
save_nodearray_file(){
const mind_data = this.jm.get_data('node_array')
const mind_name = mind_data.meta.name
const mind_str = jsMind.util.json.json2string(mind_data)
jsMind.util.file.save(mind_str, 'text/jsmind', mind_name + '.jm')
},
screen_shot(){
this.jm.screenshot.shootDownload()
},
expand_all(){
this.jm.expand_all()
},
collapse_all(){
this.jm.collapse_all()
},
expand_to_level(num){
switch(num) {
case -1:
this.collapse_all()
break
case 0:
this.expand_all()
break
default:
this.jm.expand_to_depth(num)
break
}
},
zoomIn() {
if (this.jm.view.zoomIn()) {
this.isZoomOut = false
} else {
this.isZoomIn = true
}
},
zoomOut() {
if (this.jm.view.zoomOut()) {
this.isZoomIn = false
} else {
this.isZoomOut = true
}
},
prompt_info(msg){
alert(msg)
},
get_nodearray_data() {
const mind_data = this.jm.get_data('node_array')
const mind_string = jsMind.util.json.json2string(mind_data)
this.prompt_info(mind_string)
},
set_theme(theme_name){
this.jm.set_theme(theme_name)
},
scrollFunc(e) {
e = e || window.event
if (e.wheelDelta) {
if (e.wheelDelta > 0) {
this.zoomIn()
} else {
this.zoomOut()
}
} else if (e.detail) {
if (e.detail > 0) {
this.zoomIn()
} else {
this.zoomOut()
}
}
this.jm.resize()
},
// 鼠标滚轮放大缩小
mouseWheel() {
if (document.addEventListener) {
document.addEventListener('domMouseScroll', this.scrollFunc, false)
}
this.$refs.container.onmousewheel = this.scrollFunc
}
},
beforeDestroy() {
document.removeEventListener('domMouseScroll', this.scrollFunc, false)
}
}
</script>