Vue.js自定义下拉列表,如何实现在下拉列表区域外点击即可关闭下拉列表的功能

来源:互联网 发布:淘宝开店一件代发经验 编辑:程序博客网 时间:2024/05/20 02:26

在开发过程中,为了效果好看,往往需要自己开发一个下拉列表,而不是使用 HTML 自身的 select 下拉列表。然而当编写自定义下拉列表的时候,就会碰到一个问题:如果用户在下拉列表的范围外进行鼠标点击的操作,如何关闭已经打开的下拉列表?

解决思路如下:在 DOM 的根节点上添加一个 click 事件,同时下拉列表内阻止事件的默认行为和冒泡。当响应这个点击事件的时候,说明是在下拉列表范围外的点击(因为下拉列表内阻止了事件的冒泡),就可以关闭已经打开的下拉列表。

如果是纯 JS 代码,有人可能会使用 document.onclick 来添加根节点事件。不过,我现在使用 Vue.js,会选择使用 Vue.js 的方式处理这个问题。

Vue.js 使用组件化的方式组织代码,会有一个根组件,可以在这个根组件上加上 @click 事件,来响应区域外的点击事件。在一个完整的应用中,可能有多种场景需要这种区域外点击关闭的功能。除了最普通的表单里的下拉列表外,还可能是网站右上角的消息提示框,或者菜单。比较合适的做法是把点击事件的具体处理逻辑放到各个组件中去。

那么如何让各个子组件响应根组件上的点击事件呢?可以使用Vuex来做到这一点。在这里 Vuex 起到了组件之间互相传递信息的作用。

读者可以在这个网址下载我编写的 Demo 项目:http://download.csdn.net/detail/zhangchao19890805/9855750。
推荐读者使用 yarn install 安装所需的依赖。

下面说一下关键代码:

程序入口 main.js:

import Vue from 'vue'import App from './App.vue'import VueRouter from 'vue-router'import routes from './router'import VueSuperagent from 'vue-superagent'import Vuex from 'vuex'import 'babel-polyfill';import store from './vuex/store';Vue.use(VueRouter);Vue.use(VueSuperagent);Vue.use(Vuex);const router = new VueRouter({  mode: 'history',  routes})new Vue({  el: '#app',  router,  store,  render: h => h(App)})

根节点 App.vue,添加了点击事件。

<template>  <div @click="clickRoot">    <router-view></router-view>  </div></template><script>    export default {        methods:{            clickRoot(event){                this.$store.dispatch("clickRootNumAction", 1);            }        }    }</script>

Vuex 文件结构

vuex │ └─modules      ├─clickRoot      │    ├─actions.js      │    ├─getters.js      │    ├─index.js      │    └─mutations.js      │      └─store.js

actions.js

