初次提交
This commit is contained in:
@@ -0,0 +1,431 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user