vue下拉列表的两种实现方式比较

vue下拉列表的两种实现

第一种采用v-for的方式

  <el-select v-model="form.columeType" placeholder="字段类型">
           <el-option v-for="(item,index) in columeTypeArr" :key="index" :label="item.label" :value="item.value">
           </el-option>
  </el-select>

这种方式需要在data中定义columeTypeArr,如下

data(){
    return {
       columeTypeArr:[{
            value:'String',
            label:'字符串'
        },{
            value:'Int',
            label:'整数',
        },{
            value:'Decimal',
            label:'数值型'
        }],
    }
}

第二种采用写死的方式

直接在select下写死

  <el-select v-model="form.fileOrgType" placeholder="请选择">
        <el-option label="是" value="Y"> </el-option>
        <el-option label="否" value="N"></el-option>
  </el-select>

两种方式的比较:

两种方式都差不多,只是第一种方式需要在data中进行配置,对于需要数据从后台回显的情况,明显是第一种方法好。

对于简单的下拉列表参数很少的情况,第二种明显更占优。

vue下拉菜单组件的实现

我们一起来实现一个vue的下拉菜单组件。

像这种基本UI组件,网上已经有很多了,为什么要自己实现呢?其实并不是有意重复造轮子,而是想通过这个过程回顾一下vue组件开发的一些细节和注意事项。

为什么选择下拉菜单组件?

因为:麻雀虽小五脏俱全,这个小小的组件涉及到了不少vue组件开发的知识点。

好了,那就开始吧!

首先创建一个vue-cli的项目,笔者用的是vue-cli3,创建过程略,然后创建一个vue组件:DropDownList.vue

在编写模板之前,我们来分析一下这个组件的视图结构和功能。

下拉菜单组件应该由两部分组成:

选中项的文本

待选菜单(默认隐藏)

它的主要功能包括:

鼠标经过下拉菜单组件,显示待选菜单

鼠标滑出下拉菜单组件,隐藏待选菜单

鼠标点击待选菜单中的条目,选中项文本更新,组件派发change事件

我们编写如下这样的模板:

<template>
  <div class="zq-drop-list" @mouseover="onDplOver($event)" @mouseout="onDplOut($event)">
        <span>选中项的文本<i></i></span>
        <ul>
            <li>北京</li>
            <li>上海</li>
            <li>广州</li>
        </ul>
    </div>
</template>

选中项文本右侧的i标签,用来实现下拉菜单的三角形图标,在下文的css中我们用背景图来实现。

我们给根元素div已经添加了鼠标经过和滑出的回调函数,具体实现见下文。

接下来我们为这个下拉菜单编写样式,在模板下方添加style标签,为了防止和其他组件的样式发生冲突,笔者建议大家在开发组件时,都给style加上scoped属性。另外,笔者在这里用到了scss,具体代码如下:

