b70557a077
- 单设备登录:登录成功即 last-login-wins 绑定 users.machine;非超管旧 token 在下一次受保护请求抛 4011 下线,超管豁免;仅认 token 内签名 deviceId,不用请求头。 前端两端接入踢下线跳转(?kicked=1 提示),V118 清空历史 machine - 站内通知:新增 notification 模块(任务失败扫描 / 密钥欠费 / 下游服务探测三类来源), 前后台铃铛组件 + 轮询;后台列表按主管数据范围(UserDataScopeSupport)过滤;V116 建表 均已于 2026-09-13 部署上线,此次补提交源码(此前仅存在于已部署 JAR/构建产物中)
98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
/**
|
|
* 铃铛通知纯逻辑:未读徽标文案、新通知判定、「已提醒过」去重记录、时间展示。
|
|
* 轮询调度与渲染在 NotificationBell.vue;这里只放可单测的纯函数与存储读写。
|
|
*/
|
|
|
|
/** 未读轮询间隔(60 秒)。 */
|
|
export const NOTIFICATION_POLL_INTERVAL_MS = 60_000
|
|
|
|
const NOTIFIED_KEY_PREFIX = 'notification:last-notified-id'
|
|
|
|
/** 未读徽标文案:0 或非法值显示空串,>99 显示 99+。 */
|
|
export function formatUnreadBadge(count: number | null | undefined): string {
|
|
const value = Number(count ?? 0)
|
|
if (!Number.isFinite(value) || value <= 0) {
|
|
return ''
|
|
}
|
|
return value > 99 ? '99+' : String(Math.floor(value))
|
|
}
|
|
|
|
/** 是否出现新通知:latestId 大于上次已提醒过的 id。 */
|
|
export function hasNewNotification(
|
|
latestId: number | null | undefined,
|
|
lastNotifiedId: number | null | undefined,
|
|
): boolean {
|
|
const latest = Number(latestId ?? 0)
|
|
const notified = Number(lastNotifiedId ?? 0)
|
|
if (!Number.isFinite(latest) || latest <= 0) {
|
|
return false
|
|
}
|
|
return latest > (Number.isFinite(notified) && notified > 0 ? notified : 0)
|
|
}
|
|
|
|
function storageKey(uid: string): string {
|
|
return `${NOTIFIED_KEY_PREFIX}:${uid || '0'}`
|
|
}
|
|
|
|
/** 读取当前用户「已提醒过的最大通知 id」(多页面/多会话共享)。 */
|
|
export function readLastNotifiedId(uid: string): number {
|
|
try {
|
|
const raw = window.localStorage.getItem(storageKey(uid))
|
|
const value = Number(raw ?? 0)
|
|
return Number.isFinite(value) && value > 0 ? value : 0
|
|
} catch {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
export function writeLastNotifiedId(uid: string, id: number): void {
|
|
try {
|
|
if (Number.isFinite(id) && id > 0) {
|
|
window.localStorage.setItem(storageKey(uid), String(Math.floor(id)))
|
|
}
|
|
} catch {
|
|
/* 存储不可用时静默忽略(退化为每次都提醒,不影响功能) */
|
|
}
|
|
}
|
|
|
|
/** 当前登录用户 uid(未登录返回 '0')。 */
|
|
export function currentNotificationUid(): string {
|
|
try {
|
|
return window.localStorage.getItem('uid') || '0'
|
|
} catch {
|
|
return '0'
|
|
}
|
|
}
|
|
|
|
/** 通知时间展示:今天只显示 HH:mm;昨天显示「昨天 HH:mm」;更早显示 MM-DD HH:mm。 */
|
|
export function formatNotificationTime(
|
|
value: string | null | undefined,
|
|
now: Date = new Date(),
|
|
): string {
|
|
if (!value) {
|
|
return ''
|
|
}
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) {
|
|
return ''
|
|
}
|
|
const pad = (input: number) => String(input).padStart(2, '0')
|
|
const hourMinute = `${pad(date.getHours())}:${pad(date.getMinutes())}`
|
|
const sameDay =
|
|
date.getFullYear() === now.getFullYear() &&
|
|
date.getMonth() === now.getMonth() &&
|
|
date.getDate() === now.getDate()
|
|
if (sameDay) {
|
|
return hourMinute
|
|
}
|
|
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1)
|
|
const isYesterday =
|
|
date.getFullYear() === yesterday.getFullYear() &&
|
|
date.getMonth() === yesterday.getMonth() &&
|
|
date.getDate() === yesterday.getDate()
|
|
if (isYesterday) {
|
|
return `昨天 ${hourMinute}`
|
|
}
|
|
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${hourMinute}`
|
|
}
|