Vue在页面右上角实现可悬浮/隐藏的系统菜单

这是个大多数网站很常见的功能,点击页面右上角头像显示一个悬浮菜单,点击页面其他位置或再次点击头像则菜单隐藏。

作为一个jQuery前端攻城狮实现这个功能可以说是很easy了,但是对只刚粗看了一遍vue文档的菜鸟来说,坑还是要亲自踩过才算圆满。

知识点

  • 组件及组件间通信
  • 计算属性

正文

1. 父组件

这里暂时只涉及系统菜单这一个功能,因此路由暂未涉及。

基本思路是:通过props将showCancel这个Boolean值传递到子组件,对父子组件分别绑定事件,来控制这个系统菜单的显示状态。其中在父组件的绑定click事件中,将传入子组件的showCancel值重置。

这里就涉及第一个小知识点——子组件调用:

首先写好等待被子组件渲染的自定义元素:

<t-header :showCancel=showCancel></t-header>

接着import写好的子组件:

import THeader from "./components/t-header/t-header";

然后在组件中注册子组件:

components: {
 THeader
}

到这里,新入坑的同学可能会比较疑惑这几行代码是怎样把子组件对应到<t-header>标签的,官方文档是这样说的:

当注册组件 (或者 prop) 时,可以使用 kebab-case (短横线分隔命名)、camelCase (驼峰式命名) 或 PascalCase (单词首字母大写命名);

在 HTML 模板中,请使用 kebab-case;

我的理解是(举例),当自定义元素为<t-header>时,注册组件名可以有三种写法:t-header、tHeader和THeader,在这种情况下注册的组件会自动识别并渲染到<t-header>。

需要注意的是,以上使用的是HTML 模板,是在单文件组件里用<template><template/>指定的模板;另外存在一种字符串模板,是用在组件选项里用template: "" 指定的模板。当使用字符串模板时,自定义标签可以用三种写法,具体情况请移步官方文档 组件命名约定。

这样父组件的雏形就诞生了:

<template>
 <div id="app" @click="hideCancel">
 <t-header :showCancel=showCancel></t-header>
 <!-- <router-view/> -->
 </div>
</template>
<script>
 import THeader from "./components/t-header/t-header";
 export default {
 name: "app",
 components: {
  THeader
 },
 data() {
  return {
  showCancel: false
  };
 },
 methods: {
  hideCancel() {
  this.showCancel = false;
  }
 }
 };
</script>

2. 子组件

子组件中.cancel为打开系统菜单的按钮,.cancel-div为系统菜单,最开始是这个样子:

<template>
 <div class="header-wrapper">
 /*这里是logo和title*/
 ...
 /*这里是用户名和按钮*/
 <div class="info-wrapper">
  <span class="username">你好,管理员!</span>
  <span class="cancel" @click.stop="switchCancelBoard">
  <div class="cancel-div" v-show="showCancel">
   <ul>
   <li @click.stop="doSomething" title="用户设置">设置 </li>
   <li @click.stop="doSomething" title="退出登录">退出 </li>
   </ul>
  </div>
  </span>
 </div>
 </div>
</template>

按照踩坑之前的思路,在子组件接到showCancel值后用v-show控制显示隐藏,那么在父子组件的绑定click事件中只需要根据情况更改showCancel值就可以了,只要注意对系统菜单内几个选项的绑定事件不要触发父子组件上的绑定事件——总不能一点菜单它就没了,所以在绑定事件中用到了.stop,即
@click.stop="doSomething"

于是万事大吉,也就是像这样:

<script>
 export default {
 props: {
  showCancel: {
  type: Boolean
  }
 },
 methods: {
  doSomething() {},
  switchCancelBoard() {
  this.showCancel = !this.showCancel;
  }
 },
 computed: {
  ifShowCancel() {
  return this.showCancel;
  }
 }
 };
</script>

然而第一波踩坑之后一起表明显然我还是太年轻。下面是一些不好的示范:

prop来的showCancel值的确可以用,点击子组件按钮的时候,

this.showCancel=!this.showCancel

实现了菜单的显示/隐藏,但是一打开控制台,每次点击贡献了一条报错:

vue.esm.js?efeb:578 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.

意思是:避免修改prop值,因为父组件一旦re-render,这个值就会被覆盖;

另外,尽管在这个按钮上实现了显示状态的切换,但是点击其他区域的时候,并不会隐藏它,原因是:子组件prop值的变化并没有影响到父组件,因此showCancel的值一直保持初始值没有变化,而只有在这个值被更新时才会触发子组件中相关值的更新。

