import { useState } from '#app' import { useAuth } from '~/composables/useAuth' import { useServerErrors } from '~/composables/useLocaleText' import { useToast } from '~/composables/useToast' import { useLocale } from '~/utils/locale' export interface ImportantNotification { id: number title: string | null message: string important: boolean read: boolean createdAt: string sender: { id: number name: string | null username: string | null } | null } let inFlightCheck: Promise | null = null let inFlightUserId: number | null = null let requestVersion = 0 export const useImportantNotification = () => { const notification = useState('important-notification', () => null) const loading = useState('important-notification-loading', () => false) const closing = useState('important-notification-closing', () => false) const error = useState('important-notification-error', () => '') const checkedUserId = useState('important-notification-user', () => null) const { user, isAuthenticated } = useAuth() const { localize: localizeServerError } = useServerErrors() const { importantNotification: locale } = useLocale() const toast = useToast() const resetImportantNotification = () => { requestVersion += 1 notification.value = null loading.value = false closing.value = false error.value = '' checkedUserId.value = null inFlightCheck = null inFlightUserId = null } const checkImportantNotification = async (force = false) => { if (import.meta.server) return null const currentUserId = user.value?.id if (!isAuthenticated.value || !currentUserId || user.value?.requirePasswordChange === true) { resetImportantNotification() return null } if (!force && checkedUserId.value === currentUserId) { return notification.value } if (inFlightCheck && inFlightUserId === currentUserId) { return inFlightCheck } const activeRequestVersion = requestVersion inFlightUserId = currentUserId loading.value = true error.value = '' const checkPromise = $fetch<{ notification: ImportantNotification | null }>( '/api/notifications/important' ) .then((response) => { if ( activeRequestVersion !== requestVersion || !isAuthenticated.value || user.value?.id !== currentUserId ) { return null } notification.value = response.notification checkedUserId.value = currentUserId return response.notification }) .catch((fetchError) => { if (activeRequestVersion !== requestVersion) return null error.value = localizeServerError(fetchError, locale.value.loadFailed) toast.error(error.value) return null }) .finally(() => { if (activeRequestVersion === requestVersion) { loading.value = false } if (inFlightCheck === checkPromise) { inFlightCheck = null inFlightUserId = null } }) inFlightCheck = checkPromise return checkPromise } const markAsReadAndClose = async () => { const currentNotification = notification.value const currentUserId = user.value?.id if (!currentNotification || !currentUserId || closing.value) return false closing.value = true error.value = '' try { await $fetch(`/api/notifications/${currentNotification.id}/read`, { method: 'POST' }) if (user.value?.id !== currentUserId) { resetImportantNotification() return true } if (notification.value?.id === currentNotification.id) { notification.value = { ...notification.value, read: true } notification.value = null } checkedUserId.value = null await checkImportantNotification(true) return true } catch (fetchError) { // 通知已被管理员编辑重发或删除时旧 ID 会 404,按已不存在处理,避免弹窗无法关闭 const notFoundError = fetchError as { statusCode?: number; response?: { status?: number } } if (notFoundError?.statusCode === 404 || notFoundError?.response?.status === 404) { if (notification.value?.id === currentNotification.id) { notification.value = null } checkedUserId.value = null await checkImportantNotification(true) return true } error.value = localizeServerError(fetchError, locale.value.closeFailed) return false } finally { closing.value = false } } return { notification, loading, closing, error, checkedUserId, checkImportantNotification, markAsReadAndClose, resetImportantNotification } }