Files
Henry_Du 4ce4649aaf
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s
初次提交
2026-09-06 08:19:02 +08:00

432 lines
11 KiB
TypeScript

import { computed, ref } from 'vue'
import { useAuth } from './useAuth'
import type { Notification, NotificationSettings } from '~~/types'
import { useToast } from './useToast'
interface PaginationInfo {
currentPage: number
totalPages: number
totalCount: number
limit: number
hasNextPage: boolean
hasPrevPage: boolean
}
let lastTopNotificationId: number | null = null
let lastUnreadCountSnapshot: number | null = null
let notificationsBootstrapped = false
let lastToastAt = 0
export const useNotifications = () => {
const { getAuthConfig, isAuthenticated } = useAuth()
const toast = useToast()
const notifications = ref<Notification[]>([])
const unreadCount = ref(0)
const settings = ref<NotificationSettings | null>(null)
const loading = ref(false)
const error = ref('')
// 分页相关状态
const currentPage = ref(1)
const pageSize = ref(10)
const totalPages = ref(0)
const totalCount = ref(0)
const hasNextPage = computed(() => currentPage.value < totalPages.value)
const hasPrevPage = computed(() => currentPage.value > 1)
const isPaginationLoading = ref(false)
const fetchNotifications = async (page?: number, limit?: number) => {
if (!isAuthenticated.value) {
notifications.value = []
unreadCount.value = 0
resetPagination()
lastTopNotificationId = null
lastUnreadCountSnapshot = null
notificationsBootstrapped = false
return
}
// 如果是分页请求(非初始加载),显示分页加载状态
if (page && page !== 1) {
isPaginationLoading.value = true
} else {
loading.value = true
}
error.value = ''
// 使用传入的参数或当前状态
const requestPage = page || currentPage.value
const requestLimit = limit || pageSize.value
try {
const authConfig = getAuthConfig()
const data = await $fetch('/api/notifications', {
query: {
page: requestPage,
limit: requestLimit
},
...authConfig
})
if (data) {
const nextNotifications: Notification[] = data.notifications || []
const nextUnread = data.unreadCount || 0
notifications.value = data.notifications || []
unreadCount.value = nextUnread
const isFirstPage = (page || currentPage.value) === 1
const topId = nextNotifications[0]?.id ?? null
if (isFirstPage) {
if (!notificationsBootstrapped) {
notificationsBootstrapped = true
lastTopNotificationId = topId
lastUnreadCountSnapshot = nextUnread
} else {
const now = Date.now()
const prevTopId = lastTopNotificationId
const prevUnread = lastUnreadCountSnapshot ?? 0
const delta = Math.max(0, nextUnread - prevUnread)
const hasNewTop =
(topId && prevTopId && topId > prevTopId) || (topId && prevTopId === null)
if (hasNewTop && now - lastToastAt > 3000) {
toast.info(delta > 0 ? `收到 ${delta} 条新消息` : '收到新消息')
lastToastAt = now
}
lastTopNotificationId = topId
lastUnreadCountSnapshot = nextUnread
}
}
// 更新分页信息
if (data.pagination) {
currentPage.value = data.pagination.currentPage
totalPages.value = data.pagination.totalPages
totalCount.value = data.pagination.totalCount
pageSize.value = data.pagination.limit
}
}
} catch (err: any) {
const errorHandler = useErrorHandler()
if (await errorHandler.checkAndHandleFetchError(err)) {
return
}
console.error('获取通知错误:', err)
notifications.value = []
unreadCount.value = 0
resetPagination()
} finally {
loading.value = false
isPaginationLoading.value = false
}
}
// 重置分页状态
const resetPagination = () => {
currentPage.value = 1
totalPages.value = 0
totalCount.value = 0
}
// 分页导航方法
const goToPage = async (page: number) => {
if (page >= 1 && page <= totalPages.value && page !== currentPage.value) {
await fetchNotifications(page, pageSize.value)
}
}
const nextPage = async () => {
if (hasNextPage.value) {
await goToPage(currentPage.value + 1)
}
}
const prevPage = async () => {
if (hasPrevPage.value) {
await goToPage(currentPage.value - 1)
}
}
const changePageSize = async (newSize: number) => {
if (newSize !== pageSize.value) {
pageSize.value = newSize
// 重新计算当前页,确保不超出范围
const newTotalPages = Math.ceil(totalCount.value / newSize)
const newCurrentPage = Math.min(currentPage.value, newTotalPages || 1)
await fetchNotifications(newCurrentPage, newSize)
}
}
const fetchNotificationSettings = async () => {
if (!isAuthenticated.value) {
settings.value = null
return
}
loading.value = true
error.value = ''
try {
const authConfig = getAuthConfig()
const data = await $fetch('/api/notifications/settings', {
...authConfig
})
if (data) {
settings.value = data
}
} catch (err: any) {
const errorHandler = useErrorHandler()
if (await errorHandler.checkAndHandleFetchError(err)) {
return
}
console.error('获取通知设置错误:', err)
settings.value = null
} finally {
loading.value = false
}
}
const updateNotificationSettings = async (newSettings: Partial<NotificationSettings>) => {
if (!isAuthenticated.value) {
error.value = '需要登录才能更新通知设置'
return null
}
loading.value = true
error.value = ''
try {
const authConfig = getAuthConfig()
const data = await $fetch('/api/notifications/settings', {
method: 'POST',
body: newSettings,
...authConfig
})
if (data) {
settings.value = data
return data
}
return null
} catch (err: any) {
const errorHandler = useErrorHandler()
if (await errorHandler.checkAndHandleFetchError(err)) {
return null
}
error.value = err.message || '更新通知设置失败'
console.error('更新通知设置错误:', err)
return null
} finally {
loading.value = false
}
}
// 标记通知为已读
const markAsRead = async (notificationId: number) => {
if (!isAuthenticated.value) {
error.value = '需要登录才能标记通知'
return null
}
loading.value = true
error.value = ''
try {
const authConfig = getAuthConfig()
const data = await $fetch(`/api/notifications/${notificationId}/read`, {
method: 'POST',
...authConfig
})
const index = notifications.value.findIndex((n: Notification) => n.id === notificationId)
if (index !== -1) {
notifications.value[index].read = true
unreadCount.value = Math.max(0, unreadCount.value - 1)
}
return data
} catch (err: any) {
const errorHandler = useErrorHandler()
if (await errorHandler.checkAndHandleFetchError(err)) {
return null
}
error.value = err.message || '标记通知失败'
console.error('标记通知错误:', err)
return null
} finally {
loading.value = false
}
}
// 标记所有通知为已读
const markAllAsRead = async () => {
if (!isAuthenticated.value) {
error.value = '需要登录才能标记通知'
return null
}
loading.value = true
error.value = ''
try {
const authConfig = getAuthConfig()
const data = await $fetch('/api/notifications/read-all', {
method: 'POST',
...authConfig
})
if (data) {
notifications.value.forEach((n) => (n.read = true))
unreadCount.value = 0
return data
}
return null
} catch (err: any) {
const errorHandler = useErrorHandler()
if (await errorHandler.checkAndHandleFetchError(err)) {
return null
}
error.value = err.message || '标记所有通知失败'
console.error('标记所有通知错误:', err)
return null
} finally {
loading.value = false
}
}
// 删除通知
const deleteNotification = async (notificationId: number) => {
if (!isAuthenticated.value) {
error.value = '需要登录才能删除通知'
return null
}
loading.value = true
error.value = ''
try {
const authConfig = getAuthConfig()
await $fetch(`/api/notifications/${notificationId}`, {
method: 'DELETE',
...authConfig
})
// 更新本地通知列表
const index = notifications.value.findIndex((n: Notification) => n.id === notificationId)
if (index !== -1) {
if (!notifications.value[index].read) {
unreadCount.value = Math.max(0, unreadCount.value - 1)
}
notifications.value.splice(index, 1)
}
// 删除后重新获取当前页数据,以补充后续通知
if (notifications.value.length === 0 && currentPage.value > 1) {
// 如果当前页数据已被清空且不是第一页,则跳转到上一页
await goToPage(currentPage.value - 1)
} else {
// 否则重新加载当前页(如果有下一页数据会自动补充上来)
await fetchNotifications(currentPage.value, pageSize.value)
}
return true
} catch (err: any) {
// 检查是否为401错误
const errorHandler = useErrorHandler()
if (await errorHandler.checkAndHandleFetchError(err)) {
return null
}
error.value = err.message || '删除通知失败'
console.error('删除通知错误:', err)
return null
} finally {
loading.value = false
}
}
// 清空所有通知
const clearAllNotifications = async () => {
if (!isAuthenticated.value) {
error.value = '需要登录才能清空通知'
return null
}
loading.value = true
error.value = ''
try {
const authConfig = getAuthConfig()
await $fetch('/api/notifications/clear-all', {
method: 'DELETE',
...authConfig
})
// 清空本地通知列表
notifications.value = []
unreadCount.value = 0
resetPagination()
return true
} catch (err: any) {
// 检查是否为401错误
const errorHandler = useErrorHandler()
if (await errorHandler.checkAndHandleFetchError(err)) {
return null
}
error.value = err.message || '清空通知失败'
console.error('清空通知错误:', err)
return null
} finally {
loading.value = false
}
}
// 计算未读通知数量
const computedUnreadCount = computed(() => unreadCount.value)
return {
notifications,
unreadCount: computedUnreadCount,
settings,
loading,
error,
// 分页相关状态
currentPage,
pageSize,
totalPages,
totalCount,
hasNextPage,
hasPrevPage,
isPaginationLoading,
// 原有方法
fetchNotifications,
fetchNotificationSettings,
updateNotificationSettings,
markAsRead,
markAllAsRead,
deleteNotification,
clearAllNotifications,
// 分页方法
goToPage,
nextPage,
prevPage,
changePageSize,
resetPagination
}
}