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

组件介绍

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

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

组件效果图

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

组件分析

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

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

每一行都是一个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中封装一个右键菜单组件(复制粘贴即可使用)的更多相关文章

  1. 使用better-scroll在vue中封装自己的Scroll组件

    1. better-scroll 原理 用一张图感受: 绿色部分为 wrapper,也就是父容器,它会有固定的高度.黄色部分为 content,它是父容器的第一个子元素,它的高度会随着内容的大小而撑高 ...

  2. vue中封装一个全局的弹窗js

    /** * Created by yx on 2017/12/21. */ export default { /** * 带按钮的弹框 * <!--自定义提示标题,内容,单个按钮事件--> ...

  3. uniapp中封装一个弹框组件

    第一步:在components下创建 popup.vue子组件: popup.vue中 <template> <view> <view class="popus ...

  4. JavaScript 中禁止用户右键菜单,复制,选取,Ctrl,Alt,Shift. 获取宽高,清除浮动

    //禁用右键菜单 document.oncontextmenu = function(){ event.returnValue = false; } //禁用选取内容 document.onselec ...

  5. vue中封装一个倒计时

    <template> <div class="countDownBox"> <div class="row resetStyle" ...

  6. Vue 2.0 右键菜单组件 Vue Context Menu

    Vue 2.0 右键菜单组件 Vue Context Menu https://juejin.im/entry/5976d14751882507db6e839c

  7. 记录vue中一些有意思的坑

    记录vue中一些有意思的坑 'message' handler took 401ms 在出现这个之前,我一直纠结于 是如何使用vue-router或者不使用它,通过类似的v-if来实现.结果却出现这个 ...

  8. JQuery模拟网页中自定义鼠标右键菜单

    题外话.......最近在开发一个网站项目的时候,需要用到网页自定义右键菜单,在网上看了各路前辈大神的操作,头晕目眩,为了达到目的,突然灵机一动,于是便有了这篇文章. 先放个效果图(沾沾自喜,大神勿喷 ...

  9. qt tableview中如何添加右键菜单且不可编辑单元格

    QTableView是一个比较实用的类,下面教给大家如何在QTableView中添加右键菜单. #include <QMenu>#include <QAction> QTabl ...

  10. 在vue中使用Element的message组件

    在vue中使用Element的message组件 在vue文件中使用 this.$message({ message: "提示信息", type: "success&qu ...

随机推荐

  1. CF1295

    A 用计算器式显示数字,可以显示 \(n\) 段.可以显示的最大数字是多少? 如果用了一个需要至少四段的数字,一定不如把这个替换成两个 \(1\) 好. 如果一共可以用偶数个,一定是全部 \(1\). ...

  2. Nginx+uwsgi+ssl配置https

    Nginx+uwsgi+ssl配置https 使用原始django,太过于笨重和杂多nginx是一个轻量级的web服务器,在处理静态资源和高并发有优势uwsgi是一个基于python的高效率的协议,处 ...

  3. 【Unity3D】场景切换、全屏_恢复切换、退出游戏、截屏

    1 前言 ​ 1)场景切换 ​ 场景切换可以使用 SceneManager 的 LoadScene 和 LoadSceneAsync 方法,如下: public static void LoadSce ...

  4. SpringCloud 注册中心Zookeeper实战

    介绍 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提 ...

  5. PLSQL编译存储过程无响应

    解决方法如下: 1:查V$DB_OBJECT_CACHE SELECT * FROM V$DB_OBJECT_CACHE WHERE name='CRM_LASTCHGINFO_DAY' AND LO ...

  6. Java并发编程实例--7.守护(Damon)线程

    Java有一种特殊线程叫守护(后台)线程. 1.这类线程拥有非常低的优先级且通常只是在没有其他线程运行的情况下执行. 2.其通常作为无线循环服务去执行某项任务. 3.不能让他们去执行重要任务因为你不知 ...

  7. 以二进制文件安装K8S之部署Node服务

    概述 在Node上需要部署Docker.kubelet.kube-proxy,在成功加入Kubernetes集群后,还需要部署CNI网络插件.DNS插件等管理组件. 本节以将192.168.3.138 ...

  8. vue实现导出word文档(含多张图片)

    vue实现导出word文档(含多张图片) 转自: https://www.pudn.com/news/62e1e14e55398e076bea2d2f.html

  9. 推导式,集合推导式,生成器表达式及生成器函数day13

    1.推导式 用一行循环判断遍历处一系列数据的方式 推导式在使用时,只能用for循环和判断,而且判断只能是单项判断 基本语法: lst = [i for i in range(1,51)] print( ...

  10. Kotlin 协程三 —— 数据流 Flow

    目录 一.Flow 的基本使用 1.1 Sequence 与 Flow 1.2 Flow 的简单使用 1.3 创建常规 Flow 的常用方式: 1.4 Flow 是冷流(惰性的) 1.5 Flow 的 ...