|
@@ -0,0 +1,252 @@
|
|
|
+<template>
|
|
|
+ <div class="mind-map-container">
|
|
|
+ <div class="toolbar">
|
|
|
+ <button @click="addParentNode">添加父节点</button>
|
|
|
+ <button @click="addNode">添加节点</button>
|
|
|
+ <button @click="addChildNode">添加子节点</button>
|
|
|
+ <button @click="removeNode">删除节点</button>
|
|
|
+ <button @click="forward">前进</button>
|
|
|
+ <button @click="back">回退</button>
|
|
|
+ <button @click="zoomIn">放大</button>
|
|
|
+ <button @click="zoomOut">缩小</button>
|
|
|
+ <button @click="resetZoom">重置缩放</button>
|
|
|
+ <button @click="exportToPNG">导出PNG</button>
|
|
|
+ <!-- <button @click="exportToSvg">导出SVG</button> -->
|
|
|
+ <!-- <button @click="exportToJson">导出JSON</button> -->
|
|
|
+ <!-- <button @click="importFromJson">导入JSON</button> -->
|
|
|
+ <!-- <button @click="saveData">保存数据</button> -->
|
|
|
+ </div>
|
|
|
+ <div ref="mindMapContainer" class="mind-map"></div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import MindMap from 'simple-mind-map';
|
|
|
+import Export from 'simple-mind-map/src/plugins/Export.js';
|
|
|
+MindMap.usePlugin(Export);
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'MindMap',
|
|
|
+ props: {
|
|
|
+ projectId: {
|
|
|
+ type: String,
|
|
|
+ required: true,
|
|
|
+ },
|
|
|
+ mindMapJsonData: {
|
|
|
+ type: Object,
|
|
|
+ required: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ mindMap: null,
|
|
|
+ activeNodes: [],
|
|
|
+ activeNodeIsRoot: false,
|
|
|
+ isStart: true,
|
|
|
+ isEnd: true,
|
|
|
+ scale: 1,
|
|
|
+ defaultData: {
|
|
|
+ data: {
|
|
|
+ uid: '001',
|
|
|
+ text: '中心主题',
|
|
|
+ },
|
|
|
+ children: [],
|
|
|
+ },
|
|
|
+ };
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.initMindMap();
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ this.destroyMindMap();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ initMindMap() {
|
|
|
+ let rootData = this.mindMapJsonData;
|
|
|
+ rootData = { data: rootData?.root || this.defaultData };
|
|
|
+ this.mindMap = new MindMap({
|
|
|
+ el: this.$refs.mindMapContainer,
|
|
|
+ mousewheelAction: 'zoom(放大缩小)、move(上下移动)', // zoom(放大缩小)、move(上下移动)
|
|
|
+ // 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
|
|
|
+ mousewheelMoveStep: 100,
|
|
|
+ // 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点
|
|
|
+ mouseScaleCenterUseMousePosition: true,
|
|
|
+ // 当mousewheelAction设为zoom时,或者按住Ctrl键时,默认向前滚动是缩小,向后滚动是放大,如果该属性设为true,那么会反过来
|
|
|
+ mousewheelZoomActionReverse: true,
|
|
|
+ // 禁止鼠标滚轮缩放,你仍旧可以使用api进行缩放
|
|
|
+ disableMouseWheelZoom: false,
|
|
|
+ data: rootData.data,
|
|
|
+ // theme: { template: 'default' },
|
|
|
+ // theme: {
|
|
|
+ // name: 'classic',
|
|
|
+ // palette: ['#5B8FF9', '#5AD8A6', '#5D7092', '#F6BD16', '#E86452'],
|
|
|
+ // bgColor: '#fff',
|
|
|
+ // color: '#333',
|
|
|
+ // root: {
|
|
|
+ // fillColor: '#5B8FF9',
|
|
|
+ // },
|
|
|
+ // second: {
|
|
|
+ // fillColor: '#5AD8A6',
|
|
|
+ // color: '#fff',
|
|
|
+ // borderColor: '#5AD8A6',
|
|
|
+ // borderWidth: 1,
|
|
|
+ // fontSize: 14,
|
|
|
+ // },
|
|
|
+ // },
|
|
|
+ });
|
|
|
+ // this.mindMap.renderer.activeNodeList
|
|
|
+ // 激活节点
|
|
|
+ this.mindMap.on('node_active', (node, activeNodeList) => {
|
|
|
+ this.activeNodes = activeNodeList;
|
|
|
+ this.activeNodeIsRoot = activeNodeList.some((p) => p.isRoot);
|
|
|
+ });
|
|
|
+ this.mindMap.on('back_forward', (index, len) => {
|
|
|
+ this.isStart = index <= 0;
|
|
|
+ this.isEnd = index >= len - 1;
|
|
|
+ });
|
|
|
+ this.mindMap.view.translateX(-400);
|
|
|
+ this.mindMap.view.translateY(-50);
|
|
|
+ },
|
|
|
+ destroyMindMap() {
|
|
|
+ if (this.mindMap) {
|
|
|
+ this.mindMap.destroy();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ addParentNode() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ if (this.activeNodeIsRoot) {
|
|
|
+ this.$message.error('根节点不能添加父节点!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.mindMap.execCommand('INSERT_PARENT_NODE');
|
|
|
+ },
|
|
|
+ addNode() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ if (this.activeNodeIsRoot) {
|
|
|
+ this.$message.error('只能有一个根节点!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.mindMap.execCommand(
|
|
|
+ 'INSERT_NODE',
|
|
|
+ true,
|
|
|
+ [],
|
|
|
+ {
|
|
|
+ uid: Math.random() * 100000,
|
|
|
+ text: '请添加内容',
|
|
|
+ },
|
|
|
+ [
|
|
|
+ {
|
|
|
+ data: {
|
|
|
+ text: '下级节点',
|
|
|
+ },
|
|
|
+ children: [],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ },
|
|
|
+ addChildNode() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ this.mindMap.execCommand('INSERT_CHILD_NODE');
|
|
|
+ },
|
|
|
+ removeNode() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ this.mindMap.execCommand('REMOVE_NODE');
|
|
|
+ },
|
|
|
+ forward() {
|
|
|
+ this.mindMap.execCommand('FORWARD');
|
|
|
+ },
|
|
|
+ back() {
|
|
|
+ this.mindMap.execCommand('BACK');
|
|
|
+ },
|
|
|
+ zoomIn() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ this.mindMap.view.setScale((this.scale += 0.1));
|
|
|
+ },
|
|
|
+ zoomOut() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ this.mindMap.view.setScale((this.scale -= 0.1));
|
|
|
+ },
|
|
|
+ resetZoom() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ this.mindMap.view.reset();
|
|
|
+ },
|
|
|
+ exportToPNG() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ this.mindMap.export('png', true, 'mind-map.png');
|
|
|
+ },
|
|
|
+ exportToSvg() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ this.mindMap.export('svg', true, 'mind-map.svg');
|
|
|
+ },
|
|
|
+ exportToPDF() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ this.mindMap.export('pdf', true, 'mind-map.pdf');
|
|
|
+ },
|
|
|
+ exportToJson() {
|
|
|
+ if (!this.mindMap) return;
|
|
|
+ this.mindMap.export('json', true, 'mind-map.json');
|
|
|
+ },
|
|
|
+ importFromJson() {
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.type = 'file';
|
|
|
+ input.accept = '.json';
|
|
|
+ input.onchange = (e) => {
|
|
|
+ const file = e.target.files[0];
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = (event) => {
|
|
|
+ try {
|
|
|
+ const data = JSON.parse(event.target.result);
|
|
|
+ this.destroyMindMap();
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.initMindMap();
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.mindMap.setData(data);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ alert(`导入失败:${error.message}`);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ reader.readAsText(file);
|
|
|
+ };
|
|
|
+ input.click();
|
|
|
+ },
|
|
|
+ saveData() {
|
|
|
+ // 获取完整数据
|
|
|
+ const data = this.mindMap.getData();
|
|
|
+ console.info('data', data);
|
|
|
+ return data;
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+<style lang="scss" scoped>
|
|
|
+.mind-map-container {
|
|
|
+ .toolbar {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ // justify-content: center;
|
|
|
+ margin-bottom: 6px;
|
|
|
+
|
|
|
+ button {
|
|
|
+ padding: 6px 12px;
|
|
|
+ color: #fff;
|
|
|
+ cursor: pointer;
|
|
|
+ background-color: #2d9aff;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ button:hover {
|
|
|
+ background-color: #165dff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .mind-map {
|
|
|
+ height: calc(100vh - 204px);
|
|
|
+ background-color: #fff !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|