From 27cd042df9aca0383a49f3514bc21958dd890912 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 29 六月 2026 15:42:23 +0800
Subject: [PATCH] 银川 1.联调产品维护页面 2.添加IM即时通讯模块
---
src/views/im/home/pages/conversation/components/message/message-read-status.vue | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 169 insertions(+), 0 deletions(-)
diff --git a/src/views/im/home/pages/conversation/components/message/message-read-status.vue b/src/views/im/home/pages/conversation/components/message/message-read-status.vue
new file mode 100644
index 0000000..df5152c
--- /dev/null
+++ b/src/views/im/home/pages/conversation/components/message/message-read-status.vue
@@ -0,0 +1,169 @@
+<script lang="ts" setup>
+import type { Message } from '../../../../types'
+
+import { computed, ref } from 'vue'
+
+import { CommonStatusEnum } from '#/packages/constants/src'
+
+import { Popover, TabPane, Tabs } from 'ant-design-vue'
+
+import { getGroupReadUsers as apiGetGroupReadUsers } from '#/api/im/message/group'
+
+import { ImConversationType, ImMessageReceiptStatus } from '../../../../../utils/constants'
+import { PagedScroller } from '../../../../components'
+import { GroupMember, type GroupMemberLite } from '../../../../components/group'
+import { useMessageStore } from '../../../../store/messageStore'
+
+defineOptions({ name: 'ImMessageReadStatus' })
+
+const props = defineProps<{
+ // 褰撳墠缇ょ紪鍙凤紱渚� loadReadUsers 浣滀负 /im/message/group/get-read-users 鐨勫叆鍙�
+ groupId: number
+ // 褰撳墠缇ゆ墍鏈夋垚鍛橈紙澶栭儴 MessagePanel.groupMembers 浼犲叆锛涙病鏈夊氨浼犵┖鏁扮粍锛屾湭璇诲垪琛ㄤ細鍙樼┖浣嗕笉鎶ラ敊锛�
+ groupMembers: GroupMemberLite[]
+ message: Message
+}>()
+
+const messageStore = useMessageStore()
+
+// popover 寮�鍏筹細show 鏃舵媺宸茶鍚嶅崟锛屽叧闂悗淇濈暀 readUserIds 缂撳瓨锛堥噸寮�鍚屼竴鏉℃秷鎭笉鍐嶈姹傦級
+const popVisible = ref(false)
+const activeTab = ref<'read' | 'unread'>('read')
+// 鏈嶅姟绔繑鍥炵殑"宸茶杩欐潯娑堟伅鐨� userId 鍒楄〃"锛屾湭璇婚潬 visibleMembers 鍑忓幓杩欎唤寰楀埌
+const readUserIds = ref<number[]>([])
+
+/**
+ * 鏍囩鏂囨锛�
+ * - receiptStatus === DONE锛氭湇鍔$纭鍏ㄥ憳宸茶 鈫� 鐩存帴鏄剧ず"鍏ㄩ儴宸茶"锛宺eadCount 涓嶅啀浣跨敤
+ * - readCount > 0锛氭樉绀�"N 浜哄凡璇�"
+ * - 鍏朵粬锛坮eadCount = 0 鎴� undefined锛屼笖鏈埌 DONE锛夛細鏄剧ず"鏈"
+ */
+const label = computed(() => {
+ if (props.message.receiptStatus === ImMessageReceiptStatus.DONE) {
+ return '鍏ㄩ儴宸茶'
+ }
+ const readCount = props.message.readCount || 0
+ return readCount > 0 ? `${readCount} 浜哄凡璇籤 : '鏈'
+})
+
+/**
+ * 杩欐潯娑堟伅"搴旇琚皝鐪嬪埌"鐨勫彲瑙佹垚鍛橀泦鍚堬紙宸茶 / 鏈涓や釜 tab 鍏辩敤鍩哄簳锛�
+ *
+ * 涓夊眰杩囨护锛�
+ * 1. 瀹氬悜缇ゆ秷鎭細receiverUserIds 闈炵┖鏃讹紝鍙畻鍚嶅崟鍐呯殑鎴愬憳銆傚皯浜嗚繖灞傦紝鏈湪鎶曢�掕寖鍥撮噷鐨勪汉
+ * 浼氳閿欑畻鎴�"鏈"锛坓etGroupReadUsers 鍚庣宸叉寜鍙鎬ц繃婊わ紝鍓嶇蹇呴』閰嶅锛�
+ * 2. 鍙戦�佽�呮湰浜猴細鑷繁鍙戠殑娑堟伅绠椾笉绠�"鑷繁宸茶"璇箟妯$硦锛岀储鎬т袱涓� tab 閮戒笉灞曠ず
+ * 3. 宸查��缇わ紙status === DISABLE锛夛細浠栬娌¤宸茬粡涓嶅叧蹇冿紝UI 涓嶅睍绀�
+ */
+const visibleMembers = computed<GroupMemberLite[]>(() => {
+ const receiverUserIds = props.message.receiverUserIds
+ const isDirected = !!receiverUserIds && receiverUserIds.length > 0
+ return props.groupMembers.filter(
+ (member) =>
+ member.status !== CommonStatusEnum.DISABLE &&
+ member.userId !== props.message.senderId &&
+ (!isDirected || receiverUserIds.includes(member.userId))
+ )
+})
+
+/** 宸茶 = 鍙鎴愬憳 鈭� readUserIds */
+const readMembers = computed(() =>
+ visibleMembers.value.filter((member) => readUserIds.value.includes(member.userId))
+)
+
+/** 鏈 = 鍙鎴愬憳 鈭� readUserIds */
+const unreadMembers = computed(() =>
+ visibleMembers.value.filter((member) => !readUserIds.value.includes(member.userId))
+)
+
+/**
+ * 鎷夊彇宸茶鐢ㄦ埛 id 鍒楄〃
+ *
+ * 瑙﹀彂锛歟l-popover @show锛堟寜闇�鎳掑姞杞斤紝閬垮厤姣忔潯缇ゆ秷鎭兘棰勬媺锛�
+ * 璺宠繃锛氭湰鍦板崰浣嶆秷鎭紙id = 0锛岃繕娌℃嬁鍒版湇鍔$ id锛夛紝鍚庣娌℃硶鎸� messageId 鏌�
+ * 澶辫触锛氫粎鎺у埗鍙板憡璀︼紝readUserIds 淇濇寔绌烘暟缁� 鈫� label 璧� readCount 鍏滃簳锛屼笉闃诲 UI
+ *
+ * 鎷夊埌鍚嶅崟鍚庨『鎵嬫妸 readCount / receiptStatus 鍥炲啓鍒� messageStore锛岃 popover 澶栭潰鐨�
+ * label 涔熻窡鐫�璧版渶鏂版暟锛氱绾� / 婕忔敹 RECEIPT 浜嬩欢鏃舵湰鍦� readCount 浼氬亸鏃э紝寮瑰眰閲岀湅鍒�"宸茶 5"
+ * 浣嗗闈粛鏄�"鏈"鎴栨棫浜烘暟锛涜繖閲屼互鏈嶅姟绔繑鍥炰负鍑嗙煫姝e洖鍘�
+ */
+async function loadReadUsers() {
+ if (!props.message.id) {
+ return
+ }
+ try {
+ const userIds = await apiGetGroupReadUsers({
+ groupId: props.groupId,
+ messageId: props.message.id
+ })
+ readUserIds.value = userIds || []
+ const readCount = readUserIds.value.length
+ // 鍏ㄥ彲瑙佹垚鍛橀兘宸茶 鈫� 鏇存柊涓� DONE锛岃澶栭潰 label 鐩存帴鍛戒腑銆屽叏閮ㄥ凡璇汇�嶅垎鏀紱
+ // 鍚﹀垯鍙洿鏂� readCount锛宺eceiptStatus 缁存寔涓嶅彉锛圥ENDING锛�
+ const allRead = readCount > 0 && readCount >= visibleMembers.value.length
+ messageStore.applyMessageReadReceipt({
+ conversationType: ImConversationType.GROUP,
+ targetId: props.groupId,
+ groupMessageId: props.message.id,
+ readCount,
+ receiptStatus: allRead ? ImMessageReceiptStatus.DONE : undefined
+ })
+ } catch (error) {
+ console.error('[IM] 鎷夊彇缇ゅ凡璇诲垪琛ㄥけ璐�:', error)
+ }
+}
+</script>
+
+<template>
+ <!--
+ 缇ゆ秷鎭凡璇荤姸鎬�
+ - 鏍囩褰㈡�侊細灞曠ず銆孨 宸茶銆嶆垨銆屽叏閮ㄥ凡璇汇�嶏紱鐐瑰嚮寮瑰嚭 popover 鐢� tab 鍒楀嚭宸茶 / 鏈鎴愬憳
+ - 娓叉煋鏉′欢锛氫粎鍦ㄧ兢鑱娿�佽嚜宸卞彂閫併�乺eceiptStatus !== NO_RECEIPT 鏃舵寕鍑猴紙鐢� MessageItem 鎶婂叧锛�
+ - 宸茶鍚嶅崟鎸夐渶鎷夛細popover 瑙﹀彂 show 鎵嶆墦 /im/message/group/get-read-users锛岄伩鍏嶆瘡鏉$兢娑堟伅閮介鎷�
+ - 鏈鍚嶅崟 = 褰撳墠缇ゆ垚鍛� 鈭� 鑷繁 鈭� 宸查��缇� 鈭� 宸茶闆嗗悎锛堝墠绔仛鍚堬紝鍚庣鍙洖宸茶 userId锛�
+ -->
+ <Popover
+ v-model:open="popVisible"
+ placement="left"
+ trigger="click"
+ :overlay-style="{ width: '320px' }"
+ @open-change="(open) => open && loadReadUsers()"
+ >
+ <span
+ class="text-12px whitespace-nowrap cursor-pointer text-[var(--ant-color-text-secondary)] hover:text-[#409eff]"
+ >
+ {{ label }}
+ </span>
+
+ <template #content>
+ <Tabs v-model:active-key="activeTab" centered>
+ <TabPane :tab="`宸茶(${readMembers.length})`" key="read">
+ <PagedScroller :items="readMembers" :page-size="20" item-key="userId" class="h-75">
+ <template #default="{ item }">
+ <GroupMember :member="item as GroupMemberLite" :height="40" :clickable="false" />
+ </template>
+ </PagedScroller>
+ <div
+ v-if="readMembers.length === 0"
+ class="py-5 text-12px text-center text-[var(--ant-color-text-disabled)]"
+ >
+ {{ groupMembers.length === 0 ? '缇ゆ垚鍛樻湭鍔犺浇' : '鏆傛棤宸茶' }}
+ </div>
+ </TabPane>
+ <TabPane :tab="`鏈(${unreadMembers.length})`" key="unread">
+ <PagedScroller :items="unreadMembers" :page-size="20" item-key="userId" class="h-75">
+ <template #default="{ item }">
+ <GroupMember :member="item as GroupMemberLite" :height="40" :clickable="false" />
+ </template>
+ </PagedScroller>
+ <div
+ v-if="unreadMembers.length === 0"
+ class="py-5 text-12px text-center text-[var(--ant-color-text-disabled)]"
+ >
+ {{ groupMembers.length === 0 ? '缇ゆ垚鍛樻湭鍔犺浇' : '鍏ㄩ儴宸茶' }}
+ </div>
+ </TabPane>
+ </Tabs>
+ </template>
+ </Popover>
+</template>
--
Gitblit v1.9.3