——好吧,那么老老实实的用一个计算属性接收showCancel值,这样实现点击子组件控制系统菜单的状态切换;

获得了计算属性ifShowCancel,组件相应的变成了v-show="ifShowCancel",我试图在绑定事件里通过this.ifShowCancel=!this.ifShowCancel切换菜单状态,报错,得到报错信息:Computed property "ifShowCancel" was assigned to but it has no setter;

明白了,要以直接赋值的形式改变计算属性ifShowCancel的值,需要一个setter函数,但是setter函数中无法修改prop值,因此在getter中也就无法通过return this.showCancel来更新这个计算属性,所以这个方法貌似也行不通;

到此为止,好像路都成了堵死状态:prop值不能改->要用计算属性;计算属性不能改->需要setter;而写入了getter、setter,计算属性的值依赖于prop值->prop值不能改。——一个堪称完美的闭环诞生了!

走投无路之际我想起了$emit和$on这一对。

3. 父子互相通信

前边的prop实现了从父到子的单向通信,而通过$emit和$on,就可以实现从子组件到父组件的通信:这不能直接修改父组件的属性,但却可以触发父组件的指定绑定事件,并将一个值传入父组件。

在这一步我摒弃了点击按钮时的去操作子组件内属性的想法,既然计算属性ifShowCancel依赖于prop值,那么就在点击按钮时,通过$emit触发父组件的事件,并将需要修改的属性值传入父组件,于是:

/*父组件自定义元素绑定switch-show事件*/
<t-header :showCancel=showCancel @switch-show="switchShow"></t-header>
// 父组件js
methods: {
 //会被子组件$emit触发的方法
 switchShow(val) {
 this.showCancel = val;
 }
}
// 子组件js
methods: {
 //按钮上的绑定click事件
 switchCancelBoard() {
 this.$emit("switch-show", this.ifShowCancel);
 }
}

这样处理流程就变成了:点击按钮->作为计算属性的ifShowCancel值传入父组件并触发父组件事件,对showCancel赋值->父组件属性更新->触发子组件prop更新->触发重新compute,更新ifShowCancel值->v-show起作用。
另外在点击其他区域时,通过父组件绑定的click事件,就可以重置showCancel值,进而隐藏掉出现的系统菜单。

下边放出这个功能的完整代码。

4. 完整代码

/*父组件*/
<template>
 <div id="app" @click="hideCancel">
 <t-header :showCancel=showCancel @switch-show="switchShow"></t-header>
 <!-- <router-view/> -->
 </div>
</template>
<script>
 import THeader from "./components/t-header/t-header";
 export default {
 name: "app",
 components: {
  THeader
 },
 data() {
  return {
  showCancel: false
  };
 },
 methods: {
  hideCancel() {
  this.showCancel = false;
  },
  switchShow(val) {
  this.showCancel = val;
  }
 }
 };
</script>
<style scope lang="stylus">
</style>
/*子组件*/
<template>
 <div class="header-wrapper">
 <div class="title-wrapper">
  <div class="logo"></div>
  <h2 class="title">Title</h2>
 </div>
 <div class="info-wrapper">
  <span class="username">你好,管理员!</span>
  <span class="cancel" @click.stop="switchCancelBoard">
  <div class="cancel-div" v-show="ifShowCancel">
   <ul>
   <li @click.stop="doSomething" title="用户设置">设置 </li>
   <li @click.stop="doSomething" title="退出登录">退出 </li>
   </ul>
  </div>
  </span>
 </div>
 </div>
