From 62143affd54071a7f2e6c51e0e46e440c0cb3ce8 Mon Sep 17 00:00:00 2001 From: QDKF Date: Mon, 22 Sep 2025 14:58:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B6=88=E6=81=AF=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E9=83=A8=E5=88=86=E6=8E=A5=E5=8F=A3=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/modules/chat.ts | 42 +- .../message/components/MessageInput.vue | 4 +- .../components/NotificationMessages.vue | 377 ++++++++++++++++-- 3 files changed, 388 insertions(+), 35 deletions(-) diff --git a/src/api/modules/chat.ts b/src/api/modules/chat.ts index 90a254a..0cd9caf 100644 --- a/src/api/modules/chat.ts +++ b/src/api/modules/chat.ts @@ -47,14 +47,18 @@ export interface ChatMessage { // 群聊成员接口类型定义 export interface ChatMember { id: string - chatId: string - userId: string - userName: string - userAvatar?: string - role: 'admin' | 'member' - joinTime: string + phone: string + sex: number + avatar: string + isTeacher: boolean + email: string + realname: string + username: string + // 可选字段 + chatId?: string + role?: 'admin' | 'member' + joinTime?: string isOnline?: boolean - // 根据接口文档优化的字段 status?: number // 成员状态 lastActiveTime?: string // 最后活跃时间 } @@ -173,5 +177,29 @@ export const ChatApi = { */ getUnreadCount: (): Promise> => { return ApiRequest.get('/aiol/aiolChat/unread-count') + }, + + /** + * 开启全员禁言 + * POST /aiol/aiolChat/{chatId}/mute_all + */ + muteAll: (chatId: string): Promise> => { + return ApiRequest.post(`/aiol/aiolChat/${chatId}/mute_all`) + }, + + /** + * 关闭全员禁言 + * POST /aiol/aiolChat/{chatId}/unmute_all + */ + unmuteAll: (chatId: string): Promise> => { + return ApiRequest.post(`/aiol/aiolChat/${chatId}/unmute_all`) + }, + + /** + * 查询会话详情 + * GET /aiol/aiolChat/{chatId}/detail + */ + getChatDetail: (chatId: string): Promise> => { + return ApiRequest.get(`/aiol/aiolChat/${chatId}/detail`) } } diff --git a/src/views/teacher/message/components/MessageInput.vue b/src/views/teacher/message/components/MessageInput.vue index dd51131..be735bc 100644 --- a/src/views/teacher/message/components/MessageInput.vue +++ b/src/views/teacher/message/components/MessageInput.vue @@ -284,8 +284,8 @@ const handleInput = (value: string) => { } const handleKeyDown = (event: KeyboardEvent) => { - // Ctrl/Cmd + Enter 发送消息 - if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') { + // Enter 键发送消息 + if (event.key === 'Enter') { event.preventDefault() handleSend() } diff --git a/src/views/teacher/message/components/NotificationMessages.vue b/src/views/teacher/message/components/NotificationMessages.vue index ad34825..33dceff 100644 --- a/src/views/teacher/message/components/NotificationMessages.vue +++ b/src/views/teacher/message/components/NotificationMessages.vue @@ -43,7 +43,6 @@ {{ contact.name }} ({{ contact.memberCount || 0 }}人) - {{ contact.lastMessageTime }}
{{ contact.lastMessage }} @@ -170,6 +169,17 @@
+ + +
+
+ + + + 群聊已开启全员禁言 +
+
@@ -185,21 +195,40 @@ - + +
+
+
-
-
+ +
+
- 成员头像 +
-
李小多
+
{{ member.realname }}
-
+ + +
+
+ + + +
+
未找到相关成员
+
请尝试其他关键词
+
+ + +
查看更多 @@ -211,11 +240,11 @@
班级名称 - 计算机一班 + {{ groupInfo.name || '暂无' }}
班级人数 - 149人 + {{ groupInfo.memberCount }}人
@@ -231,8 +260,9 @@
全员禁言
- - + +
@@ -328,6 +358,26 @@ interface Contact { unreadCount: number isOnline?: boolean memberCount?: number + izAllMuted?: boolean | number // 全员禁言状态,支持布尔值和数字(1/0) +} + +// 群聊成员类型定义 +interface ChatMember { + id: string + phone: string + sex: number + avatar: string + isTeacher: boolean + email: string + realname: string + username: string + // 可选字段 + chatId?: string + role?: 'admin' | 'member' + joinTime?: string + isOnline?: boolean + status?: number // 成员状态 + lastActiveTime?: string // 最后活跃时间 } // 消息类型定义(兼容API返回的数据) @@ -382,6 +432,55 @@ const contacts = ref([]) // 当前会话的消息数据 const messages = ref([]) +// 群成员数据 +const groupMembers = ref([]) + +// 群组信息数据 +const groupInfo = ref({ + name: '', + memberCount: 0 +}) + +// 全员禁言状态 +const isAllMuted = ref(false) +const isMuteLoading = ref(false) + +// 群成员显示控制 +const showAllMembers = ref(false) +const maxDisplayMembers = 20 // 5行 × 4列 = 20个成员 + +// 群成员搜索 +const memberSearchKeyword = ref('') +const isSearchingMembers = ref(false) + +// 计算属性:过滤后的群成员列表 +const filteredMembers = computed(() => { + if (!memberSearchKeyword.value.trim()) { + return groupMembers.value + } + + const keyword = memberSearchKeyword.value.toLowerCase().trim() + return groupMembers.value.filter((member: any) => + member.realname.toLowerCase().includes(keyword) || + member.username.toLowerCase().includes(keyword) + ) +}) + +// 计算属性:显示的群成员列表 +const displayedMembers = computed(() => { + const members = filteredMembers.value + + if (showAllMembers.value || members.length <= maxDisplayMembers) { + return members + } + return members.slice(0, maxDisplayMembers) +}) + +// 计算属性:是否需要显示"查看更多"按钮 +const shouldShowViewMore = computed(() => { + return filteredMembers.value.length > maxDisplayMembers && !showAllMembers.value +}) + // 生命周期钩子 onMounted(() => { loadContacts() @@ -407,7 +506,8 @@ const loadContacts = async () => { lastMessageTime: formatTime(chat.lastMessageTime || chat.updateTime), unreadCount: chat.unreadCount || 0, isOnline: chat.isOnline, - memberCount: chat.memberCount || (contactType === 'group' ? 0 : undefined) + memberCount: chat.memberCount || (contactType === 'group' ? 0 : undefined), + izAllMuted: chat.izAllMuted === 1 } }) @@ -446,6 +546,112 @@ const loadGroupMemberCount = async (chatId: string) => { } } +// 加载群成员列表 +const loadGroupMembers = async (chatId: string) => { + try { + const response = await ChatApi.getChatMembers(chatId) + + if (response.data && response.data.success) { + groupMembers.value = response.data.result || [] + console.log('群成员加载成功:', groupMembers.value.length, '个成员') + + // 从联系人数据中获取群组名称和禁言状态 + const contact = contacts.value.find((c: Contact) => c.id === chatId) + const groupName = contact ? contact.name : '暂无' + + // 更新群组信息 + groupInfo.value = { + name: groupName, + memberCount: groupMembers.value.length + } + + } else { + console.log('API响应失败:', response.data) + } + } catch (error) { + console.error('获取群成员失败:', error) + message.error('获取群成员失败') + } +} + +// 处理群成员搜索 +const handleMemberSearch = () => { + isSearchingMembers.value = true + + // 模拟搜索延迟 + setTimeout(() => { + isSearchingMembers.value = false + }, 300) +} + +// 获取群聊详情(包括禁言状态) +const loadChatDetail = async (chatId: string) => { + try { + const response = await ChatApi.getChatDetail(chatId) + + if (response.data && response.data.success) { + const chatDetail = response.data.result + + // 更新禁言状态 + if (chatDetail.izAllMuted !== undefined) { + isAllMuted.value = chatDetail.izAllMuted === 1 + } else { + // 备用方案:从联系人数据中获取禁言状态 + const contact = contacts.value.find((c: Contact) => c.id === chatId) + if (contact && contact.izAllMuted !== undefined) { + isAllMuted.value = contact.izAllMuted === 1 || contact.izAllMuted === true + } + } + + return chatDetail + } + } catch (error) { + console.error('获取群聊详情失败:', error) + + // 如果API调用失败,尝试从联系人数据中获取状态 + const contact = contacts.value.find((c: Contact) => c.id === chatId) + if (contact && contact.izAllMuted !== undefined) { + isAllMuted.value = contact.izAllMuted === 1 || contact.izAllMuted === true + } + } +} + +// 处理全员禁言切换 +const handleMuteAllToggle = async () => { + if (!activeContactId.value) return + + isMuteLoading.value = true + + // 保存当前状态,用于错误回滚 + const previousState = isAllMuted.value + + try { + if (isAllMuted.value) { + // 开启全员禁言 + await ChatApi.muteAll(activeContactId.value) + message.success('已开启全员禁言') + } else { + // 关闭全员禁言 + await ChatApi.unmuteAll(activeContactId.value) + message.success('已关闭全员禁言') + } + + // 更新联系人列表中的状态 + const contact = contacts.value.find((c: Contact) => c.id === activeContactId.value) + if (contact) { + contact.izAllMuted = isAllMuted.value ? 1 : 0 + } + + } catch (error) { + console.error('切换全员禁言状态失败:', error) + message.error('操作失败,请重试') + // 回滚状态 + isAllMuted.value = previousState + } finally { + isMuteLoading.value = false + } +} + // 加载指定会话的消息 const loadMessages = async (chatId: string) => { messagesLoading.value = true @@ -538,6 +744,16 @@ const currentMessages = computed(() => { const selectContact = async (contactId: string) => { activeContactId.value = contactId + // 重置群成员展开状态、群组信息和搜索状态 + showAllMembers.value = false + memberSearchKeyword.value = '' + isSearchingMembers.value = false + isMuteLoading.value = false + groupInfo.value = { + name: '', + memberCount: 0 + } + // 清除未读数量 const contact = contacts.value.find((c: Contact) => c.id === contactId) if (contact) { @@ -547,17 +763,23 @@ const selectContact = async (contactId: string) => { if (contact.type === 'group' && (!contact.memberCount || contact.memberCount === 0)) { loadGroupMemberCount(contactId) } + + // 如果是群聊,加载群成员列表和群聊详情 + if (contact.type === 'group') { + loadGroupMembers(contactId) + loadChatDetail(contactId) // 获取群聊详情,包括禁言状态 + } } // 加载该会话的消息 await loadMessages(contactId) - // 标记消息为已读 - try { - await ChatApi.markAsRead(contactId) - } catch (error) { - console.warn('标记消息已读失败:', error) - } + // 暂时移除标记消息已读的API调用,避免错误 + // try { + // await ChatApi.markAsRead(contactId) + // } catch (error) { + // console.warn('标记消息已读失败:', error) + // } nextTick(() => { scrollToBottom() @@ -1011,7 +1233,6 @@ onMounted(() => { .contact-header { display: flex; - justify-content: space-between; align-items: center; margin-bottom: 10px; color: #3F3F44; @@ -1033,11 +1254,6 @@ onMounted(() => { margin-left: 4px; } -.contact-time { - font-size: 12px; - color: #999; - flex-shrink: 0; -} .contact-preview { display: flex; @@ -1321,6 +1537,33 @@ onMounted(() => { word-break: break-word; } +/* 禁言状态提示样式 */ +.mute-status-tip { + display: flex; + justify-content: center; + margin: 12px 0 8px 0; + padding: 0 20px; +} + +.mute-tip-content { + display: flex; + align-items: center; + gap: 6px; + background: #e0e6ec; + color: #6c757d; + padding: 2px 8px; + border-radius: 30px; + font-size: 12px; + border: 1px solid #e9ecef; + max-width: 300px; + text-align: center; +} + +.mute-icon { + color: #6c757d; + flex-shrink: 0; +} + .image-bubble { padding: 4px; background: transparent; @@ -1473,6 +1716,62 @@ onMounted(() => { border-color: #1890ff; } +.search-loading { + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + display: flex; + align-items: center; + justify-content: center; +} + +.loading-spinner { + width: 16px; + height: 16px; + border: 2px solid #f3f3f3; + border-top: 2px solid #1890ff; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +/* 空状态样式 */ +.empty-search-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 20px; + text-align: center; +} + +.empty-icon { + color: #d9d9d9; + margin-bottom: 16px; +} + +.empty-text { + font-size: 14px; + color: #666; + margin-bottom: 8px; + font-weight: 500; +} + +.empty-hint { + font-size: 12px; + color: #999; +} + .members-section { margin: 0 20px; border-bottom: 1.5px solid #E6E6E6; @@ -1526,20 +1825,28 @@ onMounted(() => { font-size: 10px; cursor: pointer; margin-bottom: 15px; - transition: color 0.2s ease; + margin-top: 8px; + padding: 6px 12px; + background: #f8f8f8; + border: 1px solid #e0e0e0; + border-radius: 4px; + transition: all 0.2s ease; } .view-more:hover { color: #1890ff; + background: #f0f8ff; + border-color: #1890ff; } .chevron-icon { color: #666666; - transition: color 0.2s ease; + transition: all 0.2s ease; } .view-more:hover .chevron-icon { color: #1890ff; + transform: translateY(1px); } /* 私聊详情样式 */ @@ -1670,6 +1977,24 @@ onMounted(() => { transform: translateX(20px); } +.switch-label.loading { + opacity: 0.6; + cursor: not-allowed; +} + +.switch-label.loading:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 12px; + height: 12px; + margin: -6px 0 0 -6px; + border: 2px solid #f3f3f3; + border-top: 2px solid #0288D1; + border-radius: 50%; + animation: spin 1s linear infinite; +} .actions-section { margin: 0 20px; display: flex;