给消息添加右键菜单

This commit is contained in:
bob
2025-04-27 16:19:24 +08:00
parent bfecd911fe
commit f8a949b4a8
10 changed files with 195 additions and 3 deletions

1
src/assets/svg/copy.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1745740018080" class="svg-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3474" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M394.666667 106.666667h448a74.666667 74.666667 0 0 1 74.666666 74.666666v448a74.666667 74.666667 0 0 1-74.666666 74.666667H394.666667a74.666667 74.666667 0 0 1-74.666667-74.666667V181.333333a74.666667 74.666667 0 0 1 74.666667-74.666666z m0 64a10.666667 10.666667 0 0 0-10.666667 10.666666v448a10.666667 10.666667 0 0 0 10.666667 10.666667h448a10.666667 10.666667 0 0 0 10.666666-10.666667V181.333333a10.666667 10.666667 0 0 0-10.666666-10.666666H394.666667z m245.333333 597.333333a32 32 0 0 1 64 0v74.666667a74.666667 74.666667 0 0 1-74.666667 74.666666H181.333333a74.666667 74.666667 0 0 1-74.666666-74.666666V394.666667a74.666667 74.666667 0 0 1 74.666666-74.666667h74.666667a32 32 0 0 1 0 64h-74.666667a10.666667 10.666667 0 0 0-10.666666 10.666667v448a10.666667 10.666667 0 0 0 10.666666 10.666666h448a10.666667 10.666667 0 0 0 10.666667-10.666666v-74.666667z" fill="#000000" p-id="3475"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1745740451964" class="svg-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22293" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M781.28 851.36a58.56 58.56 0 0 1-58.56 58.56H301.28a58.72 58.72 0 0 1-58.56-58.56V230.4h538.56z m-421.6-725.92a11.84 11.84 0 0 1 12-12h281.28a11.84 11.84 0 0 1 12 12V160H359.68zM956.8 160H734.72v-34.56a81.76 81.76 0 0 0-81.76-81.76H371.68a82.08 82.08 0 0 0-81.76 81.76V160H67.2a35.36 35.36 0 0 0 0 70.56h105.12v620.8a128.96 128.96 0 0 0 128.96 128.96h421.44a128.96 128.96 0 0 0 128.96-128.96V230.4H956.8a35.2 35.2 0 0 0 35.2-35.2 34.56 34.56 0 0 0-35.2-35.2zM512 804.16a35.2 35.2 0 0 0 35.2-35.36V393.92a35.2 35.2 0 1 0-70.4 0V768.8a35.2 35.2 0 0 0 35.2 35.36m-164.32 0a35.36 35.36 0 0 0 35.36-35.36V393.92a35.36 35.36 0 1 0-70.56 0V768.8a36.32 36.32 0 0 0 35.2 35.36m328.64 0a35.36 35.36 0 0 0 35.2-35.36V393.92a35.36 35.36 0 1 0-70.56 0V768.8a35.36 35.36 0 0 0 35.36 35.36" fill="#000000" p-id="22294"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 972 B

After

Width:  |  Height:  |  Size: 972 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1745740069030" class="svg-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4754" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M832 250.352L468.215 612.354c-15.66 15.582-40.986 15.52-56.569-0.14-15.582-15.659-15.52-40.985 0.14-56.568L777.222 192H626c-22.091 0-40-17.909-40-40s17.909-40 40-40h174c61.856 0 112 50.144 112 112v174c0 22.091-17.909 40-40 40s-40-17.909-40-40V250.352z m0 339.909c0-22.092 17.909-40 40-40s40 17.908 40 40V800c0 61.856-50.144 112-112 112H224c-61.856 0-112-50.144-112-112V224c0-61.856 50.144-112 112-112h209.74c22.09 0 40 17.909 40 40s-17.91 40-40 40H224c-17.673 0-32 14.327-32 32v576c0 17.673 14.327 32 32 32h576c17.673 0 32-14.327 32-32V590.26z" fill="#000000" p-id="4755"></path></svg>

After

