feat: align with upstream 1.5.9.9
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { useAuth } from './useAuth'
|
||||
import { useLocale } from '~/utils/locale'
|
||||
import type { Notification, NotificationSettings } from '~/types'
|
||||
|
||||
export const useNotifications = () => {
|
||||
const { getAuthConfig, isAuthenticated } = useAuth()
|
||||
const { composableErrors } = useLocale()
|
||||
const action = (key: keyof typeof composableErrors.value.actions) => composableErrors.value.actions[key]
|
||||
const loginRequired = (key: keyof typeof composableErrors.value.actions) => composableErrors.value.loginRequired(action(key))
|
||||
const failed = (key: keyof typeof composableErrors.value.actions) => composableErrors.value.failed(action(key))
|
||||
|
||||
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 currentFilter = ref<'all' | 'unread'>('all')
|
||||
const currentSearch = ref('')
|
||||
const hasNextPage = computed(() => currentPage.value < totalPages.value)
|
||||
const hasPrevPage = computed(() => currentPage.value > 1)
|
||||
const isPaginationLoading = ref(false)
|
||||
let fetchSequence = 0
|
||||
|
||||
const fetchNotifications = async (
|
||||
page?: number,
|
||||
limit?: number,
|
||||
filter: 'all' | 'unread' = currentFilter.value,
|
||||
search: string = currentSearch.value
|
||||
) => {
|
||||
const requestId = ++fetchSequence
|
||||
if (!isAuthenticated.value) {
|
||||
notifications.value = []
|
||||
unreadCount.value = 0
|
||||
resetPagination()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是分页请求(非初始加载),显示分页加载状态
|
||||
if (page && page !== 1) {
|
||||
isPaginationLoading.value = true
|
||||
} else {
|
||||
loading.value = true
|
||||
}
|
||||
error.value = ''
|
||||
|
||||
// 使用传入的参数或当前状态
|
||||
const requestPage = page || currentPage.value
|
||||
const requestLimit = limit || pageSize.value
|
||||
const requestFilter = filter === 'unread' ? 'unread' : 'all'
|
||||
const requestSearch = search.trim().slice(0, 100)
|
||||
currentFilter.value = requestFilter
|
||||
currentSearch.value = requestSearch
|
||||
|
||||
try {
|
||||
const authConfig = getAuthConfig()
|
||||
|
||||
const data = await $fetch('/api/notifications', {
|
||||
query: {
|
||||
page: requestPage,
|
||||
limit: requestLimit,
|
||||
filter: requestFilter,
|
||||
search: requestSearch || undefined
|
||||
},
|
||||
...authConfig
|
||||
})
|
||||
|
||||
if (data && requestId === fetchSequence) {
|
||||
notifications.value = data.notifications || []
|
||||
unreadCount.value = data.unreadCount || 0
|
||||
|
||||
// 更新分页信息
|
||||
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) {
|
||||
if (requestId !== fetchSequence) return
|
||||
const errorHandler = useErrorHandler()
|
||||
if (await errorHandler.checkAndHandleFetchError(err)) {
|
||||
return
|
||||
}
|
||||
console.error('获取通知错误:', err)
|
||||
notifications.value = []
|
||||
unreadCount.value = 0
|
||||
resetPagination()
|
||||
} finally {
|
||||
if (requestId === fetchSequence) {
|
||||
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 changeFilter = async (filter: 'all' | 'unread') => {
|
||||
if (filter === currentFilter.value) return
|
||||
currentPage.value = 1
|
||||
await fetchNotifications(1, pageSize.value, filter)
|
||||
}
|
||||
|
||||
const changeSearch = async (search: string) => {
|
||||
const normalizedSearch = search.trim().slice(0, 100)
|
||||
if (normalizedSearch === currentSearch.value) return
|
||||
currentPage.value = 1
|
||||
await fetchNotifications(1, pageSize.value, currentFilter.value, normalizedSearch)
|
||||
}
|
||||
|
||||
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 = loginRequired('updateNotificationSettings')
|
||||
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 || failed('updateNotificationSettings')
|
||||
console.error('更新通知设置错误:', err)
|
||||
return null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 标记通知为已读
|
||||
const markAsRead = async (notificationId: number) => {
|
||||
if (!isAuthenticated.value) {
|
||||
error.value = loginRequired('markNotification')
|
||||
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)
|
||||
}
|
||||
|
||||
if (currentFilter.value === 'unread') {
|
||||
const targetPage = notifications.value.length <= 1 && currentPage.value > 1
|
||||
? currentPage.value - 1
|
||||
: currentPage.value
|
||||
await fetchNotifications(targetPage, pageSize.value)
|
||||
}
|
||||
|
||||
return data
|
||||
} catch (err: any) {
|
||||
const errorHandler = useErrorHandler()
|
||||
if (await errorHandler.checkAndHandleFetchError(err)) {
|
||||
return null
|
||||
}
|
||||
error.value = err.message || failed('markNotification')
|
||||
console.error('标记通知错误:', err)
|
||||
return null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 标记所有通知为已读
|
||||
const markAllAsRead = async () => {
|
||||
if (!isAuthenticated.value) {
|
||||
error.value = loginRequired('markAllNotifications')
|
||||
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
|
||||
if (currentFilter.value === 'unread') {
|
||||
notifications.value = []
|
||||
resetPagination()
|
||||
}
|
||||
return data
|
||||
}
|
||||
return null
|
||||
} catch (err: any) {
|
||||
const errorHandler = useErrorHandler()
|
||||
if (await errorHandler.checkAndHandleFetchError(err)) {
|
||||
return null
|
||||
}
|
||||
error.value = err.message || failed('markAllNotifications')
|
||||
console.error('标记所有通知错误:', err)
|
||||
return null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除通知
|
||||
const deleteNotification = async (notificationId: number) => {
|
||||
if (!isAuthenticated.value) {
|
||||
error.value = loginRequired('deleteNotification')
|
||||
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 || failed('deleteNotification')
|
||||
console.error('删除通知错误:', err)
|
||||
return null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 清空所有通知
|
||||
const clearAllNotifications = async () => {
|
||||
if (!isAuthenticated.value) {
|
||||
error.value = loginRequired('clearNotifications')
|
||||
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 || failed('clearNotifications')
|
||||
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,
|
||||
currentFilter,
|
||||
currentSearch,
|
||||
hasNextPage,
|
||||
hasPrevPage,
|
||||
isPaginationLoading,
|
||||
// 原有方法
|
||||
fetchNotifications,
|
||||
fetchNotificationSettings,
|
||||
updateNotificationSettings,
|
||||
markAsRead,
|
||||
markAllAsRead,
|
||||
deleteNotification,
|
||||
clearAllNotifications,
|
||||
// 分页方法
|
||||
goToPage,
|
||||
nextPage,
|
||||
prevPage,
|
||||
changePageSize,
|
||||
changeFilter,
|
||||
changeSearch,
|
||||
resetPagination
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user