diff --git a/frontend_nuxt/components/HeaderComponent.vue b/frontend_nuxt/components/HeaderComponent.vue index 5963eb5bc..95534f137 100644 --- a/frontend_nuxt/components/HeaderComponent.vue +++ b/frontend_nuxt/components/HeaderComponent.vue @@ -6,7 +6,10 @@ - +
- {{ - unreadMessageCount - }} + + {{ messageUnreadCount }} + +
@@ -102,7 +106,10 @@ const props = defineProps({ const isLogin = computed(() => authState.loggedIn) const isMobile = useIsMobile() -const { count: unreadMessageCount, fetchUnreadCount } = useUnreadCount() +const { count: totalUnreadCount, channelUnreadCount, fetchUnreadCount } = useUnreadCount() +const messageUnreadCount = computed(() => + Math.max(totalUnreadCount.value - channelUnreadCount.value, 0), +) const avatar = ref('') const showSearch = ref(false) const searchDropdown = ref(null) @@ -413,6 +420,16 @@ onMounted(async () => { box-sizing: border-box; } +.messages-unread-dot { + position: absolute; + top: -2px; + right: -4px; + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #ff4d4f; +} + .rss-icon { animation: rss-glow 2s 3; } diff --git a/frontend_nuxt/composables/useUnreadCount.js b/frontend_nuxt/composables/useUnreadCount.js index 381b67d6a..58aeecbaa 100644 --- a/frontend_nuxt/composables/useUnreadCount.js +++ b/frontend_nuxt/composables/useUnreadCount.js @@ -1,93 +1,123 @@ -import { ref, watch, onMounted } from 'vue'; -import { useWebSocket } from './useWebSocket'; -import { getToken } from '~/utils/auth'; +import { ref, watch } from 'vue' +import { useWebSocket } from './useWebSocket' +import { getToken } from '~/utils/auth' -const count = ref(0); -let isInitialized = false; -let wsSubscription = null; +const count = ref(0) +const channelUnreadCount = ref(0) +let isInitialized = false +let wsSubscription = null export function useUnreadCount() { - const config = useRuntimeConfig(); - const API_BASE_URL = config.public.apiBaseUrl; - const { subscribe, isConnected, connect } = useWebSocket(); + const config = useRuntimeConfig() + const API_BASE_URL = config.public.apiBaseUrl + const { subscribe, isConnected, connect } = useWebSocket() - const fetchUnreadCount = async () => { - const token = getToken(); + const fetchTotalUnreadCount = async () => { + const token = getToken() if (!token) { - count.value = 0; - return; + count.value = 0 + return } try { const response = await fetch(`${API_BASE_URL}/api/messages/unread-count`, { headers: { Authorization: `Bearer ${token}` }, - }); + }) if (response.ok) { - const data = await response.json(); - count.value = data; + const data = await response.json() + count.value = data } } catch (error) { - console.error('Failed to fetch unread count:', error); + console.error('Failed to fetch unread count:', error) } - }; + } + + const fetchChannelUnreadCount = async () => { + const token = getToken() + if (!token) { + channelUnreadCount.value = 0 + return + } + try { + const response = await fetch(`${API_BASE_URL}/api/channels`, { + headers: { Authorization: `Bearer ${token}` }, + }) + if (response.ok) { + const channels = await response.json() + channelUnreadCount.value = channels.reduce((sum, ch) => sum + (ch.unreadCount || 0), 0) + } + } catch (error) { + console.error('Failed to fetch channel unread count:', error) + } + } + + const fetchUnreadCount = async () => { + await Promise.all([fetchTotalUnreadCount(), fetchChannelUnreadCount()]) + } const initialize = async () => { - const token = getToken(); + const token = getToken() if (!token) { - count.value = 0; - return; + count.value = 0 + channelUnreadCount.value = 0 + return } // 总是获取最新的未读数量 - fetchUnreadCount(); - + fetchUnreadCount() + // 确保WebSocket连接 if (!isConnected.value) { - connect(token); + connect(token) } - + // 设置WebSocket监听 - await setupWebSocketListener(); - }; + await setupWebSocketListener() + } const setupWebSocketListener = async () => { // 只有在还没有订阅的情况下才设置监听 if (!wsSubscription) { - - watch(isConnected, (newValue) => { - if (newValue && !wsSubscription) { - const destination = `/user/queue/unread-count`; - wsSubscription = subscribe(destination, (message) => { - const unreadCount = parseInt(message.body, 10); - if (!isNaN(unreadCount)) { - count.value = unreadCount; - } - }); - } - }, { immediate: true }); + watch( + isConnected, + (newValue) => { + if (newValue && !wsSubscription) { + const destination = `/user/queue/unread-count` + wsSubscription = subscribe(destination, (message) => { + const unreadCount = parseInt(message.body, 10) + if (!isNaN(unreadCount)) { + count.value = unreadCount + fetchChannelUnreadCount() + } + }) + } + }, + { immediate: true }, + ) } - }; + } // 自动初始化逻辑 - 确保每次调用都能获取到未读数量并设置监听 - const token = getToken(); + const token = getToken() if (token) { if (!isInitialized) { - isInitialized = true; - initialize(); // 完整初始化,包括WebSocket监听 + isInitialized = true + initialize() // 完整初始化,包括WebSocket监听 } else { // 即使已经初始化,也要确保获取最新的未读数量并确保WebSocket监听存在 - fetchUnreadCount(); - + fetchUnreadCount() + // 确保WebSocket连接和监听都存在 if (!isConnected.value) { - connect(token); + connect(token) } - setupWebSocketListener(); + setupWebSocketListener() } } return { count, + channelUnreadCount, fetchUnreadCount, initialize, - }; -} \ No newline at end of file + } +}