- [填坑手册]小程序新版订阅消息+云开发实战与跳坑
[图片] 老版本的订阅消息在2020年1月10日就下线了,相信不少人在接入新版本订阅系统的时候,或多或少会遇到一些问题,这里智库君跟大家介绍下新版订阅的机制和不需要node/后端的情况下 独立完成功能开发。 一、新版订阅的机制 其实开发过程不难,但是要理清楚它里面的机制,智库君还是花了一些时间的,也踩了不少坑 先来看下官方介绍: [图片] 可以设置多个订阅选项 感叹号里面可以看到详情 有个默认不被选中的“总是”选项 这些就是新不同的地方,智库君在开发的时候也有很多疑问,点了“总是”再点“取消”按钮会怎样?部分选择订阅会怎样?下面为大家一一梳理 (1)部分选中 [图片] 比如现在有三个选项 A,B,C,用户**“部分选中”**返回的情况: [图片] 这里用真机调试可以看到,有个返回值状态为“reject”。 如果我们反复几点点击同一个订阅后,这些值是如何计算的呢? 举例: [图片] 从这里看出,微信系统会自动记录用户点击的次数,并且做累加记录,如果用户只允许2次发送,而开发者发送了3次,最后一次将会被拒绝。 (2)点击“总是保持以上选择,不再询问”的情况 [图片] 当用户点击“总是”之后,同一个类型的订阅将不再弹出,那如果有多个订阅选项呢? 举例 订阅AAA 三个订阅模板为 X Y Z 订阅BBB 二个订阅模板为 Y W 这时候如果“订阅AAA”按钮选择了“总是”,那么再点击“订阅BBB”按钮,将只会弹出一个选项“W”,不会有 “Y” 的模板,因为在之前 “订阅AAA” 按钮中已经包含了。 [代码]wx.requestSubscribeMessage({ tmplIds: ["MECDDOdhbC3SrQmMY5XrfqiIGbMTzpEN8Z7ScXJfcd0", "iSb2NIlNnnO60wlI-8Wx5Pe82jR7TRdwjotSXtM1-ww"], success(res) { console.log(res); } }) [代码] 显示内容仅一个选项: [图片] 这里需要注意,“总是”选项是全局有效,不区分页面,选中“总是”的 W,X,Y,Z的模板,在全局任意页面中再次调用,再次调用将不再会显示! [图片] 返回值无提示用户是否选中“总是”。 (3)用户点击“总是”后,获取状态 [图片] [代码]wx.getSetting({ withSubscriptions: true, success(res) { console.log(res.authSetting) // res.authSetting = { // "scope.userInfo": true, // "scope.subscribeMessage": true // } console.log(res.subscriptionsSetting) // res.subscriptionsSetting = { // SYS_MSG_TYPE_INTERACTIVE: 'accept', // SYS_MSG_TYPE_RANK: 'accept', // zun-LzcQyW-edafCVvzPkK4de2Rllr1fFpw2A_x0oXE: 'reject', // ke_OZC_66gZxALLcsuI7ilCJSP2OJ2vWo2ooUPpkWrw: 'ban', // } } }); [代码] [图片] 这里可以调用wx.getSetting方法,但是需要注意:如果用户第一次选“总是”后点击“取消”按钮或者订阅模板全部是未选中/reject的,那将获取不到状态(这里可能是BUG,期待官方未来修复)。 (4)用户点击“总是”后,让用户手动修改 前面说到用户点击“总是”后,系统将不再弹窗,但是我们可以通过**“wx.openSetting”**引导用户手动修改。 [代码]wx.openSetting({ success(res) { console.log(res.authSetting) // res.authSetting = { // "scope.userInfo": true, // "scope.userLocation": true // } } }) [代码] [图片] [图片] 当然用户自己也可以修改 [图片] 总结 【重点】选择“总是”,很多人认为就可无限发送订阅消息,这个是错误的,勾选和不勾选唯一的区别就是每次触发订阅的时候会不会弹授权窗口!!! 用户点击次数系统会自动累加,直接影响后台发送通知的次数。 用户选择“总是”后,小程序界面不再弹窗,但仍然有回调/callback。 任意订阅模板在用户选中“总是”(包括接受/拒绝2个状态)后,全局有效,就算其他订阅包含“此模板”也不再显示/弹出 当用户选择“总是”中“accept/选中/接受”的状态后,可以在wx.getSetting查询到用户是否选择“总是”。 当用户选择“总是”中“reject/未选中/拒绝”的状态后,返回值“无感知”(这里可能是BUG) 二、功能开发 使用微信自带的云开发,可以在没有node/后端开发支持下,完成整个订阅流程的开发。 (1)微信后台设置订阅模板和获取模板ID 1、打开小程序后台,找到订阅消息设置 [图片] 2、在公共模板库找模板或者自己申请新模板,建议能用现成模板用现成的,因为申请周期可能较长,且容易被拒 [图片] 3、选好模板后,点击详情 [图片] 4、查看模板内容和发送DATA的结构 [图片] 5、复制模板ID (2)配置云函数 [图片] [图片] 1、新建getOpenId云函数,用于获取用户的openID [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID, } } [代码] 2、新建订阅推送通知云函数 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() //订阅推送通知 exports.main = async (event, context) => { try { const result = await cloud.openapi.subscribeMessage.send({ touser: event.openid, //接收用户的openId page: 'pages/my/index', //订阅通知 需要跳转的页面 data: { //设置通知的内容 thing1: { value: '小程序订阅填坑' }, thing2: { value: '智库方程式' }, thing3: { value: '一起学习,一起进步' } }, templateId: '5Efr7IqIooYO9nPw047Iggxbm9Ge2Km10GQ4amGOUac' //模板id }) console.log(result) return result } catch (err) { console.log(err) return err } } [代码] 写完云函数记得右键部署下!!! (3)小程序代码部分 [代码]<!------------html -------------> <button bindtap="getOpenId" type='primary'>获取openId</button> <view class="subBtn" catch:tap="sub">订阅AAA</view> <view class="subBtn" catch:tap="send">订阅推送测试</view> <view class="subBtn" catch:tap="setting">设置“总是”后,跳转修改</view> [代码] [代码]//JS 部分 //获取用户的openid getOpenId() { wx.cloud.callFunction({ name: "getOpenId" }).then(res => { let openid = res.result.openid console.log("获取openid成功", openid) }).catch(res => { console.log("获取openid失败", res) }) }, //发送模板消息给指定的openId用户 send(openid){ wx.cloud.callFunction({ name: "sendSub", data: { openid: openid } }).then(res => { console.log("发送通知成功", res) }).catch(res => { console.log("发送通知失败", res) }); }, //消息订阅 sub: function () { wx.requestSubscribeMessage({ tmplIds: ["5Efr7IqIooYO9nPw047Iggxbm9Ge2Km10GQ4amGOUac"], success(res) { console.log("订阅授权成功:"+res); }, fail(res){ console.log("订阅授权失败:" + res); } }) }, //帮助用户跳转修改订阅状态 setting:function(){ wx.openSetting({ success(res) { console.log(res.authSetting) // res.authSetting = { // "scope.userInfo": true, // "scope.userLocation": true // } } }) }, [代码] (4)测试流程 点击发送通知后,获得这样的效果: [图片] [图片] 获得对应返回值: [图片] 当errCode为0时,即发送通知成功。 当errCode为43101,说明用户只授权了一次,但是你发送了2次,超过用户授权次数。 [图片] 三、进阶与思考 1、当你有多个订阅模板同时需要用户选择时,你可以通过以下代码记录,用户哪些选了,哪些没选。 [代码]wx.requestSubscribeMessage({ tmplIds: ["5Efr7IqIooYO9nPw047Iggxbm9Ge2Km10GQ4amGOUac", "OBB_Z10eh_Inm9p8EU6Ml_NS_mijXgTz3T07cxgKvX0","5Efr7IqIooYO9nPw047Iggxbm9Ge2Km10GQ4amGOUac"], success(res) { //console.log(res); if (res.errMsg == "requestSubscribeMessage:ok") { let acceptArray = []; //用户授权模板列表 for (let i = 0; i < tmplIds.length; i++) { const element = tmplIds[i]; if (res[element] == "accept") { acceptArray.push(element); } }; console.log(acceptArray); if (acceptArray.length > 0) { //执行下一步函数 } } } }) [代码] 2、一个关于是否需要记录用户对某个“订阅模板授权的次数”,以控制后台“发送的次数”,智库君在实战中认为,其实没有必要,顶多就是你发送返回一个错误码,微信之所有记录用户授权次数,也是为了保护用户不被骚扰。 3、你只需要记录用户点击了哪些需要授权的模板就行,为了是用户点击订阅后,改变按钮的状态,避免订阅按钮反复弹窗的问题,同时当检测到用户点错“总是”按钮后,可以自动跳转到“设置”界面。 4、这次智库君主要给大家简单介绍了下订阅全流程。后面大家可以根据自己的需要,添加和改进这些代码。比如: 配置云函数中的node函数,实现定时发送 配置云函数中的数据库,实现内容的自定义发送 最后,希望这篇文章能帮助到大家,一起学习,一起进步! (官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message.html) 往期回顾: [打怪升级]小程序自定义头部导航栏“完美”解决方案 [填坑手册]小程序Canvas生成海报(一) [拆弹时刻]小程序Canvas生成海报(二)
2021-09-13 - [填坑手册]小程序目录结构和component组件使用心得
[图片] 小程序目录结构 关于小程序的目录结构,可以说一开始大家都有各自的开发习惯和命名规则,但一旦项目变得复杂庞大的时候,你就发现管理起来和后期维护变得很麻烦,如果是 协同开发 的话,更容易出现 “互坑” 的情况。 智库君在一年多的小程序开发中也跳过不少的坑,总结了一套还算好维护的目录结构跟大家分享(仅供参考,觉得好拿去,觉得不好欢迎提出意见),以下是实战项目中的结构示例: [代码]├─ app.js --- 小程序加载时优先加载的入口JS ├─ app.json ---入口文件和公共配置 ├─ app.wxss ---公共样式表 ├─ project.config.json ---小程序全局配置文件 ├─ sitemap.json ---允许微信索引文件 │ ├─cloud-functions ---云函数 │ └─setCrypto ---数据加密模块,用户加密一些数据 │ index.js │ package.json │ ... │ ... │ ├─components ---小程序自定义组件 │ ├─plugins --- (重点)可独立运行的大型模块,可以打包成plugins │ │ ├─comment ---评论模块 │ │ │ │ index.js │ │ │ │ index.json │ │ │ │ index.wxml │ │ │ │ index.wxss │ │ │ │ services.js ---(重点)用来处理和清洗数据的service.js,配套模板和插件 │ │ │ │ │ │ │ └─submit ---评论模块子模块:提交评论 │ │ │ index.js │ │ │ index.json │ │ │ index.wxml │ │ │ index.wxss │ │ │ │ │ └─canvasPoster ---canvas海报生成模块 │ │ index.js │ │ index.json │ │ index.wxml │ │ index.wxss │ │ services.js ---(重点)用来处理和清洗数据的service.js,配套模板和插件 │ │ ... │ │ ... │ │ │ └─templates ---(重点)模板,通过外部传参的容器,不做过多的数据处理 │ │ │ ├─slideshow ---滚屏切换模板 │ │ index.js │ │ index.json │ │ index.wxml │ │ index.wxss │ │ service.js ---(重点)用来处理和清洗数据的service.js,配套模板和插件 │ │ │ └─works ---作品模板 │ │ index.js │ │ index.json │ │ index.wxml │ │ index.wxss │ │ service.js │ │ │ ├─articlePlugin ---作品模板中的文章类型 │ │ index.js │ │ index.json │ │ index.wxml │ │ index.wxss │ │ │ ├─galleryPlugin ---作品模板中的九宫格类型 │ │ index.js │ │ index.json │ │ index.wxml │ │ index.wxss │ │ │ └─videoPlugin ---作品模板中的视频类型 │ index.js │ index.json │ index.wxml │ index.wxss │ ... │ ... │ ├─config ---自定义配置文件 │ config.js ---存放基础配置 │ constants.js ---存储常量 │ weui.wxss ---第三方文件wxss,js等 │ ... │ ... │ ├─pages ---小程序页面 │ ├─user ---用户页面 │ │ index.js │ │ index.json │ │ index.wxml │ │ index.wxss │ ├─news ---新闻页面 │ │ index.js │ │ index.json │ │ index.wxml │ │ index.wxss │ │ │ └─home ---首页 │ index.js │ index.json │ index.wxml │ index.wxss │ ... │ ... │ ├─request ---https请求管理(根据switch tab分类会比较好) │ common.js ---一些公共请求获取,如兑换openId,unionId 等 │ news.js │ uri.js --- (重点)总的URI请求管理,方便切换和配置DEV,QA,PROD环境 │ user.js │ ... │ ... │ └─utils ---功能组件 logger.js ---日志管理 util.js ---公共小组件库 ... ... [代码] 例如微信自己的wepy的官方文档,现在也添加了目录结构说明: [图片] 为什么一定要写这个目录结构呢? 不知道大家有没有发现,在以往的老项目交接和多人协同开发中,容易遇到别人写的模块,变量命名不准确,或者资料缺损,一次十来个方法/组件间的互相调用,直接把接(盘)手的人整懵逼了,所以智库君觉得,无论是独立开发,还是协同开发,留一份完整的目录说明文档是很有必要的,勿坑 他人 OR 未来的自己~~~ component使用心得 大家在开发过程中肯定会去看官方文档,但不可能全看完才开始写代码,大多数情况都是用到了再看,本人也是,所以下面抽一些开发中遇到的重点来讲: 一、引用组件模板页面的自定义 组件模板的写法与页面模板相同。组件模板与组件数据结合后生成的节点树,将被插入到组件的引用位置上。 在组件模板中可以提供一个 <slot> 节点,用于承载组件引用时提供的子节点。 [代码]<!-- 组件模板 --> <view class="wrapper"> <view>这里是组件的内部节点</view> <slot></slot> </view> [代码] [代码]<!-- page页/父页面引用组件的页面模板 --> <view> <component-tag-name> <!-- 这部分内容将被放置在组件 <slot> 的位置上 --> <view>这里是插入到组件slot中的内容</view> <view>在加载组件的页面里自定义内容,将没有复用性的内容写在这里</view> </component-tag-name> </view> [代码] 页面自定义部分默认是加载在组件上方。 为什么要在引用组件的页面添加这些内容呢? 因为组件其中一个重要的特点是复用性,但是有的时候可能要根据不同场景做一些自定义,如果在组件中写大量的场景/逻辑判断,会增加组件的冗余,而且这些方法只是被复用一次的话,完全可以不写到组件里。 二、“一键换肤”根据不同场景给组件引入外部样式 [代码]<!-- 外部引用组件的页面传入样式 --> <WorkComponent extra-class="style1" j-data="{{workData}}"></WorkComponent> [代码] [代码]//组件中js Component({ /** * 引入外部样式,可传多个class */ externalClasses: ['extra-class','extra-class2'], }) [代码] extra-class 从外部引入父级css,可用根据不同场景配置不同的样式方案,这样使得组件自定义能力更强。 三、数据清洗与容错 [代码]//service.js 思路示例 module.exports = { /** * 功能:处理作者列表 * @param list * @returns {Array} */ authorList: function (list = []) { let result = []; list.forEach(item => { result.push({ guid: item.recommend_obj_id || '', type: item.recommend_type || '', logo: (item.theme_pic || '').trim() || '', title: item.title || '' }); }); return result; } }; [代码] 如果外部传入的数据要分别导入多个组件中,可以在组件中建立一个对应的service.js,有2个作用: 清洗数据,避免setData()的时有过多的脏数据 错误数据的兼容,添加数据缺省值,增加代码健壮性 四、canvas在component组件中无法选中的问题 [代码] //这里只需要在后面 添加this对象 let ctx = wx.createCanvasContext('myCanvas', this); [代码] 其他一些默认组件,遇到类似的问题,一般只要引用时传入this对象即可解决。 五、组件之间的通讯 在实际生产环境中,我们常常需要控制各个组件之间的互相通信/传参,下面介绍下具体的用法: WXML 数据绑定:用于父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容数据(自基础库版本 2.0.9 开始,还可以在数据中包含函数)。具体在 组件模板和样式 章节中介绍。 事件:用于子组件向父组件传递数据,可以传递任意数据。 如果以上两种方式不足以满足需要,父组件还可以通过 this.selectComponent 方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。 设置监听事件: [代码]<!-- wxml 中 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 --> <component-tag-name bindmyevent="setMyEvent" /> <!-- 或者可以写成 --> <component-tag-name bind:myevent="setMyEvent" /> [代码] [代码]// index.js 父页面中 Page({ setMyEvent: function(e){ let self = this; if (e.detail) { // 自定义组件触发事件时提供的detail对象 switch (e.detail) { case "hidden": //隐藏 悬浮框上的评论 this.setData({ isFixCommentShow: false }); break; case "fixRefresh": //刷新悬浮框 this.setData({ fixRefresh: true }); break; case "commentRefresh": //刷新评论 this.setData({ commentRefresh: Math.random() }); break; case "createPoster": //生成海报组件 self.setPosterSave(); break; } } } }) [代码] 父页面引用子组件,子组件发送的信息,可以通过bind的方法监听到,来获取到具体的传参值。 触发事件 自定义组件触发事件时,需要使用 triggerEvent方法,指定事件名、detail对象和事件选项: [代码]<!-- 页面 page.wxml --> <another-component bindcustomevent="pageEventListener1"> <my-component bindcustomevent="pageEventListener2"></my-component> </another-component> <!-- 组件 another-component.wxml --> <view bindcustomevent="anotherEventListener"> <slot /> </view> <!-- 组件 my-component.wxml --> <view bindcustomevent="myEventListener"> <slot /> </view> [代码] [代码]//组件中js Component({ properties: {}, methods: { onTap: function(){ var myEventDetail = {} // detail对象,提供给事件监听函数 var myEventOption = {} // 触发事件的选项 this.triggerEvent('myevent', myEventDetail, myEventOption) //myEventOption的一些配置: this.triggerEvent('customevent', {}, { bubbles: true }) // 会依次触发 pageEventListener2 、 pageEventListener1 this.triggerEvent('customevent', {}, { bubbles: true, composed: true }) // 会依次触发 pageEventListener2 、 anotherEventListener 、 pageEventListener1 } } }); [代码] myEventOption 的配置: bubbles(Boolean):事件是否冒泡 composed(Boolean):事件是否可以穿越组件边界,为false时,事件将只能在引用组件的节点树上触发,不进入其他任何组件内部 capturePhase(Boolean):事件是否拥有捕获阶段 需要强调一点:建议大家不要在组件上bind太多的监听,一方面以后管理起来会比较麻烦,另一方面首次加载如果调用过多方法会引起数据渲染的卡顿。 Component官方文档: https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/ 往期回顾: [填坑手册]小程序Canvas生成海报(一) [拆弹时刻]小程序Canvas生成海报(二)
2021-09-13