新版官网模板

This commit is contained in:
2026-04-29 01:33:33 +08:00
commit 0d82386f8f
341 changed files with 64526 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
import { ref, computed } from 'vue'
import {
getUnreadCount,
listRecentNotification,
markRead,
markAllRead,
} from '@/api/app/notification'
import type { Notification, UnreadCountResult, NotificationType } from '@/api/app/notification/model'
import { message } from 'ant-design-vue'
/** 轮询间隔30 秒 */
const POLL_INTERVAL = 30_000
/** 全局未读数(跨组件共享) */
const unreadTotal = ref(0)
const unreadByType = ref<Partial<Record<NotificationType, number>>>({})
const recentList = ref<Notification[]>([])
const loading = ref(false)
/** 轮询定时器 */
let pollTimer: ReturnType<typeof setInterval> | null = null
/** 防止重复轮询的计数器 */
let pollRefCount = 0
/** 通知类型 → 显示名称 & 图标 */
export const notificationTypeMap: Record<NotificationType, { label: string; icon: string; color: string }> = {
ticket: { label: '工单通知', icon: '🎫', color: '#1890ff' },
review: { label: '审核通知', icon: '✅', color: '#52c41a' },
system: { label: '系统通知', icon: '📢', color: '#faad14' },
resource: { label: '资源通知', icon: '🖥️', color: '#722ed1' },
permission: { label: '权限通知', icon: '🔐', color: '#eb2f96' },
member: { label: '成员通知', icon: '👥', color: '#13c2c2' },
payment: { label: '账单通知', icon: '💳', color: '#fa8c16' },
}
/**
* 通知中心 composable全局共享状态
*/
export function useNotificationCenter() {
// ---------- 加载未读数 ----------
async function fetchUnreadCount() {
try {
const data = await getUnreadCount()
unreadTotal.value = data.total ?? 0
unreadByType.value = { ...data } as Partial<Record<NotificationType, number>>
;(unreadByType.value as Record<string, unknown>).total = undefined // 去掉 total
} catch {
// 静默失败,不打扰用户
}
}
// ---------- 加载最近通知 ----------
async function fetchRecentNotifications(limit = 10) {
loading.value = true
try {
recentList.value = await listRecentNotification({ limit })
} catch {
// 静默失败
} finally {
loading.value = false
}
}
// ---------- 标记单条已读 ----------
async function markNotificationRead(id: number) {
try {
await markRead(id)
// 乐观更新
const item = recentList.value.find((n) => n.id === id)
if (item) item.isRead = 1
if (unreadTotal.value > 0) unreadTotal.value--
} catch {
message.error('标记失败,请重试')
}
}
// ---------- 全部已读 ----------
async function markAllAsRead(type?: string) {
try {
await markAllRead(type ? { type } : undefined)
recentList.value.forEach((n) => {
if (!type || n.type === type) n.isRead = 1
})
if (type) {
unreadTotal.value -= (unreadByType.value[type as NotificationType] ?? 0)
unreadByType.value[type as NotificationType] = 0
} else {
unreadTotal.value = 0
Object.keys(unreadByType.value).forEach((k) => {
unreadByType.value[k as NotificationType] = 0
})
}
if (unreadTotal.value < 0) unreadTotal.value = 0
message.success('已全部标记为已读')
} catch {
message.error('操作失败,请重试')
}
}
// ---------- 启动 / 停止轮询 ----------
function startPolling() {
pollRefCount++
if (pollTimer) return
// 立即拉取一次
fetchUnreadCount()
pollTimer = setInterval(fetchUnreadCount, POLL_INTERVAL)
}
function stopPolling() {
pollRefCount = Math.max(0, pollRefCount - 1)
if (pollRefCount === 0 && pollTimer) {
clearInterval(pollTimer)
pollTimer = null
}
}
// ---------- 时间格式化 ----------
function formatTime(time?: string): string {
if (!time) return ''
const date = new Date(time)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
const diffSec = Math.floor(diffMs / 1000)
const diffMin = Math.floor(diffSec / 60)
const diffHour = Math.floor(diffMin / 60)
const diffDay = Math.floor(diffHour / 24)
if (diffSec < 60) return '刚刚'
if (diffMin < 60) return `${diffMin} 分钟前`
if (diffHour < 24) return `${diffHour} 小时前`
if (diffDay < 7) return `${diffDay} 天前`
return `${date.getMonth() + 1}/${date.getDate()} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
}
// ---------- 获取通知跳转链接 ----------
function getNotificationLink(n: Notification): string {
if (n.linkUrl) return n.linkUrl
switch (n.refType) {
case 'ticket':
return `/console/tickets`
case 'permission_request':
return `/developer/requests`
default:
return '/console/notifications'
}
}
return {
// 状态
unreadTotal,
unreadByType,
recentList,
loading,
// 方法
fetchUnreadCount,
fetchRecentNotifications,
markNotificationRead,
markAllAsRead,
startPolling,
stopPolling,
// 工具
formatTime,
getNotificationLink,
}
}