之前按照項目需求使用element中的tree來創建目錄列表,如今記錄一下。
一、項目需求
1.完整展示目錄列表
2.右擊節點選擇重命名,刪除,創建文件夾三個選項
3.拖拽文件夾,其中拖拽文件夾有以下要求:
a. 如果該文件夾內已存在上傳文件,則其他文件夾不能拖拽進入該文件夾內
b.整個目錄中有且僅有一個根目錄,拖拽文件夾的范圍只能在該根目錄里面
4.重命名文件夾要求:
a.重命名完成后按enter鍵完成
5.刪除文件夾要求:
a.如果該文件夾內已含有上傳文件,則刪除失敗
6.創建文件夾要求:
a.如果該文件夾內已含有上傳文件,則創建失敗
二、思路整理
1.在mounted中向后台請求項目列表,然后存儲treeData
2.鼠標右鍵彈出的是menu菜單選項框,對於獲取點擊節點的信息可以使用el-tree中的 @node-contextmenu="rightClick"
3.選擇刪除文件夾的時候要注意在發送請求的同時也要完成前端的“刪除”動作,使用map,indexOf, slice
4.重命名中使用@keyup.enter.native來觸發請求,使用focus()聚焦輸入框
5.鼠標右擊的時候menu的位置是隨之改變的,且當鼠標位置加上menu的高度或寬度超出視頻范圍的時候要合適調節menu的位置。
/* 菜單定位基於鼠標點擊位置 */ let height = document.documentElement.clientHeight || document.body.clientHeight if (event.clientY + 168 > height) { menu.style.left = event.clientX - 5 + 'px' menu.style.top = event.clientY - 10 - 168 + 'px' } else { menu.style.left = event.clientX + 10 + 'px' menu.style.top = event.clientY + 5 + 'px' }
6.menu的顯示與隱藏,當鼠標點擊其他位置的時候,menu隱藏
document.addEventListener('click', this.hide, true)
document.removeEventListener('click', this.hide)
7.當文件夾命名過長的時候,出現橫向滾動條
.comp-tree { margin-top: 1em; overflow-y: hidden; overflow-x: scroll; } .el-tree { min-width: 100%; display:inline-block !important; }
三、源碼分析
參考:
在此基礎上做的修改
<template>
<div v-loading="isLoading" class="comp-tree" @click="hiddenMenu">
<el-tree ref="SlotTree"
:data="treeData"
:props="defaultProps"
:expand-on-click-node="true"
highlight-current
:node-key="NODE_KEY"
@node-click="handleNodeClick"
@node-contextmenu="rightClick"
@node-drag-start = "handleDragStart"
@node-drop="handleDrop"
:draggable = draggable
default-expand-all
>
<div class="comp-tr-node" slot-scope="{ node, data }">
<!-- 編輯狀態 -->
<template v-if="node.isEdit">
<el-input v-model="data.menuName"
autofocus
size="mini"
:ref="'slotTreeInput'+data.recordId"
@keyup.enter.native="handleInput(node, data)">
</el-input>
</template>
<!-- 非編輯狀態 -->
<template v-else>
<!-- 名稱: 新增節點增加class(is-new) -->
<span>
<span>
{{ node.label }}
</span>
</span>
</template>
</div>
</el-tree>
<!-- 鼠標右鍵產生的選項 -->
<div v-show="menuVisible" id="menu">
<el-menu
class="el-menu-vertical rightClickMenu"
@select="handleRightSelect"
text-color="#303133"
active-text-color = "#303133"
>
<el-menu-item index="1" class="menuItem">
<i class="el-icon-edit"></i>
<span slot="title">重命名</span>
</el-menu-item>
<el-menu-item index="2" class="menuItem">
<i class="el-icon-folder-add"></i>
<span slot="title">新建文件夾</span>
</el-menu-item>
<el-menu-item index="3" class="menuItem">
<i class="el-icon-delete"></i>
<span slot="title">刪除</span>
</el-menu-item>
</el-menu>
</div>
<detail-win v-if="isShow" @close="closeWin" :node = NODE v-bind="$attrs" v-on="$listeners"></detail-win>
</div>
</template>
<script>
import detailWin from './detail-win'
import { baseWarn } from 'src/common/fn'
export default {
components: {
detailWin
},
data () {
return {
isLoading: false, // 是否加載
NODE_KEY: 'recordId', // id對應字段
defaultProps: {// 默認設置
children: 'children',
label: 'menuName'
},
menuVisible: false,
isShow: false,
clickNode: '',
DATA: null,
NODE: null,
objectID: null,
dragPid: null,
dragIndex: null,
newParentId: null,
oldMenuName: null
}
},
computed: {
treeData () {
return this.$sysStore.fileTreeData
},
addNode () {
return this.$sysStore.addNode
},
draggable () {
if (!this.$store.state.user.isOuterUser) {
return true
} else {
return false
}
}
},
watch: {
},
created () {
},
mounted () {
},
methods: {
// 修改節點
handleInput (node, data) {
// 退出編輯狀態
if (node.isEdit) {
this.$set(node, 'isEdit', false)
}
if (this.oldMenuName === data.menuName) {
} else {
this.$axios({
url: '........',
method: 'post',
data: {
...
}
})
.then(res => {
this.$message({ type: 'success', message: '重命名成功' })
})
.catch(function (error) {
this.$message({ type: 'error', message: '重命名失敗' })
console.log(error)
})
}
},
// 重命名
handleEdit (node, data) {
// 模板文件夾名稱不能重命名
if (data.isTemplate === 1) {
this.$message.error('該文件夾無法重命名')
} else {
// 設置編輯狀態
if (!node.isEdit) {
this.$set(node, 'isEdit', true)
}
// 輸入框聚焦
this.$nextTick(() => {
if (this.$refs['slotTreeInput' + data.recordId]) {
this.$refs['slotTreeInput' + data.recordId].$refs.input.focus()
}
})
}
},
// 新增節點
handleAdd (node, data) {
// console.log(node,data)
if (data.fileSum !== 0) {
this.$message.error('此文件夾已存在上傳文件,不能新增文件夾!')
return
}
this.isShow = true
},
// 刪除節點
handleDelete (node, data) {
if (data.children && data.children.length !== 0) {
this.$message.error('此文件夾內含有其他文件夾,不可刪除!')
return
}
if (data.fileSum !== 0) {
this.$message.error('此文件夾內含有上傳文件,不可刪除!')
} else {
this.$confirm(`是否刪除${node.label}?`, '提示', {
confirmButtonText: '確認',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deleteProj(node, data)
}).catch(() => {})
}
},
deleteProj (node, data) {
this.$axios({
url: '...',
method: 'post',
data: {
...
}
})
.then(res => {
if (res.success) {
let _list = node.parent.data.children || node.parent.data// 節點同級數據
let _index = _list.map((c) => c.recordId).indexOf(data.recordId)
_list.splice(_index, 1)
this.$message.success('刪除成功')
// 使右側列表消失
this.$parent.$parent.disabled = true
// 直接改變vuex中treeData的數值
let fileTreeData = this.filter(this.treeData, node.data.kid)
this.$store.dispatch(`${this.wpstore}/setFileTreeData`, fileTreeData)
} else {
this.$message.error('刪除失敗,請重試')
}
})
},
/**
* 刪除或者更改treeData中指定kid的子節點
*
* @returns arr
*/
filter (arr, kid) {
for (var i = 0; i < arr.length; i++) {
var el = arr[i]
if (el.kid === kid) {
arr.splice(i, 1)
} else {
if (el.children && el.children.length) {
this.filter(el.children, kid)
}
}
}
return arr
},
// 點擊節點
handleNodeClick (node) {
this.menuVisible = false
this.$store.dispatch(`${this.wpstore}/selectNode`, node)
},
// 拖拽開始時記錄該節點的pid及其在原來數組中的原始數據
handleDragStart (node) {
this.dragPid = node.data.recordPid
let p = this.$refs.SlotTree.getNode(this.dragPid)
let _list = p.data.children // 節點同級數據
let _index = _list.map((c) => c.recordId).indexOf(node.data.recordId)
this.dragIndex = _index
},
// 拖拽成功時觸發請求
handleDrop (draggingNode, dropNode, dropType, e) {
// debugger
if (dropNode.data.fileSum !== 0 && dropType === 'inner') {
baseWarn('該文件夾內含有上傳文件,拖拽失敗', null, null, null)
let _list = dropNode.data.children // 節點同級數據
let _index = _list.map((c) => c.recordId).indexOf(draggingNode.data.recordId)
_list.splice(_index, 1)
// 將節點返回到原來的位置上
let p = this.$refs.SlotTree.getNode(this.dragPid)
// console.log(p,'p')
p.data.children.splice(this.dragIndex, 0, draggingNode.data)
} else if (dropNode.data.recordPid === 'root') {
baseWarn('無法拖拽為根文件夾', null, null, null)
this.$refs.SlotTree.remove(draggingNode.data.recordId)
let p = this.$refs.SlotTree.getNode(this.dragPid)
p.data.children.splice(this.dragIndex, 0, draggingNode.data)
} else {
// 算出子節點對於父節點的相對位置
let _list = dropNode.parent.data.children // 拖拽節點同級數據
let _index = _list.map((c) => c.recordId).indexOf(draggingNode.data.recordId)
if (_index === -1) {
_list = dropNode.childNodes
_index = _list.map((c) => c.data.recordId).indexOf(draggingNode.data.recordId)
}
// 得出目標節點的recordId
if (dropNode.parent.data.recordId === this.dragPid) {
// 相同的父節點
this.newParentId = this.dragPid
} else {
this.newParentId = dropNode.parent.data.recordId
}
// 發送請求
this.$axios({
url: '...',
method: 'post',
data: {
...
}
})
.then(res => {
this.$message({ type: 'success', message: '拖拽成功' })
})
.catch(function (error) {
console.log(error)
})
}
},
// 鼠標右擊
rightClick (event, object, value, element) {
this.oldMenuName = object.menuName
if (this.$store.state.user.isOuterUser) {
this.menuVisible = false
} else {
if (this.objectID !== object.recordId) {
this.objectID = object.recordId
this.menuVisible = true
this.DATA = object
this.NODE = value
} else {
this.menuVisible = !this.menuVisible
}
// document.addEventListener('click', (e) => {
// this.menuVisible = false
// })
this.hiddenMenu()
let menu = document.querySelector('.rightClickMenu')
/* 菜單定位基於鼠標點擊位置 */
let height = document.documentElement.clientHeight || document.body.clientHeight
if (event.clientY + 168 > height) {
menu.style.left = event.clientX - 5 + 'px'
menu.style.top = event.clientY - 10 - 168 + 'px'
} else {
menu.style.left = event.clientX + 10 + 'px'
menu.style.top = event.clientY + 5 + 'px'
}
menu.style.position = 'fixed'
}
},
handleRightSelect (key) {
this.menuVisible = false
if (key == 1) {
this.handleEdit(this.NODE, this.DATA)
} else if (key == 2) {
this.handleAdd(this.NODE, this.DATA)
} else if (key == 3) {
this.handleDelete(this.NODE, this.DATA)
}
},
hiddenMenu () {
document.addEventListener('click', this.hide, true)
document.removeEventListener('click', this.hide)
},
hide() {
this.menuVisible = false
},
closeWin (val) {
this.isShow = val
}
}
}
</script>
<style lang="scss" scoped>
.el-menu {
border: solid 1px #e6e6e6
}
.comp-tree {
margin-top: 1em;
overflow-y: hidden;
overflow-x: scroll;
}
.el-tree {
min-width: 100%;
display:inline-block !important;
}
.rightClickMenu {
z-index: 999
}
</style>
這里僅供參考,具體的編寫代碼還需根據各位實際項目需求來修改
