技术标签: plugin vue html javascript
自己写的右键菜单组件 可以直接安装 npm i @xxllxx/vue-context-menu
├── src
├── components
└── context-menu
├── index.js // 注册
├── utils.js
├── index.vue // 菜单box
└── item.vue // 菜单项
// src/components/context-menu/index.js
import contextMenu from './index.vue'
import contextMenuItem from './item.vue'
const install = (Vue, config) => {
Vue.component(config.name || contextMenu.name, contextMenu)
Vue.component(config.itemName || contextMenuItem.name, contextMenuItem)
}
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
install,
contextMenu,
contextMenuItem
}
// src/components/context-menu/utils.js
export function getElementOffset(element) {
let offset = {
left: 0, top: 0 }
let current = element.offsetParent
offset.left += element.offsetLeft
offset.top += element.offsetTop
while (current !== null) {
offset.left += current.offsetLeft
offset.top += current.offsetTop
current = current.offsetParent
}
return offset
}
// 清空菜单
export function clearContextMenu() {
if (document.querySelector('.context-menu-box')) {
let element = document.querySelector('.context-menu-box')
element.remove()
}
}
// src/components/context-menu/index.vue
<template>
<div
v-once
style="display: block;z-index: 9999;pointer-events: visible"
class="context-menu-box"
key="context_menu"
>
<ul
ref="menu"
class="list-context-menu-wrapper"
data-hide-modal="0"
style="left: -1000px; top: -1000px; "
>
<slot v-once>
<li style="width:120px" @click="emptyClick" class="empty-text">空菜单</li>
</slot>
</ul>
</div>
</template>
<script>
import {
getElementOffset, clearContextMenu } from './utils'
export default {
name: 'context-menu',
props: {
width: {
type: String,
default: ''
},
offset: {
type: Object,
default: null
},
mode: {
type: String,
default: 'contextmenu'
}
},
data() {
return {
modeList: ['contextmenu', 'click', 'all']
}
},
computed: {
trigger: function() {
if (this.modeList.find(t => t == this.mode)) return this.mode
return 'contextmenu'
}
},
mounted() {
this.$nextTick(() => {
let el = this.$el
let parent = this.$el.parentElement
let menu = this.$refs['menu']
let that = this
let offset = this.offset
if (parent) {
if (this.width) {
menu.style.width = this.width
}
parent.removeChild(el)
parent.addEventListener('click', event => {
if (this.trigger == 'contextmenu') clearContextMenu()
})
// 菜单禁用右键菜单
menu.addEventListener('contextmenu', e => {
// e.stopPropagation()
e.preventDefault()
})
const func = function(event) {
event.preventDefault()
event.stopPropagation()
clearContextMenu()
document.body.insertBefore(el, document.body.firstChild)
let x = 0
let y = 0
if (offset && !isNaN(offset.x) && !isNaN(offset.y)) {
let temp = getElementOffset(parent)
x = temp.left + offset.x
y = temp.top + offset.y
} else {
//获取鼠标视口位置
x = document.body.offsetWidth - menu.offsetWidth <= event.clientX ? event.clientX - menu.offsetWidth : event.clientX
y = 0
if (document.body.offsetHeight - menu.offsetHeight <= event.clientY) y = event.clientY - menu.offsetHeight
else y = event.clientY
if (y < 0 && document.body.offsetHeight > menu.offsetHeight) {
y = (document.body.offsetHeight - menu.offsetHeight) / 2
}
}
menu.style.left = x + 'px'
menu.style.top = y + 'px'
}
// 添加菜单事件
switch (this.trigger) {
case 'contextmenu':
parent.addEventListener('contextmenu', func)
break
case 'click':
parent.addEventListener('click', func)
break
case 'all':
parent.addEventListener('contextmenu', func)
parent.addEventListener('click', func)
break
default:
parent.addEventListener('contextmenu', func)
break
}
//#region 删除菜单
// 其他地方左键时取消已打开的菜单
document.addEventListener(
'click',
e => {
clearContextMenu()
},
true
)
// 其他地方右键时取消已打开的菜单
document.addEventListener(
'contextmenu',
e => {
// 菜单右键不取消菜单
if (!e.path.find(t => t.tagName === 'DIV' && t.className === 'context-menu-box')) clearContextMenu()
},
true
)
//#endregion
}
})
},
methods: {
emptyClick(event) {
event.stopPropagation()
}
}
}
</script>
<style lang="scss">
.context-menu-box {
position: absolute;
user-select: none;
.list-context-menu-wrapper {
position: absolute;
padding: 8px 0;
z-index: 999;
background: #fff;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12);
border-radius: 3px;
border: 1px solid #e4e4e4;
box-sizing: content-box;
margin: 0px;
white-space: nowrap;
li {
list-style: none;
}
.menu-item-space {
height: 40px;
line-height: 40px;
}
.empty-text {
height: 60px;
margin: 0px auto;
padding: 0 24px;
display: flex;
justify-content: center;
align-items: center;
color: rgba(0, 0, 0, 0.4);
}
.menu-item {
&:hover {
background: rgba(0, 0, 0, 0.02);
}
&:active {
background: rgba(0, 0, 0, 0.04);
}
&:focus {
outline: -webkit-focus-ring-color auto 1px;
}
text-align: left;
font-size: 14px;
color: rgba(0, 0, 0, 0.88);
font-weight: normal;
font-style: normal;
font-stretch: normal;
letter-spacing: normal;
display: block;
cursor: pointer;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
padding: 0 24px;
// 加图标默认样式
img,
svg {
display: inline-block;
width: 14px;
height: 14px;
vertical-align: middle;
margin-right: 9px;
}
}
.seperator {
height: 1px;
padding: 8px 0;
&::before {
content: '';
display: block;
height: 1px;
background: rgba(0, 0, 0, 0.04);
}
}
}
}
</style>
// src/components/context-menu/item.vue
<template>
<li
:class="isEmpty? 'menu-item menu-item-space':'seperator'"
:aria-label="label"
tabindex="1"
@click="menuClick"
>
<slot>{
{
label}}</slot>
</li>
</template>
<script>
export default {
name: 'context-menu-item',
props: {
label: {
type: String,
default: ''
}
},
computed: {
isEmpty: function() {
if (this.label || this.$slots.default) return true
return false
}
},
created() {
},
methods: {
menuClick() {
if (this.label) this.$emit('click')
}
}
}
</script>
// src/main.js
import Vue from 'vue'
//...
import context from '@/components/context-menu'
// 全局注册
Vue.use(context)
// 重命名组件名称 默认 context-menu 和 context-menu-item
// Vue.use(context, { name: 'contextMenu', itemName: 'contextMenuItem' })
//...
//...
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
mode | 触发方式 | String | click/contextmenu/all | contextmenu |
width | 菜单宽度 | String | — | 自适应 |
offset | 固定菜单位置,根据父级的偏移量 | Object | {x:120,y:20} | null |
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
lable | 显示文字 | —— | —— | |
@click | 触发事件 | Function | —— | —— |
直接添加到需要右键菜单的元素下
// src/view/home.vue
<template>
<div>
<div class="homeItem">
1
<context-menu width="120px">
<context-menu-item @click="item2">
<!-- 自定义摸版 -->
<img src="/svg/tag.svg" />
标签
</context-menu-item>
<context-menu-item @click="item2">
<img src="/svg/wallet.svg" />
钱包
</context-menu-item >
<context-menu-item @click="item1">
<img src="/svg/package.svg" />
包裹
</context-menu-item >
<context-menu-item @click="item1">
<img src="/svg/logistics.svg" />
物流
</context-menu-item>
</context-menu >
</div>
<div class="homeItem">
2
<context-menu width="120px">
<context-menu-item @click="item2">2</context-menu-item>
</context-menu>
</div>
</div>
</template>
<script>
export default {
name: 'home',
methods: {
item1() {
alert(1)
},
item2() {
alert(2)
},
item3() {
alert(3)
}
}
}
</script>
<style scoped>
.homeItem {
background: aqua;
width: 120px;
margin-top: 20px;
height: 120px;
}
.menu-item img {
margin-top: -1px;
}
</style>
文章浏览阅读8.7k次。剑指offer面试题68 - II. 二叉树的最近公共祖先给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]示例 1:输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = _二叉树的最近公共祖先mianshi
文章浏览阅读617次。充实每天的第3个8小时!原来我之前所见到的变量大部分都是定义在函数内部的本地变量(进入函数它就才出现,出了函数就不存在了)全局变量1、定义在函数外的变量—全局变量1>生存期、作用域为全局!2>所有的函数都可以使用、访问。2、全局变量的初始化:1>没有做初始化的全局变量编译器会自动为它分配0值。(而本地变量是随机的一个值!)2>没有初始化的全局指针会得到NULL值。3>只能使用 编译时的已知值 来初始化全局变量#include <s_宏定义是全局变量吗
文章浏览阅读603次。华为H60-L03 系统4.4.2 app更新时遇见open failed: EACCES (Permission denied) 错误,读写权限都已经注册java.io.FileNotFoundException: /storage/emulated/0/download/-------.apk: open failed: EACCES (Permission de
文章浏览阅读1.5k次。测试用例的目的是要验证一些操作否符合我们的预期结果,所以在测试用例中,断言函数是必不可少的一项。我们做的每一步操作都会有预期的结果,为了保证操作得到的结果符合预期,我们需要在测试用例中添加断言,来保证实际结果和预期结果一致。那么先让我们来认识一些常用的断言函数:1、should be equal 与should not be equal我们在第一行设置一个变量,并赋值1,第二行,意思是${var}应该等于1运行:会发现只是打印出了变量的值,一般来说,断言函数只起断言作用,符合断言没有任何操作,不_断言rf运算
文章浏览阅读644次。关注并星标从此不迷路计算机视觉研究院公众号ID|ComputerVisionGzq学习群|扫码在主页获取加入方式论文地址:https://arxiv.org/pdf/1901.01928v..._卷积核搜索空间减少
文章浏览阅读892次。1. 等值连接中不要求相等属性值的属性名相同,而自然连接要求相等属性值的属性名必须相同,即两关系只有在同名属性才能进行自然连接。如上例R中的C列和S中的D列可进行等值连接,但因为属性名不同,不能进行自然连接。 2. 等值连接不将重复属性去掉,而自然连接去掉重复属性,也可以说,自然连接是去掉重复列的等值连接。如上例R中的B列和S中的B列进行等值连接时,结果有两个重复的属性列B,而进行自然连..._自然连接的属性名可以不同吗
文章浏览阅读182次。调试合约 原文地址truffle整合了debug功能,方便调试合约,跟传统的应用程序的断点调试很相似但是truffle的调试的时机跟传统软件有很大的区别,传统软件的调试是实时的run-time,而truffle的调试是跟合约程序的运行时分离的,你不能够在合约执行的时候调试,也就是说合约的执行不能被调试打断,但是因为合约的transaction操作会返回一个hash值,所以可以根据这个ha..._truffle debug 死循环
文章浏览阅读3.2k次。Access函数Int返回小于或等于指定数值表达式的最大整数。 SQLServer函数FLOOR 返回小于或等于指定数值表达式的最大整数。CEILING 返回大于或等于指定数值表达式的最小整数。项目中常常需要获取经过四舍五入后保留两位小数的数值,根据项目需求或者限制,有时候只能在SQL语句中进行四舍五入。以下是经过整理,在SQL Service 2005/2008中...
文章浏览阅读2k次。前段时间大周测试了一个新号做影视混剪,大概有两个多月了,每天更新一个作品,现在终于开始有收益了,7天有2000多,全是靠的这些剪辑技巧。今天给大家分享一些简单的,坚持做下去你也可以。一、选择平台抖音、快手这一类平台需要积累一定的粉丝量才能接广告、带货、变现等,不推荐新手一开始就主做这种平台,但可以作为一个分发内容的平台。刚开始操作的时候选择一些有播放量就有收益的短视频平台,每天都能看到收益情况,有收益了,自然就有了做下去的动力。头条号、趣头条、百家号、企鹅号、大鱼号等都是开通收益权益后,有播放量就
文章浏览阅读767次。对上一次代码进一步修改,为适应背景修改了部分颜色。'''打地鼠游戏v1.5''''''1. 添加背景图为适应背景图,更改了原来设置的一些颜色的值2. 创建锤子类并实现锤子跟随鼠标功能'''# 导包导库导模块import pygame, sysfrom pygame import *from math import sin, cos, radiansfrom datetime ..._打地鼠pygame background image
文章浏览阅读2.3k次。https://blog.csdn.net/zuochao_2013/article/details/53428837
文章浏览阅读2.1k次。用过企业微信的人应该都知道,如果我们要发送应用消息,首先需要用get方法获取到token,下面是后端发送get请求获取企业微信应用消息的方法:string url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + corpid + "&corpsecret=" + corpsecret;//corpid和corpsecret需要从企业微信后台管理里面拿到,具体方法可以看官方文档 HttpWebReq_后端请求getsection