vue3使用simple-mind-map,分分钟开发思维导图

这几天又来了新需求,老板想在系统里一眼可以看到所有部门的相关信息,并且可以编辑,分配任务。所以需要实现一个可编辑的思维导图页面。

思维导图?感觉很复杂的样子,这种很牛p的东西应该不是我三两天就能手写搞定的,于是我github转了一圈,果然不出我所料,对比了几个插件以后我是先选择了 vue3-mindmap 简单易用。既然好用那就直接npm install
到项目里使用就完了,可是不知道是我使用的方式方法不对还是咋的,很多参数都不生效。于是发现了更好的思维到图库 simple-mind-map ,最终也是和它长相厮守。

simple-mind-map的介绍:

1.一个 js 思维导图库,不依赖任何框架,可以使用它来快速完成 Web 思维导图产品的开发。
开发文档:https://wanglin2.github.io/mind-map-docs/

2.一个 Web 思维导图,基于思维导图库、Vue2.x、ElementUI 开发,可以操作电脑本地文件,可以当做一个在线版思维导图应用使用,也可以自部署和二次开发。
在线地址:https://wanglin2.github.io/mind-map/

先看下我vue3实现的效果,写的比较简单,因为没有很复杂的需求,够用即可。

在这里插入图片描述

1.vue3使用步骤

使用npm安装
npm install simple-mind-map
提供一个宽高不为 0 的容器元素

<div class="mindMapContainer" ref="mindMapContainerRef"></div>

// 样式
.mindMapContainer {
  margin: 0;
  padding: 0;
  width: 100%;
  height: calc(100vh - 190px); // 按自己需求修改
}
然后创建一个实例(官方是vue2例子,我使用的是vue3,所以使用方式略微不同,需要手动注册组件),代码如下:
import MindMap from 'simple-mind-map'
import MiniMap from 'simple-mind-map/src/plugins/MiniMap.js'
import Watermark from 'simple-mind-map/src/plugins/Watermark.js'
import KeyboardNavigation from 'simple-mind-map/src/plugins/KeyboardNavigation.js'
import ExportPDF from 'simple-mind-map/src/plugins/ExportPDF.js'
import ExportXMind from 'simple-mind-map/src/plugins/ExportXMind.js'
import Export from 'simple-mind-map/src/plugins/Export.js'
import Drag from 'simple-mind-map/src/plugins/Drag.js'
import Select from 'simple-mind-map/src/plugins/Select.js'
import RichText from 'simple-mind-map/src/plugins/RichText.js'
import AssociativeLine from 'simple-mind-map/src/plugins/AssociativeLine.js'
import TouchEvent from 'simple-mind-map/src/plugins/TouchEvent.js'
import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js'
import SearchPlugin from 'simple-mind-map/src/plugins/Search.js'
import Painter from 'simple-mind-map/src/plugins/Painter.js'
import ScrollbarPlugin from 'simple-mind-map/src/plugins/Scrollbar.js'
import Formula from 'simple-mind-map/src/plugins/Formula.js'
import Cooperate from 'simple-mind-map/src/plugins/Cooperate.js'

// 注册插件
MindMap.usePlugin(MiniMap)
  .usePlugin(Watermark)
  .usePlugin(Drag)
  .usePlugin(KeyboardNavigation)
  .usePlugin(ExportPDF)
  .usePlugin(ExportXMind)
  .usePlugin(Export)
  .usePlugin(Select)
  .usePlugin(AssociativeLine)
  .usePlugin(NodeImgAdjust)
  .usePlugin(TouchEvent)
  .usePlugin(SearchPlugin)
  .usePlugin(Painter)
  .usePlugin(Formula)


const mindMapContainerRef = ref()
let mindMap = null;

const mindData = {
    "data": {
      "text": "Root Node",
    },
    "children": [
      {
        "data": {
          "text": "Child Node 1",
          "image": "",
          "imageTitle": "",
          "hyperlink": "",
          "note": ""
        },
        "children": []
      },
      {
        "data": {
          "text": "Child Node 2",
          "image": "",
          "imageTitle": "",
          "hyperlink": "",
          "note": ""
        },
        "children": []
      }
    ]
};

async function init() {
  mindMap = new MindMap({
      el: mindMapContainerRef.value,
      data: mindData
  });
}

onMounted( async () => {
  init()
})

根据以上使用步骤,得到的效果如下图:

在这里插入图片描述

当前效果:节点文字可编辑,Tab键可新增子节点。怎样让它更丰富一点呢!再往下瞅瞅。

2.功能实现步骤

