支持小地图
This commit is contained in:
parent
9ae40cff32
commit
7a0fd5adfb
@ -10,6 +10,7 @@ import BatchExecution from './src/BatchExecution'
|
|||||||
import Export from './src/Export'
|
import Export from './src/Export'
|
||||||
import Select from './src/Select'
|
import Select from './src/Select'
|
||||||
import Drag from './src/Drag'
|
import Drag from './src/Drag'
|
||||||
|
import MiniMap from './src/MiniMap';
|
||||||
import {
|
import {
|
||||||
layoutValueList
|
layoutValueList
|
||||||
} from './src/utils/constant'
|
} from './src/utils/constant'
|
||||||
@ -116,6 +117,11 @@ class MindMap {
|
|||||||
draw: this.draw
|
draw: this.draw
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 小地图类
|
||||||
|
this.miniMap = new MiniMap({
|
||||||
|
mindMap: this
|
||||||
|
})
|
||||||
|
|
||||||
// 导出类
|
// 导出类
|
||||||
this.doExport = new Export({
|
this.doExport = new Export({
|
||||||
mindMap: this
|
mindMap: this
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "simple-mind-map",
|
"name": "simple-mind-map",
|
||||||
"version": "0.2.10",
|
"version": "0.2.11",
|
||||||
"description": "一个简单的web在线思维导图",
|
"description": "一个简单的web在线思维导图",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -44,28 +44,9 @@ class Export {
|
|||||||
* @Desc: 获取svg数据
|
* @Desc: 获取svg数据
|
||||||
*/
|
*/
|
||||||
async getSvgData() {
|
async getSvgData() {
|
||||||
const svg = this.mindMap.svg
|
let { svg, svgHTML } = this.mindMap.miniMap.getMiniMap()
|
||||||
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)
|
|
||||||
// 把图片的url转换成data:url类型,否则导出会丢失图片
|
// 把图片的url转换成data:url类型,否则导出会丢失图片
|
||||||
let imageList = clone.find('image')
|
let imageList = svg.find('image')
|
||||||
let task = imageList.map(async (item) => {
|
let task = imageList.map(async (item) => {
|
||||||
let imgUlr = item.attr('href') || item.attr('xlink:href')
|
let imgUlr = item.attr('href') || item.attr('xlink:href')
|
||||||
let imgData = await imgToDataUrl(imgUlr)
|
let imgData = await imgToDataUrl(imgUlr)
|
||||||
@ -73,8 +54,8 @@ class Export {
|
|||||||
})
|
})
|
||||||
await Promise.all(task)
|
await Promise.all(task)
|
||||||
return {
|
return {
|
||||||
node: clone,
|
node: svg,
|
||||||
str: clone.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()
|
this.transform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* javascript comment
|
||||||
|
* @Author: 王林25
|
||||||
|
* @Date: 2022-10-10 14:03:53
|
||||||
|
* @Desc: 平移x方式到
|
||||||
|
*/
|
||||||
|
translateXTo(x) {
|
||||||
|
this.x = x
|
||||||
|
this.transform()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* javascript comment
|
* javascript comment
|
||||||
* @Author: 王林25
|
* @Author: 王林25
|
||||||
@ -137,6 +148,17 @@ class View {
|
|||||||
this.transform()
|
this.transform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* javascript comment
|
||||||
|
* @Author: 王林25
|
||||||
|
* @Date: 2022-10-10 14:04:10
|
||||||
|
* @Desc: 平移y方向到
|
||||||
|
*/
|
||||||
|
translateYTo(y) {
|
||||||
|
this.y = y
|
||||||
|
this.transform()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: 王林
|
* @Author: 王林
|
||||||
* @Date: 2021-07-04 17:13:14
|
* @Date: 2021-07-04 17:13:14
|
||||||
|
|||||||
@ -224,7 +224,7 @@ class MindMap extends Base {
|
|||||||
let y1 = top + height / 2
|
let y1 = top + height / 2
|
||||||
let x2 = item.dir === 'left' ? item.left + item.width : item.left
|
let x2 = item.dir === 'left' ? item.left + item.width : item.left
|
||||||
let y2 = item.top + item.height / 2
|
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)
|
lines[index].plot(path)
|
||||||
style && style(lines[index], item)
|
style && style(lines[index], item)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
<div class="editContainer">
|
<div class="editContainer">
|
||||||
<div class="mindMapContainer" ref="mindMapContainer"></div>
|
<div class="mindMapContainer" ref="mindMapContainer"></div>
|
||||||
<Count></Count>
|
<Count></Count>
|
||||||
|
<Navigator :mindMap="mindMap"></Navigator>
|
||||||
<NavigatorToolbar :mindMap="mindMap"></NavigatorToolbar>
|
<NavigatorToolbar :mindMap="mindMap"></NavigatorToolbar>
|
||||||
<Outline></Outline>
|
<Outline></Outline>
|
||||||
<Style></Style>
|
<Style></Style>
|
||||||
@ -27,6 +28,7 @@ import ShortcutKey from './ShortcutKey'
|
|||||||
import Contextmenu from './Contextmenu'
|
import Contextmenu from './Contextmenu'
|
||||||
import NodeNoteContentShow from './NodeNoteContentShow.vue'
|
import NodeNoteContentShow from './NodeNoteContentShow.vue'
|
||||||
import { getData, storeData, storeConfig } from '@/api'
|
import { getData, storeData, storeConfig } from '@/api'
|
||||||
|
import Navigator from './Navigator.vue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: 王林
|
* @Author: 王林
|
||||||
@ -45,7 +47,8 @@ export default {
|
|||||||
NavigatorToolbar,
|
NavigatorToolbar,
|
||||||
ShortcutKey,
|
ShortcutKey,
|
||||||
Contextmenu,
|
Contextmenu,
|
||||||
NodeNoteContentShow
|
NodeNoteContentShow,
|
||||||
|
Navigator
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
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>
|
<template>
|
||||||
<div class="navigatorContainer">
|
<div class="navigatorContainer">
|
||||||
|
<div class="item">
|
||||||
|
<el-checkbox v-model="openMiniMap" @change="toggleMiniMap">开启小地图</el-checkbox>
|
||||||
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="isReadonly"
|
v-model="isReadonly"
|
||||||
@ -40,12 +43,17 @@ export default {
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isReadonly: false
|
isReadonly: false,
|
||||||
|
openMiniMap: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
readonlyChange(value) {
|
readonlyChange(value) {
|
||||||
this.mindMap.setMode(value ? 'readonly' : 'edit')
|
this.mindMap.setMode(value ? 'readonly' : 'edit')
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleMiniMap(show) {
|
||||||
|
this.$bus.$emit('toggle_mini_map', show)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user