|
|
@@ -12,6 +12,18 @@
|
|
|
<el-button icon="Plus" circle class="add-btn" @click="showUserSelector = true" />
|
|
|
</div>
|
|
|
|
|
|
+ <div class="conversation-toolbar">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ link
|
|
|
+ :loading="markingAllRead"
|
|
|
+ :disabled="conversations.length === 0"
|
|
|
+ @click="markAllRead"
|
|
|
+ >
|
|
|
+ 全部已读
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div class="conversation-list" v-loading="loadingConversations">
|
|
|
<div
|
|
|
v-for="chat in filteredConversations"
|
|
|
@@ -43,8 +55,8 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div v-if="(messageStore.unreadMap[chat.user_id] || 0) > 0" class="unread-badge">
|
|
|
- {{ messageStore.unreadMap[chat.user_id] > 99 ? '99+' : messageStore.unreadMap[chat.user_id] }}
|
|
|
+ <div v-if="(chat.unread_count || 0) > 0" class="unread-badge">
|
|
|
+ {{ (chat.unread_count || 0) > 99 ? '99+' : chat.unread_count }}
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -265,7 +277,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, onMounted, onUnmounted, computed, nextTick } from 'vue'
|
|
|
+import { ref, onMounted, onUnmounted, onActivated, computed, nextTick } from 'vue'
|
|
|
import UserAvatar from '@/components/UserAvatar.vue'
|
|
|
import { Picture, Document, House, ArrowRight } from '@element-plus/icons-vue'
|
|
|
import api from '@/utils/request'
|
|
|
@@ -293,6 +305,7 @@ const showUserSelector = ref(false)
|
|
|
const selectedUserId = ref<number | null>(null)
|
|
|
const userOptions = ref<any[]>([])
|
|
|
const searchingUsers = ref(false)
|
|
|
+const markingAllRead = ref(false)
|
|
|
|
|
|
// 应用名称缓存
|
|
|
const appNameCache = ref<Record<number, string>>({})
|
|
|
@@ -307,6 +320,16 @@ const filteredConversations = computed(() => {
|
|
|
)
|
|
|
})
|
|
|
|
|
|
+// WS 推送到来时同步会话列表上的 unread_count(防抖)
|
|
|
+let convRefreshTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
+const scheduleRefreshConversations = () => {
|
|
|
+ if (convRefreshTimer) clearTimeout(convRefreshTimer)
|
|
|
+ convRefreshTimer = setTimeout(() => {
|
|
|
+ convRefreshTimer = null
|
|
|
+ fetchConversations()
|
|
|
+ }, 400)
|
|
|
+}
|
|
|
+
|
|
|
// WS message handler (called by store when a new message arrives)
|
|
|
const handleWsMessage = (newMessage: any) => {
|
|
|
const isSystemNotification = newMessage.type === 'NOTIFICATION' || newMessage.sender_id === null
|
|
|
@@ -338,18 +361,33 @@ const handleWsMessage = (newMessage: any) => {
|
|
|
newMessage.content_type === 'USER_NOTIFICATION' ? { title: newMessage.title } : undefined
|
|
|
)
|
|
|
}
|
|
|
+
|
|
|
+ scheduleRefreshConversations()
|
|
|
+}
|
|
|
+
|
|
|
+const refreshMessagePage = () => {
|
|
|
+ fetchConversations()
|
|
|
+ messageStore.fetchUnreadCount()
|
|
|
}
|
|
|
|
|
|
// Lifecycle
|
|
|
let unsubscribeWs: (() => void) | null = null
|
|
|
|
|
|
onMounted(() => {
|
|
|
- fetchConversations()
|
|
|
+ refreshMessagePage()
|
|
|
if (!currentUser.value) authStore.fetchUser()
|
|
|
unsubscribeWs = onNewMessage(handleWsMessage)
|
|
|
})
|
|
|
|
|
|
+onActivated(() => {
|
|
|
+ refreshMessagePage()
|
|
|
+})
|
|
|
+
|
|
|
onUnmounted(() => {
|
|
|
+ if (convRefreshTimer) {
|
|
|
+ clearTimeout(convRefreshTimer)
|
|
|
+ convRefreshTimer = null
|
|
|
+ }
|
|
|
messageStore.setCurrentChat(null)
|
|
|
if (unsubscribeWs) unsubscribeWs()
|
|
|
})
|
|
|
@@ -371,9 +409,33 @@ const selectChat = async (chat: any) => {
|
|
|
currentChatId.value = chat.user_id
|
|
|
currentChatUser.value = chat
|
|
|
messageStore.setCurrentChat(chat.user_id)
|
|
|
+ try {
|
|
|
+ await api.put(`/messages/history/${chat.user_id}/read-all`)
|
|
|
+ const conv = conversations.value.find(c => c.user_id === chat.user_id)
|
|
|
+ if (conv) conv.unread_count = 0
|
|
|
+ await messageStore.fetchUnreadCount()
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e)
|
|
|
+ }
|
|
|
await loadHistory(chat.user_id)
|
|
|
}
|
|
|
|
|
|
+const markAllRead = async () => {
|
|
|
+ markingAllRead.value = true
|
|
|
+ try {
|
|
|
+ await api.put('/messages/read-all')
|
|
|
+ conversations.value.forEach(c => {
|
|
|
+ c.unread_count = 0
|
|
|
+ })
|
|
|
+ await messageStore.fetchUnreadCount()
|
|
|
+ ElMessage.success('已全部标为已读')
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e)
|
|
|
+ } finally {
|
|
|
+ markingAllRead.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
const loadHistory = async (userId: number) => {
|
|
|
try {
|
|
|
const res = await api.get(`/messages/history/${userId}`)
|
|
|
@@ -623,6 +685,15 @@ const handleNotificationAction = async (msg: any) => {
|
|
|
border-bottom: 1px solid #e6e6e6;
|
|
|
}
|
|
|
|
|
|
+.conversation-toolbar {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ align-items: center;
|
|
|
+ padding: 6px 15px;
|
|
|
+ background: #f0f0f0;
|
|
|
+ border-bottom: 1px solid #e6e6e6;
|
|
|
+}
|
|
|
+
|
|
|
.conversation-list {
|
|
|
flex: 1;
|
|
|
overflow-y: auto;
|