当前位置: 首页 > 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>
http://www.lqws.cn/news/526123.html

相关文章:

  • 《量子计算对加密体系的降维打击:RSA2048在Shor算法下的生存时间预测》的终极解析,结合量子算法推演/后量子加密实战/蒙特卡洛预测模型
  • 编程语言与认知科学:构建理解机器与人类共同语言的桥梁
  • Rust 中的时间处理利器:chrono
  • AI是什么有什么用
  • FFmpeg音视频同步思路
  • 游戏App前端安全加固:利用AI云防护技术抵御恶意攻击
  • 《市梦录》这款游戏的完整商业计划书
  • 16.1 Python应用容器化终极指南:Dockerfile多阶段构建与安全优化实战
  • 《网络攻防技术》《数据分析与挖掘》《网络体系结构与安全防护》这三个研究领域就业如何?
  • MIT 6.824学习心得(1) 浅谈分布式系统概论与MapReduce
  • jina-embeddings-v4
  • Oracle 角色与自定义角色深度解析
  • vllm加载多个Lora部署
  • Linux系统(信号篇):信号的产生
  • 重塑音视频叙事:Premiere文本剪辑与Podcast AI降噪的革命性工作流
  • dify小用
  • 操作系统面试知识点(1):操作系统基础
  • unibest+uniapp+vue3+TS+Wot UI分包
  • uniapp页面间通信uni.$on与通过uni.navigateTo中eventChannal的方式的区别
  • 【重点】【DP】174.地下城游戏
  • HTML表单元素
  • Webpack 核心概念
  • 数的范围(连续数字边界)
  • 大语言模型(LLM)初探:核心概念与应用场景
  • 【深度学习新浪潮】什么是上下文工程?
  • 【偏微分方程】基本概念
  • 【网络实验】-配置用户登录
  • spring boot项目整合百度翻译
  • Windows 安装 Redis8.0.2
  • JVM 中的 GC 算法演进之路!(Serial、CMS、G1 到 ZGC)