</template>
<script>
 export default {
 props: {
  showCancel: {
  type: Boolean
  }
 },
 methods: {
  doSomething() {},
  switchCancelBoard() {
  // this.ifShowCancel = !this.showCancel;
  this.$emit("switch-show", !this.ifShowCancel);
  }
 },
 computed: {
  ifShowCancel() {
  return this.showCancel;
  }
 }
 };
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
 .header-wrapper
 background: #1C60D1
 color: #fff
 width: 100%
 height: 50px
 line-height: 50px
 position: fixed
 top: 0px
 left: 0px
 font-size: 0
 .title-wrapper
  display: block
  position: relative
  float: left
  height: 50px
  .logo
  display: inline-block
  background-image: url('./logo.png')
  background-size: 30px 30px
  background-repeat: no-repeat
  width: 30px
  height: 30px
  margin-top: 10px
  .title
  display: inline-block
  font-size: 16px
  height: 50px
  line-height: 50px
  margin: 0px auto 0px 16px
  font-weight: normal
  vertical-align: top
 .info-wrapper
  display: block
  position: relative
  float: right
  height: 50px
  width: 160px
  font-size: 0
  .username
  display: inline-block
  height: 50px
  line-height: 50px
  font-size: 14px
  vertical-align: top
  .cancel
  display: inline-block
  vertical-align: middle
  background-image: url('./cancel.png')
  background-size: 32px 32px
  cursor: pointer
  background-repeat: no-repeat
  width: 32px
  height: 32px
  .cancel-div
   position: absolute
   display: block
   width: 60px
   height: 80px
   background: #fff
   z-index: 50
   top: 40px
   right: 16px
   font-size: 14px
   color: #646464
   box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.4)
   ul
   padding-left: 0px
   margin: 0px
   li
    width: 100%
    height: 40px
    line-height: 40px
    text-align: center
    list-style-type: none
    &:hover
    background-color: #eaeaea
</style>

总结

以上所述是小编给大家介绍的Vue在页面右上角实现可悬浮/隐藏的系统菜单,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

您可能感兴趣的文章:

  • vue.js 左侧二级菜单显示与隐藏切换的实例代码
时间: 2018-05-02

vue.js 左侧二级菜单显示与隐藏切换的实例代码

废话不多说了,直接给大家贴代码了, 完整代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>vue点击切换显示隐藏</title> <script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></script> <style type

vue.js实现二级菜单效果

本文实例为大家分享了vue.js实现二级菜单效果的具体代码,供大家参考,具体内容如下 主要是对二级菜单和当前点击的处理: 点击导航时,如果有二级菜单,就切换二级菜单显示状态(显示或者关闭),如果没有二级菜单,就变色,表示页面处于当前位置,并且导航中最多只能有一个菜单变色. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title&g

vue.js使用v-if实现显示与隐藏功能示例

本文实例讲述了vue.js使用v-if实现显示与隐藏功能.分享给大家供大家参考,具体如下: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1

vue.js通过路由实现经典的三栏布局实例代码

经典的三栏布局效果图如下: 三栏布局 •将布局的各个区块定义成组件 <template id="header"> <div class="header bg-primary text-center"> <h3>头部区域</h3> </div> </template> <template id="left"> <div class="left bg-

Vue.js子组件向父组件通信的方法实例代码详解

一.场景描述: 曾经有个电商项目,其中有个"老带新"模块,而且该模块新增的入口很多,但是新增后展示效果还不一样,当时就考虑将新增的组件单独拿出来,其实就是一个子组件向父组同步数据的过程. 当然,背景不重要了,关键是看实现的方式. 二.场景展示效果 (PS:展示效果请忽略美感) 三.如何实现 注意:Vuejs架构通过vue-cli 3.X搭建的项目,版本无所谓. 1.先看下目录体系,下图子组件放在components文件夹内,模拟子组件为itemAdd.vue,父组件视图放在views文

el-input 标签中密码的显示和隐藏功能的实例代码

效果展示: 密码隐藏: 密码显示: 代码展示: 一:<el-input>标签代码 <el-form-item label="密码" prop="password"> <el-input :type="passw" v-model="adduser.password" style="width: 300px;" > <%-- input中加图标必须要有slot=&qu

vue.js模仿京东省市区三级联动的选择组件实例代码

前言 在最近的工作中需要一个盒京东购物车地址选择相似的一个省市区三级联动选择组件,google查了下都是下拉框形式的,于是自己写了一个,希望对使用vue开发项目的朋友有帮助,下面话不多说了,来一起看看详细的介绍吧. 显示效果如下: 注意:使用vue2.0开发 实例代码 html代码如下 <!--居住地址三级联动选项--> <section class="showChose" v-show="showChose"> <section cl

Vue.js 点击按钮显示/隐藏内容的实例代码

实例代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>vue点击切换显示隐藏</title> <script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></script> </head> <body> <d

js实现二级菜单点击显示当前内容效果

最近做了一个二级菜单点击事件的效果,一级菜单有3个,二级菜单是固定的,而且html布局并不是父子级关系,具体请看效果图如下 html结构入下 <div> <ul> <li>您好!日期</li> <li class="li_list checked grounder">滚球</li> <li class="li_list">今日赛事</li> <li class=&q

js实现二级菜单渐隐显示

先给大家看一看最终的效果图: 下面就是js实现二级菜单渐隐显示的代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head>