Demo:扩展大纲功能,支持拖拽,删除

This commit is contained in:
wanglin2 2023-08-06 22:37:13 +08:00
parent 6bdcec0fca
commit ff56fe3e68
4 changed files with 195 additions and 53 deletions

View File

@ -1,22 +1,32 @@
<template>
<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"
default-expand-all
:allow-drag="checkAllowDrag"
@node-drop="onNodeDrop"
@current-change="onCurrentChange"
@mouseenter.native="isInTreArea = true"
@mouseleave.native="isInTreArea = false"
>
<span
class="customNode"
slot-scope="{ node, data }"
@click="onClick($event, node)"
:data-id="data.uid"
@click="onClick($event, data)"
>
<span
class="nodeEdit"
:key="getKey()"
contenteditable="true"
@keydown.stop="onKeydown($event, node)"
:key="getKey()"
@keydown.stop="onNodeInputKeydown($event, node)"
@keyup.stop
@blur="onBlur($event, node)"
@paste="onPaste($event, node)"
@ -31,7 +41,8 @@ import { mapState } from 'vuex'
import {
nodeRichTextToTextWithWrap,
textToNodeRichTextWithWrap,
getTextFromHtml
getTextFromHtml,
createUid
} from 'simple-mind-map/src/utils'
//
@ -46,47 +57,114 @@ export default {
return {
data: [],
defaultProps: {
label(data) {
const text = (data.data.richText
? nodeRichTextToTextWithWrap(data.data.text)
: data.data.text
).replaceAll(/\n/g, '<br>')
data.data.textCache = text
return text
}
label: 'label'
},
notHandleDataChange: false
currentData: null,
notHandleDataChange: false,
handleNodeTreeRenderEnd: false,
beInsertNodeUid: '',
isInTreArea: false
}
},
computed: {
...mapState(['isDark','isOutlineEdit'])
...mapState(['isDark', 'isOutlineEdit'])
},
created() {
this.$bus.$on('data_change', data => {
window.addEventListener('keydown', this.onKeyDown)
this.$bus.$on('data_change', () => {
//
if (this.notHandleDataChange) {
this.notHandleDataChange = false
return
}
this.data = [this.mindMap.renderer.renderTree]
this.refresh()
})
this.$bus.$on('node_tree_render_end', () => {
//
if (this.handleNodeTreeRenderEnd) {
this.handleNodeTreeRenderEnd = false
this.notHandleDataChange = false
this.refresh()
this.$nextTick(() => {
this.afterCreateNewNode()
})
}
})
},
mounted() {
this.refresh()
},
beforeDestroy() {
window.removeEventListener('keydown', this.onKeyDown)
},
methods: {
//
refresh() {
this.data = [this.mindMap.renderer.renderTree]
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]
},
//
afterCreateNewNode() {
//
let id = this.beInsertNodeUid
if (id && this.$refs.tree) {
//
this.$refs.tree.setCurrentKey(id)
let node = this.$refs.tree.getNode(id)
this.onCurrentChange(node.data)
//
this.onClick(null, node.data)
//
const el = document.querySelector(
`.customNode[data-id="${id}"] .nodeEdit`
)
if (el) {
let selection = window.getSelection()
let range = document.createRange()
range.selectNodeContents(el)
selection.removeAllRanges()
selection.addRange(range)
let offsetTop = el.offsetTop
this.$emit('scrollTo', offsetTop)
}
}
},
//
checkAllowDrag(node) {
return !node.data.root
},
//
onBlur(e, node) {
if (node.data.data.textCache === e.target.innerHTML) {
if (node.data.textCache === e.target.innerHTML) {
return
}
delete node.data.data.textCache
const richText = node.data.data.richText
const text = richText ? e.target.innerHTML : e.target.innerText
const targetNode = this.mindMap.renderer.findNodeByUid(node.data.uid)
if (!targetNode) return
if (richText) {
node.data._node.setText(textToNodeRichTextWithWrap(text), true, true)
targetNode.setText(textToNodeRichTextWithWrap(text), true, true)
} else {
node.data._node.setText(text)
targetNode.setText(text)
}
},
@ -106,11 +184,13 @@ export default {
selection.collapseToEnd()
},
// key
getKey() {
return Math.random()
},
onKeydown(e) {
//
onNodeInputKeydown(e) {
if (e.keyCode === 13 && !e.shiftKey) {
e.preventDefault()
this.insertNode()
@ -123,25 +203,76 @@ export default {
//
insertNode() {
this.notHandleDataChange = false
this.mindMap.execCommand('INSERT_NODE', false)
this.notHandleDataChange = true
this.handleNodeTreeRenderEnd = true
this.beInsertNodeUid = createUid()
this.mindMap.execCommand('INSERT_NODE', false, [], {
uid: this.beInsertNodeUid
})
},
//
insertChildNode() {
this.notHandleDataChange = false
this.mindMap.execCommand('INSERT_CHILD_NODE', false)
this.notHandleDataChange = true
this.handleNodeTreeRenderEnd = true
this.beInsertNodeUid = createUid()
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
uid: this.beInsertNodeUid
})
},
//
onClick(e, node) {
onClick(e, data) {
this.notHandleDataChange = true
let targetNode = node.data._node
const targetNode = this.mindMap.renderer.findNodeByUid(data.uid)
if (targetNode && targetNode.nodeData.data.isActive) return
this.mindMap.renderer.textEdit.stopFocusOnNodeActive()
this.mindMap.execCommand('GO_TARGET_NODE', node.data.data.uid, () => {
this.mindMap.execCommand('GO_TARGET_NODE', data.uid, () => {
this.mindMap.renderer.textEdit.openFocusOnNodeActive()
})
},
//
onNodeDrop(data, target, postion) {
this.notHandleDataChange = true
const node = this.mindMap.renderer.findNodeByUid(data.data.uid)
const targetNode = this.mindMap.renderer.findNodeByUid(target.data.uid)
if (!node || !targetNode) {
return
}
switch (postion) {
case 'before':
this.mindMap.execCommand('INSERT_BEFORE', node, targetNode)
break
case 'after':
this.mindMap.execCommand('INSERT_AFTER', node, targetNode)
break
case 'inner':
this.mindMap.execCommand('MOVE_NODE_TO', node, targetNode)
break
default:
break
}
},
//
onCurrentChange(data) {
this.currentData = data
},
//
onKeyDown(e) {
if (!this.isInTreArea) return
if ([46, 8].includes(e.keyCode) && this.currentData) {
e.stopPropagation()
this.mindMap.renderer.textEdit.hideEditTextBox()
const node = this.mindMap.renderer.findNodeByUid(this.currentData.uid)
if (node && !node.isRoot) {
this.notHandleDataChange = true
this.$refs.tree.remove(this.currentData)
this.mindMap.execCommand('REMOVE_NODE', [node])
}
}
}
}
}
@ -153,26 +284,10 @@ export default {
color: rgba(0, 0, 0, 0.85);
font-weight: bold;
&::-webkit-scrollbar {
width: 7px;
height: 7px;
}
&::-webkit-scrollbar-thumb {
border-radius: 7px;
background-color: rgba(0, 0, 0, 0.3);
cursor: pointer;
}
&::-webkit-scrollbar-track {
box-shadow: none;
background: transparent;
display: none;
}
.nodeEdit {
outline: none;
white-space: normal;
padding-right: 20px;
}
}

View File

@ -8,7 +8,7 @@
<span class="icon iconfont iconguanbi"></span>
</div>
<div class="outlineEdit">
<Outline :mindMap="mindMap" ref="outline"></Outline>
<Outline :mindMap="mindMap" @scrollTo="onScrollTo"></Outline>
</div>
</div>
</template>
@ -35,7 +35,6 @@ export default {
isOutlineEdit(val) {
if (val) {
this.$nextTick(() => {
this.$refs.outline.refresh()
document.body.appendChild(this.$refs.outlineEditContainer)
})
}
@ -46,6 +45,16 @@ export default {
onClose() {
this.setIsOutlineEdit(false)
},
onScrollTo(y) {
let container = this.$refs.outlineEditContainer
let height = container.offsetHeight
let top = container.scrollTop
y += 50
if (y > top + height) {
container.scrollTo(0, y - height / 2)
}
}
}
}
@ -63,7 +72,6 @@ export default {
justify-content: center;
background-color: #fff;
overflow-y: auto;
padding: 50px 0;
.closeBtn {
position: absolute;
@ -80,6 +88,7 @@ export default {
width: 1000px;
height: max-content;
overflow: hidden;
padding: 50px 0;
/deep/ .customNode {
.nodeEdit {

View File

@ -3,7 +3,11 @@
<div class="changeBtn" @click="onChangeToOutlineEdit">
<span class="icon iconfont iconquanping1"></span>
</div>
<Outline :mindMap="mindMap"></Outline>
<Outline
:mindMap="mindMap"
v-if="activeSidebar === 'outline'"
@scrollTo="onScrollTo"
></Outline>
</Sidebar>
</template>
@ -37,10 +41,20 @@ export default {
}
},
methods: {
...mapMutations(['setIsOutlineEdit']),
...mapMutations(['setIsOutlineEdit', 'setActiveSidebar']),
onChangeToOutlineEdit() {
this.setActiveSidebar('')
this.setIsOutlineEdit(true)
},
onScrollTo(y) {
let container = this.$refs.sidebar.getEl()
let height = container.offsetHeight
let top = container.scrollTop
if (y > top + height) {
container.scrollTo(0, y - height / 2)
}
}
}
}

View File

@ -9,7 +9,7 @@
<div class="sidebarHeader" v-if="title">
{{ title }}
</div>
<div class="sidebarContent">
<div class="sidebarContent" ref="sidebarContent">
<slot></slot>
</div>
</div>
@ -59,6 +59,10 @@ export default {
close() {
this.show = false
this.setActiveSidebar('')
},
getEl() {
return this.$refs.sidebarContent
}
}
}