diff --git a/simple-mind-map/example/exampleData.js b/simple-mind-map/example/exampleData.js index bea996fa..1e71e274 100644 --- a/simple-mind-map/example/exampleData.js +++ b/simple-mind-map/example/exampleData.js @@ -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() } } ] diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js index b04ac4cd..b48f5f02 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -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 \ No newline at end of file diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json index f645920c..9014260a 100644 --- a/simple-mind-map/package.json +++ b/simple-mind-map/package.json @@ -5,6 +5,7 @@ "scripts": {}, "dependencies": { "@svgdotjs/svg.js": "^3.0.16", + "canvg": "^3.0.7", "deepmerge": "^1.5.2", "eventemitter3": "^4.0.7" } diff --git a/simple-mind-map/src/Event.js b/simple-mind-map/src/Event.js index 3f3ae06b..990f3dec 100644 --- a/simple-mind-map/src/Event.js +++ b/simple-mind-map/src/Event.js @@ -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' diff --git a/simple-mind-map/src/Export.js b/simple-mind-map/src/Export.js new file mode 100644 index 00000000..4b5dd6b6 --- /dev/null +++ b/simple-mind-map/src/Export.js @@ -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 \ No newline at end of file diff --git a/simple-mind-map/src/Node.js b/simple-mind-map/src/Node.js index ebf03e2f..e1869bcd 100644 --- a/simple-mind-map/src/Node.js +++ b/simple-mind-map/src/Node.js @@ -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) => { diff --git a/simple-mind-map/src/Style.js b/simple-mind-map/src/Style.js index 478dc8dd..16fb2668 100644 --- a/simple-mind-map/src/Style.js +++ b/simple-mind-map/src/Style.js @@ -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 diff --git a/simple-mind-map/src/TextEdit.js b/simple-mind-map/src/TextEdit.js index fb93573a..49355a9b 100644 --- a/simple-mind-map/src/TextEdit.js +++ b/simple-mind-map/src/TextEdit.js @@ -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) diff --git a/simple-mind-map/src/View.js b/simple-mind-map/src/View.js index 713a2d4c..a12c6328 100644 --- a/simple-mind-map/src/View.js +++ b/simple-mind-map/src/View.js @@ -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) } } diff --git a/simple-mind-map/src/layouts/Base.js b/simple-mind-map/src/layouts/Base.js index 35e979d7..9f084320 100644 --- a/simple-mind-map/src/layouts/Base.js +++ b/simple-mind-map/src/layouts/Base.js @@ -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; } } diff --git a/simple-mind-map/src/layouts/LogicalStructure.js b/simple-mind-map/src/layouts/LogicalStructure.js index 70f096ea..3cf989da 100644 --- a/simple-mind-map/src/layouts/LogicalStructure.js +++ b/simple-mind-map/src/layouts/LogicalStructure.js @@ -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 // 互相收集 diff --git a/simple-mind-map/src/themes/default.js b/simple-mind-map/src/themes/default.js index 81c7997e..769c3529 100644 --- a/simple-mind-map/src/themes/default.js +++ b/simple-mind-map/src/themes/default.js @@ -23,10 +23,6 @@ export default { backgroundImage: 'none', // 背景重复 backgroundRepeat: 'no-repeat', - // 背景图像大小 - backgroundSize: 'auto', - // 背景图像定位 - backgroundPosition: '0% 0%', // 根节点样式 root: { fillColor: '#549688', diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index 47a4d50e..f10116bb 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -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() } \ No newline at end of file diff --git a/web/public/enJFNMHnedQTYTESGfDkctCp2.jpeg b/web/public/enJFNMHnedQTYTESGfDkctCp2.jpeg new file mode 100644 index 00000000..ac324e43 Binary files /dev/null and b/web/public/enJFNMHnedQTYTESGfDkctCp2.jpeg differ diff --git a/web/src/config/index.js b/web/src/config/index.js index 242c65d7..44f6f50f 100644 --- a/web/src/config/index.js +++ b/web/src/config/index.js @@ -147,22 +147,6 @@ export const backgroundRepeatList = [ } ] -// 背景图片大小 -export const backgroundSizeList = [ - { - name: '自动', - value: 'auto' - }, - { - name: '完全覆盖', - value: 'cover' - }, - { - name: '最合适', - value: 'contain' - } -] - // 背景图片定位 export const backgroundPositionList = [ { diff --git a/web/src/pages/Edit/components/BaseStyle.vue b/web/src/pages/Edit/components/BaseStyle.vue index 20f841c6..f6f0cd42 100644 --- a/web/src/pages/Edit/components/BaseStyle.vue +++ b/web/src/pages/Edit/components/BaseStyle.vue @@ -29,7 +29,7 @@ 图片重复 - 图片大小 - - - - - -
- 图片定位 - - - - -
@@ -220,6 +176,44 @@ > + +
节点外边距
+
+ + + + +
+ 水平 + +
+
+ 垂直 + +
+
@@ -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 { diff --git a/web/src/pages/Edit/components/Count.vue b/web/src/pages/Edit/components/Count.vue new file mode 100644 index 00000000..5756a52b --- /dev/null +++ b/web/src/pages/Edit/components/Count.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/web/src/pages/Edit/components/Edit.vue b/web/src/pages/Edit/components/Edit.vue index 526a7a53..3d58697e 100644 --- a/web/src/pages/Edit/components/Edit.vue +++ b/web/src/pages/Edit/components/Edit.vue @@ -1,13 +1,10 @@ @@ -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 {