160 lines
4.7 KiB
TypeScript
160 lines
4.7 KiB
TypeScript
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<ImportantNotification | null> | null = null
|
|
let inFlightUserId: number | null = null
|
|
let requestVersion = 0
|
|
|
|
export const useImportantNotification = () => {
|
|
const notification = useState<ImportantNotification | null>('important-notification', () => null)
|
|
const loading = useState<boolean>('important-notification-loading', () => false)
|
|
const closing = useState<boolean>('important-notification-closing', () => false)
|
|
const error = useState<string>('important-notification-error', () => '')
|
|
const checkedUserId = useState<number | null>('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
|
|
}
|
|
}
|