Vue实现typeahead组件功能(非常靠谱)
程序员文章站
2022-09-08 22:31:23
前言
之前那个typeahead写的太早,不满足当前的业务需求。
而且有些瑕疵,还有也不方便传入数据和响应数据..
于是就推倒了重来,写了个v2的版本...
前言
之前那个typeahead写的太早,不满足当前的业务需求。
而且有些瑕疵,还有也不方便传入数据和响应数据..
于是就推倒了重来,写了个v2的版本
看图,多了一些细节的考虑;精简了实现的逻辑代码
效果图
实现的功能
1: 鼠标点击下拉框之外的区域关闭下拉框
2: 支持键盘上下键选择,支持鼠标选择
3: 支持列表过滤搜索
4: 支持外部传入列表json格式的映射
5: 支持placeholder的传入
6: 选中对象的响应(.sync vue2.3的组件通讯的语法糖)
7: 箭头icon的映射,感觉作用不大,移除了
用法
<select-search style="max-width:195px" placeholder="请选择广告主" :asyncdata.sync="adhostdata" :mapdata="adhostdatalist" :mapdataformat="{label:'username',value:'userid'}"> </select-search>
- asyncdata:响应的数据,也就是选中的..回来是一个对象
- mapdata : 搜索的列表数据,肯定是外部传入了…
- mapdata : 列表值映射
代码
selectsearch.vue
<template> <div class="select-search" v-if="typeaheaddata" ref="selectsearch" @click.native="showhidemenu($event)"> <div class="select-header"> <input type="text" autocomplete="off" readonly :placeholder="placeholder" :value="placeholdervalue" @keydown.down.prevent="selectchildwidtharrowdown" @keydown.up.prevent="selectchildwidtharrowup" @keydown.enter="selectchildwidthenter"> <i class="fzicon " :class="isexpand?'fz-ad-jiantou1':'fz-ad-jiantou'"></i> </div> <div class="select-body" v-if="isexpand && typeaheaddata"> <input type="text" placeholder="关键字" v-model="searchval" autocomplete="off" @keydown.esc="resetdefaultstatus" @keydown.down.prevent="selectchildwidtharrowdown" @keydown.up.prevent="selectchildwidtharrowup" @keydown.enter="selectchildwidthenter"> <transition name="el-fade-in-linear" mode="out-in"> <div class="typeahead-filter"> <transition-group tag="ul" name="el-fade-in-linear" v-show="typeaheaddata.length>0"> <li v-for="(item,index) in typeaheaddata" :key="index" :class="item.active ? 'active':''" @mouseenter="setactiveclass(index)" @mouseleave="setactiveclass(index)" @click="selectchild(index)"> <a href="javascript:;" rel="external nofollow" > {{item[mapdataformat.label]}} </a> </li> </transition-group> <p class="nofound" v-show="typeaheaddata && typeaheaddata.length === 0">未能查询到,请重新输入!</p> </div> </transition> </div> </div> </template> <script> export default { name: 'selectsearch', data: function () { return { placeholdervalue: '',// 给看到选择内容的 isexpand: false, searchval: '', // 搜索关键字 resultval: '', // 保存搜索到的值 searchlist: [], //保存过滤的结果集 currentindex: -1, // 当前默认选中的index, } }, computed: { mapformatdata () { // 外部有传入格式的时候映射mapdata return this.mapdata.map(item => { item[this.mapdataformat.value] = item[this.mapdataformat.value]; return item; }); }, typeaheaddata () { let temp = []; if (this.searchval && this.searchval === '') { return this.mapformatdata; } else { this.currentindex = -1; // 重置特殊情况下的索引 this.mapformatdata.map(item => { if (item[this.mapdataformat.label].indexof(this.searchval.tolowercase().trim()) !== -1) { temp.push(item) } return item; }) return temp; } } }, props: { placeholder: { type: string, default: '--请选择--' }, emptytext: { type: string, default: '暂无数据' }, mapdata: { // 外部传入的列表数据 type: array, default: function () { return [] } }, mapdataformat: { // 映射传入数据的格式 type: object, default: function () { return { label: 'text', value: 'value', extratext: 'extratext' } } }, asyncdata: { // 实时响应的值 type: [object, string], default: function () { return {} } } }, methods: { showhidemenu (e) { // 点击其他区域关闭下拉列表 if (e) { if (this.$refs.selectsearch && this.$refs.selectsearch.contains(e.target)) { this.isexpand = true; } else { this.isexpand = false; } } }, resetdefaultstatus () { // 清除所有选中状态 this.searchval = ''; this.currentindex = -1; this.typeaheaddata.map(item => { this.$delete(item, 'active'); }) }, setactiveclass (index) { // 设置样式活动类 this.typeaheaddata.map((item, innerindex) => { if (index === innerindex) { this.$set(item, 'active', true); this.currentindex = index; // 这句话是用来修正index,就是键盘上下键的索引,不然会跳位 } else { this.$set(item, 'active', false) } }) }, selectchildwidtharrowdown () { // 判断index选中子项 if (this.currentindex < this.typeaheaddata.length) { this.currentindex++; this.typeaheaddata.map((item, index) => { this.currentindex === index ? this.$set(item, 'active', true) : this.$set(item, 'active', false); }) } }, selectchildwidtharrowup () { // 判断index选中子项 if (this.currentindex > 0) { this.currentindex--; this.typeaheaddata.map((item, index) => { this.currentindex === index ? this.$set(item, 'active', true) : this.$set(item, 'active', false); }) } }, selectchildwidthenter () { // 若是结果集只有一个,则默认选中 if (this.typeaheaddata.length === 1) { this.$emit('update:asyncdata', this.typeaheaddata[0]); // emit响应的值 this.placeholdervalue = this.typeaheaddata[0][this.mapdataformat.label]; } else { // 若是搜索的内容完全匹配到项内的内容,则默认选中 this.typeaheaddata.map(item => { if (this.searchval === item[this.mapdataformat.label] || item.active === true) { this.$emit('update:asyncdata', item); // emit响应的值 this.placeholdervalue = item[this.mapdataformat.label]; } }) } this.isexpand = false; }, selectchild (index) { // 鼠标点击选择子项 this.typeaheaddata.map((item, innerindex) => { if (index === innerindex || item.active) { this.placeholdervalue = item[this.mapdataformat.label]; this.$emit('update:asyncdata', item); // emit响应的值 } }); this.isexpand = false; }, }, mounted () { window.addeventlistener('click', this.showhidemenu); }, beforedestroy () { window.removeeventlistener('click', this.showhidemenu); }, watch: { 'isexpand' (newvalue) { if (newvalue === false) { this.resetdefaultstatus(); } } } } </script> <style scoped lang="scss"> .el-fade-in-linear-enter-active, .el-fade-in-linear-leave-active, .fade-in-linear-enter-active, .fade-in-linear-leave-active { transition: opacity .2s linear; } .el-fade-in-enter, .el-fade-in-leave-active, .el-fade-in-linear-enter, .el-fade-in-linear-leave, .el-fade-in-linear-leave-active, .fade-in-linear-enter, .fade-in-linear-leave, .fade-in-linear-leave-active { opacity: 0; } .nofound { text-align: center; } .select-search { position: relative; z-index: 1000; a { color: #333; text-decoration: none; padding: 5px; } ul { list-style: none; padding: 6px 0; margin: 0; max-height: 200px; overflow-x: hidden; overflow-y: auto; li { display: block; width: 100%; padding: 5px; font-size: 14px; padding: 8px 10px; position: relative; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #48576a; height: 36px; line-height: 1.5; box-sizing: border-box; cursor: pointer; &.active { background-color: #20a0ff; a { color: #fff; } } } } .select-header { position: relative; border-radius: 4px; border: 1px solid #bfcbd9; outline: 0; padding: 0 8px; >input { border: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; width: 100%; outline: 0; box-sizing: border-box; color: #1f2d3d; font-size: inherit; height: 36px; line-height: 1; } >i { transition: all .3s linear; display: inline-block; position: absolute; right: 3%; top: 50%; transform: translatey(-50%); } } .select-body { position: absolute; border-radius: 2px; background-color: #fff; box-sizing: border-box; margin: 5px 0; padding: 8px; width: 100%; box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04); >input { -webkit-appearance: none; -moz-appearance: none; appearance: none; background-color: #fff; background-image: none; border-radius: 4px; border: 1px solid #bfcbd9; box-sizing: border-box; color: #1f2d3d; font-size: inherit; height: 36px; line-height: 1; outline: 0; padding: 3px 10px; transition: border-color .2s cubic-bezier(.645, .045, .355, 1); width: 100%; display: inline-block; &:focus { outline: 0; border-color: #20a0ff; } } } } </style>
总结
以上所述是小编给大家介绍的vue实现typeahead组件功能(非常靠谱),希望对大家有所帮助