mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-22 02:47:46 +08:00
完成导出功能
This commit is contained in:
parent
e1969cff16
commit
cde53602a3
@ -1,16 +1,16 @@
|
||||
const createFullData = () => {
|
||||
return {
|
||||
// "image": "http://aliyuncdn.lxqnsys.com/whbm/enJFNMHnedQTYTESGfDkctCp2",
|
||||
// "imageTitle": "图片名称",
|
||||
// "imageSize": {
|
||||
// "width": 1000,
|
||||
// "height": 563
|
||||
// },
|
||||
// "icon": ['priority_1'],
|
||||
// "tag": ["标签1", "标签2"],
|
||||
// "hyperlink": "http://lxqnsys.com/",
|
||||
// "hyperlinkTitle": "理想青年实验室",
|
||||
// "note": "理想青年实验室\n一个有意思的角落"
|
||||
"image": "http://192.168.3.118:8080/enJFNMHnedQTYTESGfDkctCp2.jpeg",
|
||||
"imageTitle": "图片名称",
|
||||
"imageSize": {
|
||||
"width": 1000,
|
||||
"height": 563
|
||||
},
|
||||
"icon": ['priority_1'],
|
||||
"tag": ["标签1", "标签2"],
|
||||
"hyperlink": "http://lxqnsys.com/",
|
||||
"hyperlinkTitle": "理想青年实验室",
|
||||
"note": "理想青年实验室\n一个有意思的角落"
|
||||
};
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ const createFullData = () => {
|
||||
export default {
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "根节点",
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
@ -38,7 +38,6 @@ export default {
|
||||
}, {
|
||||
"data": {
|
||||
"text": "子节点1-2",
|
||||
...createFullData()
|
||||
}
|
||||
},]
|
||||
},
|
||||
@ -56,31 +55,26 @@ export default {
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-1-1",
|
||||
...createFullData()
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-1-2",
|
||||
...createFullData()
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-1-2-1",
|
||||
...createFullData()
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-1-2-2",
|
||||
...createFullData()
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-1-2-2-1",
|
||||
...createFullData()
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -92,7 +86,6 @@ export default {
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-1-2-2-3",
|
||||
...createFullData()
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -108,7 +101,6 @@ export default {
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-1-3",
|
||||
...createFullData()
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -116,7 +108,6 @@ export default {
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-2",
|
||||
...createFullData()
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -129,7 +120,6 @@ export default {
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点3-1",
|
||||
...createFullData()
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -148,19 +138,16 @@ export default {
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点4-1",
|
||||
...createFullData()
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点4-1-1",
|
||||
...createFullData()
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点4-1-2",
|
||||
...createFullData()
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -174,7 +161,6 @@ export default {
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点4-2",
|
||||
...createFullData()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -7,11 +7,12 @@ import Style from './src/Style'
|
||||
import KeyCommand from './src/KeyCommand'
|
||||
import Command from './src/Command'
|
||||
import BatchExecution from './src/BatchExecution'
|
||||
import Export from './src/Export';
|
||||
import {
|
||||
SVG
|
||||
} from '@svgdotjs/svg.js'
|
||||
|
||||
// 默认选项
|
||||
// 默认选项配置
|
||||
const defaultOpt = {
|
||||
// 布局
|
||||
layout: 'logicalStructure',
|
||||
@ -19,8 +20,12 @@ const defaultOpt = {
|
||||
theme: 'default', // 内置主题:default(默认主题)
|
||||
// 主题配置,会和所选择的主题进行合并
|
||||
themeConfig: {},
|
||||
// 放大缩小的增量比例,即step = scaleRatio * width|height
|
||||
scaleRatio: 0.1
|
||||
// 放大缩小的增量比例
|
||||
scaleRatio: 0.1,
|
||||
// 设置鼠标左键还是右键按下拖动,1(左键)、2(右键)
|
||||
dragButton: 1,
|
||||
// 最多显示几个标签
|
||||
maxTag: 5
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,7 +43,7 @@ class MindMap {
|
||||
*/
|
||||
constructor(opt = {}) {
|
||||
// 合并选项
|
||||
this.opt = merge(defaultOpt, opt)
|
||||
this.opt = this.handleOpt(merge(defaultOpt, opt))
|
||||
|
||||
// 容器元素
|
||||
this.el = this.opt.el
|
||||
@ -51,8 +56,9 @@ class MindMap {
|
||||
this.width = width
|
||||
this.height = height
|
||||
|
||||
// 画笔
|
||||
this.draw = SVG().addTo(this.el).size(width, height)
|
||||
// 画布
|
||||
this.svg = SVG().addTo(this.el).size(width, height)
|
||||
this.draw = this.svg.group()
|
||||
|
||||
// 节点id
|
||||
this.uid = 0
|
||||
@ -86,6 +92,11 @@ class MindMap {
|
||||
draw: this.draw
|
||||
})
|
||||
|
||||
// 导出类
|
||||
this.doExport = new Export({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 批量执行类
|
||||
this.batchExecution = new BatchExecution()
|
||||
|
||||
@ -96,6 +107,23 @@ class MindMap {
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:15:22
|
||||
* @Desc: 配置参数处理
|
||||
*/
|
||||
handleOpt(opt) {
|
||||
// 检查布局配置
|
||||
if (!['logicalStructure'].includes(opt.layout)) {
|
||||
opt.layout = 'logicalStructure'
|
||||
}
|
||||
// 检查主题配置
|
||||
opt.theme = opt.theme && theme[opt.theme] ? opt.theme : 'default'
|
||||
// 检查鼠标键值
|
||||
opt.dragButton = [1, 3].includes(opt.dragButton) ? opt.dragButton : 1
|
||||
return opt
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
@ -144,7 +172,7 @@ class MindMap {
|
||||
*/
|
||||
initTheme() {
|
||||
// 合并主题配置
|
||||
this.themeConfig = merge(this.opt.theme && theme[this.opt.theme] ? theme[this.opt.theme] : theme.default, this.opt.themeConfig)
|
||||
this.themeConfig = merge(theme[this.opt.theme], this.opt.themeConfig)
|
||||
// 设置背景样式
|
||||
Style.setBackgroundStyle(this.el, this.themeConfig)
|
||||
}
|
||||
@ -195,6 +223,16 @@ class MindMap {
|
||||
execCommand(...args) {
|
||||
this.command.exec(...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:06:38
|
||||
* @Desc: 导出
|
||||
*/
|
||||
async export(...args) {
|
||||
let result = await this.doExport.export(...args)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default MindMap
|
||||
@ -5,6 +5,7 @@
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
"canvg": "^3.0.7",
|
||||
"deepmerge": "^1.5.2",
|
||||
"eventemitter3": "^4.0.7"
|
||||
}
|
||||
|
||||
@ -55,11 +55,16 @@ class Event extends EventEmitter {
|
||||
* @Desc: 绑定事件
|
||||
*/
|
||||
bind() {
|
||||
this.mindMap.draw.on('click', this.onDrawClick)
|
||||
this.mindMap.svg.on('click', this.onDrawClick)
|
||||
this.mindMap.el.addEventListener('mousedown', this.onMousedown)
|
||||
window.addEventListener('mousemove', this.onMousemove)
|
||||
window.addEventListener('mouseup', this.onMouseup)
|
||||
this.mindMap.el.addEventListener('mousewheel', this.onMousewheel)
|
||||
// 兼容火狐浏览器
|
||||
if(window.navigator.userAgent.toLowerCase().indexOf("firefox") != -1){
|
||||
this.mindMap.el.addEventListener('DOMMouseScroll', this.onMousewheel)
|
||||
} else {
|
||||
this.mindMap.el.addEventListener('mousewheel', this.onMousewheel)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,6 +96,9 @@ class Event extends EventEmitter {
|
||||
* @Desc: 鼠标按下事件
|
||||
*/
|
||||
onMousedown(e) {
|
||||
if (e.which !== this.mindMap.opt.dragButton) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault()
|
||||
this.isMousedown = true
|
||||
this.mousedownPos.x = e.clientX
|
||||
@ -137,7 +145,7 @@ class Event extends EventEmitter {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let dir
|
||||
if (e.wheelDeltaY > 0) {
|
||||
if ((e.wheelDeltaY || e.detail) > 0) {
|
||||
dir = 'up'
|
||||
} else {
|
||||
dir = 'down'
|
||||
|
||||
203
simple-mind-map/src/Export.js
Normal file
203
simple-mind-map/src/Export.js
Normal file
@ -0,0 +1,203 @@
|
||||
import { imgToDataUrl, downloadFile } from './utils';
|
||||
const URL = window.URL || window.webkitURL || window
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:05:16
|
||||
* @Desc: 导出类
|
||||
*/
|
||||
class Export {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:05:42
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt) {
|
||||
this.mindMap = opt.mindMap
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-02 07:44:06
|
||||
* @Desc: 导出
|
||||
*/
|
||||
async export(type, isDownload = true) {
|
||||
if (this[type]) {
|
||||
let result = await this[type]()
|
||||
if (isDownload) {
|
||||
downloadFile(result, '思维导图.' + type)
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 14:57:40
|
||||
* @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()
|
||||
// 去除变换效果
|
||||
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY).translate(0, 0)
|
||||
// 获取实际内容当前变换后的位置信息
|
||||
const rect = draw.rbox()
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.wdith, rect.height)
|
||||
// 把实际内容变换
|
||||
draw.translate(-rect.x, -rect.y)
|
||||
// 克隆一份数据
|
||||
const clone = svg.clone()
|
||||
// 恢复原先的大小和变换信息
|
||||
svg.size(origWidth, origHeight)
|
||||
draw.transform(origTransform)
|
||||
// 把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
let imageList = clone.find('image')
|
||||
let task = imageList.map(async (item) => {
|
||||
let imgUlr = item.attr('href') || item.attr('xlink:href')
|
||||
let imgData = await imgToDataUrl(imgUlr)
|
||||
item.attr('href', imgData)
|
||||
})
|
||||
await Promise.all(task)
|
||||
return {
|
||||
node: clone,
|
||||
str: clone.svg()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 15:25:19
|
||||
* @Desc: svg转png
|
||||
*/
|
||||
svgToPng(svgSrc) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
|
||||
img.setAttribute('crossOrigin', 'anonymous')
|
||||
img.onload = async () => {
|
||||
try {
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
let ctx = canvas.getContext('2d')
|
||||
// 绘制背景
|
||||
await this.drawBackgroundToCanvas(ctx, img.width, img.height)
|
||||
// 图片绘制到canvas里
|
||||
ctx.drawImage(img, 0, 0, img.width, img.height)
|
||||
resolve(canvas.toDataURL())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
img.onerror = (e) => {
|
||||
reject(e)
|
||||
}
|
||||
img.src = svgSrc
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 15:32:07
|
||||
* @Desc: 在canvas上绘制思维导图背景
|
||||
*/
|
||||
drawBackgroundToCanvas(ctx, width, height) {
|
||||
return new Promise((resolve, rejct) => {
|
||||
let { backgroundColor = '#fff', backgroundImage, backgroundRepeat = "repeat" } = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
ctx.save()
|
||||
ctx.rect(0, 0, width, height)
|
||||
ctx.fillStyle = backgroundColor
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
ctx.save()
|
||||
let img = new Image()
|
||||
img.src = backgroundImage
|
||||
img.onload = () => {
|
||||
let pat = ctx.createPattern(img, backgroundRepeat)
|
||||
ctx.rect(0, 0, width, height)
|
||||
ctx.fillStyle = pat
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
resolve()
|
||||
}
|
||||
img.onerror = (e) => {
|
||||
rejct(e)
|
||||
}
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:09:51
|
||||
* @Desc: 导出为png
|
||||
* 方法1.把svg的图片都转化成data:url格式,再转换
|
||||
* 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换
|
||||
*/
|
||||
async png() {
|
||||
let { str } = await this.getSvgData()
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
});
|
||||
// 转换成data:url数据
|
||||
let svgUrl = URL.createObjectURL(blob);
|
||||
// 绘制到canvas上
|
||||
let imgDataUrl = await this.svgToPng(svgUrl)
|
||||
URL.revokeObjectURL(svgUrl);
|
||||
return imgDataUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 15:32:07
|
||||
* @Desc: 在svg上绘制思维导图背景
|
||||
*/
|
||||
drawBackgroundToSvg(svg) {
|
||||
return new Promise(async (resolve, rejct) => {
|
||||
let { backgroundColor = '#fff', backgroundImage, backgroundRepeat = "repeat" } = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
svg.css('background-color', backgroundColor)
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
let imgDataUrl = await imgToDataUrl(backgroundImage)
|
||||
svg.css('background-image', `url(${imgDataUrl})`)
|
||||
svg.css('background-repeat', backgroundRepeat)
|
||||
resolve()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 14:54:07
|
||||
* @Desc: 导出为svg
|
||||
*/
|
||||
async svg() {
|
||||
let { node } = await this.getSvgData()
|
||||
await this.drawBackgroundToSvg(node)
|
||||
let str = node.svg()
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
});
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
}
|
||||
|
||||
export default Export
|
||||
@ -1,7 +1,8 @@
|
||||
import Style from './Style'
|
||||
import {
|
||||
resizeImgSize,
|
||||
copyRenderTree
|
||||
copyRenderTree,
|
||||
imgToDataUrl
|
||||
} from './utils'
|
||||
import {
|
||||
Image,
|
||||
@ -215,11 +216,12 @@ class Node {
|
||||
if (!_data.icon || _data.icon.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
let iconSize = this.themeConfig.iconSize
|
||||
return _data.icon.map((item) => {
|
||||
return {
|
||||
node: SVG(iconsSvg.getNodeIconListIcon(item)).size(this.themeConfig.iconSize, this.themeConfig.iconSize),
|
||||
width: this.themeConfig.iconSize,
|
||||
height: this.themeConfig.iconSize
|
||||
node: SVG(iconsSvg.getNodeIconListIcon(item)).size(iconSize, iconSize),
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -231,10 +233,7 @@ class Node {
|
||||
* @Desc: 创建文本节点
|
||||
*/
|
||||
createTextNode() {
|
||||
if (!this.nodeData.data.text) {
|
||||
return
|
||||
}
|
||||
let node = this.draw.text(this.nodeData.data.text)
|
||||
let node = this.draw.text(this.nodeData.data.text || '')
|
||||
this.style.text(node)
|
||||
let {
|
||||
width,
|
||||
@ -255,22 +254,24 @@ class Node {
|
||||
* @Desc: 创建超链接节点
|
||||
*/
|
||||
createHyperlinkNode() {
|
||||
if (!this.nodeData.data.hyperlink) {
|
||||
let { hyperlink, hyperlinkTitle } = this.nodeData.data
|
||||
if (!hyperlink) {
|
||||
return
|
||||
}
|
||||
let iconSize = this.themeConfig.iconSize
|
||||
let node = this.draw.element('a')
|
||||
let node = this.draw.link(hyperlink).target('_blank')
|
||||
node.node.addEventListener('click', (e) => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
node.attr('href', this.nodeData.data.hyperlink).attr('target', '_blank')
|
||||
if (this.nodeData.data.hyperlinkTitle) {
|
||||
node.attr('title', this.nodeData.data.hyperlinkTitle)
|
||||
if (hyperlinkTitle) {
|
||||
node.attr('title', hyperlinkTitle)
|
||||
}
|
||||
node.add(this.draw.rect(iconSize, iconSize).fill({ color: 'transparent' }))
|
||||
node.add(SVG(iconsSvg.hyperlink).size(iconSize, iconSize))
|
||||
node.rect(iconSize, iconSize).fill({ color: 'transparent' })
|
||||
let iconNode = SVG(iconsSvg.hyperlink).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode)
|
||||
node.add(iconNode)
|
||||
return {
|
||||
node: this.draw.nested().add(node),
|
||||
node: node,
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
}
|
||||
@ -282,12 +283,13 @@ class Node {
|
||||
* @Desc: 创建标签节点
|
||||
*/
|
||||
createTagNode() {
|
||||
if (!this.nodeData.data.tag || this.nodeData.data.tag.length <= 0) {
|
||||
let tagData = this.nodeData.data.tag
|
||||
if (!tagData || tagData.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
let nodes = []
|
||||
this.nodeData.data.tag.slice(0, 5).forEach((item, index) => {
|
||||
let tag = this.draw.nested()
|
||||
tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => {
|
||||
let tag = this.draw.group()
|
||||
let text = this.draw.text(item).x(8).cy(10)
|
||||
this.style.tagText(text, index)
|
||||
let {
|
||||
@ -317,10 +319,12 @@ class Node {
|
||||
if (!this.nodeData.data.note) {
|
||||
return null;
|
||||
}
|
||||
let node = this.draw.nested().attr('cursor', 'pointer')
|
||||
let node = this.draw.group().attr('cursor', 'pointer')
|
||||
let iconSize = this.themeConfig.iconSize
|
||||
node.add(this.draw.rect(iconSize, iconSize).fill({ color: 'transparent' }))
|
||||
node.add(SVG(iconsSvg.note).size(iconSize, iconSize))
|
||||
let iconNode = SVG(iconsSvg.note).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode)
|
||||
node.add(iconNode)
|
||||
let el = document.createElement('div')
|
||||
el.style.cssText = `
|
||||
position: absolute;
|
||||
@ -377,11 +381,11 @@ class Node {
|
||||
imgObj.node.cx(left + width / 2).y(top + paddingY)
|
||||
}
|
||||
// 内容节点
|
||||
let textContentNested = this.draw.nested()
|
||||
let textContentNested = this.draw.group()
|
||||
let textContentOffsetX = 0
|
||||
// icon
|
||||
let iconObjs = this.createIconNode()
|
||||
let iconNested = this.draw.nested()
|
||||
let iconNested = this.draw.group()
|
||||
if (iconObjs && iconObjs.length > 0) {
|
||||
let iconLeft = 0
|
||||
iconObjs.forEach((item) => {
|
||||
@ -403,13 +407,13 @@ class Node {
|
||||
// 超链接
|
||||
let hyperlinkObj = this.createHyperlinkNode()
|
||||
if (hyperlinkObj) {
|
||||
hyperlinkObj.node.x(textContentOffsetX).y((_textContentHeight - hyperlinkObj.height) / 2)
|
||||
hyperlinkObj.node.translate(textContentOffsetX, (_textContentHeight - hyperlinkObj.height) / 2)
|
||||
textContentNested.add(hyperlinkObj.node)
|
||||
textContentOffsetX += hyperlinkObj.width + _textContentItemMargin
|
||||
}
|
||||
// 标签
|
||||
let tagObjs = this.createTagNode()
|
||||
let tagNested = this.draw.nested()
|
||||
let tagNested = this.draw.group()
|
||||
if (tagObjs && tagObjs.length > 0) {
|
||||
let tagLeft = 0
|
||||
tagObjs.forEach((item) => {
|
||||
@ -423,12 +427,15 @@ class Node {
|
||||
// 备注
|
||||
let noteObj = this.createNoteNode()
|
||||
if (noteObj) {
|
||||
noteObj.node.x(textContentOffsetX).y((_textContentHeight - noteObj.height) / 2)
|
||||
noteObj.node.translate(textContentOffsetX, (_textContentHeight - noteObj.height) / 2)
|
||||
textContentNested.add(noteObj.node)
|
||||
textContentOffsetX += noteObj.width
|
||||
}
|
||||
// 文字内容整体
|
||||
textContentNested.x(left + width / 2).dx(-textContentNested.bbox().width / 2).y(top + imgHeight + paddingY + (imgHeight > 0 && _textContentHeight > 0 ? this._blockContentMargin : 0))
|
||||
textContentNested.translate(
|
||||
left + width / 2 - textContentNested.bbox().width / 2,
|
||||
top + imgHeight + paddingY + (imgHeight > 0 && _textContentHeight > 0 ? this._blockContentMargin : 0)
|
||||
)
|
||||
group.add(textContentNested)
|
||||
// 单击事件
|
||||
group.click((e) => {
|
||||
|
||||
@ -13,13 +13,11 @@ class Style {
|
||||
* @Desc: 设置背景样式
|
||||
*/
|
||||
static setBackgroundStyle(el, themeConfig) {
|
||||
let { backgroundColor, backgroundImage, backgroundRepeat, backgroundSize, backgroundPosition } = themeConfig
|
||||
let { backgroundColor, backgroundImage, backgroundRepeat } = themeConfig
|
||||
el.style.backgroundColor = backgroundColor
|
||||
if (backgroundImage) {
|
||||
el.style.backgroundImage = `url(${backgroundImage})`
|
||||
el.style.backgroundRepeat = backgroundRepeat
|
||||
el.style.backgroundSize = backgroundSize
|
||||
el.style.backgroundPosition = backgroundPosition
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,6 +125,17 @@ class Style {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-03 22:37:19
|
||||
* @Desc: 内置图标
|
||||
*/
|
||||
iconNode(node) {
|
||||
node.attr({
|
||||
fill: this.merge('color')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 14:50:49
|
||||
|
||||
@ -59,9 +59,6 @@ export default class TextEdit {
|
||||
* @Desc: 显示文本编辑框
|
||||
*/
|
||||
show(node) {
|
||||
if (!node.nodeData.data.text) {
|
||||
return;
|
||||
}
|
||||
this.showEditTextBox(node, node.textNode.node.node.getBoundingClientRect())
|
||||
}
|
||||
|
||||
@ -98,8 +95,9 @@ export default class TextEdit {
|
||||
}
|
||||
this.renderer.activeNodeList.forEach((node) => {
|
||||
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
|
||||
node.nodeData.data.text = str
|
||||
console.log(8)
|
||||
this.mindMap.execCommand('UPDATE_NODE_DATA', node, {
|
||||
text: str
|
||||
})
|
||||
this.mindMap.render()
|
||||
})
|
||||
this.mindMap.emit('hide_text_edit', this.textEditNode, this.renderer.activeNodeList)
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import merge from 'deepmerge'
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
@ -16,19 +14,11 @@ class View {
|
||||
constructor(opt = {}) {
|
||||
this.opt = opt
|
||||
this.mindMap = this.opt.mindMap
|
||||
this.viewBox = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.mindMap.width,
|
||||
height: this.mindMap.height
|
||||
}
|
||||
this.cacheViewBox = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.mindMap.width,
|
||||
height: this.mindMap.height
|
||||
}
|
||||
this.scale = 1
|
||||
this.sx = 0
|
||||
this.sy = 0
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.bind()
|
||||
}
|
||||
|
||||
@ -41,29 +31,35 @@ class View {
|
||||
bind() {
|
||||
// 拖动视图
|
||||
this.mindMap.event.on('mousedown', () => {
|
||||
this.cacheViewBox = merge({}, this.viewBox)
|
||||
this.sx = this.x
|
||||
this.sy = this.y
|
||||
})
|
||||
this.mindMap.event.on('drag', (e, event) => {
|
||||
// 视图放大缩小后拖动的距离也要相应变化
|
||||
this.viewBox.x = this.cacheViewBox.x - event.mousemoveOffset.x * this.scale
|
||||
this.viewBox.y = this.cacheViewBox.y - event.mousemoveOffset.y * this.scale
|
||||
this.setViewBox()
|
||||
this.x = this.sx + event.mousemoveOffset.x
|
||||
this.y = this.sy + event.mousemoveOffset.y
|
||||
this.mindMap.draw.transform({
|
||||
scale: this.scale,
|
||||
origin: 'left center',
|
||||
translate: [this.x, this.y],
|
||||
})
|
||||
})
|
||||
// 放大缩小视图
|
||||
this.mindMap.event.on('mousewheel', (e, dir) => {
|
||||
let stepWidth = this.viewBox.width * this.mindMap.opt.scaleRatio
|
||||
let stepHeight = this.viewBox.height * this.mindMap.opt.scaleRatio
|
||||
// 放大
|
||||
// // 放大
|
||||
if (dir === 'down') {
|
||||
this.scale += this.mindMap.opt.scaleRatio
|
||||
this.viewBox.width += stepWidth
|
||||
this.viewBox.height += stepHeight
|
||||
} else { // 缩小
|
||||
this.scale -= this.mindMap.opt.scaleRatio
|
||||
this.viewBox.width -= stepWidth
|
||||
this.viewBox.height -= stepHeight
|
||||
if (this.scale - this.mindMap.opt.scaleRatio > 0.1) {
|
||||
this.scale -= this.mindMap.opt.scaleRatio
|
||||
} else {
|
||||
this.scale = 0.1
|
||||
}
|
||||
}
|
||||
this.setViewBox()
|
||||
this.mindMap.draw.transform({
|
||||
scale: this.scale,
|
||||
origin: 'left center',
|
||||
translate: [this.x, this.y],
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -73,13 +69,10 @@ class View {
|
||||
* @Date: 2021-04-07 15:43:26
|
||||
* @Desc: 设置视图
|
||||
*/
|
||||
setViewBox() {
|
||||
let {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
} = this.viewBox
|
||||
setViewBox({ x,
|
||||
y,
|
||||
width,
|
||||
height }) {
|
||||
this.opt.draw.viewbox(x, y, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,8 +16,6 @@ class Base {
|
||||
this.mindMap = renderer.mindMap
|
||||
// 渲染树
|
||||
this.renderTree = renderer.renderTree
|
||||
// 主题配置
|
||||
this.themeConfig = this.mindMap.themeConfig
|
||||
// 绘图对象
|
||||
this.draw = this.mindMap.draw
|
||||
// 根节点
|
||||
@ -97,7 +95,7 @@ class Base {
|
||||
* @Desc: 获取节点的marginX
|
||||
*/
|
||||
getMarginX(layerIndex) {
|
||||
return layerIndex === 1 ? this.themeConfig.second.marginX : this.themeConfig.node.marginX;
|
||||
return layerIndex === 1 ? this.mindMap.themeConfig.second.marginX : this.mindMap.themeConfig.node.marginX;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +104,7 @@ class Base {
|
||||
* @Desc: 获取节点的marginY
|
||||
*/
|
||||
getMarginY(layerIndex) {
|
||||
return layerIndex === 1 ? this.themeConfig.second.marginY : this.themeConfig.node.marginY;
|
||||
return layerIndex === 1 ? this.mindMap.themeConfig.second.marginY : this.mindMap.themeConfig.node.marginY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@ class LogicalStructure extends Base {
|
||||
this.root = newNode
|
||||
} else {
|
||||
// 非根节点
|
||||
let marginX = layerIndex === 1 ? this.themeConfig.second.marginX : this.themeConfig.node.marginX
|
||||
let marginX = layerIndex === 1 ? this.mindMap.themeConfig.second.marginX : this.mindMap.themeConfig.node.marginX
|
||||
// 定位到父节点右侧
|
||||
newNode.left = parent._node.left + parent._node.width + marginX
|
||||
// 互相收集
|
||||
|
||||
@ -23,10 +23,6 @@ export default {
|
||||
backgroundImage: 'none',
|
||||
// 背景重复
|
||||
backgroundRepeat: 'no-repeat',
|
||||
// 背景图像大小
|
||||
backgroundSize: 'auto',
|
||||
// 背景图像定位
|
||||
backgroundPosition: '0% 0%',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: '#549688',
|
||||
|
||||
@ -131,4 +131,46 @@ export const copyRenderTree = (tree, root) => {
|
||||
})
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 09:08:43
|
||||
* @Desc: 图片转成dataURL
|
||||
*/
|
||||
export const imgToDataUrl = (src) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
|
||||
img.setAttribute('crossOrigin', 'anonymous')
|
||||
img.onload = () => {
|
||||
try {
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
let ctx = canvas.getContext('2d')
|
||||
// 图片绘制到canvas里
|
||||
ctx.drawImage(img, 0, 0, img.width, img.height)
|
||||
resolve(canvas.toDataURL())
|
||||
} catch (error) {
|
||||
reject(e)
|
||||
}
|
||||
}
|
||||
img.onerror = (e) => {
|
||||
reject(e)
|
||||
}
|
||||
img.src = src
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 16:20:06
|
||||
* @Desc: 下载文件
|
||||
*/
|
||||
export const downloadFile = (file, fileName) => {
|
||||
let a = document.createElement('a')
|
||||
a.href = file
|
||||
a.download = fileName
|
||||
a.click()
|
||||
}
|
||||
BIN
web/public/enJFNMHnedQTYTESGfDkctCp2.jpeg
Normal file
BIN
web/public/enJFNMHnedQTYTESGfDkctCp2.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 695 KiB |
@ -147,22 +147,6 @@ export const backgroundRepeatList = [
|
||||
}
|
||||
]
|
||||
|
||||
// 背景图片大小
|
||||
export const backgroundSizeList = [
|
||||
{
|
||||
name: '自动',
|
||||
value: 'auto'
|
||||
},
|
||||
{
|
||||
name: '完全覆盖',
|
||||
value: 'cover'
|
||||
},
|
||||
{
|
||||
name: '最合适',
|
||||
value: 'contain'
|
||||
}
|
||||
]
|
||||
|
||||
// 背景图片定位
|
||||
export const backgroundPositionList = [
|
||||
{
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
<span class="name">图片重复</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
style="width: 120px"
|
||||
v-model="style.backgroundRepeat"
|
||||
placeholder=""
|
||||
@change="
|
||||
@ -47,50 +47,6 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">图片大小</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.backgroundSize"
|
||||
placeholder=""
|
||||
@change="
|
||||
(value) => {
|
||||
update('backgroundSize', value);
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in backgroundSizeList"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">图片定位</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.backgroundPosition"
|
||||
placeholder=""
|
||||
@change="
|
||||
(value) => {
|
||||
update('backgroundPosition', value);
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in backgroundPositionList"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
@ -220,6 +176,44 @@
|
||||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 二级节点外边距 -->
|
||||
<div class="title noTop">节点外边距</div>
|
||||
<div class="row column">
|
||||
<el-tabs
|
||||
class="tab"
|
||||
v-model="marginActiveTab"
|
||||
@tab-click="initMarginStyle"
|
||||
>
|
||||
<el-tab-pane label="二级节点" name="second"></el-tab-pane>
|
||||
<el-tab-pane label="三级及以下节点" name="node"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<div class="rowItem">
|
||||
<span class="name">水平</span>
|
||||
<el-slider
|
||||
:max="200"
|
||||
style="width: 200px"
|
||||
v-model="style.marginX"
|
||||
@change="
|
||||
(value) => {
|
||||
updateMargin('marginX', value);
|
||||
}
|
||||
"
|
||||
></el-slider>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">垂直</span>
|
||||
<el-slider
|
||||
:max="200"
|
||||
style="width: 200px"
|
||||
v-model="style.marginY"
|
||||
@change="
|
||||
(value) => {
|
||||
updateMargin('marginY', value);
|
||||
}
|
||||
"
|
||||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Sidebar>
|
||||
</template>
|
||||
@ -229,9 +223,7 @@ import Sidebar from "./Sidebar";
|
||||
import Color from "./Color";
|
||||
import {
|
||||
lineWidthList,
|
||||
backgroundRepeatList,
|
||||
backgroundSizeList,
|
||||
backgroundPositionList,
|
||||
backgroundRepeatList
|
||||
} from "@/config";
|
||||
import ImgUpload from "@/components/ImgUpload";
|
||||
|
||||
@ -260,9 +252,8 @@ export default {
|
||||
return {
|
||||
lineWidthList,
|
||||
backgroundRepeatList,
|
||||
backgroundSizeList,
|
||||
backgroundPositionList,
|
||||
activeTab: "color",
|
||||
marginActiveTab: "second",
|
||||
style: {
|
||||
backgroundColor: "",
|
||||
lineColor: "",
|
||||
@ -274,8 +265,8 @@ export default {
|
||||
iconSize: 0,
|
||||
backgroundImage: "",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "auto",
|
||||
backgroundPosition: "0% 0%",
|
||||
marginX: 0,
|
||||
marginY: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
@ -306,10 +297,24 @@ export default {
|
||||
"iconSize",
|
||||
"backgroundImage",
|
||||
"backgroundRepeat",
|
||||
"backgroundSize",
|
||||
"backgroundPosition",
|
||||
].forEach((key) => {
|
||||
this.style[key] = this.mindMap.getThemeConfig(key);
|
||||
if (key === "backgroundImage" && this.style[key] === "none") {
|
||||
this.style[key] = "";
|
||||
}
|
||||
});
|
||||
this.initMarginStyle();
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-03 22:27:32
|
||||
* @Desc: margin初始值
|
||||
*/
|
||||
initMarginStyle() {
|
||||
["marginX", "marginY"].forEach((key) => {
|
||||
this.style[key] =
|
||||
this.mindMap.getThemeConfig()[this.marginActiveTab][key];
|
||||
});
|
||||
},
|
||||
|
||||
@ -319,9 +324,27 @@ export default {
|
||||
* @Desc: 更新配置
|
||||
*/
|
||||
update(key, value) {
|
||||
this.style[key] = value;
|
||||
if (key === "backgroundImage" && value === "none") {
|
||||
this.style[key] = "";
|
||||
} else {
|
||||
this.style[key] = value;
|
||||
}
|
||||
this.data.theme.config[key] = value;
|
||||
this.$emit("change");
|
||||
this.mindMap.setThemeConfig(this.data.theme.config);
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-03 22:08:12
|
||||
* @Desc: 设置margin
|
||||
*/
|
||||
updateMargin(type, value) {
|
||||
this.style[type] = value;
|
||||
if (!this.data.theme.config[this.marginActiveTab]) {
|
||||
this.data.theme.config[this.marginActiveTab] = {};
|
||||
}
|
||||
this.data.theme.config[this.marginActiveTab][type] = value;
|
||||
this.mindMap.setThemeConfig(this.data.theme.config);
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -350,6 +373,10 @@ export default {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab {
|
||||
width: 100%;
|
||||
}
|
||||
@ -371,7 +398,7 @@ export default {
|
||||
|
||||
.name {
|
||||
font-size: 12px;
|
||||
margin-right: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.block {
|
||||
|
||||
82
web/src/pages/Edit/components/Count.vue
Normal file
82
web/src/pages/Edit/components/Count.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="countContainer">
|
||||
<div class="item">
|
||||
<span class="name">字数</span>
|
||||
<span class="value">{{ words }}</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="name">节点</span>
|
||||
<span class="value">{{ num }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 22:53:10
|
||||
* @Desc: 字数及节点数量统计
|
||||
*/
|
||||
export default {
|
||||
name: "Count",
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
words: 0,
|
||||
num: 0,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$bus.$on("data_change", (data) => {
|
||||
this.words = 0;
|
||||
this.num = 0;
|
||||
this.walk(data);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-30 22:13:07
|
||||
* @Desc: 遍历
|
||||
*/
|
||||
walk(data) {
|
||||
this.num++;
|
||||
this.words += (String(data.data.text) || "").length;
|
||||
if (data.children && data.children.length > 0) {
|
||||
data.children.forEach((item) => {
|
||||
this.walk(item);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.countContainer {
|
||||
padding: 0 12px;
|
||||
position: fixed;
|
||||
left: 20px;
|
||||
bottom: 20px;
|
||||
background: hsla(0, 0%, 100%, 0.6);
|
||||
border-radius: 2px;
|
||||
opacity: 0.8;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
|
||||
.item {
|
||||
color: #555;
|
||||
margin-right: 15px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,13 +1,10 @@
|
||||
<template>
|
||||
<div class="editContainer">
|
||||
<div class="mindMapContainer" ref="mindMapContainer"></div>
|
||||
<Count></Count>
|
||||
<Outline></Outline>
|
||||
<Style></Style>
|
||||
<BaseStyle
|
||||
:data="mindMapData"
|
||||
:mindMap="mindMap"
|
||||
@change="changeThemeConfig"
|
||||
></BaseStyle>
|
||||
<BaseStyle :data="mindMapData" :mindMap="mindMap"></BaseStyle>
|
||||
<Theme :mindMap="mindMap"></Theme>
|
||||
<Structure :mindMap="mindMap"></Structure>
|
||||
</div>
|
||||
@ -20,7 +17,8 @@ import Style from "./Style";
|
||||
import BaseStyle from "./BaseStyle";
|
||||
import exampleData from "simple-mind-map/example/exampleData";
|
||||
import Theme from "./Theme";
|
||||
import Structure from './Structure';
|
||||
import Structure from "./Structure";
|
||||
import Count from "./Count";
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
@ -34,20 +32,32 @@ export default {
|
||||
Style,
|
||||
BaseStyle,
|
||||
Theme,
|
||||
Structure
|
||||
Structure,
|
||||
Count,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mindMap: null,
|
||||
mindMapData: exampleData,
|
||||
mindMapData: null,
|
||||
prevImg: "",
|
||||
};
|
||||
},
|
||||
created() {},
|
||||
mounted() {
|
||||
this.getData();
|
||||
this.init();
|
||||
this.$bus.$on("execCommand", this.execCommand);
|
||||
this.$bus.$on("export", this.export);
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-03 22:11:37
|
||||
* @Desc: 获取思维导图数据,实际应该调接口获取
|
||||
*/
|
||||
getData() {
|
||||
this.mindMapData = exampleData;
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-10 15:01:01
|
||||
@ -70,22 +80,12 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 13:49:25
|
||||
* @Desc: 修改主题配置
|
||||
*/
|
||||
changeThemeConfig() {
|
||||
this.mindMap.setThemeConfig(this.mindMapData.theme.config);
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 13:32:11
|
||||
* @Desc: 重新渲染
|
||||
*/
|
||||
reRender() {
|
||||
console.log(12)
|
||||
this.mindMap.render();
|
||||
},
|
||||
|
||||
@ -97,6 +97,19 @@ export default {
|
||||
execCommand(...args) {
|
||||
this.mindMap.execCommand(...args);
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:33:02
|
||||
* @Desc: 导出
|
||||
*/
|
||||
async export(...args) {
|
||||
try {
|
||||
this.mindMap.export(...args);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
78
web/src/pages/Edit/components/Export.vue
Normal file
78
web/src/pages/Edit/components/Export.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="nodeDialog"
|
||||
title="导出"
|
||||
:visible.sync="dialogVisible"
|
||||
width="500"
|
||||
>
|
||||
<div>
|
||||
<div class="nameInputBox">
|
||||
<span class="name">导出文件名称</span>
|
||||
<el-input style="width: 300px" v-model="fileName" size="mini"></el-input>
|
||||
</div>
|
||||
<el-radio-group v-model="exportType">
|
||||
<el-radio label="png">图片文件(PNG)</el-radio>
|
||||
<el-radio label="svg">svg文件(SVG)</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="confirm">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 22:53:54
|
||||
* @Desc: 导出
|
||||
*/
|
||||
export default {
|
||||
name: "Export",
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
exportType: "png",
|
||||
fileName: '思维导图'
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$bus.$on("showExport", () => {
|
||||
this.dialogVisible = true;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-22 22:08:11
|
||||
* @Desc: 取消
|
||||
*/
|
||||
cancel() {
|
||||
this.dialogVisible = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-06 22:28:20
|
||||
* @Desc: 确定
|
||||
*/
|
||||
confirm() {
|
||||
this.$bus.$emit("export", this.exportType);
|
||||
this.cancel();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nodeDialog {
|
||||
.nameInputBox {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.name {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -9,6 +9,7 @@
|
||||
v-model="tag"
|
||||
@keyup.native.enter="add"
|
||||
:disabled="tagArr.length >= max"
|
||||
placeholder="请按回车键添加"
|
||||
>
|
||||
</el-input>
|
||||
<div class="tagList">
|
||||
|
||||
@ -190,7 +190,7 @@
|
||||
<div class="rowItem">
|
||||
<span class="name">水平</span>
|
||||
<el-slider
|
||||
style="width: 230px"
|
||||
style="width: 200px"
|
||||
v-model="style.paddingX"
|
||||
@change="update('paddingX')"
|
||||
></el-slider>
|
||||
@ -200,7 +200,7 @@
|
||||
<div class="rowItem">
|
||||
<span class="name">垂直</span>
|
||||
<el-slider
|
||||
style="width: 230px"
|
||||
style="width: 200px"
|
||||
v-model="style.paddingY"
|
||||
@change="update('paddingY')"
|
||||
></el-slider>
|
||||
@ -222,10 +222,10 @@ import {
|
||||
borderRadiusList,
|
||||
} from "@/config";
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 22:54:47
|
||||
* @Desc: 节点样式设置
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 22:54:47
|
||||
* @Desc: 节点样式设置
|
||||
*/
|
||||
export default {
|
||||
name: "Style",
|
||||
@ -320,6 +320,7 @@ export default {
|
||||
* @Desc: 修改样式
|
||||
*/
|
||||
update(prop) {
|
||||
console.log(this.style[prop])
|
||||
this.activeNode.setStyle(
|
||||
prop,
|
||||
this.style[prop],
|
||||
@ -436,7 +437,7 @@ export default {
|
||||
|
||||
.name {
|
||||
font-size: 12px;
|
||||
margin-right: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.block {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="toolbarContainer">
|
||||
<div class="toolbar">
|
||||
<!-- 节点操作 -->
|
||||
<div class="left">
|
||||
<div class="toolbarBlock">
|
||||
<div
|
||||
class="toolbarBtn"
|
||||
:class="{
|
||||
@ -85,7 +85,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 通用操作 -->
|
||||
<div class="center">
|
||||
<div class="toolbarBlock">
|
||||
<div class="toolbarBtn" @click="$bus.$emit('showOutline')">
|
||||
<span class="icon iconfont iconfuhao-dagangshu"></span>
|
||||
<span class="text">显示大纲</span>
|
||||
@ -103,12 +103,20 @@
|
||||
<span class="text">结构</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 导出 -->
|
||||
<div class="toolbarBlock">
|
||||
<div class="toolbarBtn" @click="$bus.$emit('showExport')">
|
||||
<span class="icon iconfont icondaochu"></span>
|
||||
<span class="text">导出</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NodeImage></NodeImage>
|
||||
<NodeHyperlink></NodeHyperlink>
|
||||
<NodeIcon></NodeIcon>
|
||||
<NodeNote></NodeNote>
|
||||
<NodeTag></NodeTag>
|
||||
<Export></Export>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -118,6 +126,7 @@ import NodeHyperlink from "./NodeHyperlink";
|
||||
import NodeIcon from "./NodeIcon";
|
||||
import NodeNote from "./NodeNote";
|
||||
import NodeTag from "./NodeTag";
|
||||
import Export from './Export';
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
@ -132,6 +141,7 @@ export default {
|
||||
NodeIcon,
|
||||
NodeNote,
|
||||
NodeTag,
|
||||
Export
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -159,7 +169,6 @@ export default {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding: 0 20px;
|
||||
padding-top: 20px;
|
||||
@ -169,8 +178,7 @@ export default {
|
||||
color: rgba(26, 26, 26, 0.8);
|
||||
z-index: 2;
|
||||
|
||||
.left,
|
||||
.center {
|
||||
.toolbarBlock {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
padding: 10px 20px;
|
||||
@ -178,6 +186,10 @@ export default {
|
||||
box-shadow: 0 2px 16px 0 rgb(0 0 0 / 6%);
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
margin-right: 20px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbarBtn {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user