小小的列一下要实现的功能:
  1. 右键点击节点可弹出操作框。
  2. 新增子节点,增加同级节点,删除节点,复制节点,粘贴节点。
  3. 保存,导出。

更多功能实现,可以根据需求参考下 官方开发文档 进行开发。

2.1 右键点击节点可弹出操作框
2.1.1 弹出的元素
<!-- 右键菜单 -->
<div v-if="showContextMenu" 
  class="context-menu" 
  :style="{ top: `${menuPosition.y}px`, left: `${menuPosition.x}px` }"
  >
  <ul>
    <li>添加子节点</li>
    <li>添加同级节点</li>
    <li>删除节点</li>
    <li>复制节点</li>
    <li>粘贴节点</li>
  </ul>
</div>
2.1.2 属性设置
// 当前右键点击的类型
const type = ref('')
// 如果点击的节点,那么代表被点击的节点
const currentNode = shallowRef(null)
// 是否显示菜单
const showContextMenu = ref(false);
// 菜单显示的位置
const menuPosition = ref({ x: 0, y: 0 });
2.1.2 节点右击事件
mindMap.on('node_contextmenu', (e, node) => {
  if (e.which == 3) {
    menuPosition.value = { x: e.clientX +10, y: e.clientY+10 };
    showContextMenu.value = true;
    currentNode.value = node
  }
})
2.1.3 css样式
.top-menu-fixed{
  position: fixed;
  top: 100px;
  left: 50%;
  width: 180px;
  z-index: 1000;
  display: flex;
  justify-content: space-around;
  background-color: #fff;
  padding: 10px 20px;
  border-radius: 6px;
  box-shadow: 0 2px 16px 0 rgba(0, 0, 0, .06);
  border: 1px solid rgba(0, 0, 0, .06);
  margin-right: 20px;
  .top-menu-item{
    width: 50px;
    text-align: center;
    border: 1px solid rgba(0, 0, 0, .06);
    cursor: pointer;
    padding: 3px 0px;
    border-radius: 5px;
    .top-menu-item--text{
      font-size: 14px;
    }
  }
}

.context-menu {
  position: fixed;
  background-color: white;
  border: 1px solid #ccc;
  box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1);
  z-index: 1000;
  padding: 10px;
  border-radius: 4px;
}

.context-menu ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.context-menu li {
  padding: 8px 12px;
  cursor: pointer;
}

.context-menu li:hover {
  background-color: #f0f0f0;
}
2.1 完整代码
<template>
  <div>
    <div class="mindMapContainer" ref="mindMapContainerRef"></div>

    <!-- 右键菜单 -->
    <div
      v-if="showContextMenu" 
      class="context-menu" 
      :style="{ top: `${menuPosition.y}px`, left: `${menuPosition.x}px` }"
      >
      <ul>
        <li @click="addChildNode">添加子节点</li>
        <li @click="addSameNode">添加同级节点</li>
        <li @click="removeNode">删除节点</li>
        <li @click="copyNode">复制节点</li>
        <li @click="pasteNode">粘贴节点</li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import MindMap from 'simple-mind-map'
import MiniMap from 'simple-mind-map/src/plugins/MiniMap.js'
import Watermark from 'simple-mind-map/src/plugins/Watermark.js'
import KeyboardNavigation from 'simple-mind-map/src/plugins/KeyboardNavigation.js'
import ExportPDF from 'simple-mind-map/src/plugins/ExportPDF.js'
import ExportXMind from 'simple-mind-map/src/plugins/ExportXMind.js'
import Export from 'simple-mind-map/src/plugins/Export.js'
import Drag from 'simple-mind-map/src/plugins/Drag.js'
import Select from 'simple-mind-map/src/plugins/Select.js'
import RichText from 'simple-mind-map/src/plugins/RichText.js'
import AssociativeLine from 'simple-mind-map/src/plugins/AssociativeLine.js'
import TouchEvent from 'simple-mind-map/src/plugins/TouchEvent.js'
import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js'
import SearchPlugin from 'simple-mind-map/src/plugins/Search.js'
import Painter from 'simple-mind-map/src/plugins/Painter.js'
import ScrollbarPlugin from 'simple-mind-map/src/plugins/Scrollbar.js'
import Formula from 'simple-mind-map/src/plugins/Formula.js'
import Cooperate from 'simple-mind-map/src/plugins/Cooperate.js'

// 注册插件
MindMap.usePlugin(MiniMap)
  .usePlugin(Watermark)
  .usePlugin(Drag)
  .usePlugin(KeyboardNavigation)
  .usePlugin(ExportPDF)
  .usePlugin(ExportXMind)
  .usePlugin(Export)
  .usePlugin(Select)
  .usePlugin(AssociativeLine)
  .usePlugin(NodeImgAdjust)
  .usePlugin(TouchEvent)
  .usePlugin(SearchPlugin)
  .usePlugin(Painter)
  .usePlugin(Formula)


