vue中el-autocomplete支持分页上拉加载功能
el-autocomplete使用
效果图
template
<template> <el-autocomplete :clearable="true" //支持清空 :title="searchStr" // 鼠标移上去提示文案 :trigger-on-focus="true" // 聚焦时是否触发下拉列表展示 :fetch-suggestions="querySearchAsync" // 筛选符合条件的数据 :placeholder="placeholder" // 占位符提示信息 v-scrollLoad="load" // 自定义上拉加载指令 v-model="searchStr" // 搜索关键字 popper-class="diy-autocomplete" // 下拉框自定义class控制样式 class="el-autocomplete-component" // 给当前组件定义专属类名 size="small" // 组件显示尺寸 ref="autocomplete" // 用于后期获取dom元素 @select="handleSelect" // 选中时触发事件 @blur="handleBlur" // 失去焦点时触发 @clear="handleClear" // 清空数据时触发 ></el-autocomplete> </template>
实现需求分析
1. 输入框为空时聚焦或失焦后又重新聚焦不会触发请求数据接口
// blurTxt: 记录上次失焦时 和 选中时的筛选字段 // blurArr: 记录上次失焦时 和 选中时已经查询到的数据 async querySearchAsync(queryString, cb) { if (this.blurTxt === queryString || !queryString) { cb(this.blurArr) return } },
2. 缓存上一次已查询的数据&搜索条件:blurArr、blurTxt
// 失焦事件 handleBlur() { this.blurTxt = this.searchStr || '' this.blurArr = this.$refs['autocomplete'].$data.suggestions }, // 过滤数据时及时更新筛选字段 async querySearchAsync(queryString, cb) { this.blurTxt = searchVal },
3.滚动加载指令(监听容器的scroll事件并进行防抖处理)
- 防抖函数
/** * @param {Function} func * @param {number} wait * @param {boolean} immediate * @return {*} */ export function debounce(func, wait, immediate) { let timeout, args, context, timestamp, result const later = function() { // 据上一次触发时间间隔 const last = +new Date() - timestamp // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait if (last < wait && last > 0) { timeout = setTimeout(later, wait - last) } else { timeout = null // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 if (!immediate) { result = func.apply(context, args) if (!timeout) context = args = null } } } return function(...args) { context = this timestamp = +new Date() const callNow = immediate && !timeout // 如果延时不存在,重新设定延时 if (!timeout) timeout = setTimeout(later, wait) if (callNow) { result = func.apply(context, args) context = args = null } return result } }
- 滚动加载指令
directives: { scrollLoad: { bind(el, binding, vnode) { let wrapDom = el.querySelector('.el-autocomplete-suggestion__wrap') let listDom = el.querySelector('.el-autocomplete-suggestion__wrap .el-autocomplete-suggestion__list') // 滚动事件做防抖处理 wrapDom.addEventListener( 'scroll', debounce(e => { let condition = wrapDom.offsetHeight + wrapDom.scrollTop + 50 - listDom.offsetHeight if (condition > 0 && !vnode.context.loading) { binding.value() } }, 300), false ) } } }
4. 分页加载
- 请求前展示加载圈
- 加载至最后一页时不再进行请求并提示暂无更多数据
- 关闭loading加载圈
- 把数据追加至已展示的数据列表中
4.0 获取数据,并进行格式化
第一种方式: 在组件上设置valueKey为你要展示的字段名称,默认值为value
<el-autocomplete valueKey="nickName"></el-autocomplete>
第二种方式:拿到数据后遍历数据为每一项增添value属性,值为自己组合想展示的方式
// 获取用户列表 async getList(queryString) { let result = await searchUserList({ pageNum: this.pageNum, pageSize: this.pageSize, searchValue: decodeURI(queryString) }) this.total = result.total // 调用 callback 返回建议列表的数据 result.rows && result.rows.forEach(element => { // 学生展示 姓名+班级 if (element.classList[0] && element.roleId === 101) { element.value = element.nickName + '-' + element.classList[0].className } else { // 非学生或者学生没有主班级展示 姓名+身份 element.value = element.nickName + '-' + (element.roleName || '暂无角色ID') } }) return result.rows },
第三种方式:在组件对应的插槽slot中自定义展示内容
<el-autocomplete > <!-- 输入框小图标插槽 --> <i class="el-icon-edit el-input__icon" slot="suffix"> </i> <!-- 搜索列表每一项展示 --> <template slot-scope="{ item }"> <div class="name">{{ item.nickName }} - {{item.className}}</div> </template> </el-autocomplete>
4.1 关闭加载圈
// 关闭加载圈 closeLoading() { loadingInstance && loadingInstance.close && loadingInstance.close() loadingInstance = null },
4.2 分页加载事件
// 滚动加载 async load() { this.closeLoading() // 加载到最后一页停止加载 if (this.pageNum * this.pageSize > this.total) { return } this.pageNum++ loadingInstance = Loading.service({ target: document.querySelector('.el-autocomplete-suggestion'), fullscreen: false, spinner: 'el-icon-loading', lock: true, text: '加载中...' }) let results = await this.getList(this.searchStr) this.closeLoading() this.pageNum * this.pageSize >= this.total ? results.push({ value: '暂无更多数据' }) : '' // 将数据添加到下拉列表 this.$refs['autocomplete'].$data.suggestions = this.$refs['autocomplete'].$data.suggestions.concat(results) },
4.3 清空输入框,重置上次记录的数据
// 清空搜索项 handleClear() { this.blurTxt = '' this.blurArr = [] this.$refs['autocomplete'].$data.suggestions = [] },
4.4 选中时记录相关数据
// 选中用户跳转至对应的页面 handleSelect(item) { this.$refs['autocomplete'].$data.suggestions = this.blurArr = [item] this.blurTxt = this.searchStr || '' this.pageNum = 1 this.total = 0 ... //下拉选中的值 // console.log(item) }
数据展示不稳定问题
例如姓名模糊搜索过程中,也许我们会先输入姓为第一个关键词,接着在输入第二个关键词名字,只输入姓的时候肯定要比姓名要查询的数据多,当在大量数据中查询时会面临着第二个请求(搜索条件:输入姓名的)先返回数据,然后第一个请求(搜索条件:输入姓的)才会返回数据的情况,而此时筛选列表中展示的肯定是最后请求出来的结果(搜索框中展示的是完整姓名:张三,而展示列表中却展示出了:张一、张二、张三...),此时的解决方案是相同接口取消上一次的接口。
- 请求拦截中限制重复请求某个接口
import axios from 'axios' let pending = []; //声明一个数组用于存储每个ajax请求的取消函数和ajax标识 let cancelToken = axios.CancelToken; let removePending = (ever) => { for (let p in pending) { if (pending[p].u === ever.url + '&' + ever.method) { //当当前请求在数组中存在时执行函数体 pending[p].f(); //执行取消操作 pending.splice(p, 1); //把这条记录从数组中移除 } } } var errorFlag = false; var erFlag = false; // 创建axios实例 const service = axios.create({ // axios中请求配置有baseURL选项,表示请求URL公共部分 baseURL: process.env.VUE_APP_BASE_API, // 超时 timeout: 90000 }) // request拦截器 service.interceptors.request.use( config => { // 如果你是在老项目中开发就加一个限制,避免影响到原有的功能 // if(config.url.indexOf('system/user/newsearch_list')!==-1){ config && removePending(config); //在一个ajax发送前执行一下取消操作 config.cancelToken = new cancelToken((c) => { // 这里的ajax标识我是用请求地址&请求方式拼接的字符串,当然你可以选择其他的一些方式 pending.push({ u: config.url + '&' + config.method, f: c }); }); // } return config }, error => { console.log(error) Promise.reject(error) } )
- 相应拦截中对取消请求这个操作单独处理,不展示错误消息提示弹窗
// 响应拦截器 service.interceptors.response.use(res => { const code = res.data.code if (code === 401) { ... } else if (code !== 200) { if(!errorFlag){ ... return Promise.reject(res.data || {}) } } else { return res.data } }, error => { // 单独处理取消请求导致的错误 if(error.__CANCEL__){ return false } if(!erFlag){ Message({ message: error.message, type: 'error', duration: 3 * 1000 }) return Promise.reject(error) } } )
完整的 scss 文件
.el-autocomplete-component { max-width: 230px; vertical-align: text-bottom; height: 50px; padding-top: 1px; cursor: pointer; /deep/ .el-input__inner { cursor: pointer; padding-left: 5px; padding-right: 8px; background: transparent; border: none; color: #fff; font-size: 14px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; &::placeholder { color: #bfbfbf; font-size: 12px; } } } .diy-autocomplete { .name { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; height: 34px; } }
完整的 js 文件
<script> import { searchUserList } from '@/api/system/user' // 请求用户列表的接口 import { debounce } from '@/utils/index' // 防抖函数 import { Loading } from 'element-ui' // 下拉加载时的过渡loading let loadingInstance = null export default { data() { return { showAutocomplete: false, searchStr: '', //输入关键词的值 pageNum: 1, pageSize: 20, total: 0, //筛选数据的总值 placeholder: '请输入用户名/手机号/QQ', blurTxt: '', //记录失焦时搜索框中的文字,避免聚焦时重新筛选数据 blurArr: [] //记录失焦时已经搜索出来的列表 } }, methods: { // 失焦事件 handleBlur() { this.blurTxt = this.searchStr || '' this.blurArr = this.$refs['autocomplete'].$data.suggestions }, // 清空搜索项 handleClear() { this.blurTxt = '' this.blurArr = [] this.$refs['autocomplete'].$data.suggestions = [] }, // 关闭加载圈 closeLoading() { loadingInstance && loadingInstance.close && loadingInstance.close() loadingInstance = null }, // 条件查询 async querySearchAsync(queryString, cb) { this.$refs['autocomplete'].$data.suggestions = [] if (this.blurTxt === queryString || !queryString) { cb(this.blurArr) return } this.handleClear() let searchVal = queryString // 后面所拼接的班级名称和角色不参与筛选字段中 queryString.indexOf('-') !== -1 ? (searchVal = queryString.split('-')[0]) : '' this.pageNum = 1 this.blurTxt = searchVal let results = await this.getList(searchVal) cb(results || []) }, // 获取用户列表 async getList(queryString) { let result = await searchUserList({ pageNum: this.pageNum, pageSize: this.pageSize, searchValue: decodeURI(queryString) }) this.total = result.total // 调用 callback 返回建议列表的数据 result.rows && result.rows.forEach(element => { // 学生展示 姓名+班级 if (element.classList[0] && element.roleId === 101) { element.value = element.nickName + '-' + element.classList[0].className } else { // 非学生或者学生没有主班级展示 姓名+身份 element.value = element.nickName + '-' + (element.roleName || '暂无角色ID') } }) return result.rows }, // 滚动加载 async load() { this.closeLoading() // 加载到最后一页停止加载 if (this.pageNum * this.pageSize > this.total) { return } this.pageNum++ loadingInstance = Loading.service({ target: document.querySelector('.el-autocomplete-suggestion'), fullscreen: false, spinner: 'el-icon-loading', lock: true, text: '加载中...' }) let results = await this.getList(this.searchStr) this.closeLoading() this.pageNum * this.pageSize >= this.total ? results.push({ value: '暂无更多数据' }) : '' // 将数据添加到下拉列表 this.$refs['autocomplete'].$data.suggestions = this.$refs['autocomplete'].$data.suggestions.concat(results) }, // 选中用户跳转至对应的页面 handleSelect(item) { this.$refs['autocomplete'].$data.suggestions = this.blurArr = [item] this.blurTxt = this.searchStr || '' this.pageNum = 1 this.total = 0 let routeData = {} if (item.roleId === 101) { // 学生 routeData = this.$router.resolve({ path: '/personInf/student', query: { userId: item.userId } }) } else { // 非学生 routeData = this.$router.resolve({ path: '/userManagement/user', query: { userInfo: item.nickName ,roleId: item.roleId||''} }) } window.open(routeData.href, '_blank') //下拉选中的值 // console.log(item) } }, directives: { scrollLoad: { bind(el, binding, vnode) { let wrapDom = el.querySelector('.el-autocomplete-suggestion__wrap') let listDom = el.querySelector('.el-autocomplete-suggestion__wrap .el-autocomplete-suggestion__list') // 滚动事件做防抖处理 wrapDom.addEventListener( 'scroll', debounce(e => { let condition = wrapDom.offsetHeight + wrapDom.scrollTop + 50 - listDom.offsetHeight if (condition > 0 && !vnode.context.loading) { binding.value() } }, 300), false ) } } } } </script>
总结
到此这篇关于vue中el-autocomplete支持分页上拉加载功能的文章就介绍到这了,更多相关el-autocomplete分页上拉加载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
vue-cli创建项目ERROR in Conflict: Multiple assets emit dif
最近vue/cli创建项目后出现了错误,下面这篇文章主要给大家介绍了关于vue-cli创建项目ERROR in Conflict: Multiple assets emit different content to the same filename index.html问题的解决办法,需要的朋友可以参考下2023-02-02vue实现codemirror代码编辑器中的SQL代码格式化功能
这篇文章主要介绍了vue实现codemirror代码编辑器中的SQL代码格式化功能,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下2019-08-08
最新评论