Giter Club home page Giter Club logo

vue3-menus's Introduction

vue3-menus

Vue3.0 自定义右键菜单,支持 Vite2.0,官网

Vue3.0 原生实现完全自定义右键菜单组件, 零依赖,可根据可视区域自动调节显示位置,可支持插槽完全重写每一项菜单

演示

在线演示

快速安装

npm 安装

npm install vue3-menus

yarn add vue3-menus

CDN

<script src="https://unpkg.com/vue3-menus/dist/vue3-menus.umd.min.js">

使用(Vite 情况下同样使用)

CDN引入则不需要 app.use(Vue3Menus)

样例中使用的是@ant-design/icons-vue图标与@element-plus/icons图标、图标可以使用html代码传入、也可以通过插槽自定义图标、也可以完全重写每一项菜单

// 全局注册组件、指令、方法
import { createApp } from 'vue';
import Menus from 'vue3-menus';
import App from './App.vue';
const app = createApp(App);
app.use(Menus);
app.mount('#app');
// 单个注册某个,以下三种方式均可在单个文件内使用
import { createApp } from 'vue';
import { directive, menusEvent, Vue3Menus } from 'vue3-menus';
import App from './App.vue';
const app = createApp(App);
app.component('vue3-menus', Vue3Menus); // 只注册组件
app.directive('menus', directive); // 只注册指令
app.config.globalProperties.$menusEvent = menusEvent; // 只绑定方法
app.mount('#app');
<template>
  <div style="height: 98vh; width: 100%;" v-menus:left="menus">
    <div class="div" v-menus:left="menus">指令方式打开菜单</div>
    <div class="div" @click.stop @contextmenu="($event) => $menusEvent($event, menus)">事件方式打开菜单</div>
    <div class="div" @click.stop @contextmenu="rightClick">组件方式打开菜单</div>
    <vue3-menus :open="isOpen" :event="eventVal" :menus="menus.menus">
      <template #icon="{menu, activeIndex, index}">{{activeIndex}}</template>
      <template #label="{ menu, activeIndex, index }">插槽:{{ menu.label }}</template>
    </vue3-menus>
  </div>
</template>
<script>
import { defineComponent, nextTick, ref, shallowRef } from "vue";
import { SyncOutlined, WindowsOutlined, QrcodeOutlined } from '@ant-design/icons-vue';
import { Printer } from '@element-plus/icons'

export default defineComponent({
  name: "App",
  setup() {
    const isOpen = ref(false);
    const eventVal = ref({});
    function rightClick(event) {
      isOpen.value = false;
      nextTick(() => {
        eventVal.value = event;
        isOpen.value = true;
      })
      event.preventDefault();
    }
    const menus = shallowRef({
      menus: [
        {
          label: "返回(B)",
          tip: 'Alt+向左箭头',
          click: () => {
            window.history.back(-1);
          }
        },
        {
          label: "点击不关闭菜单",
          tip: '不关闭菜单',
          click: () => {
            return false;
          }
        },
        {
          label: "前进(F)",
          tip: 'Alt+向右箭头',
          disabled: true
        },
        {
          label: "重新加载(R)",
          tip: 'Ctrl+R',
          click: () => location.reload(),
          divided: true
        },
        {
          label: "另存为(A)...",
          tip: 'Ctrl+S'
        },
        {
          label: "打印(P)...",
          tip: 'Ctrl+P',
          click: () => window.print(),
        },
        {
          label: "投射(C)...",
          divided: true
        },
        {
          label: '发送到你的设备',
          children: [
            {
              label: 'iPhone',
            },
            {
              label: 'iPad'
            },
            {
              label: 'Windows 11'
            }
          ]
        },
        {
          label: "为此页面创建二维码",
          divided: true,
        },
        {
          label: "使用网页翻译(F)",
          divided: true,
          children: [
            { label: "翻译成繁体中文" },
            { label: "翻译成繁体中文" },
            {
              label: "百度翻译", children: [
                { label: "翻译成繁体中文" },
                { label: "翻译成繁体中文" },]
            },
            {
              label: "搜狗翻译", children: [
                { label: "翻译成繁体中文" },
                { label: "翻译成繁体中文" },
              ]
            },
            {
              label: "有道翻译", children: [
                { label: "翻译成繁体中文" },
                { label: "翻译成繁体中文" },
              ]
            },
          ]
        },
        {
          label: "截取网页(R)"
        },
        { label: "查看网页源代码(U)", tip: 'Ctrl+U' },
        { label: "检查(N)", tip: 'Ctrl+Shift+I' }
      ]
    })
    return { menus, isOpen, rightClick, eventVal }
  },
});
</script>
.div {
  display: inline-block;
  background-color: aqua;
  margin: 0 20px;
  line-height: 200px;
  padding: 0 20px;
  height: 200px;
}