Width:  |  Height:  |  Size: 922 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1745740175552" class="svg-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7413" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M727.984 393.184a31.968 31.968 0 0 0-45.248 0.256L449.44 629.248l-103.28-106.112a32 32 0 1 0-45.888 44.608l126.032 129.504c0.048 0.096 0.192 0.096 0.256 0.192 0.064 0.064 0.096 0.192 0.16 0.256 2.016 1.984 4.512 3.2 6.88 4.544 1.248 0.672 2.24 1.792 3.52 2.304a31.728 31.728 0 0 0 24.064 0.064c1.232-0.512 2.208-1.536 3.392-2.176 2.4-1.344 4.896-2.528 6.944-4.544 0.064-0.064 0.096-0.192 0.192-0.256 0.064-0.096 0.16-0.128 0.256-0.192l256.224-259.008a32 32 0 0 0-0.224-45.248zM832.992 928h-640c-52.928 0-96-43.072-96-96V192c0-52.928 43.072-96 96-96h640c52.928 0 96 43.072 96 96v640c0 52.928-43.056 96-96 96z m-640-768c-17.632 0-32 14.368-32 32v640c0 17.664 14.368 32 32 32h640a32 32 0 0 0 32-32V192c0-17.632-14.336-32-32-32h-640z" fill="#000000" p-id="7414"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
src/assets/svg/quote.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1745740304487" class="svg-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16699" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M309.44 997.376h-92.8l1.6-131.648-83.456 1.472-5.504-1.28a186.752 186.752 0 0 1-82.816-42.88l-4.48-4.608a190.4 190.4 0 0 1-41.088-84.928L0 724.224l1.024-555.2c6.464-30.848 20.608-60.032 40.768-84.352l4.48-4.672A181.824 181.824 0 0 1 129.728 36.864l9.92-1.088 756.608 1.536c29.184 7.68 56.896 22.016 80.064 41.408l4.864 4.736c21.888 24.576 36.48 54.656 42.112 87.104l0.704 8.064-0.96 554.816c-6.4 30.976-20.544 60.16-40.832 84.544l-4.544 4.736a184.256 184.256 0 0 1-83.136 43.008l-10.368 1.088-385.152-1.792-189.568 132.352z m3.072-226.432l-1.344 111.68 158.72-110.848 408.96 1.92a92.16 92.16 0 0 0 33.92-17.92c8.576-10.944 14.784-23.68 18.176-37.12V183.104a83.712 83.712 0 0 0-17.088-35.2 115.648 115.648 0 0 0-35.968-19.008H145.216a88.832 88.832 0 0 0-33.856 17.792 101.952 101.952 0 0 0-18.24 37.184v535.168c3.328 13.376 9.536 25.984 17.984 36.8 9.92 8.32 21.504 14.4 33.984 17.984l167.424-2.88z" fill="#000000" p-id="16700" data-spm-anchor-id="a313x.search_index.0.i23.4a633a81AeDDIC" class=""></path><path d="M353.088 262.4h70.4v66.432s-106.368 59.2-105.92 132.416H423.68v165.504H211.84V428.16A296.768 296.768 0 0 1 353.088 262.4zM677.44 262.4h70.4v66.432s-106.368 59.2-105.92 132.416h105.92v165.504H536.256V428.16a296.832 296.832 0 0 1 141.184-165.76z" fill="#000000" p-id="16701" data-spm-anchor-id="a313x.search_index.0.i24.4a633a81AeDDIC" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1745741368623" class="svg-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23509" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M237.303467 377.216l113.152 106.026667c16.584533 16.763733 33.6512 43.933867 17.066666 60.693333-16.597333 16.759467-39.227733 16.759467-55.816533 0L138.368 368.3968c-13.162667-13.2608-14.122667-34.491733-0.96-47.752533l174.301867-178.2784c16.5888-16.759467 39.223467-16.759467 55.812266 0s-0.477867 43.933867-17.066666 60.689066L238.775467 313.216h380.881066c153.211733 0 276.343467 132.881067 276.343467 285.738667 0 152.853333-123.136 298.845867-276.343467 298.845866H213.457067c-23.317333 0-42.88-10.824533-42.88-34.133333 0-23.313067 19.562667-29.870933 42.88-29.870933h402.816c102.762667 0 215.714133-132.322133 215.714133-234.845867s-112.951467-221.725867-215.714133-221.725867H237.303467z" fill="#000" p-id="23510"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -10,7 +10,7 @@ import {
} from '@element-plus/icons-vue'
import AtIcon from '@/assets/svg/at.svg'
import adminIcon from '@/assets/svg/administrator.svg'
import DeleteIcon from '@/assets/svg/delete.svg'
import DeleteIcon from '@/assets/svg/deleteuser.svg'
import TransferIcon from '@/assets/svg/transfer.svg'
import { useUserStore, useGroupStore, useMenuStore } from '@/stores'

View File