const mindMapContainerRef = ref()
let mindMap = null;
// 当前右键点击的类型
const type = ref('')
// 如果点击的节点,那么代表被点击的节点
const currentNode = shallowRef(null)
// 是否显示菜单
const showContextMenu = ref(false);
// 菜单显示的位置
const menuPosition = ref({ x: 0, y: 0 });

const mindData = {
    "data": {
      "text": "Root Node",
    },
    "children": [
      {
        "data": {
          "text": "Child Node 1",
          "image": "",
          "imageTitle": "",
          "hyperlink": "",
          "note": ""
        },
        "children": []
      },
      {
        "data": {
          "text": "Child Node 2",
          "image": "",
          "imageTitle": "",
          "hyperlink": "",
          "note": ""
        },
        "children": []
      }
    ]
};

async function init() {
  mindMap = new MindMap({
      el: mindMapContainerRef.value,
      data: mindData
  });

  // 节点右键事件
  mindMap.on('node_contextmenu', (e, node) => {
    if (e.which == 3) {
      menuPosition.value = { x: e.clientX +10, y: e.clientY+10 };
      showContextMenu.value = true;
      currentNode.value = node
    }
  })

  // 点击空白处
  mindMap.on('node_click', hide)
  mindMap.on('draw_click', hide)
  mindMap.on('expand_btn_click', hide)
}

// 隐藏右侧菜单
const hide = () => {
  menuPosition.value = { x: 0, y: 0 };
  showContextMenu.value = false;
  currentNode.value = null
}

onMounted( async () => {
  init()
})
</script>
<style lang="scss" scoped>
.mindMapContainer {
  margin: 0;
  padding: 0;
  width: 100%;
  height: calc(100vh - 190px);
}
.top-menu-fixed{
  position: fixed;
  top: 100px;
  left: 50%;
  width: 180px;
  z-index: 1000;
  display: flex;
  justify-content: space-around;
  background-color: #fff;
  padding: 10px 20px;
  border-radius: 6px;
  box-shadow: 0 2px 16px 0 rgba(0, 0, 0, .06);
  border: 1px solid rgba(0, 0, 0, .06);
  margin-right: 20px;
  .top-menu-item{
    width: 50px;
    text-align: center;
    border: 1px solid rgba(0, 0, 0, .06);
    cursor: pointer;
    padding: 3px 0px;
    border-radius: 5px;
    .top-menu-item--text{
      font-size: 14px;
    }
  }
}

.context-menu {
  position: fixed;
  background-color: white;
  border: 1px solid #ccc;
  box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1);
  z-index: 1000;
  padding: 10px;
  border-radius: 4px;
}

.context-menu ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.context-menu li {
  padding: 8px 12px;
  cursor: pointer;
}

.context-menu li:hover {
  background-color: #f0f0f0;
}
</style>

2.2、2.3的实现是一样的,代码如下:
// 添加节点
const addChildNode = () => {
  if (mindMap) {
    mindMap.execCommand('INSERT_CHILD_NODE')
  }
  showContextMenu.value = false; // 关闭菜单
};

// 添加同级节点
const addSameNode = () => {
  if (mindMap) {
    mindMap.execCommand('INSERT_NODE')
  }
  showContextMenu.value = false; // 关闭菜单
};
// 删除节点
const removeNode = () => {
  if (mindMap && currentNode.value) {
    mindMap.execCommand('REMOVE_NODE')
  }
  showContextMenu.value = false; // 关闭菜单
};

// 复制节点
const copyNode = () => {
  if (mindMap && currentNode.value) {
    mindMap.renderer.copy()
  }
  showContextMenu.value = false; // 关闭菜单
};

// 粘贴节点
const pasteNode = () => {
  if (mindMap && currentNode.value) {
    mindMap.renderer.paste()
  }
  showContextMenu.value = false; // 关闭菜单
};

// 导出为图片
const exportMindMap = () => {
  if (mindMap) {
    mindMap.export('png', true, '底格里斯任务中心图')
  }
};

3.效果图完整代码实现

