tangyuxian
文章84
标签38
分类5

文章分类

文章归档

vue-封装函数式触发右键菜单组件

vue-封装函数式触发右键菜单组件

通过使用函数式调用方式去触发右键菜单逻辑

vue文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
<script setup lang="ts" name="FillContextMenu">
import { onClickOutside } from '@vueuse/core'
import {nextTick, onMounted, ref, useTemplateRef} from "vue";
import {ElDropdown,ElDropdownItem,ElDropdownMenu} from "element-plus";

import type {DropdownInstance} from "element-plus";

// prop
const props = withDefaults(defineProps<{
  position: {x:number,y:number},
  index:number,
  value:any,
  obj:any,
  propKey:string,
  length:number
}>(),{
  position:()=>({x:0,y:0}),
  index:()=>0,
  value:"",
  obj:()=>({}),
  propKey:"year_",
  length:0
})

// emits
const emits = defineEmits(['close'])

/**
 * 初始化
 */
const init = () => {
  nextTick(() => {
    handleContextmenu()
  })
}

/**
 * 等值填充
 */
const handleLineFill = () => {
  console.log(props.index,props.value,props.obj,props.propKey,props.length)
  for(let i = props.index; i < props.length; i++) {
    const field = `${props.propKey}${i + 1}`
    props.obj[field] = props.value
  }
  emits('close')
}

// 菜单实例
const fillContextMenuRef = useTemplateRef<HTMLDivElement>("fillContextMenuRef")

/**
 * 点击菜单外部关闭菜单
 */
onClickOutside(fillContextMenuRef, () => {
  emits('close')
})

// 下拉菜单实例
const dropdownRef =  useTemplateRef<DropdownInstance>("dropdownRef")
const position = ref({
  top: 0,
  left: 0,
  bottom: 0,
  right: 0,
} as DOMRect)

// 触发元素
const triggerRef = ref({
  getBoundingClientRect: () => position.value,
})

/**
 * 处理上下文菜单
 */
const handleContextmenu = () => {
  dropdownRef.value?.handleClose()
  const { x, y } = props.position
  position.value = DOMRect.fromRect({
    x: x,
    y: y,
  })
  dropdownRef.value?.handleOpen()
}

onMounted(() => {
  init()
})

</script>

<template>
  <div>
    <ElDropdown
        ref="dropdownRef"
        :virtual-ref="triggerRef"
        :show-arrow="false"
        :popper-options="{
        modifiers: [{ name: 'offset', options: { offset: [0, 0] } }],
    }"
        virtual-triggering
        trigger="contextmenu"
        placement="bottom-start"
    >
      <template #dropdown>
        <ElDropdownMenu>
          <ElDropdownItem @click="handleLineFill">按行等值填充</ElDropdownItem>
        </ElDropdownMenu>
      </template>
    </ElDropdown>
  </div>
</template>

<style scoped lang="scss">
.fill-context-menu {

}
</style>

ts文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import {h, render} from 'vue'
import FillContextMenu from './index.vue'


interface ContextMenuOptions {
    // 上下文菜单位置
    position: { x: number; y: number }
    // 上下文菜单索引
    index: number
    // 上下文菜单值
    value: any
    // 上下文菜单对象
    obj: Record<string, any>
    // 上下文菜单属性名
    propKey: string
    // 上下文菜单长度
    length: number
}

// 添加滚轮事件处理函数
const handleWheel = () => {
    if (currentCloseMenu) {
        currentCloseMenu()
    }
}
// 添加中键点击处理函数
const handleMiddleClick = (event: MouseEvent) => {
    if (event.button === 1 && currentCloseMenu) { // 1 表示鼠标中键
        currentCloseMenu()
    }
}
// 添加全局变量保存当前菜单关闭函数
let currentCloseMenu: (() => void) | null = null

export function showFillContextMenu(options: ContextMenuOptions) {
    // 关闭已存在的菜单
    if (currentCloseMenu) {
        currentCloseMenu()
        currentCloseMenu = null
    }

    const container = document.createElement('div')

    /**
     * 关闭上下文菜单
     */
    const closeMenu = () => {
        render(null, container)
        container.remove()
        currentCloseMenu = null // 清除引用
        // 移除事件监听
        document.removeEventListener('wheel', handleWheel)
        document.removeEventListener('mousedown', handleMiddleClick) // 新增移除监听
    }
    currentCloseMenu = closeMenu // 保存当前实例的关闭方法
    // 添加滚轮事件监听
    document.addEventListener('wheel', handleWheel, {passive: true})
    document.addEventListener('mousedown', handleMiddleClick) // 新增中键监听
    const vnode = h(FillContextMenu, {
        ...options,
        onClose: closeMenu
    })

    document.body.appendChild(container)
    render(vnode, container)

    return closeMenu
}
本文作者:tangyuxian
本文链接:https://www.tangyuxian.com/2025/09/23/%E5%89%8D%E7%AB%AF/vue/vue-%E5%B0%81%E8%A3%85%E5%87%BD%E6%95%B0%E5%BC%8F%E8%A7%A6%E5%8F%91%E5%8F%B3%E9%94%AE%E8%8F%9C%E5%8D%95/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可