当前位置:Gxlcms > JavaScript > 怎样使用Vue在页面右上角实现可悬浮/隐藏菜单

怎样使用Vue在页面右上角实现可悬浮/隐藏菜单

时间:2021-07-01 10:21:17 帮助过:8人阅读

这次给大家带来怎样使用Vue在页面右上角实现可悬浮/隐藏菜单,使用Vue在页面右上角实现可悬浮/隐藏菜单的注意事项有哪些,下面就是实战案例,一起来看一下。

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

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

知识点

  • 组件及组件间通信

  • 计算属性

正文

1. 父组件

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

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

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

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

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

接着import写好的子组件:

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

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

  1. components: {
  2. THeader
  3. }

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

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

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

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

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

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

  1. <template>
  2. <p id="app" @click="hideCancel">
  3. <t-header :showCancel=showCancel></t-header>
  4. <!-- <router-view/> -->
  5. </p>
  6. </template>
  7. <script>
  8. import THeader from "./components/t-header/t-header";
  9. export default {
  10. name: "app",
  11. components: {
  12. THeader
  13. },
  14. data() {
  15. return {
  16. showCancel: false
  17. };
  18. },
  19. methods: {
  20. hideCancel() {
  21. this.showCancel = false;
  22. }
  23. }
  24. };
  25. </script>

2. 子组件

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

  1. <template>
  2. <p class="header-wrapper">
  3. /*这里是logo和title*/
  4. ...
  5. /*这里是用户名和按钮*/
  6. <p class="info-wrapper">
  7. <span class="username">你好,管理员!</span>
  8. <span class="cancel" @click.stop="switchCancelBoard">
  9. <p class="cancel-p" v-show="showCancel">
  10. <ul>
  11. <li @click.stop="doSomething" title="用户设置">设置 </li>
  12. <li @click.stop="doSomething" title="退出登录">退出 </li>
  13. </ul>
  14. </p>
  15. </span>
  16. </p>
  17. </p>
  18. </template>

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

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

  1. <script>
  2. export default {
  3. props: {
  4. showCancel: {
  5. type: Boolean
  6. }
  7. },
  8. methods: {
  9. doSomething() {},
  10. switchCancelBoard() {
  11. this.showCancel = !this.showCancel;
  12. }
  13. },
  14. computed: {
  15. ifShowCancel() {
  16. return this.showCancel;
  17. }
  18. }
  19. };
  20. </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触发父组件的事件,并将需要修改的属性值传入父组件,于是:

  1. /*父组件自定义元素绑定switch-show事件*/
  2. <t-header :showCancel=showCancel @switch-show="switchShow"></t-header>
  3. // 父组件js
  4. methods: {
  5. //会被子组件$emit触发的方法
  6. switchShow(val) {
  7. this.showCancel = val;
  8. }
  9. }
  10. // 子组件js
  11. methods: {
  12. //按钮上的绑定click事件
  13. switchCancelBoard() {
  14. this.$emit("switch-show", this.ifShowCancel);
  15. }
  16. }

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

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

4. 完整代码

  1. /*父组件*/
  2. <template>
  3. <p id="app" @click="hideCancel">
  4. <t-header :showCancel=showCancel @switch-show="switchShow"></t-header>
  5. <!-- <router-view/> -->
  6. </p>
  7. </template>
  8. <script>
  9. import THeader from "./components/t-header/t-header";
  10. export default {
  11. name: "app",
  12. components: {
  13. THeader
  14. },
  15. data() {
  16. return {
  17. showCancel: false
  18. };
  19. },
  20. methods: {
  21. hideCancel() {
  22. this.showCancel = false;
  23. },
  24. switchShow(val) {
  25. this.showCancel = val;
  26. }
  27. }
  28. };
  29. </script>
  30. <style scope lang="stylus">
  31. </style>
  32. /*子组件*/
  33. <template>
  34. <p class="header-wrapper">
  35. <p class="title-wrapper">
  36. <p class="logo"></p>
  37. <h2 class="title">Title</h2>
  38. </p>
  39. <p class="info-wrapper">
  40. <span class="username">你好,管理员!</span>
  41. <span class="cancel" @click.stop="switchCancelBoard">
  42. <p class="cancel-p" v-show="ifShowCancel">
  43. <ul>
  44. <li @click.stop="doSomething" title="用户设置">设置 </li>
  45. <li @click.stop="doSomething" title="退出登录">退出 </li>
  46. </ul>
  47. </p>
  48. </span>
  49. </p>
  50. </p>
  51. </template>
  52. <script>
  53. export default {
  54. props: {
  55. showCancel: {
  56. type: Boolean
  57. }
  58. },
  59. methods: {
  60. doSomething() {},
  61. switchCancelBoard() {
  62. // this.ifShowCancel = !this.showCancel;
  63. this.$emit("switch-show", !this.ifShowCancel);
  64. }
  65. },
  66. computed: {
  67. ifShowCancel() {
  68. return this.showCancel;
  69. }
  70. }
  71. };
  72. </script>
  73. <style lang="stylus" rel="stylesheet/stylus" scoped>
  74. .header-wrapper
  75. background: #1C60D1
  76. color: #fff
  77. width: 100%
  78. height: 50px
  79. line-height: 50px
  80. position: fixed
  81. top: 0px
  82. left: 0px
  83. font-size: 0
  84. .title-wrapper
  85. display: block
  86. position: relative
  87. float: left
  88. height: 50px
  89. .logo
  90. display: inline-block
  91. background-image: url('./logo.png')
  92. background-size: 30px 30px
  93. background-repeat: no-repeat
  94. width: 30px
  95. height: 30px
  96. margin-top: 10px
  97. .title
  98. display: inline-block
  99. font-size: 16px
  100. height: 50px
  101. line-height: 50px
  102. margin: 0px auto 0px 16px
  103. font-weight: normal
  104. vertical-align: top
  105. .info-wrapper
  106. display: block
  107. position: relative
  108. float: right
  109. height: 50px
  110. width: 160px
  111. font-size: 0
  112. .username
  113. display: inline-block
  114. height: 50px
  115. line-height: 50px
  116. font-size: 14px
  117. vertical-align: top
  118. .cancel
  119. display: inline-block
  120. vertical-align: middle
  121. background-image: url('./cancel.png')
  122. background-size: 32px 32px
  123. cursor: pointer
  124. background-repeat: no-repeat
  125. width: 32px
  126. height: 32px
  127. .cancel-p
  128. position: absolute
  129. display: block
  130. width: 60px
  131. height: 80px
  132. background: #fff
  133. z-index: 50
  134. top: 40px
  135. right: 16px
  136. font-size: 14px
  137. color: #646464
  138. box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.4)
  139. ul
  140. padding-left: 0px
  141. margin: 0px
  142. li
  143. width: 100%
  144. height: 40px
  145. line-height: 40px
  146. text-align: center
  147. list-style-type: none
  148. &:hover
  149. background-color: #eaeaea
  150. </style>

相信看了本文案例你已经掌握了方法,更多精彩请关注Gxl网其它相关文章!

推荐阅读:

怎样使用vue mint-ui tabbar(附代码)

怎样使用Vue结合Video.js播放m3u8视频

以上就是怎样使用Vue在页面右上角实现可悬浮/隐藏菜单的详细内容,更多请关注Gxl网其它相关文章!

人气教程排行