<template>
  <!-- vue3-mindmap https://github.com/hellowuxin/vue3-mindmap  -->
  <div>
    <div class="mindMapContainer" ref="mindMapContainerRef"></div>

    <div class="top-menu-fixed">
      <div class="top-menu-item" @click="saveMindMap">
        <div class="top-menu-item--img"><el-icon><Suitcase /></el-icon></div>
        <div class="top-menu-item--text">保存</div>
      </div>
      <div class="top-menu-item" @click="submitMindMap">
        <div class="top-menu-item--img"><el-icon><SuitcaseLine /></el-icon></div>
        <div class="top-menu-item--text">提交</div>
      </div>
      <div class="top-menu-item" @click="exportMindMap">
        <div class="top-menu-item--img"><el-icon><TakeawayBox /></el-icon></div>
        <div class="top-menu-item--text">导出</div>
      </div>
    </div>

    <!-- 右键菜单 -->
    <div v-if="showContextMenu" class="context-menu" :style="{ top: `${menuPosition.y}px`, left: `${menuPosition.x}px` }">
      <ul>
        <li @click="addChildNode">添加子节点</li>
        <li @click="addSameNode">添加同级节点</li>
        <li @click="removeNode">删除节点</li>
        <li @click="copyNode">复制节点</li>
        <li @click="pasteNode">粘贴节点</li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { TakeawayBox, SuitcaseLine, Suitcase } from '@element-plus/icons-vue'
import MindMap from 'simple-mind-map'
import MiniMap from 'simple-mind-map/src/plugins/MiniMap.js'
import Watermark from 'simple-mind-map/src/plugins/Watermark.js'
import KeyboardNavigation from 'simple-mind-map/src/plugins/KeyboardNavigation.js'
import ExportPDF from 'simple-mind-map/src/plugins/ExportPDF.js'
import ExportXMind from 'simple-mind-map/src/plugins/ExportXMind.js'
import Export from 'simple-mind-map/src/plugins/Export.js'
import Drag from 'simple-mind-map/src/plugins/Drag.js'
import Select from 'simple-mind-map/src/plugins/Select.js'
import RichText from 'simple-mind-map/src/plugins/RichText.js'
import AssociativeLine from 'simple-mind-map/src/plugins/AssociativeLine.js'
import TouchEvent from 'simple-mind-map/src/plugins/TouchEvent.js'
import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js'
import SearchPlugin from 'simple-mind-map/src/plugins/Search.js'
import Painter from 'simple-mind-map/src/plugins/Painter.js'
import ScrollbarPlugin from 'simple-mind-map/src/plugins/Scrollbar.js'
import Formula from 'simple-mind-map/src/plugins/Formula.js'
import Cooperate from 'simple-mind-map/src/plugins/Cooperate.js'

// 注册插件
MindMap.usePlugin(MiniMap)
  .usePlugin(Watermark)
  .usePlugin(Drag)
  .usePlugin(KeyboardNavigation)
  .usePlugin(ExportPDF)
  .usePlugin(ExportXMind)
  .usePlugin(Export)
  .usePlugin(Select)
  .usePlugin(AssociativeLine)
  .usePlugin(NodeImgAdjust)
  .usePlugin(TouchEvent)
  .usePlugin(SearchPlugin)
  .usePlugin(Painter)
  .usePlugin(Formula)

import { TaskCentreApi } from '@/api/system/taskcentre'

const mindMapContainerRef = ref()
let mindMap = null;

const mindData = {
    "data": {
      "text": "Root Node",
    },
    "children": [
      {
        "data": {
          "text": "Child Node 1",
          "image": "",
          "imageTitle": "",
          "hyperlink": "",
          "note": ""
        },
        "children": []
      },
      {
        "data": {
          "text": "Child Node 2",
          "image": "",
          "imageTitle": "",
          "hyperlink": "",
          "note": ""
        },
        "children": []
      }
    ]
};

let data = ref([])
// 当前右键点击的类型
const type = ref('')
// 如果点击的节点,那么代表被点击的节点
const currentNode = shallowRef(null)
// 是否显示菜单
const showContextMenu = ref(false);
// 菜单显示的位置
const menuPosition = ref({ x: 0, y: 0 });
let selectedNode = null;

