记录–vue中封装一个右键菜单组件(复制粘贴即可使用)

  • 记录–vue中封装一个右键菜单组件(复制粘贴即可使用)已关闭评论
  • 97 次浏览
  • A+
所属分类:Web前端
摘要

关于web端的右键功能常用的地方有表格的右键,或者tab标签的右键等,本文记录一下封装一个右键菜单组件的思路步骤代码。


这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

记录--vue中封装一个右键菜单组件(复制粘贴即可使用)

组件介绍

关于web端的右键功能常用的地方有表格的右键,或者tab标签的右键等,本文记录一下封装一个右键菜单组件的思路步骤代码。

程序员除了会用轮子,还要尝试去贴合自己公司业务场景造轮子。

组件效果图

我们先看一下右键组件的效果图

记录--vue中封装一个右键菜单组件(复制粘贴即可使用)

组件分析

1.封装组件第一步考虑dom结构

我们观察这个右键菜单,可以明白右键菜单就是一个ul标签包裹着很多li标签的弹出层组件,如下图:

记录--vue中封装一个右键菜单组件(复制粘贴即可使用)

 

 每一行都是一个li,每一行中包含图标行按钮名称文字,于是我们的dom结构可以这样写:

<ul class="table-right-menu">     <!-- 每个li都是一行,循环菜单数据,菜单数据后面再设计 -->     <li       v-for="item in menulists"       :key="item.btnName"        @click.stop="fnHandler(item)"     >       <div class="table-right-menu-item-btn">         <!-- 图标和按钮名 -->         <i class="el-icon-ele" />         <span>复制数据</span>       </div>     </li>   </ul>

2.dom结构搞清楚了,接下来就是考虑右键菜单组件接收的参数

如何考虑菜单组件接收哪些参数呢?

主要是想组件中会使用到哪些变量。如下:

  • 右键菜单需要一个数组,数组中存放的是每个菜单项的数据(菜单项图标、菜单项按钮名字、当然还有一些其他的需要传递的参数,统一挂在一个变量身上,如params)
  • 其次右键菜单组件的触发时机是拥挤点击右键的时候,那我们就得知道,用户右键点击的位置x、y的距离,所以这里还需要参数position中的x和y去记录距离视口的clientX和clientY值,因为右键菜单的位置就以这个作基准
  • 同时,我们还需要知道用户点击的是哪个菜单项按钮,所以再加一个事件名参数进去

综上所述,我们可以设计右键点击时,要给右键菜单组件传递的参数信息如下:

