当前位置:   article > 正文

封装el-select,实现虚拟滚动,可单选、多选、搜索查询、创建条目

el-select

本组件基于下文代码有所改动:element 下拉数据过多,导致列表卡顿加载慢,使用虚拟列表方式_element下拉框数据过多_风掠过有空白的博客-CSDN博客

一、问题描述

        表单中某下拉框,由于数据过多,选择的时候会因为数据量过大导致页面卡顿,于是对于el-select进行二次封装,实现虚拟滚动。

二、实现如下:
        可把代码拉下来查看:vue-demo: vueDemo (https://gitee.com/kyang00/vue-demo.git​​​​​​​)

        看起来是加载了全部数据,实际上只加载了自己设定的10条(可修改)数据。

 Step1: npm install vue-virtual-scroll-list --save

 Step2: 创建两个文件

 一、Select.vue

  1. <template>
  2. <div>
  3. <el-select
  4. popper-class="virtualselect"
  5. class="virtual-select-custom-style"
  6. :popper-append-to-body="false"
  7. :value="defaultValue"
  8. filterable
  9. :filter-method="filterMethod"
  10. default-first-option
  11. clearable
  12. :placeholder="placeholderParams"
  13. :multiple="isMultiple"
  14. :allow-create="allowCreate"
  15. @visible-change="visibleChange"
  16. v-on="$listeners"
  17. @clear="clearChange"
  18. >
  19. <virtual-list
  20. ref="virtualList"
  21. class="virtualselect-list"
  22. :data-key="value"
  23. :data-sources="selectArr"
  24. :data-component="itemComponent"
  25. :keeps="keepsParams"
  26. :extra-props="{
  27. label: label,
  28. value: value,
  29. isRight: isRight,
  30. isConcat: isConcat,
  31. concatSymbol: concatSymbol
  32. }"
  33. ></virtual-list>
  34. </el-select>
  35. </div>
  36. </template>
  37. <script>
  38. import {
  39. validatenull
  40. } from '@/utils'
  41. import virtualList from 'vue-virtual-scroll-list'
  42. import ElOptionNode from './el-option-node'
  43. export default {
  44. components: {
  45. 'virtual-list': virtualList
  46. },
  47. model: {
  48. prop: 'bindValue',
  49. event: 'change'
  50. },
  51. props: {
  52. // // 父组件传的值
  53. // selectData: {
  54. // type: Object,
  55. // default() {
  56. // return {}
  57. // }
  58. // },
  59. // 数组
  60. list: {
  61. type: Array,
  62. default() {
  63. return []
  64. }
  65. },
  66. // 显示名称
  67. label: {
  68. type: String,
  69. default: ''
  70. },
  71. // 标识
  72. value: {
  73. type: String,
  74. default: ''
  75. },
  76. // 是否拼接label | value
  77. isConcat: {
  78. type: Boolean,
  79. default: false
  80. },
  81. // 拼接label、value符号
  82. concatSymbol: {
  83. type: String,
  84. default: ' | '
  85. },
  86. // 显示右边
  87. isRight: {
  88. type: Boolean,
  89. default: false
  90. },
  91. // 加载条数
  92. keepsParams: {
  93. type: Number,
  94. default: 10
  95. },
  96. // 绑定的默认值
  97. bindValue: {
  98. type: [String, Array],
  99. default() {
  100. if (typeof this.bindValue === 'string') return ''
  101. return []
  102. }
  103. },
  104. // 是否多选
  105. isMultiple: {
  106. type: Boolean,
  107. default: false
  108. },
  109. placeholderParams: {
  110. type: String,
  111. default: '请选择'
  112. },
  113. // 是否允许创建条目
  114. allowCreate: {
  115. type: Boolean,
  116. default: false
  117. }
  118. },
  119. data() {
  120. return {
  121. itemComponent: ElOptionNode,
  122. selectArr: [],
  123. defaultValue: null // 绑定的默认值
  124. }
  125. },
  126. watch: {
  127. 'list'() {
  128. this.init()
  129. },
  130. bindValue: {
  131. handler(val, oldVal) {
  132. this.defaultValue = this.bindValue
  133. if (validatenull(val)) this.clearChange()
  134. this.init()
  135. },
  136. immediate: false,
  137. deep: true
  138. }
  139. },
  140. mounted() {
  141. this.defaultValue = this.bindValue
  142. this.init()
  143. },
  144. methods: {
  145. init() {
  146. if (!this.defaultValue || this.defaultValue?.length === 0) {
  147. this.selectArr = this.list
  148. } else {
  149. // 回显问题
  150. // 由于只渲染固定keepsParams(10)条数据,当默认数据处于10条之外,在回显的时候会显示异常
  151. // 解决方法:遍历所有数据,将对应回显的那一条数据放在第一条即可
  152. this.selectArr = JSON.parse(JSON.stringify(this.list))
  153. if (typeof this.defaultValue === 'string' && !this.isMultiple) {
  154. let obj = {}
  155. if (this.allowCreate) {
  156. const arr = this.selectArr.filter(val => {
  157. return val[this.value] === this.defaultValue
  158. })
  159. if (arr.length === 0) {
  160. const item = {}
  161. // item[this.value] = `Create-${this.defaultValue}`
  162. item[this.value] = this.defaultValue
  163. item[this.label] = this.defaultValue
  164. item.allowCreate = true
  165. this.selectArr.push(item)
  166. this.$emit('selChange', item)
  167. } else {
  168. this.$emit('selChange', arr[0])
  169. }
  170. }
  171. // 单选
  172. for (let i = 0; i < this.selectArr.length; i++) {
  173. const element = this.selectArr[i]
  174. if (element[this.value]?.toLowerCase() === this.defaultValue?.toLowerCase()) {
  175. obj = element
  176. this.selectArr?.splice(i, 1)
  177. break
  178. }
  179. }
  180. this.selectArr?.unshift(obj)
  181. } else if (this.isMultiple) {
  182. if (this.allowCreate) {
  183. this.defaultValue.map(v => {
  184. const arr = this.selectArr.filter(val => {
  185. return val[this.value] === v
  186. })
  187. if (arr?.length === 0) {
  188. const item = {}
  189. // item[this.value] = `Create-${v}`
  190. item[this.value] = v
  191. item[this.label] = v
  192. item.allowCreate = true
  193. this.selectArr.push(item)
  194. this.$emit('selChange', item)
  195. } else {
  196. this.$emit('selChange', arr[0])
  197. }
  198. })
  199. }
  200. // 多选
  201. for (let i = 0; i < this.selectArr.length; i++) {
  202. const element = this.selectArr[i]
  203. this.defaultValue?.map(val => {
  204. if (element[this.value]?.toLowerCase() === val?.toLowerCase()) {
  205. obj = element
  206. this.selectArr?.splice(i, 1)
  207. this.selectArr?.unshift(obj)
  208. }
  209. })
  210. }
  211. }
  212. }
  213. },
  214. // 搜索
  215. filterMethod(query) {
  216. if (!validatenull(query?.trim())) {
  217. this.$refs.virtualList.scrollToIndex(0) // 滚动到顶部
  218. setTimeout(() => {
  219. this.selectArr = this.list.filter(item => {
  220. return this.isRight || this.isConcat
  221. ? (item[this.label].trim()?.toLowerCase()?.indexOf(query?.trim()?.toLowerCase()) > -1 || item[this.value]?.toLowerCase()?.indexOf(query?.trim()?.toLowerCase()) > -1)
  222. : item[this.label]?.toLowerCase()?.indexOf(query?.trim()?.toLowerCase()) > -1
  223. })
  224. }, 100)
  225. } else {
  226. setTimeout(() => {
  227. this.init()
  228. }, 100)
  229. }
  230. },
  231. visibleChange(bool) {
  232. if (!bool) {
  233. this.$refs.virtualList.reset()
  234. this.init()
  235. }
  236. },
  237. clearChange() {
  238. if (typeof this.defaultValue === 'string') {
  239. this.defaultValue = ''
  240. } else if (this.isMultiple) {
  241. this.defaultValue = []
  242. }
  243. this.visibleChange(false)
  244. }
  245. }
  246. }
  247. </script>
  248. <style lang="scss" scoped>
  249. .virtual-select-custom-style ::v-deep .el-select-dropdown__item {
  250. // 设置最大宽度,超出省略号,鼠标悬浮显示
  251. // options 需写 :title="source[label]"
  252. width: 250px;
  253. display: inline-block;
  254. overflow: hidden;
  255. text-overflow: ellipsis;
  256. white-space: nowrap;
  257. }
  258. .virtualselect {
  259. // 设置最大高度
  260. &-list {
  261. max-height:245px;
  262. overflow-y:auto;
  263. }
  264. }
  265. ::-webkit-scrollbar {
  266. width: 6px;
  267. height: 6px;
  268. background-color: transparent;
  269. cursor: pointer;
  270. margin-right: 5px;
  271. }
  272. ::-webkit-scrollbar-thumb {
  273. background-color: rgba(144,147,153,.3) !important;
  274. border-radius: 3px !important;
  275. }
  276. ::-webkit-scrollbar-thumb:hover{
  277. background-color: rgba(144,147,153,.5) !important;
  278. }
  279. ::-webkit-scrollbar-track {
  280. background-color: transparent !important;
  281. border-radius: 3px !important;
  282. -webkit-box-shadow: none !important;
  283. }
  284. ::v-deep .el-select__tags {
  285. flex-wrap: unset;
  286. overflow: auto;
  287. }
  288. </style>

 二、el-option-node.vue

  1. <template>
  2. <el-option
  3. :key="label+value"
  4. :label="concatString(source[label], source[value])"
  5. :value="source[value]"
  6. :disabled="source.disabled"
  7. :title="concatString(source[label], source[value])"
  8. >
  9. <span>{{ concatString(source[label], source[value]) }}</span>
  10. <span
  11. v-if="isRight"
  12. style="float:right;color:#939393"
  13. >{{ source[value] }}</span>
  14. </el-option>
  15. </template>
  16. <script>
  17. export default {
  18. name: 'ItemComponent',
  19. props: {
  20. // 每一行的索引
  21. index: {
  22. type: Number,
  23. default: 0
  24. },
  25. // 每一行的内容
  26. source: {
  27. type: Object,
  28. default() {
  29. return {}
  30. }
  31. },
  32. // 需要显示的名称
  33. label: {
  34. type: String,
  35. default: ''
  36. },
  37. // 绑定的值
  38. value: {
  39. type: String,
  40. default: ''
  41. },
  42. // 是否拼接label | value
  43. isConcat: {
  44. type: Boolean,
  45. default: false
  46. },
  47. // 拼接label、value符号
  48. concatSymbol: {
  49. type: String,
  50. default: ' | '
  51. },
  52. // 右侧是否显示绑定的值
  53. isRight: {
  54. type: Boolean,
  55. default() {
  56. return false
  57. }
  58. }
  59. },
  60. methods: {
  61. concatString(a, b) {
  62. a = a || ''
  63. b = b || ''
  64. if (this.isConcat) {
  65. // return a + ((a && b) ? ' | ' : '') + b
  66. return a + ((a && b) ? this.concatSymbol : '') + b
  67. }
  68. return a
  69. }
  70. }
  71. }
  72. </script>

 三、utils index.js

  1. /**
  2. * 判断是否为空
  3. */
  4. export function validatenull(val) {
  5. if (typeof val === 'boolean') {
  6. return false;
  7. }
  8. if (typeof val === 'number') {
  9. return false;
  10. }
  11. if (val instanceof Array) {
  12. if (val.length===0) return true;
  13. } else if (val instanceof Object) {
  14. if (JSON.stringify(val) === '{}') return true;
  15. } else {
  16. if (val==='null' || val===null || val==='undefined' || val===undefined || val==='') return true;
  17. return false;
  18. }
  19. return false;
  20. }

 四、组件使用

  1. <template>
  2. <div>
  3. <virtual-select
  4. v-model="selectValue"
  5. :list="selectValueOptions"
  6. label="name"
  7. value="code"
  8. :placeholder-params="'请选择(单选)'"
  9. :keeps-params="10"
  10. :is-concat="true"
  11. :concat-symbol="' || '"
  12. :is-multiple="false"
  13. @change="changeVal"
  14. />
  15. <virtual-select
  16. v-model="selectValue2"
  17. :list="selectValueOptions"
  18. label="name"
  19. value="code"
  20. placeholder-params="请选择(多选)"
  21. :keeps-params="10"
  22. :is-concat="false"
  23. :is-multiple="true"
  24. :is-right="true"
  25. :allow-create="true"
  26. @change="changeVal"
  27. />
  28. </div>
  29. </template>
  30. <script>
  31. // 引入组件
  32. import VirtualSelect from '@/components/common/VirtualSelect/Select'
  33. export default {
  34. name: 'VirtualSelectDemo',
  35. // 注册组件
  36. components: { VirtualSelect },
  37. data () {
  38. return {
  39. selectValue: null,
  40. selectValue2: null,
  41. selectValueOptions: []
  42. }
  43. },
  44. created() {
  45. this.getList(10000, 1000).then(res => {
  46. this.selectValueOptions = res
  47. })
  48. },
  49. methods: {
  50. changeVal(val) {
  51. console.log(val, 'val')
  52. },
  53. getList(num = 10000, time) {
  54. return new Promise((resolve, reject) => {
  55. setTimeout(() => {
  56. const tempArr = []
  57. let count = num
  58. while (count--) {
  59. const index = num - count
  60. tempArr.push({
  61. code: `${index}$${Math.random().toString(16).substring(9)}`,
  62. index,
  63. name: `测试数据-${index}`,
  64. value: index
  65. })
  66. }
  67. resolve(tempArr)
  68. }, time)
  69. })
  70. },
  71. }
  72. }
  73. </script>
  74. <style scoped>
  75. </style>

总结:

  • list的值是自己的选择框下拉值集数据。
  • label 和value和v-model和原来的el-select属性一样。基本能满足一般需求。
  • keepsParams 显示滚动加载的条数
  • isMultiple 是否多选

  • bindValue 绑定的默认值 (defaultValue)

  • isRight 是否右边显示value(code)值

  • isConcat 是否拼接 label 和 value

  • concatSymbol 拼接 label 和 value 的符号

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/107272
推荐阅读