1、效果图,向上展示和向下展示
这是我将该组件作为子组件应用到父组件中使用的效果。同时子组件可以多次使用,子组件返回相应的数据
1、主要是通过控制 选项item 的 显示与隐藏 来实现搜索结果的。数据传递通过vue的父传子和子传父
2、子组件接收数组的时候,如果是从后端加载的数据,会有延迟,使用this.$nextTick()重新渲染子组件
3、数组的Item是对象,使用Item[name]方式显示选项,name是对象的属性名称
4、下拉列表的向上显示还是向下显示
我的主要代码结构,主要
1是子组件代码:
html(SelectCustom.vue)
<!--选择框 自定义--> <style scoped> .select_wrap__custom{ display: inline-block; } .select_wrap__content__body{ position: relative; } .select_wrap__body{ display: flex; justify-content: flex-start; } .select_input{ height: 32px; width: 200px; padding: 0; outline: none; border: 1px solid #dcdfe6; border-radius: 4px; cursor: pointer; } input.select_input:focus{ border: 1px solid #5C356E; border-radius: 4px; } .select_icon{ position: relative; } .select_icon_triangle{ width: 0px; /*设置宽高为0,所以div的内容为空,从才能形成三角形尖角*/ height: 0px; border-top: 8px solid #5C356E; border-left: 6px solid transparent; /*transparent 表示透明*/ border-right: 6px solid transparent; position: absolute; top: 15px; right: 5px; } .select_icon_triangle_down{ width: 0px; /*设置宽高为0,所以div的内容为空,从才能形成三角形尖角*/ height: 0px; border-bottom: 8px solid #5C356E; border-left: 6px solid transparent; /*transparent 表示透明*/ border-right: 6px solid transparent; position: absolute; top: 15px; right: 5px; } .select_icon_rotate{ transform: rotate(-180deg); transition: all 0.3s ease-out; } /* 下拉列表 */ .select_wrap_content{ box-shadow: 0 0 2px #5C356E; background-color: #FFFFFF; position: absolute; } .bottom{ top: 100%; } .top{ bottom: 125%; } ul{ margin: 2px 0 0 0 ; padding: 0; min-width: 200px; } ul li{ list-style: none; line-height: 40px; padding: 2px 1rem; } ul.something_list li._data-point{ cursor: pointer; } ul.something_list li._data-point:hover{ background-color: rgba(92,53,110,0.6); } ._li-none{ display: none; } </style> <template> <div class="select_wrap__custom" ref="sel" v-clickoutside="handleClose"> <div class="select_wrap__content__body"> <div class="select_wrap__body" @click="showSelectList"> <input class="select_input" v-model="inputValue" :readonly="!filterable" @keyup="keyUpSomething"/> <span class="select_icon"> <!--:class="[showListStatus==false?'select_icon_triangle':'select_icon_triangle_down']"--> <span class="select_icon_triangle" :class="{'select_icon_rotate':showListStatus}"></span> </span> </div> <!--下拉列表--> <div class="select_wrap_content" :class="{'bottom' : position == 'bottom', 'top' : position == 'top'}" v-if="showListStatus"> <ul v-show="selectList.length > 0" class="something_list"> <li v-for="(item,index) in selectList" :key="index" v-show="noLikeValue == false" @click.stop="selectThisValue(index,item)" class="_data-point" :data-value="(label)?item[label]:item"> {{(label)?item[label]:item}} </li> <li v-show="noLikeValue == true" class="_no_data_point">无匹配数据</li> </ul> <ul v-show="selectList.length == 0" class="nothing_list"> <li>没有数据</li> </ul> </div> </div> </div> </template> <script> export default { name: 'SelectCustom', props: { arr: { type: Array, default: () => [] }, label: { // 展示的属性arr[label],默认展示arr[0] type: String, default: null }, value: { // 返回的属性arr[value],默认返回arr[0] type: String, default: null }, clearable: { // 可清除,未实现 type: Boolean, default: false }, filterable: { // 可搜索 type: Boolean, default: false } }, data () { return { DataList: [], inputValue: '', noLikeValue: false, showListStatus: false, isKeyUpInput: false, position: 'bottom' // 列表向上还是向下 } }, computed: { selectList () { // this.DataList = (this.arr || []) return this.arr || [] } }, mounted () {}, watch: { showListStatus (newValue, oldValue) { if (!newValue) { // 去掉所有的隐藏样式 this.noLikeValue = false const dataPoint = document.querySelectorAll('.something_list ._data-point') dataPoint.forEach((element, index) => { // console.log(element) if (element.classList.contains('_li-none')) { element.classList = ' _data-point' } }) } } }, methods: { // 展开列表 showSelectList () { this.showListStatus = false this.$nextTick(() => { this.showListStatus = true }) this.composeHeight() }, getElementTop (element) { var actualTop = element.offsetTop var current = element.offsetParent // 父元素的高度 while (current !== null) { actualTop += current.offsetTop current = current.offsetParent } return actualTop }, composeHeight () { const elHeight = this.$refs.sel.offsetHeight // 整个选择框占用的高度 const absPos = this.getElementTop(this.$refs.sel) // 获取定位的父级元素的总高度 let contentHeight = this.selectList.length * 40 // 列表内容的高度 if (this.selectList.length === 0) { contentHeight = 40 } const docScrollHei = document.body.scrollTop || document.documentElement.scrollTop || 0 // 滚动条距离顶部的高度 const docHeight = document.documentElement.clientHeight || document.body.clientHeight || 0 // 窗口总高度 if ((elHeight + absPos + contentHeight - docScrollHei) > docHeight) { this.position = 'top' } else { this.position = 'bottom' } }, // 点击除本身之外的地方,隐藏 handleClose (e) { this.showListStatus = false if (this.isKeyUpInput) { // 手动输入内容清空 this.inputValue = '' } }, // 选择一个选项 selectThisValue (index, item) { this.showListStatus = false this.isKeyUpInput = false this.inputValue = this.label ? item[this.label] : item this.$emit('selectChange', this.value ? item[this.value] : item) }, // 正在输入 keyUpSomething () { if (this.filterable) { this.isKeyUpInput = true this.getMatchValueBySearch(this.inputValue) } }, /** * 搜索匹配的数据 * @param val 输入的值 */ getMatchValueBySearch (val) { var dataPoint = document.querySelectorAll(' ._data-point') let hideCount = 0// 隐藏的数据个数 if ((typeof val === 'string' && val.length === 0) || val === null) { dataPoint.forEach((element, index) => { if (element.classList.contains('_li-none')) { element.classList = ' _data-point' } }) hideCount = 0 } else { dataPoint.forEach((element, index) => { if (element.getAttribute('data-value').indexOf(val) === -1) { hideCount++ console.log('隐藏', index) if (element.classList.contains('_li-none')) { } else { element.classList += ' _li-none' } } else { if (element.classList.contains('_li-none')) { element.classList = ' _data-point' } } }) } hideCount === this.selectList.length ? this.noLikeValue = true : this.noLikeValue = false // console.log('没有匹配数据的状态', this.noLikeValue, hideCount, this.selectList.length) } } } </script>js(index.js),实现自定义指令,点击除本身之外的地方隐藏下拉框
import Select from './SelectCustom.vue' const clickoutside = { /** * 初始化指令 * @param el 元素target * @param binding 事件 * @param vnode 元素dom */ bind(el, binding, vnode) { function documentHandler(e) { // 这里判断点击的元素是否是本身,是本身,则返回 if (el.contains(e.target)) { return false } if (binding.expression) { // 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法 binding.value(e) } } // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听 el.__vueClickOutside__ = documentHandler document.addEventListener('click', documentHandler) }, update() {}, unbind(el, binding) { // 解除事件监听 document.removeEventListener('click', el.__vueClickOutside__) delete el.__vueClickOutside__ } } // 全局注册 const SelectCustom = { install: function(Vue) { Vue.directive('clickoutside', clickoutside) // 自定义指令 Vue.component('SelectCustom', Select) } } export default SelectCustom2是父组件
html(HelloWorld.vue)
<template> <p>选择框</p> <div> <hr style="width:80%;height:10px;background-color:red;"> <select-custom :arr="arr" @selectChange="val=>{this.arrValue = val}"></select-custom> <select-custom :arr="arr2" :label="'label'" :value="'key'" :filterable="true" @selectChange="val=>{this.arr2Value = val}"></select-custom> <p> 选项值1:{{arrValue}} </p> <p>选项值2:{{arr2Value}}</p> </div> </template> <script> export default { name: 'HelloWorld', data () { return { arr:[ "1","你好","不是" ], arrValue:'', arr2:[ {key:'huhu',label:'张三'}, {key:'wuwi',label:'王五'}, {key:'luji',label:'卤鸡'}, ], arr2Value:'' } }, } </script>
3是main文件,全局注册该组件就不用了在父组件中单独引入了
main.js的关键代码
import SelectCustom from './components/select' Vue.use(SelectCustom)