形式一:
业务需求:
1. 在树形列表中选择项目;
2. 树形列表需带有筛选过滤功能;
3. 将已选择的项目展示在输入框中;
4. 输入框中的选中项可以被单独快速删除;
5. 下拉框中可再次对之前选择过的项目进行重新选择。
实现思路:
1. 所用技术:vue + elementUI组件库;
2. 左侧:使用 el-select 下拉框多选;
右侧:使用 el-popover 弹出框 里面包含了 el-tree 树形列表进行展示。
最终效果:
1. 初始页面展示如下:
2. 点击“蓝色添加按钮”,弹出树形列表:
3. 输入关键字可直接进行筛选查询:
4. 点击右侧叶子节点进行选择,展示在左侧输入框中:
5. 输入框中的选择项可以直接点击选项后的关闭按钮取消选择,也可在下拉框列表中进行选择:
代码如下:
<!--
* @Description: select下拉多选 搭配 tree树形展示选择
-->
<template>
<div class="selectTree">
<label>发送到人:</label>
<!-- 下拉多选框 -->
<el-select class="selectStyle" v-model="selectedId" multiple placeholder="请选择">
<el-option
clearable
v-for="item in selectOptions"
:key="item.id"
:label="item.label"
:value="item.id">
</el-option>
</el-select>
<!-- 弹出框:树形选择列表 -->
<div class="treeStyle">
<el-popover
placement="right"
width="400"
trigger="click">
<!-- 树形列表的关键字过滤 -->
<el-input placeholder="输入关键字搜索" v-model="filterText">
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-input>
<!-- 树形列表区域定高滚动 -->
<div class="popContent">
<el-tree
class="filter-tree"
:data="data"
:props="defaultProps"
default-expand-all
:filter-node-method="filterNode"
@node-click="getClickNodes"
ref="tree">
</el-tree>
</div>
<!-- 弹框外的点击按钮(即:点击此按钮后,弹框出现) -->
<div slot="reference"><img src="../../images/icon_add.png" /></div>
</el-popover>
</div>
</div>
</template>
<script>
export default {
data() {
return {
selectOptions: [], // 下拉框选择项列表
selectedId: [], // 下拉框选中值数组(多选)
filterText: '', // 输入框关键字过滤
data: [ // 树形列表展示数据
{
id: 1,
label: '张一',
children: [{
id: 2,
label: '张二',
children: [{
id: 3,
label: '张三'
}, {
id: 4,
label: '张四'
}]
}]
},
{
id: 5,
label: '王一',
children: [{
id: 6,
label: '王二'
}, {
id: 7,
label: '王三'
}]
},
{
id: 8,
label: '李一',
children: [{
id: 9,
label: '李二'
}, {
id: 10,
label: '李三'
}]
}
],
defaultProps: {
children: 'children',
label: 'label'
}
}
},
watch: {
// 输入关键字之后,过滤树形列表
filterText(val) {
this.$refs.tree.filter(val);
}
},
created() {
},
methods: {
// 过滤树形列表名称的方法
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
// 获取当前点击的节点信息
getClickNodes(currentData) {
// 只针对叶子节点,所以有子节点的跳过
if(currentData.children == null) {
// 第一次点击时,直接添加到下拉框的选项列表中 和 选中值中
if(this.selectOptions.length == 0) {
this.selectOptions.push(currentData)
this.selectedId.push(currentData.id)
} else {
// 第二次之后点击时,需判断是否重复(重复元素将不再添加到数组中)
// 下拉选项判重后添加
let num1 = 0;
this.selectOptions.forEach(item => {
item.id == currentData.id ? num1++ : num1;
})
if(num1 == 0) {
this.selectOptions.push(currentData)
}
// 选中值判重后添加
let num2 = 0;
this.selectedId.forEach(item => {
item == currentData.id ? num2++ : num2;
})
if(num2 == 0) {
this.selectedId.push(currentData.id)
}
}
}
}
}
}
</script>
<style lang="scss">
.selectTree {
margin-top: 300px;
padding: 30px;
.selectStyle {
width: 500px;
}
.el-icon-circle-plus-outline {
font-size: 32px;
color: cornflowerblue;
position: absolute;
left: 620px;
}
.treeStyle {
.el-popover__reference {
width: 32px;
height: 32px;
position: absolute;
left: 620px;
top: 30px;
}
}
}
.el-popover {
padding: 20px !important;
}
.popContent {
margin-top: 10px;
min-height: 200px;
max-height: 400px;
overflow-y: auto;
}
</style>
形式二:
最终效果:
1. 下拉单选:
2. 下拉多选:
<!--
* @Description: select下拉搭配tree树形 选择
-->
<template>
<div class="selectTree">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="树型结构" >
<!-- 单选 -->
<el-select v-model="form.treeData" placeholder="请选择" style="width: 16rem">
<!-- 多选 -->
<!-- <el-select v-model="form.treeData" multiple placeholder="请选择" style="width: 16rem"> -->
<el-option :value="treeDataValue" style="height: auto">
<el-tree
ref="tree"
:data="data"
default-expand-all
node-key="id"
:props="defaultProps"
@node-click="handleNodeClick"
/>
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
form: {
treeData: "", // 单选
// treeData: [], // 多选
},
treeDataValue: "",
data: [
{
id: 1,
name: "一级 1",
children: [
{
id: 4,
name: "一级 1-1",
}
]
},
{
id: 2,
name: "一级 2",
children: [
{
id: 5,
name: "二级 2-1",
},
{
id: 6,
name: "二级 2-2",
}
]
},
{
id: 3,
name: "一级 3",
children: [
{
id: 7,
name: "二级 3-1",
},
{
id: 8,
name: "二级 3-2",
}
]
}
],
defaultProps: {
children: "children",
label: "name",
}
}
},
created() {
},
methods: {
// 点击树节点
handleNodeClick(data, node, nodeData){
// select 单选
this.treeDataValue = data
this.form.treeData = data.name
// select 多选(判重后添加到选择结果数组中)
// this.treeDataValue = data
// let num = 0;
// this.form.treeData.forEach(item => {
// item == data.name ? num++ : num;
// })
// if(num == 0) {
// this.form.treeData.push(data.name)
// }
}
}
}
</script>
<style lang="scss">
.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover {
background-color: #FFFFFF;
}
.el-select-dropdown__item.hover, .el-select-dropdown__item:hover {
background-color: #FFFFFF;
}
.el-select-dropdown__item.selected {
font-weight: normal;
}
</style>
形式三(封装成组件使用):
最终效果:
组件代码( el-select-tree.vue ):
<!--
* @Description: 封装组件 el-select & el-tree 下拉树形选择
-->
<template>
<el-select ref="selectTree" :value="value" v-model="valueName" :clearable="clearable" @clear="clearHandle">
<el-option :value="valueName" :label="valueName" class="options">
<el-tree
id="tree-option"
ref="selectTree"
:accordion="accordion"
:data="options"
:props="props"
:node-key="props.value"
:default-expanded-keys="defaultExpandedKey"
@node-click="handleNodeClick">
<span slot-scope="{ data }">
<i :class="[data.color!=null?'ification_col':'']" :style="{'background-color':data.color}"></i> {{ data.name }}
</span>
</el-tree>
</el-option>
</el-select>
</template>
<script>
export default {
name: "el-tree-select",
props: {
// 配置项
props: {
type: Object,
default: () => {
return {
value: 'id',
children: 'children',
label: 'name'
}
}
},
// 选项列表数据(树形结构的对象数组)
options: {
type: Array,
default: () => {
return []
}
},
// 初始值
value: {
type: Object,
default: () => {
return {}
}
},
// 可清空选项
clearable: {
type: Boolean,
default: true
},
// 自动收起
accordion: {
type: Boolean,
default: false
}
},
data() {
return {
resultValue: null,
valueName: '',
defaultExpandedKey: []
}
},
watch: {
value() {
this.resultValue = this.value
this.initHandle()
}
},
mounted() {
this.resultValue = this.value.name; // 初始值
this.initHandle();
},
methods: {
// 初始化值
initHandle() {
if (this.resultValue) {
this.valueName = this.resultValue.name; // 初始化显示
this.$refs.selectTree.setCurrentKey(this.resultValue) // 设置默认选中
this.defaultExpandedKey = [this.resultValue] // 设置默认展开
}
this.initScroll()
},
// 初始化滚动条
initScroll() {
this.$nextTick(() => {
let scrollWrap = document.querySelectorAll('.el-scrollbar .el-select-dropdown__wrap')[0]
let scrollBar = document.querySelectorAll('.el-scrollbar .el-scrollbar__bar')
scrollWrap.style.cssText = 'margin: 0px; max-height: none; overflow: hidden;'
scrollBar.forEach(ele => ele.style.width = 0)
})
},
// 切换选项
handleNodeClick(node) {
// 点击叶子节点后被选中
if (node.children == null || node.children == undefined){
this.$refs.selectTree.blur();
this.valueName = node[this.props.label];
this.resultValue = node;
this.$emit('getValue', this.resultValue);
this.defaultExpandedKey = [];
}
},
// 清除选中
clearHandle() {
this.valueName = ''
this.resultValue = null
this.defaultExpandedKey = []
this.clearSelected()
this.$emit('getValue', null)
},
// 清空选中样式
clearSelected() {
let allNode = document.querySelectorAll('#tree-option .el-tree-node')
allNode.forEach((element) => element.classList.remove('is-current'))
}
}
}
</script>
<style scoped>
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
height: auto;
max-height: 274px;
padding: 0;
overflow: hidden;
overflow-y: auto;
}
.el-select-dropdown__item.selected {
font-weight: normal;
}
ul li >>> .el-tree .el-tree-node__content {
height: auto;
padding: 0 20px;
}
.el-tree-node__label {
font-weight: normal;
}
.el-tree >>> .is-current .el-tree-node__label {
color: #409EFF;
font-weight: 700;
}
.el-tree >>> .is-current .el-tree-node__children .el-tree-node__label {
color: #606266;
font-weight: normal;
}
.el-popper {
z-index: 9999;
}
.ification_col {
width: 20px;
height: 10px;
display: inline-block;
}
</style>
页面中应用组件( index.vue ):
<!--
* @Description: select下拉搭配tree树形 选择
-->
<template>
<div class="selectTree">
<label>发送到人:</label>
<el-select-tree :options="options" @getValue="getSelectedValue"></el-select-tree>
</div>
</template>
<script>
import elSelectTree from './el-select-tree.vue'
export default {
components: {
elSelectTree
},
data() {
return {
options: [
{
id: 1,
name: '张一',
children: [{
id: 2,
name: '张二',
children: [{
id: 3,
name: '张三'
}, {
id: 4,
name: '张四'
}]
}]
},
{
id: 5,
name: '王一',
children: [{
id: 6,
name: '王二'
}, {
id: 7,
name: '王三'
}]
},
{
id: 8,
name: '李一',
children: [{
id: 9,
name: '李二'
}, {
id: 10,
name: '李三'
}]
}
]
}
},
created() {
},
methods: {
getSelectedValue(value) {
console.log('选中的结果值', value)
}
}
}
</script>
<style lang="scss">
</style>
优化了上面的组件,添加了多选以及默认值显示:
组件代码( el-select-tree.vue ):
<!--
* @Description: 封装组件 el-select & el-tree 下拉树形选择
-->
<template>
<el-select ref="selectTree" :value="value" v-model="valueName" :multiple="multiple" :clearable="clearable" @clear="clearHandle" @change="changeValue">
<el-option :value="valueName" class="options">
<el-tree
id="tree-option"
ref="selectTree"
:accordion="accordion"
:data="options"
:props="props"
:node-key="props.value"
@node-click="handleNodeClick">
<span slot-scope="{ data }">
<i :class="[data.color!=null?'ification_col':'']" :style="{'background-color':data.color}"></i> {{ data.name }}
</span>
</el-tree>
</el-option>
</el-select>
</template>
<script>
export default {
name: "el-tree-select",
props: {
// 配置项
props: {
type: Object,
default: () => {
return {
value: 'id',
children: 'children',
label: 'name'
}
}
},
// 选项列表数据(树形结构的对象数组)
options: {
type: Array,
default: () => {
return []
}
},
// 初始值(单选)
value: {
type: Object,
default: () => {
return {}
}
},
// 初始值(多选)
valueMultiple: {
type: Array,
default: () => {
return []
}
},
// 可清空选项
clearable: {
type: Boolean,
default: true
},
// 自动收起
accordion: {
type: Boolean,
default: false
},
// 是否多选
multiple: {
type: Boolean,
default: false
}
},
data() {
return {
resultValue: [], // 传给父组件的数组对象值
valueName: this.multiple ? [] : '' // 输入框显示值
}
},
watch: {
value() {
this.resultValue = this.multiple ? this.valueMultiple : this.value; // 初始值
this.initHandle()
}
},
mounted() {
this.resultValue = this.multiple ? this.valueMultiple : this.value; // 初始值
this.initHandle();
},
methods: {
// 初始化显示
initHandle() {
if (this.resultValue) {
if(this.multiple) {
// 多选
this.resultValue.forEach(item => this.valueName.push(item.name));
} else {
// 单选
this.valueName = this.resultValue.name;
}
}
this.initScroll()
},
// 初始化滚动条
initScroll() {
this.$nextTick(() => {
let scrollWrap = document.querySelectorAll('.el-scrollbar .el-select-dropdown__wrap')[0]
let scrollBar = document.querySelectorAll('.el-scrollbar .el-scrollbar__bar')
scrollWrap.style.cssText = 'margin: 0px; max-height: none; overflow: hidden;'
scrollBar.forEach(ele => ele.style.width = 0)
})
},
// 切换选项
handleNodeClick(node) {
// 点击叶子节点后被选中
if (node.children == null || node.children == undefined){
if(this.multiple) {
// 多选(判重后添加)
let num = 0;
this.valueName.forEach(item => {
item == node[this.props.label] ? num++ : num;
})
if(num == 0) {
this.valueName.push(node[this.props.label]); // 输入框显示值
this.resultValue.push(node);
}
} else {
// 单选
this.$refs.selectTree.blur();
this.valueName = node[this.props.label];
this.resultValue = node;
}
this.$emit('getValue', this.resultValue);
}
},
// 从输入框中直接删除某个值时
changeValue(val) {
if(this.multiple) {
// 多选(同时删掉传给父组件中多余的值,再传给父组件)
this.resultValue.map((item, index) => {
let i = val.indexOf(item.name)
if(i == -1) {
this.resultValue.splice(index, 1)
}
})
this.$emit('getValue', this.resultValue);
} else {
// 单选
this.$emit('getValue', val);
}
},
// 清除选中
clearHandle() {
this.valueName = this.multiple ? [] : ''
this.resultValue = []
this.clearSelected()
this.$emit('getValue', this.resultValue)
},
// 清空选中样式
clearSelected() {
let allNode = document.querySelectorAll('#tree-option .el-tree-node')
allNode.forEach((element) => element.classList.remove('is-current'))
}
}
}
</script>
<style scoped>
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
height: auto;
max-height: 300px;
padding: 0;
overflow: hidden;
overflow-y: auto;
}
.el-select-dropdown__item.selected {
font-weight: normal;
}
ul li >>> .el-tree .el-tree-node__content {
height: auto;
padding: 0 20px;
}
.el-tree-node__label {
font-weight: normal;
}
.el-tree >>> .is-current .el-tree-node__label {
color: #409EFF;
font-weight: 700;
}
.el-tree >>> .is-current .el-tree-node__children .el-tree-node__label {
color: #606266;
font-weight: normal;
}
.el-popper {
z-index: 9999;
}
.ification_col {
width: 20px;
height: 10px;
display: inline-block;
}
</style>
页面中应用组件( index.vue ):
<!--
* @Description: select下拉搭配tree树形 选择
-->
<template>
<div class="selectTree">
<label>发送到人:</label>
<!-- 单选 + 默认值 -->
<el-select-tree :options="options" :value="value" @getValue="getSelectedValue"></el-select-tree>
<!-- 多选 + 默认值 -->
<!-- <el-select-tree :options="options" :multiple="multiple" :valueMultiple="valueMultiple" @getValue="getSelectedValue"></el-select-tree> -->
</div>
</template>
<script>
import elSelectTree from './el-select-tree.vue'
export default {
components: {
elSelectTree
},
data() {
return {
multiple: true,
value: {
id: 3,
name: '张三'
},
valueMultiple: [
{
id: 3,
name: '张三'
}, {
id: 4,
name: '张四'
}
],
options: [
{
id: 1,
name: '张一',
children: [{
id: 2,
name: '张二',
children: [{
id: 3,
name: '张三'
}, {
id: 4,
name: '张四'
}]
}]
},
{
id: 5,
name: '王一',
children: [{
id: 6,
name: '王二'
}, {
id: 7,
name: '王三'
}]
},
{
id: 8,
name: '李一',
children: [{
id: 9,
name: '李二'
}, {
id: 10,
name: '李三'
}]
}
]
}
},
created() {
},
methods: {
// 组件传出来的选中结果
getSelectedValue(value) {
console.log('选中的结果值', value)
}
}
}
</script>
<style lang="scss">
</style>