javascript
复制代码
<!-- 使用文档 https://blog.csdn.net/weixin_49455560/article/details/160083586?spm=1011.2415.3001.5331 -->
<template type="text/x-template" id="virtual-select">
<el-select :size="size" v-model="value" :loading="loading" @change="handleChange" @focus="handleFocus" @blur="handleBlur" :multiple="multiple" :filterable="filterable" :filter-method="handleFilterMethod" @visible-change="handleVisibleChange" :disabled="disabled" :collapse-tags="collapseTags" :placeholder="placeholder" :clearable="clearable">
<!-- begin 普通下拉 -->
<recycle-scroller v-if="type == 1" :items="filteredItems" :item-size="itemSize" :key-field="props.value" style="max-height: 250px">
<template v-slot:default="{item, index}">
<el-option :label="item[props.label]" :value="item[props.value]" :key="item[props.value]">
<slot name="default" :item="item"></slot>
</el-option>
</template>
<template #empty v-if="!filteredItems.length">
<div class="empty-txt">暂无数据</div>
</template>
</recycle-scroller>
<!-- end 普通下拉 -->
<!-- begin 分组下拉 -->
<dynamic-scroller v-if="type == 2" :items="filteredGroupItems" :min-item-size="groupItemSize" :key-field="props.groupId" style="max-height: 250px">
<template v-slot="{ item, index, active }">
<dynamic-scroller-item :item="item" :active="active" :size-dependencies="[ item[props.children] || [] ]" :data-index="index">
<el-option-group v-if="item[props.children].length > 0" :label="item[props.group]" :key="item[props.groupId]">
<el-option v-for="(citem, cindex) in item[props.children] || []" :key="citem[props.value]" :label="citem[props.label]" :value="citem[props.value]">
<slot name="default" :item="citem"></slot>
</el-option>
</el-option-group>
</dynamic-scroller-item>
</template>
<template #empty v-if="!filteredGroupItems.length">
<div class="empty-txt">暂无数据</div>
</template>
</dynamic-scroller>
<!-- end 分组下拉 -->
</el-select>
</template>
<link rel="stylesheet" href="/assets/js/vue-virtual-scroller/vue-virtual-scroller.css" />
<script src="/assets/js/vue-virtual-scroller/vue-virtual-scroller.min.js"></script>
<script>
Vue.component("recycle-scroller", VueVirtualScroller.RecycleScroller);
Vue.component("dynamic-scroller", VueVirtualScroller.DynamicScroller);
Vue.component("dynamic-scroller-item", VueVirtualScroller.DynamicScrollerItem);
Vue.component("virtual-select", {
template: "#virtual-select",
model: {
prop: "value",
event: "change",
},
props: {
type: {
type: Number,
default: 1, // 1-普通虚拟下拉 2-分组虚拟下拉
},
value: {
type: [String, Array],
required: true,
},
loading: {
type: Boolean,
default: false,
},
items: {
type: Array,
default: [],
},
multiple: {
type: Boolean,
default: false,
},
filterable: {
type: Boolean,
default: false,
},
filterMethod: {
type: Function,
default: null,
},
clearable: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: "请选择",
},
size: {
type: String,
default: "mini",
},
disabled: {
type: Boolean,
default: false,
},
collapseTags: {
type: Boolean,
default: false,
},
// 单行高度(px)
itemSize: {
type: Number,
default: 34,
},
// 分组项最小高度(px)
groupItemSize: {
type: Number,
default: 64,
},
// 自定义属性名称
props: {
type: Object,
default: () => {
return {
group: "group", // 分组名称
groupId: "groupId", // 分组id
label: "label", // 选项名称
value: "value", // 选项值
children: "children", // 子数据
};
},
},
},
computed: {
// 过滤后的普通列表数据
filteredItems() {
if (!this.filterable || !this.query || this.filterMethod) {
return this.items;
}
const labelField = this.props.label;
return this.items.filter(item => String(item[labelField]).toLowerCase().includes(this.query.toLowerCase()));
},
// 过滤后的分组列表数据
filteredGroupItems() {
if (!this.filterable || !this.query || this.filterMethod) {
return this.items.filter(group => group[this.props.children] && group[this.props.children].length);
}
const labelField = this.props.label;
const childrenField = this.props.children;
const keyword = this.query.toLowerCase();
return this.items
.map(group => {
const filteredChildren = (group[childrenField] || []).filter(child => String(child[labelField]).toLowerCase().includes(keyword));
return {
...group,
[childrenField]: filteredChildren,
};
})
.filter(group => group[this.props.children] && group[this.props.children].length);
},
},
data: () => {
return {
query: "",
};
},
methods: {
// 过滤方法
handleFilterMethod(val) {
this.query = val;
if (this.filterMethod) {
this.filterMethod(this.query);
}
},
// 选择方法
handleChange(val) {
this.$emit("change", val);
},
// 可见性改变方法
handleVisibleChange(visible) {
if (visible) {
this.query = "";
}
this.$emit("visible-change", visible);
},
// 聚焦方法
handleFocus() {
this.$emit("focus", this);
},
// 失焦方法
handleBlur() {
this.$emit("blur", this);
},
},
});
</script>
<style>
.empty-txt {
line-height: 32px;
text-align: center;
color: #999;
}
</style>