export default {    // action 允许异步加载,实际项目中    // 这里可以发起个请求,再返回。    clickRootNumAction(context, value) {        context.commit('clickRootNum', value);    }}

getters.js

export default {    getClickRootNum(state) {        return state.clickRootNum;    }}

index.js

import actions from './actions'import getters from './getters'import mutations from './mutations'const state = {    clickRootNum: 0}export default {    state,    actions,    getters,    mutations}

mutations.js

export default {    clickRootNum(state, value) {        let sum = state.clickRootNum + value        state.clickRootNum = sum;    }}

store.js

import Vue from 'vue';import Vuex from 'vuex';import clickRoot from './modules/clickRoot'Vue.use(Vuex);const debug = process.env.NODE_ENV !== 'production';export default new Vuex.Store({    modules: {        clickRoot    },    strict: debug})

页面代码 test.vue

<template>    <div >        <p>测试</p>        <table>            <tbody>                <tr>                    <td style="vertical-align: top;">                        <div class="dropDownList">                            <button class="controll" @click.prevent.stop="listShow()"                                     @keydown.prevent.40="arrowDown1" @keydown.prevent.38="arrowUp1">                                {{selectItem}}                                <span  :class="['triangle',showList==false?'triangleShow':'triangleHidden']"></span>                            </button>                            <ul class="showList" v-if="showList" @click.prevent.stop>                                <input v-model="filterText" class="search"/>                                <li v-for="item in newObj" class="optionArea" @click="selectOption(item)">  {{item.type}} </li>                            </ul>                        </div>                    </td>                    <td style="vertical-align: top;">                        <div class="dropDownList">                            <button class="controll" @click.prevent.stop="listShow2()"                                     @keydown.prevent.40="arrowDown2" @keydown.prevent.38="arrowUp2">                                {{selectItem2}}                                <span  :class="['triangle',showList2==false?'triangleShow':'triangleHidden']"></span>                            </button>                            <ul class="showList" v-if="showList2" @click.prevent.stop>                                <input v-model="filterText2" class="search"/>                                <li v-for="item in newObj2" class="optionArea" @click="selectOption2(item)">  {{item.type}} </li>                            </ul>                        </div>                    </td>                </tr>            </tbody>        </table>    </div></template><script>    export default {        data(){            return {                showList:false,                obj:[                    {type:"男装"},                    {type:"女装"},                    {type:"童装"},                    {type:"老年装"},                ],                filterText:"",                selectItem:"请选择",                showList2:false,                obj2:[                    {type:"奔驰"},                    {type:"桑塔纳"},                    {type:"大众"},                    {type:"比亚迪"},                ],                filterText2:"",                selectItem2:"请选择"            };        },        methods:{            listShow(){                this.showList=!this.showList;                if (this.showList2) {                    this.showList2 = false;                }            },            selectOption(item){                this.selectItem=item.type;                this.showList=false;            },            // 第一个下拉列表 按键:向下的箭头            arrowDown1(e){                if (!this.showList) {                    this.showList = true;                }                if (this.showList2) {                    this.showList2 = false;                }            },            // 第一个下拉列表 按键:向上的箭头            arrowUp1(e){                if (this.showList) {                    this.showList = false;                }                if (this.showList2) {                    this.showList2 = false;                }            },            listShow2(){                this.showList2=!this.showList2;                if (this.showList) {                    this.showList = false;                }            },            selectOption2(item){                this.selectItem2=item.type;                this.showList2=false;            },            // 第二个下拉列表 按键:向下的箭头            arrowDown2(e){                if (!this.showList2) {                    this.showList2 = true;                }                if (this.showList) {                    this.showList = false;                }            },            // 第一个下拉列表 按键:向上的箭头            arrowUp2(e){                if (this.showList2) {                    this.showList2 = false;                }                if (this.showList) {                    this.showList = false;                }            }        },        computed:{            newObj:function(){                let self = this;                return self.obj.filter(function (item) {                    return item.type.toLowerCase().indexOf(self.filterText.toLowerCase()) !== -1;                })            },            newObj2:function(){                let self = this;                return self.obj2.filter(function (item) {                    return item.type.toLowerCase().indexOf(self.filterText2.toLowerCase()) !== -1;                })            }        },        watch:{            '$store.getters.getClickRootNum': function () {                if (this.showList){                    this.showList = false;                }                if (this.showList2) {                    this.showList2 = false;                }            }        }    };</script><style lang="scss" rel="stylesheet/scss" scoped>    .dropDownList{        margin-left:50px;        width: 150px;        .controll{            position: relative;            width: 150px;            border: 1px solid #E3E9EF;            cursor: pointer;            .triangle{                display: inline-block;                position: absolute;                top: 7px;                right: 10px;                cursor: pointer;            }            .triangleHidden{                border-left: 5px solid transparent;                border-right: 5px solid transparent;                border-bottom: 8px solid #676F7F;            }            .triangleShow{                border-left: 5px solid transparent;                border-right: 5px solid transparent;                border-top: 8px solid #676F7F;            }        }        .showList{            margin: 0;            padding: 0;            border: 1px solid #E3E9EF;            // padding-top: 5px;            padding-bottom: 5px;            margin-top: 2px;            width: 145px;            .search{                width: 141px;                border: 1px solid #E3E9EF;            }            .optionArea{                list-style: none;                cursor: pointer;                font-size: 14px;                margin-left: 5px;                &:hover{                    background-color: #B2CFEB;                    color: #fff;                }            }        }    }</style>
原创粉丝点击