Merge branch 'feature' into main
This commit is contained in:
commit
075bf54d28
14
README.md
14
README.md
@ -44,7 +44,7 @@ Github:[releases](https://github.com/wanglin2/mind-map/releases)。百度云
|
|||||||
|
|
||||||
官方提供了如下插件,可根据需求按需引入(某个功能不生效大概率是因为你没有引入对应的插件),具体使用方式请查看文档:
|
官方提供了如下插件,可根据需求按需引入(某个功能不生效大概率是因为你没有引入对应的插件),具体使用方式请查看文档:
|
||||||
|
|
||||||
> RichText(节点富文本插件)、Select(鼠标多选节点插件)、Drag(节点拖拽插件)、AssociativeLine(关联线插件)、Export(导出插件)、KeyboardNavigation(键盘导航插件)、MiniMap(小地图插件)、Watermark(水印插件)、TouchEvent(移动端触摸事件支持插件)、NodeImgAdjust(拖拽调整节点图片大小插件)、Search(搜索插件)、Painter(节点格式刷插件)、Scrollbar(滚动条插件)、Formula(数学公式插件)、Cooperate(协同编辑插件)、RainbowLines(彩虹线条插件)、Demonstrate(演示模式插件)、OuterFrame(外框插件)、HandDrawnLikeStyle(手绘风格插件)[收费]、Notation(节点标记插件)[收费]、Numbers(节点编号插件)[收费]
|
> RichText(节点富文本插件)、Select(鼠标多选节点插件)、Drag(节点拖拽插件)、AssociativeLine(关联线插件)、Export(导出插件)、KeyboardNavigation(键盘导航插件)、MiniMap(小地图插件)、Watermark(水印插件)、TouchEvent(移动端触摸事件支持插件)、NodeImgAdjust(拖拽调整节点图片大小插件)、Search(搜索插件)、Painter(节点格式刷插件)、Scrollbar(滚动条插件)、Formula(数学公式插件)、Cooperate(协同编辑插件)、RainbowLines(彩虹线条插件)、Demonstrate(演示模式插件)、OuterFrame(外框插件)、HandDrawnLikeStyle(手绘风格插件)[收费]、Notation(节点标记插件)[收费]、Numbers(节点编号插件)[收费]、Freemind(Freemind格式导入导出插件)[收费]、Excel(Excel格式导入导出插件)[收费]
|
||||||
|
|
||||||
本项目不会实现的特性:
|
本项目不会实现的特性:
|
||||||
|
|
||||||
@ -457,4 +457,16 @@ const mindMap = new MindMap({
|
|||||||
<img src="./web/src/assets/avatar/炫.jpg" style="width: 50px;height: 50px;" />
|
<img src="./web/src/assets/avatar/炫.jpg" style="width: 50px;height: 50px;" />
|
||||||
<span>炫</span>
|
<span>炫</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span>
|
||||||
|
<img src="./web/src/assets/avatar/Lawliet.jpg" style="width: 50px;height: 50px;" />
|
||||||
|
<span>Lawliet</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<img src="./web/src/assets/avatar/一叶孤舟.jpg" style="width: 50px;height: 50px;" />
|
||||||
|
<span>一叶孤舟</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||||
|
<span>晏江</span>
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
2
copy.js
2
copy.js
@ -13,4 +13,4 @@ if (fs.existsSync(src)) {
|
|||||||
fs.unlinkSync(src)
|
fs.unlinkSync(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn('请检查手绘风格、标记插件、编号插件是否启用!!!')
|
console.warn('请检查付费插件是否启用!!!')
|
||||||
@ -31,7 +31,7 @@ MindMap.iconList = icons.nodeIconList
|
|||||||
MindMap.constants = constants
|
MindMap.constants = constants
|
||||||
MindMap.themes = themes
|
MindMap.themes = themes
|
||||||
MindMap.defaultTheme = defaultTheme
|
MindMap.defaultTheme = defaultTheme
|
||||||
MindMap.version = '0.11.0'
|
MindMap.version = '0.11.1'
|
||||||
|
|
||||||
MindMap.usePlugin(MiniMap)
|
MindMap.usePlugin(MiniMap)
|
||||||
.usePlugin(Watermark)
|
.usePlugin(Watermark)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "simple-mind-map",
|
"name": "simple-mind-map",
|
||||||
"version": "0.11.0",
|
"version": "0.11.1",
|
||||||
"description": "一个简单的web在线思维导图",
|
"description": "一个简单的web在线思维导图",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -328,7 +328,9 @@ export const nodeDataNoStylePropList = [
|
|||||||
'notation',
|
'notation',
|
||||||
'outerFrame',
|
'outerFrame',
|
||||||
'number',
|
'number',
|
||||||
'range'
|
'range',
|
||||||
|
'customLeft',
|
||||||
|
'customTop'
|
||||||
]
|
]
|
||||||
|
|
||||||
// 错误类型
|
// 错误类型
|
||||||
|
|||||||
@ -67,9 +67,7 @@ export const defaultOpt = {
|
|||||||
close: ''
|
close: ''
|
||||||
},
|
},
|
||||||
// 处理收起节点数量
|
// 处理收起节点数量
|
||||||
expandBtnNumHandler: num => {
|
expandBtnNumHandler: null,
|
||||||
return num
|
|
||||||
},
|
|
||||||
// 是否显示带数量的收起按钮
|
// 是否显示带数量的收起按钮
|
||||||
isShowExpandNum: true,
|
isShowExpandNum: true,
|
||||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||||
@ -176,11 +174,6 @@ export const defaultOpt = {
|
|||||||
addHistoryTime: 100,
|
addHistoryTime: 100,
|
||||||
// 是否禁止拖动画布
|
// 是否禁止拖动画布
|
||||||
isDisableDrag: false,
|
isDisableDrag: false,
|
||||||
// 鼠标移入概要高亮所属节点时的高亮框样式
|
|
||||||
highlightNodeBoxStyle: {
|
|
||||||
stroke: 'rgb(94, 200, 248)',
|
|
||||||
fill: 'transparent'
|
|
||||||
},
|
|
||||||
// 创建新节点时的行为
|
// 创建新节点时的行为
|
||||||
/*
|
/*
|
||||||
DEFAULT :默认会激活新创建的节点,并且进入编辑模式。如果同时创建了多个新节点,那么只会激活而不会进入编辑模式
|
DEFAULT :默认会激活新创建的节点,并且进入编辑模式。如果同时创建了多个新节点,那么只会激活而不会进入编辑模式
|
||||||
@ -238,6 +231,10 @@ export const defaultOpt = {
|
|||||||
padding: 100, // 超出画布四周指定范围内依旧渲染节点
|
padding: 100, // 超出画布四周指定范围内依旧渲染节点
|
||||||
removeNodeWhenOutCanvas: true // 节点移除画布可视区域后从画布删除
|
removeNodeWhenOutCanvas: true // 节点移除画布可视区域后从画布删除
|
||||||
},
|
},
|
||||||
|
// 如果节点文本为空,那么为了避免空白节点高度塌陷,会用该字段指定的文本测量一个高度
|
||||||
|
emptyTextMeasureHeightText: 'abc123我和你',
|
||||||
|
// 是否在进行节点文本编辑时实时更新节点大小和节点位置,开启后当节点数量比较多时可能会造成卡顿
|
||||||
|
openRealtimeRenderOnNodeTextEdit: false,
|
||||||
|
|
||||||
// 【Select插件】
|
// 【Select插件】
|
||||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||||
@ -409,5 +406,13 @@ export const defaultOpt = {
|
|||||||
|
|
||||||
// 【OuterFrame】插件
|
// 【OuterFrame】插件
|
||||||
outerFramePaddingX: 10,
|
outerFramePaddingX: 10,
|
||||||
outerFramePaddingY: 10
|
outerFramePaddingY: 10,
|
||||||
|
|
||||||
|
// 【Painter】插件
|
||||||
|
// 是否只格式刷节点手动设置的样式,不考虑节点通过主题的应用的样式
|
||||||
|
onlyPainterNodeCustomStyles: false,
|
||||||
|
|
||||||
|
// 【NodeImgAdjust】插件
|
||||||
|
// 拦截节点图片的删除,点击节点图片上的删除按钮删除图片前会调用该函数,如果函数返回true则取消删除
|
||||||
|
beforeDeleteNodeImg: null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,6 +99,7 @@ class Render {
|
|||||||
this.currentBeingPasteType = ''
|
this.currentBeingPasteType = ''
|
||||||
// 节点高亮框
|
// 节点高亮框
|
||||||
this.highlightBoxNode = null
|
this.highlightBoxNode = null
|
||||||
|
this.highlightBoxNodeStyle = null
|
||||||
// 上一次节点激活数据
|
// 上一次节点激活数据
|
||||||
this.lastActiveNode = null
|
this.lastActiveNode = null
|
||||||
this.lastActiveNodeList = []
|
this.lastActiveNodeList = []
|
||||||
@ -147,6 +148,19 @@ class Render {
|
|||||||
})
|
})
|
||||||
// 性能模式
|
// 性能模式
|
||||||
this.performanceMode()
|
this.performanceMode()
|
||||||
|
// 实时渲染当节点文本编辑时
|
||||||
|
if (this.mindMap.opt.openRealtimeRenderOnNodeTextEdit) {
|
||||||
|
this.mindMap.on('node_text_edit_change', ({ node, text }) => {
|
||||||
|
node._textData = node.createTextNode(text)
|
||||||
|
const { width, height } = node.getNodeRect()
|
||||||
|
node.width = width
|
||||||
|
node.height = height
|
||||||
|
node.layout()
|
||||||
|
this.mindMap.render(() => {
|
||||||
|
this.textEdit.updateTextEditNode()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 性能模式,懒加载节点
|
// 性能模式,懒加载节点
|
||||||
@ -1593,40 +1607,53 @@ class Render {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 展开所有
|
// 展开所有
|
||||||
expandAllNode() {
|
expandAllNode(uid = '') {
|
||||||
if (!this.renderTree) return
|
if (!this.renderTree) return
|
||||||
walk(
|
|
||||||
this.renderTree,
|
const _walk = (node, enableExpand) => {
|
||||||
null,
|
// 如果该节点为目标节点,那么修改允许展开的标志
|
||||||
node => {
|
if (!enableExpand && node.data.uid === uid) {
|
||||||
if (!node.data.expand) {
|
enableExpand = true
|
||||||
node.data.expand = true
|
}
|
||||||
}
|
if (enableExpand && !node.data.expand) {
|
||||||
},
|
node.data.expand = true
|
||||||
null,
|
}
|
||||||
true,
|
if (node.children && node.children.length > 0) {
|
||||||
0,
|
node.children.forEach(child => {
|
||||||
0
|
_walk(child, enableExpand)
|
||||||
)
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_walk(this.renderTree, !uid)
|
||||||
|
|
||||||
this.mindMap.render()
|
this.mindMap.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收起所有
|
// 收起所有
|
||||||
unexpandAllNode(isSetRootNodeCenter = true) {
|
unexpandAllNode(isSetRootNodeCenter = true, uid = '') {
|
||||||
if (!this.renderTree) return
|
if (!this.renderTree) return
|
||||||
walk(
|
|
||||||
this.renderTree,
|
const _walk = (node, isRoot, enableUnExpand) => {
|
||||||
null,
|
// 如果该节点为目标节点,那么修改允许展开的标志
|
||||||
(node, parent, isRoot) => {
|
if (!enableUnExpand && node.data.uid === uid) {
|
||||||
if (!isRoot && node.children && node.children.length > 0) {
|
enableUnExpand = true
|
||||||
node.data.expand = false
|
}
|
||||||
}
|
if (
|
||||||
},
|
enableUnExpand &&
|
||||||
null,
|
!isRoot &&
|
||||||
true,
|
node.children &&
|
||||||
0,
|
node.children.length > 0
|
||||||
0
|
) {
|
||||||
)
|
node.data.expand = false
|
||||||
|
}
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
node.children.forEach(child => {
|
||||||
|
_walk(child, false, enableUnExpand)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_walk(this.renderTree, true, !uid)
|
||||||
|
|
||||||
this.mindMap.render(() => {
|
this.mindMap.render(() => {
|
||||||
if (isSetRootNodeCenter) {
|
if (isSetRootNodeCenter) {
|
||||||
this.setRootNodeCenter()
|
this.setRootNodeCenter()
|
||||||
@ -2035,19 +2062,39 @@ class Render {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 高亮节点或子节点
|
// 高亮节点或子节点
|
||||||
highlightNode(node, range) {
|
highlightNode(node, range, style) {
|
||||||
// 如果当前正在渲染,那么不进行高亮,因为节点位置可能不正确
|
// 如果当前正在渲染,那么不进行高亮,因为节点位置可能不正确
|
||||||
if (this.isRendering) return
|
if (this.isRendering) return
|
||||||
const { highlightNodeBoxStyle = {} } = this.mindMap.opt
|
style = {
|
||||||
|
stroke: 'rgb(94, 200, 248)',
|
||||||
|
fill: 'transparent',
|
||||||
|
...(style || {})
|
||||||
|
}
|
||||||
|
// 尚未创建
|
||||||
if (!this.highlightBoxNode) {
|
if (!this.highlightBoxNode) {
|
||||||
this.highlightBoxNode = new Polygon()
|
this.highlightBoxNode = new Polygon()
|
||||||
.stroke({
|
.stroke({
|
||||||
color: highlightNodeBoxStyle.stroke || 'transparent'
|
color: style.stroke || 'transparent'
|
||||||
})
|
})
|
||||||
.fill({
|
.fill({
|
||||||
color: highlightNodeBoxStyle.fill || 'transparent'
|
color: style.fill || 'transparent'
|
||||||
})
|
})
|
||||||
|
} else if (this.highlightBoxNodeStyle) {
|
||||||
|
// 样式更新了
|
||||||
|
if (
|
||||||
|
this.highlightBoxNodeStyle.stroke !== style.stroke ||
|
||||||
|
this.highlightBoxNodeStyle.fill !== style.fill
|
||||||
|
) {
|
||||||
|
this.highlightBoxNode
|
||||||
|
.stroke({
|
||||||
|
color: style.stroke || 'transparent'
|
||||||
|
})
|
||||||
|
.fill({
|
||||||
|
color: style.fill || 'transparent'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this.highlightBoxNodeStyle = { ...style }
|
||||||
let minx = Infinity,
|
let minx = Infinity,
|
||||||
miny = Infinity,
|
miny = Infinity,
|
||||||
maxx = -Infinity,
|
maxx = -Infinity,
|
||||||
|
|||||||
@ -25,6 +25,8 @@ export default class TextEdit {
|
|||||||
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
|
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
|
||||||
this.cacheEditingText = ''
|
this.cacheEditingText = ''
|
||||||
this.hasBodyMousedown = false
|
this.hasBodyMousedown = false
|
||||||
|
this.textNodePaddingX = 5
|
||||||
|
this.textNodePaddingY = 3
|
||||||
this.bindEvent()
|
this.bindEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +216,7 @@ export default class TextEdit {
|
|||||||
this.registerTmpShortcut()
|
this.registerTmpShortcut()
|
||||||
if (!this.textEditNode) {
|
if (!this.textEditNode) {
|
||||||
this.textEditNode = document.createElement('div')
|
this.textEditNode = document.createElement('div')
|
||||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
|
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
|
||||||
this.textEditNode.setAttribute('contenteditable', true)
|
this.textEditNode.setAttribute('contenteditable', true)
|
||||||
this.textEditNode.addEventListener('keyup', e => {
|
this.textEditNode.addEventListener('keyup', e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@ -240,6 +242,13 @@ export default class TextEdit {
|
|||||||
handleInputPasteText(e)
|
handleInputPasteText(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
this.textEditNode.addEventListener('input', () => {
|
||||||
|
this.mindMap.emit('node_text_edit_change', {
|
||||||
|
node: this.currentNode,
|
||||||
|
text: this.getEditText(),
|
||||||
|
richText: false
|
||||||
|
})
|
||||||
|
})
|
||||||
const targetNode =
|
const targetNode =
|
||||||
this.mindMap.opt.customInnerElsAppendTo || document.body
|
this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||||
targetNode.appendChild(this.textEditNode)
|
targetNode.appendChild(this.textEditNode)
|
||||||
@ -256,8 +265,10 @@ export default class TextEdit {
|
|||||||
node.style.domText(this.textEditNode, scale, isMultiLine)
|
node.style.domText(this.textEditNode, scale, isMultiLine)
|
||||||
this.textEditNode.style.zIndex = nodeTextEditZIndex
|
this.textEditNode.style.zIndex = nodeTextEditZIndex
|
||||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||||
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
|
this.textEditNode.style.minWidth =
|
||||||
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
|
rect.width + this.textNodePaddingX * 2 + 'px'
|
||||||
|
this.textEditNode.style.minHeight =
|
||||||
|
rect.height + this.textNodePaddingY * 2 + 'px'
|
||||||
this.textEditNode.style.left = rect.left + 'px'
|
this.textEditNode.style.left = rect.left + 'px'
|
||||||
this.textEditNode.style.top = rect.top + 'px'
|
this.textEditNode.style.top = rect.top + 'px'
|
||||||
this.textEditNode.style.display = 'block'
|
this.textEditNode.style.display = 'block'
|
||||||
@ -280,6 +291,24 @@ export default class TextEdit {
|
|||||||
this.cacheEditingText = ''
|
this.cacheEditingText = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新文本编辑框的大小和位置
|
||||||
|
updateTextEditNode() {
|
||||||
|
if (this.mindMap.richText) {
|
||||||
|
this.mindMap.richText.updateTextEditNode()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.showTextEdit || !this.currentNode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const rect = this.currentNode._textData.node.node.getBoundingClientRect()
|
||||||
|
this.textEditNode.style.minWidth =
|
||||||
|
rect.width + this.textNodePaddingX * 2 + 'px'
|
||||||
|
this.textEditNode.style.minHeight =
|
||||||
|
rect.height + this.textNodePaddingY * 2 + 'px'
|
||||||
|
this.textEditNode.style.left = rect.left + 'px'
|
||||||
|
this.textEditNode.style.top = rect.top + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
// 删除文本编辑元素
|
// 删除文本编辑元素
|
||||||
removeTextEditEl() {
|
removeTextEditEl() {
|
||||||
if (this.mindMap.richText) {
|
if (this.mindMap.richText) {
|
||||||
@ -313,12 +342,7 @@ export default class TextEdit {
|
|||||||
}
|
}
|
||||||
this.mindMap.render()
|
this.mindMap.render()
|
||||||
})
|
})
|
||||||
this.mindMap.emit(
|
const currentNode = this.currentNode
|
||||||
'hide_text_edit',
|
|
||||||
this.textEditNode,
|
|
||||||
this.renderer.activeNodeList,
|
|
||||||
this.currentNode
|
|
||||||
)
|
|
||||||
this.currentNode = null
|
this.currentNode = null
|
||||||
this.textEditNode.style.display = 'none'
|
this.textEditNode.style.display = 'none'
|
||||||
this.textEditNode.innerHTML = ''
|
this.textEditNode.innerHTML = ''
|
||||||
@ -327,6 +351,12 @@ export default class TextEdit {
|
|||||||
this.textEditNode.style.fontWeight = 'normal'
|
this.textEditNode.style.fontWeight = 'normal'
|
||||||
this.textEditNode.style.transform = 'translateY(0)'
|
this.textEditNode.style.transform = 'translateY(0)'
|
||||||
this.showTextEdit = false
|
this.showTextEdit = false
|
||||||
|
this.mindMap.emit(
|
||||||
|
'hide_text_edit',
|
||||||
|
this.textEditNode,
|
||||||
|
this.renderer.activeNodeList,
|
||||||
|
currentNode
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前正在编辑中的节点实例
|
// 获取当前正在编辑中的节点实例
|
||||||
|
|||||||
@ -34,6 +34,8 @@ class MindMapNode {
|
|||||||
this.lineDraw = this.mindMap.lineDraw
|
this.lineDraw = this.mindMap.lineDraw
|
||||||
// 样式实例
|
// 样式实例
|
||||||
this.style = new Style(this)
|
this.style = new Style(this)
|
||||||
|
// 节点当前生效的全部样式
|
||||||
|
this.effectiveStyles = {}
|
||||||
// 形状实例
|
// 形状实例
|
||||||
this.shapeInstance = new Shape(this)
|
this.shapeInstance = new Shape(this)
|
||||||
this.shapePadding = {
|
this.shapePadding = {
|
||||||
|
|||||||
@ -64,8 +64,10 @@ class Style {
|
|||||||
let themeConfig = this.ctx.mindMap.themeConfig
|
let themeConfig = this.ctx.mindMap.themeConfig
|
||||||
// 三级及以下节点
|
// 三级及以下节点
|
||||||
let defaultConfig = themeConfig.node
|
let defaultConfig = themeConfig.node
|
||||||
|
let useRoot = false
|
||||||
if (root || rootProp.includes(prop)) {
|
if (root || rootProp.includes(prop)) {
|
||||||
// 直接使用最外层样式
|
// 直接使用最外层样式
|
||||||
|
useRoot = true
|
||||||
defaultConfig = themeConfig
|
defaultConfig = themeConfig
|
||||||
} else if (this.ctx.isGeneralization) {
|
} else if (this.ctx.isGeneralization) {
|
||||||
// 概要节点
|
// 概要节点
|
||||||
@ -78,9 +80,16 @@ class Style {
|
|||||||
defaultConfig = themeConfig.second
|
defaultConfig = themeConfig.second
|
||||||
}
|
}
|
||||||
// 优先使用节点本身的样式
|
// 优先使用节点本身的样式
|
||||||
return this.getSelfStyle(prop) !== undefined
|
const value =
|
||||||
? this.getSelfStyle(prop)
|
this.getSelfStyle(prop) !== undefined
|
||||||
: defaultConfig[prop]
|
? this.getSelfStyle(prop)
|
||||||
|
: defaultConfig[prop]
|
||||||
|
if (!useRoot) {
|
||||||
|
this.addToEffectiveStyles({
|
||||||
|
[prop]: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取某个样式值
|
// 获取某个样式值
|
||||||
@ -93,6 +102,14 @@ class Style {
|
|||||||
return this.ctx.getData(prop)
|
return this.ctx.getData(prop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新当前节点生效的样式数据
|
||||||
|
addToEffectiveStyles(styles) {
|
||||||
|
this.ctx.effectiveStyles = {
|
||||||
|
...this.ctx.effectiveStyles,
|
||||||
|
...styles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 矩形
|
// 矩形
|
||||||
rect(node) {
|
rect(node) {
|
||||||
this.shape(node)
|
this.shape(node)
|
||||||
@ -101,18 +118,30 @@ class Style {
|
|||||||
|
|
||||||
// 形状
|
// 形状
|
||||||
shape(node) {
|
shape(node) {
|
||||||
if (this.merge('gradientStyle')) {
|
const styles = {
|
||||||
|
gradientStyle: this.merge('gradientStyle'),
|
||||||
|
startColor: this.merge('startColor'),
|
||||||
|
endColor: this.merge('endColor'),
|
||||||
|
startDir: this.merge('startDir'),
|
||||||
|
endDir: this.merge('endDir'),
|
||||||
|
fillColor: this.merge('fillColor'),
|
||||||
|
borderColor: this.merge('borderColor'),
|
||||||
|
borderWidth: this.merge('borderWidth'),
|
||||||
|
borderDasharray: this.merge('borderDasharray')
|
||||||
|
}
|
||||||
|
if (styles.gradientStyle) {
|
||||||
if (!this._gradient) {
|
if (!this._gradient) {
|
||||||
this._gradient = this.ctx.nodeDraw.gradient('linear')
|
this._gradient = this.ctx.nodeDraw.gradient('linear')
|
||||||
}
|
}
|
||||||
this._gradient.update(add => {
|
this._gradient.update(add => {
|
||||||
add.stop(0, this.merge('startColor'))
|
add.stop(0, styles.startColor)
|
||||||
add.stop(1, this.merge('endColor'))
|
add.stop(1, styles.endColor)
|
||||||
})
|
})
|
||||||
|
this._gradient.from(...styles.startDir).to(...styles.endDir)
|
||||||
node.fill(this._gradient)
|
node.fill(this._gradient)
|
||||||
} else {
|
} else {
|
||||||
node.fill({
|
node.fill({
|
||||||
color: this.merge('fillColor')
|
color: styles.fillColor
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 节点使用横线样式,不需要渲染非激活状态的边框样式
|
// 节点使用横线样式,不需要渲染非激活状态的边框样式
|
||||||
@ -125,56 +154,89 @@ class Style {
|
|||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
node.stroke({
|
node.stroke({
|
||||||
color: this.merge('borderColor'),
|
color: styles.borderColor,
|
||||||
width: this.merge('borderWidth'),
|
width: styles.borderWidth,
|
||||||
dasharray: this.merge('borderDasharray')
|
dasharray: styles.borderDasharray
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文字
|
// 文字
|
||||||
text(node) {
|
text(node) {
|
||||||
|
const styles = {
|
||||||
|
color: this.merge('color'),
|
||||||
|
fontFamily: this.merge('fontFamily'),
|
||||||
|
fontSize: this.merge('fontSize'),
|
||||||
|
fontWeight: this.merge('fontWeight'),
|
||||||
|
fontStyle: this.merge('fontStyle'),
|
||||||
|
textDecoration: this.merge('textDecoration')
|
||||||
|
}
|
||||||
node
|
node
|
||||||
.fill({
|
.fill({
|
||||||
color: this.merge('color')
|
color: styles.color
|
||||||
})
|
})
|
||||||
.css({
|
.css({
|
||||||
'font-family': this.merge('fontFamily'),
|
'font-family': styles.fontFamily,
|
||||||
'font-size': this.merge('fontSize'),
|
'font-size': styles.fontSize,
|
||||||
'font-weight': this.merge('fontWeight'),
|
'font-weight': styles.fontWeight,
|
||||||
'font-style': this.merge('fontStyle'),
|
'font-style': styles.fontStyle,
|
||||||
'text-decoration': this.merge('textDecoration')
|
'text-decoration': styles.textDecoration
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成内联样式
|
// 生成内联样式
|
||||||
createStyleText() {
|
createStyleText() {
|
||||||
|
const styles = {
|
||||||
|
color: this.merge('color'),
|
||||||
|
fontFamily: this.merge('fontFamily'),
|
||||||
|
fontSize: this.merge('fontSize'),
|
||||||
|
fontWeight: this.merge('fontWeight'),
|
||||||
|
fontStyle: this.merge('fontStyle'),
|
||||||
|
textDecoration: this.merge('textDecoration')
|
||||||
|
}
|
||||||
return `
|
return `
|
||||||
color: ${this.merge('color')};
|
color: ${styles.color};
|
||||||
font-family: ${this.merge('fontFamily')};
|
font-family: ${styles.fontFamily};
|
||||||
font-size: ${this.merge('fontSize') + 'px'};
|
font-size: ${styles.fontSize + 'px'};
|
||||||
font-weight: ${this.merge('fontWeight')};
|
font-weight: ${styles.fontWeight};
|
||||||
font-style: ${this.merge('fontStyle')};
|
font-style: ${styles.fontStyle};
|
||||||
text-decoration: ${this.merge('textDecoration')}
|
text-decoration: ${styles.textDecoration}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文本样式
|
// 获取文本样式
|
||||||
getTextFontStyle() {
|
getTextFontStyle() {
|
||||||
return {
|
const styles = {
|
||||||
italic: this.merge('fontStyle') === 'italic',
|
color: this.merge('color'),
|
||||||
bold: this.merge('fontWeight'),
|
fontFamily: this.merge('fontFamily'),
|
||||||
fontSize: this.merge('fontSize'),
|
fontSize: this.merge('fontSize'),
|
||||||
fontFamily: this.merge('fontFamily')
|
fontWeight: this.merge('fontWeight'),
|
||||||
|
fontStyle: this.merge('fontStyle'),
|
||||||
|
textDecoration: this.merge('textDecoration')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
italic: styles.fontStyle === 'italic',
|
||||||
|
bold: styles.fontWeight,
|
||||||
|
fontSize: styles.fontSize,
|
||||||
|
fontFamily: styles.fontFamily
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// html文字节点
|
// html文字节点
|
||||||
domText(node, fontSizeScale = 1, isMultiLine) {
|
domText(node, fontSizeScale = 1, isMultiLine) {
|
||||||
node.style.fontFamily = this.merge('fontFamily')
|
const styles = {
|
||||||
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
|
color: this.merge('color'),
|
||||||
node.style.fontWeight = this.merge('fontWeight') || 'normal'
|
fontFamily: this.merge('fontFamily'),
|
||||||
node.style.lineHeight = !isMultiLine ? 'normal' : this.merge('lineHeight')
|
fontSize: this.merge('fontSize'),
|
||||||
node.style.fontStyle = this.merge('fontStyle')
|
fontWeight: this.merge('fontWeight'),
|
||||||
|
fontStyle: this.merge('fontStyle'),
|
||||||
|
textDecoration: this.merge('textDecoration'),
|
||||||
|
lineHeight: this.merge('lineHeight')
|
||||||
|
}
|
||||||
|
node.style.fontFamily = styles.fontFamily
|
||||||
|
node.style.fontSize = styles.fontSize * fontSizeScale + 'px'
|
||||||
|
node.style.fontWeight = styles.fontWeight || 'normal'
|
||||||
|
node.style.lineHeight = !isMultiLine ? 'normal' : styles.lineHeight
|
||||||
|
node.style.fontStyle = styles.fontStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标签文字
|
// 标签文字
|
||||||
@ -286,7 +348,7 @@ class Style {
|
|||||||
|
|
||||||
// hover和激活节点
|
// hover和激活节点
|
||||||
hoverNode(node) {
|
hoverNode(node) {
|
||||||
const { hoverRectColor } = this.ctx.mindMap.opt
|
const hoverRectColor = this.merge('hoverRectColor') || this.ctx.mindMap.opt.hoverRectColor
|
||||||
node.radius(5).fill('none').stroke({
|
node.radius(5).fill('none').stroke({
|
||||||
color: hoverRectColor
|
color: hoverRectColor
|
||||||
})
|
})
|
||||||
|
|||||||
@ -114,8 +114,10 @@ function createIconNode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建富文本节点
|
// 创建富文本节点
|
||||||
function createRichTextNode() {
|
function createRichTextNode(specifyText) {
|
||||||
const { textAutoWrapWidth } = this.mindMap.opt
|
let text =
|
||||||
|
typeof specifyText === 'string' ? specifyText : this.getData('text')
|
||||||
|
const { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt
|
||||||
let g = new G()
|
let g = new G()
|
||||||
// 重新设置富文本节点内容
|
// 重新设置富文本节点内容
|
||||||
let recoverText = false
|
let recoverText = false
|
||||||
@ -129,7 +131,6 @@ function createRichTextNode() {
|
|||||||
recoverText = true
|
recoverText = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let text = this.getData('text')
|
|
||||||
if (recoverText && !isUndef(text)) {
|
if (recoverText && !isUndef(text)) {
|
||||||
// 判断节点内容是否是富文本
|
// 判断节点内容是否是富文本
|
||||||
let isRichText = checkIsRichText(text)
|
let isRichText = checkIsRichText(text)
|
||||||
@ -153,7 +154,7 @@ function createRichTextNode() {
|
|||||||
text: text
|
text: text
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
let html = `<div>${this.getData('text')}</div>`
|
let html = `<div>${text}</div>`
|
||||||
if (!this.mindMap.commonCaches.measureRichtextNodeTextSizeEl) {
|
if (!this.mindMap.commonCaches.measureRichtextNodeTextSizeEl) {
|
||||||
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl =
|
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl =
|
||||||
document.createElement('div')
|
document.createElement('div')
|
||||||
@ -174,7 +175,7 @@ function createRichTextNode() {
|
|||||||
let { width, height } = el.getBoundingClientRect()
|
let { width, height } = el.getBoundingClientRect()
|
||||||
// 如果文本为空,那么需要计算一个默认高度
|
// 如果文本为空,那么需要计算一个默认高度
|
||||||
if (height <= 0) {
|
if (height <= 0) {
|
||||||
div.innerHTML = '<p>abc123我和你</p>'
|
div.innerHTML = `<p>${emptyTextMeasureHeightText}</p>`
|
||||||
let elTmp = div.children[0]
|
let elTmp = div.children[0]
|
||||||
elTmp.classList.add('smm-richtext-node-wrap')
|
elTmp.classList.add('smm-richtext-node-wrap')
|
||||||
height = elTmp.getBoundingClientRect().height
|
height = elTmp.getBoundingClientRect().height
|
||||||
@ -199,10 +200,12 @@ function createRichTextNode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建文本节点
|
// 创建文本节点
|
||||||
function createTextNode() {
|
function createTextNode(specifyText) {
|
||||||
if (this.getData('richText')) {
|
if (this.getData('richText')) {
|
||||||
return this.createRichTextNode()
|
return this.createRichTextNode(specifyText)
|
||||||
}
|
}
|
||||||
|
const text =
|
||||||
|
typeof specifyText === 'string' ? specifyText : this.getData('text')
|
||||||
if (this.getData('resetRichText')) {
|
if (this.getData('resetRichText')) {
|
||||||
delete this.nodeData.data.resetRichText
|
delete this.nodeData.data.resetRichText
|
||||||
}
|
}
|
||||||
@ -212,10 +215,11 @@ function createTextNode() {
|
|||||||
// 文本超长自动换行
|
// 文本超长自动换行
|
||||||
let textStyle = this.style.getTextFontStyle()
|
let textStyle = this.style.getTextFontStyle()
|
||||||
let textArr = []
|
let textArr = []
|
||||||
if (!isUndef(this.getData('text'))) {
|
if (!isUndef(text)) {
|
||||||
textArr = String(this.getData('text')).split(/\n/gim)
|
textArr = String(text).split(/\n/gim)
|
||||||
}
|
}
|
||||||
let maxWidth = this.mindMap.opt.textAutoWrapWidth
|
const { textAutoWrapWidth: maxWidth, emptyTextMeasureHeightText } =
|
||||||
|
this.mindMap.opt
|
||||||
let isMultiLine = false
|
let isMultiLine = false
|
||||||
textArr.forEach((item, index) => {
|
textArr.forEach((item, index) => {
|
||||||
let arr = item.split('')
|
let arr = item.split('')
|
||||||
@ -247,6 +251,13 @@ function createTextNode() {
|
|||||||
g.add(node)
|
g.add(node)
|
||||||
})
|
})
|
||||||
let { width, height } = g.bbox()
|
let { width, height } = g.bbox()
|
||||||
|
// 如果文本为空,那么需要计算一个默认高度
|
||||||
|
if (height <= 0) {
|
||||||
|
const tmpNode = new Text().text(emptyTextMeasureHeightText)
|
||||||
|
this.style.text(tmpNode)
|
||||||
|
const tmpBbox = tmpNode.bbox()
|
||||||
|
height = tmpBbox.height
|
||||||
|
}
|
||||||
width = Math.min(Math.ceil(width), maxWidth)
|
width = Math.min(Math.ceil(width), maxWidth)
|
||||||
height = Math.ceil(height)
|
height = Math.ceil(height)
|
||||||
g.attr('data-width', width)
|
g.attr('data-width', width)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import btnsSvg from '../../../svg/btns'
|
import btnsSvg from '../../../svg/btns'
|
||||||
import { SVG, Circle, G, Text } from '@svgdotjs/svg.js'
|
import { SVG, Circle, G, Text } from '@svgdotjs/svg.js'
|
||||||
|
import { isUndef } from '../../../utils'
|
||||||
|
|
||||||
// 创建展开收起按钮的内容节点
|
// 创建展开收起按钮的内容节点
|
||||||
function createExpandNodeContent() {
|
function createExpandNodeContent() {
|
||||||
@ -78,7 +79,12 @@ function updateExpandBtnNode() {
|
|||||||
})
|
})
|
||||||
// 计算子节点数量
|
// 计算子节点数量
|
||||||
let count = this.sumNode(this.nodeData.children)
|
let count = this.sumNode(this.nodeData.children)
|
||||||
count = expandBtnNumHandler(count)
|
if (typeof expandBtnNumHandler === 'function') {
|
||||||
|
const res = expandBtnNumHandler(count, this)
|
||||||
|
if (!isUndef(res)) {
|
||||||
|
count = res
|
||||||
|
}
|
||||||
|
}
|
||||||
node.text(String(count))
|
node.text(String(count))
|
||||||
} else {
|
} else {
|
||||||
this._fillExpandNode.stroke('none')
|
this._fillExpandNode.stroke('none')
|
||||||
|
|||||||
@ -186,15 +186,29 @@ function handleGeneralizationMouseenter() {
|
|||||||
const list = belongNode.formatGetGeneralization()
|
const list = belongNode.formatGetGeneralization()
|
||||||
const index = belongNode.getGeneralizationNodeIndex(this)
|
const index = belongNode.getGeneralizationNodeIndex(this)
|
||||||
const generalizationData = list[index]
|
const generalizationData = list[index]
|
||||||
|
// 如果主题中设置了hoverRectColor颜色,那么使用该颜色
|
||||||
|
// 否则使用hoverRectColor实例化选项的颜色
|
||||||
|
// 兜底使用highlightNode方法的默认颜色
|
||||||
|
const hoverRectColor = this.getStyle('hoverRectColor')
|
||||||
|
const color = hoverRectColor || this.mindMap.opt.hoverRectColor
|
||||||
|
const style = color
|
||||||
|
? {
|
||||||
|
stroke: color
|
||||||
|
}
|
||||||
|
: null
|
||||||
// 区间概要,框子节点
|
// 区间概要,框子节点
|
||||||
if (
|
if (
|
||||||
Array.isArray(generalizationData.range) &&
|
Array.isArray(generalizationData.range) &&
|
||||||
generalizationData.range.length > 0
|
generalizationData.range.length > 0
|
||||||
) {
|
) {
|
||||||
this.mindMap.renderer.highlightNode(belongNode, generalizationData.range)
|
this.mindMap.renderer.highlightNode(
|
||||||
|
belongNode,
|
||||||
|
generalizationData.range,
|
||||||
|
style
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// 否则框自己
|
// 否则框自己
|
||||||
this.mindMap.renderer.highlightNode(belongNode)
|
this.mindMap.renderer.highlightNode(belongNode, null, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -128,8 +128,9 @@ class Base {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本等情况需要重新计算节点大小和布局
|
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本等情况需要重新计算节点大小和布局
|
||||||
|
const isNeedResizeSources = this.checkIsNeedResizeSources()
|
||||||
if (
|
if (
|
||||||
this.checkIsNeedResizeSources() ||
|
isNeedResizeSources ||
|
||||||
isLayerTypeChange ||
|
isLayerTypeChange ||
|
||||||
newNode.getData('resetRichText') ||
|
newNode.getData('resetRichText') ||
|
||||||
isNumberChange
|
isNumberChange
|
||||||
@ -137,7 +138,7 @@ class Base {
|
|||||||
newNode.getSize()
|
newNode.getSize()
|
||||||
newNode.needLayout = true
|
newNode.needLayout = true
|
||||||
}
|
}
|
||||||
this.checkGetGeneralizationChange(newNode)
|
this.checkGetGeneralizationChange(newNode, isNeedResizeSources)
|
||||||
} else if (
|
} else if (
|
||||||
(this.lru.has(uid) || this.renderer.lastNodeCache[uid]) &&
|
(this.lru.has(uid) || this.renderer.lastNodeCache[uid]) &&
|
||||||
!this.renderer.reRender
|
!this.renderer.reRender
|
||||||
@ -186,7 +187,7 @@ class Base {
|
|||||||
newNode.getSize()
|
newNode.getSize()
|
||||||
newNode.needLayout = true
|
newNode.needLayout = true
|
||||||
}
|
}
|
||||||
this.checkGetGeneralizationChange(newNode)
|
this.checkGetGeneralizationChange(newNode, isResizeSource)
|
||||||
} else {
|
} else {
|
||||||
// 创建新节点
|
// 创建新节点
|
||||||
const newUid = uid || createUid()
|
const newUid = uid || createUid()
|
||||||
@ -228,7 +229,7 @@ class Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查概要节点是否需要更新
|
// 检查概要节点是否需要更新
|
||||||
checkGetGeneralizationChange(node) {
|
checkGetGeneralizationChange(node, isResizeSource) {
|
||||||
const generalizationList = node.getData('generalization')
|
const generalizationList = node.getData('generalization')
|
||||||
if (
|
if (
|
||||||
generalizationList &&
|
generalizationList &&
|
||||||
@ -239,7 +240,10 @@ class Base {
|
|||||||
const gNode = item.generalizationNode
|
const gNode = item.generalizationNode
|
||||||
const oldData = gNode.getData()
|
const oldData = gNode.getData()
|
||||||
const newData = generalizationList[index]
|
const newData = generalizationList[index]
|
||||||
if (newData && JSON.stringify(oldData) !== JSON.stringify(newData)) {
|
if (
|
||||||
|
isResizeSource ||
|
||||||
|
(newData && JSON.stringify(oldData) !== JSON.stringify(newData))
|
||||||
|
) {
|
||||||
gNode.nodeData.data = newData
|
gNode.nodeData.data = newData
|
||||||
gNode.getSize()
|
gNode.getSize()
|
||||||
gNode.needLayout = true
|
gNode.needLayout = true
|
||||||
@ -371,18 +375,32 @@ class Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 二次贝塞尔曲线
|
// 二次贝塞尔曲线
|
||||||
quadraticCurvePath(x1, y1, x2, y2) {
|
quadraticCurvePath(x1, y1, x2, y2, v = false) {
|
||||||
let cx = x1 + (x2 - x1) * 0.2
|
let cx, cy
|
||||||
let cy = y1 + (y2 - y1) * 0.8
|
if (v) {
|
||||||
|
cx = x1 + (x2 - x1) * 0.8
|
||||||
|
cy = y1 + (y2 - y1) * 0.2
|
||||||
|
} else {
|
||||||
|
cx = x1 + (x2 - x1) * 0.2
|
||||||
|
cy = y1 + (y2 - y1) * 0.8
|
||||||
|
}
|
||||||
return `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
return `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 三次贝塞尔曲线
|
// 三次贝塞尔曲线
|
||||||
cubicBezierPath(x1, y1, x2, y2) {
|
cubicBezierPath(x1, y1, x2, y2, v = false) {
|
||||||
let cx1 = x1 + (x2 - x1) / 2
|
let cx1, cy1, cx2, cy2
|
||||||
let cy1 = y1
|
if (v) {
|
||||||
let cx2 = cx1
|
cx1 = x1
|
||||||
let cy2 = y2
|
cy1 = y1 + (y2 - y1) / 2
|
||||||
|
cx2 = x2
|
||||||
|
cy2 = cy1
|
||||||
|
} else {
|
||||||
|
cx1 = x1 + (x2 - x1) / 2
|
||||||
|
cy1 = y1
|
||||||
|
cx2 = cx1
|
||||||
|
cy2 = y2
|
||||||
|
}
|
||||||
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
|
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,14 @@ class OrganizationStructure extends Base {
|
|||||||
this.renderer.renderTree,
|
this.renderer.renderTree,
|
||||||
null,
|
null,
|
||||||
(cur, parent, isRoot, layerIndex, index, ancestors) => {
|
(cur, parent, isRoot, layerIndex, index, ancestors) => {
|
||||||
let newNode = this.createNode(cur, parent, isRoot, layerIndex, index, ancestors)
|
let newNode = this.createNode(
|
||||||
|
cur,
|
||||||
|
parent,
|
||||||
|
isRoot,
|
||||||
|
layerIndex,
|
||||||
|
index,
|
||||||
|
ancestors
|
||||||
|
)
|
||||||
// 根节点定位在画布中心位置
|
// 根节点定位在画布中心位置
|
||||||
if (isRoot) {
|
if (isRoot) {
|
||||||
this.setNodeCenter(newNode)
|
this.setNodeCenter(newNode)
|
||||||
@ -148,13 +155,56 @@ class OrganizationStructure extends Base {
|
|||||||
|
|
||||||
// 绘制连线,连接该节点到其子节点
|
// 绘制连线,连接该节点到其子节点
|
||||||
renderLine(node, lines, style, lineStyle) {
|
renderLine(node, lines, style, lineStyle) {
|
||||||
if (lineStyle === 'direct') {
|
if (lineStyle === 'curve') {
|
||||||
|
this.renderLineCurve(node, lines, style)
|
||||||
|
} else if (lineStyle === 'direct') {
|
||||||
this.renderLineDirect(node, lines, style)
|
this.renderLineDirect(node, lines, style)
|
||||||
} else {
|
} else {
|
||||||
this.renderLineStraight(node, lines, style)
|
this.renderLineStraight(node, lines, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 曲线风格连线
|
||||||
|
renderLineCurve(node, lines, style) {
|
||||||
|
if (node.children.length <= 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let { left, top, width, height, expandBtnSize } = node
|
||||||
|
const { alwaysShowExpandBtn, notShowExpandBtn } = this.mindMap.opt
|
||||||
|
if (!alwaysShowExpandBtn || notShowExpandBtn) {
|
||||||
|
expandBtnSize = 0
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
nodeUseLineStyle,
|
||||||
|
rootLineStartPositionKeepSameInCurve,
|
||||||
|
rootLineKeepSameInCurve
|
||||||
|
} = this.mindMap.themeConfig
|
||||||
|
node.children.forEach((item, index) => {
|
||||||
|
if (node.layerIndex === 0) {
|
||||||
|
expandBtnSize = 0
|
||||||
|
}
|
||||||
|
let x1 = left + width / 2
|
||||||
|
let y1 =
|
||||||
|
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
|
||||||
|
? top + height / 2
|
||||||
|
: top + height + expandBtnSize
|
||||||
|
let x2 = item.left + item.width / 2
|
||||||
|
let y2 = item.top
|
||||||
|
let path = ''
|
||||||
|
// 节点使用横线风格,需要额外渲染横线
|
||||||
|
let nodeUseLineStylePath = nodeUseLineStyle
|
||||||
|
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
|
||||||
|
: ''
|
||||||
|
if (node.isRoot && !rootLineKeepSameInCurve) {
|
||||||
|
path =
|
||||||
|
this.quadraticCurvePath(x1, y1, x2, y2, true) + nodeUseLineStylePath
|
||||||
|
} else {
|
||||||
|
path = this.cubicBezierPath(x1, y1, x2, y2, true) + nodeUseLineStylePath
|
||||||
|
}
|
||||||
|
this.setLineStyle(style, lines[index], path, item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 直连风格
|
// 直连风格
|
||||||
renderLineDirect(node, lines, style) {
|
renderLineDirect(node, lines, style) {
|
||||||
if (node.children.length <= 0) {
|
if (node.children.length <= 0) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import katex from 'katex'
|
import katex from 'katex'
|
||||||
import Quill from 'quill'
|
import Quill from 'quill'
|
||||||
import { getChromeVersion } from '../utils/index'
|
import { getChromeVersion, htmlEscape } from '../utils/index'
|
||||||
import { getBaseStyleText, getFontStyleText } from './FormulaStyle'
|
import { getBaseStyleText, getFontStyleText } from './FormulaStyle'
|
||||||
|
|
||||||
// 数学公式支持插件
|
// 数学公式支持插件
|
||||||
@ -58,7 +58,7 @@ class Formula {
|
|||||||
let node = super.create(value)
|
let node = super.create(value)
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
katex.render(value, node, self.config)
|
katex.render(value, node, self.config)
|
||||||
node.setAttribute('data-value', value)
|
node.setAttribute('data-value', htmlEscape(value))
|
||||||
}
|
}
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
@ -110,11 +110,7 @@ class Formula {
|
|||||||
for (const el of els)
|
for (const el of els)
|
||||||
nodeText = nodeText.replace(
|
nodeText = nodeText.replace(
|
||||||
el.outerHTML,
|
el.outerHTML,
|
||||||
`\$${el
|
`\$${el.getAttribute('data-value')}\$`
|
||||||
.getAttribute('data-value')
|
|
||||||
.replaceAll('&', '&')
|
|
||||||
.replaceAll('<', '<')
|
|
||||||
.replaceAll('>', '>')}\$`
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return nodeText
|
return nodeText
|
||||||
|
|||||||
@ -192,8 +192,14 @@ class NodeImgAdjust {
|
|||||||
if (this.isMousedown) return
|
if (this.isMousedown) return
|
||||||
this.hideHandleEl()
|
this.hideHandleEl()
|
||||||
})
|
})
|
||||||
btnRemove.addEventListener('click', e => {
|
btnRemove.addEventListener('click', async e => {
|
||||||
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, { url: null })
|
let stop = false
|
||||||
|
if (typeof this.mindMap.opt.beforeDeleteNodeImg === 'function') {
|
||||||
|
stop = await this.mindMap.opt.beforeDeleteNodeImg(this.node)
|
||||||
|
}
|
||||||
|
if (!stop) {
|
||||||
|
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, { url: null })
|
||||||
|
}
|
||||||
})
|
})
|
||||||
// 添加元素到页面
|
// 添加元素到页面
|
||||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||||
|
|||||||
@ -53,17 +53,22 @@ class Painter {
|
|||||||
node.uid === this.painterNode.uid
|
node.uid === this.painterNode.uid
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
const style = {}
|
let style = {}
|
||||||
|
// 格式刷节点所有生效的样式
|
||||||
|
if (!this.mindMap.opt.onlyPainterNodeCustomStyles) {
|
||||||
|
style = {
|
||||||
|
...this.painterNode.effectiveStyles
|
||||||
|
}
|
||||||
|
}
|
||||||
const painterNodeData = this.painterNode.getData()
|
const painterNodeData = this.painterNode.getData()
|
||||||
Object.keys(painterNodeData).forEach(key => {
|
Object.keys(painterNodeData).forEach(key => {
|
||||||
if (checkIsNodeStyleDataKey(key)) {
|
if (checkIsNodeStyleDataKey(key)) {
|
||||||
style[key] = painterNodeData[key]
|
style[key] = painterNodeData[key]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// 先去除目标节点的样式
|
||||||
|
this.mindMap.renderer._handleRemoveCustomStyles(node.getData())
|
||||||
node.setStyles(style)
|
node.setStyles(style)
|
||||||
if (painterNodeData.activeStyle) {
|
|
||||||
node.setStyles(painterNodeData.activeStyle, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 插件被移除前做的事情
|
// 插件被移除前做的事情
|
||||||
|
|||||||
@ -57,6 +57,8 @@ class RichText {
|
|||||||
this.cacheEditingText = ''
|
this.cacheEditingText = ''
|
||||||
this.lostStyle = false
|
this.lostStyle = false
|
||||||
this.isCompositing = false
|
this.isCompositing = false
|
||||||
|
this.textNodePaddingX = 6
|
||||||
|
this.textNodePaddingY = 4
|
||||||
this.initOpt()
|
this.initOpt()
|
||||||
this.extendQuill()
|
this.extendQuill()
|
||||||
this.appendCss()
|
this.appendCss()
|
||||||
@ -71,14 +73,17 @@ class RichText {
|
|||||||
// 绑定事件
|
// 绑定事件
|
||||||
bindEvent() {
|
bindEvent() {
|
||||||
this.onCompositionStart = this.onCompositionStart.bind(this)
|
this.onCompositionStart = this.onCompositionStart.bind(this)
|
||||||
|
this.onCompositionUpdate = this.onCompositionUpdate.bind(this)
|
||||||
this.onCompositionEnd = this.onCompositionEnd.bind(this)
|
this.onCompositionEnd = this.onCompositionEnd.bind(this)
|
||||||
window.addEventListener('compositionstart', this.onCompositionStart)
|
window.addEventListener('compositionstart', this.onCompositionStart)
|
||||||
|
window.addEventListener('compositionupdate', this.onCompositionUpdate)
|
||||||
window.addEventListener('compositionend', this.onCompositionEnd)
|
window.addEventListener('compositionend', this.onCompositionEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解绑事件
|
// 解绑事件
|
||||||
unbindEvent() {
|
unbindEvent() {
|
||||||
window.removeEventListener('compositionstart', this.onCompositionStart)
|
window.removeEventListener('compositionstart', this.onCompositionStart)
|
||||||
|
window.removeEventListener('compositionupdate', this.onCompositionUpdate)
|
||||||
window.removeEventListener('compositionend', this.onCompositionEnd)
|
window.removeEventListener('compositionend', this.onCompositionEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,8 +203,8 @@ class RichText {
|
|||||||
let scaleX = rect.width / originWidth
|
let scaleX = rect.width / originWidth
|
||||||
let scaleY = rect.height / originHeight
|
let scaleY = rect.height / originHeight
|
||||||
// 内边距
|
// 内边距
|
||||||
let paddingX = 6
|
let paddingX = this.textNodePaddingX
|
||||||
let paddingY = 4
|
let paddingY = this.textNodePaddingY
|
||||||
if (richTextEditFakeInPlace) {
|
if (richTextEditFakeInPlace) {
|
||||||
let paddingValue = node.getPaddingVale()
|
let paddingValue = node.getPaddingVale()
|
||||||
paddingX = paddingValue.paddingX
|
paddingX = paddingValue.paddingX
|
||||||
@ -287,6 +292,20 @@ class RichText {
|
|||||||
this.cacheEditingText = ''
|
this.cacheEditingText = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新文本编辑框的大小和位置
|
||||||
|
updateTextEditNode() {
|
||||||
|
if (!this.node) return
|
||||||
|
const rect = this.node._textData.node.node.getBoundingClientRect()
|
||||||
|
const g = this.node._textData.node
|
||||||
|
const originWidth = g.attr('data-width')
|
||||||
|
const originHeight = g.attr('data-height')
|
||||||
|
this.textEditNode.style.minWidth =
|
||||||
|
originWidth + this.textNodePaddingX * 2 + 'px'
|
||||||
|
this.textEditNode.style.minHeight = originHeight + 'px'
|
||||||
|
this.textEditNode.style.left = rect.left + 'px'
|
||||||
|
this.textEditNode.style.top = rect.top + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
// 删除文本编辑框元素
|
// 删除文本编辑框元素
|
||||||
removeTextEditEl() {
|
removeTextEditEl() {
|
||||||
if (!this.textEditNode) return
|
if (!this.textEditNode) return
|
||||||
@ -340,6 +359,18 @@ class RichText {
|
|||||||
return html.replace(/<p><br><\/p>$/, '')
|
return html.replace(/<p><br><\/p>$/, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 给html字符串中的节点样式按样式名首字母排序
|
||||||
|
sortHtmlNodeStyles(html) {
|
||||||
|
return html.replace(/(<[^<>]+\s+style=")([^"]+)("\s*>)/g, (_, a, b, c) => {
|
||||||
|
let arr = b.match(/[^:]+:[^:]+;/g) || []
|
||||||
|
arr = arr.map(item => {
|
||||||
|
return item.trim()
|
||||||
|
})
|
||||||
|
arr.sort()
|
||||||
|
return a + arr.join('') + c
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 隐藏文本编辑控件,即完成编辑
|
// 隐藏文本编辑控件,即完成编辑
|
||||||
hideEditText(nodes) {
|
hideEditText(nodes) {
|
||||||
if (!this.showTextEdit) {
|
if (!this.showTextEdit) {
|
||||||
@ -350,6 +381,7 @@ class RichText {
|
|||||||
beforeHideRichTextEdit(this)
|
beforeHideRichTextEdit(this)
|
||||||
}
|
}
|
||||||
let html = this.getEditText()
|
let html = this.getEditText()
|
||||||
|
html = this.sortHtmlNodeStyles(html)
|
||||||
let list =
|
let list =
|
||||||
nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
|
nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
|
||||||
list.forEach(node => {
|
list.forEach(node => {
|
||||||
@ -360,12 +392,13 @@ class RichText {
|
|||||||
// }
|
// }
|
||||||
this.mindMap.render()
|
this.mindMap.render()
|
||||||
})
|
})
|
||||||
this.mindMap.emit('hide_text_edit', this.textEditNode, list, this.node)
|
const node = this.node
|
||||||
this.textEditNode.style.display = 'none'
|
this.textEditNode.style.display = 'none'
|
||||||
this.showTextEdit = false
|
this.showTextEdit = false
|
||||||
this.mindMap.emit('rich_text_selection_change', false)
|
this.mindMap.emit('rich_text_selection_change', false)
|
||||||
this.node = null
|
this.node = null
|
||||||
this.isInserting = false
|
this.isInserting = false
|
||||||
|
this.mindMap.emit('hide_text_edit', this.textEditNode, list, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化Quill富文本编辑器
|
// 初始化Quill富文本编辑器
|
||||||
@ -490,6 +523,11 @@ class RichText {
|
|||||||
this.setTextStyleIfNotRichText(this.node)
|
this.setTextStyleIfNotRichText(this.node)
|
||||||
this.lostStyle = false
|
this.lostStyle = false
|
||||||
}
|
}
|
||||||
|
this.mindMap.emit('node_text_edit_change', {
|
||||||
|
node: this.node,
|
||||||
|
text: this.getEditText(),
|
||||||
|
richText: true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
// 拦截粘贴,只允许粘贴纯文本
|
// 拦截粘贴,只允许粘贴纯文本
|
||||||
// this.quill.clipboard.addMatcher(Node.TEXT_NODE, node => {
|
// this.quill.clipboard.addMatcher(Node.TEXT_NODE, node => {
|
||||||
@ -544,6 +582,16 @@ class RichText {
|
|||||||
this.isCompositing = true
|
this.isCompositing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 中文输入中
|
||||||
|
onCompositionUpdate() {
|
||||||
|
if (!this.showTextEdit || !this.node) return
|
||||||
|
this.mindMap.emit('node_text_edit_change', {
|
||||||
|
node: this.node,
|
||||||
|
text: this.getEditText(),
|
||||||
|
richText: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 中文输入结束
|
// 中文输入结束
|
||||||
onCompositionEnd() {
|
onCompositionEnd() {
|
||||||
if (!this.showTextEdit) {
|
if (!this.showTextEdit) {
|
||||||
|
|||||||
@ -82,8 +82,12 @@ export default {
|
|||||||
gradientStyle: false,
|
gradientStyle: false,
|
||||||
startColor: '#549688',
|
startColor: '#549688',
|
||||||
endColor: '#fff',
|
endColor: '#fff',
|
||||||
|
startDir: [0, 0],
|
||||||
|
endDir: [1, 0],
|
||||||
// 连线标记的位置,start(头部)、end(尾部),该配置在showLineMarker配置为true时生效
|
// 连线标记的位置,start(头部)、end(尾部),该配置在showLineMarker配置为true时生效
|
||||||
lineMarkerDir: 'end'
|
lineMarkerDir: 'end',
|
||||||
|
// 节点鼠标hover和激活时显示的矩形边框的颜色,主题里不设置,默认会取hoverRectColor实例化选项的值
|
||||||
|
hoverRectColor: ''
|
||||||
},
|
},
|
||||||
// 二级节点样式
|
// 二级节点样式
|
||||||
second: {
|
second: {
|
||||||
@ -94,7 +98,7 @@ export default {
|
|||||||
fontFamily: '微软雅黑, Microsoft YaHei',
|
fontFamily: '微软雅黑, Microsoft YaHei',
|
||||||
color: '#565656',
|
color: '#565656',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 'noraml',
|
fontWeight: 'normal',
|
||||||
fontStyle: 'normal',
|
fontStyle: 'normal',
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
borderColor: '#549688',
|
borderColor: '#549688',
|
||||||
@ -105,7 +109,10 @@ export default {
|
|||||||
gradientStyle: false,
|
gradientStyle: false,
|
||||||
startColor: '#549688',
|
startColor: '#549688',
|
||||||
endColor: '#fff',
|
endColor: '#fff',
|
||||||
lineMarkerDir: 'end'
|
startDir: [0, 0],
|
||||||
|
endDir: [1, 0],
|
||||||
|
lineMarkerDir: 'end',
|
||||||
|
hoverRectColor: ''
|
||||||
},
|
},
|
||||||
// 三级及以下节点样式
|
// 三级及以下节点样式
|
||||||
node: {
|
node: {
|
||||||
@ -116,7 +123,7 @@ export default {
|
|||||||
fontFamily: '微软雅黑, Microsoft YaHei',
|
fontFamily: '微软雅黑, Microsoft YaHei',
|
||||||
color: '#6a6d6c',
|
color: '#6a6d6c',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 'noraml',
|
fontWeight: 'normal',
|
||||||
fontStyle: 'normal',
|
fontStyle: 'normal',
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
borderColor: 'transparent',
|
borderColor: 'transparent',
|
||||||
@ -127,7 +134,10 @@ export default {
|
|||||||
gradientStyle: false,
|
gradientStyle: false,
|
||||||
startColor: '#549688',
|
startColor: '#549688',
|
||||||
endColor: '#fff',
|
endColor: '#fff',
|
||||||
lineMarkerDir: 'end'
|
startDir: [0, 0],
|
||||||
|
endDir: [1, 0],
|
||||||
|
lineMarkerDir: 'end',
|
||||||
|
hoverRectColor: ''
|
||||||
},
|
},
|
||||||
// 概要节点样式
|
// 概要节点样式
|
||||||
generalization: {
|
generalization: {
|
||||||
@ -138,7 +148,7 @@ export default {
|
|||||||
fontFamily: '微软雅黑, Microsoft YaHei',
|
fontFamily: '微软雅黑, Microsoft YaHei',
|
||||||
color: '#565656',
|
color: '#565656',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 'noraml',
|
fontWeight: 'normal',
|
||||||
fontStyle: 'normal',
|
fontStyle: 'normal',
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
borderColor: '#549688',
|
borderColor: '#549688',
|
||||||
@ -148,7 +158,10 @@ export default {
|
|||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
gradientStyle: false,
|
gradientStyle: false,
|
||||||
startColor: '#549688',
|
startColor: '#549688',
|
||||||
endColor: '#fff'
|
endColor: '#fff',
|
||||||
|
startDir: [0, 0],
|
||||||
|
endDir: [1, 0],
|
||||||
|
hoverRectColor: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +192,10 @@ const nodeSizeIndependenceList = [
|
|||||||
'gradientStyle',
|
'gradientStyle',
|
||||||
'lineRadius',
|
'lineRadius',
|
||||||
'startColor',
|
'startColor',
|
||||||
'endColor'
|
'endColor',
|
||||||
|
'startDir',
|
||||||
|
'endDir',
|
||||||
|
'hoverRectColor'
|
||||||
]
|
]
|
||||||
export const checkIsNodeSizeIndependenceConfig = config => {
|
export const checkIsNodeSizeIndependenceConfig = config => {
|
||||||
let keys = Object.keys(config)
|
let keys = Object.keys(config)
|
||||||
|
|||||||
@ -1184,9 +1184,18 @@ export const handleInputPasteText = (e, text) => {
|
|||||||
// 去除格式
|
// 去除格式
|
||||||
text = getTextFromHtml(text)
|
text = getTextFromHtml(text)
|
||||||
// 去除换行
|
// 去除换行
|
||||||
text = text.replaceAll(/\n/g, '')
|
// text = text.replaceAll(/\n/g, '')
|
||||||
const node = document.createTextNode(text)
|
const textArr = text.split(/\n/g)
|
||||||
selection.getRangeAt(0).insertNode(node)
|
const fragment = document.createDocumentFragment()
|
||||||
|
textArr.forEach((item, index) => {
|
||||||
|
const node = document.createTextNode(item)
|
||||||
|
fragment.appendChild(node)
|
||||||
|
if (index < textArr.length - 1) {
|
||||||
|
const br = document.createElement('br')
|
||||||
|
fragment.appendChild(br)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
selection.getRangeAt(0).insertNode(fragment)
|
||||||
selection.collapseToEnd()
|
selection.collapseToEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const fs = require('fs')
|
|
||||||
const hljs = require('highlight.js')
|
|
||||||
const md = require('markdown-it')({
|
|
||||||
html: true,
|
|
||||||
xhtmlOut: true,
|
|
||||||
highlight: function(str, lang) {
|
|
||||||
if (lang && hljs.getLanguage(lang)) {
|
|
||||||
try {
|
|
||||||
return (
|
|
||||||
'<pre class="hljs"><code>' +
|
|
||||||
hljs.highlight(str, {
|
|
||||||
language: lang,
|
|
||||||
ignoreIllegals: true
|
|
||||||
}).value +
|
|
||||||
'</code></pre>'
|
|
||||||
)
|
|
||||||
} catch (__) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
'<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}).use(require('markdown-it-checkbox'))
|
|
||||||
|
|
||||||
const templatePath = path.join(__dirname, '../src/pages/Doc/Template.vue')
|
|
||||||
|
|
||||||
exports.transformMdToVue = (content) => {
|
|
||||||
let result = md.render(content)
|
|
||||||
let template = fs.readFileSync(templatePath, 'utf-8')
|
|
||||||
return template.replace('$$$$', result)
|
|
||||||
}
|
|
||||||
BIN
web/src/assets/avatar/Lawliet.jpg
Normal file
BIN
web/src/assets/avatar/Lawliet.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
BIN
web/src/assets/avatar/一叶孤舟.jpg
Normal file
BIN
web/src/assets/avatar/一叶孤舟.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 2479351 */
|
font-family: "iconfont"; /* Project id 2479351 */
|
||||||
src: url('iconfont.woff2?t=1719815803051') format('woff2'),
|
src: url('iconfont.woff2?t=1726022313538') format('woff2'),
|
||||||
url('iconfont.woff?t=1719815803051') format('woff'),
|
url('iconfont.woff?t=1726022313538') format('woff'),
|
||||||
url('iconfont.ttf?t=1719815803051') format('truetype');
|
url('iconfont.ttf?t=1726022313538') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -13,6 +13,14 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iconfile-excel:before {
|
||||||
|
content: "\e7b7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfreemind:before {
|
||||||
|
content: "\e97d";
|
||||||
|
}
|
||||||
|
|
||||||
.iconwaikuang:before {
|
.iconwaikuang:before {
|
||||||
content: "\e640";
|
content: "\e640";
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -85,12 +85,14 @@ export const formulaList = [
|
|||||||
'\\begin{cases}3x + 5y + z \\\\7x - 2y + 4z \\\\-6x + 3y + 2z\\end{cases}'
|
'\\begin{cases}3x + 5y + z \\\\7x - 2y + 4z \\\\-6x + 3y + 2z\\end{cases}'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 支持某种连线类型的结构
|
||||||
export const supportLineStyleLayoutsMap = {
|
export const supportLineStyleLayoutsMap = {
|
||||||
curve: [
|
curve: [
|
||||||
'logicalStructure',
|
'logicalStructure',
|
||||||
'logicalStructureLeft',
|
'logicalStructureLeft',
|
||||||
'mindMap',
|
'mindMap',
|
||||||
'verticalTimeline'
|
'verticalTimeline',
|
||||||
|
'organizationStructure'
|
||||||
],
|
],
|
||||||
direct: [
|
direct: [
|
||||||
'logicalStructure',
|
'logicalStructure',
|
||||||
@ -101,6 +103,7 @@ export const supportLineStyleLayoutsMap = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 直线模式支持设置圆角的结构
|
||||||
export const supportLineRadiusLayouts = [
|
export const supportLineRadiusLayouts = [
|
||||||
'logicalStructure',
|
'logicalStructure',
|
||||||
'logicalStructureLeft',
|
'logicalStructureLeft',
|
||||||
@ -108,6 +111,7 @@ export const supportLineRadiusLayouts = [
|
|||||||
'verticalTimeline'
|
'verticalTimeline'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 支持只显示底边直线风格的结构
|
||||||
export const supportNodeUseLineStyleLayouts = [
|
export const supportNodeUseLineStyleLayouts = [
|
||||||
'logicalStructure',
|
'logicalStructure',
|
||||||
'logicalStructureLeft',
|
'logicalStructureLeft',
|
||||||
@ -116,10 +120,12 @@ export const supportNodeUseLineStyleLayouts = [
|
|||||||
'organizationStructure'
|
'organizationStructure'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 支持曲线模式下,根节点样式和其他节点样式保持一致的结构
|
||||||
export const supportRootLineKeepSameInCurveLayouts = [
|
export const supportRootLineKeepSameInCurveLayouts = [
|
||||||
'logicalStructure',
|
'logicalStructure',
|
||||||
'logicalStructureLeft',
|
'logicalStructureLeft',
|
||||||
'mindMap'
|
'mindMap',
|
||||||
|
'organizationStructure'
|
||||||
]
|
]
|
||||||
|
|
||||||
// 彩虹线条配置
|
// 彩虹线条配置
|
||||||
|
|||||||
@ -495,6 +495,18 @@ export const downTypeList = [
|
|||||||
type: 'txt',
|
type: 'txt',
|
||||||
icon: 'iconTXT',
|
icon: 'iconTXT',
|
||||||
desc: 'Plain text file'
|
desc: 'Plain text file'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'FreeMind',
|
||||||
|
type: 'mm',
|
||||||
|
icon: 'iconfreemind',
|
||||||
|
desc: 'FreeMind software format'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Excel',
|
||||||
|
type: 'xlsx',
|
||||||
|
icon: 'iconfile-excel',
|
||||||
|
desc: 'Excel software format'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -557,3 +569,55 @@ export const numberLevelList = [
|
|||||||
value: 0
|
value: 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 背景渐变方向
|
||||||
|
export const linearGradientDirList = [
|
||||||
|
{
|
||||||
|
name: 'Left to right',
|
||||||
|
value: '1',
|
||||||
|
start: [0, 0],
|
||||||
|
end: [1, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Right to left',
|
||||||
|
value: '2',
|
||||||
|
start: [1, 0],
|
||||||
|
end: [0, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Top to bottom',
|
||||||
|
value: '3',
|
||||||
|
start: [0, 0],
|
||||||
|
end: [0, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Bottom to top',
|
||||||
|
value: '4',
|
||||||
|
start: [0, 1],
|
||||||
|
end: [0, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Left top to right bottom',
|
||||||
|
value: '5',
|
||||||
|
start: [0, 0],
|
||||||
|
end: [1, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Left bottom to right top',
|
||||||
|
value: '6',
|
||||||
|
start: [0, 1],
|
||||||
|
end: [1, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Right top to left bottom',
|
||||||
|
value: '7',
|
||||||
|
start: [1, 0],
|
||||||
|
end: [0, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Right bottom to left top',
|
||||||
|
value: '8',
|
||||||
|
start: [1, 1],
|
||||||
|
end: [0, 0]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
@ -21,7 +21,8 @@ import {
|
|||||||
shapeListMap as shapeListMapZh,
|
shapeListMap as shapeListMapZh,
|
||||||
lineStyleMap as lineStyleMapZh,
|
lineStyleMap as lineStyleMapZh,
|
||||||
numberTypeList as numberTypeListZh,
|
numberTypeList as numberTypeListZh,
|
||||||
numberLevelList as numberLevelListZh
|
numberLevelList as numberLevelListZh,
|
||||||
|
linearGradientDirList as linearGradientDirListZh
|
||||||
} from './zh'
|
} from './zh'
|
||||||
import {
|
import {
|
||||||
fontFamilyList as fontFamilyListEn,
|
fontFamilyList as fontFamilyListEn,
|
||||||
@ -36,7 +37,8 @@ import {
|
|||||||
backgroundSizeList as backgroundSizeListEn,
|
backgroundSizeList as backgroundSizeListEn,
|
||||||
downTypeList as downTypeListEn,
|
downTypeList as downTypeListEn,
|
||||||
numberTypeList as numberTypeListEn,
|
numberTypeList as numberTypeListEn,
|
||||||
numberLevelList as numberLevelListEn
|
numberLevelList as numberLevelListEn,
|
||||||
|
linearGradientDirList as linearGradientDirListEn
|
||||||
} from './en'
|
} from './en'
|
||||||
|
|
||||||
const fontFamilyList = {
|
const fontFamilyList = {
|
||||||
@ -114,6 +116,11 @@ const numberLevelList = {
|
|||||||
en: numberLevelListEn
|
en: numberLevelListEn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const linearGradientDirList = {
|
||||||
|
zh: linearGradientDirListZh,
|
||||||
|
en: linearGradientDirListEn
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
fontSizeList,
|
fontSizeList,
|
||||||
lineHeightList,
|
lineHeightList,
|
||||||
@ -137,5 +144,6 @@ export {
|
|||||||
sidebarTriggerList,
|
sidebarTriggerList,
|
||||||
downTypeList,
|
downTypeList,
|
||||||
numberTypeList,
|
numberTypeList,
|
||||||
numberLevelList
|
numberLevelList,
|
||||||
|
linearGradientDirList
|
||||||
}
|
}
|
||||||
|
|||||||
@ -593,6 +593,18 @@ export const downTypeList = [
|
|||||||
type: 'txt',
|
type: 'txt',
|
||||||
icon: 'iconTXT',
|
icon: 'iconTXT',
|
||||||
desc: '纯文本文件'
|
desc: '纯文本文件'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'FreeMind',
|
||||||
|
type: 'mm',
|
||||||
|
icon: 'iconfreemind',
|
||||||
|
desc: 'FreeMind软件格式'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Excel',
|
||||||
|
type: 'xlsx',
|
||||||
|
icon: 'iconfile-excel',
|
||||||
|
desc: 'Excel软件格式'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -655,3 +667,55 @@ export const numberLevelList = [
|
|||||||
value: 0
|
value: 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 背景渐变方向
|
||||||
|
export const linearGradientDirList = [
|
||||||
|
{
|
||||||
|
name: '从左到右',
|
||||||
|
value: '1',
|
||||||
|
start: [0, 0],
|
||||||
|
end: [1, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '从右到左',
|
||||||
|
value: '2',
|
||||||
|
start: [1, 0],
|
||||||
|
end: [0, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '从上到下',
|
||||||
|
value: '3',
|
||||||
|
start: [0, 0],
|
||||||
|
end: [0, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '从下到上',
|
||||||
|
value: '4',
|
||||||
|
start: [0, 1],
|
||||||
|
end: [0, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '从左上到右下',
|
||||||
|
value: '5',
|
||||||
|
start: [0, 0],
|
||||||
|
end: [1, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '从左下到右上',
|
||||||
|
value: '6',
|
||||||
|
start: [0, 1],
|
||||||
|
end: [1, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '从右上到左下',
|
||||||
|
value: '7',
|
||||||
|
start: [1, 0],
|
||||||
|
end: [0, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '从右下到左上',
|
||||||
|
value: '8',
|
||||||
|
start: [1, 1],
|
||||||
|
end: [0, 0]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
@ -157,8 +157,9 @@ export default {
|
|||||||
import: {
|
import: {
|
||||||
title: 'Import',
|
title: 'Import',
|
||||||
selectFile: 'Select file',
|
selectFile: 'Select file',
|
||||||
supportFile: 'Support .smm、.json、.xmind、.xlsx、.md file',
|
support: 'Support',
|
||||||
enableFileTip: 'Please select .smm、.json、.xmind、.xlsx、.md file',
|
file: 'file',
|
||||||
|
pleaseSelect: 'Please select',
|
||||||
maxFileNum: 'At most one file can be selected',
|
maxFileNum: 'At most one file can be selected',
|
||||||
notSelectTip: 'Please select the file to import',
|
notSelectTip: 'Please select the file to import',
|
||||||
fileContentError: 'The file content is incorrect',
|
fileContentError: 'The file content is incorrect',
|
||||||
@ -238,7 +239,8 @@ export default {
|
|||||||
endColor: 'End',
|
endColor: 'End',
|
||||||
arrowDir: 'Arrow dir',
|
arrowDir: 'Arrow dir',
|
||||||
arrowDirStart: 'Start',
|
arrowDirStart: 'Start',
|
||||||
arrowDirEnd: 'End'
|
arrowDirEnd: 'End',
|
||||||
|
direction: 'Direction'
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
title: 'Theme',
|
title: 'Theme',
|
||||||
@ -305,7 +307,8 @@ export default {
|
|||||||
yes: 'Yes',
|
yes: 'Yes',
|
||||||
no: 'No',
|
no: 'No',
|
||||||
exportError: 'Export failed',
|
exportError: 'Export failed',
|
||||||
dragTip: 'Release here to import the file'
|
dragTip: 'Release here to import the file',
|
||||||
|
deleteNodeImgTip: 'Are you sure to delete the node image?'
|
||||||
},
|
},
|
||||||
mouseAction: {
|
mouseAction: {
|
||||||
tip1:
|
tip1:
|
||||||
|
|||||||
@ -155,8 +155,9 @@ export default {
|
|||||||
import: {
|
import: {
|
||||||
title: '导入',
|
title: '导入',
|
||||||
selectFile: '选取文件',
|
selectFile: '选取文件',
|
||||||
supportFile: '支持.smm、.json、.xmind、.xlsx、.md文件',
|
support: '支持',
|
||||||
enableFileTip: '请选择.smm、.json、.xmind、.xlsx、.md文件',
|
file: '文件',
|
||||||
|
pleaseSelect: '请选择',
|
||||||
maxFileNum: '最多只能选择一个文件',
|
maxFileNum: '最多只能选择一个文件',
|
||||||
notSelectTip: '请选择要导入的文件',
|
notSelectTip: '请选择要导入的文件',
|
||||||
fileContentError: '文件内容有误',
|
fileContentError: '文件内容有误',
|
||||||
@ -236,7 +237,8 @@ export default {
|
|||||||
endColor: '结束',
|
endColor: '结束',
|
||||||
arrowDir: '箭头位置',
|
arrowDir: '箭头位置',
|
||||||
arrowDirStart: '头部',
|
arrowDirStart: '头部',
|
||||||
arrowDirEnd: '尾部'
|
arrowDirEnd: '尾部',
|
||||||
|
direction: '方向'
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
title: '主题',
|
title: '主题',
|
||||||
@ -299,7 +301,8 @@ export default {
|
|||||||
yes: '是',
|
yes: '是',
|
||||||
no: '否',
|
no: '否',
|
||||||
exportError: '导出失败',
|
exportError: '导出失败',
|
||||||
dragTip: '在此释放以导入该文件'
|
dragTip: '在此释放以导入该文件',
|
||||||
|
deleteNodeImgTip: '是否确认删除该节点图片?'
|
||||||
},
|
},
|
||||||
mouseAction: {
|
mouseAction: {
|
||||||
tip1: '当前:左键拖动画布,右键框选节点',
|
tip1: '当前:左键拖动画布,右键框选节点',
|
||||||
|
|||||||
@ -79,6 +79,11 @@ import OuterFrame from 'simple-mind-map/src/plugins/OuterFrame.js'
|
|||||||
// import Notation from 'simple-mind-map-plugin-notation'
|
// import Notation from 'simple-mind-map-plugin-notation'
|
||||||
// 编号插件,该插件为付费插件,详情请查看开发文档
|
// 编号插件,该插件为付费插件,详情请查看开发文档
|
||||||
// import Numbers from 'simple-mind-map-plugin-numbers'
|
// import Numbers from 'simple-mind-map-plugin-numbers'
|
||||||
|
// Freemind软件格式导入导出插件,该插件为付费插件,详情请查看开发文档
|
||||||
|
// import Freemind from 'simple-mind-map-plugin-freemind'
|
||||||
|
// Excel软件格式导入导出插件,该插件为付费插件,详情请查看开发文档
|
||||||
|
// import Excel from 'simple-mind-map-plugin-excel'
|
||||||
|
// npm link simple-mind-map-plugin-excel simple-mind-map-plugin-freemind simple-mind-map-plugin-numbers simple-mind-map-plugin-notation simple-mind-map-plugin-handdrawnlikestyle simple-mind-map
|
||||||
import OutlineSidebar from './OutlineSidebar'
|
import OutlineSidebar from './OutlineSidebar'
|
||||||
import Style from './Style'
|
import Style from './Style'
|
||||||
import BaseStyle from './BaseStyle'
|
import BaseStyle from './BaseStyle'
|
||||||
@ -418,6 +423,25 @@ export default {
|
|||||||
},
|
},
|
||||||
expandBtnNumHandler: num => {
|
expandBtnNumHandler: num => {
|
||||||
return num >= 100 ? '…' : num
|
return num >= 100 ? '…' : num
|
||||||
|
},
|
||||||
|
beforeDeleteNodeImg: node => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this.$confirm(
|
||||||
|
this.$t('edit.deleteNodeImgTip'),
|
||||||
|
this.$t('edit.tip'),
|
||||||
|
{
|
||||||
|
confirmButtonText: this.$t('edit.yes'),
|
||||||
|
cancelButtonText: this.$t('edit.no'),
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
resolve(false)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// createNodePrefixContent: (node) => {
|
// createNodePrefixContent: (node) => {
|
||||||
// const el = document.createElement('div')
|
// const el = document.createElement('div')
|
||||||
@ -544,6 +568,16 @@ export default {
|
|||||||
this.mindMap.addPlugin(Numbers)
|
this.mindMap.addPlugin(Numbers)
|
||||||
this.$store.commit('setSupportNumbers', true)
|
this.$store.commit('setSupportNumbers', true)
|
||||||
}
|
}
|
||||||
|
if (typeof Freemind !== 'undefined') {
|
||||||
|
this.mindMap.addPlugin(Freemind)
|
||||||
|
this.$store.commit('setSupportFreemind', true)
|
||||||
|
Vue.prototype.Freemind = Freemind
|
||||||
|
}
|
||||||
|
if (typeof Excel !== 'undefined') {
|
||||||
|
this.mindMap.addPlugin(Excel)
|
||||||
|
this.$store.commit('setSupportExcel', true)
|
||||||
|
Vue.prototype.Excel = Excel
|
||||||
|
}
|
||||||
this.mindMap.keyCommand.addShortcut('Control+s', () => {
|
this.mindMap.keyCommand.addShortcut('Control+s', () => {
|
||||||
this.manualSave()
|
this.manualSave()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
element-loading-spinner="el-icon-loading"
|
element-loading-spinner="el-icon-loading"
|
||||||
element-loading-background="rgba(0, 0, 0, 0.8)"
|
element-loading-background="rgba(0, 0, 0, 0.8)"
|
||||||
:width="isMobile ? '90%' : '50%'"
|
:width="isMobile ? '90%' : '50%'"
|
||||||
:top="isMobile? '20px' : '15vh'"
|
:top="isMobile ? '20px' : '15vh'"
|
||||||
>
|
>
|
||||||
<div class="exportContainer" :class="{ isDark: isDark }">
|
<div class="exportContainer" :class="{ isDark: isDark }">
|
||||||
<div class="nameInputBox">
|
<div class="nameInputBox">
|
||||||
@ -98,12 +98,14 @@
|
|||||||
import { mapState, mapMutations } from 'vuex'
|
import { mapState, mapMutations } from 'vuex'
|
||||||
import { downTypeList } from '@/config'
|
import { downTypeList } from '@/config'
|
||||||
import { isMobile } from 'simple-mind-map/src/utils/index'
|
import { isMobile } from 'simple-mind-map/src/utils/index'
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: 王林
|
* @Author: 王林
|
||||||
* @Date: 2021-06-24 22:53:54
|
* @Date: 2021-06-24 22:53:54
|
||||||
* @Desc: 导出
|
* @Desc: 导出
|
||||||
*/
|
*/
|
||||||
|
let md = null
|
||||||
export default {
|
export default {
|
||||||
name: 'Export',
|
name: 'Export',
|
||||||
data() {
|
data() {
|
||||||
@ -124,11 +126,23 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
openNodeRichText: state => state.localConfig.openNodeRichText,
|
openNodeRichText: state => state.localConfig.openNodeRichText,
|
||||||
isDark: state => state.localConfig.isDark
|
isDark: state => state.localConfig.isDark,
|
||||||
|
supportFreemind: state => state.supportFreemind,
|
||||||
|
supportExcel: state => state.supportExcel
|
||||||
}),
|
}),
|
||||||
|
|
||||||
downTypeList() {
|
downTypeList() {
|
||||||
return downTypeList[this.$i18n.locale] || downTypeList.zh
|
const list = downTypeList[this.$i18n.locale] || downTypeList.zh
|
||||||
|
return list.filter(item => {
|
||||||
|
if (item.type === 'mm') {
|
||||||
|
return this.supportFreemind
|
||||||
|
}
|
||||||
|
if (item.type === 'xlsx') {
|
||||||
|
return this.supportExcel
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -203,6 +217,22 @@ export default {
|
|||||||
this.fileName,
|
this.fileName,
|
||||||
this.isTransparent
|
this.isTransparent
|
||||||
)
|
)
|
||||||
|
} else if (this.exportType === 'mm') {
|
||||||
|
this.$bus.$emit('export', this.exportType, true, this.fileName, {
|
||||||
|
transformNote: note => {
|
||||||
|
if (!md) {
|
||||||
|
md = new MarkdownIt()
|
||||||
|
}
|
||||||
|
return md.render(note)
|
||||||
|
},
|
||||||
|
transformImage: img => {
|
||||||
|
if (/^https?:\/\//.test(img)) {
|
||||||
|
return img
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
this.$bus.$emit('export', this.exportType, true, this.fileName)
|
this.$bus.$emit('export', this.exportType, true, this.fileName)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<el-upload
|
<el-upload
|
||||||
ref="upload"
|
ref="upload"
|
||||||
action="x"
|
action="x"
|
||||||
accept=".smm,.json,.xmind,.xlsx,.md"
|
:accept="supportFileStr"
|
||||||
:file-list="fileList"
|
:file-list="fileList"
|
||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
@ -22,7 +22,7 @@
|
|||||||
$t('import.selectFile')
|
$t('import.selectFile')
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
<div slot="tip" class="el-upload__tip">
|
<div slot="tip" class="el-upload__tip">
|
||||||
{{ $t('import.supportFile') }}
|
{{ $t('import.support') }}{{ supportFileStr }}{{ $t('import.file') }}
|
||||||
</div>
|
</div>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
<span slot="footer" class="dialog-footer">
|
<span slot="footer" class="dialog-footer">
|
||||||
@ -40,9 +40,12 @@
|
|||||||
:show-close="false"
|
:show-close="false"
|
||||||
>
|
>
|
||||||
<el-radio-group v-model="selectCanvas" class="canvasList">
|
<el-radio-group v-model="selectCanvas" class="canvasList">
|
||||||
<el-radio v-for="(item, index) in canvasList" :key="index" :label="index">{{
|
<el-radio
|
||||||
item.title
|
v-for="(item, index) in canvasList"
|
||||||
}}</el-radio>
|
:key="index"
|
||||||
|
:label="index"
|
||||||
|
>{{ item.title }}</el-radio
|
||||||
|
>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
<span slot="footer" class="dialog-footer">
|
<span slot="footer" class="dialog-footer">
|
||||||
<el-button type="primary" @click="confirmSelect">{{
|
<el-button type="primary" @click="confirmSelect">{{
|
||||||
@ -56,9 +59,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import xmind from 'simple-mind-map/src/parse/xmind.js'
|
import xmind from 'simple-mind-map/src/parse/xmind.js'
|
||||||
import markdown from 'simple-mind-map/src/parse/markdown.js'
|
import markdown from 'simple-mind-map/src/parse/markdown.js'
|
||||||
import { fileToBuffer } from '@/utils'
|
import { mapMutations, mapState } from 'vuex'
|
||||||
import { read, utils } from 'xlsx'
|
import Vue from 'vue'
|
||||||
import { mapMutations } from 'vuex'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: 王林
|
* @Author: 王林
|
||||||
@ -77,6 +79,22 @@ export default {
|
|||||||
canvasList: []
|
canvasList: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
supportFreemind: state => state.supportFreemind,
|
||||||
|
supportExcel: state => state.supportExcel
|
||||||
|
}),
|
||||||
|
supportFileStr() {
|
||||||
|
let res = '.smm,.json,.xmind,.md'
|
||||||
|
if (this.supportFreemind) {
|
||||||
|
res += ',.mm'
|
||||||
|
}
|
||||||
|
if (this.supportExcel) {
|
||||||
|
res += ',.xlsx'
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
dialogVisible(val, oldVal) {
|
dialogVisible(val, oldVal) {
|
||||||
if (!val && oldVal) {
|
if (!val && oldVal) {
|
||||||
@ -101,12 +119,20 @@ export default {
|
|||||||
this.dialogVisible = true
|
this.dialogVisible = true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getRegexp() {
|
||||||
|
return new RegExp(
|
||||||
|
`\.(smm|json|xmind|md${this.supportFreemind ? '|mm' : ''}${
|
||||||
|
this.supportExcel ? '|xlsx' : ''
|
||||||
|
})$`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
// 检查url中是否操作需要打开的文件
|
// 检查url中是否操作需要打开的文件
|
||||||
async handleFileURL() {
|
async handleFileURL() {
|
||||||
try {
|
try {
|
||||||
const fileURL = this.$route.query.fileURL
|
const fileURL = this.$route.query.fileURL
|
||||||
if (!fileURL) return
|
if (!fileURL) return
|
||||||
const macth = /\.(smm|json|xmind|md|xlsx)$/.exec(fileURL)
|
const macth = this.getRegexp().exec(fileURL)
|
||||||
if (!macth) {
|
if (!macth) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -124,6 +150,8 @@ export default {
|
|||||||
this.handleExcel(data)
|
this.handleExcel(data)
|
||||||
} else if (type === 'md') {
|
} else if (type === 'md') {
|
||||||
this.handleMd(data)
|
this.handleMd(data)
|
||||||
|
} else if (type === 'mm') {
|
||||||
|
this.handleMm(data)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@ -132,9 +160,12 @@ export default {
|
|||||||
|
|
||||||
// 文件选择
|
// 文件选择
|
||||||
onChange(file) {
|
onChange(file) {
|
||||||
let reg = /\.(smm|xmind|json|xlsx|md)$/
|
if (!this.getRegexp().test(file.name)) {
|
||||||
if (!reg.test(file.name)) {
|
this.$message.error(
|
||||||
this.$message.error(this.$t('import.enableFileTip'))
|
this.$t('import.pleaseSelect') +
|
||||||
|
this.supportFileStr +
|
||||||
|
this.$t('import.file')
|
||||||
|
)
|
||||||
this.fileList = []
|
this.fileList = []
|
||||||
} else {
|
} else {
|
||||||
this.fileList.push(file)
|
this.fileList.push(file)
|
||||||
@ -171,6 +202,8 @@ export default {
|
|||||||
this.handleExcel(file)
|
this.handleExcel(file)
|
||||||
} else if (/\.md$/.test(file.name)) {
|
} else if (/\.md$/.test(file.name)) {
|
||||||
this.handleMd(file)
|
this.handleMd(file)
|
||||||
|
} else if (/\.mm$/.test(file.name)) {
|
||||||
|
this.handleMm(file)
|
||||||
}
|
}
|
||||||
this.cancel()
|
this.cancel()
|
||||||
this.setActiveSidebar(null)
|
this.setActiveSidebar(null)
|
||||||
@ -212,6 +245,36 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 处理Freemind格式
|
||||||
|
handleMm(file) {
|
||||||
|
const fileReader = new FileReader()
|
||||||
|
fileReader.readAsText(file.raw)
|
||||||
|
fileReader.onload = async evt => {
|
||||||
|
try {
|
||||||
|
const data = await Vue.prototype.Freemind.freemindToSmm(
|
||||||
|
evt.target.result,
|
||||||
|
{
|
||||||
|
// withStyle: true,
|
||||||
|
transformImg: image => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (/^https?:\/\//.test(image)) {
|
||||||
|
resolve({ url: image })
|
||||||
|
} else {
|
||||||
|
resolve(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.$bus.$emit('setData', data)
|
||||||
|
this.$message.success(this.$t('import.importSuccess'))
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
this.$message.error(this.$t('import.fileParsingFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 显示xmind文件的多个画布选择弹窗
|
// 显示xmind文件的多个画布选择弹窗
|
||||||
showSelectXmindCanvasDialog(content) {
|
showSelectXmindCanvasDialog(content) {
|
||||||
this.canvasList = content
|
this.canvasList = content
|
||||||
@ -230,59 +293,8 @@ export default {
|
|||||||
// 处理.xlsx文件
|
// 处理.xlsx文件
|
||||||
async handleExcel(file) {
|
async handleExcel(file) {
|
||||||
try {
|
try {
|
||||||
const wb = read(await fileToBuffer(file.raw))
|
const res = await Vue.prototype.Excel.excelTo(file.raw)
|
||||||
const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], {
|
this.$bus.$emit('setData', res)
|
||||||
header: 1
|
|
||||||
})
|
|
||||||
if (data.length <= 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let max = 0
|
|
||||||
data.forEach(arr => {
|
|
||||||
if (arr.length > max) {
|
|
||||||
max = arr.length
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let layers = []
|
|
||||||
let walk = layer => {
|
|
||||||
if (!layers[layer]) {
|
|
||||||
layers[layer] = []
|
|
||||||
}
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
if (data[i][layer]) {
|
|
||||||
let node = {
|
|
||||||
data: {
|
|
||||||
text: data[i][layer]
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
_row: i
|
|
||||||
}
|
|
||||||
layers[layer].push(node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (layer < max - 1) {
|
|
||||||
walk(layer + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
walk(0)
|
|
||||||
let getParent = (arr, row) => {
|
|
||||||
for (let i = arr.length - 1; i >= 0; i--) {
|
|
||||||
if (row >= arr[i]._row) {
|
|
||||||
return arr[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 1; i < layers.length; i++) {
|
|
||||||
let arr = layers[i]
|
|
||||||
for (let j = 0; j < arr.length; j++) {
|
|
||||||
let item = arr[j]
|
|
||||||
let parent = getParent(layers[i - 1], item._row)
|
|
||||||
if (parent) {
|
|
||||||
parent.children.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$bus.$emit('setData', layers[0][0])
|
|
||||||
this.$message.success(this.$t('import.importSuccess'))
|
this.$message.success(this.$t('import.importSuccess'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@ -296,7 +308,7 @@ export default {
|
|||||||
fileReader.readAsText(file.raw)
|
fileReader.readAsText(file.raw)
|
||||||
fileReader.onload = async evt => {
|
fileReader.onload = async evt => {
|
||||||
try {
|
try {
|
||||||
let data = await markdown.transformMarkdownTo(evt.target.result)
|
let data = markdown.transformMarkdownTo(evt.target.result)
|
||||||
this.$bus.$emit('setData', data)
|
this.$bus.$emit('setData', data)
|
||||||
this.$message.success(this.$t('import.importSuccess'))
|
this.$message.success(this.$t('import.importSuccess'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<div class="scaleInfo">
|
<div class="scaleInfo">
|
||||||
<input
|
<input
|
||||||
|
ref="inputRef"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="scaleNum"
|
v-model="scaleNum"
|
||||||
@input="onScaleNumInput"
|
@input="onScaleNumInput"
|
||||||
@ -55,13 +56,16 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
mindMap(val, oldVal) {
|
mindMap(val, oldVal) {
|
||||||
if (val && !oldVal) {
|
if (val && !oldVal) {
|
||||||
this.mindMap.on('scale', scale => {
|
this.mindMap.on('scale', this.onScale)
|
||||||
this.scaleNum = this.toPer(scale)
|
this.mindMap.on('draw_click', this.onDrawClick)
|
||||||
})
|
|
||||||
this.scaleNum = this.toPer(this.mindMap.view.scale)
|
this.scaleNum = this.toPer(this.mindMap.view.scale)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.mindMap.off('scale', this.onScale)
|
||||||
|
this.mindMap.off('draw_click', this.onDrawClick)
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 转换成百分数
|
// 转换成百分数
|
||||||
toPer(scale) {
|
toPer(scale) {
|
||||||
@ -98,6 +102,14 @@ export default {
|
|||||||
const cy = this.mindMap.height / 2
|
const cy = this.mindMap.height / 2
|
||||||
this.mindMap.view.setScale(this.scaleNum / 100, cx, cy)
|
this.mindMap.view.setScale(this.scaleNum / 100, cx, cy)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onScale(scale) {
|
||||||
|
this.scaleNum = this.toPer(scale)
|
||||||
|
},
|
||||||
|
|
||||||
|
onDrawClick() {
|
||||||
|
if (this.$refs.inputRef) this.$refs.inputRef.blur()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -247,16 +247,16 @@
|
|||||||
<el-popover ref="popover4" placement="bottom" trigger="hover">
|
<el-popover ref="popover4" placement="bottom" trigger="hover">
|
||||||
<Color :color="style.fillColor" @change="changeFillColor"></Color>
|
<Color :color="style.fillColor" @change="changeFillColor"></Color>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</div>
|
<span class="name" style="margin-left: 20px;">{{
|
||||||
</div>
|
$t('style.gradientStyle')
|
||||||
<div class="row">
|
}}</span>
|
||||||
<div class="rowItem">
|
|
||||||
<span class="name">{{ $t('style.gradientStyle') }}</span>
|
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="style.gradientStyle"
|
v-model="style.gradientStyle"
|
||||||
@change="update('gradientStyle')"
|
@change="update('gradientStyle')"
|
||||||
></el-checkbox>
|
></el-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" v-if="style.gradientStyle">
|
||||||
<div class="rowItem">
|
<div class="rowItem">
|
||||||
<span class="name">{{ $t('style.startColor') }}</span>
|
<span class="name">{{ $t('style.startColor') }}</span>
|
||||||
<span
|
<span
|
||||||
@ -282,6 +282,24 @@
|
|||||||
<Color :color="style.endColor" @change="changeEndColor"></Color>
|
<Color :color="style.endColor" @change="changeEndColor"></Color>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="rowItem">
|
||||||
|
<span class="name">{{ $t('style.direction') }}</span>
|
||||||
|
<el-select
|
||||||
|
size="mini"
|
||||||
|
style="width: 80px"
|
||||||
|
v-model="style.linearGradientDir"
|
||||||
|
placeholder=""
|
||||||
|
@change="update('linearGradientDir')"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in linearGradientDirList"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 形状 -->
|
<!-- 形状 -->
|
||||||
<div class="title">{{ $t('style.shape') }}</div>
|
<div class="title">{{ $t('style.shape') }}</div>
|
||||||
@ -458,7 +476,8 @@ import {
|
|||||||
borderRadiusList,
|
borderRadiusList,
|
||||||
lineHeightList,
|
lineHeightList,
|
||||||
shapeList,
|
shapeList,
|
||||||
shapeListMap
|
shapeListMap,
|
||||||
|
linearGradientDirList
|
||||||
} from '@/config'
|
} from '@/config'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
@ -502,7 +521,8 @@ export default {
|
|||||||
lineMarkerDir: '',
|
lineMarkerDir: '',
|
||||||
gradientStyle: false,
|
gradientStyle: false,
|
||||||
startColor: '',
|
startColor: '',
|
||||||
endColor: ''
|
endColor: '',
|
||||||
|
linearGradientDir: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -522,6 +542,11 @@ export default {
|
|||||||
},
|
},
|
||||||
shapeListMap() {
|
shapeListMap() {
|
||||||
return shapeListMap[this.$i18n.locale] || shapeListMap.zh
|
return shapeListMap[this.$i18n.locale] || shapeListMap.zh
|
||||||
|
},
|
||||||
|
linearGradientDirList() {
|
||||||
|
return (
|
||||||
|
linearGradientDirList[this.$i18n.locale] || linearGradientDirList.zh
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -587,6 +612,24 @@ export default {
|
|||||||
].forEach(item => {
|
].forEach(item => {
|
||||||
this.style[item] = this.activeNodes[0].getStyle(item, false)
|
this.style[item] = this.activeNodes[0].getStyle(item, false)
|
||||||
})
|
})
|
||||||
|
this.initLinearGradientDir()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 初始化渐变方向样式
|
||||||
|
initLinearGradientDir() {
|
||||||
|
const startDir = this.activeNodes[0].getStyle('startDir', false)
|
||||||
|
const endDir = this.activeNodes[0].getStyle('endDir', false)
|
||||||
|
const target = this.linearGradientDirList.find(item => {
|
||||||
|
return (
|
||||||
|
item.start[0] === startDir[0] &&
|
||||||
|
item.start[1] === startDir[1] &&
|
||||||
|
item.end[0] === endDir[0] &&
|
||||||
|
item.end[1] === endDir[1]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if (target) {
|
||||||
|
this.style.linearGradientDir = target.value
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -595,9 +638,21 @@ export default {
|
|||||||
* @Desc: 修改样式
|
* @Desc: 修改样式
|
||||||
*/
|
*/
|
||||||
update(prop) {
|
update(prop) {
|
||||||
this.activeNodes.forEach(node => {
|
if (prop === 'linearGradientDir') {
|
||||||
node.setStyle(prop, this.style[prop])
|
const target = this.linearGradientDirList.find(item => {
|
||||||
})
|
return item.value === this.style.linearGradientDir
|
||||||
|
})
|
||||||
|
this.activeNodes.forEach(node => {
|
||||||
|
node.setStyles({
|
||||||
|
startDir: [...target.start],
|
||||||
|
endDir: [...target.end]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.activeNodes.forEach(node => {
|
||||||
|
node.setStyle(prop, this.style[prop])
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -31,6 +31,8 @@ const store = new Vuex.Store({
|
|||||||
supportHandDrawnLikeStyle: false, // 是否支持设置手绘风格
|
supportHandDrawnLikeStyle: false, // 是否支持设置手绘风格
|
||||||
supportMark: false, // 是否支持标记
|
supportMark: false, // 是否支持标记
|
||||||
supportNumbers: false, // 是否支持编号
|
supportNumbers: false, // 是否支持编号
|
||||||
|
supportFreemind: false, // 是否支持Freemind插件
|
||||||
|
supportExcel: false, // 是否支持Excel插件
|
||||||
isDragOutlineTreeNode: false // 当前是否正在拖拽大纲树的节点
|
isDragOutlineTreeNode: false // 当前是否正在拖拽大纲树的节点
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
@ -93,6 +95,16 @@ const store = new Vuex.Store({
|
|||||||
state.supportNumbers = data
|
state.supportNumbers = data
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 设置是否支持Freemind插件
|
||||||
|
setSupportFreemind(state, data) {
|
||||||
|
state.supportFreemind = data
|
||||||
|
},
|
||||||
|
|
||||||
|
// 设置是否支持Excel插件
|
||||||
|
setSupportExcel(state, data) {
|
||||||
|
state.supportExcel = data
|
||||||
|
},
|
||||||
|
|
||||||
// 设置树节点拖拽
|
// 设置树节点拖拽
|
||||||
setIsDragOutlineTreeNode(state, data) {
|
setIsDragOutlineTreeNode(state, data) {
|
||||||
state.isDragOutlineTreeNode = data
|
state.isDragOutlineTreeNode = data
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user