mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-17 22:08:25 +08:00
支持小地图
This commit is contained in:
@@ -10,6 +10,7 @@ import BatchExecution from './src/BatchExecution'
|
||||
import Export from './src/Export'
|
||||
import Select from './src/Select'
|
||||
import Drag from './src/Drag'
|
||||
import MiniMap from './src/MiniMap';
|
||||
import {
|
||||
layoutValueList
|
||||
} from './src/utils/constant'
|
||||
@@ -116,6 +117,11 @@ class MindMap {
|
||||
draw: this.draw
|
||||
})
|
||||
|
||||
// 小地图类
|
||||
this.miniMap = new MiniMap({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 导出类
|
||||
this.doExport = new Export({
|
||||
mindMap: this
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.2.10",
|
||||
"version": "0.2.11",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -44,28 +44,9 @@ class Export {
|
||||
* @Desc: 获取svg数据
|
||||
*/
|
||||
async getSvgData() {
|
||||
const svg = this.mindMap.svg
|
||||
const draw = this.mindMap.draw
|
||||
// 保存原始信息
|
||||
const origWidth = svg.width()
|
||||
const origHeight = svg.height()
|
||||
const origTransform = draw.transform()
|
||||
const elRect = this.mindMap.el.getBoundingClientRect()
|
||||
// 去除放大缩小的变换效果
|
||||
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
|
||||
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||
const rect = draw.rbox()
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.width, rect.height)
|
||||
// 把实际内容变换
|
||||
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top)
|
||||
// 克隆一份数据
|
||||
const clone = svg.clone()
|
||||
// 恢复原先的大小和变换信息
|
||||
svg.size(origWidth, origHeight)
|
||||
draw.transform(origTransform)
|
||||
let { svg, svgHTML } = this.mindMap.miniMap.getMiniMap()
|
||||
// 把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
let imageList = clone.find('image')
|
||||
let imageList = svg.find('image')
|
||||
let task = imageList.map(async (item) => {
|
||||
let imgUlr = item.attr('href') || item.attr('xlink:href')
|
||||
let imgData = await imgToDataUrl(imgUlr)
|
||||
@@ -73,8 +54,8 @@ class Export {
|
||||
})
|
||||
await Promise.all(task)
|
||||
return {
|
||||
node: clone,
|
||||
str: clone.svg()
|
||||
node: svg,
|
||||
str: svgHTML
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
175
simple-mind-map/src/MiniMap.js
Normal file
175
simple-mind-map/src/MiniMap.js
Normal file
@@ -0,0 +1,175 @@
|
||||
// 小地图类
|
||||
class MiniMap {
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:00:45
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt) {
|
||||
this.mindMap = opt.mindMap;
|
||||
this.isMousedown = false;
|
||||
this.mousedownPos = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
this.startViewPos = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:00:43
|
||||
* @Desc: 获取小地图相关数据
|
||||
*/
|
||||
getMiniMap() {
|
||||
const svg = this.mindMap.svg;
|
||||
const draw = this.mindMap.draw;
|
||||
// 保存原始信息
|
||||
const origWidth = svg.width();
|
||||
const origHeight = svg.height();
|
||||
const origTransform = draw.transform();
|
||||
const elRect = this.mindMap.el.getBoundingClientRect();
|
||||
// 去除放大缩小的变换效果
|
||||
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY);
|
||||
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||
const rect = draw.rbox();
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.width, rect.height);
|
||||
// 把实际内容变换
|
||||
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top);
|
||||
// 克隆一份数据
|
||||
const clone = svg.clone();
|
||||
// 恢复原先的大小和变换信息
|
||||
svg.size(origWidth, origHeight);
|
||||
draw.transform(origTransform);
|
||||
|
||||
return {
|
||||
svg: clone, // 思维导图图形的整体svg元素,包括:svg(画布容器)、g(实际的思维导图组)
|
||||
svgHTML: clone.svg(), // svg字符串
|
||||
rect: {
|
||||
...rect, // 思维导图图形未缩放时的位置尺寸等信息
|
||||
ratio: rect.width / rect.height, // 思维导图图形的宽高比
|
||||
},
|
||||
origWidth, // 画布宽度
|
||||
origHeight, // 画布高度
|
||||
scaleX: origTransform.scaleX, // 思维导图图形的水平缩放值
|
||||
scaleY: origTransform.scaleY, // 思维导图图形的垂直缩放值
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:05:51
|
||||
* @Desc: 计算小地图的渲染数据
|
||||
* boxWidth:小地图容器的宽度
|
||||
* boxHeight:小地图容器的高度
|
||||
*/
|
||||
calculationMiniMap(boxWidth, boxHeight) {
|
||||
let { svgHTML, rect, origWidth, origHeight, scaleX, scaleY } =
|
||||
this.getMiniMap();
|
||||
// 计算数据
|
||||
let boxRatio = boxWidth / boxHeight;
|
||||
let actWidth = 0;
|
||||
let actHeight = 0;
|
||||
if (boxRatio > rect.ratio) {
|
||||
// 高度以box为准,缩放宽度
|
||||
actHeight = boxHeight;
|
||||
actWidth = rect.ratio * actHeight;
|
||||
} else {
|
||||
// 宽度以box为准,缩放高度
|
||||
actWidth = boxWidth;
|
||||
actHeight = actWidth / rect.ratio;
|
||||
}
|
||||
// svg图形的缩放及位置
|
||||
let miniMapBoxScale = actWidth / rect.width;
|
||||
let miniMapBoxLeft = (boxWidth - actWidth) / 2;
|
||||
let miniMapBoxTop = (boxHeight - actHeight) / 2;
|
||||
// 视口框大小及位置
|
||||
let _rectX = rect.x - (rect.width * scaleX - rect.width) / 2;
|
||||
let _rectX2 = rect.x2 + (rect.width * scaleX - rect.width) / 2;
|
||||
let _rectY = rect.y - (rect.height * scaleY - rect.height) / 2;
|
||||
let _rectY2 = rect.y2 + (rect.height * scaleY - rect.height) / 2;
|
||||
let _rectWidth = rect.width * scaleX;
|
||||
let _rectHeight = rect.height * scaleY;
|
||||
let viewBoxStyle = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
viewBoxStyle.left =
|
||||
Math.max(0, (-_rectX / _rectWidth) * actWidth) + miniMapBoxLeft + "px";
|
||||
viewBoxStyle.right =
|
||||
Math.max(0, ((_rectX2 - origWidth) / _rectWidth) * actWidth) +
|
||||
miniMapBoxLeft +
|
||||
"px";
|
||||
|
||||
viewBoxStyle.top =
|
||||
Math.max(0, (-_rectY / _rectHeight) * actHeight) + miniMapBoxTop + "px";
|
||||
viewBoxStyle.bottom =
|
||||
Math.max(0, ((_rectY2 - origHeight) / _rectHeight) * actHeight) +
|
||||
miniMapBoxTop +
|
||||
"px";
|
||||
return {
|
||||
svgHTML, // 小地图html
|
||||
viewBoxStyle, // 视图框的位置信息
|
||||
miniMapBoxScale, // 视图框的缩放值
|
||||
miniMapBoxLeft, // 视图框的left值
|
||||
miniMapBoxTop, // 视图框的top值
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:22:40
|
||||
* @Desc: 小地图鼠标按下事件
|
||||
*/
|
||||
onMousedown(e) {
|
||||
this.isMousedown = true;
|
||||
this.mousedownPos = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
};
|
||||
// 保存视图当前的偏移量
|
||||
let transformData = this.mindMap.view.getTransformData();
|
||||
this.startViewPos = {
|
||||
x: transformData.state.x,
|
||||
y: transformData.state.y,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:22:55
|
||||
* @Desc: 小地图鼠标移动事件
|
||||
*/
|
||||
onMousemove(e, sensitivityNum = 5) {
|
||||
if (!this.isMousedown) {
|
||||
return;
|
||||
}
|
||||
let ox = e.clientX - this.mousedownPos.x;
|
||||
let oy = e.clientY - this.mousedownPos.y;
|
||||
// 在视图最初偏移量上累加更新量
|
||||
this.mindMap.view.translateXTo(ox * sensitivityNum + this.startViewPos.x);
|
||||
this.mindMap.view.translateYTo(oy * sensitivityNum + this.startViewPos.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:23:01
|
||||
* @Desc: 小地图鼠标松开事件
|
||||
*/
|
||||
onMouseup() {
|
||||
this.isMousedown = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default MiniMap;
|
||||
@@ -126,6 +126,17 @@ class View {
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:03:53
|
||||
* @Desc: 平移x方式到
|
||||
*/
|
||||
translateXTo(x) {
|
||||
this.x = x
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
@@ -137,6 +148,17 @@ class View {
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:04:10
|
||||
* @Desc: 平移y方向到
|
||||
*/
|
||||
translateYTo(y) {
|
||||
this.y = y
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 17:13:14
|
||||
|
||||
@@ -224,7 +224,7 @@ class MindMap extends Base {
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.dir === 'left' ? item.left + item.width : item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
let path = path = `M ${x1},${y1} L ${x1 + _s},${y1} L ${x1 + _s},${y2} L ${x2},${y2}`
|
||||
let path = `M ${x1},${y1} L ${x1 + _s},${y1} L ${x1 + _s},${y2} L ${x2},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<div class="editContainer">
|
||||
<div class="mindMapContainer" ref="mindMapContainer"></div>
|
||||
<Count></Count>
|
||||
<Navigator :mindMap="mindMap"></Navigator>
|
||||
<NavigatorToolbar :mindMap="mindMap"></NavigatorToolbar>
|
||||
<Outline></Outline>
|
||||
<Style></Style>
|
||||
@@ -27,6 +28,7 @@ import ShortcutKey from './ShortcutKey'
|
||||
import Contextmenu from './Contextmenu'
|
||||
import NodeNoteContentShow from './NodeNoteContentShow.vue'
|
||||
import { getData, storeData, storeConfig } from '@/api'
|
||||
import Navigator from './Navigator.vue';
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
@@ -45,7 +47,8 @@ export default {
|
||||
NavigatorToolbar,
|
||||
ShortcutKey,
|
||||
Contextmenu,
|
||||
NodeNoteContentShow
|
||||
NodeNoteContentShow,
|
||||
Navigator
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
141
web/src/pages/Edit/components/Navigator.vue
Normal file
141
web/src/pages/Edit/components/Navigator.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="showMiniMap"
|
||||
class="navigatorBox"
|
||||
ref="navigatorBox"
|
||||
@mousedown="onMousedown"
|
||||
@mousemove="onMousemove"
|
||||
@mouseup="onMouseup"
|
||||
>
|
||||
<div
|
||||
class="svgBox"
|
||||
ref="svgBox"
|
||||
:style="{
|
||||
transform: `scale(${svgBoxScale})`,
|
||||
left: svgBoxLeft + 'px',
|
||||
top: svgBoxTop + 'px',
|
||||
}"
|
||||
></div>
|
||||
<div class="windowBox" :style="viewBoxStyle"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMiniMap: false,
|
||||
timer: null,
|
||||
boxWidth: 0,
|
||||
boxHeight: 0,
|
||||
svgBoxScale: 1,
|
||||
svgBoxLeft: 0,
|
||||
svgBoxTop: 0,
|
||||
viewBoxStyle: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$bus.$on("toggle_mini_map", (show) => {
|
||||
this.showMiniMap = show;
|
||||
this.$nextTick(() => {
|
||||
this.init();
|
||||
this.drawMiniMap();
|
||||
});
|
||||
});
|
||||
this.$bus.$on("data_change", () => {
|
||||
if (!this.showMiniMap) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.drawMiniMap();
|
||||
}, 500);
|
||||
});
|
||||
this.$bus.$on("view_data_change", () => {
|
||||
if (!this.showMiniMap) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.drawMiniMap();
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
let { width, height } = this.$refs.navigatorBox.getBoundingClientRect();
|
||||
this.boxWidth = width;
|
||||
this.boxHeight = height;
|
||||
},
|
||||
|
||||
drawMiniMap() {
|
||||
let {
|
||||
svgHTML,
|
||||
viewBoxStyle,
|
||||
miniMapBoxScale,
|
||||
miniMapBoxLeft,
|
||||
miniMapBoxTop,
|
||||
} = this.mindMap.miniMap.calculationMiniMap(
|
||||
this.boxWidth,
|
||||
this.boxHeight
|
||||
);
|
||||
// 渲染到小地图
|
||||
this.$refs.svgBox.innerHTML = svgHTML;
|
||||
this.viewBoxStyle = viewBoxStyle;
|
||||
this.svgBoxScale = miniMapBoxScale;
|
||||
this.svgBoxLeft = miniMapBoxLeft;
|
||||
this.svgBoxTop = miniMapBoxTop;
|
||||
},
|
||||
|
||||
onMousedown(e) {
|
||||
this.mindMap.miniMap.onMousedown(e);
|
||||
},
|
||||
|
||||
onMousemove(e) {
|
||||
this.mindMap.miniMap.onMousemove(e);
|
||||
},
|
||||
|
||||
onMouseup(e) {
|
||||
this.mindMap.miniMap.onMouseup(e);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.navigatorBox {
|
||||
position: absolute;
|
||||
width: 350px;
|
||||
height: 220px;
|
||||
background-color: #fff;
|
||||
bottom: 80px;
|
||||
right: 20px;
|
||||
box-shadow: 0 0 16px #989898;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #eee;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
.svgBox {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
transform-origin: left top;
|
||||
}
|
||||
|
||||
.windowBox {
|
||||
position: absolute;
|
||||
border: 2px solid rgb(238, 69, 69);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="navigatorContainer">
|
||||
<div class="item">
|
||||
<el-checkbox v-model="openMiniMap" @change="toggleMiniMap">开启小地图</el-checkbox>
|
||||
</div>
|
||||
<div class="item">
|
||||
<el-switch
|
||||
v-model="isReadonly"
|
||||
@@ -40,12 +43,17 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isReadonly: false
|
||||
isReadonly: false,
|
||||
openMiniMap: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
readonlyChange(value) {
|
||||
this.mindMap.setMode(value ? 'readonly' : 'edit')
|
||||
},
|
||||
|
||||
toggleMiniMap(show) {
|
||||
this.$bus.$emit('toggle_mini_map', show)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user