- 实战分享: 小程序云开发玩转订阅消息(二)
[图片]这是实战分享: 小程序云开发玩转订阅消息的第二部分 第一部分链接 《实战分享: 小程序云开发玩转订阅消息(一)》 将订阅消息存入云开发数据库接下来我们创建一个云函数 [代码]subscribe[代码] ,这个云函数的作用是将用户的订阅信息存入云开发数据库的集合 [代码]messages[代码] 中,等待将来需要通知用户时进行调用。 在微信开发者工具的云开发面板中创建数据库集合 [代码]messages[代码] [图片]微信开发者工具新增数据库集合 创建一个 [代码]subscribe[代码] 云函数,在云函数中我们将小程序端发送过来的课程订阅信息,存储在云开发数据库集合中,开发完成后,在微信开发者工具中右键上传并部署云函数。 cloudfunctions/subscribe/index.js [代码]const cloud = require('wx-server-sdk'); cloud.init(); const db = cloud.database(); exports.main = async (event, context) => { try { const {OPENID} = cloud.getWXContext(); // 在云开发数据库中存储用户订阅的课程 const result = await db.collection('messages').add({ data: { touser: OPENID, // 订阅者的openid page: 'index', // 订阅消息卡片点击后会打开小程序的哪个页面 data: event.data, // 订阅消息的数据 templateId: event.templateId, // 订阅消息模板ID done: false, // 消息发送状态设置为 false }, }); return result; } catch (err) { console.log(err); return err; } }; [代码]利用定时触发器来定期发送订阅消息接下来我们需要实现一个定时执行的云函数[代码]send[代码],来检查数据库中是否有需要发送给用户的订阅消息。如果有需要发送的订阅消息,会通过云调用 [代码]cloud.openapi.subscribeMessage.send[代码] 将订阅消息发送给用户。 创建一个名叫 [代码]send[代码] 的云函数,首先要配置云函数,在 [代码]config.json[代码] 的 [代码]permissions[代码] 中新增 [代码]subscribeMessage.send[代码]的云调用权限,然后新增一个 [代码]sendMessagerTimer[代码] 的定时触发器,定时触发器的语法和 [代码]linux[代码] 的 [代码]crontab[代码] 类似,比如,我们配置的 [代码]"0 * * * * * *"[代码] 代表每分钟执行一次云函数。 cloudfunctions/send/config.json [代码]{ "permissions": { "openapi": ["subscribeMessage.send"] }, "triggers": [ { "name": "sendMessagerTimer", "type": "timer", "config": "0 * * * * * *" } ] } [代码]接下来是实现发送订阅消息的云函数,这个云函数会从云开发数据库集合[代码]messages[代码]中查询等待发送的消息列表,检查数据库中是否有需要发送给用户的订阅消息,发送条件可以根据自己的业务实现,比如开课提醒可以根据课程开课日期来检查是否需要发送订阅消息,在我们下面的代码示例里做了简化,筛选条件只检查了状态为未发送。 查询到待发送的消息列表之后,我们会循环消息列表,依次发送每条订阅消息,发送成功后将数据库中消息的状态改为已发送。 cloudfunctions/send/index.js [代码]const cloud = require('wx-server-sdk'); exports.main = async (event, context) => { cloud.init(); const db = cloud.database(); try { // 从云开发数据库中查询等待发送的消息列表 const messages = await db .collection('messages') // 查询条件这里做了简化,只查找了状态为未发送的消息 // 在真正的生产环境,可以根据开课日期等条件筛选应该发送哪些消息 .where({ done: false, }) .get(); // 循环消息列表 const sendPromises = messages.data.map(async message => { try { // 发送订阅消息 await cloud.openapi.subscribeMessage.send({ touser: message.touser, page: message.page, data: message.data, templateId: message.templateId, }); // 发送成功后将消息的状态改为已发送 return db .collection('messages') .doc(message._id) .update({ data: { done: true, }, }); } catch (e) { return e; } }); return Promise.all(sendPromises); } catch (err) { console.log(err); return err; } }; [代码]最终效果 [图片]开课提醒订阅消息截图 源代码https://github.com/binggg/tcb-subscribe-demo[3] 参考资料 [1]注册小程序帐号: https://tencentcloudbase.github.io/2019-09-03-wx-dev-guide-register/ [2]开通云开发服务: https://tencentcloudbase.github.io/2019-09-03-wx-dev-guide-service/ [3]https://github.com/binggg/tcb-subscribe-demo: https://github.com/binggg/tcb-subscribe-demo
2019-10-23 - 纪录一个增加订阅次数的交互方案
普通小程序没有长期订阅消息,只能按照用户订阅次数发送订阅消息。如果订阅次数用完了怎么办呢? 可以让用户多点几次订阅,就可以做到了,但是这个操作对用户来说还是不太方便。怎样设计交互,让用户做起来不那么麻烦,是需要好好考虑的事情。 最近正好看到ReadHub的订阅方式,截图纪录一下。 [图片][图片][图片] 首先提醒用户,通知次数用完了,需要手动点击订阅,增加订阅次数。第一次会申请两个订阅,“每日早报提醒”和“订阅次数耗尽提醒”。用户点击允许以后,会发现可以接收的次数变成1了。 [图片][图片] 再次点击增加通知次数,从第二次开始,只申请一个“每日早报提醒”,每操作一次,订阅次数+1。点多了以后,嫌麻烦索性选中“总是保持以上选择,不再询问”,然后狂按增加订阅次数,会发现增加的次数并没有那么快,应该是每次订阅服务器端都要纪录一下才能展示出来,也就是说每次点击都落到数据库了。这样大概每秒1次,我点这21下也花了接近一分钟时间,不是一个可以忽略的操作。 更多参考: 云开发·多次订阅一次性订阅消息后定时发送
2021-03-08 - 微信小程序UI组件库合集
UI组件库合集,大家有遇到好的组件库,欢迎留言评论然后加入到文档里。 第一款: 官方WeUI组件库,地址 https://developers.weixin.qq.com/miniprogram/dev/extended/weui/ 预览码: [图片] 第二款: ColorUI:地址 https://github.com/weilanwl/ColorUI 预览码: [图片] 第三款: vantUI(又名:ZanUI):地址 https://youzan.github.io/vant-weapp/#/intro 预览码: [图片] 第四款: MinUI: 地址 https://meili.github.io/min/docs/minui/index.html 预览码: [图片] 第五款: iview-weapp:地址 https://weapp.iviewui.com/docs/guide/start 预览码: [图片] 第六款: WXRUI:暂无地址 预览码: [图片] 第七款: WuxUI:地址https://www.wuxui.com/#/introduce 预览码: [图片] 第八款: WussUI:地址 https://phonycode.github.io/wuss-weapp/quickstart.html 预览码: [图片] 第九款: TouchUI:地址 https://github.com/uileader/touchwx 预览码: [图片] 第十款: Hello UniApp: 地址 https://m3w.cn/uniapp 预览码: [图片] 第十一款: TaroUI:地址 https://taro-ui.jd.com/#/docs/introduction 预览码: [图片] 第十二款: Thor UI: 地址 https://thorui.cn/doc/ 预览码: [图片] 第十三款: GUI:https://github.com/Gensp/GUI 预览码: [图片] 第十四款: QyUI:暂无地址 预览码: [图片] 第十五款: WxaUI:暂无地址 预览码: [图片] 第十六款: kaiUI: github地址 https://github.com/Chaunjie/kai-ui 组件库文档:https://chaunjie.github.io/kui/dist/#/start 预览码: [图片] 第十七款: YsUI:暂无地址 预览码: [图片] 第十八款: BeeUI:git地址 http://ued.local.17173.com/gitlab/wxc/beeui.git 预览码: [图片] 第十九款: AntUI: 暂无地址 预览码: [图片] 第二十款: BleuUI:暂无地址 预览码: [图片] 第二十一款: uniydUI:暂无地址 预览码: [图片] 第二十二款: RovingUI:暂无地址 预览码: [图片] 第二十三款: DojayUI:暂无地址 预览码: [图片] 第二十四款: SkyUI:暂无地址 预览码: [图片] 第二十五款: YuUI:暂无地址 预览码: [图片] 第二十六款: wePyUI:暂无地址 预览码: [图片] 第二十七款: WXDUI:暂无地址 预览码: [图片] 第二十八款: XviewUI:暂无地址 预览码: [图片] 第二十九款: MinaUI:暂无地址 预览码: [图片] 第三十款: InyUI:暂无地址 预览码: [图片] 第三十一款: easyUI:地址 https://github.com/qq865738120/easyUI 预览码: [图片] 第三十二款 Kbone-UI: 地址 https://wechat-miniprogram.github.io/kboneui/ui/#/ 暂无预览码 第三十三款 VtuUi: 地址 https://github.com/jisida/VtuWeapp 预览码: [图片] 第三十四款 Lin-UI 地址:http://doc.mini.talelin.com/ 预览码: [图片] 第三十五款 GraceUI 地址: http://grace.hcoder.net/ 这个是收费的哦~ 预览码: [图片] 第三十六款 anna-remax-ui npm:https://www.npmjs.com/package/anna-remax-ui/v/1.0.12 anna-remax-ui 地址: https://annasearl.github.io/anna-remax-ui/components/general/button 预览码 [图片] 第三十七款 Olympus UI 地址:暂无 网易严选出品。 预览码 [图片] 第三十八款 AiYunXiaoUI 地址暂无 预览码 [图片] 第三十九款 visionUI npm:https://www.npmjs.com/package/vision-ui 预览码: [图片] 第四十款 AnimaUI(灵动UI) 地址:https://github.com/AnimaUI/wechat-miniprogram 预览码: [图片] 第四十一款 uView 地址:http://uviewui.com/components/quickstart.html 预览码: [图片] 第四十二款 firstUI 地址:https://www.firstui.cn/ 预览码: [图片]
2023-01-10 - 只有三行代码的神奇云函数的功能之四:获取电话号码
这是一个神奇的网站,哦不,神奇的云函数,它只有三行代码:(真的只有三行哦) 云函数:login index.js: const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event) => { return { ...event, ...cloud.getWXContext() } } 神奇功能之四:获取电话号码: 还是这三行代码,获取用户的电话号码。 wxml: <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" >{{mobile||"获得电话号码"}}</button> js: getPhoneNumber: function (e) { wx.cloud.callFunction({ name: 'login', data: {weRunData: wx.cloud.CloudID(e.detail.cloudID)} }).then(res => { this.setData({ mobile: res.result.weRunData.data.phoneNumber }) }) } 其他功能: 神奇功能之一:获取openid: https://developers.weixin.qq.com/community/develop/article/doc/00080c6e3746d8a940f9b43e55fc13 神奇功能之二:不用授权获取unionid: https://developers.weixin.qq.com/community/develop/article/doc/000a0c6b580338e947f9db0c65b813 神奇功能之三:100%成功获取unionid: https://developers.weixin.qq.com/community/develop/article/doc/00066a967c4e384949f93fe1151413 神奇功能之五:获取群id: 将小程序分享到某群里,可获得该群的群id, https://developers.weixin.qq.com/community/develop/article/doc/000ea802c00f70894cf9fe72556013 [图片]
2020-12-16 - 自定义标题栏
使用效果 [图片][图片][图片][图片] 使用方法 属性介绍 属性名 类型 默认值 是否必须 说明 menuSrc String ‘’ 否 按钮图片地址 bgImgSrc String ‘’ 否 背景图片地址 bgImgMode String aspectFill 否 背景图片的显示模式 title String ‘’ 否 标题 titleTextColor String ‘’ 否 字体和按钮以及loading图标的颜色,按钮和loading暂时只有黑白2色 backgroundColor String ‘’ 否 整个标题栏的背景颜色 loading Boolean false 否 是否是加载状态 backProxy Boolean false 否 是否重写了返回键 标题栏中属性的默认数据会自动获取json配置以及系统的默认数据,如果不需要动态更改样式,可以在json中设置,组件中同样起作用 事件介绍 属性名 detail NaviBack 返回的逻辑方法 MenuTap 按钮的点击事件 [代码]"usingComponents": { "toolBar": "/component/toolbar" }, [代码] [代码]<toolBar menuSrc='/image/menu_white.png' bindMenuTap='onMenuTap' bgImgSrc='/image/navi-bg.jpg' /> [代码] 高度说明: 为了方便适配,这里给出自定义标题栏的计算公式: const MenuRect = wx.getMenuButtonBoundingClientRect() const statusBarHeight = wx.getSystemInfoSync().statusBarHeight; const height = (MenuRect.top - statusBarHeight) * 2 + MenuRect.height +MenuRect.top Github地址:https://github.com/Aracy/wx-mini-navigationbar
2019-05-21 - 小程序生成海报:通过 json 配置方式轻松制作一张海报图
背景 由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 Canvas 功能生成一张带有二维码的图片,然后引导用户下载图片到本地后再分享到朋友圈。而小程序 Canvas 功能是很难用的,往往为了绘制一张简单图片,就得写上一堆 boilerplate code ,而且一不小心还会踩到 Canvas 的各种彩蛋(坑)。我想此时你的心情肯定是这样的。 分析 想到小程序中有如此大量的生成图片需求,而 Canvas 生成方法又是如此难用和坑爹。那我们就想到可不可以做一款可以很方便生成图片,并且还能屏蔽掉直接使用 Canvas 的一些坑的库呢?所以我们发起了 “画家计划— 通过 json 数据形式,来进行动态渲染并绘制出图片”。 Painter 库的整体架构如下: 首先,我们定义了一套绘图 JSON 规范,开发者可以根据需求构建生成图片的 Palette(调色板),然后在程序运行过程中把调色板传入给 Painter(画家)。Painter 会调用 Pen(画笔),根据 Palette 内容绘制出对应的图片后返回。 解决问题 那可不可以开发一款生成海报的插件库呢? 首先,只需要提供一份简单的参数配置文件即可 解决掉小程序Canvas遇到的一些大大小小的坑 有严苛的测试环节,解决各种环境和各种机型遇到的问题,并提供稳定的线上版本 长期维护,并有专人更新迭代更新颖的功能 ###隆重介绍 Painter 的优势 功能全,支持文本、图片、矩形、qrcode 类型的 view 绘制 布局全,支持多种布局方式,如 align(对齐方式)、rotate(旋转) 支持渐变色,阴影,配置简单,容易上手,兼容性好 支持圆角,其中图片,矩形,和整个画布支持 borderRadius 来设置圆角 杠杠的性能优化,我们对网络素材图片加载实现了一套 LRU 存储机制,不用重复下载素材图片。 杠杠的容错,因为某些特殊情况会导致 Canvas 绘图不完整。我们对此加入了对结果图片进行检测机制,如果绘图出错会进行重绘。 How To Use 运行例子 [代码]git clone https://github.com/Kujiale-Mobile/Painter.git [代码] 代码下载后,用小程序 IDE 打开后即可使用。 注:请选择小程序项目,非小游戏,例子中无 appid,所以无法在手机上运行,如果需要真机调试,请在打开例子时,填上自己的小程序 id 具体详细使用教程 GitHub 地址https://github.com/Kujiale-Mobile/Painter 扩展工具 由于编写配置再加上调试还是有些麻烦,故制作一款可视化编辑工具,直接拖动编辑元素即可生成海报 可视化编辑拖拽直接生成painter代码 工具体验地址https://lingxiaoyi.github.io/painter-custom-poster/ 工具使用文档https://juejin.im/post/5d8b20ba51882509615bca09 例子展示 [图片] [图片] [图片] [图片]
2019-10-14 - 如何用小程序实现类原生APP下一条无限刷体验
1.背景 如今信息流业务是各大互联网公司争先抢占的一个大面包,为了提高用户的后续消费,产品想出了各种各样的方法,例如在微视中,用户可以无限上拉出下一条视频;在知乎中,也可以无限上拉出下一条回答。这样的操作方式用户体验更好,后续消费也更多。最近几年的时间,微信小程序已经从一颗小小的萌芽成长为参天大树,形成了较大规模的生态,小程序也拥有了一个很大的流量入口。 2.demo体验 那如何才能在小程序中实现类原生APP效果的下一条无限刷体验? 这篇文章详细记录了下一条无限刷效果的实现原理,以及细节和体验优化,并将相关代码抽象成一个微信小程序代码片段,有需要的同学可查看demo源码。 线上效果请用微信扫码体验: [图片] 小程序demo体验请点击:https://developers.weixin.qq.com/s/vIfPUomP7f9a 3.实现原理 出于性能和兼容性考虑,我们尽量采用小程序官方提供的原生组件来实现下一条无限刷效果。我们发现,可以将无限上拉下一篇的文章看作一个竖向滚动的轮播图,又由于每一篇文章的内容长度高于一屏幕高度,所以需要实现文章内部可滚动,以及文章之间可以上拉和下拉切换的功能。 在多次尝试后,我们最终采用了在[代码]<swiper>[代码]组件内部嵌套一个[代码]<scroll-view>[代码]组件的方式实现,利用[代码]<swiper>[代码]组件来实现文章之间上拉和下拉切换的功能,利用[代码]<scroll-view>[代码]来实现一篇文章内部可上下滚动的功能。 所以页面的dom结构如下所示: [代码]<swiper class='scroll-swiper' circular="{{false}}" vertical="{{true}}" bindchange="bindChange" skip-hidden-item-layout="{{true}}" duration="{{500}}" easing-function="easeInCubic" > <block wx:for="{{articleData}}"> <swiper-item> <scroll-view scroll-top="0" scroll-with-animation="{{false}}" scroll-y > content </scroll-view> </swiper-item> </block> </swiper> [代码] 4.性能优化 我们知道view部分是运行在webview上的,所以前端领域的大多数优化方式都有用。例如减少代码包体积,使用分包,渲染性能优化等。下面主要讲一下渲染性能优化。 4.1 dom优化 由于页面需要无限上拉刷新,所以要在[代码]<swiper>[代码]组件中不断的增加[代码]<swiper-item>[代码],这样必然会导致页面的dom节点成倍数的增加,最后非常卡顿。 为了优化页面的dom节点,我们利用[代码]<swiper>[代码]的[代码]current[代码]和[代码]<swiper-item>[代码]的[代码]index[代码]来做优化,控制是否渲染dom节点。首先,仅当[代码]index <= current + 1[代码]时渲染[代码]<swiper-item>[代码],也就是页面中最多预先加载出下一条,而不是将接口返回的所有后续数据都渲染出来;其次,对于用户已经消费过的之前的[代码]<swiper-item>[代码],不能直接销毁dom节点,否则会导致[代码]<swiper>[代码]的[代码]current[代码]值出现错乱,但是我们可以控制是否渲染[代码]<swiper-item>[代码]内部的子节点,我们设置了仅当[代码]current <= index + 1 && index -1 <= current[代码]时才会渲染[代码]<swiper-item>[代码]中的内容,也就是仅渲染当先文章,及上一篇和下一篇的文章内容,其他文章的dom节点都被销毁了。 这样,无论用户上拉刷新了多少次,页面中最多只会渲染3篇文章的内容,避免了因为上拉次数太多导致的页面卡顿。 4.2 分页时setData的优化 setData工作原理 [图片] 小程序的视图层目前使用[代码]WebView[代码]作为渲染载体,而逻辑层是由独立的 [代码]JavascriptCore[代码] 作为运行环境。在架构上,[代码]WebView[代码] 和 [代码]JavascriptCore[代码] 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 [代码]evaluateJavascript[代码] 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 [代码]JS[代码] 脚本,再通过执行 [代码]JS[代码] 脚本的形式传递到两边独立环境。 而 [代码]evaluateJavascript[代码] 的执行会受很多方面的影响,数据到达视图层并不是实时的。 每次 [代码]setData[代码] 的调用都是一次进程间通信过程,通信开销与 setData 的数据量正相关。 [代码]setData[代码] 会引发视图层页面内容的更新,这一耗时操作一定时间中会阻塞用户交互。 [代码]setData[代码] 是小程序开发中使用最频繁的接口,也是最容易引发性能问题的接口。 避免不当使用setData [代码]data[代码] 应仅包括与页面渲染相关的数据,其他数据可绑定在this上。使用 [代码]data[代码] 在方法间共享数据,会增加 setData 传输的数据量,。 使用 [代码]setData[代码] 传输大量数据,通讯耗时与数据正相关,页面更新延迟可能造成页面更新开销增加。仅传输页面中发生变化的数据,使用 [代码]setData[代码] 的特殊 [代码]key[代码] 实现局部更新。 避免不必要的 [代码]setData[代码],避免短时间内频繁调用 [代码]setData[代码],对连续的setData调用进行合并。不然会导致操作卡顿,交互延迟,阻塞通信,页面渲染延迟。 避免在后台页面进行 [代码]setData[代码],这样会抢占前台页面的渲染资源。可将页面切入后台后的[代码]setData[代码]调用延迟到页面重新展示时执行。 优化示例 无限上拉刷新的数据会采用分页接口的形式,分多次请求回来。在使用分页接口拉取到下一刷的数据后,我们需要调用[代码]setData[代码]将数据写进[代码]data[代码]的[代码]articleData[代码]中,这个[代码]articleData[代码]是一个数组,里面存放着所有的文章数据,数据量十分庞大,如果直接[代码]setData[代码]会增加通讯耗时和页面更新开销,导致操作卡顿,交互延迟。 为了避免这个问题,我们将[代码]articleData[代码]改进为一个二维数组,每一次[代码]setData[代码]通过分页的 [代码]cachedCount[代码]标识来实现局部更新,具体代码如下: [代码]this.setData({ [`articleData[${cachedCount}]`]: [...data], cachedCount: cachedCount + 1, }) [代码] [代码]articleData[代码]的结构如下: [图片] 4.3 体验优化 解决了操作卡顿,交互延迟等问题,我们还需要对动画和交互的体验进行优化,以达到类原生APP效果的体验。 在文章间上拉切换时,我们使用了[代码]<swiper>[代码]组件自带的动画效果,并通过设置[代码]duration[代码]和[代码]easing-function[代码]来优化滚动细节和动画。 当用户阅读文章到底部时,会提示下一篇文章的标题等信息,而在页面上拉时,由于下一篇文章的内容已经加载出来了,这样在滑动过程中会出现两个重复的标题。为了避免这种情况出现,我们通过一个占满屏幕宽高的空白[代码]<view>[代码]来将下一篇文章的内容撑出屏幕,并在滚动结束时,通过[代码]hidden="{{index !== current && index !== current + 1}}"[代码]来隐藏这个空白[代码]<view>[代码],并对这个空白[代码]<view>[代码]的高度变化增加动画,来实现下一篇文章从屏幕底部滚动到屏幕顶部的效果: [代码].fake-scroll { height: 100%; width: 100%; transition: height 0.3s cubic-bezier(0.167,0.167,0.4,1); } [代码] [图片] 而当用户想要上拉查看之前阅读过的文章时,我们需要给用户一个“下滑查看上一条”提示,所以也可以采用同上的方式,通过一个占满屏幕宽高的提示语[代码]<view>[代码]来将上一篇文章的内容撑出屏幕,并在滚动结束时,通过[代码]wx:if="{{index + 1 === current}}"[代码]来隐藏这个提示语[代码]<view>[代码],并对这个提示语[代码]<view>[代码]的透明度变化增加动画,来实现下拉时提示“下滑查看上一条”的效果: [代码].fake-previous { height: 100%; width: 100%; opacity: 0; transition: opacity 1s ease-in; } .fake-previous.show-fake-previous { opacity: 1; } [代码] 至此,这个类原生APP效果的下一条无限刷体验的需求的所有要点和细节都已实现。 记录在此,欢迎交流和讨论。 小程序demo体验请点击:https://developers.weixin.qq.com/s/vIfPUomP7f9a
2019-06-25