diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js
index 4bfa6c31..c6be1777 100644
--- a/simple-mind-map/src/core/render/Render.js
+++ b/simple-mind-map/src/core/render/Render.js
@@ -65,7 +65,9 @@ class Render {
this.mindMap = opt.mindMap
this.themeConfig = this.mindMap.themeConfig
// 渲染树,操作过程中修改的都是这里的数据
- this.renderTree = this.mindMap.opt.data ? merge({}, this.mindMap.opt.data) : null
+ this.renderTree = this.mindMap.opt.data
+ ? merge({}, this.mindMap.opt.data)
+ : null
// 是否重新渲染
this.reRender = false
// 是否正在渲染中
@@ -246,6 +248,9 @@ class Render {
// 设置节点备注
this.setNodeNote = this.setNodeNote.bind(this)
this.mindMap.command.add('SET_NODE_NOTE', this.setNodeNote)
+ // 设置节点附件
+ this.setNodeAttachment = this.setNodeAttachment.bind(this)
+ this.mindMap.command.add('SET_NODE_ATTACHMENT', this.setNodeAttachment)
// 设置节点标签
this.setNodeTag = this.setNodeTag.bind(this)
this.mindMap.command.add('SET_NODE_TAG', this.setNodeTag)
@@ -1600,6 +1605,14 @@ class Render {
})
}
+ // 设置节点附件
+ setNodeAttachment(node, url, name = '') {
+ this.setNodeDataRender(node, {
+ attachmentUrl: url,
+ attachmentName: name
+ })
+ }
+
// 设置节点标签
setNodeTag(node, tag) {
this.setNodeDataRender(node, {
diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js
index 80863b74..8a1cad17 100644
--- a/simple-mind-map/src/core/render/node/Node.js
+++ b/simple-mind-map/src/core/render/node/Node.js
@@ -75,6 +75,7 @@ class Node {
this._noteData = null
this.noteEl = null
this.noteContentIsShow = false
+ this._attachmentData = null
this._expandBtn = null
this._lastExpandBtnType = null
this._showExpandBtn = false
@@ -199,6 +200,7 @@ class Node {
this._hyperlinkData = this.createHyperlinkNode()
this._tagData = this.createTagNode()
this._noteData = this.createNoteNode()
+ this._attachmentData = this.createAttachmentNode()
}
// 计算节点的宽高
@@ -267,6 +269,11 @@ class Node {
textContentWidth += this._noteData.width
textContentHeight = Math.max(textContentHeight, this._noteData.height)
}
+ // 附件
+ if (this._attachmentData) {
+ textContentWidth += this._attachmentData.width
+ textContentHeight = Math.max(textContentHeight, this._attachmentData.height)
+ }
// 文字内容部分的尺寸
this._rectInfo.textContentWidth = textContentWidth
this._rectInfo.textContentHeight = textContentHeight
@@ -399,6 +406,14 @@ class Node {
textContentNested.add(this._noteData.node)
textContentOffsetX += this._noteData.width
}
+ // 附件
+ if (this._attachmentData) {
+ this._attachmentData.node
+ .x(textContentOffsetX)
+ .y((this._rectInfo.textContentHeight - this._attachmentData.height) / 2)
+ textContentNested.add(this._attachmentData.node)
+ textContentOffsetX += this._attachmentData.width
+ }
// 文字内容整体
textContentNested.translate(
width / 2 - textContentNested.bbox().width / 2,
diff --git a/simple-mind-map/src/core/render/node/nodeCommandWraps.js b/simple-mind-map/src/core/render/node/nodeCommandWraps.js
index 1a51130e..3f961cfa 100644
--- a/simple-mind-map/src/core/render/node/nodeCommandWraps.js
+++ b/simple-mind-map/src/core/render/node/nodeCommandWraps.js
@@ -28,6 +28,11 @@ function setNote(note) {
this.mindMap.execCommand('SET_NODE_NOTE', this, note)
}
+// 设置附件
+function setAttachment(url, name) {
+ this.mindMap.execCommand('SET_NODE_ATTACHMENT', this, url, name)
+}
+
// 设置标签
function setTag(tag) {
this.mindMap.execCommand('SET_NODE_TAG', this, tag)
@@ -55,6 +60,7 @@ export default {
setIcon,
setHyperlink,
setNote,
+ setAttachment,
setTag,
setShape,
setStyle,
diff --git a/simple-mind-map/src/core/render/node/nodeCreateContents.js b/simple-mind-map/src/core/render/node/nodeCreateContents.js
index 8fe5f3c7..20fda808 100644
--- a/simple-mind-map/src/core/render/node/nodeCreateContents.js
+++ b/simple-mind-map/src/core/render/node/nodeCreateContents.js
@@ -262,7 +262,7 @@ function createHyperlinkNode() {
e.stopPropagation()
})
if (hyperlinkTitle) {
- a.attr('title', hyperlinkTitle)
+ node.add(SVG(`
${hyperlinkTitle}`))
}
// 添加一个透明的层,作为鼠标区域
a.rect(iconSize, iconSize).fill({ color: 'transparent' })
@@ -368,6 +368,36 @@ function createNoteNode() {
}
}
+// 创建附件节点
+function createAttachmentNode() {
+ const { attachmentUrl, attachmentName } = this.getData()
+ if (!attachmentUrl) {
+ return
+ }
+ const iconSize = this.mindMap.themeConfig.iconSize
+ const node = new SVG().attr('cursor', 'pointer').size(iconSize, iconSize)
+ if (attachmentName) {
+ node.add(SVG(`${attachmentName}`))
+ }
+ // 透明的层,用来作为鼠标区域
+ node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' }))
+ // 备注图标
+ const iconNode = SVG(iconsSvg.attachment).size(iconSize, iconSize)
+ this.style.iconNode(iconNode)
+ node.add(iconNode)
+ node.on('click', e => {
+ this.mindMap.emit('node_attachmentClick', this, e, node)
+ })
+ node.on('contextmenu', e => {
+ this.mindMap.emit('node_attachmentContextmenu', this, e, node)
+ })
+ return {
+ node,
+ width: iconSize,
+ height: iconSize
+ }
+}
+
// 获取节点备注显示位置
function getNoteContentPosition() {
const iconSize = this.mindMap.themeConfig.iconSize
@@ -415,6 +445,7 @@ export default {
createHyperlinkNode,
createTagNode,
createNoteNode,
+ createAttachmentNode,
getNoteContentPosition,
measureCustomNodeContentSize,
isUseCustomNodeContent
diff --git a/simple-mind-map/src/core/view/View.js b/simple-mind-map/src/core/view/View.js
index 8def88a3..41ee92ba 100644
--- a/simple-mind-map/src/core/view/View.js
+++ b/simple-mind-map/src/core/view/View.js
@@ -158,7 +158,8 @@ class View {
...viewData.transform
})
this.mindMap.emit('view_data_change', this.getTransformData())
- this.mindMap.emit('scale', this.scale)
+ this.emitEvent('scale')
+ this.emitEvent('translate')
}
}
@@ -168,6 +169,7 @@ class View {
this.x += x
this.y += y
this.transform()
+ this.emitEvent('translate')
}
// 平移x方向
@@ -175,12 +177,14 @@ class View {
if (step === 0) return
this.x += step
this.transform()
+ this.emitEvent('translate')
}
// 平移x方式到
translateXTo(x) {
this.x = x
this.transform()
+ this.emitEvent('translate')
}
// 平移y方向
@@ -188,12 +192,14 @@ class View {
if (step === 0) return
this.y += step
this.transform()
+ this.emitEvent('translate')
}
// 平移y方向到
translateYTo(y) {
this.y = y
this.transform()
+ this.emitEvent('translate')
}
// 应用变换
@@ -211,13 +217,17 @@ class View {
// 恢复
reset() {
- let scaleChange = this.scale !== 1
+ const scaleChange = this.scale !== 1
+ const translateChange = this.x !== 0 || this.y !== 0
this.scale = 1
this.x = 0
this.y = 0
this.transform()
if (scaleChange) {
- this.mindMap.emit('scale', this.scale)
+ this.emitEvent('scale')
+ }
+ if (translateChange) {
+ this.emitEvent('translate')
}
}
@@ -227,7 +237,7 @@ class View {
const scale = Math.max(this.scale - scaleRatio, 0.1)
this.scaleInCenter(scale, cx, cy)
this.transform()
- this.mindMap.emit('scale', this.scale)
+ this.emitEvent('scale')
}
// 放大
@@ -236,7 +246,7 @@ class View {
const scale = this.scale + scaleRatio
this.scaleInCenter(scale, cx, cy)
this.transform()
- this.mindMap.emit('scale', this.scale)
+ this.emitEvent('scale')
}
// 基于指定中心进行缩放,cx,cy 可不指定,此时会使用画布中心点
@@ -262,7 +272,7 @@ class View {
this.scale = scale
}
this.transform()
- this.mindMap.emit('scale', this.scale)
+ this.emitEvent('scale')
}
// 适应画布大小
@@ -394,6 +404,16 @@ class View {
bottom
}
}
+
+ // 派发事件
+ emitEvent(type) {
+ switch (type) {
+ case 'scale':
+ this.mindMap.emit('scale', this.scale)
+ case 'translate':
+ this.mindMap.emit('translate', this.x, this.y)
+ }
+ }
}
export default View
diff --git a/simple-mind-map/src/plugins/Search.js b/simple-mind-map/src/plugins/Search.js
index 7b5d69a4..8e7727ac 100644
--- a/simple-mind-map/src/plugins/Search.js
+++ b/simple-mind-map/src/plugins/Search.js
@@ -86,6 +86,7 @@ class Search {
this.matchNodeList = []
this.currentIndex = -1
const { isOnlySearchCurrentRenderNodes } = this.mindMap.opt
+ // 如果要搜索收起来的节点,那么要遍历渲染树而不是节点树
const tree = isOnlySearchCurrentRenderNodes
? this.mindMap.renderer.root
: this.mindMap.renderer.renderTree
@@ -103,6 +104,11 @@ class Search {
})
}
+ // 判断对象是否是节点实例
+ isNodeInstance(node) {
+ return node instanceof Node
+ }
+
// 搜索下一个,定位到下一个匹配节点
searchNext(callback) {
if (!this.isSearching || this.matchNodeList.length <= 0) return
@@ -113,13 +119,12 @@ class Search {
}
const currentNode = this.matchNodeList[this.currentIndex]
this.notResetSearchText = true
- const uid =
- currentNode instanceof Node
- ? currentNode.getData('uid')
- : currentNode.data.uid
+ const uid = this.isNodeInstance(currentNode)
+ ? currentNode.getData('uid')
+ : currentNode.data.uid
const targetNode = this.mindMap.renderer.findNodeByUid(uid)
this.mindMap.execCommand('GO_TARGET_NODE', uid, node => {
- if (!(currentNode instanceof Node)) {
+ if (!this.isNodeInstance(currentNode)) {
this.matchNodeList[this.currentIndex] = node
}
callback()
@@ -173,15 +178,20 @@ class Search {
return
replaceText = String(replaceText)
this.matchNodeList.forEach(node => {
- let text = this.getReplacedText(node, this.searchText, replaceText)
- this.mindMap.renderer.setNodeDataRender(
- node,
- {
- text,
- resetRichText: !!node.getData('richText')
- },
- true
- )
+ const text = this.getReplacedText(node, this.searchText, replaceText)
+ if (this.isNodeInstance(node)) {
+ this.mindMap.renderer.setNodeDataRender(
+ node,
+ {
+ text,
+ resetRichText: !!node.getData('richText')
+ },
+ true
+ )
+ } else {
+ node.data.text = text
+ node.data.resetRichText = !!node.data.richText
+ }
})
this.mindMap.render()
this.mindMap.command.addHistory()
@@ -190,7 +200,9 @@ class Search {
// 获取某个节点替换后的文本
getReplacedText(node, searchText, replaceText) {
- let { richText, text } = node.getData()
+ let { richText, text } = this.isNodeInstance(node)
+ ? node.getData()
+ : node.data
if (richText) {
return replaceHtmlText(text, searchText, replaceText)
} else {
diff --git a/simple-mind-map/src/svg/icons.js b/simple-mind-map/src/svg/icons.js
index cafe49d4..f056545e 100644
--- a/simple-mind-map/src/svg/icons.js
+++ b/simple-mind-map/src/svg/icons.js
@@ -8,6 +8,10 @@ const hyperlink =
const note =
''
+// 附件图标
+const attachment =
+ ''
+
// 节点icon
export const nodeIconList = [
{
@@ -303,6 +307,7 @@ const getNodeIconListIcon = (name, extendIconList = []) => {
export default {
hyperlink,
note,
+ attachment,
nodeIconList,
getNodeIconListIcon
}
diff --git a/web/src/assets/icon-font/iconfont.css b/web/src/assets/icon-font/iconfont.css
index a7b6d1bc..063864fb 100644
--- a/web/src/assets/icon-font/iconfont.css
+++ b/web/src/assets/icon-font/iconfont.css
@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2479351 */
- src: url('iconfont.woff2?t=1711536835850') format('woff2'),
- url('iconfont.woff?t=1711536835850') format('woff'),
- url('iconfont.ttf?t=1711536835850') format('truetype');
+ src: url('iconfont.woff2?t=1711935414521') format('woff2'),
+ url('iconfont.woff?t=1711935414521') format('woff'),
+ url('iconfont.ttf?t=1711935414521') format('truetype');
}
.iconfont {
@@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
+.iconfujian:before {
+ content: "\e88a";
+}
+
.icongeshihua:before {
content: "\e7a3";
}
diff --git a/web/src/assets/icon-font/iconfont.ttf b/web/src/assets/icon-font/iconfont.ttf
index 530a259c..bb0fbedf 100644
Binary files a/web/src/assets/icon-font/iconfont.ttf and b/web/src/assets/icon-font/iconfont.ttf differ
diff --git a/web/src/assets/icon-font/iconfont.woff b/web/src/assets/icon-font/iconfont.woff
index 670ceb03..fdb5ff4b 100644
Binary files a/web/src/assets/icon-font/iconfont.woff and b/web/src/assets/icon-font/iconfont.woff differ
diff --git a/web/src/assets/icon-font/iconfont.woff2 b/web/src/assets/icon-font/iconfont.woff2
index f330a8bb..4225466d 100644
Binary files a/web/src/assets/icon-font/iconfont.woff2 and b/web/src/assets/icon-font/iconfont.woff2 differ
diff --git a/web/src/lang/en_us.js b/web/src/lang/en_us.js
index 6f01ed98..8eafa1e7 100644
--- a/web/src/lang/en_us.js
+++ b/web/src/lang/en_us.js
@@ -261,6 +261,7 @@ export default {
save: 'Save',
painter: 'Painter',
formula: 'Formula',
+ attachment: 'Attachment',
more: 'More',
selectFileTip: 'Please select a file',
notSupportTip:
@@ -333,5 +334,8 @@ export default {
formatErrorTip: 'The JSON format is incorrect. Please check and try again',
copyTip: 'Copied to clipboard',
formatTip: 'Format complete'
+ },
+ attachment: {
+ deleteAttachment: 'Delete attachment'
}
}
diff --git a/web/src/lang/zh_cn.js b/web/src/lang/zh_cn.js
index 1eda5518..f0e2a546 100644
--- a/web/src/lang/zh_cn.js
+++ b/web/src/lang/zh_cn.js
@@ -258,6 +258,7 @@ export default {
save: '保存',
painter: '格式刷',
formula: '公式',
+ attachment: '附件',
more: '更多',
selectFileTip: '请选择文件',
notSupportTip: '你的浏览器不支持该功能,或者当前页面非https协议',
@@ -327,5 +328,8 @@ export default {
formatErrorTip: 'JSON格式有误,请检查后再试',
copyTip: '已复制到剪贴板',
formatTip: '格式化完成'
+ },
+ attachment: {
+ deleteAttachment: '删除附件'
}
}
diff --git a/web/src/pages/Edit/components/Contextmenu.vue b/web/src/pages/Edit/components/Contextmenu.vue
index 325a64cf..6eefe6cb 100644
--- a/web/src/pages/Edit/components/Contextmenu.vue
+++ b/web/src/pages/Edit/components/Contextmenu.vue
@@ -225,6 +225,7 @@ export default {
this.$bus.$on('expand_btn_click', this.hide)
this.$bus.$on('svg_mousedown', this.onMousedown)
this.$bus.$on('mouseup', this.onMouseup)
+ this.$bus.$on('translate', this.hide)
},
beforeDestroy() {
this.$bus.$off('node_contextmenu', this.show)
@@ -233,6 +234,7 @@ export default {
this.$bus.$off('expand_btn_click', this.hide)
this.$bus.$off('svg_mousedown', this.onMousedown)
this.$bus.$off('mouseup', this.onMouseup)
+ this.$bus.$off('translate', this.hide)
},
methods: {
...mapMutations(['setLocalConfig']),
diff --git a/web/src/pages/Edit/components/Edit.vue b/web/src/pages/Edit/components/Edit.vue
index ae86fbb7..6f0a3426 100644
--- a/web/src/pages/Edit/components/Edit.vue
+++ b/web/src/pages/Edit/components/Edit.vue
@@ -16,6 +16,7 @@
v-if="mindMap"
:mindMap="mindMap"
>
+
@@ -87,6 +88,7 @@ import Scrollbar from './Scrollbar.vue'
import exampleData from 'simple-mind-map/example/exampleData'
import FormulaSidebar from './FormulaSidebar.vue'
import SourceCodeEdit from './SourceCodeEdit.vue'
+import NodeAttachment from './NodeAttachment.vue'
// 注册插件
MindMap.usePlugin(MiniMap)
@@ -139,7 +141,8 @@ export default {
OutlineEdit,
Scrollbar,
FormulaSidebar,
- SourceCodeEdit
+ SourceCodeEdit,
+ NodeAttachment
},
data() {
return {
@@ -532,7 +535,10 @@ export default {
'painter_start',
'painter_end',
'scrollbar_change',
- 'scale'
+ 'scale',
+ 'translate',
+ 'node_attachmentClick',
+ 'node_attachmentContextmenu'
].forEach(event => {
this.mindMap.on(event, (...args) => {
this.$bus.$emit(event, ...args)
diff --git a/web/src/pages/Edit/components/NodeAttachment.vue b/web/src/pages/Edit/components/NodeAttachment.vue
new file mode 100644
index 00000000..63d92250
--- /dev/null
+++ b/web/src/pages/Edit/components/NodeAttachment.vue
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
diff --git a/web/src/pages/Edit/components/NodeNoteContentShow.vue b/web/src/pages/Edit/components/NodeNoteContentShow.vue
index 19a7c9e4..ac5cdf21 100644
--- a/web/src/pages/Edit/components/NodeNoteContentShow.vue
+++ b/web/src/pages/Edit/components/NodeNoteContentShow.vue
@@ -37,6 +37,7 @@ export default {
document.body.addEventListener('click', this.hideNoteContent)
this.$bus.$on('node_active', this.hideNoteContent)
this.$bus.$on('scale', this.onScale)
+ this.$bus.$on('translate', this.onScale)
this.$bus.$on('svg_mousedown', this.hideNoteContent)
},
mounted() {
@@ -48,6 +49,7 @@ export default {
document.body.removeEventListener('click', this.hideNoteContent)
this.$bus.$off('node_active', this.hideNoteContent)
this.$bus.$off('scale', this.onScale)
+ this.$bus.$off('translate', this.onScale)
this.$bus.$off('svg_mousedown', this.hideNoteContent)
},
methods: {
@@ -97,6 +99,8 @@ export default {
border-radius: 5px;
max-height: 300px;
overflow-y: auto;
+ box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.06);
+ border: 1px solid rgba(0, 0, 0, 0.06);
&::-webkit-scrollbar {
width: 7px;
diff --git a/web/src/pages/Edit/components/Toolbar.vue b/web/src/pages/Edit/components/Toolbar.vue
index e90635b5..6b91638f 100644
--- a/web/src/pages/Edit/components/Toolbar.vue
+++ b/web/src/pages/Edit/components/Toolbar.vue
@@ -184,6 +184,7 @@ export default {
'summary',
'associativeLine',
'formula'
+ // 'attachment'
],
horizontalList: [],
verticalList: [],
diff --git a/web/src/pages/Edit/components/ToolbarNodeBtnList.vue b/web/src/pages/Edit/components/ToolbarNodeBtnList.vue
index 7e84e0b2..70eedb6f 100644
--- a/web/src/pages/Edit/components/ToolbarNodeBtnList.vue
+++ b/web/src/pages/Edit/components/ToolbarNodeBtnList.vue
@@ -156,6 +156,17 @@
{{ $t('toolbar.formula') }}
+
+
+ {{ $t('toolbar.attachment') }}
+
@@ -258,6 +269,11 @@ export default {
// 打开公式侧边栏
showFormula() {
this.setActiveSidebar('formulaSidebar')
+ },
+
+ // 选择附件
+ selectAttachmentFile() {
+ this.$bus.$emit('selectAttachment', this.activeNodes)
}
}
}