feat(站内通知): 铃铛面板支持时间/内容搜索、按天分组与分页

- 列表接口加 keyword(标题/内容模糊)与 startDate/endDate(年月日闭区间)参数,
  两端控制器透传,服务端补筛选日志
- 两端铃铛面板:搜索框(防抖 300ms)+ 日期区间 + 按年月日分组 + 翻页,
  面板改 Teleport 到 body(挂在顶栏时会被页面 el-select 压住,提 z-index 无效)
- 固化可见范围回归测试:超管全量/管理员只看本组/普通用户只看自己,
  含读写两侧的 user_id+audience 裁剪断言与两端控制器身份来源断言
This commit is contained in:
2026-09-13 23:59:06 +08:00
parent 9ffd68bae5
commit ff1ffbbfa3
18 changed files with 1426 additions and 136 deletions
@@ -64,6 +64,64 @@ export function currentNotificationUid(): string {
}
}
/** 通知所属日期键(本地时区 yyyy-MM-dd);时间缺失/非法返回空串。 */
export function notificationDayKey(value: string | null | undefined): string {
if (!value) {
return ''
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return ''
}
const pad = (input: number) => String(input).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
}
/** 日期组头文案:今天 / 昨天 / 2026年9月11日;空键兜底「未知时间」。 */
export function formatDayLabel(day: string, now: Date = new Date()): string {
if (!day) {
return '未知时间'
}
if (day === notificationDayKey(now.toISOString())) {
return '今天'
}
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1)
if (day === notificationDayKey(yesterday.toISOString())) {
return '昨天'
}
const [year, month, date] = day.split('-').map((part) => Number(part))
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(date)) {
return day
}
return `${year}${month}${date}`
}
export interface NotificationDayGroup<T> {
day: string
label: string
items: T[]
}
/** 按年月日分组(组内保持入参顺序,列表本身已按 id 倒序)。 */
export function groupNotificationsByDay<T extends { createdAt: string | null }>(
items: T[],
now: Date = new Date(),
): NotificationDayGroup<T>[] {
const groups: NotificationDayGroup<T>[] = []
const indexByDay = new Map<string, NotificationDayGroup<T>>()
for (const item of items ?? []) {
const day = notificationDayKey(item.createdAt)
let group = indexByDay.get(day)
if (!group) {
group = { day, label: formatDayLabel(day, now), items: [] }
indexByDay.set(day, group)
groups.push(group)
}
group.items.push(item)
}
return groups
}
/** 通知时间展示:今天只显示 HH:mm;昨天显示「昨天 HH:mm」;更早显示 MM-DD HH:mm。 */
export function formatNotificationTime(
value: string | null | undefined,