async function init() {
  let res = await TaskCentreApi.getTaskCentreTree();
  console.log(mindData);
  
  console.log(res);
  
  mindMap = new MindMap({
      el: mindMapContainerRef.value,
      data: res[0],
      editable: true, // 开启编辑模式
      mousewheelAction: 'move',// zoom(放大缩小)、move(上下移动)
      // 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
      mousewheelMoveStep: 100,
      // 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点
      mouseScaleCenterUseMousePosition: true,
      // 当mousewheelAction设为zoom时,或者按住Ctrl键时,默认向前滚动是缩小,向后滚动是放大,如果该属性设为true,那么会反过来
      mousewheelZoomActionReverse: true,
      // 禁止鼠标滚轮缩放,你仍旧可以使用api进行缩放
      disableMouseWheelZoom: false,
      layout: 'logicalStructure',
      // 连线的粗细
      lineWidth: 1,
      // 连线的颜色
      lineColor: '#549688',
      // 连线样式
      lineDasharray: 'none',
      // 连线风格,支持三种
      // 1.曲线(curve)。仅logicalStructure、mindMap、verticalTimeline三种结构支持。
      // 2.直线(straight)。
      // 3.直连(direct)。仅logicalStructure、mindMap、organizationStructure、verticalTimeline四种结构支持。
      lineStyle: 'curve', 
      // 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型,设为true后,都为 { 型。仅logicalStructure、mindMap两种结构支持。
      rootLineKeepSameInCurve: true,
      // 直线连接(straight)时,连线的圆角大小,设置为0代表没有圆角,仅支持logicalStructure、mindMap、verticalTimeline三种结构
      lineRadius: 5,
      // 连线尾部是否显示标记,目前只支持箭头
      showLineMarker: false,
      // 概要连线的粗细
      generalizationLineWidth: 1,
  });
  
  // 节点右键事件
  mindMap.on('node_contextmenu', (e, node) => {
    if (e.which == 3) {
      menuPosition.value = { x: e.clientX +10, y: e.clientY+10 };
      showContextMenu.value = true;
      currentNode.value = node
    }
  })

  // 点击空白处
  mindMap.on('node_click', hide)
  mindMap.on('draw_click', hide)
  mindMap.on('expand_btn_click', hide)
  
}

// 隐藏右侧菜单
const hide = () => {
  menuPosition.value = { x: 0, y: 0 };
  showContextMenu.value = false;
  currentNode.value = null
}

// 添加节点
const addChildNode = () => {
  if (mindMap) {
    mindMap.execCommand('INSERT_CHILD_NODE')
  }
  showContextMenu.value = false; // 关闭菜单
};

// 添加同级节点
const addSameNode = () => {
  if (mindMap) {
    mindMap.execCommand('INSERT_NODE')
  }
  showContextMenu.value = false; // 关闭菜单
};
// 删除节点
const removeNode = () => {
  if (mindMap && currentNode.value) {
    mindMap.execCommand('REMOVE_NODE')
  }
  showContextMenu.value = false; // 关闭菜单
};

// 复制节点
const copyNode = () => {
  if (mindMap && currentNode.value) {
    mindMap.renderer.copy()
  }
  showContextMenu.value = false; // 关闭菜单
};

// 粘贴节点
const pasteNode = () => {
  if (mindMap && currentNode.value) {
    mindMap.renderer.paste()
  }
  showContextMenu.value = false; // 关闭菜单
};

// 导出为图片
const exportMindMap = () => {
  if (mindMap) {
    mindMap.export('png', true, '底格里斯任务中心图')
  }
};

const submitMindMap = () => {
  if (mindMap) {
    const data = mindMap.getData(true)
    console.log(data);
    
  }
};

const saveMindMap = () => {
  if (mindMap) {
    mindMap.export('xmind', true, '底格里斯任务中心图')
  }
};

onMounted( async () => {
  init()
})
</script>
<style lang="scss" scoped>
.mindMapContainer {
  margin: 0;
  padding: 0;
  width: 100%;
  height: calc(100vh - 190px);
}
.top-menu-fixed{
  position: fixed;
  top: 100px;
  left: 50%;
  width: 180px;
  z-index: 1000;
  display: flex;
  justify-content: space-around;
  background-color: #fff;
  padding: 10px 20px;
  border-radius: 6px;
  box-shadow: 0 2px 16px 0 rgba(0, 0, 0, .06);
  border: 1px solid rgba(0, 0, 0, .06);
  margin-right: 20px;
  .top-menu-item{
    width: 50px;
    text-align: center;
    border: 1px solid rgba(0, 0, 0, .06);
    cursor: pointer;
    padding: 3px 0px;
    border-radius: 5px;
    .top-menu-item--text{
      font-size: 14px;
    }
  }
}

.context-menu {
  position: fixed;
  background-color: white;
  border: 1px solid #ccc;
  box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1);
  z-index: 1000;
  padding: 10px;
  border-radius: 4px;
}

.context-menu ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.context-menu li {
  padding: 8px 12px;
  cursor: pointer;
}

.context-menu li:hover {
  background-color: #f0f0f0;
}
</style>

总结

更多的功能实现可参考官方文档,文档很详细。该js库很灵活,可根据自己的需求去实现。有疑问的朋友也可以在评论区提问或交流。下一篇文章再见!!!
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值