1.前言
小谭最近遇到一个需求:因为下拉选项过多,用el-tree对于逐级寻找很不方便,于是小谭就自己手写了个下拉框弹窗,废话不多说,上效果图:
页面展示: 基本上和普通下拉框一样
点击下拉框弹出的弹窗: 弹窗内最多可以实现三级下拉如果想实现更多,可以自己根据源码更改哦
2.代码实现:
HTML:
<!--
* @description: 通用下拉弹窗
* @fileName: SelectDialog.vue
* @author:xiaotan
* @date: 2023-04-06 10:42:58
* @Attributes: props:{
value: 对应label对应值 默认值value,
label: '展示的文字 默认值label,
children: 默认值 children,
parentId:关联的父级id字段 默认值parentId,若要实现回显则必传!!!
};
content: 双向绑定的值,
data:展示的数据
disabled:是否禁用
selectLastNode:是否只能选择最后一级
showSearch: 是否展示搜索栏 默认不展示
@change 选中数据触发的事件 第一个参数为选中的节点数据 第二个参数为选中的节点数据链
@searchData 搜索逻辑 参数为搜索的关键字
slot
search 自定义搜索内容
first 自定义一级内容 使用方法: <template #first="{data}"></template> data为当前节点数据
secend 自定义一二级内容
three 自定义三级内容
!-->
<template>
<div>
<div class="mySelectInput">
<el-select
ref="category"
value
:placeholder="showContent?'':'请选择'"
clearable
style="width: 100%;"
:disabled="disabled"
@focus="$refs.category.blur()"
@click.native="handleDialog"
/>
<div class="mySelectInputText" v-if="content">{{ showContent}}</div>
</div>
<el-dialog
class="category-container"
:visible.sync="visible"
width="80vw"
:append-to-body="true"
:before-close="handleClose"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<div v-loading="loading">
<div slot="title">请选择</div>
<el-row type="flex" class="search" v-if="showSearch">
<slot name="search" v-if="$scopedSlots.search"></slot>
<template v-else>
<el-input v-model="searchVal" placeholder="请输入"></el-input>
<el-button type="primary" @click="handleSearch" style="margin-right: 10px;">搜索</el-button>
<el-button type="primary" plain @click="handleClear">重置</el-button>
</template>
</el-row>
<div class="main">
<div v-if="!categoryList[0]">
<div>
<el-empty description="暂无数据"></el-empty>
</div>
</div>
<div>
<div class="list" v-for="(item, index) in categoryList" :key="item.id + index">
<p
class="list-title"
@click="handleChange(item)"
:class="item[props.value]|classFilter"
>
<slot name="first" :data="item" v-if="$scopedSlots.first"></slot>
<template v-else>{{ item[props.label||'label'] }}</template>
</p>
<div class="content">
<el-dropdown
v-for="(i, index) in item[props.children ||'children']"
:class="i[props.value]|classFilter"
:key="i.id + index"
>
<span class="down" @click="handleChange(item, i)">
<slot name="secend" :data="item" v-if="$scopedSlots.secend"></slot>
<template v-else>
{{ i[props.label||'label'] }}
<i
v-if=" i[props.children ||'children']"
class="el-icon-arrow-down"
></i>
</template>
</span>
<el-dropdown-menu slot="dropdown" v-if=" i[props.children ||'children']">
<el-dropdown-item
v-for="(j, index) in i[props.children ||'children']"
:key="j.id + index"
@click.native="handleChange(item, i, j)"
:class="j[props.value]|classFilter"
>
<slot name="three" :data="item" v-if="$scopedSlots.three"></slot>
<template v-else>{{ j[props.label||'label'] }}</template>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
JS:
let that = null;
export default {
props: {
content: {
type: undefined,
default: null,
},
disabled: {
type: Boolean,
default: false,
},
data: {
type: Array,
default: [],
},
props: {
type: Object,
default: { value: 'value', label: 'label', children: 'children', parentId: 'parentId' },
},
},
data() {
return {
visible: false,
loading: false,
showResult: false,
searchVal: '',
selectData: null,
showContent: '',
};
},
computed: {
categoryList() {
return this.data;
},
selectLastNode() {
return this.$attrs.selectLastNode == '';
},
showSearch() {
return this.$attrs.showSearch == '';
},
},
created() {
that = this;
},
mounted() {
let timer = setInterval(() => {
if (this.data) {
clearInterval(timer);
if (this.content) {
this.getSelectData();
this.$nextTick(() => {
this.contentFilter();
this.$forceUpdate();
});
}
}
}, 500);
},
methods: {
// 打开弹窗
handleDialog() {
if (!this.disabled) this.visible = true;
},
// 选中内容改变
handleChange(first, secend, three) {
let obj = three || secend || first;
if (this.selectLastNode && obj[this.props.children || 'children'] && obj[this.props.children || 'children']?.length) {
return false;
}
this.selectData = {
first,
secend,
three,
};
this.visible = false;
this.showContent = obj[this.props.label];
this.$emit('update:content', obj[this.props.value]);
this.$emit('change', obj, this.selectData);
},
// 关闭弹窗
handleClose() {
this.visible = false;
},
// 默认搜索事件
handleSearch() {
this.showResult = true;
this.$emit('searchData', this.searchVal);
},
// 搜索条件重置
handleClear() {
this.searchVal = '';
this.showResult = false;
},
// 选项回显过滤
contentFilter() {
if (this.selectData) {
let obj = this.selectData.three || this.selectData.secend || this.selectData.first;
this.showContent = obj?.[this.props.label] || this.content;
} else {
this.showContent = this.content;
}
},
// 获取回显数据
getSelectData() {
let [firstData] = this.recurrenceQuery(this.data, 'id', this.content);
this.selectData = {
first: '',
secend: '',
three: '',
};
let flag1 = firstData && firstData[this.props.parentId || 'parentId'] != undefined && firstData[this.props.parentId || 'parentId'] != null;
if (flag1) {
let [secendData] = this.recurrenceQuery(this.data, 'id', firstData[this.props.parentId || 'parentId']);
let flag2 = secendData && secendData[this.props.parentId || 'parentId'] != undefined && secendData[this.props.parentId || 'parentId'] != null;
if (flag2) {
let [threeData] = this.recurrenceQuery(this.data, 'id', secendData[this.props.parentId || 'parentId']);
let flag3 = threeData && threeData[this.props.parentId || 'parentId'] != undefined && threeData[this.props.parentId || 'parentId'] != null;
if (flag3) {
this.selectData.first = threeData;
this.selectData.secend = secendData;
this.selectData.three = firstData;
this.$set(this.selectData, 'first', threeData);
this.$set(this.selectData, 'secend', secendData);
this.$set(this.selectData, 'three', firstData);
} else {
this.$set(this.selectData, 'first', secendData);
this.$set(this.selectData, 'secend', firstData);
}
} else {
this.$set(this.selectData, 'first', secendData);
this.$set(this.selectData, 'secend', firstData);
}
} else {
this.$set(this.selectData, 'first', firstData);
}
if (this.content && !this.selectData.first) {
this.getSelectData();
this.contentFilter();
}
},
},
filters: {
classFilter(item) {
if (that.selectData) {
for (const key in that.selectData) {
if (that.selectData[key]?.[that.props.value] == item) {
return 'active';
}
}
}
return '';
},
},
};
CSS:
.category-container {
::v-deep .el-dialog {
user-select: none;
background: #ffffff;
border-radius: 12px;
margin: 0 !important;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
.el-dialog__header {
padding: 12px 20px 0;
font-size: 16px;
font-weight: 500;
color: #1b62ff;
line-height: 22px;
}
.el-dialog__body {
padding: 8px 20px;
.search {
margin-bottom: 10px;
.el-input {
width: 162px;
margin-right: 16px;
}
}
.main {
height: 650px;
overflow-y: auto;
.com {
padding: 9px 0;
border-bottom: 1px solid #e1e1e5;
.title {
font-size: 16px;
font-weight: 600;
color: #3b3b5a;
line-height: 22px;
margin: 0;
}
}
.list {
display: flex;
flex-direction: row;
padding: 4px 0;
border-bottom: 1px solid #e1e1e5;
box-sizing: border-box;
align-items: center;
.list-title {
font-size: 14px;
font-weight: 600;
color: #3b3b5a;
margin: 0;
padding: 2px 8px;
cursor: pointer;
min-width: 72px;
text-align-last: justify;
border-radius: 4px;
text-align: justify;
&:hover {
background: #e8efff;
}
}
.content {
flex: 1;
margin: 0 14px;
.el-dropdown {
line-height: 22px;
margin-right: 5px;
border-radius: 4px;
padding: 0px 8px;
border: 1px solid transparent;
color: #6e7b9a;
margin-bottom: 2px;
margin-top: 2px;
&:hover {
background: #e8efff;
}
.down {
// font-size: 16px;
font-size: 12px;
line-height: 22px;
cursor: pointer;
}
.el-dropdown-menu__item {
font-size: 14px;
color: #1b62ff;
line-height: 16px;
}
}
}
}
}
}
}
}
.active {
border: 1px dashed #aaa !important;
}
.el-dropdown-menu__item.active {
border: 0px !important;
background-color: #eee;
color: #000 !important;
}
.mySelectInput {
position: relative;
> .mySelectInputText {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
margin: auto;
line-height: 32px;
padding-left: 15px;
padding-right: 32px;
font-size: 13px;
pointer-events: none;
}
}
本文转载自: https://blog.csdn.net/qq_45947497/article/details/130157706
版权归原作者 小谭鸡米花 所有, 如有侵权,请联系我们删除。
版权归原作者 小谭鸡米花 所有, 如有侵权,请联系我们删除。