右键菜单
This commit is contained in:
@@ -204,6 +204,31 @@
|
||||
</div>
|
||||
|
||||
<ContextMenu />
|
||||
|
||||
<!-- 弹窗 4:移除成员确认 -->
|
||||
<div v-if="removingMember" class="fixed inset-0 bg-black/80 z-[60] flex items-center justify-center p-4 backdrop-blur-sm animate-fade-in">
|
||||
<div class="bg-gray-800 rounded-xl p-6 w-80 text-center border border-gray-700 shadow-2xl">
|
||||
<div class="flex justify-center mb-4">
|
||||
<Avatar
|
||||
:name="removingMember.user?.name || removingMember.nickname"
|
||||
:avatar="removingMember.user?.avatar"
|
||||
size="lg"
|
||||
rounded="full"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="text-white font-bold mb-2">移除成员</h3>
|
||||
<p class="text-gray-400 text-sm mb-6">
|
||||
确定要将 <span class="text-white font-medium">{{ removingMember.nickname || removingMember.user?.name || '该成员' }}</span> 移出群聊吗?
|
||||
</p>
|
||||
<div class="flex gap-3">
|
||||
<button class="flex-1 py-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600 text-sm" @click="removingMember = null">取消</button>
|
||||
<button class="flex-1 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 text-sm" :disabled="removing" @click="handleRemoveMember">
|
||||
<i v-if="removing" class="fas fa-circle-notch fa-spin mr-1"></i>
|
||||
确定移除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -221,9 +246,10 @@ import { useToastStore } from '@/stores/toast'
|
||||
import { useContextMenu } from '@/composables/useContextMenu'
|
||||
import { useGroupWebRTC } from '@/composables/useGroupWebRTC'
|
||||
import type { GroupMember, Contact } from '@/types/api'
|
||||
import type { MenuItem } from '@/stores/contextMenu'
|
||||
|
||||
const props = defineProps<{ roomId: string }>()
|
||||
const emit = defineEmits(['member-clicked', 'group-updated', 'show-info', 'show-notice'])
|
||||
const emit = defineEmits(['member-clicked', 'member-removed', 'group-updated', 'show-info', 'show-notice'])
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const chatStore = useChatStore()
|
||||
@@ -243,13 +269,32 @@ const showCallModal = ref(false)
|
||||
const showAnnouncementModal = ref(false)
|
||||
const isEditingAnnouncement = ref(false)
|
||||
const editAnnouncementText = ref('')
|
||||
const removingMember = ref<GroupMember | null>(null)
|
||||
const removing = ref(false)
|
||||
|
||||
// Computed
|
||||
const isOwner = computed(() => members.value.find(m => m.user_id === authStore.user?.id)?.role === 2)
|
||||
const isAdmin = computed(() => members.value.find(m => m.user_id === authStore.user?.id)?.role === 1)
|
||||
const currentUserRole = computed(() => members.value.find(m => m.user_id === authStore.user?.id)?.role ?? 0)
|
||||
const isOwner = computed(() => currentUserRole.value === 2)
|
||||
const isAdmin = computed(() => currentUserRole.value === 1)
|
||||
const canInvite = computed(() => isOwner.value || isAdmin.value)
|
||||
const canEditAnnouncement = computed(() => isOwner.value || isAdmin.value)
|
||||
|
||||
// 判断是否为好友
|
||||
function isFriendCheck(userId: string): boolean {
|
||||
return chatStore.contacts.some(c => c.user_id === userId || c.id === userId)
|
||||
}
|
||||
|
||||
// 判断是否可以移除某个成员
|
||||
function canRemoveMember(member: GroupMember): boolean {
|
||||
// 不能移除自己
|
||||
if (member.user_id === authStore.user?.id) return false
|
||||
// 群主可以移除任何人
|
||||
if (isOwner.value) return true
|
||||
// 管理员只能移除普通成员
|
||||
if (isAdmin.value && member.role === 0) return true
|
||||
return false
|
||||
}
|
||||
|
||||
// 过滤出可通话的候选人(排除自己)
|
||||
const callCandidates = computed(() => {
|
||||
return members.value
|
||||
@@ -308,12 +353,108 @@ function handleMemberClick(member: GroupMember) {
|
||||
}
|
||||
|
||||
function showMemberContextMenu(event: MouseEvent, member: GroupMember) {
|
||||
const items = [
|
||||
{ label: '查看资料', icon: 'fas fa-user', action: () => handleMemberClick(member) }
|
||||
]
|
||||
const items: MenuItem[] = []
|
||||
|
||||
// 1. 查看资料 - 始终显示
|
||||
items.push({
|
||||
label: '查看资料',
|
||||
icon: 'fas fa-user',
|
||||
action: () => handleMemberClick(member)
|
||||
})
|
||||
|
||||
// 不是自己时才显示以下选项
|
||||
if (member.user_id !== authStore.user?.id) {
|
||||
// 2. 添加好友 - 非好友时显示
|
||||
if (!isFriendCheck(member.user_id)) {
|
||||
items.push({
|
||||
label: '添加好友',
|
||||
icon: 'fas fa-user-plus',
|
||||
action: () => handleAddFriendFromMenu(member)
|
||||
})
|
||||
}
|
||||
|
||||
// 3. 设置/取消管理员 - 仅群主可操作
|
||||
if (isOwner.value) {
|
||||
if (member.role === 0) {
|
||||
// 普通成员 -> 设置为管理员
|
||||
items.push({
|
||||
label: '设置为管理员',
|
||||
icon: 'fas fa-user-shield',
|
||||
action: () => handleSetAdmin(member)
|
||||
})
|
||||
} else if (member.role === 1) {
|
||||
// 管理员 -> 取消管理员
|
||||
items.push({
|
||||
label: '取消管理员',
|
||||
icon: 'fas fa-user-minus',
|
||||
action: () => handleRemoveAdmin(member)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 移除成员 - 群主可移除任何人,管理员只能移除普通成员
|
||||
if (canRemoveMember(member)) {
|
||||
items.push({
|
||||
label: '移除成员',
|
||||
icon: 'fas fa-user-times',
|
||||
danger: true,
|
||||
action: () => { removingMember.value = member }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
showContextMenu(event, items)
|
||||
}
|
||||
|
||||
// --- 成员管理逻辑 ---
|
||||
async function handleAddFriendFromMenu(member: GroupMember) {
|
||||
try {
|
||||
await contactApi.addFriend({ to_user_id: member.user_id })
|
||||
toastStore.success('好友申请已发送')
|
||||
} catch (e: any) {
|
||||
toastStore.error(e?.response?.data?.message || e?.message || '添加好友失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSetAdmin(member: GroupMember) {
|
||||
try {
|
||||
await groupApi.updateMemberRole(props.roomId, member.user_id, { role: 1 })
|
||||
toastStore.success('已设置为管理员')
|
||||
loadData()
|
||||
emit('group-updated')
|
||||
} catch (e: any) {
|
||||
toastStore.error(e?.response?.data?.message || e?.message || '设置失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveAdmin(member: GroupMember) {
|
||||
try {
|
||||
await groupApi.updateMemberRole(props.roomId, member.user_id, { role: 0 })
|
||||
toastStore.success('已取消管理员')
|
||||
loadData()
|
||||
emit('group-updated')
|
||||
} catch (e: any) {
|
||||
toastStore.error(e?.response?.data?.message || e?.message || '取消失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveMember() {
|
||||
if (!removingMember.value) return
|
||||
removing.value = true
|
||||
try {
|
||||
await groupApi.removeGroupMember(props.roomId, removingMember.value.user_id)
|
||||
toastStore.success('已移除该成员')
|
||||
removingMember.value = null
|
||||
loadData()
|
||||
emit('member-removed')
|
||||
emit('group-updated')
|
||||
} catch (e: any) {
|
||||
toastStore.error(e?.response?.data?.message || e?.message || '移除失败')
|
||||
} finally {
|
||||
removing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// --- 通话逻辑 ---
|
||||
function handleStartCallClick() {
|
||||
if (callCandidates.value.length === 0) {
|
||||
|
||||
@@ -1490,38 +1490,87 @@ async function startCall(type: 'audio' | 'video') {
|
||||
|
||||
// 会话右键菜单
|
||||
function showConversationMenu(event: MouseEvent, conv: any) {
|
||||
showContextMenu(event, [
|
||||
const isGroup = conv.is_group || conv.room_type === 'group'
|
||||
const contact = chatStore.contacts.find(c => c.user_id === conv.target_id || c.id === conv.target_id)
|
||||
|
||||
const menuItems: Array<{ label: string; icon: string; action: () => void; danger?: boolean }> = [
|
||||
// 1. 置顶会话
|
||||
{
|
||||
label: conv.is_top ? '取消置顶' : '置顶会话',
|
||||
icon: 'fas fa-thumbtack',
|
||||
action: async () => {
|
||||
// 查找对应联系人以更新状态
|
||||
const contact = chatStore.contacts.find(c => c.user_id === conv.target_id || c.id === conv.target_id)
|
||||
if (contact) {
|
||||
try {
|
||||
await contactApi.updateContact(contact.id, { is_top: !contact.is_top })
|
||||
// 手动更新store中的状态,触发 filteredConversations 重新计算
|
||||
contact.is_top = !contact.is_top
|
||||
// 强制刷新会话列表 (触发 computed 更新)
|
||||
await conversationStore.loadConversations()
|
||||
toastStore.success(contact.is_top ? '已置顶' : '已取消置顶')
|
||||
} catch(e) {
|
||||
toastStore.error('操作失败')
|
||||
}
|
||||
} else {
|
||||
toastStore.warning('无法找到对应联系人')
|
||||
// 群聊可能没有 contact,使用 conversationApi
|
||||
try {
|
||||
await conversationApi.updateConversation({ target_id: conv.target_id, is_top: !conv.is_top })
|
||||
await conversationStore.loadConversations()
|
||||
toastStore.success(!conv.is_top ? '已置顶' : '已取消置顶')
|
||||
} catch(e) {
|
||||
toastStore.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 2. 免打扰
|
||||
{
|
||||
label: conv.is_muted ? '开启提醒' : '消息免打扰',
|
||||
icon: conv.is_muted ? 'fas fa-bell' : 'fas fa-bell-slash',
|
||||
action: async () => {
|
||||
try {
|
||||
if (contact) {
|
||||
await contactApi.updateContact(contact.id, { is_muted: !conv.is_muted })
|
||||
contact.is_muted = !conv.is_muted
|
||||
} else {
|
||||
await conversationApi.updateConversation({ target_id: conv.target_id, is_muted: !conv.is_muted })
|
||||
}
|
||||
await conversationStore.loadConversations()
|
||||
toastStore.success(!conv.is_muted ? '已开启免打扰' : '已开启提醒')
|
||||
} catch(e) {
|
||||
toastStore.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 3. 特别关心 - 仅私聊显示
|
||||
if (!isGroup && contact) {
|
||||
menuItems.push({
|
||||
label: conv.is_special_care ? '取消特别关心' : '特别关心',
|
||||
icon: conv.is_special_care ? 'fas fa-heart' : 'far fa-heart',
|
||||
action: async () => {
|
||||
try {
|
||||
await contactApi.updateContact(contact.id, { is_special_care: !conv.is_special_care })
|
||||
contact.is_special_care = !conv.is_special_care
|
||||
await conversationStore.loadConversations()
|
||||
toastStore.success(!conv.is_special_care ? '已设为特别关心' : '已取消特别关心')
|
||||
} catch(e) {
|
||||
toastStore.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 4. 标记已读
|
||||
menuItems.push({
|
||||
label: '标记已读',
|
||||
icon: 'fas fa-check-double',
|
||||
action: async () => {
|
||||
await conversationStore.clearUnread(conv.target_id)
|
||||
toastStore.success('已标记已读')
|
||||
}
|
||||
},
|
||||
{
|
||||
})
|
||||
|
||||
// 5. 删除会话
|
||||
menuItems.push({
|
||||
label: '删除会话',
|
||||
icon: 'fas fa-trash-alt',
|
||||
danger: true,
|
||||
@@ -1529,8 +1578,9 @@ function showConversationMenu(event: MouseEvent, conv: any) {
|
||||
pendingDeleteConversation.value = conv
|
||||
showDeleteConversationConfirm.value = true
|
||||
}
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
showContextMenu(event, menuItems)
|
||||
}
|
||||
|
||||
async function handleMarkRead(conv: Conversation) {
|
||||
|
||||
Reference in New Issue
Block a user