小程序开发日记④:消息推送与消息中心

1135 字
6 分钟
小程序开发日记④:消息推送与消息中心

消息模块既要面对微信订阅消息,也要维护小程序自己的消息中心。推送负责把用户叫回来,站内消息负责把事情说清楚,两者不能只完成其中一半。

1. 消息分页模型#

store/modules/message.js 保存列表、当前消息、分页与加载状态:

const messages = ref([])
const pageNum = ref(1)
const pageSize = 20
const total = ref(0)
const loading = ref(false)
const loadingMore = ref(false)
const hasMore = computed(() => messages.value.length < total.value)

下拉刷新请求最新第 1 页,触底后请求下一页。每页 20 条既避免首次加载过重,也让历史消息可以逐步展开。

2. 刷新与加载更多不能混用状态#

async function load(reset = false) {
if (loading.value || loadingMore.value) return
if (!reset && !hasMore.value) return
const targetPage = reset ? 1 : pageNum.value + 1
reset ? loading.value = true : loadingMore.value = true
// 请求并合并
}

两个 loading 分开后,页面可以在刷新时显示顶部状态,在追加历史消息时只显示底部“加载更多”,不会整页闪回骨架屏。

页面使用 scroll-view:

<scroll-view
scroll-y
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="refreshMessages"
@scrolltolower="loadMore"
>

3. 去重与排序#

刷新与分页期间,服务端可能刚好插入新消息,导致相邻页数据发生位移。Store 使用 notificationId 合并去重,再按发送时间倒序:

const merged = new Map()
;(reset ? rows : messages.value.concat(rows)).forEach(item => {
merged.set(String(item.notificationId), item)
})
messages.value = [...merged.values()].sort(sortBySendTimeDesc)

这比简单 concat 更能抵抗分页边界变化。

4. 已读状态的本地兜底#

接口暂未提供完整已读写回能力时,消息中心使用本地集合保存已读 ID:

const locallyReadIds = ref(new Set(uni.getStorageSync('readNotificationIds') || []))
function markMessageRead(id) {
locallyReadIds.value.add(String(id))
uni.setStorageSync('readNotificationIds', [...locallyReadIds.value])
}

加载列表时再把本地状态覆盖到消息对象。它不是最终的多端同步方案,但能保证同一设备上返回列表后红点不会复活。

5. 冷启动进入消息详情#

微信通知可能直接打开详情页,此时 Pinia 中没有缓存消息,而接口文档又没有单条详情接口。当前实现按较大页逐页查找目标 ID:

while ((page - 1) * pageSize < total) {
const response = await getNotificationList({ pageNum: page, pageSize: 100 })
const found = response.rows.find(item => String(item.notificationId) === targetId)
if (found) return found
total = Number(response.total)
page += 1
}

这是兼容方案。更理想的后端能力是提供 /notification/{id},这样消息历史很多时不会为了找一条记录扫描多页。

6. 关联订单显示业务订单号#

消息卡片和详情中的“关联订单”优先展示 orderNo,不能把数据库自增 orderId 当订单号。若通知只返回 ID,则额外读取订单详情进行补全。用户联系客服时说的是业务订单号,内部主键对他没有意义。

7. 订阅消息的定位#

小程序通过 wx.requestSubscribeMessage 请求用户授权,模板 ID 由统一工具封装。订阅请求应在用户主动操作后触发,例如下单成功、开启配送提醒,而不能在页面加载时强行弹窗。

站内消息作为事实记录,订阅消息作为触达渠道。即使用户拒绝订阅,消息中心仍应能查看状态变化。

8. 本篇小结#

未读数的口径#

当前列表只加载部分历史消息时,直接对 messages 数组计数只能得到“已加载消息中的未读数”,不一定等于账号全部未读。更完善的接口应单独返回 unreadTotal,底部导航红点使用总数,列表内部仍使用本地状态渲染单条消息。

推送打开后的路由恢复#

订阅消息携带 notificationId 或业务订单号,小程序冷启动时先完成登录态恢复,再进入详情。若消息不存在,页面显示可返回、可重试的错误状态;若关联订单已删除或无权限,只展示消息正文,不让整个详情失效。

消息内容还需要按类型决定动作按钮:订单状态通知可以查看订单,异常处置通知可以查看反馈结果,纯系统公告则没有业务跳转。按钮由消息类型映射生成,不能靠正文关键词猜测。

消息中心最开始只有 20 条时,看起来像是接口少给了点;补上刷新、分页、去重和冷启动后,它才像一个真正能用的收件箱。消息不一定都让人开心,但至少不能让人找不到。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
小程序开发日记④:消息推送与消息中心
https://firefly.cuteleaf.cn/posts/mmp-dev-diary-4/
作者
Yore
发布于
2026-08-11
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Yore
Hello, I'm Yore.
公告
欢迎来到Yore的博客!本站基于原创作者夏叶的博客网站模版创作。
分类
标签
最新动态
站点统计
文章
51
分类
2
标签
124
总字数
94,115
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
EdgeOne Pages
博客版本
Firefly v6.15.0
文章许可
CC BY-NC-SA 4.0