指令方式使用

<template>
  <div v-menus:left="menus">指令方式打开菜单</div>
</template>
<script>
import { defineComponent, shallowRef } from "vue";
import { directive } from 'vue3-menus';

export default defineComponent({
  name: "App",
  directives: {
    menus: directive
  },
  setup() {
    const menus = shallowRef({
      menus: [
        {
          label: "返回(B)",
          tip: 'Alt+向左箭头',
          click: () => {
            window.history.back(-1);
          }
        },
        {
          label: "点击不关闭菜单",
          tip: '不关闭菜单',
          click: () => {
            return false;
          }
        }
      ]
    })
    return { menus }
  },
});
</script>

方法方式使用

<template>
  <div class="div" @click.stop @contextmenu="rightClick">事件方式打开菜单</div>
</template>
<script>
import { defineComponent, shallowRef } from "vue";
import { menusEvent } from 'vue3-menus';

export default defineComponent({
  name: "App",
  setup() {
    const menus = shallowRef({
      menus: [
        {
          label: "返回(B)",
          tip: 'Alt+向左箭头',
          click: () => {
            window.history.back(-1);
          }
        },
        {
          label: "点击不关闭菜单",
          tip: '不关闭菜单',
          click: () => {
            return false;
          }
        }
      ]
    });
    function rightClick(event) {
      menusEvent(event, menus.value);
      event.preventDefault();
    }
    return { rightClick }
  },
});
</script>

组件方式使用

<template>
  <div class="div" @click.stop @contextmenu="rightClick">组件方式打开菜单</div>
  <vue3-menus v-model:open="isOpen" :event="eventVal" :menus="menus" hasIcon>
    <template #icon="{menu, activeIndex, index}">{{activeIndex}}</template>
    <template #label="{ menu, activeIndex, index}">插槽:{{ menu.label }}</template>
  </vue3-menus>
</template>
<script>
import { defineComponent, nextTick, ref, shallowRef } from "vue";
import { Vue3Menus } from 'vue3-menus';

export default defineComponent({
  name: "App",
  components: {
    Vue3Menus
  },
  setup() {
    const isOpen = ref(false);
    const eventVal = ref({});
    function rightClick(event) {
      isOpen.value = false;
      nextTick(() => {
        eventVal.value = event;
        isOpen.value = true;
      })
      event.preventDefault();
    }
    const menus = shallowRef([
      {
        label: "返回(B)",
        tip: 'Alt+向左箭头',
        click: () => {
          window.history.back(-1);
        }
      },
      {
        label: "点击不关闭菜单",
        tip: '不关闭菜单',
        click: () => {
          return false;
        }
      }
    ]);
    return { menus, isOpen, rightClick, eventVal }
  },
});
</script>

参数说明

单个菜单项参数MenusItemOptions

属性 描述 类型 是否必填 默认值
key 菜单项键值 string false
label 菜单项名称 string | (key?: string)=>string true
style 每一项菜单的自定义样式 object false {}
icon 图标参数,内部支持html字符串图标,传入组件时需要实现icon插槽 string|(key?: string)=>string | 其他类型 false undefined
disabled 是否禁用菜单项 boolean | (key?: string)=>boolean false undefined
divided 是否显示分割线 boolean | (key?: string)=>boolean false undefined
tip 没项菜单后面的小提示 string | (key?: string)=>string false ''
hidden 是否隐藏该项 boolean | (key?: string)=>boolean false undefined
children 子菜单列表信息 MenusItemOptions[] false undefined
enter 菜单项移入事件,返回nullfalse不展开子菜单 Function() false undefined
click 菜单项点击事件,返回nullfalse不关闭菜单 Function() false undefined

指令与方法使用参数

属性 描述 类型 是否必填 默认值
menus 菜单列表信息 MenusItemOptions[] true []
menusClass 菜单外层 divclass string false null
itemClass 菜单每一项的class string false null
minWidth 菜单容器最小宽度 number | string false none
maxWidth 菜单容器最打宽度 number | string false none
zIndex 菜单层级 number | string false 3
direction 菜单打开方向 left | right false right

组件使用参数

