mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-21 10:27:44 +08:00
Demo:单独编辑大纲不再和画布联动,优化大数据量下的编辑体验
This commit is contained in:
parent
002ec41ba8
commit
9af8afca22
@ -145,7 +145,8 @@ export default {
|
||||
addTip: 'Press Enter to add'
|
||||
},
|
||||
outline: {
|
||||
title: 'Outline'
|
||||
title: 'Outline',
|
||||
nodeDefaultText: 'Branch node'
|
||||
},
|
||||
scale: {
|
||||
zoomIn: 'Zoom in',
|
||||
|
||||
@ -145,7 +145,8 @@ export default {
|
||||
addTip: '请按回车键添加'
|
||||
},
|
||||
outline: {
|
||||
title: '大纲'
|
||||
title: '大纲',
|
||||
nodeDefaultText: '分支节点'
|
||||
},
|
||||
scale: {
|
||||
zoomIn: '放大',
|
||||
|
||||
@ -69,7 +69,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isDark', 'isOutlineEdit'])
|
||||
...mapState(['isDark'])
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('keydown', this.onKeyDown)
|
||||
|
||||
@ -10,7 +10,38 @@
|
||||
</div>
|
||||
<div class="outlineEditBox" ref="outlineEditBox">
|
||||
<div class="outlineEdit">
|
||||
<Outline :mindMap="mindMap" @scrollTo="onScrollTo"></Outline>
|
||||
<el-tree
|
||||
ref="tree"
|
||||
class="outlineTree"
|
||||
node-key="uid"
|
||||
draggable
|
||||
default-expand-all
|
||||
:class="{ isDark: isDark }"
|
||||
:data="data"
|
||||
:props="defaultProps"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false"
|
||||
:allow-drag="checkAllowDrag"
|
||||
@node-drop="onNodeDrop"
|
||||
@current-change="onCurrentChange"
|
||||
>
|
||||
<span
|
||||
class="customNode"
|
||||
slot-scope="{ node, data }"
|
||||
:data-id="data.uid"
|
||||
>
|
||||
<span
|
||||
class="nodeEdit"
|
||||
contenteditable="true"
|
||||
:key="getKey()"
|
||||
@blur="onBlur($event, node)"
|
||||
@keydown.stop="onNodeInputKeydown($event, node)"
|
||||
@keyup.stop
|
||||
@paste="onPaste($event, node)"
|
||||
v-html="node.label"
|
||||
></span>
|
||||
</span>
|
||||
</el-tree>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -18,39 +49,190 @@
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import Outline from './Outline.vue'
|
||||
import {
|
||||
nodeRichTextToTextWithWrap,
|
||||
textToNodeRichTextWithWrap,
|
||||
getTextFromHtml,
|
||||
createUid,
|
||||
simpleDeepClone
|
||||
} from 'simple-mind-map/src/utils'
|
||||
import { storeData } from '@/api'
|
||||
|
||||
// 大纲侧边栏
|
||||
export default {
|
||||
name: 'OutlineEdit',
|
||||
components: {
|
||||
Outline
|
||||
},
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
data: [],
|
||||
defaultProps: {
|
||||
label: 'label'
|
||||
},
|
||||
currentData: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isOutlineEdit', 'isDark'])
|
||||
},
|
||||
watch: {
|
||||
isOutlineEdit(val) {
|
||||
if (val) {
|
||||
this.refresh()
|
||||
this.$nextTick(() => {
|
||||
document.body.appendChild(this.$refs.outlineEditContainer)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('keydown', this.onKeyDown)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('keydown', this.onKeyDown)
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setIsOutlineEdit']),
|
||||
|
||||
onClose() {
|
||||
this.setIsOutlineEdit(false)
|
||||
// 刷新树数据
|
||||
refresh() {
|
||||
let data = this.mindMap.getData()
|
||||
data.root = true // 标记根节点
|
||||
let walk = root => {
|
||||
const text = (root.data.richText
|
||||
? nodeRichTextToTextWithWrap(root.data.text)
|
||||
: root.data.text
|
||||
).replaceAll(/\n/g, '<br>')
|
||||
root.textCache = text // 保存一份修改前的数据,用于对比是否修改了
|
||||
root.label = text
|
||||
root.uid = root.data.uid
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach(item => {
|
||||
walk(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(data)
|
||||
this.data = [data]
|
||||
},
|
||||
|
||||
onScrollTo(y) {
|
||||
// 根节点不允许拖拽
|
||||
checkAllowDrag(node) {
|
||||
return !node.data.root
|
||||
},
|
||||
|
||||
// 拖拽结束事件
|
||||
onNodeDrop() {
|
||||
this.save()
|
||||
},
|
||||
|
||||
// 当前选中的树节点变化事件
|
||||
onCurrentChange(data) {
|
||||
this.currentData = data
|
||||
},
|
||||
|
||||
// 失去焦点更新节点文本
|
||||
onBlur(e, node) {
|
||||
// 节点数据没有修改
|
||||
if (node.data.textCache === e.target.innerHTML) {
|
||||
return
|
||||
}
|
||||
const richText = node.data.data.richText
|
||||
const text = richText ? e.target.innerHTML : e.target.innerText
|
||||
node.data.data.text = richText ? textToNodeRichTextWithWrap(text) : text
|
||||
if (richText) node.data.data.resetRichText = true
|
||||
node.data.textCache = e.target.innerHTML
|
||||
this.save()
|
||||
},
|
||||
|
||||
// 节点输入区域按键事件
|
||||
onNodeInputKeydown(e, node) {
|
||||
const richText = !!node.data.data.richText
|
||||
const uid = createUid()
|
||||
const text = this.$t('outline.nodeDefaultText')
|
||||
const data = {
|
||||
textCache: text,
|
||||
uid,
|
||||
label: text,
|
||||
data: {
|
||||
text: richText ? textToNodeRichTextWithWrap(text) : text,
|
||||
uid,
|
||||
richText
|
||||
},
|
||||
children: []
|
||||
}
|
||||
if (richText) {
|
||||
data.data.resetRichText = true
|
||||
}
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
this.$refs.tree.insertAfter(data, node)
|
||||
}
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault()
|
||||
this.$refs.tree.append(data, node)
|
||||
}
|
||||
this.save()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.tree.setCurrentKey(uid)
|
||||
const el = document.querySelector(
|
||||
`.customNode[data-id="${uid}"] .nodeEdit`
|
||||
)
|
||||
if (el) {
|
||||
let selection = window.getSelection()
|
||||
let range = document.createRange()
|
||||
range.selectNodeContents(el)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
let offsetTop = el.offsetTop
|
||||
this.scrollTo(offsetTop)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 删除节点
|
||||
onKeyDown(e) {
|
||||
if (!this.isOutlineEdit) return
|
||||
if ([46, 8].includes(e.keyCode) && this.currentData) {
|
||||
e.stopPropagation()
|
||||
this.$refs.tree.remove(this.currentData)
|
||||
this.currentData = null
|
||||
this.save()
|
||||
}
|
||||
},
|
||||
|
||||
// 拦截粘贴事件
|
||||
onPaste(e) {
|
||||
e.preventDefault()
|
||||
const selection = window.getSelection()
|
||||
if (!selection.rangeCount) return
|
||||
selection.deleteFromDocument()
|
||||
let text = (e.clipboardData || window.clipboardData).getData('text')
|
||||
// 去除格式
|
||||
text = getTextFromHtml(text)
|
||||
// 去除换行
|
||||
text = text.replaceAll(/\n/g, '')
|
||||
const node = document.createTextNode(text)
|
||||
selection.getRangeAt(0).insertNode(node)
|
||||
selection.collapseToEnd()
|
||||
},
|
||||
|
||||
// 生成唯一的key
|
||||
getKey() {
|
||||
return Math.random()
|
||||
},
|
||||
|
||||
// 关闭
|
||||
onClose() {
|
||||
this.setIsOutlineEdit(false)
|
||||
this.$bus.$emit('setData', this.getData())
|
||||
},
|
||||
|
||||
// 滚动
|
||||
scrollTo(y) {
|
||||
let container = this.$refs.outlineEditBox
|
||||
let height = container.offsetHeight
|
||||
let top = container.scrollTop
|
||||
@ -58,6 +240,28 @@ export default {
|
||||
if (y > top + height) {
|
||||
container.scrollTo(0, y - height / 2)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取思维导图数据
|
||||
getData() {
|
||||
let newNode = {}
|
||||
let node = this.data[0]
|
||||
let walk = (root, newRoot) => {
|
||||
newRoot.data = root.data
|
||||
newRoot.children = []
|
||||
;(root.children || []).forEach(child => {
|
||||
const newChild = {}
|
||||
newRoot.children.push(newChild)
|
||||
walk(child, newChild)
|
||||
})
|
||||
}
|
||||
walk(node, newNode)
|
||||
return simpleDeepClone(newNode)
|
||||
},
|
||||
|
||||
// 保存
|
||||
save() {
|
||||
storeData(this.getData())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,4 +319,79 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.customNode {
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-weight: bold;
|
||||
|
||||
.nodeEdit {
|
||||
outline: none;
|
||||
white-space: normal;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.outlineTree {
|
||||
&.isDark {
|
||||
background-color: #262a2e;
|
||||
|
||||
.customNode {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.el-tree--highlight-current {
|
||||
/deep/ .el-tree-node.is-current > .el-tree-node__content {
|
||||
background-color: hsla(0, 0%, 100%, 0.05) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .el-tree-node__content:hover,
|
||||
.el-upload-list__item:hover {
|
||||
background-color: hsla(0, 0%, 100%, 0.02) !important;
|
||||
}
|
||||
|
||||
/deep/ .el-tree-node__content {
|
||||
.el-tree-node__expand-icon {
|
||||
color: #fff;
|
||||
|
||||
&.is-leaf {
|
||||
&::after {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .el-tree-node > .el-tree-node__children {
|
||||
overflow: inherit;
|
||||
}
|
||||
|
||||
/deep/ .el-tree-node__content {
|
||||
height: auto;
|
||||
margin: 5px 0;
|
||||
|
||||
.el-tree-node__expand-icon {
|
||||
color: #262a2e;
|
||||
|
||||
&.is-leaf {
|
||||
color: transparent;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
background-color: #262a2e;
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user