this.rightclickInfo = {         position: {           x: event.clientX,           y: event.clientY,         },         menulists: [           {             fnName: "copy", // 事件名字,组件届时可this.$emit(fnName)抛出事件             params: xxx,  // 参数,组件届时可this.$emit(fnName,params)抛出事件,并携带参数             icoName: "el-icon-document-copy", // 图标名             btnName: "复制数据", // 菜单项按钮名             // 这三项是发散,可往下看             // divided: true, // 是否禁用             // disabled: true, // 是否带分隔线             // children: [], // 是否有子菜单(递龟)           },           {             fnName: "look",             params: { row, column, event },             icoName: "el-icon-view",             btnName: "查看行数据",           },         ],       };

注意,上述参数代码示例中,多了三个参数divided、disabled、children,实际上,参数的设计要结合业务场景,我司的需求没有右键菜单禁用项,也不用有分割线,以及没有右键菜单的子菜单,所以封装组件就暂时没有加上这三个参数。

组件化、模块化的同时,主要高内聚,一个组件满足业务需求,精简为主,不可无节制的死命封装,否则就变成了诗山代码了,当然大家也可以仿照真正右键菜单去加功能,比如右键菜单可以绑定快捷键、改成递归形式等更多功能...

所以组件props中接收参数可以写成:

props: {     // 接收右键点击的信息     rightclickInfo: {       type: Object,       default: () => {         return {           position: {             // 右键点击的位置             x: null,             y: null,           },           menulists: [             {               fnName: "", // 点击菜单项的事件名               params: {}, // 点击的参数               icoName: "", // 图标名               btnName: "", // 按钮名             },           ],         };       },     },   },

3.实现右键打开菜单弹出层,左键点击一下菜单弹出层就关闭了

不难发现,只要一右键菜单就弹出,点一下菜单消失,这种不停的显示和消失,去不停的v-if就不合适了,所以这里可以从v-show的角度出发

  • 一开始让菜单层隐藏display:none,而后再设置成dispaly:block
  • 当右键点击时,右键点击的位置参数positionx和y的值就会发生变化
  • 我们可以watch监听这个变化,position的x、y值变了,说明右键点击了
  • 右键点击了,我们就可以让菜单弹出层出现
  • 同时,需要监听鼠标点击事件,当点击的是右键或者中间滚轮键时,不去隐藏面板,点击的是左键时,才去隐藏面板

通过上述五点,我们即做到了显示隐藏菜单面板了

4.监听右键位置变化,显示菜单项代码

这一块的思路请看代码中注释即可,如下:

.table-right-menu {     dispaly:none; // 初始为隐藏,监听更改显示 }   watch: {     // 监听右键点击时点击位置的变化,只要变化了,就弹出右键菜单供用户点击操作     "rightclickInfo.position"(val) {       let x = val.x; // 获取x轴坐标       let y = val.y; // 获取y轴坐标       let innerWidth = window.innerWidth; // 获取页面可是区域宽度,即页面的宽度       let innerHeight = window.innerHeight; // 获取可视区域高度,即页面的高度       /**        * 注意,这里要使用getElementsByClassName去选中对应dom,因为右键菜单组件可能被多处使用        * classIndex标识就是去找到对应的那个右键菜单组件的,需要加的        * */        let menu =         document.getElementsByClassName("table-right-menu")[this.classIndex];        menu.style.display = "block"; // 由隐藏改为显示       let menuHeight = this.rightclickInfo.menulists.length * 30; // 菜单容器高       let menuWidth = 180; // 菜单容器宽       // 菜单的位置计算(边界留点间隙空间)       menu.style.top =         (y + menuHeight > innerHeight ? innerHeight - menuHeight : y) + "px";       menu.style.left =         (x + menuWidth > innerWidth ? innerWidth - menuWidth : x) + "px";       // 因为菜单还要关闭,就绑定一个鼠标点击事件,通过e.button判断点击的是否是左键,左键关闭菜单       document.addEventListener("mouseup", this.hide, false);     },   },       hide(e) {   if (e.button === 0) {     // 0是左键、1是滚轮按钮或中间按钮(若有)、2鼠标右键     let menu = document.querySelector(".table-right-menu");     menu.style.display = "none"; // 菜单关闭     document.removeEventListener("mouseup", this.hide); // 及时解绑监听事件   } },

事件绑定后别忘了解绑 document.removeEventListener("mouseup", this.hide);

5.知识点回顾e.button

  • e.button,鼠标事件
  • 返回一个数字,表示触发鼠标事件的是按下了哪个按钮
  • 值为只读,不可修改

具体返回数字值,表示鼠标事件发生时按下的鼠标按钮。

可能的值:

0:鼠标左键、 1:滚轮按钮或中间按钮(如果有)、 2:鼠标右键

IE8返回有一些不同:1:鼠标左键、 2:鼠标右键、 4:滚轮按钮或中间按钮(如果有)

注意:左手鼠标,返回值相反

6.组件中的事件要抛出去哦

item即为循环的菜单项,包含事件名、参数、图标名、按钮名

fnHandler(item) {   this.$emit(item.fnName, item.params);   // 事件再传出去,即为:   // this.$emit('事件名',事件参数) },

7.外界接收事件,正常@xxx='xxx'使用即可

如下:

<my-right-menu   :rightclickInfo="rightclickInfo"   @copy="copy"   @look="look"   @edit="edit"   @delete="deleteFn"   @refresh="refresh" ></my-right-menu>

使用组件

搭配el-table使用

  • el-table中可以使用封装好的事件:@row-contextmenu="xxx"

  • 然后在xxx方法中去传递参数给右键菜单组件即可,如下简化代码:

<el-table   :data="tableData"   @row-contextmenu="rightclick" >   ... </el-table>  <my-right-menu   :rightclickInfo="rightclickInfo"   @copy="copy" ></my-right-menu>  rightclickInfo:{}  // 饿了么UI封装好的右键菜单事件,可直接使用,有行数据,列数据,以及事件 rightclick(row, column, event) {   this.rightclickInfo = {     position: {       x: event.clientX,       y: event.clientY,     },     menulists: [       {         fnName: "copy",         params: { row, column, event },         icoName: "el-icon-document-copy",         btnName: "复制数据",       },     ],   };   event.preventDefault(); // 阻止默认的鼠标右击事件 },

event.preventDefault()要加上,阻止默认的右键菜单事件

搭配普通dom使用

也同理,传参的时,需要阻止默认时间,如下:

<!-- 右键菜单搭配普通的dom元素使用,普通的dom元素需要阻止默认右键事件,即.prevent --> <div class="normalDom" @contextmenu.prevent="onContextmenu">区域内右键</div>  onContextmen(){     // 定义参数传递给my-right-menu组件 }

完整代码

复制粘贴即可使用哦

使用组件代码

<template>   <div>     <h5>表格内右键</h5>     <br />     <!-- 右键菜单搭配el-table使用 -->     <el-table       border       :data="tableData"       style="width: 100%"       @row-contextmenu="rightclick"     >       <el-table-column prop="name" label="姓名"> </el-table-column>       <el-table-column prop="age" label="年龄"> </el-table-column>       <el-table-column prop="home" label="家乡"> </el-table-column>       <el-table-column prop="hobby" label="爱好"> </el-table-column>     </el-table>     <br />     <br />     <br />     <!-- 右键菜单搭配普通的dom元素使用,普通的dom元素需要阻止默认右键事件,即.prevent -->     <div class="normalDom" @contextmenu.prevent="onContextmenu">区域内右键</div>     <!-- 右键菜单 -->     <my-right-menu       :class-index="0"       :rightclickInfo="rightclickInfo"       @copy="copy"       @look="look"       @edit="edit"       @delete="deleteFn"       @refresh="refresh"     ></my-right-menu>   </div> </template>  <script> export default {   name: "myRightMenuName",   data() {     return {       tableData: [         {           id: "1",           name: "孙悟空",           age: 500,           home: "花果山水帘洞",           hobby: "桃子",         },         {           id: "2",           name: "猪八戒",           age: 88,           home: "高老庄",           hobby: "肉包子",         },         {           id: "3",           name: "沙和尚",           age: 500,           home: "通天河",           hobby: "游泳",         },         {           id: "4",           name: "唐僧",           age: 1000,           home: "东土大唐",           hobby: "吃斋念经",         },       ],       rightclickInfo: {},     };   },   methods: {     // 饿了么UI封装好的右键菜单事件,可直接使用     rightclick(row, column, event) {       this.rightclickInfo = {         position: {           x: event.clientX,           y: event.clientY,         },         menulists: [           {             fnName: "copy",             params: { row, column, event },             icoName: "el-icon-document-copy",             btnName: "复制数据",             // divided: true,             // disabled: true,             // children: [],           },           {             fnName: "look",             params: { row, column, event },             icoName: "el-icon-view",             btnName: "查看行数据",           },           {             fnName: "edit",             params: { row, column, event },             icoName: "el-icon-edit",             btnName: "编辑行数据",           },           {             fnName: "delete",             params: { row, column, event },             icoName: "el-icon-delete",             btnName: "删除行数据",           },           {             fnName: "refresh",             params: { row, column, event },             icoName: "el-icon-refresh",             btnName: "刷新页面",           },         ],       };       event.preventDefault(); // 阻止默认的鼠标右击事件     },     copy(params) {       console.log(         "copy",         params.row ? params.row[params.column.property] : params       );     },     look(params) {       console.log("look", params.row ? JSON.stringify(params.row) : params);     },     edit(params) {       console.log("edit", params);     },     deleteFn(params) {       console.log("deleteFn", params.row ? params.row.id : params);     },     refresh(params) {       console.log("refresh 刷新页面啦");     },     // 普通dom右键     onContextmenu(e) {       this.rightclickInfo = {         position: {           x: e.clientX,           y: e.clientY,         },         menulists: [           {             fnName: "copy",             params: "代码修仙",             icoName: "el-icon-star-on",             btnName: "代码修仙",           },           {             fnName: "look",             params: "路漫漫",             icoName: "el-icon-star-off",             btnName: "路漫漫",           },         ],       };     },   }, }; </script>  <style> .normalDom {   width: 240px;   height: 240px;   line-height: 240px;   text-align: center;   border: 6px dotted pink;   font-family: "楷体", Courier, monospace;   font-weight: 600; } </style>

封装组件代码

<template>   <ul class="table-right-menu">     <!-- 循环菜单项,事件带参数抛出 -->     <li       v-for="item in rightclickInfo.menulists"       :key="item.btnName"       class="table-right-menu-item"       @click.stop="fnHandler(item)"     >       <div class="table-right-menu-item-btn">         <!-- 图标和按钮名 -->         <i :class="item.icoName" class="iii" />         <span>{{ item.btnName }}</span>       </div>     </li>   </ul> </template>  <script> export default {   name: "myRightMenu",   props: {     // 接收右键点击的信息     rightclickInfo: {       type: Object,       default: () => {         return {           position: {             // 右键点击的位置             x: null,             y: null,           },           menulists: [             {               fnName: "", // 点击菜单项的事件名               params: {}, // 点击的参数               icoName: "", // 图标名               btnName: "", // 按钮名             },           ],         };       },     },     // 重要参数,用于标识是哪个右键菜单dom元素     classIndex: {       type: Number,       default: 0,     },   },   watch: {     // 监听右键点击时点击位置的变化,只要变化了,就弹出右键菜单供用户点击操作     "rightclickInfo.position"(val) {       let x = val.x; // 获取x轴坐标       let y = val.y; // 获取y轴坐标       let innerWidth = window.innerWidth; // 获取页面可是区域宽度,即页面的宽度       let innerHeight = window.innerHeight; // 获取可视区域高度,即页面的高度       /**        * 注意,这里要使用getElementsByClassName去选中对应dom,因为右键菜单组件可能被多处使用        * classIndex标识就是去找到对应的那个右键菜单组件的,需要加的        * */        let menu =         document.getElementsByClassName("table-right-menu")[this.classIndex];        menu.style.display = "block";       let menuHeight = this.rightclickInfo.menulists.length * 30; // 菜单容器高       let menuWidth = 180; // 菜单容器宽       // 菜单的位置计算       menu.style.top =         (y + menuHeight > innerHeight ? innerHeight - menuHeight : y) + "px";       menu.style.left =         (x + menuWidth > innerWidth ? innerWidth - menuWidth : x) + "px";       // 因为菜单还要关闭,就绑定一个鼠标点击事件,通过e.button判断点击的是否是左键,左键关闭菜单       document.addEventListener("mouseup", this.hide, false);     },   },   methods: {     hide(e) {       if (e.button === 0) {         // 0是左键、1是滚轮按钮或中间按钮(若有)、2鼠标右键         let menu =           document.getElementsByClassName("table-right-menu")[this.classIndex]; // 同样的精确查找         menu.style.display = "none"; // 菜单关闭         document.removeEventListener("mouseup", this.hide); // 及时解绑监听事件       }     },     fnHandler(item) {       this.$emit(item.fnName, item.params);       // 事件再传出去,即为:       // this.$emit('事件名',事件参数)     },   }, }; </script>  <style lang='less' scoped> .table-right-menu {   color: #333;   background: #fff;   border-radius: 4px;   list-style-type: none;   box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);   font-size: 12px;   font-weight: 500;   box-sizing: border-box;   padding: 4px 0;   // 固定定位,抬高层级,初始隐藏,右击时置为display:block显示   position: fixed;   z-index: 3000;   display: none;   .table-right-menu-item {     box-sizing: border-box;     padding: 6px 12px;     border-radius: 4px;     transition: all 0.36s;     cursor: pointer;     .table-right-menu-item-btn {       .iii {         margin-right: 4px;       }     }   }   .table-right-menu-item:hover {     background-color: #ebf5ff;     color: #6bacf2;   } } </style>

本文转载于:

https://juejin.cn/post/7174420692954251272

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 记录--vue中封装一个右键菜单组件(复制粘贴即可使用)