微信小程序信件功能开发:5步实现用户消息模板配置与自动推送
微信小程序里做“信件”功能,听起来像是个小需求,但真正动手时你会发现,它比想象中复杂得多。一上来就想着怎么画界面、怎么存数据,结果做到一半才发现:用户写完信怎么保存?草稿丢了怎么办?发送后对方怎么收到?这些坑我当年都踩过。今天咱们就用讲课的方式,把“小程序信件开发”从头到尾拆解清楚,重点放在那些网上搜不到、但实际开发中一定会遇到的细节上。
一、信件功能的核心不是“写”,而是“状态管理”
先纠正一个常见误区:以为信件开发就是做个富文本编辑器,其实真正的难点在于“信件状态”怎么流转。举个例子,用户写了一封长信,中途退出——这封信是“草稿”还是“已保存”?用户点击发送,但网络断了——这封信该显示“发送中”还是“发送失败”?
我建议你在设计数据结构时,直接给每封信加上一个 status 字段,用数字表示状态:0=草稿、1=待发送、2=已发送、3=已读、4=撤回。这样后续所有逻辑都围绕这个状态展开。比如在 onShow 生命周期里,自动把status为0的信件存入本地缓存,防止用户丢失内容。具体代码可以这样写:
// 在页面显示时自动保存草稿
onShow() {
const draft = wx.getStorageSync('letter_draft')
if (draft && draft.status === 0) {
this.setData({ content: draft.content })
}
},
// 用户每次输入都同步缓存
onInput(e) {
wx.setStorageSync('letter_draft', {
content: e.detail.value,
status: 0,
timestamp: Date.now()
})
}
这里有个独门技巧:不要用 onUnload 保存草稿,因为用户可能直接切后台或小程序被销毁。用 onShow + 实时缓存更稳妥,哪怕微信闪退,草稿也丢不了。
二、富文本编辑器的选型陷阱与替代方案
会直接引入第三方富文本编辑器,比如 wxParse 或 towxml。但小程序环境特殊,这些库往往导致包体积暴涨、渲染卡顿。我踩过最大的坑是:用户写了一封带图片的信,编辑器直接崩溃,因为图片转base64后字符串太长。
我的建议是:放弃富文本,改用“文本+附件”模式。具体做法是:
1. 信件正文用纯文本textarea,支持换行和简单标记(比如用**加粗**这种用户约定)。
2. 需要插入图片时,单独用 wx.chooseMedia 上传到云存储,返回fileID后存到信件的 images 数组里。
3. 展示时,用 image 组件循环渲染图片列表,文本部分用 rich-text 组件,但只处理换行和链接。
这样做的优势很明显:用户输入体验流畅,不会因为图片导致编辑器卡死;同时数据存储更轻量,云数据库里只需要存文本和图片ID列表,而不是一大串HTML。对比一下:传统富文本编辑器存的是 <p>内容</p><img src='data:image...'>,一个图片可能占几MB;而我们的方案,文本几百字节,图片ID列表也就几十KB。
三、发送逻辑必须处理的三个边界情况
当用户点击“发送”按钮,不是简单调个API就完事了。我总结出三个必须处理的边界:
情况1:网络中断
用 wx.getNetworkType 先检测网络状态,如果无网络,不要直接报错,而是把信件加入“发送队列”存到本地,等网络恢复后自动重发。具体实现:
// 发送前检查网络
wx.getNetworkType({
success: (res) => {
if (res.networkType === 'none') {
// 加入待发送队列
const queue = wx.getStorageSync('send_queue') || []
queue.push(letterData)
wx.setStorageSync('send_queue', queue)
wx.showToast({ title: '已加入发送队列', icon: 'none' })
} else {
this.doSend(letterData)
}
}
})
情况2:对方已删除对话
小程序没有“消息撤回”这种原生能力,所以发送前最好调用云函数检查收件人是否还“存在”。比如在用户表里加一个 isActive 字段,如果对方已注销或删除了你的对话,直接提示“该用户暂无法接收信件”,避免信件石沉大海。
情况3:信件内容敏感词
微信小程序对内容审核很严,建议用云函数调用微信的 msgSecCheck 接口,在发送前做一次检查。注意这个接口有频率限制,所以最好在用户点击发送时调用一次,而不是每次输入都调用。
四、收信体验:让用户觉得“拆信”有仪式感
很多开发者在收信界面就放一个列表,点开看全文——太单调了。我参考了纸质信件的感觉,做了个“拆信动画”:用户点击信件时,先显示一个信封图标,然后信封慢慢打开,露出信纸。这个动画用 wx.createAnimation 就能实现,核心代码:
// 拆信动画
openLetter() {
const anim = wx.createAnimation({
duration: 600,
timingFunction: 'ease'
})
anim.translateY(-100).opacity(0).step() // 信封上移消失
anim.translateY(0).opacity(1).step() // 信纸出现
this.setData({
envelopeAnim: anim.export(),
showLetter: true
})
}
同时,收信列表里不要只显示标题,要显示“信纸样式”的缩略图。我让用户写信时可以选择信纸主题(比如“简约白”、“复古黄”、“樱花粉”),这些主题本质是CSS变量,收信时根据信件的 theme 字段动态加载对应的背景色和字体样式。用户反馈说这种细节让收信变得很期待。
五、数据存储:选云数据库还是自建服务器?
对于个人开发者或小团队,我强烈建议用微信云开发。原因不是技术上的,而是成本和管理上的。云数据库有 实时数据推送 功能,当收件人打开信件时,你可以通过 watch 监听到状态变化,自动把信件的status从2(已发送)改为3(已读)。如果用自建服务器,你还得自己搭WebSocket,费时费力。
但云开发也有坑:集合索引必须提前建好。比如你要查询“某用户的所有已发送信件”,如果没给 sender 和 status 建联合索引,数据量一大就会报错。我的习惯是:在云开发控制台里,提前建好三个索引——sender+status、receiver+status、timestamp,这样查询性能就不会出问题。
六、一个容易被忽略的细节:信件“撤回”功能
用户发错信了怎么办?微信没有原生撤回接口,但我们可以模拟。做法是:发送后的 2分钟内,允许用户点击“撤回”。撤回不是真的删除数据,而是把status改为4(撤回),同时给收件人发一条系统通知“对方撤回了一封信”。收件人打开信件时,如果status为4,就显示“此信件已被发送者撤回”。
这个功能的关键是计时器:发送成功时,在本地存一个 sentTime,每次用户点击撤回时,用 Date.now() - sentTime 判断是否超过120秒。超过的话,按钮置灰并提示“撤回时间已过”。
另外,撤回后要同步更新收件人的界面。这里有个技巧:用云开发的 callFunction 触发一个云函数,云函数里直接修改数据库的status字段,并通过 watch 推送给收件人。这样收件人即使正在看信,界面也会自动变成“已撤回”。
七、性能优化:信件列表的“懒加载”与“虚拟滚动”
当用户有几百封信时,列表渲染会非常卡。我试过用 wx:for 一次性渲染,结果页面直接白屏。解决方案是:分页加载 + 虚拟滚动。
分页加载很简单,每次请求20条数据,用 onReachBottom 触发下一页。但虚拟滚动需要点技巧:只渲染当前屏幕可见的10条数据,其他用占位div代替。我参考了一个开源库 wx-virtual-list,但那个库太老了,我自己改了一版:用 scroll-view 的 bindscroll 事件计算当前滚动位置,然后动态更新 displayList。核心逻辑:
// 虚拟滚动计算
onScroll(e) {
const scrollTop = e.detail.scrollTop
const itemHeight = 80 // 每个信件项高度
const startIdx = Math.floor(scrollTop / itemHeight)
const endIdx = startIdx + 15 // 渲染15条
this.setData({
displayList: this.data.allList.slice(startIdx, endIdx),
offset: startIdx * itemHeight
})
}
注意要给 scroll-view 设置一个 height,同时用 padding-top 模拟被跳过的项目高度,这样滚动条才会正常显示位置。
最后说一句:信件开发最考验的不是技术,而是对用户心理的把握。用户写信时想要的是“安心”——草稿不会丢、发送不会失败;收信时想要的是“惊喜”——拆信有仪式感、内容有温度。把这些细节做好了,比炫酷的动画效果更打动人。