<style scoped lang="scss">
    .zq-drop-list{
        display: inline-block;
        min-width: 100px;
        position: relative;
        span{
            display: block;
            height: 30px;
            line-height: 30px;
            background: #f1f1f1;
            font-size: 14px;
            text-align: center;
            color: #333333;
            border-radius: 4px;
            i{
                background: url(https://www.easyicon.net/api/resizeApi.php?id=1189852&size=16) no-repeat center center;
                margin-left: 6px;
                display: inline-block;
            }
        }
        ul{
            position: absolute;
            top: 30px;
            left: 0;
            width: 100%;
            margin: 0;
            padding: 0;
            border: solid 1px #f1f1f1;
            border-radius: 4px;
            overflow: hidden;
            li{
                list-style: none;
                height: 30px;
                line-height: 30px;
                font-size: 14px;
                border-bottom: solid 1px #f1f1f1;
                background: #ffffff;
            }
            li:last-child{
                border-bottom: none;
            }
            li:hover{
                background: #f6f6f6;
            }
        }
    }
</style>

关于样式,这里就不详细展开了,只说其中几个需要注意的点:

那个i元素的样式,我用到了一个网络图片,大家可以自行更换

待选菜单ul在css里并没有让它隐藏,因为我们要通过js来控制,具体原因见下文

待选菜单ul使用了绝对定位,因为当它展开的时候,不应该影响页面上其他元素的布局

现在这个组件大概长这个样子:

我们继续为这个组件定义属性,很显然,待选菜单应该作为属性传进来,一定不能是内部写死的,属性定义如下:

<script>
export default {
    name: "DropDownList",
    props:{
        dataList:{
            type:Array,
            default(){
                return [
                    {name: "选项一"},
                    {name: "选项二"}
                ]
            }
        },
        labelProperty:{
            type:String,
            default(){ return "name" }
        }
    },
    data(){
        return {
            activeIndex:0
        }
    },
}

其中dataList就是待选菜单的数据源属性,这里我们给这个属性定义了默认值,这也是笔者建议大家养成的一个习惯,作为一个组件,最好有默认值,因为当别人使用你的组件时,可以先不设置相关属性,就能看到一个成品的效果,也能快速查看你这个组件所需属性的数据细节。

另外一个属性是labelProperty,这个属性的作用是什么?我们实际项目中的数据源,并不一定都含有name这个字段,因此就可能导致下拉菜单无法渲染数据的文本,于是我们定义了这个属性用来指定实际数据源渲染文本的字段,这个字段必须是字符串。这个属性的默认值是name,因为它需要和默认数据源保持一致。相信你还看到了一个组件内部数据,activeIndex,这个是用来表示当前选中项的索引的,我们后面会用到。

现在我们就可以在其他地方引入并使用这个组件了,虽然它还没有完成,但我们不妨先让它显示在界面上吧:

<template>
  <div class="home">
    <DropList :dataList="dplist" labelProperty="city" @change="onDpChange($event)"></DropList>
    <p>其他文本内容</p>
  </div>
</template>
<script>
  import DropList from '@/components/DropDownList.vue'
  //其他代码略
</script>

这个页面引入并使用了我们的DropDownList组件,:dataList="dplist" 绑定了当前页面的dplist数组到组件的dataList属性上,这个数组中的对象有一个city字段,我们希望此字段显示在下拉菜单上,因此我们设置组件的labelProperty为city,我们还给这个组件注册了change事件,这个组件内部需要派发这个事件,见下文。

现在我们回到组件的模板部分,发现它都还是静态内容,我们把这些静态内容修改为通过属性渲染。

<template>
    <div class="zq-drop-list" @mouseover="onDplOver($event)" @mouseout="onDplOut($event)">
        <span>{{dplLable}}<i></i></span>
        <ul>
            <li v-for="(item, index) in dataList" :key="index" @click="onLiClick(index, $event)">{{item[labelProperty]}}</li>
        </ul>
    </div>
</template>

其中待选菜单li的文本是 item[labelProperty] 这样就能正确的显示开发者指定的字段了。

我们看看选中项的文本表达式:dplLabel,我们并没有定义这个属性,也没有定义这个内部数据,它是哪儿来的?选中项的文本应该是 dataList[activeIndex][labelProperty] (这个很好理解吧,有问题请留言),但这个表达式太长了,写在模板里不利于维护,我们就把它写到计算属性里吧。

computed:{
        dplLable(){
            return this.dataList[this.activeIndex][this.labelProperty]
        }
    }

于是才有了上面的dplLabel,计算属性真的很好用呢。

现在下拉菜单的视图和数据关联部分我们已经写完了,接下来我们要实现它的功能。

第一步是先让待选菜单默认隐藏起来,这里我们为什么不直接用css的display:none呢,然后鼠标经过的时候display:block不就可以了吗?因为这样的话,我们无法实现点击待选菜单条目的时候让它隐藏,体验不好。我们用js来控制,但vue对直接访问dom元素支持的并不好,我们要想在组件初始化的时候访问dom元素,有一个最方便的做法,那就是:自定义指令。

我们为下拉菜单组件添加局部自定义指令,代码如下:

directives:{
        dpl:{
            bind(el){
                el.style.display = "none";
            }
        }
    },

这个dpl就是自定义指令啦,请忽略我笨拙的命名哈!然后我们在自定义指令的钩子函数bind方法中,访问el元素,控制它的style属性display:none; 最后,把这个自定义指令加到模板里面的ul标签上。别忘了要加v-,现在看看效果,待选菜单已经隐藏了。

<ul v-dpl>

我们利用自定义指令钩子函数访问dom元素,实现了对dom的控制,这一点非常实用!

让我们继续实现最开始为下拉菜单定义的鼠标经过和鼠标滑出的监听,实现待选菜单的显示与隐藏。

onDplOver(event){
    let ul = event.currentTarget.childNodes[1];
    ul.style.display = "block";
},
onDplOut(event){
    let ul = event.currentTarget.childNodes[1];
    ul.style.display = "none";
},

我们在鼠标事件中,访问event的currentTarget对象,为什么不是target?因为下拉菜单的子元素也会触发这个事件,如果访问target,可能不会是我们预期的顶层元素。

最后一步,我们实现待选菜单条目的点击事件,点击后,待选菜单隐藏,修改内部状态,派发change事件。

onLiClick(index){
    let path = event.path || (event.composedPath && event.composedPath()) //兼容火狐和safari
    path[1].style.display = "none";
    this.activeIndex = index;
    this.$emit("change", {
        index:index,
        value:this.dataList[index]
    })
}

这里有一个细节需要注意,我们要通过li元素找到外层ul元素,但path不支持火狐和safari,好在这两个浏览器支持composedPath,因此才有了第一行代码的兼容写法。然后通过修改内部数据activeIndex实现选中项文本的更新,最后调用emit方法向父元素派发change事件,别忘了把事件对象封装好传出去。

完整的代码如下:

<template>
    <div class="zq-drop-list" @mouseover="onDplOver($event)" @mouseout="onDplOut($event)">
        <span>{{dplLable}}<i></i></span>
        <ul v-dpl>
            <li v-for="(item, index) in dataList" :key="index" @click="onLiClick(index, $event)">{{item[labelProperty]}}</li>
        </ul>
    </div>
</template>
<script>
export default {
    name: "DropDownList",
    data(){
        return {
            activeIndex:0
        }
    },
    props:{
        dataList:{
            type:Array,
            default(){
                return [
                    {name: "选项一"},
                    {name: "选项二"}
                ]
            }
        },
        labelProperty:{
            type:String,
            default(){ return "name" }
        }
    },
    directives:{
        dpl:{
            bind(el){
                el.style.display = "none";
            }
        }
    },
    methods:{
        onDplOver(event){
            let ul = event.currentTarget.childNodes[1];
            ul.style.display = "block";
        },
        onDplOut(event){
            let ul = event.currentTarget.childNodes[1];
            ul.style.display = "none";
        },
        onLiClick(index){
            let path = event.path || (event.composedPath && event.composedPath()) //兼容火狐和safari
            path[1].style.display = "none";
            this.activeIndex = index;
            this.$emit("change", {
                index:index,
                value:this.dataList[index]
            })
        }
    },
    computed:{
        dplLable(){
            return this.dataList[this.activeIndex][this.labelProperty]
        }
    }
}
</script>
<style scoped lang="scss">
    .zq-drop-list{
        display: inline-block;
        min-width: 100px;
        position: relative;
        span{
            display: block;
            height: 30px;
            line-height: 30px;
            background: #f1f1f1;
            font-size: 14px;
            text-align: center;
            color: #333333;
            border-radius: 4px;
            i{
                background: url(https://www.easyicon.net/api/resizeApi.php?id=1189852&size=16) no-repeat center center;
                margin-left: 6px;
                display: inline-block;
            }
        }
        ul{
            position: absolute;
            top: 30px;
            left: 0;
            width: 100%;
            margin: 0;
            padding: 0;
            border: solid 1px #f1f1f1;
            border-radius: 4px;
            overflow: hidden;
            li{
                list-style: none;
                height: 30px;
                line-height: 30px;
                font-size: 14px;
                border-bottom: solid 1px #f1f1f1;
                background: #ffffff;
            }
            li:last-child{
                border-bottom: none;
            }
            li:hover{
                background: #f6f6f6;
            }
        }
    }
</style>

以上为大家展示了vue如何实现一个下拉菜单组件,虽然比较简单,但也基本涉及到了组件开发常用的一些特性。希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • vue下拉列表功能实例代码

    最近在弄作品,做了个下拉列表.心想各位小哥哥.小姐姐可能会用到相同的需求,就把下拉列表封装一下,希望能对各位小哥哥,小姐姐有帮助 github地址: https://github.com/ClmPisces/vue-droplist 喜欢的请反手来个star,有issue的欢迎提出 安装 cnpm install vue-droplist --save 组件中导入 import DropList from 'vue-droplist' // 显示下拉列表 showDropList() { //

  • vue实现百度下拉列表交互操作示例

    本文实例讲述了vue实现百度下拉列表交互操作.分享给大家供大家参考,具体如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>www.jb51.net vue百度下拉列表</title> <style> .gray{ background: #ccc; } </style> &l

  • Vue表单之v-model绑定下拉列表功能

    vue要绑定下拉列表会稍微有点不同. 因为下拉列表不是一个标签能搞掂的. 原生的html写法如下 <select> <option value="Vue.js">Vue.js</option> <option value="React.js">React.js</option> <option value="Angular.js">Angular.js</option&

  • vue实现歌手列表字母排序下拉滚动条侧栏排序实时更新

    今天写项目的时候遇到了歌手排序的问题,联想到了我们平时的手机通讯录侧栏字母排序,按照ABCDE这样的顺序对名字进行排序. 我们先来看看效果 那就用vue来实现一遍 首先新建一个vue页面,取名为helloworld.vue 在页面里写入内容 <template> <div class="hello"> <div class="singer" id="singer"> <div class="si

  • 在vue中根据光标的显示与消失实现下拉列表

    重点知识 mousedown事件:mousedown的执行顺序大于blur事件 HTML代码 <template> <div> <input ref="search" @focus="showList(true)" v-model="search" @blur="showList(false)"/> <ul v-if="state"> <li style

  • vue下拉列表的两种实现方式比较

    vue下拉列表的两种实现 第一种采用v-for的方式 <el-select v-model="form.columeType" placeholder="字段类型"> <el-option v-for="(item,index) in columeTypeArr" :key="index" :label="item.label" :value="item.value"&

  • Vue弹窗的两种实现方式实例详解

    目录 方法一 使用.sync修饰符 方法二 使用v-model 方法一 使用.sync修饰符 element就是使用的这种方式,实现方式如下: 父组件: <template> <div id="demo"> <test-model :show.sync="showFlag"></test-model> </div> </template> <script> import testMo

  • vue动态子组件的两种实现方式

    文章目录 方式一:局部注册所需组件 使用缓存 方式二:动态注册组件实现 让多个组件使用同一个挂载点,并动态切换,这就是动态组件. 通过使用保留的 <component>元素,动态地绑定到它的 is 特性,可以实现动态组件. 方式一:局部注册所需组件 <div id="example"> <button @click="change">切换页面</button> <component :is="curre

  • Vue中keep-alive的两种应用方式

    Vue中keep-alive的使用我总结的有两种方式应用: 首先简述一下keep-alive的作用,kee-alive可以缓存不活动的的组件.当组件之间进行相互切换的时候,默认会销毁,当重新切换回来时又重新初始化.现在有需求切换回来不销毁组件,保持原来的状态,此时用keep-alive就可以实现了 我创建了两个组件,可以相互切换 组件1: 组件2: 第一种方式 在组件1的路由中添加 meta: { keepAlive: true }, 也就是当前路由需要缓存 此时路由设置完毕,keep-aliv

  • vue用户长时间不操作退出到登录页的两种实现方式

    目录 问题描述 前端控制(方式一) 思路 代码 后端控制(方式二) 思路 代码 总结 问题描述 产品说,出于安全考虑,用户长时间不操作,就回到登录页面,让用户重新登录,就像银行的app一样.本文就记录一下实现这种效果的两种方式,分别是前端控制和后端控制,各有细节及适用使用场景 前端控制(方式一) 思路 首先,用户长时间不操作具体表现形式是啥?其实就是事件是否长时间没有被触发执行. 比如用户长时间不操作,就没有鼠标点击(click)事件.鼠标滚轮(mousewheel)事件.鼠标移动(mousem

  • jQuery autoComplete插件两种使用方式及动态改变参数值的方法详解

    本文实例讲述了jQuery autoComplete插件两种使用方式及动态改变参数值的方法.分享给大家供大家参考,具体如下: 一.一次加载.多次使用: 前端JS代码: /*客户名称自动匹配*/ function customerAutoComplete(){ $.ajax({ type:"GET", url:encodeURI("/approvalajax/salesOrderApproval_findCustomerList"), dataType:"j

  • 详解Nuxt内导航栏的两种实现方式

    方式一 | 通过嵌套路由实现 在pages页面根据nuxt的路由规则,建立页面 1. 创建文件目录及文件 根据规则,如果要创建子路由,子路由的文件夹名字,必须和父路由名字相同 所以,我们的文件夹也为index,index文件夹需要一个默认的页面不然nuxt的路由规则就不能正确匹配页面 一级路由是根路由 二级路由是index,user,默认进入index路由 下面是router页面自动生成的路由 { path: "/", component: _93624e48, children: [

  • 浅谈TreeSet中的两种排序方式

    直接上代码: package exercise1; public class Person implements Comparable{ private int id; private String name; public Person(int id, String name) { super(); this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { th

  • MyBatis之自查询使用递归实现 N级联动效果(两种实现方式)

    A:首先先看下一个简单的面试题 斐波那契数列 计算数组{1,1,2,3,5,8.......} 第30位值 规律:1 1 从第三项开始,每一项都是前两项之和 有两种实现方式  第一种方式: public class TestOne { public int TestSelf(int n){ if(n<0){ throw new IllegalArgumentException("n不能为负数"); }else if(n<=2){ return 1; }else{ retur

  • JS 动态加载js文件和css文件 同步/异步的两种简单方式

    /*动态添加js或css,URL:文件路径,FileType:文件类型(js/css)*/ function AddJsFiles(URL,FileType){ var oHead = document.getElementsByTagName('HEAD').item(0); var addheadfile; if(FileType=="js"){ addheadfile= document.createElement("script"); addheadfile

随机推荐