@@ -0,0 +1,177 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick, watch, markRaw } from 'vue'
import QuoteIcon from '@/assets/svg/quote.svg'
import ForwardIcon from '@/assets/svg/forward.svg'
import DeletemsgIcon from '@/assets/svg/deletemsg.svg'
import CopyIcon from '@/assets/svg/copy.svg'
import MultiselectIcon from '@/assets/svg/multiselect.svg'
import RevokeIcon from '@/assets/svg/revoke.svg'
import { useMenuStore } from '@/stores'
const props = defineProps(['msg'])
const menuData = useMenuStore()
const menuName = computed(() => {
return 'MenuMsgItem-' + props.msg.msgId
})
const emit = defineEmits(['selectMenu'])
const menu = computed(() => {
return [
{
label: 'copy',
desc: '复制',
icon: markRaw(CopyIcon)
},
{
label: 'forward',
desc: '转发',
icon: markRaw(ForwardIcon)
},
{
label: 'multiSelect',
desc: '多选',
icon: markRaw(MultiselectIcon)
},
{
label: 'quote',
desc: '引用',
icon: markRaw(QuoteIcon)
},
{
label: 'revoke',
desc: '撤回',
icon: markRaw(RevokeIcon)
},
{
label: 'delete',
desc: '删除',
icon: markRaw(DeletemsgIcon)
}
]
})
const containerRef = ref()
const menuRef = ref()
const isShowMenu = ref(false)
const x = ref(0)
const y = ref(0)
onMounted(() => {
containerRef.value?.addEventListener('contextmenu', handleShowMenu)
document.addEventListener('keydown', handleEscEvent)
document.addEventListener('click', closeMenu) //在其他地方的click事件要能关闭菜单
document.addEventListener('contextmenu', closeMenu) //在其他地方的菜单事件也要能关闭菜单
})
onUnmounted(() => {
containerRef.value?.removeEventListener('contextmenu', handleShowMenu)
document.removeEventListener('keydown', handleEscEvent)
document.removeEventListener('click', closeMenu)
document.removeEventListener('contextmenu', closeMenu)
})
// 监听菜单状态变化
watch(
() => menuData.activeMenu,
(newVal) => {
if (newVal !== menuName.value && isShowMenu.value) {
closeMenu()
}
}
)
const handleShowMenu = (e) => {
e.preventDefault() //阻止浏览器默认行为
e.stopPropagation() // 阻止冒泡
isShowMenu.value = true
menuData.setActiveMenu(menuName.value)
nextTick(() => {
//如果发现菜单超出window.innerWidth屏幕宽度x要修正一下往左边弹出菜单
if (e.clientX + menuRef.value.clientWidth > window.innerWidth) {
x.value = e.clientX - menuRef.value.clientWidth
} else {
x.value = e.clientX
}
// 如果发现菜单超出window.innerHeight屏幕高度y要修正一下往上面弹出菜单
if (e.clientY + menuRef.value.clientHeight > window.innerHeight) {
y.value = e.clientY - menuRef.value.clientHeight
} else {
y.value = e.clientY
}
})
}
const handleEscEvent = (event) => {
if (event.key === 'Escape') isShowMenu.value = false
}
const closeMenu = () => {
isShowMenu.value = false
}
const handleClick = (item) => {
emit('selectMenu', item.label)
}
</script>
<template>
<div class="context-menu-container" ref="containerRef">
<!-- 在定义的插槽范围内都能打开菜单超出了就不行 -->
<slot></slot>
<Teleport to="body">
<div
v-if="isShowMenu"
class="context-menu"
:style="{ left: x + 'px', top: y + 'px' }"
@contextmenu.prevent
ref="menuRef"
>
<div class="menu-list">
<div class="menu-item" v-for="item in menu" :key="item.label" @click="handleClick(item)">
<component class="menu-icon" :is="item.icon" />
<span class="menu-desc text-ellipsis">{{ item.desc }}</span>
</div>
</div>
</div>
</Teleport>
</div>
</template>
<style lang="scss" scoped>
.context-menu {
padding: 5px;
border-radius: 6px;
background-color: #fff;
position: absolute;
box-shadow: 2px 2px 20px gray;
.menu-item {
padding: 5px;
margin-top: 3px;
border-radius: 4px;
display: flex;
cursor: pointer;
&:hover {
background-color: #e6e8eb;
}
.menu-icon {
width: 20px;
height: 20px;
}
.menu-desc {
padding-left: 5px;
padding-right: 5px;
display: flex;
justify-content: start;
align-items: center;
font-size: 14px;
}
}
}
</style>

View File

@@ -21,6 +21,7 @@ import MsgBoxImage from '@/views/message/components/MsgBoxImage.vue'
import MsgBoxAudio from '@/views/message/components/MsgBoxAudio.vue'
import MsgBoxVideo from '@/views/message/components/MsgBoxVideo.vue'
import MsgBoxDocument from '@/views/message/components/MsgBoxDocument.vue'
import MenuMsgItem from '@/views/message/components/MenuMsgItem.vue'
const props = defineProps([
'sessionId',
@@ -668,6 +669,10 @@ const onResendMsg = () => {
}
}
const onSelectMenuMsgItem = (label) => {
console.log(label)
}
/**
* 只要内容发生变化,重新渲染
*/
@@ -738,7 +743,9 @@ watch(
<div v-if="myMsgIsRead" class="remote_read"></div>
<div v-else class="remote_unread"></div>
</div>
<div class="div-content" :id="`div-content-${msg.msgId}`"></div>
<MenuMsgItem :msg="msg" @selectMenu="onSelectMenuMsgItem">
<div class="div-content" :id="`div-content-${msg.msgId}`"></div>
</MenuMsgItem>
</el-main>
</el-container>
</el-main>
@@ -770,7 +777,9 @@ watch(
<span>{{ msgTime }}</span>
</el-header>
<el-main class="message-content">
<div class="div-content" :id="`div-content-${msg.msgId}`"></div>
<MenuMsgItem :msg="msg" @selectMenu="onSelectMenuMsgItem">
<div class="div-content" :id="`div-content-${msg.msgId}`"></div>
</MenuMsgItem>
</el-main>
</el-container>
</el-main>