右键菜单

This commit is contained in:
2025-12-08 13:57:37 +08:00
parent b7a90e2fc2
commit 9012caa7aa
2 changed files with 217 additions and 26 deletions

View File

@@ -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) {