最近遇到一个需求:需要点击输入框后,出现下拉选项,且下拉选项的内容以树结构展示,如图
其中遇到一个棘手的问题,在输入框失去焦点时,下拉选项框需要隐藏,
这意味着当我准备点击树节点时,整个下拉选项框就被隐藏了,
所以虽然视觉效果上我点击了节点,但实际上我一直未曾真正点击到。
解决思路:
=> 在点击节点时,需要保证输入框不会失去焦点
=> 在下拉选项区域内做操作时,输入框不会失去焦点
=> 输入框的失焦是在鼠标onmousedown事件执行时浏览器的默认行为
=> 给下拉选项区域加上onmousedown事件,并阻止默认行为,代码如下
this.$refs.treeGroup.onmousedown = function (e) {
if (e && e.preventDefault) {
// 现代浏览器阻止默认事件
e.preventDefault()
} else {
// IE阻止默认事件
window.event.returnValue = false
}
return false
}
完整代码,仅供学习参考
<!-- 基于 vue.js 2.0 + element-ui 2.14.x -->
<template>
<div class="select-tree">
<v-input
class="search"
v-model="filterText"
:placeholder="placeholder"
:width="width"
:suffix-icon="showTree ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"
@focus="onFocus"
@blur="onBlur"
@clear="clear"
/>
<div class="tree-group" v-show="showTree" ref="treeGroup">
<div class="tree-group__box" v-show="treeData.length !== 0">
<button-tree
ref="buttonTree"
class="button-tree"
:key="key"
:data="treeData"
:defaultProps="treeProps"
:filterText="filterText"
:highlightCurrent="highlightCurrent"
:filterNodeMethod="filterNodeMethod"
:expand-on-click-node="false"
@nodeClick="nodeClick"
/>
</div>
<div v-show="treeData.length === 0" class="tree-group__box is-empty">暂无数据</div>
</div>
</div>
</template>
<script>
import { ButtonTree } from 'components/bussiness'
export default {
name: 'selectTree',
components: {
ButtonTree
},
props: {
// 选中项的值,支持`.sync`
value: {
type: String,
require: true
},
placeholder: {
type: String,
default: '请输入'
},
width: {
type: Number,
default: 182
},
treeData: {
type: Array,
default: () => {
return []
}
},
treeProps: {
type: Object,
default: () => {
return {}
}
},
expandOnClickNode: {
type: Boolean,
default: true
},
highlightCurrent: {
type: Boolean,
default: true
},
filterNodeMethod: {
type: Function
}
},
data () {
return {
showTree: false,
filterText: '',
result: '',
model: this.value,
key: 0
}
},
watch: {
model (newValue) {
console.log(newValue, 'newValue')
// 返回当前所选值的value数组
this.$emit('update:value', newValue)
},
showTree (val) {
if (val) { return }
// 树弹框隐藏时触发
if (this.model === '') {
this.filterText = ''
} else {
this.recursGetData(this.model, this.treeData)
this.filterText = this.result[this.treeProps.label]
this.result = null
}
}
},
mounted () {
this.$refs.treeGroup.onmousedown = function (e) {
if (e && e.preventDefault) {
// 现代浏览器阻止默认事件
e.preventDefault()
} else {
// IE阻止默认事件
window.event.returnValue = false
}
return false
}
},
methods: {
// input 输入框获取焦点
onFocus () {
this.filterText = ''
setTimeout(() => {
this.showTree = true
}, 100)
this.$emit('focus', event)
},
// input 输入框失去焦点
onBlur () {
this.showTree = false
this.$emit('blur', event)
},
clear () {
// this.form.orgId = ''
this.model = ''
this.key += 1
this.$emit('clear')
},
nodeClick (data, node, nodeVue) {
this.filterText = data[this.treeProps.label]
// this.form.orgId = data[this.treeProps.id]
this.model = data[this.treeProps.id]
this.$emit('nodeClick', data, node, nodeVue)
},
/* 计算 */
// 通过值递归获取数据
recursGetData (id, arr) {
console.log(id, arr, this.treeProps, 'id, arr')
arr.map((d, i) => {
if (id === d[this.treeProps.id]) {
this.result = d
} else {
if (d[this.treeProps.children].length !== 0) {
this.recursGetData(id, d[this.treeProps.children])
}
}
})
}
}
}
</script>
<style lang="scss" scoped>
.select-tree {
position: relative;
.search {
// ::v-deep .el-input__inner {
// border-radius:18px;
// background: rgba(144,166,186, 0.1);
// box-shadow: inset 4px 1px 8px 0px rgba(95,122,155,0.1);
// border: none;
// }
::v-deep .el-input__suffix {
line-height: 32px;
}
}
.tree-group {
&::before {
content: '';
border: 1px solid;
border-color: #E4E7ED transparent transparent #E4E7ED;
background-color: #fff;
width: 6px;
height: 6px;
position: absolute;
top: 2px;
left: 36px;
z-index: 4;
transform: rotateZ(45deg)
}
position: absolute;
left: 0;
top: 110%;
z-index: 2;
.tree-group__box {
&.is-empty {
height: auto;
color: #909399;
text-align: center;
padding: 4px 0;
font-size: 14px;
}
/** 不可使用定位,会破坏button-tree组件结构 */
width: 182px;
height: 204px;
border: 1px solid #E4E7ED;
border-radius: 4px;
background-color: #FFF;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
box-sizing: border-box;
margin: 5px 0;
padding: 5px 0 0;
}
}
}
</style>
// button-tree 组件
<template>
<div class="tree-button-wrapper" id="treeButtonBox">
<div class="tree-button-box" id="treeButton">
<el-tree
ref="elTree"
:node-key="nodeKey"
:default-checked-keys="defaultCheckedKeys"
:data="data"
:props="defaultProps"
:filter-node-method="filterNodeMethod"
:empty-text="emptyText"
:lazy="lazy"
:load="loadData"
:default-expanded-keys="defaultExpandedKeys"
:default-expand-all="expandAll"
:expand-on-click-node="expandOnClickNode"
:accordion="accordion"
:highlight-current="highlightCurrent"
:show-checkbox="showCheckbox"
:check-on-click-node="checkOnClickNode"
:draggable="draggable"
:allow-drag="allowDrag"
:allow-drop="allowDrop"
:indent="indent"
@node-click="nodeClick"
@node-contextmenu="nodeContextMenu"
@check-change="checkChange"
@check="check"
@current-change="currentChange"
@node-expand="nodeExpand"
@node-collapse="nodeCollapse"
@node-drag-start="nodeDragStart"
@node-drag-enter="nodeDragEnter"
@node-drag-leave="nodeDragLeave"
@node-drag-over="nodeDragOver"
@node-drag-end="nodeDragEnd"
@node-drop="nodeDrop">
<template slot-scope="{ node, data }">
<div class="custom-tree-node">
<span class="icon" v-if="data.icon" :style="handleBG(data, node)"></span>
<div class="node-content" ref="nodeContent" @contextmenu.stop.prevent="contentContextMenu(data, node, $event)">
<!--传入html-->
<div style="display: inline-block;" v-if="data && data.html" v-html="data.value"></div>
<!--传入自定义组件-->
<component v-else-if="data && data.component" :is="data.componentName" :row="scope.row"></component>
<!--传入普通文本-->
<div style="display: inline-block;" v-else v-text="data[defaultProps.label]"></div>
</div>
<!-- 按钮列表 -->
<div class="node-button" v-if="showBtnAll(data)">
<div class="node-button-icon" @click.stop.prevent="buttonIconClick(data)"><i class="el-icon-more"></i></div>
<ul class="node-button-list" v-show="showList(data)">
<li
v-show="showBtn(data, item)"
v-for="item in btnFilterOps"
:key="item.value"
@click.stop.prevent="buttonClick(data, item)"
>
{{item.text}}
</li>
<!-- <li @click.stop="buttonClick(data, 'add')">添加部门</li> -->
<!-- <li>添加成员</li>
<li>编辑</li>
<li>删除</li>
<li>移动</li> -->
</ul>
</div>
</div>
</template>
</el-tree>
</div>
</div>
</template>
<script>
import { Tree } from 'element-ui'
import _ from 'lodash'
// @group 基础组件
// @title Tree 树形组件
export default {
name: 'button-tree',
components: {
'el-tree': Tree
},
props: {
// 是否开启防抖
isDebounce: {
type: Boolean,
default: true
},
// 用于显示弹框按钮的列表数据
btnOps: {
type: Array,
default: () => {
// [{ text: '添加部门', value: 'add' }] text 按钮名称 value 按钮编号
return []
}
},
// 用于树节点过滤
filterText: {
type: String,
default: ''
},
// 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的,建议业务方传入这个属性
nodeKey: {
type: String,
default: 'id'
},
// 默认勾选的节点的 key 的数组
defaultCheckedKeys: {
type: Array
},
// 与data配套使用,详情请参考element-ui的tree组件的props介绍:http://element-cn.eleme.io/#/zh-CN/component/tree
defaultProps: {
type: Object,
default: () => {
return {
// 必传,唯一性id
id: 'id',
// 必传,label 指定节点标签为节点对象的某个属性值 string, function(data, node)
label: 'label',
// 必传,children 指定子树为节点对象的某个属性值 string
children: 'children',
// disabled 指定节点选择框是否禁用为节点对象的某个属性值 boolean, function(data, node)
disabled: 'disabled',
// isLeaf 指定节点是否为叶子节点,仅在指定了 lazy 属性的情况下生效 boolean, function(data, node)
isLeaf: 'isLeaf',
// btn 指定节点存在哪些功能按钮,Array<>
btn: 'btn'
}
}
},
// 展示数据
data: {
type: Array,
default: () => {
return []
}
},
// 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
filterNodeMethod: {
type: Function
},
// 内容为空的时候展示的文本
emptyText: {
type: String,
default: '暂无数据'
},
// 是否懒加载子节点,需与 load 方法结合使用
lazy: {
type: Boolean,
default: false
},
// 默认展开的节点的 key 的数组
defaultExpandedKeys: {
type: Array
},
// 是否默认展开所有节点
expandAll: {
type: Boolean,
default: false
},
// 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。
expandOnClickNode: {
type: Boolean,
default: true
},
// 是否每次只打开一个同级树节点展开
accordion: {
type: Boolean,
default: false
},
// 是否高亮当前选中节点,默认值是 false。
highlightCurrent: {
type: Boolean,
default: false
},
// 节点是否可被选择
showCheckbox: {
type: Boolean,
default: false
},
// 是否在点击节点的时候选中节点,默认值为 false,即只有在点击复选框时才会选中节点。
checkOnClickNode: {
type: Boolean,
default: false
},
// 是否开启拖拽节点功能
draggable: {
type: Boolean,
default: false
},
// 判断节点能否被拖拽
allowDrag: {
type: Function
},
// 拖拽时判定目标节点能否被放置。type 参数有三种情况:'prev'、'inner' 和 'next',分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
allowDrop: {
type: Function
},
// 相邻级节点间的水平缩进,单位为像素
indent: {
type: Number,
default: 16
},
// 自定义树节点的图标
iconClass: {
type: String,
default: 'el-icon-plus'
}
},
data () {
return {
// 选中节点的数据,此处选中为点击显示按钮的图标,不要修改该数据
currentData: {},
scrollAction: {
x: undefined,
y: undefined
},
// 当前展开的节点的vnode对象,再 loadData 方法中被使用
currentNode: null
}
},
computed: {
btnFilterOps () {
return this.btnOps.filter(d => {
// return this.$store.getters.getPermission(d.value)
/** 测试用代码 */
return true
/** end */
})
}
},
watch: {
data: {
handler () {
// 初始化一次div.node-button元素的位置
this.$nextTick(() => {
this.nodeButtonElementMove()
})
},
deep: true,
immediate: true
},
currentNode: {
handler (val) {
if (val && val.loading === false) {
this.$nextTick(() => {
this.nodeButtonElementMove(val)
})
this.currentNode = null
}
},
deep: true
},
filterText (val) {
if (this.isDebounce) {
this.filter(val)
} else {
this.$refs.elTree.filter(val)
}
}
},
mounted () {
// 操作dom必须再节点挂载之后,为treeButtonBox元素绑定滚动事件
this.treeButtonBoxElementBindEvent()
// 为过滤事件绑定this值
// this.filter.bind(this)
},
methods: {
// 不可以使用箭头函数,this指向会出现问题
filter: _.debounce(function (val) {
console.log('过滤节点触发')
this.$refs.elTree.filter(val)
}, 100),
// 是否显示所有与弹出按钮相关的内容
showBtnAll (data) {
return this.btnFilterOps.length !== 0 && data[this.defaultProps.btn] && data[this.defaultProps.btn].length !== 0
},
// 是否显示按钮列表
showList (data) {
const currentId = this.currentData[this.defaultProps.id]
const dataId = data[this.defaultProps.id]
// console.log(currentId, dataId, currentId === dataId)
return currentId !== undefined && dataId === currentId
},
// 是否显示按钮
showBtn (data, item) {
// const permission = this.$store.getters.getPermission(item.value)
// return this.defaultProps.btn && data[this.defaultProps.btn].includes(item.value) && permission
console.log('是否显示按钮')
/** 测试用代码 */
return data[this.defaultProps.btn].includes(item.value)
/** end */
},
// 按钮图标被点击时触发
buttonIconClick (data) {
const currentId = this.currentData[this.defaultProps.id]
const dataId = data[this.defaultProps.id]
// 若点击节点是当前存储节点,则清除存储节点数据
if (currentId !== undefined && dataId === currentId) {
this.currentData = {}
} else {
this.currentData = data
}
},
// 为treeButtonBox元素绑定滚动事件
treeButtonBoxElementBindEvent () {
const elTreeBox = document.getElementById('treeButtonBox')
elTreeBox.addEventListener('scroll', () => {
if (this.scrollAction.x !== undefined && this.scrollAction.x !== elTreeBox.scrollLeft) {
this.nodeButtonElementMove()
}
})
},
// 为div.node-button元素设置left值
nodeButtonElementMove (data) {
// 若按钮无需显示,避免运行下面的代码消耗性能
if (!this.btnFilterOps.length) {
return
}
if (data && data[this.defaultProps.btn] && data[this.defaultProps.btn].length === 0) {
return
}
const el = window.document.getElementById('treeButtonBox')
this.scrollAction.x = el.scrollLeft
el.querySelectorAll('.node-button').forEach(e => {
// 注意: 这里的21px是div.node-button元素的高度,因为旋转了90deg,高成了宽
e.style.left = `${el.scrollLeft + el.clientWidth - 22}px`
})
},
// 按钮被点击时触发
// @arg (data, value)接收两个参数 1.data 传递给 data 属性的数组中该节点所对应的对象
// @arg 2.item 按钮对应的对象
buttonClick (data, item) {
this.currentData = {}
this.$emit('buttonClick', data, item)
},
loadData (node, resolve) {
// 加载子树数据的方法,仅当 lazy 属性为true 时生效
// @arg (node, resolve) 接收两个参数,1. 当前节点 2. 渲染数据的方法
// console.log(node, resolve, 'reslove')
this.currentNode = node
this.$emit('loadData', node, resolve)
},
// @vuese
// 自定义树节点图标的方法
handleBG (data, node) {
let expanded = node.expanded
let bgUrl
if (!expanded) {
// 未打开
bgUrl = `url(${data.icon.iconOpen})`
} else {
// 已打开
bgUrl = `url(${data.icon.iconClose})`
}
return {
backgroundImage: bgUrl
}
},
// @vuese
// 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点所组成的数组
// @arg (leafOnly, includeHalfChecked)接收两个参数 1.leafOnly 是否只是叶子节点,默认值为 false
// @arg 2.includeHalfChecked 是否包含半选节点,默认值为 false
getCheckedNodes (leafOnly, includeHalfChecked) {
return this.$refs.elTree.getCheckedNodes(leafOnly, includeHalfChecked)
},
// @vuese
// 设置目前勾选的节点,使用此方法必须设置 node-key 属性
// @arg (nodes)接收一个参数 1.nodes 接收勾选节点数据的数组
setCheckedNodes (nodes) {
this.$refs.elTree.setCheckedNodes(nodes)
},
// @vuese
// 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点的 key 所组成的数组
// @arg (leafOnly)接收一个参数 1.leafOnly 若为 true 则仅返回被选中的叶子节点的 keys,默认值为 false
getCheckedKeys (leafOnly) {
return this.$refs.elTree.getCheckedKeys(leafOnly)
},
// @vuese
// 通过 keys 设置目前勾选的节点,使用此方法必须设置 node-key 属性
// @arg (keys, leafOnly)接收两个参数 1.keys 勾选节点的 key 的数组
// @arg 2.leafOnly 若为 true 则仅设置叶子节点的选中状态,默认值为 false
setCheckedKeys (keys, leafOnly) {
this.$refs.elTree.setCheckedKeys(keys, leafOnly)
},
// @vuese
// 获取当前被选中节点的 key,使用此方法必须设置 node-key 属性,若没有节点被选中则返回 null
getCurrentKey () {
return this.$refs.elTree.getCurrentKey()
},
// @vuese
// 获取当前被选中节点的 data,若没有节点被选中则返回 null
getCurrentNode () {
return this.$refs.elTree.getCurrentNode()
},
// @vuese
// 通过 key 设置某个节点的当前选中状态,使用此方法必须设置 node-key 属性
// @arg (key)接收一个参数 1.key 待被选节点的 key,若为 null 则取消当前高亮的节点
setCurrentKey (key) {
this.$refs.elTree.setCurrentKey(key)
},
// @vuese
// 通过 node 设置某个节点的当前选中状态,使用此方法必须设置 node-key 属性
// @arg (node)接收一个参数 1.node 待被选节点的 node
setCurrentNode (node) {
this.$refs.elTree.setCurrentNode(node)
},
// @vuese
// 根据 data 或者 key 拿到 Tree 组件中的 node
// @arg (data)接收一个参数 1.data 要获得 node 的 key 或者 data
getNode (data) {
return this.$refs.elTree.getNode(data)
},
// @vuese
// 删除 Tree 中的一个节点,使用此方法必须设置 node-key 属性
// @arg (data)接收一个参数 1.data 要删除的节点的 data 或者 node
remove (data) {
this.$refs.elTree.remove(data)
},
// @vuese
// 为 Tree 中的一个节点追加一个子节点
// @arg (data, parentNode)接收两个参数 1.data 要增加的节点的 data
// @arg 2.parentNode 要增加的节点的后一个节点的 data、key 或者 node
append (data, parentNode) {
this.$refs.elTree.append(data, parentNode)
},
// @vuese
// 为 Tree 的一个节点的前面增加一个节点
// @arg (data, refNode)接收两个参数 1.data 要增加的节点的 data
// @arg 2.refNode 要增加的节点的后一个节点的 data、key 或者 node
insertBefore (data, refNode) {
this.$refs.elTree.insertBefore(data, refNode)
},
// @vuese
// 为 Tree 的一个节点的后面增加一个节点
// @arg (data, refNode)接收两个参数 1.data 要增加的节点的 data
// @arg 2.refNode 要增加的节点的前一个节点的 data、key 或者 node
insertAfter (data, refNode) {
this.$refs.elTree.insertAfter(data, refNode)
},
nodeClick (data, node, nodeVue) {
// 节点被点击时的回调
// @arg (data, node, nodeVue)接收三个参数 1.data 传递给 data 属性的数组中该节点所对应的对象
// @arg 2.node 节点对应的 Node
// @arg 3.nodeVue 节点组件本身
console.log('节点被点击')
// 节点被点击后
this.$emit('nodeClick', data, node, nodeVue)
},
nodeContextMenu (event, data, node, nodeVue) {
// 当某一节点被鼠标右键点击时会触发该事件
// @arg (event, data, node, nodeVue)接收四个参数 1.event 事件对象
// @arg 2.data 传递给 data 属性的数组中该节点所对应的对象
// @arg 3.node 节点对应的 Node
// @arg 4.nodeVue 节点组件本身
this.$emit('nodeContextMenu', event, data, node, nodeVue)
},
contentContextMenu (data, node, event) {
// 当某一节点内容被鼠标右键点击时会触发该事件
// @arg (event, data, node)接收三个参数 1.event 事件对象
// @arg 2.data 传递给 data 属性的数组中该节点所对应的对象
// @arg 3.node 节点对应的 Node
this.$emit('contentContextMenu', event, data, node)
},
checkChange (data, checked, childrenChecked) {
// 节点选中状态发生变化时的回调
// @arg (data, checked, childrenChecked)接收三个参数 1.data 传递给 data 属性的数组中该节点所对应的对象
// @arg 2.checked 节点本身是否被选中
// @arg 3.childrenChecked 节点的子树中是否有被选中的节点
this.$emit('checkChange', data, checked, childrenChecked)
},
check (data, checkedObj) {
// 当复选框被点击的时候触发
// @arg (data, checkedObj)接收两个参数 1.data 传递给 data 属性的数组中该节点所对应的对象
// @arg 2.checkedObj 树目前的选中状态对象,包含 checkedNodes、checkedKeys、halfCheckedNodes、halfCheckedKeys 四个属性
this.$emit('check', data, checkedObj)
},
currentChange (data, node) {
// 当前选中节点变化时触发的事件
// @arg (data, node)接收两个参数 1.data 当前节点的数据
// @arg 2.node 当前节点的 Node 对象
this.$emit('currentChange', data, node)
},
nodeExpand (data, node, nodeVue) {
// 节点被展开时触发的事件
// @arg (data, node, nodeVue)接收三个参数 1.data 传递给 data 属性的数组中该节点所对应的对象
// @arg 2.node 节点对应的 Node
// @arg 3.nodeVue 节点组件本身
// 当并非懒加载时,节点展开后,且dom挂载完成时,需要将div.node-button元素位置重新计算一次
if (!this.lazy) {
this.$nextTick(() => {
this.nodeButtonElementMove()
})
}
this.$emit('nodeExpand', data, node, nodeVue)
},
nodeCollapse (data, node, nodeVue) {
// 节点被关闭时触发的事件
// @arg (data, node, nodeVue)接收三个参数 1.data 传递给 data 属性的数组中该节点所对应的对象
// @arg 2.node 节点对应的 Node
// @arg 3.nodeVue 节点组件本身
this.$emit('nodeCollapse', data, node, nodeVue)
},
nodeDragStart (node, event) {
// 节点开始拖拽时触发的事件
// @arg (node, event)接收两个参数 1.node 被拖拽节点对应的 Node
// @arg 2.event 事件对象
this.$emit('nodeCollapse', node, event)
},
nodeDragEnter (node, enterNode, event) {
// 拖拽进入其他节点时触发的事件
// @arg (node, enterNode, event)接收三个参数 1.node 被拖拽节点对应的 Node
// @arg 2.enterNode 所进入节点对应的 Node
// @arg 3.event 事件对象
this.$emit('nodeDragEnter', node, enterNode, event)
},
nodeDragLeave (node, leaveNode, event) {
// 拖拽离开某个节点时触发的事件
// @arg (node, leaveNode, event)接收三个参数 1.node 被拖拽节点对应的 Node
// @arg 2.leaveNode 所离开节点对应的 Node
// @arg 3.event 事件对象
this.$emit('nodeDragLeave', node, leaveNode, event)
},
nodeDragOver (node, enterNode, event) {
// 在拖拽节点时触发的事件(类似浏览器的 mouseover 事件)
// @arg (node, enterNode, event)接收三个参数 1.node 被拖拽节点对应的 Node
// @arg 2.enterNode 当前进入节点对应的 Node
// @arg 3.event 事件对象
this.$emit('nodeDragOver', node, enterNode, event)
},
nodeDragEnd (node, overNode, pos, event) {
// 拖拽结束时(可能未成功)触发的事件
// @arg (node, overNode, pos, event)接收四个参数 1.node 被拖拽节点对应的 Node
// @arg 2.overNode 结束拖拽时最后进入的节点(可能为空)
// @arg 3.pos 被拖拽节点的放置位置(before、after、inner)
// @arg 4.event 事件对象
this.$emit('nodeDragEnd', node, overNode, pos, event)
},
nodeDrop (node, overNode, pos, event) {
// 拖拽成功完成时触发的事件
// @arg (node, overNode, pos, event)接收四个参数 1.node 被拖拽节点对应的 Node
// @arg 2.overNode 结束拖拽时最后进入的节点
// @arg 3.pos 被拖拽节点的放置位置(before、after、inner)
// @arg 4.event 事件对象
this.$emit('nodeDrop', node, overNode, pos, event)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped>
.tree-button-wrapper {
position: relative; // 该属性不可删除
height: 100%;
overflow: auto;
.tree-button-box {
min-width: 100%;
display: inline-block;
// ::v-deep .el-tree{
// }
}
// .el-tree-node>.el-tree-node__children {
// overflow: auto;
// }
::v-deep .el-tree-node__content {
height: 24px;
position: relative;
&:hover{
z-index: 3;
.custom-tree-node {
.node-content {
opacity: 0.3;
}
.node-button {
.node-button-icon {
display: inline-block;
}
}
}
}
.custom-tree-node {
display: inline-block;
height: 24px;
line-height: 24px;
.icon {
margin: 0 4px 0 0;
width: 22px;
height: 22px;
display: inline-block;
vertical-align: top;
// vertical-align: ;
border: 0 none;
cursor: pointer;
outline: none;
background-repeat: no-repeat;
background-attachment: scroll;
background-size: contain;
}
.node-content {
display: inline-block;
padding: 0;
}
.node-button {
// display: none;
position: absolute;
top: 0;
right: 8px;
width: 18px;
height: 100%;
font-size: 20px;
color: #909399;
text-align: center;
.node-button-icon {
display: none;
transform: rotateZ(-90deg);
}
.node-button-list {
user-select: none;
// display: none;
position: absolute;
top: 24px;
right: 8px;
list-style: none;
font-size: 14px;
color: #909399;
padding: 2px 0px;
border: 1px solid #e4e7ed;
border-radius: 4px;
background-color: #fff;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
z-index: 1;
> li {
padding: 0px 12px;
&:hover {
color: #1B8CF2;
background-color: #f5f7fa;
}
// text-decoration: underline;
// list-style: none;
}
}
}
}
}
::v-deep .el-tree-node__children {
overflow: inherit;
}
}
</style>