|
|
|
|
@ -0,0 +1,250 @@
|
|
|
|
|
<!-- 树状选择器 -->
|
|
|
|
|
<template>
|
|
|
|
|
<!-- Element UI 弹出框组件,用于显示树状菜单 -->
|
|
|
|
|
<el-popover
|
|
|
|
|
ref="popover"
|
|
|
|
|
placement="bottom-start"
|
|
|
|
|
trigger="click"
|
|
|
|
|
@show="onShowPopover"
|
|
|
|
|
@hide="onHidePopover"
|
|
|
|
|
>
|
|
|
|
|
<!-- Element UI 树组件,用于显示部门树状结构 -->
|
|
|
|
|
<el-tree
|
|
|
|
|
ref="tree"
|
|
|
|
|
:style="`min-width: ${treeWidth}`"
|
|
|
|
|
:data="data"
|
|
|
|
|
:props="props"
|
|
|
|
|
:expand-on-click-node="false"
|
|
|
|
|
:filter-node-method="filterNode"
|
|
|
|
|
placeholder="选择部门"
|
|
|
|
|
class="select-tree"
|
|
|
|
|
:check-strictly="false"
|
|
|
|
|
highlight-current
|
|
|
|
|
default-expand-all
|
|
|
|
|
@node-click="onClickNode"
|
|
|
|
|
/>
|
|
|
|
|
<!-- Element UI 输入框组件,作为弹出框的触发元素 -->
|
|
|
|
|
<el-input
|
|
|
|
|
slot="reference"
|
|
|
|
|
ref="input"
|
|
|
|
|
v-model="labelModel"
|
|
|
|
|
:style="`width: ${width}px`"
|
|
|
|
|
:class="{ 'rotate': showStatus }"
|
|
|
|
|
:placeholder="placeholder"
|
|
|
|
|
clearable
|
|
|
|
|
suffix-icon="el-icon-arrow-down"
|
|
|
|
|
/>
|
|
|
|
|
</el-popover>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
export default {
|
|
|
|
|
// 组件名称
|
|
|
|
|
name: 'DepartTree',
|
|
|
|
|
// 设置绑定参数,指定 prop 为 value,事件为 selected
|
|
|
|
|
model: {
|
|
|
|
|
prop: 'value',
|
|
|
|
|
event: 'selected'
|
|
|
|
|
},
|
|
|
|
|
// 组件接收的属性
|
|
|
|
|
props: {
|
|
|
|
|
// 接收绑定的选中值
|
|
|
|
|
value: String,
|
|
|
|
|
// 输入框的宽度
|
|
|
|
|
width: String,
|
|
|
|
|
// 选项数据,必须为数组类型
|
|
|
|
|
options: {
|
|
|
|
|
type: Array,
|
|
|
|
|
required: true
|
|
|
|
|
},
|
|
|
|
|
// 输入框的占位符文本
|
|
|
|
|
placeholder: {
|
|
|
|
|
type: String,
|
|
|
|
|
required: false,
|
|
|
|
|
default: '请选择'
|
|
|
|
|
},
|
|
|
|
|
// 树节点的配置选项
|
|
|
|
|
props: {
|
|
|
|
|
type: Object,
|
|
|
|
|
required: false,
|
|
|
|
|
default: () => ({
|
|
|
|
|
parent: 'parentId',
|
|
|
|
|
value: 'rowGuid',
|
|
|
|
|
label: 'areaName',
|
|
|
|
|
children: 'children'
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 组件的数据
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
// 树状菜单的显示状态
|
|
|
|
|
showStatus: false,
|
|
|
|
|
// 树状菜单的宽度
|
|
|
|
|
treeWidth: 'auto',
|
|
|
|
|
// 输入框显示的文本
|
|
|
|
|
labelModel: '',
|
|
|
|
|
// 实际请求传递的值
|
|
|
|
|
valueModel: '0'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 计算属性
|
|
|
|
|
computed: {
|
|
|
|
|
// 判断选项数据是否为树状结构
|
|
|
|
|
dataType() {
|
|
|
|
|
const jsonStr = JSON.stringify(this.options)
|
|
|
|
|
return jsonStr.indexOf(this.props.children) !== -1
|
|
|
|
|
},
|
|
|
|
|
// 若选项数据不是树状结构,则将其转换为树状结构
|
|
|
|
|
data() {
|
|
|
|
|
return this.dataType ? this.options : this.switchTree()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 监听器
|
|
|
|
|
watch: {
|
|
|
|
|
// 监听输入框显示文本的变化,若为空则清空选中值,并过滤树节点
|
|
|
|
|
labelModel(val) {
|
|
|
|
|
if (!val) {
|
|
|
|
|
this.valueModel = ''
|
|
|
|
|
}
|
|
|
|
|
this.$refs.tree.filter(val)
|
|
|
|
|
},
|
|
|
|
|
// 监听绑定值的变化,更新输入框显示的文本
|
|
|
|
|
value(val) {
|
|
|
|
|
this.labelModel = this.queryTree(this.data, val)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 组件创建完成后的钩子函数
|
|
|
|
|
created() {
|
|
|
|
|
// 若存在初始绑定值,则更新输入框显示的文本
|
|
|
|
|
if (this.value) {
|
|
|
|
|
this.labelModel = this.queryTree(this.data, this.value)
|
|
|
|
|
}
|
|
|
|
|
// 等待 DOM 更新完成后,同步输入框宽度到树状菜单宽度
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
this.treeWidth = `${(this.width || this.$refs.input.$refs.input.clientWidth) - 24}px`
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
// 组件的方法
|
|
|
|
|
methods: {
|
|
|
|
|
// 点击树节点时的处理方法
|
|
|
|
|
onClickNode(node) {
|
|
|
|
|
// 更新输入框显示的文本
|
|
|
|
|
this.labelModel = node[this.props.label]
|
|
|
|
|
// 更新实际请求传递的值
|
|
|
|
|
this.valueModel = node[this.props.value]
|
|
|
|
|
// 关闭树状菜单
|
|
|
|
|
this.onCloseTree()
|
|
|
|
|
},
|
|
|
|
|
// 将扁平数组转换为树状层级结构
|
|
|
|
|
switchTree() {
|
|
|
|
|
return this.cleanChildren(this.buildTree(this.options, '0'))
|
|
|
|
|
},
|
|
|
|
|
// 关闭树状菜单
|
|
|
|
|
onCloseTree() {
|
|
|
|
|
this.$refs.popover.showPopper = false
|
|
|
|
|
},
|
|
|
|
|
// 弹出框显示时的处理方法
|
|
|
|
|
onShowPopover() {
|
|
|
|
|
// 更新树状菜单显示状态
|
|
|
|
|
this.showStatus = true
|
|
|
|
|
// 清除树节点的过滤
|
|
|
|
|
this.$refs.tree.filter(false)
|
|
|
|
|
},
|
|
|
|
|
// 弹出框隐藏时的处理方法
|
|
|
|
|
onHidePopover() {
|
|
|
|
|
// 更新树状菜单显示状态
|
|
|
|
|
this.showStatus = false
|
|
|
|
|
// 触发 selected 事件,传递实际请求的值
|
|
|
|
|
this.$emit('selected', this.valueModel)
|
|
|
|
|
},
|
|
|
|
|
// 树节点过滤方法
|
|
|
|
|
filterNode(query, data) {
|
|
|
|
|
// 若查询文本为空,则显示所有节点
|
|
|
|
|
if (!query) return true
|
|
|
|
|
// 根据节点的 label 属性进行过滤
|
|
|
|
|
return data[this.props.label].indexOf(query) !== -1
|
|
|
|
|
},
|
|
|
|
|
// 在树状数据中搜索指定 ID 对应的 label
|
|
|
|
|
queryTree(tree, id) {
|
|
|
|
|
let stark = []
|
|
|
|
|
stark = stark.concat(tree)
|
|
|
|
|
while (stark.length) {
|
|
|
|
|
const temp = stark.shift()
|
|
|
|
|
if (temp[this.props.children]) {
|
|
|
|
|
stark = stark.concat(temp[this.props.children])
|
|
|
|
|
}
|
|
|
|
|
if (temp[this.props.value] === id) {
|
|
|
|
|
return temp[this.props.label]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ''
|
|
|
|
|
},
|
|
|
|
|
// 将一维的扁平数组转换为多层级对象
|
|
|
|
|
buildTree(data, id = '0') {
|
|
|
|
|
const fa = (parentId) => {
|
|
|
|
|
const temp = []
|
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
|
|
|
const n = data[i]
|
|
|
|
|
if (n[this.props.parent] === parentId) {
|
|
|
|
|
n.children = fa(n.rowGuid)
|
|
|
|
|
temp.push(n)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return temp
|
|
|
|
|
}
|
|
|
|
|
return fa(id)
|
|
|
|
|
},
|
|
|
|
|
// 清除树状数据中为空的 children 属性
|
|
|
|
|
cleanChildren(data) {
|
|
|
|
|
const fa = (list) => {
|
|
|
|
|
list.map((e) => {
|
|
|
|
|
if (e.children.length) {
|
|
|
|
|
fa(e.children)
|
|
|
|
|
} else {
|
|
|
|
|
delete e.children
|
|
|
|
|
}
|
|
|
|
|
return e
|
|
|
|
|
})
|
|
|
|
|
return list
|
|
|
|
|
}
|
|
|
|
|
return fa(data)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
/* 带后缀图标的输入框样式 */
|
|
|
|
|
.el-input.el-input--suffix {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
/* 带后缀图标且旋转状态的输入框样式,使后缀图标旋转 180 度 */
|
|
|
|
|
.el-input.el-input--suffix.rotate .el-input__suffix {
|
|
|
|
|
transform: rotate(180deg);
|
|
|
|
|
}
|
|
|
|
|
/* 树状选择器样式,设置最大高度和垂直滚动条 */
|
|
|
|
|
.select-tree {
|
|
|
|
|
max-height: 350px;
|
|
|
|
|
overflow-y: scroll;
|
|
|
|
|
}
|
|
|
|
|
/* 菜单滚动条样式 */
|
|
|
|
|
.select-tree::-webkit-scrollbar {
|
|
|
|
|
z-index: 11;
|
|
|
|
|
width: 6px;
|
|
|
|
|
}
|
|
|
|
|
.select-tree::-webkit-scrollbar-track,
|
|
|
|
|
.select-tree::-webkit-scrollbar-corner {
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
.select-tree::-webkit-scrollbar-thumb {
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
width: 6px;
|
|
|
|
|
background: #b4bccc;
|
|
|
|
|
}
|
|
|
|
|
.select-tree::-webkit-scrollbar-track-piece {
|
|
|
|
|
background: #fff;
|
|
|
|
|
width: 6px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|