Files

135 lines
4.9 KiB
TypeScript

import { request, redirectToLogin } from '../../utils/request'
import * as auth from '../../utils/auth'
import { PagedResult, NotificationItem } from '../../types/models'
import { STORAGE_KEYS } from '../../utils/config'
import { formatRelativeTime } from '../../utils/format'
import { normalizePaged, mergePaged, hasMorePaged } from '../../utils/paged'
import { getTabBarInstance } from '../../utils/tabbar'
import { navScroll } from '../../utils/nav'
import { logger } from '../../utils/logger'
// 日志模块名
const LOG = 'notification'
const FILTERS = [
{ name: '全部', value: 0 },
{ name: '点赞', value: 1 },
{ name: '订单', value: 2 },
{ name: '系统', value: 3 },
]
Page({
data: {
filters: FILTERS,
activeFilter: 0,
list: [] as NotificationItem[],
loading: true,
loadingMore: false,
page: 1,
pageSize: 20,
hasMore: true,
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
navScrolled: false,
},
// scroll-view 滚动 → 导航收缩为玻璃小标题
onNavScroll: navScroll,
onShow() {
// 未登录直接回登录页(幂等跳转),不再发出必然 401 的请求
if (!auth.isLoggedIn()) {
logger.debug(LOG, '未登录,跳转登录页')
redirectToLogin()
return
}
// 先拉列表、再全部标记已读:本次进入仍保留未读标记,让用户看清哪些是新消息;
// 服务端状态与红点在列表渲染后清零,下次进入即为已读样式。
this.refresh().then(() => this.markAllRead())
},
// 全部标记已读:接口顺带返回最新未读数,直接用它刷新红点,省掉一次 /notification/unread
markAllRead(): Promise<void> {
return request<{ unread_count: number }>({
url: '/notification/read-all',
method: 'POST',
showError: false,
silentAuth: true,
})
.then((res) => {
const unread = res.unread_count || 0
logger.debug(LOG, '全部标记已读成功, unread=', unread)
this.syncUnread(unread)
})
.catch((e: any) => {
// 失败不阻塞列表展示,红点维持原状,等下次进入再补
logger.warn(LOG, '全部标记已读失败(静默降级), code=', e && e.code)
})
},
// 同步未读红点:本页不是 tab 页,getTabBar() 恒为 undefined,需从页面栈里取 tab 页的 tabBar
syncUnread(unread: number) {
wx.setStorageSync(STORAGE_KEYS.UNREAD, unread)
const tabBar = getTabBarInstance()
// 直接更新红点,避免 tabBar 内部再发一次请求
if (tabBar && tabBar.setUnread) tabBar.setUnread(unread)
},
// 返回列表请求的 Promise:onShow 需要等首页数据渲染完再标记全部已读
refresh(): Promise<void> {
this.setData({ page: 1, hasMore: true, loading: this.data.list.length === 0 })
return this.fetchList(1)
},
onReachBottom() {
if (!this.data.hasMore || this.data.loading || this.data.loadingMore) return
this.fetchList(this.data.page + 1)
},
fetchList(page: number): Promise<void> {
const params: Record<string, any> = { page, page_size: this.data.pageSize }
const filter = this.data.activeFilter
if (filter > 0) params.msg_type = filter
// 请求序号:切换筛选时旧响应可能后到,序号不匹配直接丢弃
const seq = ((this as any).reqSeq || 0) + 1
;(this as any).reqSeq = seq
if (page > 1) this.setData({ loadingMore: true })
return request<PagedResult<NotificationItem>>({ url: '/notification', data: params })
.then((res) => {
if ((this as any).reqSeq !== seq) return
const { list: raw, total } = normalizePaged<NotificationItem>(res)
logger.debug(LOG, '通知加载成功, 筛选=', filter, 'page=', page, '条数=', raw && raw.length)
const list = raw.map((it) => ({ ...it, _time: formatRelativeTime(it.created_at) }))
const merged = mergePaged(this.data.list, list, page === 1)
this.setData({
list: merged,
hasMore: hasMorePaged(merged, total, this.data.pageSize),
loading: false,
loadingMore: false,
page,
})
})
.catch((e: any) => {
if ((this as any).reqSeq !== seq) return
logger.warn(LOG, '通知加载失败, page=', page, 'code=', e && e.code)
this.setData({ loading: false, loadingMore: false })
})
},
onFilterTap(e: WechatMiniprogram.TouchEvent) {
const index = Number(e.currentTarget.dataset.index)
if (index === this.data.activeFilter) return
this.setData({ activeFilter: index, list: [], loading: true })
this.refresh()
},
onItemTap(e: WechatMiniprogram.TouchEvent) {
const uuid = e.currentTarget.dataset.uuid as string
wx.navigateTo({
url: `/pages/notification-detail/notification-detail?uuid=${uuid}`,
fail: (e) => logger.warn(LOG, 'navigateTo 通知详情失败, err=', e && e.errMsg),
})
},
})