当前位置: 首页 > news >正文

el-select封装下拉加载组件

单选下拉框

<template><el-selectv-bind="$attrs"ref="searchSelect"filterableremote:remote-method="getFirstList":class="[selectClass, 'select-load-more']":popper-class="pClass + ' select-load-more-popper '":value-key="valueName":value="valueItem"v-on="innerListeners"@visible-change="handleVisible"@input="onInput"@change="onChange"><el-optionv-for="item in showOptions":key="item[valueName]":label="getNestedValue(item, labelName)":value="item"><el-tooltip placement="left"><divslot="content"class="tooltip-content">{{ getNestedValue(item, labelName) }}</div><div class="longOmitted">{{ getNestedValue(item, labelName) }}</div></el-tooltip></el-option></el-select>
</template><script>
import { debounce } from 'lodash'export default {props: {// value名valueName: {type: String,default: 'value',},// value值value: {type: [String, Number],default: null,},// label名labelName: {type: String,default: 'label',},// label值labelValue: {type: String,default: '',},// 自定义label函数setLabelFun: {type: Function,default: null,},// 接口入参查询字段searchWord: {type: String,required: true,},// 获取下拉列表数据方法getList: {type: Function,required: true,},// 分页参数pageParams: {type: Object,default() {return {setPageInfo: 'pageInfo',setPageNo: 'pageNo',setPageSize: 20,}},},// 是否禁用编辑回显(列表筛选框时可设为true)disabledEditShow: {type: Boolean,default: false,},},data() {return {innerValueItem: null,options: [],optionsResBak: null, // 缓存全量的数据loadingFlag: true,pClass: '', // 用于唯一指定下拉框的节点selectClass: '', // select选择框的唯一样式pageInfo: {pageNo: 1,total: 0,},}},computed: {innerListeners() {const res = {}Object.keys(this.$listeners).filter((key) => {return !['input', 'change'].includes(key) // 这两个事件单独处理了}).forEach((key) => {res[key] = this.$listeners[key]})return res},valueItem() {// 禁用回显使用内部组件值if (this.disabledEditShow) {return this.innerValueItem}return {[this.labelName]: this.labelValue,[this.valueName]: this.value,}},innerArray() {return this.valueItem ? [this.valueItem] : [] // 实时表单值},showOptions() {return this.endWithInnerValue(this.options)},},watch: {value(val) {// 兼容 disabledEditShow 时从组件外部清空表单的情况if (this.disabledEditShow) {if (!val && typeof val != 'number') {this.innerValueItem = null}}},},created() {this.pClass = this.getUniqueKey()this.selectClass = this.getUniqueKey() + '-select'this.getFirstList('')},mounted() {// 获取dom节点 update 更新类名使得全局唯一const domElementNode = document.querySelector(`.${this.pClass} .el-select-dropdown__wrap`)// 注册下拉滚动事件domElementNode.addEventListener('scroll', () => this.debOnBottom(domElementNode))},methods: {debOnBottom: debounce(function (domElementNode) {const isBottom = domElementNode.scrollHeight - domElementNode.scrollTop <= domElementNode.clientHeight * 1.1 // *1.1 屏幕缩放时可能有误差if (isBottom && this.showOptions.length < this.pageInfo.total && !this.loadingFlag) {this.pageInfo[this.pageParams.setPageNo]++this.getNextList(this?.$refs?.searchSelect?.previousQuery)}}, 150),onInput(val) {this.innerValueItem = valthis.$emit('input', this.formatValue2Id(val), val)},onChange(val) {this.innerValueItem = valthis.$emit('change', this.formatValue2Id(val), val)},formatValue2Id(val) {if (Array.isArray(val)) {return val.map((i) => i[this.valueName])}return val?.[this.valueName] || ''},getUniqueKey() {const timestamp = Date.now()// 生成一个随机数(0-9999)const randomNum = Math.floor(Math.random() * 10000)// 将时间戳和随机数组合为唯一键return `key_${timestamp}_${randomNum}`},// 获取下一页列表getNextList(val = '') {if (this.loadingFlag) returnthis.loadingFlag = truethis.getList({ [this.pageParams.setPageNo]: this.pageInfo[this.pageParams.setPageNo], pageSize: this.pageParams.setPageSize, [this.searchWord]: (val || '').trim() }).then((res) => {if (res?.success) {this.pageInfo = res?.data?.[this.pageParams.setPageInfo]if (!this.disabledEditShow) {this.options = this.options.concat(this.filterRecordsInnerValue(res.data.records || []))} else {this.options = this.options.concat(res.data.records || [])}}}).finally(() => {this.loadingFlag = false})},// 获取第一页getFirstList(val = '') {this.loadingFlag = truethis.pageInfo[this.pageParams.setPageNo] = 1this.getList({ [this.pageParams.setPageNo]: this.pageInfo[this.pageParams.setPageNo], pageSize: this.pageParams.setPageSize, [this.searchWord]: (val || '').trim() }).then((res) => {if (res?.success) {this.pageInfo = res?.data?.[this.pageParams.setPageInfo]this.options = res?.data?.records || []// 缓存全量数据if (val == '') {this.optionsResBak = res}}}).finally(() => {this.loadingFlag = false})},insertItems(arr, index, elements) {// 处理负数的 index 值if (index < 0) {index = Math.max(arr.length + index, 0)}// 将数组从 index 位置拆分为两部分const before = arr.slice(0, index) // index 之前的部分const after = arr.slice(index) // index 及之后的部分return [...before, ...elements, ...after] // 返回修改后的数组},// 将选中项去重后, concat到下拉选项中(用于第一页)-避免ID重复endWithInnerValue(records) {// 拼接到最后会导致底部下拉有问题,这里修改为拼接到第一页之后const index = Math.min(this.pageParams.setPageSize, records.length)return this.insertItems(records,index,this.innerArray.filter((innerItem) => !records.map((i) => i[this.valueName]).includes(innerItem[this.valueName])))},// 去掉记录中的选中项(用于非第一页)-避免ID重复filterRecordsInnerValue(records) {return records?.filter((recordItem) => !this.innerArray.map((i) => i[this.valueName]).includes(recordItem[this.valueName]))},// 下拉框出现 / 消失handleVisible(flag) {if (!flag) {// 下拉框出现时重新请求数据 优先从缓存取数据if (this.optionsResBak) {this.pageInfo = this.optionsResBak?.data?.[this.pageParams.setPageInfo]this.options = this.optionsResBak?.data?.records || []} else {this.getFirstList()}}},// 递归获取对象的属性值getNestedValue(item, label) {if (this.setLabelFun) {return this.setLabelFun(item)} else {return item[label]}},},
}
</script><style lang="scss" scoped>
.tooltip-content {max-width: 200px;
}.longOmitted {max-width: 280px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;
}.select-load-more {/deep/ .el-select__input {min-width: 80px;}/deep/ .el-tag--info {overflow: hidden;text-overflow: ellipsis;white-space: nowrap;.el-tag--disabled {width: 100%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}}::v-deep .el-tag {box-sizing: border-box;height: 22px;padding: 0 10px;font-size: 12px;line-height: 20px;white-space: nowrap;}::v-deep .el-select__caret:first-child::before {content: '\e6e1';/* 添加下拉箭头图标对应的字体图标代码 */}::v-deep .is-focus {.el-select__caret:first-child {transform: rotateZ(0deg);/* 控制箭头旋转 */}}
}.select-load-more-popper {.el-select-dropdown__item.is-disabled.hover {color: #bfbfbf;background-color: #fff;}
}
</style>
<!-- 
示例:<SingleSelectLoadMoreref="publisherFormRef"v-model="contentForm.authorId"value-name="id"label-name="authorName":label-value="contentForm.authorName":placeholder="$t(COMMON_MODULE.PLEASE_SELECT)"search-word="authorName":page-params="{setPageInfo: 'pageInfo',setPageNo: 'pageNo',setPageSize: 20,}"class="w430":get-list="(params) => postAdminContentAuthorPageApi(params)"@change="onSelectChange"/>
-->

兼容多选

<template><co-selectv-if="isCo"v-bind="$attrs"ref="searchSelect":class="[selectClass, 'select-load-more']"filterableremote:remote-method="getFirstList":popper-class="pClass + ' select-load-more-popper ' + popperClass":value-key="setValue":value="valueItem":multiple="multiple"v-on="innerListeners"@visible-change="handleVisible"@input="onInput"@change="onChange"><!-- v-on="$listeners" --><co-optionv-for="item in showOptions":key="item[setValue]":label="getNestedValue(item, setLabel)":value="item"><co-tooltip placement="left"><divslot="content"class="tooltip-content">{{ getNestedValue(item, setLabel) }}</div><divclass="longOmitted":style="{ width: optionItemWidth }">{{ getNestedValue(item, setLabel) }}</div></co-tooltip></co-option></co-select><el-selectv-elsev-bind="$attrs"ref="searchSelect"filterableremote:remote-method="getFirstList":class="[selectClass, 'select-load-more']":popper-class="pClass + ' select-load-more-popper ' + popperClass":value-key="setValue":value="valueItem":multiple="multiple"v-on="innerListeners"@visible-change="handleVisible"@input="onInput"@change="onChange"><el-optionv-for="item in showOptions":key="item[setValue]":label="getNestedValue(item, setLabel)":value="item"><el-tooltip placement="left"><divslot="content"class="tooltip-content">{{ getNestedValue(item, setLabel) }}</div><divclass="longOmitted":style="{ width: optionItemWidth }">{{ getNestedValue(item, setLabel) }}</div></el-tooltip></el-option></el-select>
</template><script>
import { debounce } from 'lodash'export default {//表示不添加在组件的根元素上inheritAttrs: false,props: {// Cook UI传此参数isCo: {type: Boolean,default: false,},value: {type: [String, Number, Array, Object],default: null,},// 定义接口入参的关键词的key:  keyword => params[setKeywordKey]setKeywordKey: {type: String,required: true,},// 设置列表绑定值 label => option[setLabel]setLabel: {type: String,default: 'label',},// 设置列表绑定值 label => option[setLabel]setLabelFun: {type: Function,default: null, // (option)=>option['label']},// 设置列表显示字段 value => option[setValue]setValue: {type: String,default: 'value',},// 设置每页加载数量setPageSize: {type: Number,default: 50,},// 获取下拉列表数据方法getList: {type: Function,required: true,},popperClass: {type: String,default: '',},// 设置返回值的分页对象: pageInfo => res.data[setPageInfo]setPageInfo: {type: String,default: 'pageInfo',},// 设置返回值的分页对象: pageNo => res.data[setPageInfo][setPageNo]setPageNo: {type: String,default: 'pageNo',},// 是否多选multiple: {type: Boolean,default: false,},// 仅默认 isItemValue=false && disabledEditShow=false 时需要// 用于编辑时回显下拉选项,需要与下拉选项接口的返回数据格式保持一致// 调用时需要反向存值 @change=(id,item)=>valueOptionsData=itemvalueOptions: {type: [Array, Object, String],default() {return []},},// 禁用编辑回显: 列表筛选框时可设为 truedisabledEditShow: {type: Boolean,default: false,},// value值绑定为整个选项Item,而不是idisItemValue: {type: Boolean,default: false,},// 禁用初次加载数据disabledGetListFrist: {type: Boolean,default: false,},// 下拉框宽度optionItemWidth: {type: String,default: '280px',},},data() {return {innerValueItem: null,options: [],optionsResBak: null, // 缓存全量的数据loadingFlag: true,pClass: '', // 用于唯一指定下拉框的节点selectClass: '', // select选择框的唯一样式selectWidth: 0,pageInfo: {pageNo: 1,total: 0,},}},computed: {innerListeners() {const res = {}Object.keys(this.$listeners).filter((key) => {return !['input', 'change'].includes(key) // 这两个事件单独处理了}).forEach((key) => {res[key] = this.$listeners[key]})return res},valueItem() {// 表单直接绑定整个对象if (this.isItemValue) {return this.value}// 禁用回显使用内部组件值if (this.disabledEditShow) {return this.innerValueItem}// 表单仅绑定id时,需要使用valueOptions来回显return this.valueOptions},showOptions() {return this.endWithInnerValue(this.options)},},watch: {value(val) {// 兼容 disabledEditShow 时从组件外部清空表单的情况if (this.disabledEditShow) {if (!val && typeof val != 'number') {if (this.multiple) {this.innerValueItem = []} else {this.innerValueItem = null}}}},},created() {this.pClass = this.getUniqueKey()this.selectClass = this.getUniqueKey() + '-select'// this.updateValueItem()if (!this.disabledGetListFrist) {this.getFirstList('')}},mounted() {// 获取dom节点 update 更新类名使得全局唯一const domElementNode = document.querySelector(`.${this.pClass} .el-select-dropdown__wrap`)// 注册下拉滚动事件domElementNode.addEventListener('scroll', () => this.debOnBottom(domElementNode))},methods: {debOnBottom: debounce(function (domElementNode) {const isBottom = domElementNode.scrollHeight - domElementNode.scrollTop <= domElementNode.clientHeight * 1.1 // *1.1 屏幕缩放时可能有误差if (isBottom && this.showOptions.length < this.pageInfo.total && !this.loadingFlag) {this.pageInfo[this.setPageNo]++this.getNextList(this?.$refs?.searchSelect?.previousQuery)}}, 150),onInput(val) {this.innerValueItem = valif (this.isItemValue) {this.$emit('input', val)} else {this.$emit('input', this.formatValue2Id(val), val)}// emit(id,item)},onChange(val) {this.innerValueItem = valif (this.isItemValue) {this.$emit('change', val)} else {this.$emit('change', this.formatValue2Id(val), val)}},formatValue2Id(val) {if (Array.isArray(val)) {return val.map((i) => i[this.setValue])}return val?.[this.setValue] || ''},getUniqueKey() {const timestamp = Date.now()// 生成一个随机数(0-9999)const randomNum = Math.floor(Math.random() * 10000)// 将时间戳和随机数组合为唯一键return `key_${timestamp}_${randomNum}`},// 获取下一页列表getNextList(val = '') {if (this.loadingFlag) returnthis.loadingFlag = truethis.getList({ [this.setPageNo]: this.pageInfo[this.setPageNo], pageSize: this.setPageSize, [this.setKeywordKey]: (val || '').trim() }).then((res) => {if (res?.success) {this.pageInfo = res?.data?.[this.setPageInfo]if (!this.disabledEditShow) {this.options = this.options.concat(this.filterRecordsInnerValue(res.data.records || []))} else {this.options = this.options.concat(res.data.records || [])}}}).finally(() => {this.loadingFlag = false})},// 获取第一页getFirstList(val = '') {this.loadingFlag = truethis.pageInfo[this.setPageNo] = 1this.getList({ [this.setPageNo]: this.pageInfo[this.setPageNo], pageSize: this.setPageSize, [this.setKeywordKey]: (val || '').trim() }).then((res) => {if (res?.success) {this.pageInfo = res?.data?.[this.setPageInfo]this.options = res?.data?.records || []// 缓存全量数据if (val == '' && this.pageInfo[this.setPageNo] === 1) {this.optionsResBak = res}}}).finally(() => {this.loadingFlag = false})},insertItems(arr, index, elements) {// 处理负数的 index 值if (index < 0) {index = Math.max(arr.length + index, 0)}// 将数组从 index 位置拆分为两部分const before = arr.slice(0, index) // index 之前的部分const after = arr.slice(index) // index 及之后的部分return [...before, ...elements, ...after] // 返回修改后的数组},// 将选中项去重后, concat到下拉选项中(用于第一页)-避免ID重复endWithInnerValue(records) {// const innerArray = this.multiple ? this.valueOptions || [] : this.valueOptions ? [this.valueOptions] : [] // 表单初始值const innerArray = this.multiple ? this.valueItem || [] : this.valueItem ? [this.valueItem] : [] // 实时表单值// 拼接到最后会导致底部下拉有问题,这里修改为拼接到第一页之后const index = Math.min(this.setPageSize, records.length)return this.insertItems(records,index,innerArray.filter((innerItem) => !records.map((i) => i[this.setValue]).includes(innerItem[this.setValue])))},// 去掉记录中的选中项(用于非第一页)-避免ID重复filterRecordsInnerValue(records) {const innerArray = this.multiple ? this.valueItem || [] : this.valueItem ? [this.valueItem] : [] // 实时表单值return records?.filter((recordItem) => !innerArray.map((i) => i[this.setValue]).includes(recordItem[this.setValue]))},// 下拉框出现 / 消失handleVisible(flag) {if (!flag) {// 下拉框出现时重新请求数据 优先从缓存取数据if (this.optionsResBak) {this.pageInfo = JSON.parse(JSON.stringify(this.optionsResBak?.data?.[this.setPageInfo]))this.options = JSON.parse(JSON.stringify(this.optionsResBak?.data?.records || []))} else {this.getFirstList()}}},// 处理label// 递归获取对象的属性值getNestedValue(item, label) {if (this.setLabelFun) {return this.setLabelFun(item)} else {return item[label]}},},
}
</script><style lang="scss" scoped>
.tooltip-content {max-width: 200px;
}
.longOmitted {// max-width: 280px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;
}
.select-load-more {/deep/ .el-select__input {min-width: 80px;}/deep/ .el-tag--info {overflow: hidden;text-overflow: ellipsis;white-space: nowrap;.el-tag--disabled {width: 100%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}}::v-deep .el-tag {box-sizing: border-box;height: 22px;padding: 0 10px;font-size: 12px;line-height: 20px;white-space: nowrap;}::v-deep .el-select__caret:first-child::before {content: '\e6e1'; /* 添加下拉箭头图标对应的字体图标代码 */}::v-deep .is-focus {.el-select__caret:first-child {transform: rotateZ(0deg); /* 控制箭头旋转 */}}
}.select-load-more-popper {.el-select-dropdown__item.is-disabled.hover {color: #bfbfbf;background-color: #fff;}
}
</style>

相关文章:

  • wordpress发布产品seo搜索优化网站推广排名
  • 做网站最好用的软件搜索引擎优化排名工具
  • 如何创办公司广西百度seo
  • 制作网站怎么做的全网营销整合营销
  • 中国建设官网招聘网站网站seo入门基础教程
  • 盘锦网站建设公司seo的概念是什么
  • 【Linux学习笔记】进程通信之消息队列和信号量
  • Oracle数据库捕获造成死锁的SQL语句
  • 采集文章+原创AI处理+发布网站详细教程
  • 开疆智能CCLinkIE转ModbusTCP网关连接PCA3200电能表配置案例
  • HarmonyOS NEXT仓颉开发语言实战案例:银行App
  • 【C++】组合模式
  • GitLab 18.1 发布依赖列表过滤、合规状态报告控制状态弹窗,可升级体验!
  • 前端框架大乱斗:React、Vue 和 Angular 的相爱相杀
  • 连接打印机0x0000011b错误两种完美解决方法
  • LightGBM:极速梯度提升机——结构化数据建模的终极武器
  • 数据结构进阶 第六章 树与二叉树
  • MongoDB 相关知识文档
  • YOLOv13:目标检测的全面攻略与实战指南
  • 进程和线程的区别?
  • 组织策略性陪伴顾问
  • 认识Jacobian
  • Java 大视界 -- Java 大数据机器学习模型在卫星通信信号干扰检测与智能抗干扰中的应用(323)
  • 【机器学习第一期(Python)】梯度提升决策树 GBDT
  • 2D写实交互数字人如何重塑服务体验?
  • 4.2_1朴素模式匹配算法