Skip to content

Commit

Permalink
perf(service): ⚡ 优化群聊功能 (#208)
Browse files Browse the repository at this point in the history
完善一些代码和操作的提示
  • Loading branch information
nongyehong authored Feb 22, 2025
1 parent ac9d922 commit 0e07e33
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 53 deletions.
2 changes: 1 addition & 1 deletion public/icon.js

Large diffs are not rendered by default.

64 changes: 34 additions & 30 deletions src/components/common/ContextMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@
left: `${pos.posX}px`,
top: `${pos.posY - 42}px`
}">
<n-flex
v-for="(item, index) in emoji as any[]"
:key="index"
align="center"
justify="space-between"
class="emoji-list">
<n-flex v-for="(item, index) in emoji" :key="index" align="center" justify="space-between" class="emoji-list">
<n-popover trigger="hover" :show-arrow="false" placement="top">
<template #trigger>
<n-flex :size="0" align="center" justify="center" class="emoji-item" @click="handleReplyEmoji(item)">
Expand All @@ -37,7 +32,7 @@
top: `${pos.posY}px`
}">
<div v-resize="handleSize" v-if="visibleMenu && visibleMenu.length > 0" class="menu-list">
<div v-for="(item, index) in visibleMenu as any[]" :key="index">
<div v-for="(item, index) in visibleMenu" :key="index">
<!-- 禁止的菜单选项需要禁止点击事件 -->
<div class="menu-item-disabled" v-if="item.disabled" @click.prevent="$event.preventDefault()">
<svg><use :href="`#${item.icon}`"></use></svg>
Expand All @@ -49,10 +44,10 @@
</div>
</div>
<!-- 判断是否有特别的菜单项才需要分割线 -->
<div v-if="specialMenu.length > 0" class="flex-col-y-center gap-6px">
<div v-if="visibleSpecialMenu.length > 0" class="flex-col-y-center gap-6px">
<!-- 分割线 -->
<div class="h-1px bg-[--line-color] m-[2px_8px]"></div>
<div @click="handleClick(item)" class="menu-item" v-for="item in specialMenu as any[]" :key="item.label">
<div @click="handleClick(item)" class="menu-item" v-for="item in visibleSpecialMenu" :key="item.label">
<svg><use :href="`#${item.icon}`"></use></svg>
{{ item.label }}
</div>
Expand All @@ -68,35 +63,44 @@
import { useContextMenu } from '@/hooks/useContextMenu.ts'
import { useViewport } from '@/hooks/useViewport.ts'
const { content, menu, emoji, specialMenu } = defineProps({
content: {
type: Object,
required: false
},
menu: {
type: Array
},
emoji: {
type: Array
},
specialMenu: {
type: Array,
default: () => []
}
type Props = {
content?: Record<string, any>
menu?: any[]
emoji?: any[]
specialMenu?: any[]
}
const props = withDefaults(defineProps<Props>(), {
content: () => ({}),
menu: () => [],
emoji: () => [],
specialMenu: () => []
})
// 使用计算属性过滤显示的菜单项
const visibleMenu = computed(() => {
return menu?.filter((item: any) => {
// 检查是否有 visible 属性并作为函数调用
// 检查是否有 visible 属性并作为函数调用
return props.menu?.filter((item: any) => {
if (typeof item.visible === 'function') {
return item.visible(content) // 如果 visible 是函数,则调用它
return item.visible(props.content) // 如果 visible 是函数,则调用它
}
// 如果没有 visible 属性,则默认显示
return true
})
})
// 添加 specialMenu 的过滤功能
const visibleSpecialMenu = computed(() => {
return props.specialMenu?.filter((item: any) => {
if (typeof item.visible === 'function') {
return item.visible(props.content)
}
return true
})
})
/** 判断是否传入了menu */
const isNull = computed(() => menu === void 0)
const isNull = computed(() => props.menu === void 0)
const ContextMenuRef = useTemplateRef('ContextMenuRef')
const emit = defineEmits(['select', 'reply-emoji'])
/** 获取鼠标位置和是否显示右键菜单 */
Expand Down Expand Up @@ -138,10 +142,10 @@ const handleClick = (item: string) => {
}
/** 处理回复表情事件 */
const handleReplyEmoji = (item: string) => {
const handleReplyEmoji = (item: { label: string }) => {
nextTick(() => {
showMenu.value = false
emit('reply-emoji', item)
emit('reply-emoji', item.label)
})
}
Expand Down
6 changes: 6 additions & 0 deletions src/components/rightBox/chatBox/ChatHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,12 @@ const handleConfirm = () => {
// TODO: 删除后当前删除的人提示不准确,无论是删除方还是被删除方都提示“您已被对方拉黑”
})
} else if (optionsType.value === RoomActEnum.EXIT_GROUP) {
if (activeItem.roomId === 1) {
window.$message.warning('无法退出频道')
modalShow.value = false
return
}
groupStore.exitGroup(activeItem.roomId).then(() => {
modalShow.value = false
sidebarShow.value = false
Expand Down
18 changes: 13 additions & 5 deletions src/components/rightBox/chatBox/ChatMain.vue
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@
:style="item.fromUser.uid === userUid ? 'flex-direction: row-reverse' : ''">
<!-- 用户徽章 -->
<n-popover
v-if="useBadgeInfo(useUserInfo(item.fromUser.uid).value.wearingItemId).value.img"
v-if="
currentRoomId === 1 &&
useBadgeInfo(useUserInfo(item.fromUser.uid).value.wearingItemId).value.img
"
trigger="hover">
<template #trigger>
<img
Expand All @@ -199,15 +202,15 @@
</ContextMenu>
<!-- 群主 -->
<div
v-if="chatStore.isGroup && item.message.id === 1"
v-if="chatStore.isGroup && groupStore.currentLordId === item.fromUser.uid"
class="flex p-4px rounded-4px bg-#f5dadf size-fit select-none">
<span class="text-(10px #d5304f)">群主</span>
</div>
<!-- 管理员 -->
<div
v-if="chatStore.isGroup && item.message.id === 2"
class="flex p-4px rounded-4px bg-#13987F66size-fit select-none">
<span class="text-(10px #13987f)">管理员</span>
v-if="chatStore.isGroup && groupStore.adminUidList.includes(item.fromUser.uid)"
class="flex p-4px rounded-4px bg-#cef9ec size-fit select-none">
<span class="text-(10px #1a7d6b)">管理员</span>
</div>
<!-- 信息时间(群聊) -->
<Transition name="fade-group">
Expand Down Expand Up @@ -414,6 +417,8 @@ import { AvatarUtils } from '@/utils/AvatarUtils'
import VirtualList, { type VirtualListExpose } from '@/components/common/VirtualList.vue'
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import { useTauriListener } from '@/hooks/useTauriListener'
import { useGroupStore } from '@/stores/group.ts'
import { useGlobalStore } from '@/stores/global'
const appWindow = WebviewWindow.getCurrent()
const { addListener } = useTauriListener()
Expand All @@ -423,6 +428,8 @@ const props = defineProps<{
const activeItemRef = shallowRef<SessionItem>(props.activeItem)
const chatStore = useChatStore()
const userStore = useUserStore()
const groupStore = useGroupStore()
const globalStore = useGlobalStore()
// 记录当前滚动位置相关信息
const isAutoScrolling = ref(false)
Expand All @@ -437,6 +444,7 @@ const chatMessageList = computed(() => chatStore.chatMessageList)
const currentNewMsgCount = computed(() => chatStore.currentNewMsgCount)
const messageOptions = computed(() => chatStore.currentMessageOptions)
const { createWebviewWindow } = useWindow()
const currentRoomId = computed(() => globalStore.currentSession?.roomId)
/** 是否是超级管理员 */
// const isAdmin = computed(() => userInfo?.power === PowerEnum.ADMIN)
/** 跳转回复消息后选中效果 */
Expand Down
12 changes: 6 additions & 6 deletions src/components/rightBox/chatBox/ChatSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
style="max-height: calc(100vh - 260px)"
item-resizable
@scroll="handleScroll($event)"
:item-size="42"
:item-size="46"
:items="filteredUserList">
<template #default="{ item }">
<n-popover
Expand All @@ -84,16 +84,16 @@
align="center"
justify="space-between"
class="item">
<n-flex align="center" :size="8">
<n-flex align="center" :size="8" class="flex-1 truncate">
<n-avatar
round
class="grayscale"
:class="{ 'grayscale-0': item.activeStatus === OnlineEnum.ONLINE }"
:color="'#fff'"
:size="26"
:src="AvatarUtils.getAvatarUrl(item.avatar)" />
<n-flex vertical :size="2">
<p class="text-12px truncate flex-1">{{ item.name }}</p>
<n-flex vertical :size="2" class="flex-1 truncate">
<p :title="item.name" class="text-12px truncate flex-1">{{ item.name }}</p>
<n-flex
v-if="item.userStateId && getUserState(item.userStateId)"
align="center"
Expand All @@ -114,8 +114,8 @@
</div>
<div
v-if="item.roleId === RoleEnum.ADMIN"
class="flex p-4px rounded-4px bg-#13987F66 size-fit select-none">
<p class="text-(10px #13987f)">管理员</p>
class="flex p-4px rounded-4px bg-#cef9ec size-fit select-none">
<p class="text-(10px #1a7d6b)">管理员</p>
</div>
</n-flex>
</ContextMenu>
Expand Down
1 change: 1 addition & 0 deletions src/components/rightBox/renderMessage/Image.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const openImageViewer = async () => {
// 如果窗口已存在,更新图片内容并显示窗口
await existingWindow.emit('update-image', { list, index }) // 发送更新事件
await existingWindow.show()
await existingWindow.setFocus()
return
}
Expand Down
120 changes: 119 additions & 1 deletion src/hooks/useChatMain.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCommon } from '@/hooks/useCommon.ts'
import { MittEnum, MsgEnum, PowerEnum } from '@/enums'
import { MittEnum, MsgEnum, PowerEnum, RoleEnum, RoomTypeEnum } from '@/enums'
import { MessageType } from '@/services/types.ts'
import { useMitt } from '@/hooks/useMitt.ts'
import { useChatStore } from '@/stores/chat.ts'
Expand All @@ -13,12 +13,14 @@ import { translateText } from '@/services/translate'
import { useSettingStore } from '@/stores/setting.ts'
import { save } from '@tauri-apps/plugin-dialog'
import { useDownload } from '@/hooks/useDownload'
import { useGroupStore } from '@/stores/group'

export const useChatMain = () => {
const { removeTag, openMsgSession, userUid } = useCommon()
const settingStore = useSettingStore()
const { chat } = storeToRefs(settingStore)
const globalStore = useGlobalStore()
const groupStore = useGroupStore()
const chatStore = useChatStore()
const userStore = useUserStore()?.userInfo
const { downloadFile } = useDownload()
Expand Down Expand Up @@ -235,10 +237,126 @@ export const useChatMain = () => {
globalStore.addFriendModalInfo.uid = item.uid || item.fromUser.uid
},
visible: (item: any) => !checkFriendRelation(item.uid || item.fromUser.uid, 'all')
},
{
label: '设为管理员',
icon: 'people-safe',
click: async (item: any) => {
const targetUid = item.uid || item.fromUser.uid
const roomId = globalStore.currentSession?.roomId
if (!roomId) return

try {
await groupStore.addAdmin([targetUid])
window.$message.success('设置管理员成功')
} catch (error) {
window.$message.error('设置管理员失败')
}
},
visible: (item: any) => {
// 1. 检查是否在群聊中
const isInGroup = globalStore.currentSession?.type === RoomTypeEnum.GROUP
if (!isInGroup) return false

// 2. 检查房间号是否为1(频道)
const roomId = globalStore.currentSession?.roomId
if (!roomId || roomId === 1) return false

// 3. 获取目标用户ID
const targetUid = item.uid || item.fromUser?.uid
if (!targetUid) return false

// 4. 检查目标用户是否已经是管理员或群主
if (item.roleId === RoleEnum.ADMIN || item.roleId === RoleEnum.LORD) return false

// 5. 检查当前用户是否是群主
const currentUser = groupStore.userList.find((user) => user.uid === userUid.value)
return currentUser?.roleId === RoleEnum.LORD
}
},
{
label: '撤销管理员',
icon: 'reduce-user',
click: async (item: any) => {
const targetUid = item.uid || item.fromUser.uid
const roomId = globalStore.currentSession?.roomId
if (!roomId) return

try {
await groupStore.revokeAdmin([targetUid])
window.$message.success('撤销管理员成功')
} catch (error) {
window.$message.error('撤销管理员失败')
}
},
visible: (item: any) => {
// 1. 检查是否在群聊中
const isInGroup = globalStore.currentSession?.type === RoomTypeEnum.GROUP
if (!isInGroup) return false

// 2. 检查房间号是否为1(频道)
const roomId = globalStore.currentSession?.roomId
if (!roomId || roomId === 1) return false

// 3. 获取目标用户ID
const targetUid = item.uid || item.fromUser?.uid
if (!targetUid) return false

// 4. 检查目标用户是否是管理员(只能撤销管理员,不能撤销群主)
if (item.roleId !== RoleEnum.ADMIN) return false

// 5. 检查当前用户是否是群主
const currentUser = groupStore.userList.find((user) => user.uid === userUid.value)
return currentUser?.roleId === RoleEnum.LORD
}
}
])
/** 举报选项 */
const report = ref([
{
label: '移出本群',
icon: 'people-delete-one',
click: async (item: any) => {
const targetUid = item.uid || item.fromUser.uid
const roomId = globalStore.currentSession?.roomId
if (!roomId) return

try {
await apis.removeGroupMember({ roomId, uid: targetUid })
// 从群成员列表中移除该用户
groupStore.filterUser(targetUid)
window.$message.success('移出群聊成功')
} catch (error) {
window.$message.error('移出群聊失败')
}
},
visible: (item: any) => {
// 1. 检查是否在群聊中
const isInGroup = globalStore.currentSession?.type === RoomTypeEnum.GROUP
if (!isInGroup) return false

// 2. 检查房间号是否为1(频道)
const roomId = globalStore.currentSession?.roomId
if (!roomId || roomId === 1) return false

// 3. 获取目标用户ID
const targetUid = item.uid || item.fromUser?.uid
if (!targetUid) return false

// 4. 检查目标用户是否是群主(群主不能被移出)
if (item.roleId === RoleEnum.LORD) return false

// 5. 检查当前用户是否有权限(群主或管理员)
const currentUser = groupStore.userList.find((user) => user.uid === userUid.value)
const isLord = currentUser?.roleId === RoleEnum.LORD
const isAdmin = currentUser?.roleId === RoleEnum.ADMIN

// 6. 如果当前用户是管理员,则不能移出其他管理员
if (isAdmin && item.roleId === RoleEnum.ADMIN) return false

return isLord || isAdmin
}
},
{
label: '举报',
icon: 'caution',
Expand Down
Loading

0 comments on commit 0e07e33

Please sign in to comment.