目录
- el-autocomplete使用
- template
- 实现需求分析
- 1. 输入框为空时聚焦或失焦后又重新聚焦不会触发请求数据接口
- 2. 缓存上一次已查询的数据&搜索条件:blurArr、blurTxt
- 3.滚动加载指令(监听容器的scroll事件并进行防抖处理)
- 4. 分页加载
- 4.0 获取数据,并进行格式化
- 4.1 关闭加载圈
- 4.2 分页加载事件
- 4.3 清空输入框,重置上次记录的数据
- 4.4 选中时记录相关数据
- 数据展示不稳定问题
- 完整的 scss 文件
- 完整的 js 文件
- 总结
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 >) { | |
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 + - listDom.offsetHeight | |
if (condition > && !vnode.context.loading) { | |
binding.value() | |
} | |
},), | |
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[] && element.roleId === 101) { | |
element.value = element.nickName + '-' + element.classList[].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 = | |
this.total = | |
... | |
//下拉选中的值 | |
// 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,); //把这条记录从数组中移除 | |
} | |
} | |
} | |
var errorFlag = false; | |
var erFlag = false; | |
// 创建axios实例 | |
const service = axios.create({// axios中请求配置有baseURL选项,表示请求URL公共部分 | |
baseURL: process.env.VUE_APP_BASE_API,// 超时 | |
timeout: | |
}) | |
// request拦截器 | |
service.interceptors.request.use(config => { | |
// 如果你是在老项目中开发就加一个限制,避免影响到原有的功能 | |
// if(config.url.indexOf('system/user/newsearch_list')!==-){ | |
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 ===) { | |
... | |
} else if (code !==) { | |
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: * 1000 | |
}) | |
return Promise.reject(error) | |
} | |
} | |
) |
完整的 scss 文件
.el-autocomplete-component {max-width:px; | |
vertical-align: text-bottom;height:px; | |
padding-top:px;cursor: pointer; | |
/deep/ .el-input__inner { cursor: pointer; | |
padding-left:px; | |
padding-right:px; | |
background: transparent; | |
border: none; | |
color: #fff; | |
font-size:px; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
overflow: hidden; | |
&::placeholder { | |
color: #bfbfbf; | |
font-size:px; | |
} | |
} | |
}.diy-autocomplete { | |
.name { max-width:px; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
height:px; | |
} | |
} |
完整的 js 文件
<script> | |
import { searchUserList } from '@/api/system/user' // 请求用户列表的接口 | |
import { debounce } from '@/utils/index' // 防抖函数 | |
import { Loading } from 'element-ui' // 下拉加载时的过渡loading | |
let loadingInstance = nullexport default { | |
data() { return { | |
showAutocomplete: false, | |
searchStr: '', //输入关键词的值 | |
pageNum:, | |
pageSize:, | |
total:, //筛选数据的总值 | |
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('-') !== - ? (searchVal = queryString.split('-')[0]) : '' | |
this.pageNum = | |
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[] && element.roleId === 101) { | |
element.value = element.nickName + '-' + element.classList[].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 = | |
this.total = | |
let routeData = {} | |
if (item.roleId ===) { | |
// 学生 | |
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 + - listDom.offsetHeight | |
if (condition > && !vnode.context.loading) { | |
binding.value() | |
} | |
},), | |
false | |
) | |
} | |
} | |
} | |
} | |
</script> |