Demo:单独编辑大纲不再和画布联动,优化大数据量下的编辑体验

This commit is contained in:
wanglin2 2023-08-16 09:25:33 +08:00
parent 002ec41ba8
commit 9af8afca22
4 changed files with 292 additions and 11 deletions

View File

@ -145,7 +145,8 @@ export default {
addTip: 'Press Enter to add'
},
outline: {
title: 'Outline'
title: 'Outline',
nodeDefaultText: 'Branch node'
},
scale: {
zoomIn: 'Zoom in',

View File

@ -145,7 +145,8 @@ export default {
addTip: '请按回车键添加'
},
outline: {
title: '大纲'
title: '大纲',
nodeDefaultText: '分支节点'
},
scale: {
zoomIn: '放大',

View File

@ -69,7 +69,7 @@ export default {
}
},
computed: {
...mapState(['isDark', 'isOutlineEdit'])
...mapState(['isDark'])
},
created() {
window.addEventListener('keydown', this.onKeyDown)

View File

@ -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>