属性 描述 类型 是否必填 默认值 插槽传入值
menus 菜单列表信息 MenusItemOptions[] true []
event 鼠标事件信息(指令使用时不传) Event position必填一项 {}
menusClass 菜单外层 divclass string false null
itemClass 菜单每一项的class string false null
minWidth 菜单容器最小宽度 number | string false none
maxWidth 菜单容器最打宽度 number | string false none
zIndex 菜单层级 number | string false 3
direction 菜单打开方向 left | right false right
open 控制菜单组件显示 boolean true false
args 附加参数 unknown false undefined
default 默认插槽 Slot false - activeIndex: 当前选中索引, menu: 当前菜单项 MenusItemOptions, index: 当前菜单索引
icon 图标插槽 Slot false - activeIndex: 当前选中索引, menu: 当前菜单项 MenusItemOptions, index: 当前菜单索引
label 菜单标题插槽 Slot false - activeIndex: 当前选中索引, menu: 当前菜单项 MenusItemOptions, index: 当前菜单索引
suffix 菜单后缀插槽 Slot false - activeIndex: 当前选中索引, menu: 当前菜单项 MenusItemOptions, index: 当前菜单索引

指令使用配置

配置参数与方法使用相同

指令使用方式 描述 参数类型 参数是否必填 默认值
v-menus 绑定元素右击打开菜单 MenuOptions true -
v-menus:all 绑定元素左右击均可打开菜单 MenuOptions true -
v-menus:left 绑定元素左击打开 MenuOptions true -
v-menus:right 绑定元素右击打开 MenuOptions true -

vue3-menus's People

Contributors

jitlee avatar xfy520 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

vue3-menus's Issues

显示问题

当右键滑动到二级或者三级菜单(子菜单)时,再去鼠标右键开启一个新事件,上一次事件的子菜单不会关闭

Vue3Menus.vue文件中,watch props.open逻辑有问题

watch(() => props.open, (newVal) => {
if (newVal) {
if (lastInstance) {
lastInstance.close()
lastInstance = null
}
const node = h(Menus, {
...props
}, slots)
const app = createApp(node)
lastInstance = app.mount(document.createElement('div'))
lastInstance.$unmount = app.unmount
setTimeout(() => {
document.addEventListener('click', mouseEvent)
document.addEventListener('contextmenu', mouseEvent)
document.addEventListener('wheel', mouseEvent)
}, 0)
} else {
if (lastInstance) {
lastInstance.close()
lastInstance = null
}
document.removeEventListener('click', mouseEvent)
document.removeEventListener('contextmenu', mouseEvent)
document.removeEventListener('wheel', mouseEvent)
}
})

当 newVal为false时,需要判断当前lastInstance是否为null,如果不为null,应执行close方法

watch(() => props.open, (newVal) => {
if (newVal) {
if (lastInstance) {
lastInstance.close()
lastInstance = null
}
const node = h(Menus, {
...props
}, slots)
const app = createApp(node)
lastInstance = app.mount(document.createElement('div'))
lastInstance.$unmount = app.unmount
setTimeout(() => {
document.addEventListener('click', mouseEvent)
document.addEventListener('contextmenu', mouseEvent)
document.addEventListener('wheel', mouseEvent)
}, 0)
} else {
if (lastInstance) {
lastInstance.close()
lastInstance = null
}
document.removeEventListener('click', mouseEvent)
document.removeEventListener('contextmenu', mouseEvent)
document.removeEventListener('wheel', mouseEvent)
}
})

重合时只有外层menu能用?

我刚接触前端不久, 问个问题。

有2个div, A和B. A比较大,A包含B, 宽度都是相等的,假如A的高度有100,B有50. 现在有个鼠标右击事件打开菜单,我希望在右击A和B重合的地方显示B的菜单,其他空白的地方显示A的菜单,这个怎么实现?
<div class="file-items" @click.right="($event) => menusEvent($event, spaceMenus)"> <p v-for="item in fileListData" @click.right="($event) => menusEvent($event, fileItemMenus, item)">item.....p div
我发现这样写,它只会显示出外层的spaceMenus菜单,内层的fileItemMenus菜单无法显示。

在electron下,build的代码,click 中参数有问题

menus: [
{
label: '转发',
tip: '',
click: (menu, arg: { data: string }) => {
let s = msgList.value.findIndex(r=> {return Number(r.msgId) === Number(arg.data);});
console.log(menu, arg, msgList.value);
if (s>-1) {
$emit('forwardMsg', [msgList.value[s]]);
} else {
ElMessageFun({ type: 'error', cont: '转发错误~' });
}
},
},
],

arg 参数是空数据

国际化好像不能动态切换语言

菜单国际化是这么写的
import { useI18n } from "vue-i18n";
let i18n=useI18n()

const menuse = [
{
label: i18n.t('msg.tagsView.refresh'),
click: () => {
window.location.reload()
}
},
}
必须要刷新页面才能切换语言

指令参数

如果使用指令的形式怎么自定义右侧的小箭头

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.