feat(站内通知): 铃铛面板支持时间/内容搜索、按天分组与分页
- 列表接口加 keyword(标题/内容模糊)与 startDate/endDate(年月日闭区间)参数, 两端控制器透传,服务端补筛选日志 - 两端铃铛面板:搜索框(防抖 300ms)+ 日期区间 + 按年月日分组 + 翻页, 面板改 Teleport 到 body(挂在顶栏时会被页面 el-select 压住,提 z-index 无效) - 固化可见范围回归测试:超管全量/管理员只看本组/普通用户只看自己, 含读写两侧的 user_id+audience 裁剪断言与两端控制器身份来源断言
This commit is contained in:
@@ -142,6 +142,57 @@ function json(res, payload, status = 200) {
|
||||
res.end(body)
|
||||
}
|
||||
|
||||
/* ===================== 站内通知夹具(铃铛面板:搜索/按天分组/分页验收用) ===================== */
|
||||
const NOTIFICATIONS = [
|
||||
...[6, 5, 4, 3, 2, 1].map((seq) => notif(seq, '2026-09-13', 22, seq, seq <= 3)),
|
||||
...[4, 3, 2, 1].map((seq) => notif(20 + seq, '2026-09-12', 20, seq, true)),
|
||||
...[4, 3, 2, 1].map((seq) => notif(30 + seq, '2026-09-10', 18, seq, true)),
|
||||
]
|
||||
|
||||
function notif(id, day, hour, seq, read) {
|
||||
const minutes = String(seq * 7).padStart(2, '0')
|
||||
return {
|
||||
id,
|
||||
scene: id % 3 === 0 ? 'secret_balance' : 'task_failed',
|
||||
level: id % 3 === 0 ? 'error' : 'warning',
|
||||
title: id % 3 === 0 ? `用户密钥异常:测试用户${seq}` : `跟价任务失败`,
|
||||
content: id % 3 === 0 ? `用户 测试用户${seq}(uid=${1100 + seq})的代理设置对应服务商余额不足` : `用户 测试用户${seq} 有 ${seq} 个跟价任务失败`,
|
||||
read,
|
||||
readAt: read ? `${day} ${hour}:${minutes}:00` : null,
|
||||
createdAt: `${day} ${hour}:${minutes}:00`,
|
||||
}
|
||||
}
|
||||
|
||||
/** 通知列表:关键字匹配标题/内容,日期按年月日区间(与 Java 侧同语义)。 */
|
||||
function notificationPage(searchParams) {
|
||||
const keyword = (searchParams.get('keyword') || '').trim()
|
||||
const startDate = searchParams.get('startDate') || ''
|
||||
const endDate = searchParams.get('endDate') || ''
|
||||
const page = Math.max(1, Number(searchParams.get('page') || 1))
|
||||
const pageSize = Math.min(100, Math.max(1, Number(searchParams.get('pageSize') || 20)))
|
||||
let rows = NOTIFICATIONS
|
||||
if (keyword) {
|
||||
rows = rows.filter((item) => `${item.title}${item.content}`.includes(keyword))
|
||||
}
|
||||
if (startDate) {
|
||||
rows = rows.filter((item) => item.createdAt.slice(0, 10) >= startDate)
|
||||
}
|
||||
if (endDate) {
|
||||
rows = rows.filter((item) => item.createdAt.slice(0, 10) <= endDate)
|
||||
}
|
||||
const start = (page - 1) * pageSize
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
items: rows.slice(start, start + pageSize),
|
||||
total: rows.length,
|
||||
page,
|
||||
pageSize,
|
||||
unreadCount: rows.filter((item) => !item.read).length,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const server = createServer((req, res) => {
|
||||
const url = (req.url || '').split('?')[0]
|
||||
const method = req.method || 'GET'
|
||||
@@ -180,6 +231,18 @@ const server = createServer((req, res) => {
|
||||
data: { pending: false, scanned_at: '2026-09-05 08:30:00', items: ALL_ROWS, total: ALL_ROWS.length, page: 1, page_size: 20 },
|
||||
})
|
||||
}
|
||||
if (url === '/api/admin/notifications/summary') {
|
||||
return json(res, {
|
||||
success: true,
|
||||
data: {
|
||||
unreadCount: NOTIFICATIONS.filter((item) => !item.read).length,
|
||||
latestId: NOTIFICATIONS.reduce((max, item) => Math.max(max, item.id), 0),
|
||||
},
|
||||
})
|
||||
}
|
||||
if (url === '/api/admin/notifications') {
|
||||
return json(res, notificationPage(new URL(req.url || '/', 'http://127.0.0.1').searchParams))
|
||||
}
|
||||
if (url.startsWith('/api/')) {
|
||||
return json(res, { success: true, data: { items: [], total: 0 } })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user