使用vue实现简单的select下拉选择
看看效果
下面看看代码如何实现:
1.代码结构:
index.js文件 注册全局组件
TySelect.vue文件
<template> <div class="ty-select" :style="{width:width}"> <div class="select-type" @click="openSelect"> <slot class="select-name" name="selectType"></slot> <label class="down-icon" :class={active:selectFlag}></label> </div> <div class="selectLists" :class="{active:selectFlag}" v-loading="loading"> <ul v-if="tyMultiple"> <li v-for="(v, i) in newOptions" :key="i" @click.stop="selectValue(v)"> <input class="checkbox" type="checkbox" :checked="v.selectFlag" @click.stop="selectValue(v)"> <p>{{v.name}}</p> </li> </ul> <ul v-else> <li v-for="(v, i) in newOptions" :key="i" @click.stop="selectValue(v)"> <input class="checkbox" type="radio" name="group" :checked="checkTag === null ? false : (checkTag.id === v.id ? true : false)" @click.stop="selectValue(v)"> <p>{{v.name}}</p> </li> </ul> <div class="enter"> <button @click="enter">确定</button> </div> </div> </div> </template> <script> export default { props: { options: { type: Array, default: () => [] }, width: { type: String, default: () => '300px' }, loading: { type: Boolean, default: () => false }, tyMultiple: { type: Boolean, default: () => false }, valueType: { // 为true是选中的值为当前数据的整条数据都传递给父组件,如果为false的话 传递当前选中数据的id type: Boolean, default: () => false } }, watch: { options (val, oldVal) { this.newOptions = this.tyMultiple ? val.map(v => ({...v, selectFlag: false})) : val } }, data () { return { selectFlag: false, checkTag: null, checkTags: [], newOptions: [] } }, mounted () {}, methods: { // 打开下拉列表 自组件传递事件 调用父组件内的方法拿到下拉列表的数据 openSelect () { this.selectFlag = !this.selectFlag this.$emit('visible-change', this.options.length === 0 ? this.selectFlag : false) // 添加事件监听 监听点击下拉框外的区域时 关闭下拉 document.addEventListener('click', this.handleSelectShow) }, // 当点击区域外的地方时 下拉列表隐藏 handleSelectShow (e) { if (this.$el.contains(e.target)) return false this.selectFlag = false this.checkTags = [] this.checkTag = null this.selectMultipe() document.removeEventListener('click', this.handleSelectShow) }, // 点击选中的值 selectValue (v) { // 多选 if (this.tyMultiple) { const newTags = this.checkTags.filter(item => item.id !== v.id) // 如果选中值在checkTags中,则去掉该值,等于是取消选择 if (newTags.length === this.checkTags.length) { this.checkTags.push(v) } else { this.checkTags = newTags } this.selectMultipe(v) return false } // 单选 this.checkTag = v }, // 点击确定按钮传递所选值给父组件 关闭下拉框 清除掉所选的数据 enter () { this.$emit('handleChange', this.tyMultiple ? this.checkTags : this.checkTag) this.checkTags = [] this.checkTag = null this.selectMultipe() this.selectFlag = false }, // 是否显示选中 多选的时候函数处理 selectMultipe (v) { if (this.checkTags.length === 0) { this.newOptions.forEach(item => { item.selectFlag = false}) return false } this.checkTags.forEach(item => { if (item.id === v.id) { v.selectFlag = true } else { v.selectFlag = false } }) } } } </script>样式代码:
<style lang="scss" scoped> .ty-select{ position: relative; font-size: 12px; .select-type{ display: flex; align-items: center; cursor: pointer; .select-name{ padding: 0 5px; cursor: pointer; } .down-icon{ margin-left: 10px; width: 15px; height: 15px; background: url('../assets/image/downIcon.png') no-repeat center/contain; cursor: pointer; transition: all ease 0.2s; &.active{ transform: rotate(-180deg); transition: all ease 0.2s; } } } .selectLists{ padding: 0px 20px; position: absolute; top: 30px; left: 0; z-index: 3; width: 335px; height: 0; overflow: hidden; box-shadow: 0px 0px 5px 0px #dcdfe6; transition: all ease 0.5s; background: #ffffff; &.active{ padding: 20px 20px 40px; height: auto; transition: all ease 0.5s; } >ul{ overflow: scroll; display: flex; justify-content: space-between; flex-wrap: wrap; max-height: 220px; li{ padding: 0 10px; display: flex; align-items: center; width: 48%; height: 32px; color: #333333; cursor: pointer; >p{ margin-left: 10px; } &:hover{ background: #f2f2f2; } } &::-webkit-scrollbar{ display: none; } } .enter{ position: absolute; bottom: 0; left: 0; right: 0; height: 36px; line-height: 36px; margin: auto; text-align: center; background: #ffffff; // box-shadow: 0px -2px 5px 0px #dcdfe6; >button{ outline: none; margin: 0 auto; width: 240px; color: #ffffff; background: #f55; border-radius: 5px; line-height: 28px; cursor: pointer; } } } } </style>
参数说明
1.父组件传递给子组件的参数
optiions 格式为数组默认为空的
width可动态设置ty-select的宽度 默认值为空; 默认值可设置为300px;
loading 加载数据是的状态
tyMultiple 是否可以多选 默认false
valueType 为true是选中的值为当前数据的整条数据都传递给父组件,如果为false的话 传递当前选中数据的id 默认为false
watch: {} 监听options数据
发生变化的时候判断是多选还是单选,单选不作处理,多选的话在原来数据的基础上添加selectFlag字段 (用来判断是否选中)
openSelect 方法触发时
this.$emit('visible-change', this.options.length === 0 ? this.selectFlag : false) 这里调用父组件方法是用来获取下拉列表的数据,这里的参数进行判断,下拉列表内有数据了就返回false不需要在进行获取,列表为空的话返回下拉列表的开关,开为true获取数据,关闭为false不获取数据。这里我没做滚动加载,如果有滚动加载的话就得另行处理了。
document.addEventListener('click', this.handleSelectShow) 添加事件监听 点击下拉列表以外的区域关闭下拉列表 这里我参考的这篇文章 搜了很多自己也试了半天才好了
https://blog.csdn.net/xingyu_qie/article/details/78831045(评论里的有说的不错的)
https://download.csdn.net/download/zhangchao19890805/9855750
下面是点击选中的时候的判断
首先是判断 this.tyMultiple 是否支持多选 如果是多选的传递出去的值就是数组格式的 否则就是单个数据
<input class="checkbox" type="checkbox" :checked="!tyMultiple ? v.id === (valueType ? (checkId ? checkId.id : '') : checkId) : v.selectFlag" @click.stop="selectValue(v)">
多选框的代码 checked是否选中这里判断有点套的多 首先判断是否多选,不是多选直接返回选中的id,如果是多选的那么返回选中的对象的id,一开始对象时空的不存在id 所以这里要判断选中对象为真的时候才返回checkId.id 否则返回‘’
// 点击选中的值 selectValue (v) { // 多选 if (this.tyMultiple) { const newTags = this.checkTags.filter(item => item.id !== v.id) // 如果选中值在checkTags中,则去掉该值,等于是取消选择 if (newTags.length === this.checkTags.length) { this.checkTags.push(v) } else { this.checkTags = newTags } this.selectMultipe(v) return false } // 单选 this.checkTag = v },
选中的值处理这单选的就直接赋值给对象传递出去, 多选就要操作数组,如果已经选择过了就从数组中摘除出去,否则添加进去。
<ty-select @handleChange="handleChange" @visible-change="getMusicTags($event, 1)" :options="emotions" width="100px" :loading="selectLoading" :tyMultiple="true"> <label slot="selectType">情绪</label> </ty-select>这是使用时的代码 handleChange当点击确定选中的值的时候触发
visible-change 这里是当打开下拉的时候触发的方法 获取远程的数据然后传递给子组件。获取数据的时候传递给子组件的loading为true 显示loading 获取完毕为false 显示数据
我们这个选中的值是在别的地方展示的 所以这里我直接用的label放了个插槽 标签没有使用input 如果要使用input的话 label这里的插槽切换成input使用 我们这里也是必须点击确定按钮触发选中后的值的 不是选中数据后直接触发 所以要使用的话也得改造下 不难
知识积累,有不足的地方希望各位评论中指出来改进