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

组件介绍

关于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. 借助Rich库实现Pandas DataFrame颜值升级

    pandas的DataFrame功能强大自不必说,它可以帮助我们极大的提高统计分析的效率. 不过,使用DataFrame开发我们的分析程序的时候,经常需要打印出DataFrame的内容,以验证和调试数 ...

  2. 初次尝试GPU Driven —— 大范围植被渲染

    初次尝试GPU Driven -- 大范围植被渲染 GPU Driver简单概要,即把整体逻辑放到GPU上运行,解放CPU压榨GPU,初次尝试,记录一下研究过程. 渡神纪 塞尔达 塞尔达 塞尔达 在开 ...

  3. NC20139 [JLOI2014]松鼠的新家

    题目链接 题目 题目描述 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他居然真的住在"树& ...

  4. NC24949 [USACO 2008 Jan S]Running

    题目链接 题目 题目描述 The cows are trying to become better athletes, so Bessie is running on a track for exac ...

  5. NVME(学习杂谈)—Asynchronous Event

    Asynchronous Event Request Host Software Recommendations 当一个异步事件请求完成(提供Event Type,Event Information, ...

  6. 从零开始手写 mybatis(二)mybatis interceptor 插件机制详解

    前景回顾 第一节 从零开始手写 mybatis(一)MVP 版本 中我们实现了一个最基本的可以运行的 mybatis. 常言道,万事开头难,然后中间难. mybatis 的插件机制是 mybatis ...

  7. Wireguard笔记(二) 命令行操作

    目录 Wireguard笔记(一) 节点安装配置和参数说明 Wireguard笔记(二) 命令行操作 Wireguard笔记(三) lan-to-lan子网穿透和多网段并存 命令行操作 创建wg0网卡 ...

  8. 【Unity3D】角色控制器(CharacterController)

    1 简介 ​ 控制角色移动的组件主要有:Transform 组件.Rigidbody 组件.CharacterController 组件.Transform 组件通过控制角色位置实现移动,Rogidb ...

  9. 【Unity3D】MonoBehaviour的生命周期

    1 前言 ​ Unity3D 中可以给每个游戏对象添加脚本,这些脚本必须继承 MonoBehaviour,用户可以根据需要重写 MonoBehaviour 的部分生命周期函数,这些生命周期函数由系统自 ...

  10. Python之密码生成器

    介绍 这段程序用来随机批量生成一批安全性相对较高的密码,要了解你当前使用的密码强度到底如何? 可以试一下这个网站: https://howsecureismypassword.net/ 他会告诉你计算 ...