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. 缓存上一次已查询的数据&搜索条件:blurArrblurTxt

 // 失焦事件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, resultconst later = function() {// 据上一次触发时间间隔const last = +new Date() - timestamp// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 waitif (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 = thistimestamp = +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.offsetHeightif (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 = 1this.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.codeif (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 = 1this.blurTxt = searchVallet 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 = 1this.total = 0let 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.offsetHeightif (condition > 0 && !vnode.context.loading) {binding.value()}}, 300),false)}}}
}
</script> 
pDom.offsetHeight + wrapDom.scrollTop + 50 - listDom.offsetHeightif (condition > 0 && !vnode.context.loading) {binding.value()}}, 300),false)}}}
}
</script> 

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值