先上图

接着上代码,两个文件,一个js文件,一个vue文件

1.rightContext.js


import rightMenu from "./rightMenu.vue"const install = function (Vue) {let oldInstance = null;const rightCxtCtr = Vue.extend(rightMenu)/*** @param options.menu 右键的菜单选项* @param options.event 右键点击事件* @param options.posX 如果不传event对象,则需要指定X坐标* @param options.posY 如果不传event对象,则需要指定Y坐标* @param options.canClickFather 点击父级节点是否也触发事件* @param options.canHoverOut 鼠标是否可以悬浮在ctxmenu之外* @param options.backgroundColor 背景色* @param options.activeColor 弹窗悬浮颜色* @param options.nowBoxWidth 顶级弹窗的宽度*/const rightCxtFn = function (options) {return new Promise((resolve, reject) => {const instance = new rightCxtCtr()instance.menu = options.menuinstance.canClickFather = options.canClickFather; // 点击父级节点是否也触发事件instance.canHoverOut = options.canHoverOut; // 鼠标是否可以悬浮在ctxmenu之外instance.backgroundColor = options.backgroundColor || "#fff"; // 弹窗背景色instance.activeColor = options.activeColor || "#f2f2f2"; // 弹窗悬浮颜色// 处理X, Y坐标instance.posX = (options.event && options.event.clientX) || options.posX || 0;instance.absoluteX = instance.posX;instance.posY = (options.event && options.event.clientY) || options.posY || 0;instance.nowBoxWidth = options.nowBoxWidth || 200; // 当前节点的宽度,不写则为200instance.needReverse = instance.posX + instance.nowBoxWidth > document.body.clientWidth - 5if (instance.needReverse) {instance.posX = instance.posX - instance.nowBoxWidth}oldInstance = instanceinstance.topEmitClick = (data) => {resolve(data)}instance.topEmitCancel = (data) => {reject(data)}rightCxtFn.destroy()instance.$mount()document.body.appendChild(instance.$el)})}rightCxtFn.destroy = () => {if (oldInstance) {oldInstance.$destroy()if (oldInstance.$el && oldInstance.$el.className) {const oldClassName = `.${oldInstance.$el.className.replace(" ", ".")}`if (document.querySelectorAll(oldClassName).length) {document.body.removeChild(oldInstance.$el)}}oldInstance = null}}Vue.prototype.$rightCtx = rightCxtFn
}export default {install
}

2.rightMenu.vue

<template><transition :name="isTop?'fade':'leftin'"><ulclass="ctx-menu-out":style="compstyle + `width: ${isTop?nowBoxWidth:''}px;`":class="{'top': isTop}"v-if="!isDestoryed"@contextmenu="handleContextmenu"ref="menuout"><!-- 每个右侧菜单的选项 --><liclass="ctx-menu-item":class="{'active': activeIndex === item.key}":style="activeIndex === item.key?`background-color: ${activeColor};`:''"v-for="(item, index) in menu":key="item.key"@mouseover="($event)=>handleMouseover($event, item, index)"@click="($event)=>handleClick(item, $event)"><img :src="item.img" alt="logo" class="itemlogo" v-if="hasImg" :style="`visibility: ${item.img?'':'hidden'}`" /><!-- <span class="logobox"></span> --><span class="labelbox">{{item.label}}</span><template v-if="item.children"><!-- 是否有子菜单 --><svgclass="svgright"viewBox="0 0 1024 1024"version="1.1"xmlns="http://www.w3.org/2000/svg"p-id="17235"xmlns:xlink="http://www.w3.org/1999/xlink"width="24"height="24"><pathd="M332.032 171.008a28.8 28.8 0 0 0 8.96 20.992l312 320-312 320a28.8 28.8 0 0 0-8.96 20.992 28.8 28.8 0 0 0 8.96 20.992 28.8 28.8 0 0 0 20.992 8.96 30.592 30.592 0 0 0 20.992-8l331.008-340.992a29.952 29.952 0 0 0 8.96-22.016 29.952 29.952 0 0 0-8.96-22.016L382.976 148.928a30.592 30.592 0 0 0-20.992-8 30.592 30.592 0 0 0-20.992 8v0.064a32.256 32.256 0 0 0-8.96 22.016z"fill="#ccc"p-id="17236"></path></svg><!-- 递归本组件,产生新的子菜单,并传入新的参数(menu,posX,posY,isTop) --><rightMenu:menu="item.children":posX="sonPosX":posY="sonPosY":isTop="false":nowBoxWidth="ulWidth":needReverse="sonNeedReverse":canClickFather="canClickFather":backgroundColor="backgroundColor":activeColor="activeColor":absoluteX="sonAbsoluteX":showFlag="activeIndex === item.key"v-show="activeIndex === item.key"@emitClick="handleClick"ref="sonmenu"/></template></li></ul></transition>
</template>
<script>
const isString = str => Object.prototype.toString.call(str) === "[object String]"
// 获取隐藏元素的宽高
const getHiddenEl = el => {let hiddenWidth = 0, hiddenHeight = 0;if (el.style.display === "none") {const baseTransform = el.style.transformel.style.transform = "translateY(-1000000px)";el.style.display = "block";hiddenWidth = el.clientWidthhiddenHeight = el.clientHeightel.style.transform = baseTransform;el.style.display = "none"} else {hiddenWidth = el.clientWidthhiddenHeight = el.clientHeight}return {hiddenWidth,hiddenHeight}
}
export default {name: "rightMenu",props: {// 配置项menu: { // 本节点的菜单选项type: Array,default: () => { }},canClickFather: { // 点击父级节点是否也触发事件type: Boolean,default: false},canHoverOut: { // 是否能悬浮在ctxmenu之外type: Boolean,default: false},backgroundColor: {type: String,default: ""},activeColor: {type: String,default: ""},isTop: { // 是否是最顶层的那个组件,顶层组件需要特殊处理type: Boolean,default: true},posX: {type: Number,default: 0},posY: {type: Number,default: 0},nowBoxWidth: { // 当前ul的宽度, 用于计算子节点的X坐标type: Number,default: 0},absoluteX: { // 当前ul的绝对坐标type: Number,default: 0},needReverse: { // 是否需要翻转,窗口右侧点击的时候需要type: Boolean,default: false},showFlag: { // 显示隐藏切换,控制子节点的activeindextype: Boolean,default: false},},data () {return {activeIndex: null,sonPosX: 0, // 相对于父节点的x坐标sonPosY: 0,eventListening: false,isDestoryed: false,ulWidth: 0,sonNeedReverse: false,sonAbsoluteX: 0,hasImg: false, // 当前列表有没有图标}},watch: {showFlag () { // 每次切换显示隐藏之后清空子项的active项this.activeIndex = null}},methods: {handleContextmenu (event) {event.preventDefault()},handleMouseover (event, item, index) {event.stopPropagation()this.activeIndex = item.key// 如果有子项,就获取子项的位置信息if (item.children && item.children.length) {let liNode = this.findInCtxMenu(event.target, "LI").ellet ulNode = nullif (event.target.nodeName === "UL" && !this.isTop) {ulNode = event.target} else {ulNode = this.findInCtxMenu(liNode, "UL").el}this.ulWidth = ulNode.clientWidth; // 当前UL的宽度if (liNode.lastChild && liNode.lastChild.nodeName === "UL") {const { hiddenWidth } = getHiddenEl(liNode.lastChild);if (this.absoluteX + this.ulWidth + hiddenWidth > document.body.clientWidth - 5) {this.sonNeedReverse = true} else {if (ulNode === this.$el) {this.sonPosX = this.ulWidth - 5}}}this.sonAbsoluteX = this.absoluteX + this.ulWidth; // 子节点的绝对坐标this.sonPosY = index * 32 + 5 // 如果换行这个就需要特殊处理}},handleClick (item, event) {if (event) {event.stopPropagation()}if (this.canClickFather|| (!this.canClickFather && !item.children)) {if (this.isTop) { // 顶层节点在实例化的时候定义了这个方法this.topEmitClick(item, event)this.destoryCom(event, true)} else {this.$emit("emitClick", item, event)}}},findInCtxMenu (el, tarNode) { // 判断一个元素是否是在ctx菜单里面, tarNode表示目标,为LI或者ULlet flag = falselet tempEl = el // 最近一个父级节点while (tempEl && !flag) {if (tempEl.className&& isString(tempEl.className)&& tempEl.className.indexOf("ctx-menu-") !== -1&& (!tarNode || tarNode === tempEl.nodeName)) {flag = true} else {tempEl = tempEl.parentNode}}return {flag,el: tempEl}},mouseWheelEvent (event) {this.destoryCom(event)},mouseClickEvent (event) {let { flag } = this.findInCtxMenu(event.target)if (!flag) {this.destoryCom(event)}},mouseMoveEvent (event) {let { flag } = this.findInCtxMenu(event.target)if (!flag) {this.activeIndex = null}},addEvent () {// 只绑定一次点击事件if (!this.eventListening && this.isTop) {document.addEventListener("click", this.mouseClickEvent);document.addEventListener("mousedown", this.mouseClickEvent);document.addEventListener("mousewheel", this.mouseWheelEvent);if (!this.canHoverOut) {document.addEventListener("mouseover", this.mouseMoveEvent);}this.eventListening = true;}},removeEvent () {document.removeEventListener("click", this.mouseClickEvent);document.removeEventListener("mousedown", this.mouseClickEvent);document.removeEventListener("mousewheel", this.mouseWheelEvent);document.addEventListener("mouseover", this.mouseMoveEvent);this.eventListening = false;},destoryCom (event, noEmit) {if (!noEmit) {this.topEmitCancel(event)}this.removeEvent()this.isDestoryed = true}},mounted () {this.addEvent()let hasImg = falsefor (let i = 0; i < this.menu.length; i++) {if (this.menu[i].img) {hasImg = true;break}}this.hasImg = hasImg},computed: {compstyle () {if (this.needReverse && !this.isTop) {return `background-color: ${this.backgroundColor};top: ${this.posY}px;right: 5px;`}return `background-color: ${this.backgroundColor};top: ${this.posY}px;left: ${this.posX}px;`}}
}
</script>
<style scoped>
ul,
li {list-style: none;padding: 0;margin: 0;
}
.ctx-menu-out {display: flex;flex-direction: column;background-color: rgba(255, 255, 255, 0.9);border-radius: 5px;position: absolute;padding: 5px;width: fit-content;
}
.ctx-menu-out:not(.top) {max-width: 160px;
}
.ctx-menu-out.top {position: fixed;z-index: 1000000;
}
.ctx-menu-out.top .ctx-menu-item {width: 100%;
}
.ctx-menu-out .ctx-menu-item {cursor: pointer;min-height: 32px;line-height: 32px;border-radius: 3px;padding-left: 5px;display: flex;align-items: center;width: fit-content;min-width: 80px;
}
.ctx-menu-out .ctx-menu-item.active {background-color: #f5f5f5;
}
.ctx-menu-out .ctx-menu-item .labelbox {flex-grow: 1;font-size: 14px;
}
.ctx-menu-out .ctx-menu-item .svgright {flex-shrink: 0;
}
.itemlogo{width: 24px;height: 24px;margin-right: 5px;}.fade-enter-active,
.fade-leave-active {transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {opacity: 0;
}
.leftin-enter-active {transition: transform ease-out 0.3s;
}
.leftin-leave-active {display: none;
}
.leftin-enter {transform: translateX(-20px);
}
</style>

3.接着注册一下

4.使用

<template><div id="app"><p @contextmenu.prevent="handleContextMenu">我是一段测试的文本</p></div>
</template><script>
export default {name: 'App',methods: {handleContextMenu (event) {const menu = [{label: "添加",key: "add",children: [{label: "在上方添加",key: "addbefore"},{label: "在下方添加",key: "addafter"},{label: "下级添加",key: "addin"},]},{label: "移动",key: "move",children: [{label: "上移",key: "moveup",},{label: "下移",key: "movedown",},{label: "降级",key: "minuslevel"},{label: "升级",key: "uplevel"},]},{label: "删除",key: "delete"},{label: "重命名",key: "rename"},]const options = {menu,event}this.$rightCtx(options).then(res => {console.log("成功", res);}).catch(err => {console.log("失败", err);})},}
}
</script>

github地址

https://github.com/bigbanner/rightContext

你可以下载下来,放入自己的项目,根据实际情况进行修改,喜欢给个星

vue 右键菜单功能相关推荐

  1. vue 右键菜单插件 简单、可扩展、样式自定义的右键菜单

    今天分享的不是技术,今天给大家分享个插件,针对现有的vue右键菜单插件,大多数都是需要使用插件本身自定义的标签,很多地方不方便,可扩展性也很低,所以我决定写了一款自定义指令调用右键菜单(vuerigh ...

  2. html 元素允许右键,JavaScript 自定义html元素鼠标右键菜单功能

    自定义html元素鼠标右键菜单 实现思路 在触发contextmenu事件时,取消默认行为(也就是阻止浏览器显示自带的菜单),获取右键事件对象,来确定鼠标的点击位置,作为显示菜单的left和top值 ...

  3. html模拟右键系统菜单,HTML中自定义右键菜单功能

    我们使用的应用系统很多都有右键菜单功能.但是在网页上面,点击右键一般显示的却是IE默认的右键菜单,那么我们如何实现自己的右键菜单呢?下面将讲解右键菜单功能的实现原理和实现代码. 实现原理 在HTML语 ...

  4. html中自定义右键菜单功能,HTML中自定义右键菜单功能

    我们使用的应用系统很多都有右键菜单功能.但是在网页上面,点击右键一般显示的却是IE默认的右键菜单,那么我们如何实现自己的右键菜单呢?下面将讲解右键菜单功能的实现原理和实现代码. 实现原理 在HTML语 ...

  5. 动手做一个 vue 右键菜单

    有一个vue的右键菜单的需求,先上网查了一下是否有插件,比如下面这个 1分钟Vue实现右键菜单 https://www.jb51.net/article/226761.htm 一顿操作之后,页面白屏, ...

  6. treelist右键菜单功能contextMenuStrip

    1.新建表 2.新建Map表 3.新建SQL中的表 选择右键菜单控件 1.添加项 2.在DroDownItem(集合)中添加项 3.Classify.DropDown.Items.Add(); 添加显 ...

  7. Win11砍掉任务栏文件拖放、右键菜单功能

    本文转载自IT之家 IT之家 7 月 22 日消息 Windows 11 正式版预计将在 10 月推出,微软已经透露了新操作系统中被删除的一些功能.一些关键功能,如时间轴,将被删除或替换,以减少杂乱, ...

  8. 给QTreeWidget添加右键菜单功能

    第一种方法: 1.在包含有QTreeWidget的窗体中添加customContextMenuRequested的信号处理,同时要添加setContextMenuPolicy(Qt::CustomCo ...

  9. TreeList右键菜单功能

    1:常用属性 1): Appearance->FocusedRow-->BackColor  设置选中的节点颜色 2):LookAndFell-->  将树的样式设置为加减号的样式, ...

最新文章

  1. Bug tracker .net 部署经验(完善中)
  2. Linux系统中的Page cache和Buffer cache
  3. [SLAM] a bite of SLAM
  4. 关于python中的self,ins , cls的解释
  5. information_schema.triggers 学习
  6. 周五话营销 | 健身房花式卖卡,诠释点击营销流
  7. SAP CRM呼叫中心里confirm按钮的实现逻辑
  8. 【转】JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解
  9. linux服务器搭建_Linux下搭建FTP服务器笔记
  10. [python + debug] set()操作对象的元素为字符串,则结果随机排序,使用sorted()函数以固定顺序
  11. 升级总代分享思路_旧笔记本光驱换SSD,升级内存,改造散热还能再战5年
  12. 台风怎么看内存颗粒_RGB灯效内存条怎么选,看老胡推荐
  13. 我是如何同时在字节和微信成功拿到实习转正offer!
  14. 点击换图 秀米的svg_时隔五年再用秀米,我发现了这个超强玩法。
  15. HHUOJ 1389 出栈合法性
  16. 基于阿里云的API简介
  17. 计算机论文的参考文献,应该怎么引用? - 易智编译EaseEditing
  18. android+动画打包命令,Android 开机动画客制化
  19. 【DCDC转换器】BUCK电路的演进
  20. Docker安装halo

热门文章

  1. xss challenges闯关详细(6-10)
  2. 施一公:我对科研的体验
  3. win10打印机共享错误0x0000011b
  4. Matlab:无穷和 NaN
  5. ActiveX控件:设置控件属性和方法的一种简易办法(VS2013)
  6. QT编译提示crashed的错误提示
  7. 正版授权|Charles 4 网络封包分析调试工具软件
  8. 转载 各大流行linux版本评价
  9. PostgreSQL创建表及约束
  10. streamlit基本使用