(21年总计的一篇文章,可能最新版element ui库组件的很多语法已经改变。放上来,仅提供一些思路的思考和分享交流。)
elment-ui库中存在不少好资源,其官方文档并未介绍。我是在参考学习它的组件input的autocomplete源代码时发现的。以下为个人实现的一个需求:即时搜索。结合这些发现的资源,记录这个功能实现的思路。
技术栈:vue 2、element ui
1 需求
视觉同事提出了一个需求,在element-ui autocomplete组件即时搜索的基础上,增加了历史搜索关键词展示的功能:
- 当输入框还未输入文字时,展示最近5个历史搜索词;输入文字后则进行远程搜索匹配
- 历史搜索词存于浏览器,不作后端存储
2 实现过程
2.1 实现思路
- div[contentEditable=true]代替传统的input
<template>
<div class="autocomplete-input">
<!-- 模拟input -->
<span
v-text="innerText"
contenteditable="plaintext-only"
:placeholder="placeholder"
@input="changeText"
@focus="handleFocus"
@blur="handleBlur"
@keydown.enter.prevent="handleSearchClick"
ref="autocomplete"></span>
</div>
<!-- 搜索按钮 -->
<div class="icon-search" @click.stop="handleSearchClick"></div>
</template>
<script lang="ts">
@Component
export default class AutoComplete extends Vue {
private keyword = '' // 搜索关键词
private innerText = ''
private isLocked = false // 用于keyword和innerText的值
private isFocus = false // 搜索框是否focus
private suggestionDisabled = true // 是否可以请求获取建议
public handleFocus () {
this.isFocus = true
this.isLocked = true
this.suggestionDisabled = false
}
public handleBlur () {
this.isLocked = false
}
public changeText (e: any) {
this.keyword = e.target.innerText
this.suggestionDisabled = false
}
public handleWordDel () {
this.keyword = ''
// this.autocompleteRef.innerText = '' // 暂时解决输入框中的innerText不消失
this.handleSearchClick()
}
public handleClickOutside () {
this.suggestionDisabled = true
this.isFocus = false
}
// contenteditable的div不能添加change事件
@Watch('keyword')
onKeywordChange (n: string, o: string) {
if (!this.isLocked && !this.innerText) {
this.innerText = n
}
if (!this.isLocked && n !== o) {
this.innerText = n
}
if (this.isFocus) {
if (n) {
this.debouncedGetData() // 防抖 获取远程匹配建议
} else {
this.getHistoryWordList() // 获取 客户端存储的历史搜索词
}
}
}
}
<script>
- 搜索匹配建议相关:ElScrollbar作为滚动容器,存储建议list;实时搜索查询时使用throttle-debounce中的debounce防抖
首先,我采用了element-ui的Scrollbar组件来作为远程搜索建议的滚动条容器:
<template>
<el-scrollbar
wrap-class="autocomplete-suggestion__wrap"
v-if="hintList.length"
v-show="hintListShow">
<ul class="suggestions-list">
<li
class="ellipsis"
v-for="(item, index) in hintList"
:key="index"
@click.stop="handleHintClick(item.value)"
role="option">{{item.value}}</li>
</ul>
</el-scrollbar>
</template>
<script lang="ts">
@Component
export default class AutoComplete extends Vue {
// 下拉建议是否展示(为true时场景:1.获取建议时;2.keyword&&isFocus)
private get hintListShow () {
return !!this.hintList.length && this.isFocus && !!this.keyword
}
private hintList: Hint[] = []
// 点击“建议”item进行搜索
public handleHintClick (value: string) {}
}
<script>
其次,我使用了throttle-debounce/debounce插件实现的debounce防抖技术:
<script lang="ts">
import debounce from 'throttle-debounce/debounce'
@Component
export default class AutoComplete extends Vue {
@Prop({ type: Number, default: 500 }) debounce?: number
private created () {
this.debouncedGetData = debounce(this.debounce, false, this.getSeachHint)
}
// ajax远程获取匹配建议
public getSeachHint () {}
// 监听到搜索的关键词改变,则进行搜索匹配建议
@Watch('keyword')
onKeywordChange (n: string) {
if (n) {
this.debouncedGetData()
}
}
}
<script>
-
历史关键词查询、存储、删除等,前端缓存使用IndexedDB
-
移动安卓端搜索时,软键盘被顶起的问题,可以分3步解决:
首先,判断软键盘是否弹起:通过页面可视区域的高度改变来判断;
<script lang="ts">
export default class App extends Vue {
private mounted () {
this.$nextTick(() => {
this.bodyClientHeight = body.clientHeight; // 键盘未弹出前页面可视区域的高度
})
}
// 是否软键盘弹出
public isKeyboardEjected (): boolean {
const body = document.querySelector('body') as HTMLElement
return body.clientHeight - this.bodyClientHeight < 0 // 软键盘弹出时,当前的页面可视区域高度变小
}
}
</script>
其次,通知页面软键盘弹起:通过vue的EventBus通知;
<script lang="ts">
import Bus from '@/utils/bus.js';
export default class App extends Vue {
private mounted () {
window.addEventListener('resize', () => {
this.emitKeyboardStatus()
})
}
// 安卓端解决input键盘弹出导致页面压缩变形
public emitKeyboardStatus () {
if (isAndroid) {
const isKeyboardEjected = this.isKeyboardEjected()
Bus.$emit('keyboardPop', isKeyboardEjected)
}
}
}
</script>
最后,对应修改页面布局代码:
- 软键盘弹起后,若匹配建议的容器高度由于太大、位置错乱,可以设置较小的高度;软键盘收起后,再恢复原高度
- 当前页面离开前,记得让输入框失去焦点,避免因软键盘弹起造成的问题遗留
<script lang="ts">
import Bus from '@/utils/bus.js'
@Component
export default class AutoComplete extends Vue {
private mounted () {
Bus.$on('keyboardPop', (val: boolean) => {
this.isKeyboardEjected = val
})
}
}
<script>
- 点击搜索组件外的任意空白处,展示 建议或者历史搜索词 的区域收起:这是一个细节问题,element-ui中的clickoutside就是用于解决此问题的。
<template>
<div class="autocomplete-box" v-clickoutside="handleClickOutside">
</div>
</template>
<script lang="ts">
@Component
export default class AutoComplete extends Vue {
public handleClickOutside () {
this.suggestionDisabled = true
this.isFocus = false // input失去焦点,软键盘收起
}
}
<script>
3 总结记录
3.1 Scrollbar
这是一个隐藏组件,官网文档并没有提供相关API。按需加载需要单独加载Scrollbar组件并使用,同时我们需要修改css样式,如height值、分析具体情况是否要隐藏x轴滚动条等。它允许传的props如下:
props: {
native: Boolean,
wrapStyle: {},
wrapClass: {},
viewClass: {},
viewStyle: {},
noresize: Boolean, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
tag: {
type: String,
default: 'div'
}
}
3.2 throttle-debounce
throttle-debounce提供了throttle(节流)和debounce(防抖) 两个函数,通过它们可以限制函数的执行效率,避免短时间内函数多次执行造成性能问题。
3.3 Vue的事件总线Eventbus
EventBus事件总线可以用来通信,vue中所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,组件平行通知其他组件。但使用太频繁会造成难以维护的“灾难”。
解决软键盘弹出问题中的bus.js代码如下:
import Vue from 'vue'
export default new Vue()
3.4 clickoutside指令
clickoutside是element-ui实现的一个自定义指令,用来处理目标节点之外的点击事件,常用来处理下拉菜单等展开内容的关闭,在element-ui的Select选择器、Dropdrow下拉菜单、Popover弹出框等组件中都用到了。源码学习可Element源码学习--指令 v-clickoutside。
在我们项目中,我们需要注册指令才能使用:
import Vue from 'vue'
import Clickoutside from 'element-ui/src/utils/clickoutside'
Vue.directive('clickoutside', Clickoutside) //全局注册指令 v-clickoutside