- 小程序周刊#1 | 2019-08-05
你也可以为这个项目出一份力,如果发现有价值的信息、文章、工具等可以到 Issues 里提给我们,我们会尽快处理。记得写上推荐的理由哦。有建议和意见也欢迎到 Issues 提出。 新闻 行业相关的新闻、趣事、看法 小程序赋能第三方服务商,提供加急审核:微信团队针对第三方服务商提供了单独的预检加速机制。为优质小程序服务商赋能,帮助服务商更好的进行小程序的审核。 小程序发布用户账户登陆规范调整和优化建议:微信团队针对小程序中出现的账户登录功能给出了官方的指导意见,帮助开发者开发小程序时,合理利用用户登陆功能,优化用户体验。如未满足登录规范要求,从2019年9月1日开始,平台将会在后续的代码审核环节进行规则提示和修改要求反馈。 小程序新功能 小程序发布的新功能 新能力解读:小程序后台持续定位能力 @bestony:这篇文章介绍了 7 月 30 日发布的小程序后台持续定位能力的使用说明和介绍。 新能力解读:小程序页面间通信介绍 @bestony:这篇文章介绍了 7 月 2 日发布的小程序页面间通讯接口的使用说明和介绍。 新手推荐 适合新手的文章、教程 小程序云开发·学习路线 V1.0.0 @bestony: 这篇文章介绍了一些适合新手学习小程序云开发的视频课程。 文章 写的不错的技术博客,包含但不局限于小程序、设计、产品等 😺小程序性能优化的几点实践技巧 @bestony: 这篇文章分享了一下常见的几个小程序的优化点,包括了 存在 setData 中的数据过大 存在短时间内发起太多图片请求 存在图片太大而显示区域过小 列表渲染的问题 对于绝大多数开发者来说,仔细审阅你的代码,大多会遇到这个问题。所以你可以好好审查一下自己的代码,解决这几个问题,就可以提升小程序的速度。 😺小程序数据通信方法大全 @bestony: 这篇文章主要介绍的是小程序中的几种数据通信的模式,包括了页面与组件、组件与组件、页面与页面等。 在读这篇文章的时候需要注意的是,小程序现已经支持了页面之间之间传递数据。 😺微信小程序Video组件实践总结 - 掘金 @bestony: 这篇文章分享了如何基于 Cover-View 及 Video 组件进行页面布局、Video 组件的实现诸如”视频 WiFi 下自动播放“、”视频静音播放“、”视频列表“、”视频不在可视范围,停止播放“等功能的实现。 在我们使用 Video 组件制作小程序时,这些功能都是我们会用到的,你可以看一看这篇文章,或者收藏文章,后续需要的时候直接参考代码。 工具 开发过程中常用的工具,及一些新工具的介绍 🧐Remax.js:使用 React.js 开发小程序 @bestony:Remax.js 是一个新的小程序开发框架,支持使用 React 的 原生语法如 React Hooks 来编写小程序。 该组件库支持微信小程序、支付宝小程序等平台、集成了Umi 、React Developer Tools等工具。如果你是一个 React 开发者,又对小程序的编写有兴趣,那么这个仓库值得你去看。 👍 westore: 微信小程序的状态管理解决方案 @bestony: westore 是腾讯官方提供的状态管理解决方案,借助 westore ,你可以在原生的小程序代码中实现多页之间数据的同步。 对于简单的应用来说,你可以使用页面间跳转、App 实例的 globalData 数据来做页面间的同步,不过对于复杂的应用,显然这样是不够的,因此,你可以考虑使用 wstore 来完成更加复杂的状态管理。 蚂蚁搬家工具:一键实现微信小程序项目到支付宝小程序的迁徙,不再为重复开发而烦恼。 @bestony: 除了微信小程序,目前做的比较好的是支付宝的小程序,如果你对于开发支付宝小程序没有兴趣,同时你的应用对于性能、样式等的要求不那么高,你可以考虑使用这个工具来完成将微信小程序转换为支付宝小程序。 微信小程序开发助手 for VSCode @bestony: VSCode 是我自己用的比较多的编辑器,但是在开发小程序时,除了官方的 IDE 以外,其他的编辑器很少有对小程序支持的比较好的,这个工具,可以帮助你更好的使用 VSCode 开发小程序。这样你可以使用 VSCode 来写代码,然后使用小程序官方的 IDE 来完成预览。 hanzi-writer-miniprogram: Wechat Miniprogram plugin for Hanzi Writer (微信小程序组件) @bestony: 这是一个用来演示汉字笔顺的小程序组件,使用它,你可以在你自己的小程序中加入汉字笔顺的功能,对于一些教育型的小程序来说,这样的组件可以大大的节省时间,提升工作效率。 miniprogram-network @bestony: 这是一个小程序网络库,相比于小程序原生的网络请求,提供了完整代码自动完成 和 类型检查,支持Promise、自动重试、缓存、取消、自定义超时、全局拦截、和事件监听等…… 代码 库,代码段,开源小程序 WeChat-MiniProgram-WebAR:A WeChat MiniProgram Web AR with JavaScript Computer Vision Libraries. @bestony: 在 7 月 5 号,小程序官方发布 Web AR 的小程序 API,这个项目使用了其 API,完成了在小程序端的颜色识别、人脸识别等功能。 关注我们 我们开通了公众号,每期发布时公众号(程序百晓生)会推送消息,欢迎关注。 [图片] 同时也支持了 RSS 订阅:https://www.chengxuzhoukan.com/atom.xml 。 你可以在电脑访问我们的官网,查看最新的周刊:https://www.chengxuzhoukan.com 说明 🚧 表示需翻墙,🌟 表示编辑推荐 预计阅读时间:🐎 很快就能读完(1 - 10 mins);🐕 中等 (10 - 20 mins);🐢 慢(20+ mins)
2019-08-05 - 微信小程序--页面与组件之间如何进行信息传递和函数调用
微信小程序–页面与组件之间如何进行信息传递和函数调用 这篇文章我会以我自己开发经验从如下几个角度来讲解相关的内容 页面如何向组件传数据 组件如何向页面传数据 页面如何调用组件内的函数 组件如何调用页面内的函数 1.页面如何向组件传数据 最常用,最规范的方式,设置数据监听器[代码]observer[代码]。 假设在页面内引入了组件[代码]sc[代码] [代码]"usingComponents": { "sc":"" } [代码] 想要配置一个监听器用来监听页面中数据[代码]list[代码]的变化,组件在页面中的写法如下: [代码]<sc list="{{list}}" > </sc> [代码] [代码]组件中的监听器写法如下,当页面中的`list`值每次发生变化,都会触发`observer`监听器,打印出变化值。 [代码] [代码]properties: { list:{ type:Array, observer: function (newVal, oldVal){ console.log(newVal) } } } [代码] 同理,除了动态传值以外,这种方式也可以直接传入静态值,即不需要调用[代码]obeserver[代码]监听器。在组件中可以直接使用[代码]this.properties.*[代码]来获取[代码]properties[代码]中的各个值(*代表各个属性值的名称)。 2.组件如何向页面传数据 既然组件可以设置监听器用来监听页面数据变化,用来达到数据传递的效果,页面同样可以使用监听器,来监听组件触发的信息传送。 仍然以上面的组件为例,如何向页面中传送信息? 在页面中配置组件监听器 [代码]ComponentListener(e){ let info = e.detail; } [代码] 组件选择事件并绑定该监听器 [代码]<sc bind:listener="ComponentListener" > </sc> [代码] 从组件中往页面传入输入只需要在组件中触发对应事件,[代码]e.detail[代码]就是传过去的数据 [代码] this.triggerEvent('listener',{func,tid}); [代码] 3.页面如何调用组件内的函数 假设我们引入并使用了一个组件[代码]comment-bottom[代码],组件内有函数[代码]handleCloseInput[代码],需要在某个逻辑中触发。 想要使用组件内的函数,必须为组件配置一个唯一[代码]id[代码],这样就可以在页面中通过[代码]dom[代码]操作选中组件并调用组件中的函数。 [代码]<comment-bottom id="commentBottom"></comment-bottom> [代码] 组件中的函数在页面中的调用逻辑如下: [代码]this.commentBottom = this.selectComponent("#commentBottom"); this.commentBottom.handleCloseInput(); [代码] 4.组件如何调用页面内的函数 上面向页面传数据的方式,实际上就是调用了页面中的函数。我们可以这样理解该逻辑,将该用法理解为一个函数映射。 [代码]<component bind:componentfunc="pagefun"></component> [代码] 当使用[代码]trigger[代码]触发[代码]componentfunc[代码]时,通过[代码]bind:[代码]这个函数映射关系,就会触发页面中的[代码]pagefunc[代码]。 其次,调用页面内的函数,还可以通过页面栈的方式,组件并不占用页面的栈空间,因此在组件中使用[代码]getCurrentPages[代码]就可以获得对应页面的数据和方法。 [代码]var allpages = getCurrentPages();//获取全部页面数据 var nowpage = allpages[allpages.length - 1].data;//获取当前页面的数据。 var nowpage = allpages[allpages.length - 1];//获取页面,包括数据和方法 [代码] 这部分内容出自我的一篇文章,我会把地址放在参考文件中。 结语: 组件和组件之间的数据传递和组件与页面之间并没有太大区别,组件中也可以嵌套组件。 觉得ok请点下关注 参考文件 微信小程序开发技巧总结 (一)-- 数据传递和存储
2020-12-28 - 小程序流量运营之微信搜一搜
悄悄地使用微信搜一搜,然后惊艳所有人 微信小程序的服务一直很强大,大部分人眼馋,主要是不知道怎么操作才能让自己的小程序出现在服务标签里,今天给大家细说一下。 本文阅读指引:1、看展示;2、看效果;3、学习操作;4注意事项 一、首先给大家看下展示效果(随便搜的非广告) [图片] 二、开通服务后的效果 我有一个小程序本来是要放弃的,名字起的一般,搜索量没有,每天用户不到10人,都准备注销了。但是开通服务后每天至少能达到100多人。下面是最近一周详细数据。 [图片] [图片] [图片] [图片] 看着数据是不是很带劲,对于日活上万的运营人员来说可能不算什么,但是对于一个即将放弃、没有流量的小程序来说,看着它日活从0-100的成就感还是很强的,可以毫不夸张的说,开通搜一搜服务让我的流量至少翻了10倍。 三、开通服务操作 3-1、硬件条件:认证的公众号超过6个月,只支持普通微信小程序(微店等小程序不支持) 3-2、公众号-微信搜一搜,开通服务搜索接入(下图) [图片] 3-3、然后转移到【服务搜索】添加服务,去提交小程序,审核通过即可(下图) [图片] [图片] 3-4、最后去品牌主页,编辑添加你的小程序去提交即可。(下图) [图片] 四、审核注意事项 下面内容均为我审核期间遇到的问题(非官方),如果审核失败没遇到我出现的问题,请自行解决,或者私信我帮你一起解决。 4-1、落地页不能有激励、插屏、视频、关注公众号、webview等其他广告 4-2、服务名称、服务标签要有服务意图 4-3、服务标签不能用大众化关键字,不能与其他服务关键字冲突 4-4、服务标签要与服务名称有相关性 4-5、自己体验落地页,要保证对用户服务的质量 4-6、落地页不能聚集服务,必须单服务和服务名称对应(同一个小程序多种服务,可多次提交不同页面) 4-7、服务图标不得含有品牌图标(不要侵权) 4-8、要分清自己的小程序是服务类还是内容类,内容类开通不了。例:头像制作类、表情类相关不支持,如果你有这类小程序的话,提审也会失败。 以下为不定期数据更新 20207月30日记录:29号下午开通了流量主,今天查看收益为4.99元,大部分用的是原生模板广告,无激励,用户数依旧呈现上升趋势。 [图片] [图片] see you next article
2020-07-30 - 第三方注册的小程序可以直接开通云开发-无限制
亲测,开发工具版本:1.05.2102242,通过第三方注册的小程序可以直接开通云开发,手机号还是需要绑定的。但是,是突破以前的5个限制。 我的情况:目前有72个小程序,我自己的手机号100%已经绑定5个小程序了,所以我的手机号是无法再次绑定小程序的。 下面是体验流程: 1、下载最新版开发工具:https://developers.weixin.qq.com/miniprogram/dev/devtools/nightly.html [图片] 2、创建云开发项目 [图片] 3、开通云开发,会提示发送到管理员微信绑定手机号 [图片] 4、微信公众平台会直接给通知,点击进去即可 [图片] 5、直接绑定手机号,我用的手机号已经绑定了5个小程序 [图片] 5、绑定成功 [图片] 6、开通后需要关闭开发工具重新打开才能开通,要不然还是提示第三步的发送到管理员微信绑定手机号 7、看完有帮助的话,知道干嘛吧?
2021-02-25 - Swiper、video等组件自适应高度的办法。
用过swiper和video等组件的人都知道,这几个组件的高度是无法自适应屏幕高度的,也退style="height:100%"是无用的。 以下是解决组件自适应高度的办法,代码如下: <view style="height: 100%;display: flex;flex-direction: column;"> <swiper style="flex:1"> </swiper> </view> 这样,这个swiper就能自适应高度了。
2021-03-29 - 如何提升你的云函数性能
在使用云开发一段时间后,你一定会遇见一个问题:虽然云函数非常的方便,但我的云函数似乎性能不够好,为什么我的云函数每次加载都需 2 ~ 3 秒种,时间太长了!。 这篇文章,就来告诉你,应该如何提升你的云函数性能。 如何了解云函数运行情况? 在了解如何优化云函数的运行情况之前, 我们需要先了解,如何查看当前的云函数运行情况,这样才能有个对比。 [图片] 打开小程序开发者工具,并打开你的项目 进入到你要调试的页面,打开调试器 调用云函数,并在调试器中切换到 Network 页面,找到你的请求。 点击你的请求,然后切换到 Timing 页面,查看具体的情况。 在这个页面中,你可以理解其中的 Waiting(TTFB) 是你发起请求到你接收到返回结果的第一个字节的时间,简单的来说,就是服务器计算结果需要花费的时间。而下方的 Content Download 则是下载内容所需的时间,你可以理解是表现出网络速度快慢的数据。 总结来说,就是如果 Waiting TTFB 的值比较大,你就去优化云函数性能。如果 Content Download 的数值毕竟大,你就需要优化网络情况 优化 Waiting TTFB 云函数的运行机制 Waiting TTFB 的优化是云函数性能端的优化,那么在优化之前,我们就需要先来了解一下云函数的运行机制,以便帮助我们了解应该如何去进行性能优化。 [图片] 在蕴含运行时,具体的顺序是这样的 用户发起请求,请求发送到云开发的后台 云开发后台的调度器将请求分发给下方的执行的 worker 、容器 容器创建环境、下载代码 执行代码 在这个过程中,发起请求到云开发、调度器调度速度、调度器传递信息到容器、函数调用等,都是可以优化的,但是我们在具体的使用过程中。这些大都需要由云开发的工作人员来完成,对于我们自己来说,只能去尽可能的优化容器内部到代码层面的东西。 接下来,我们可以看看更细致的调用逻辑。 [图片] 在云开发中,我们可以将调用分为三种类型: 冷启动:图中的红色阶段,需要重新创建容器、下载代码,耗时最长 温启动:图中的黄色阶段,需要下载代码,耗时较长 热启动:图中的蓝色阶段,不需要下载代码,耗时最短 我们可以看到,最快的,是热启动,函数不需要创建容器,不需要启动函数就可以完成执行,显然比要创建容器或要下载代码的温启动和冷启动速度更快。这样,我们就得到了优化云函数性能的第一个方法 1. 让你的云函数每次调用都走热启动 当我们可以让我们的云函数的每一次调用都走热启动,少了容器的创建和函数的部署,请求的速度理所当然的要比冷启动和温启动更快。 我们可以测试一下,我设置每秒调用一次云函数,看看 TTFB 的变化。 [代码]setInterval(()=>{wx.cloud.callFunction({name:'profile'})},1000) [代码] 函数内代码是默认创建的云函数代码。 则对应的执行效果如下 [图片] 可以看到,函数的执行时间从第一次的 1.2s 降低到了 200ms左右,性能提升了 80%,我们仅仅是简单的提升了函数的调用频次,就可以实现提升函数的调用性能,这就是热启动带给我们的价值。 实施方案 如果你需要足够高的性能,不妨借助云开发的定时器,定期唤起你的容器,从而为你的容器保活,确保你的函数时刻被热启动。 2. 缩小你的函数大小 在前面我们曾介绍过,云函数在启动过程中,会创建容器和下载代码。创建容器的过程对于开发者来说不可控,不过我们可以使用一些方法,缩小我们的代码,提升代码的下载速度,比如说,缩小我们的函数代码。 这里我们可以做个测试,这里我创建了两个函数,两个函数的代码完全一致,不同的是,在实验组的函数中,我加入了一个 temp 变量的声明,这个变量的值是一个非常长的字符串,从而使得两个函数的大小分别是 68K 和 4K。 接下来,我们看看二者的执行时间。 [图片] 我们会发现,几乎没有差距的代码,因为加入了变量声明的因素,在性能上会略慢几毫秒,后续随着容器的不断复用,函数的之间的差距也越来越小,几乎可以忽视。 实施方案 对于你的代码,要尽可能的精炼,减少无用的代码,减少代码下载所需时间。 3. 削减不需要的 Package 除了下载代码以外,还需要下载 Node 环境运行所需的依赖包,虽然云开发可能针对 Node Modules 已经做了缓存,但依然存在下载的时间差区别,这里我也做了一个实验。 空包:什么都没装,把 wx-server-sdk 都卸载掉了。 复杂包:装了 Mongoose、sequelize、sails 等依赖的包。 函数逻辑上也相差无几,都是返回 Event ,则结果如下 [图片] 我们发现,前三次可能是因为涉及到依赖包的下载问题,所以前三次的时长大小对比特别的明显,而从第四次开始,二者的区别就不大了,可能是因为依赖已经完成了缓存,所以可以直接使用缓存来完成函数的执行。 实施方案 你可以选择看看你的 package.json ,看看其中是否有你不需要的依赖,将其删除,仅保留有需要的依赖,可以有效提升你的代码执行速度。 优化 Content Download 如果你想要优化 Content Download ,核心需要优化的是两个点: 手机到服务端的节点的距离和速度 内容的大小 前者一般来说,你可以通过切换不同的网络环境来实现优化,比如从 3G 切换到 4G ,从 4G 升级到 5G,这些都可以提升你的手机到服务端节点之间的速度。 此外,还可以借助内容分发网络 CDN 能力来完成缩小你到服务端节点之间的距离,不过对于云函数来说,因为你不可控,无法控制,所以这一点不再谈。 这里补充一句,云开发的文件存储都是有 CDN 的,因此,你通过云存储下载的文件才会比别人更快。 后者则一般通过调整代码来完成,比如只返回必须的资源,对于不需要的内容,不再返回,或压缩返回。 总结 最后,我们回顾一下这篇文章中介绍的优化云函数的方法: 函数下载性能优化 保持函数容器的热启动,提升函数启动性能 缩小函数大小,提升代码下载速度 削减不必要的包,减少依赖大小 网络优化 使用更好的网络,比如 Wi-Fi 云函数中仅返回所需要的内容,减少下载时间。 以上这些方法,你都在你的函数中试过么?有没有其他的优化方法?欢迎你与我分享。
2019-12-08 - 云数据库聚合查询,利用$.setUnion或$.setDifference对数组型字段的元素去重
一、查遍文档也没找到去重的办法,最后发现这两个函数可以曲线救国 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/command/aggregate/AggregateCommand.setUnion.html https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/command/aggregate/AggregateCommand.setDifference.html 假设记录中存在一个数组型字段suppliers,里面存在重复的元素,我们在输出时希望去重,给前台一份干净的数据。 suppliers: ['a', 'a', 'b', 'c'] 二、先试试setUnion,函数的作用是输出两个数组的并集,那么加入一个空数组,相当于去重,示例如下: await db.collection('orders') .aggregate() .addFields({ suppliersDistinct: $.setUnion(['$suppliers', []]) }).end() 输出结果为: suppliersDistinct: ['a', 'b', 'c'] 三、再试试setDifference,函数作用是输出仅存在第一个数组中的值,可以用于先排除一个指定值,假如想排除c,示例如下 await db.collection('orders') .aggregate() .addFields({ suppliersDistinct: $.setDifference(['$suppliers', ['c']]) }).end() 输出结果为: suppliersDistinct: ['a', 'b']
2020-08-03 - 【笔记】云开发通过客服消息实现自动回复进群,同时兼容客服小助手
小程序不具备小程序内扫描二维码的能力,因此如果要实现关注公众号或加用户群功能大家一般都利用微信客服功能的自动回复来实现。此时如果自己去实现微信客服自动回复,客服小助手就不能用了,很令人纠结。经过我的研究,借助云开发找到了一个方案,可以实现当用户想获取微信群走自动回复的接口,真正咨询时,直接到客服小助手进行回复。 效果如下 [图片] 原理解析 云开发在做消息推送配置的时候可以配置消息类型,这个时候如果我们只配置一种类型(小程序卡片),那么就只有卡片才会被云函数接管做自动回复,其他消息类型(图片、文字)则正常走小程序客服的通道。 实现步骤 1.小程序端设置按钮属性open-type="contact",用于用户点击时带上定义的卡片跳到客服消息界面。 申请加入 2.新建云端的函数,设置config.json定义权限,如下config.json { "permissions": { "openapi": [ "customerServiceMessage.send", "customerServiceMessage.uploadTempMedia", "customerServiceMessage.setTyping" ] } } 3.写云函数端代码,如下 if (event.Title == "我要进用户群"||event.Title =="关注公众号"||event) { //设置输入状态 cloud.openapi.customerServiceMessage.setTyping({ touser: OPENID, command: 'Typing' }) //从云储存中拉取图片数据 const qunimg = await cloud.downloadFile({ fileID: "cloud://pm-hsfip.706d-pm-hsfip-1259751853/img/qun.png", }) //上传图片素材到媒体库,并获取到图片id const qunmedia = await cloud.openapi.customerServiceMessage.uploadTempMedia({ type: 'image', media: { contentType: 'image/png', value: qunimg.fileContent } }) //向用户发送群二维码 await cloud.openapi.customerServiceMessage.send({ touser: OPENID, msgtype: 'image', image: { mediaId: qunmedia.mediaId, } }) //取消状态设置 cloud.openapi.customerServiceMessage.setTyping({ touser: OPENID, command: 'CancelTyping' }) } 4.设置消息推送,路径如下 云开发-设置-全局设置-云函数接收消息推送 中添加消息类型为miniprogrampage,绑定云函数为新建的云函数。 [图片] 5.微信公众平台绑定客服[图片] 注意事项 如果按照本教程 客服小助手无法收到消息 或 无法自动回复,可以先将以上消息推送配置删除,然后再微信后台绑定客服后,再重新进行消息推送配置。
2020-07-29 - wx.getUserProfile 修改方案
最近微信关于用户头像、昵称授权又做了调整。 点击查看原文 解决方案思路如下: 1、在util.js里写一个通用函数,函数的功能是,用户授权成功,将头像昵称,存入服务器,同时,在本地缓存设置标记用户授权成功。 [代码]// util.js function getUserProfile() { wx.getUserProfile({ desc: '用于完善个人资料', success: function(res) { var userInfo = res.userInfo // console.log('userInfo==>', userInfo) wx.setStorageSync('storage_info', 1);//本地标记 //下面将userInfo存入服务器中的用户个人资料 //... }, fail() { console.log("用户拒绝授权") } }) } [代码] 2、在需要用户授权时,做判断,如果本地已经授权,直接执行正常业务逻辑。如果未授权,则提示授权。 [代码] chooseTap: function(e) { //如果未授权,就提示授权,如果授权了,就执行正常的业务逻辑 if (!wx.getStorageSync('storage_info')) { util.getUserProfile() return } //下面是正常业务逻辑 //... } [代码] 3、在用户进入小程序时,从服务器获取用户信息(如果已授权,就有之前存入的头像,昵称),在页面展示用户信息。 完成以上3步,就全部完成了。 说明:把授权状态存入缓存的好处是:因为wx.getUserProfile每次都会弹授权框,如果每次都让用户授权,体验不好。如果只授权一次,存入服务器,以后都展示的是这个用户信息。在用户微信改名,改头像后,服务器储存的用户信息还是以前的。 所以,把授权状态存入缓存,起码在用户更换手机,或者删除过小程序,又进来时,会弹出授权提示,可以让用户重新授权,将服务器里用户的信息进行一次更新。
2021-04-11 - 小程序奇技淫巧之 -- 日志能力
日志与反馈 前端开发在进行某个问题定位的时候,日志是很重要的。因为机器兼容性问题、环境问题等,我们常常无法复现用户的一些bug。而微信官方也提供了较完整的日志能力,我们一起来看一下。 用户反馈 小程序官方提供了用户反馈携带日志的能力,大概流程是: 开发中日志打印,使用日志管理器实例 LogManager。 用户在使用过程中,可以在小程序的 profile 页面(【右上角胶囊】-【关于xxxx】),点击【投诉与反馈】-【功能异常】(旧版本还需要勾选上传日志),则可以上传日志。 在小程序管理后台,【管理】-【反馈管理】,就可以查看上传的日志(还包括了很详细的用户和机型版本等信息)。 这个入口可能对于用户来说过于深入(是的,官方也发现这个问题了,所以后面有了实时日志),我们小程序也可以通过[代码]button[代码]组件,设置[代码]openType[代码]为[代码]feedback[代码],然后用户点击按钮就可以直接拉起意见反馈页面了。利用这个能力,我们可以监听用户截屏的操作,然后弹出浮层引导用户主动进行反馈。 [代码]<view class="dialog" wx:if="{{isFeedbackShow}}"> <view>是否遇到问题?</view> <button open-type="feedback">点击反馈</button> </view> wx.onUserCaptureScreen(() => { // 设置弹窗出现 this.setData({isFeedbackShow: true}) }); [代码] LogManager 关于小程序的 LogManager,大概是非常实用又特别低调的一个能力了。它的使用方式其实和 console 很相似,提供了 log、info、debug、warn 等日志方式。 [代码]const logger = wx.getLogManager() logger.log({str: 'hello world'}, 'basic log', 100, [1, 2, 3]) logger.info({str: 'hello world'}, 'info log', 100, [1, 2, 3]) logger.debug({str: 'hello world'}, 'debug log', 100, [1, 2, 3]) logger.warn({str: 'hello world'}, 'warn log', 100, [1, 2, 3]) [代码] 打印的日志,从管理后台下载下来之后,也是很好懂: [代码]2019-6-25 22:11:6 [log] wx.setStorageSync api invoke 2019-6-25 22:11:6 [log] wx.setStorageSync return 2019-6-25 22:11:6 [log] wx.setStorageSync api invoke 2019-6-25 22:11:6 [log] wx.setStorageSync return 2019-6-25 22:11:6 [log] [v1.1.0] request begin 2019-6-25 22:11:6 [log] wx.request api invoke with seq 0 2019-6-25 22:11:6 [log] wx.request success callback with msg request:ok with seq 0 2019-6-25 22:11:6 [log] [v1.1.0] request done 2019-6-25 22:11:7 [log] wx.navigateTo api invoke 2019-6-25 22:11:7 [log] page packquery/pages/index/index onHide have been invoked 2019-6-25 22:11:7 [log] page packquery/pages/logs/logs onLoad have been invoked 2019-6-25 22:11:7 [log] [v1.1.0] logs | onShow | | [] 2019-6-25 22:11:7 [log] wx.setStorageSync api invoke 2019-6-25 22:11:7 [log] wx.setStorageSync return 2019-6-25 22:11:7 [log] wx.reportMonitor api invoke 2019-6-25 22:11:7 [log] page packquery/pages/logs/logs onShow have been invoked 2019-6-25 22:11:7 [log] wx.navigateTo success callback with msg navigateTo:ok [代码] LogManager 最多保存 5M 的日志内容,超过5M后,旧的日志内容会被删除。基础库默认会把 App、Page 的生命周期函数和 wx 命名空间下的函数调用写入日志,基础库的日志帮助我们定位具体哪些地方出了问题。 实时日志 小程序的 LogManager 有一个很大的痛点,就是必须依赖用户上报,入口又是右上角胶囊-【关于xxxx】-【投诉与反馈】-【功能异常】这么长的路径,甚至用户的反馈过程也会经常丢失日志,导致无法查问题。 为帮助小程序开发者快捷地排查小程序漏洞、定位问题,微信推出了实时日志功能。从基础库 2.7.1 开始,开发者可通过提供的接口打印日志,日志汇聚并实时上报到小程序后台。 使用方式如下: 使用 wx.getRealtimeLogManager 在代码⾥⾯打⽇志。 可从小程序管理后台【开发】-【运维中心】-【实时日志】进入日志查询页面,查看开发者打印的日志信息。 开发者可通过设置时间、微信号/OpenID、页面链接、FilterMsg内容(基础库2.7.3及以上支持setFilterMsg)等筛选条件查询指定用户的日志信息: [图片] 由于后台资源限制,实时日志使用规则如下: 为了定位问题方便,日志是按页面划分的,某一个页面,在onShow到onHide(切换到其它页面、右上角圆点退到后台)之间打的日志,会聚合成一条日志上报,并且在小程序管理后台上可以根据页面路径搜索出该条日志。 每个小程序账号每天限制500万条日志,日志会保留7天,建议遇到问题及时定位。 一条日志的上限是5KB,最多包含200次打印日志函数调用(info、warn、error调用都算),所以要谨慎打日志,避免在循环里面调用打日志接口,避免直接重写console.log的方式打日志。 意见反馈里面的日志,可根据OpenID搜索日志。 setFilterMsg 可以设置过滤的 Msg。这个接口的目的是提供某个场景的过滤能力,例如[代码]setFilterMsg('scene1')[代码],则在 MP 上可输入 scene1 查询得到该条日志。比如上线过程中,某个监控有问题,可以根据 FilterMsg 过滤这个场景下的具体的用户日志。FilterMsg 仅支持大小写字母。如果需要添加多个关键字,建议使用 addFilterMsg 替代 setFilterMsg。 日志开发技巧 既然官方提供了 LogManager 和实时日志,我们当然是两个都要用啦。 log.js 我们将所有日志的能力都封装在一起,暴露一个通用的接口给调用方使用: [代码]// log.js const VERSION = "0.0.1"; // 业务代码版本号,用户灰度过程中观察问题 const canIUseLogManage = wx.canIUse("getLogManager"); const logger = canIUseLogManage ? wx.getLogManager({level: 0}) : null; var realtimeLogger = wx.getRealtimeLogManager ? wx.getRealtimeLogManager() : null; /** * @param {string} file 所在文件名 * @param {...any} arg 参数 */ export function DEBUG(file, ...args) { console.debug(file, " | ", ...args); if (canIUseLogManage) { logger!.debug(`[${VERSION}]`, file, " | ", ...args); } realtimeLogger && realtimeLogger.info(`[${VERSION}]`, file, " | ", ...args); } /** * * @param {string} file 所在文件名 * @param {...any} arg 参数 */ export function RUN(file, ...args) { console.log(file, " | ", ...args); if (canIUseLogManage) { logger!.log(`[${VERSION}]`, file, " | ", ...args); } realtimeLogger && realtimeLogger.info(`[${VERSION}]`, file, " | ", ...args); } /** * * @param {string} file 所在文件名 * @param {...any} arg 参数 */ export function ERROR(file, ...args) { console.error(file, " | ", ...args); if (canIUseLogManage) { logger!.warn(`[${VERSION}]`, file, " | ", ...args); } if (realtimeLogger) { realtimeLogger.error(`[${VERSION}]`, file, " | ", ...args); // 判断是否支持设置模糊搜索 // 错误的信息可记录到 FilterMsg,方便搜索定位 if (realtimeLogger.addFilterMsg) { try { realtimeLogger.addFilterMsg( `[${VERSION}] ${file} ${JSON.stringify(args)}` ); } catch (e) { realtimeLogger.addFilterMsg(`[${VERSION}] ${file}`); } } } } // 方便将页面名字自动打印 export function getLogger(fileName: string) { return { DEBUG: function(...args) { DEBUG(fileName, ...args); }, RUN: function(...args) { RUN(fileName, ...args); }, ERROR: function(...args) { ERROR(fileName, ...args); } }; } [代码] 通过这样的方式,我们在一个页面中使用日志的时候,可以这么使用: [代码]import { getLogger } from "./log"; const PAGE_MANE = "page_name"; const logger = getLogger(PAGE_MANE); [代码] autolog-behavior 现在有了日志组件,我们需要在足够多的地方记录日志,才能在问题出现的时候及时进行定位。一般来说,我们需要在每个方法在被调用的时候都打印一个日志,所以这里封装了一个 autolog-behavior 的方式,每个页面(需要是 Component 方式)中只需要引入这个 behavior,就可以在每个方法调用的时候,打印日志: [代码]// autolog-behavior.js import * as Log from "../utils/log"; /** * 本 Behavior 会在小程序 methods 中每个方法调用前添加一个 Log 说明 * 需要在 Component 的 data 属性中添加 PAGE_NAME,用于描述当前页面 */ export default Behavior({ definitionFilter(defFields) { // 获取定义的方法 Object.keys(defFields.methods || {}).forEach(methodName => { const originMethod = defFields.methods![methodName]; // 遍历更新每个方法 defFields.methods![methodName] = function(ev, ...args) { if (ev && ev.target && ev.currentTarget && ev.currentTarget.dataset) { // 如果是事件类型,则只需要记录 dataset 数据 Log.RUN(defFields.data.PAGE_NAME, `${methodName} invoke, event dataset = `, ev.currentTarget.dataset, "params = ", ...args); } else { // 其他情况下,则都记录日志 Log.RUN( defFields.data.PAGE_NAME, `${methodName} invoke, params = `, ev, ...args); } // 触发原有的方法 originMethod.call(this, ev, ...args); }; }); } }); [代码] 我们能看到,日志打印依赖了页面中定义了一个[代码]PAGE_NAME[代码]的 data 数据,所以我们在使用的时候可以这么处理: [代码]import { getLogger } from "../../utils/log"; import autologBehavior from "../../behaviors/autolog-behavior"; const PAGE_NAME = "page_name"; const logger = getLogger(PAGE_NAME); Component({ behaviors: [autologBehavior], data: { PAGE_NAME, // 其他数据 }, methods: { // 定义的方法会在调用的时候自动打印日志 } }); [代码] 页面如何使用 Behavior 看看官方文档:事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用[代码]Component[代码]构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应[代码]json[代码]文件中包含[代码]usingComponents[代码]定义段。 完整的项目可以参考wxapp-typescript-demo。 参考 LogManager 实时日志 Component构造器 behaviors 结束语 使用自定义组件的方式来写页面,有特别多好用的技巧,behavior 就是其中一个比较重要的能力,大家可以发挥自己的想象力来实现很多奇妙的功能。
2019-12-10 - 小程序使用css变量实现“换肤”方案
使用sass,stylus可以很方便的使用变量来做样式设计,其实css也同样可以定义变量,在小程序中由于原生不支持动态css语法,so,可以使用css变量来使用开发工作变简单。 基础用法 <!--web开发中顶层变量的key名是:root,小程序使用page--> page { --main-bg-color: brown; } .one { color: white; background-color: var(--main-bg-color); margin: 10px; } .two { color: white; background-color: black; margin: 10px; } .three { color: white; background-color: var(--main-bg-color); } 提升用法 <div class="one"> <div class="two"> <div class="three"> </div> <div class="four"> </div> <div> </div> .two { --test: 10px; } .three { --test: 2em; } 在这个例子中,var(--test)的结果是: class="two" 对应的节点: 10px class="three" 对应的节点: element: 2em class="four" 对应的节点: 10px (继承自父级.two) class="one" 对应的节点: 无效值, 即此属性值为未被自定义css变量覆盖的默认值 上述是一些基本概念,大致说明css变量的使用方法,注意在web开发中,我们使用:root来设置顶层变量,更多详细说明参考MDN的 文档 妙用css变量 开发中经常遇到的问题是,css的数据是写死的,不能够和js变量直通,即有些数据使用动态变化的,但css用不了。对了,可以使用css变量试试呀 js // 在js中设置css变量 let myStyle = ` --bg-color:red; --border-radius:50%; --wid:200px; --hgt:200px;` let chageStyle = ` --bg-color:red; --border-radius:50%; --wid:300px; --hgt:300px;` Page({ data: { viewData: { style: myStyle } }, onLoad(){ setTimeout(() => { this.setData({'viewData.style': chageStyle}) }, 2000); } }) wxml <!--将css变量(js中设置的那些)赋值给style--> <view class="container"> <view class="my-view" style="{{viewData.style}}"> <image src="/images/abc.png" mode="widthFix"/> </view> </view> wxss /* 使用var */ .my-view{ width: var(--wid); height: var(--hgt); border-radius: var(--border-radius); padding: 10px; box-sizing: border-box; background-color: var(--bg-color); transition: all 0.3s ease-in; } .my-view image{ width: 100%; height: 100%; border-radius: var(--border-radius); } 通过css变量就可以动态设置css的属性值 作者:天天修改 链接:https://segmentfault.com/a/1190000021922132
2020-07-06 - 如何实现快速生成朋友圈海报分享图
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 Canvas 功能生成一张带有小程序码的图片,然后引导用户下载图片到本地后再分享到朋友圈。相信大家在绘制分享图中应该踩到 Canvas 的各种(坑)彩dan了吧~ 这里首先推荐一个开源的组件:painter(通过该组件目前我们已经成功在支付宝小程序上也应用上了分享图功能) 咱们不多说,直接上手就是干。 [图片] 首先我们新增一个自定义组件,在该组件的json中引入painter [代码]{ "component": true, "usingComponents": { "painter": "/painter/painter" } } [代码] 然后组件的WXML (代码片段在最后) [代码]// 将该组件定位在屏幕之外,用户查看不到。 <painter style="position: absolute; top: -9999rpx;" palette="{{imgDraw}}" bind:imgOK="onImgOK" /> [代码] 重点来了 JS (代码片段在最后) [代码]Component({ properties: { // 是否开始绘图 isCanDraw: { type: Boolean, value: false, observer(newVal) { newVal && this.handleStartDrawImg() } }, // 用户头像昵称信息 userInfo: { type: Object, value: { avatarUrl: '', nickName: '' } } }, data: { imgDraw: {}, // 绘制图片的大对象 sharePath: '' // 生成的分享图 }, methods: { handleStartDrawImg() { wx.showLoading({ title: '生成中' }) this.setData({ imgDraw: { width: '750rpx', height: '1334rpx', background: 'https://qiniu-image.qtshe.com/20190506share-bg.png', views: [ { type: 'image', url: 'https://qiniu-image.qtshe.com/1560248372315_467.jpg', css: { top: '32rpx', left: '30rpx', right: '32rpx', width: '688rpx', height: '420rpx', borderRadius: '16rpx' }, }, { type: 'image', url: this.data.userInfo.avatarUrl || 'https://qiniu-image.qtshe.com/default-avatar20170707.png', css: { top: '404rpx', left: '328rpx', width: '96rpx', height: '96rpx', borderWidth: '6rpx', borderColor: '#FFF', borderRadius: '96rpx' } }, { type: 'text', text: this.data.userInfo.nickName || '青团子', css: { top: '532rpx', fontSize: '28rpx', left: '375rpx', align: 'center', color: '#3c3c3c' } }, { type: 'text', text: `邀请您参与助力活动`, css: { top: '576rpx', left: '375rpx', align: 'center', fontSize: '28rpx', color: '#3c3c3c' } }, { type: 'text', text: `宇宙最萌蓝牙耳机测评员`, css: { top: '644rpx', left: '375rpx', maxLines: 1, align: 'center', fontWeight: 'bold', fontSize: '44rpx', color: '#3c3c3c' } }, { type: 'image', url: 'https://qiniu-image.qtshe.com/20190605index.jpg', css: { top: '834rpx', left: '470rpx', width: '200rpx', height: '200rpx' } } ] } }) }, onImgErr(e) { wx.hideLoading() wx.showToast({ title: '生成分享图失败,请刷新页面重试' }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') }, onImgOK(e) { wx.hideLoading() // 展示分享图 wx.showShareImageMenu({ path: e.detail.path, fail: err => { console.log(err) } }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') } } }) [代码] 那么我们该如何引用呢? 首先json里引用我们封装好的组件share-box [代码]{ "usingComponents": { "share-box": "/components/shareBox/index" } } [代码] 以下示例为获取用户头像昵称后再生成图。 [代码]<button class="intro" bindtap="getUserInfo">点我生成分享图</button> <share-box isCanDraw="{{isCanDraw}}" userInfo="{{userInfo}}" bind:initData="handleClose" /> [代码] 调用的地方: [代码]const app = getApp() Page({ data: { isCanDraw: false }, // 组件内部关掉或者绘制完成需重置状态 handleClose() { this.setData({ isCanDraw: !this.data.isCanDraw }) }, getUserInfo(e) { wx.getUserProfile({ desc: "获取您的头像昵称信息", success: res => { const { userInfo = {} } = res this.setData({ userInfo, isCanDraw: true // 开始绘制海报图 }) }, fail: err => { console.log(err) } }) } }) [代码] 最后绘制分享图的自定义组件就完成啦~效果图如下: [图片] tips: 文字居中实现可以看下代码片段 文字换行实现(maxLines)只需要设置宽度,maxLines如果设置为1,那么超出一行将会展示为省略号 代码片段:https://developers.weixin.qq.com/s/J38pKsmK7Qw5 附上painter可视化编辑代码工具:点我直达,因为涉及网络图片,代码片段设置不了downloadFile合法域名,建议真机开启调试模式,开发者工具 详情里开启不校验合法域名进行代码片段的运行查看。 最后看下面大家评论问的较多的问题:downLoadFile合法域名在小程序后台 开发>开发设置里配置,域名为你图片的域名前缀 比如我文章里的图https://qiniu-image.qtshe.com/20190605index.jpg。配置域名时填写https://qiniu-image.qtshe.com即可。如果你图片cdn地址为https://aaa.com/xxx.png, 那你就配置https://aaa.com即可。
2022-01-20 - 实战:图片处理服务之快速压缩模版
前言 在昨天发布的《实战:如何降低云开发服务器成本? 》文章,评论区有提到需要「关于cloudbase的扩展能力-图像处理-快速缩略模板的用法」今天我就来和大家分享一下具体用法和效果。 安装 地址:https://console.cloud.tencent.com/tcb/env/overview 选中「扩展能力」菜单下面的「扩展应用」 [图片] 选择「图片处理」服务进行「安装」 [图片] 安装过程一直「下一步」就行没有需要配置的地方,需要等待几分钟 [图片] 查看文档 安装完成之后我们就可以使用了 首先看下文档:https://cloud.tencent.com/document/product/876/42103 [图片] 找到我们要用的「快速压缩模版」。 地址:https://cloud.tencent.com/document/product/460/6929 使用方法,直接在图片后面来评价参数即可。 [图片] 实战使用 通常使用在列表场景中,本来就不要高清图,所以可以进行压缩也不会影响用户体验。 [图片] 我们找一个图片链接放在浏览器上来看 [图片] 然后使用下快速压缩模版拼接参数 ?imageView2/1/w/100/h/100 [图片] 把两张图片下载下来对比一下大小 [图片] 压缩后小了54倍 总结 这样以来不仅让用户能够更快的加载出图片,并且还能降低服务器资源成本。
2020-12-02 - 【云开发·云函数】分层(layer)实现代码复用,云函数之间共用底层文件
导读 当我们有很多云函数时,其中可能很多 [代码]中间件[代码] 、 [代码]工具[代码] 是共用的。 把这些 [代码]中间件[代码] 、 [代码]工具[代码] 复制到每个云函数下,是一件很麻烦的事情,而且如果发生了更新,还要重新复制。最重要的是,这种方式,不符合科学的设计模式。 云函数里的 层(layer) 就可以很好的解决我们这个问题。它是把我们共通需要的 [代码]中间件[代码] 、 [代码]工具[代码] 等文件放在一个公共的路径下,使用过程中只需要引用进来即可。 注意: 层暂时还没出现在小程序官方文档,但是可以在 [代码]腾讯云 - 云函数[代码]找到文档。 实操 这里我们使用 [代码]Nodejs8.9[代码] 来做示范 1. 先确认小程序已经开通了云开发 2. 登录腾讯云 打开登录界面 选择【其他登录方式 - 微信公众号 - 扫码选择对应的小程序(不是公众号)】. [图片] 注意:小程序云开发和腾讯云的账户体系其实是有关联的,可以把腾讯云面板看作更高级的云开发面板 3. 编写层代码 编写以下代码,并保存为 [代码]demo.js[代码] [代码]exports.main = async (event) => { return "hello,layer." } [代码] 把 [代码]demo.js[代码] 打包成 [代码]demo.zip压缩包[代码] 4. 创建分层 打开云函数面板. 点击左侧菜单的『层』 地域选择『上海』 点击『新建按钮』 层名称:demo 提交方法:本地上传zip包 层代码:选择上一步打包的 [代码]demo.zip[代码] 压缩包 运行环境:Nodejs8.9 [图片] 点击提交 注意: 有些压缩软件压缩后,会多一层文件夹,需要注意下,压缩包内打开应该直接就是文件 [代码]demo.js[代码] 没有多一层文件夹。 5. 小程序·云开发面板创建云函数 创建云函数 [图片] 修改 [代码]云函数 demo 下的 index.js[代码] 代码并上传 [代码]var hello = require("demo.js") exports.main = (event, context, callback) => { return hello.main() }; [代码] 6. 云函数绑定层 回到 腾讯云 云函数面板,选择左侧菜单 [代码]函数服务[代码] 注意左上角的 命名空间 选择小程序云开发的命名空间,地域:上海 选择刚创建的 [代码]demo[代码] 云函数 点击 [代码]层管理[代码] 绑定刚创建的 [代码]demo[代码] 层 7. 调试 还是 [代码]demo[代码] 的云函数里,点击 [代码]函数代码[代码] 项 点击测试,返回结果 [代码]"hello,layer."[代码] 既成功 [图片] Q&A 问:为什么云函数里可以直接[代码]require("demo.js")[代码] 引入 [代码]demo.js[代码] ? 答:绑定层后,会根据顺序把层文件放在系统目录 [代码]/opt[代码] 下,然后该目录在 Node.js 环境下又在环境变量里。当引入 [代码]demo.js[代码] 在当前目录找不到时,会尝试在环境变量内查找。所以云函数可以找到该文件。 再问:如果层的文件名和云函数列表的文件名冲突,岂不是会引入错误? 答:是的,这时候可以使用绝对路径确保引入的是层文件 [代码]require("/opt/demo.js")[代码] 再再问:如果云函数绑定了多个层,每个层里都有 [代码]demo.js[代码] 会怎么样? 答:会根据层的顺序,逐个覆盖文件,最终 [代码]/opt/demo.js[代码] 文件是最后一层的文件。 再再再问:环境变量除了这个路径,还有哪些路径? 答:这里有一份各语言的环境变量路径,可以参考下,具体查看文档 关环境变量 路径 THONPATH [代码]/var/user:/opt[代码] ASSPATH [代码]/var/runtime/java8:/var/runtime/java8/lib/*:/opt[代码] DE_PATH [代码]/var/user:/var/user/node_modules:/var/lang/node6/lib/node_modules:/opt:/opt/node_modules[代码] 问:云函数只支持 [代码]Nodejs8.9[代码] 版本吗? 答:在小程序云开发面板里,确实只能创建。但是可以通过服务端 SDK 创建以下语言支持。 Python 3.6 Python 2.7 Nodejs 10.15 Nodejs 8.9 Nodejs 6.10 Php 7.2 Php 5.6 Java 8 Golang 1 持续更新… 参考资料 腾讯云·云函数文档
2020-05-26 - 【笔记】云开发聚合实现分页,涉及跨表查询、逻辑计算、判断权限、数据格式化、限制输出
背景: 之前不会用聚合,因此把数据库结构分为了用户表、帖子表、喜欢表。小程序端请求一次列表,要根据帖子列表,循环查询用户表,并且还要做一系列的逻辑运算处理,计算当前帖子的权限、是否喜欢过、喜欢人数、是否有这个帖子管理权限等信息。 这样做有很多弊端: 处理速度慢,资源耗费严重,循环查询肯定慢且耗费资源,一个列表需要21次查询。需要写大量逻辑处理代码,如计算管理权限,喜欢数量、当前用户是否喜欢,格式处理等等。于是使用聚合进行了优化: 跨表查询数据格式化逻辑计算,权限判断、是否喜欢等数据统计,喜欢总人数权限判断,是否为管理员限制输出效果: 之前:上百行代码,多次查询,需要单独判断函数,处理时间在3000ms以上之后:几行代码,一次查询,直接查询时算出结果,处理时间在300ms以内 数据库结构 [图片] 代码实现: const { OPENID } = cloud.getWXContext(context) //构建查询条件 let query = null switch (Number(event.listType)) { case 0: query = db.collection('post').aggregate() .match({ //0我的 '_openid': OPENID }) .sort({ createTime: -1 }) .skip(20 * (event.pageNum - 1)) .limit(20) break; case 1: //1 随机 query = db.collection('post').aggregate() .match({ public: true, // feeling: _.gte(50) }) .sample({ size: 20 }) break; case 2: query = db.collection('post').aggregate() .match({ //2喜欢 likes: _.all([OPENID]) }) .sort({ createTime: -1 }) .skip(20 * (event.pageNum - 1)) .limit(20) break; case 4: query = db.collection('post').aggregate() .match({ //4指定 _id: event.id }) .sort({ createTime: -1 }) .skip(20 * (event.pageNum - 1)) .limit(20) break; } //使用聚合处理后续数据 let listData = await query .lookup({ from: "user", localField: "_openid", foreignField: "_id", as: "postList" })//联表查询用户表 .replaceRoot({ newRoot: $.mergeObjects([$.arrayElemAt(['$postList', 0]), '$$ROOT']) })//将用户表输出到根节点 .addFields({ day: $.dayOfMonth('$createTime'), month: $.month('$createTime'), year: $.year('$createTime'), isLike: $.in([OPENID, '$likes']), //是否喜欢 isLiked: $.in([OPENID, '$liked']), //是否喜欢过 isAdmin: $.eq([OPENID, 'oy0T-4yk7lCRFGDefpFC4Yvx_ppU']),//是否管理员 isAuthor: $.eq(['$_openid', OPENID]),//是否为作者 like: $.size('$likes'), //喜欢该帖子数 face: $.switch({ branches: [ { case: $.gte(['$feeling', 90]), then: 9 }, { case: $.gte(['$feeling', 80]), then: 8 }, { case: $.gte(['$feeling', 70]), then: 7 }, { case: $.gte(['$feeling', 60]), then: 6 }, { case: $.gte(['$feeling', 50]), then: 5 }, { case: $.gte(['$feeling', 40]), then: 4 }, { case: $.gte(['$feeling', 30]), then: 3 }, { case: $.gte(['$feeling', 20]), then: 2 }, { case: $.gte(['$feeling', 10]), then: 1 } ], default: 0 }) //根据心情值判断对应表情 }) .project({ postList: 0, userInfo: 0, liked: 0, likes: 0, city: 0, province: 0, country: 0, language: 0, nlp: 0, saveType: 0, }) //清楚掉不需要的数据 .end() return listData
2020-05-26 - 如何只使用一个云函数搞定一个庞大而复杂的系统
吐槽 翻遍社区的文章,关于云开发的干货,少之又少,大部分都还是官方文档的搬来搬去,没啥营养,是时候放出一点技术"干货"了(有经验的开发者都能想到的方案)! 正题 小程序云开发的云函数的最大限制是 [代码]50[代码] 个,假设每个接口都使用 [代码]1[代码] 个云函数的话,有 [代码]10[代码] 张表,每张表都有 [代码]增删改查[代码] 四个接口,那么就会有 [代码]40[代码] 个接口,再加上一些其他接口,差不多刚刚好够用,那如果有 [代码]20[代码] 张表,甚至更多的表、更多的接口呢?对于中小型的小程序来说足够使用,那如果一个非常庞大而复杂的系统该怎么办呢? 而且每一个云函数的运行环境是独立的,想要共享一些数据也不是特别方便,那么有没有什么办法突破这样的限制呢? 其实解决方案很简单,只需要一点点的 [代码]OOP[代码] 思想和利用 [代码]JavaScript[代码] 的特性,一个云函数就可以搞定所有的接口。 具体的实现请往下看。 思路 云函数的运行环境是 [代码]Nodejs[代码] , 那么使用的语言就是 [代码]JavaScript[代码] ,可以充分的利用 [代码]JavaScript[代码] 的特性。 [代码]JavaScript[代码] 中的 [代码]属性访问表达式[代码] 有两种语法 [代码]expression . identifier expression [ expression ] [代码] 第一种写法是一个表达式后跟随一个句点 [代码].[代码] 和一个标识符。表达式指定对象,标识符则指定需要访问的属性的名称。 第二种写法是使用方括号 [代码][][代码],方括号内是另一个表达式(这种方法适用于对象和数组)。第二个表达式指定要访问的属性的名称或者代表要访问数组元素的索引。 不管使用哪种形式的属性访问表达式,在 [代码].[代码] 和 [代码][][代码] 之前的表达式总是会首先计算。 虽然 [代码].[代码] 的写法更加简单,但这种方式只适用于要访问的属性名称的合法标识符,并需要准确知道访问的属性的名字,如果属性的名称是一个保留字或者包含空格和标点符号,或者是一个数字(对于数组来说),则必须使用方括号 [代码][][代码] 的写法。当属性名是通过运算得出的值而不是固定值的时候,这时也必须使用方括号 [代码][][代码] 写法。 感谢社区大神 @卢霄霄 提供参考资料,详见 [代码]JavaScript权威指南[代码] (犀牛书)4.4章节。 可以使用 [代码][][代码] 的形式来完成动态的属性访问。具体实现请往下看。 实现 上面说了太多废话了,下面直接开干吧。 新建云函数 在云开发目录中新建一个云函数,我这里命名为 [代码]cloud[代码]。 打开 [代码]index.js[代码] 文件你会看到下面这段代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') // 初始化 cloud cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID, } } [代码] 这个云函数仅作为入口使用,上面提到了云函数的运行环境是 [代码]Nodejs[代码] 那么 [代码]Nodejs[代码] 的特性也是可以使用的,这里主要用到的是全局对象 [代码]global[代码],详见文档 在文件中,写入一些必要的全局变量,主要还是云数据库方面的,方便后面使用。 在初始化后面插入代码 [代码]global.cloud = cloud global.db = cloud.database() global._ = db.command global.$ = _.aggregate [代码] 这样就可以在同一个云函数环境中直接访问这些全局变量。 创建公共类 然后新建一个文件夹,我这里命名为 [代码]controllers[代码] ,这个文件夹用于存放所有的接口。 在 [代码]controllers[代码] 中新建一个 [代码]base-controller.js[代码] 文件,创建一个叫做 [代码]BaseController[代码] 的类,用于提供一些公用的方法。 内容如下: [代码]class BaseController { /** * 调用成功 */ success (data) { return { code: 0, data } } /** * 调用失败 */ fail (erroCode = 0, msg = '') { return { erroCode, msg, code: -1 } } } module.exports = BaseController [代码] 看到这里大家可能有点没看懂在做什么,那么请继续往下看。 创建接口 假设创建一些要操作用户相关的的接口,可以在 [代码]controllers[代码] 文件夹中新建一个 [代码]user-controller.js[代码] 的文件,创建一个名为 [代码]UserController[代码] 的类,并继承上面的 [代码]BaseController[代码] 类,内容如下: [代码]const BaseController = require('./base-controller.js') class UserController extends BaseController { // ... } module.exports = UserController [代码] 可以在这个类中编写所有关于 [代码]user[代码] 的接口方法。 编写接口 假设要分页查询用户信息,可以在 [代码]UserController[代码] 类中创建一个 [代码]list[代码] 方法。 代码如下: [代码]async list (data) { const { pageIndex, pageSize } = data let result = await db.collection('users') .skip((pageIndex - 1) * pageSize) .limit(pageSize) .get() .then(result => this.success(result.data)) .catch(() => this.fail([])) return result } [代码] 由于上面已经定义了全局变量 [代码]db[代码] 所以在 [代码]UserController[代码] 中无需引入 [代码]wx-server-sdk[代码] 引入接口类 写到这里接口已经完成了,还需要再引入这些接口类才可以进行访问。在 [代码]index.js[代码] 中引入 [代码]user-controller.js[代码] [代码]const User = require('./controllers/user-controller.js') [代码] 然后创建一个 [代码]api[代码] 变量,[代码]new[代码] 一个 [代码]User[代码] 实例 [代码]const api = { user: new User() } [代码] 在 [代码]main[代码] 方法中调用 [代码]UserController[代码] 中的方法。 [代码]exports.main = async (event, context) => { const { data } = event let result = await api['user']['list'](data) return result } [代码] 写到这里基本已经完成了接口的调用,但想要一个云函数动态调用所有接口还需要做一些改动。 动态调用接口 刚开始的时候介绍了 [代码]属性访问表达式[代码],限制稍微改动一下 [代码]main[代码] 方法 [代码]exports.main = async (event, context) => { const { controller, action, data } = event const result = await api[controller][action](data) return result } [代码] 在小程序调用云函数时,需要传入 [代码]controller[代码]、[代码]action[代码] 和 [代码]data[代码] 参数即可 [代码]const result = await wx.cloud.callFunction({ name: 'cloud', data: { controller, action, data } }) [代码] 完整 [代码]index.js[代码] 文件的代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') const User = require('./controllers/user-controller.js') const api = { user: new User() } // 初始化 cloud cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) global.cloud = cloud global.db = cloud.database() global._ = db.command global.$ = _.aggregate // 云函数入口 exports.main = async (event, context) => { exports.main = async (event, context) => { const { controller, action, data } = event const result = await api[controller][action](data) return result } } [代码] 其他实现 云开发官方团队打造的轮子 tcb-router
2020-05-26 - swiper组件化的时候,找不到wx-swiper-dot,无法改变轮播小点点
将swiper进行组件化封装后,找不到wx-swiper-dot,无法改变轮播小点点的样式,也无法把轮播小点点进行隐藏,但是如果将swiper在页面使用的时候,是可以在wxss中使用.wx-swiper-dot、.wx-swiper-dot::before以及.wx-swiper-dot-active::before等class对轮播的小点点进行改变样式,请求各位大神以及官方人员解答一下,谢谢了
2019-01-22 - 自定义组件中使用swiper组件时的bug
在自定义组件中,如果styleIsolation 值 为 shared ,那么组件内可以通过 .wx-swiper-dot 来修改小圆点的样式; 如果styleIsolation 值 为 apply-shared, 那上述修改方式就无效;
2020-04-10 - 【必收】精心整理!小程序开发资源汇总(附带源码)
很多小伙伴想在春节放假期间学小程序,但是小程序学习的资源和教程可能不太好找。所以小助手精心整理了一期,全是干货!认真学,开启美妙的小程序开发之旅,做一个属于自己的微信小程序。有需要的小伙伴收藏好这期文章哦~ 本文收集整理了微信小程序开发资源,包括官方文档,云开发训练营文档,视频教程以及实战源码推荐,会不间断更新。。 欢迎添加云开发小助手CloudBase微信:Tcloudedu1 ,一起加入技术交流群~ 小程序云开发官方公众号 [图片] 目录 官方文档 云开发训练营 视频教程 小程序·云开发Demo 技术交流群 官方文档 小程序开发者工具 小程序设计指南 小程序开发教程 小程序框架 小程序组件 小程序API 小程序开发者工具 小程序云开发文档 云开发训练营 小程序开发入门 小程序与JavaScript 云开发快速入门 [图片] 视频教程 腾讯云云开发B站:https://space.bilibili.com/447496276 [图片] 小程序·云开发Demo 技术博客小程序 包括文章的发布及浏览、评论、点赞、浏览历史、分类、排行榜、分享、生成海报图等。 网盘小程序 兼具文件存储与分享功能的专属网盘小程序。 教务助手小程序 用完即走,查个成绩和课表,无需下载app或去翻看公众号内的历史内容。 功能日历小程序 既能查看日历又能备注事项,看云开发如何支持功能性日历小程序的快速开发。 客户业务需求收集小程序 用云开发快速制作客户业务需求收集小程序,教你用云开发实现小程序版“朋友圈”的发布与展示。 小程序朋友圈 把朋友圈装进小程序需要几步?借助云开发实现小程序朋友圈的发布与展示。 南苑导览 一款由学生独立开发的以地图为载体,提供中山大学南方学院具体地点的位置信息、导航、校园历史及文化介绍的小程序。 互动打卡小程序 用云开发轻松构建精美互动打卡小程序,交互式双人打卡,快乐加倍。 个性头像小程序 别再@官方啦!云开发教你轻松制作个性头像小程序,趣味挂件、个性icon。 二手书商城小程序 云开发轻松制作二手书交易商城小程序,让智慧延续,让温暖传递。 后台数据批量导出 小程序开发过程中如何将云数据库中的数据批量导出至excel。 发送邮件 初学者福音,手把手教你用小程序云开发实现邮件发送功能。 高考查分小程序 实现高考分数轻松查,小程序源码。 mini论坛 仅需两天轻松搭建mini论坛小程序。 运动圈小程序 打造运动圈小程序(以乒乓球为例),实现球友间高效互动。 心情日记小程序 我能想到最浪漫的事,可能就是“你的心事我全知晓”。 最美恋爱小程序 小程序前端用的是taro框架写的,后台用的云开发。教你用云开发为心爱的人做个小程。 校园约拍小程序 校园场景下,小程序·云开发大显身手,校园约拍小程序源码。 体重记录小程序 只想记录每日体重还得下个APP,不用那么麻烦!用云开发做个专属体重记录小程序,看看你每天瘦了多少。 口袋工具 口袋工具之历史上的今天。一个基于云开发的小程序,看看历史上的今天都发生了啥。 迷你微博 独立做个精简版微博出来让你刷刷刷吗?而且,它还兼具搜索、点赞、主页的功能 多媒体小程序 使用小程序·云开发构建多媒体小程序。 技术交流群 交流技术为主,开发学习工作中遇到问题可以在群内交流,欢迎有需要的朋友加群。 添加小助手微信(Tcloudedu1),回复“技术群”,即可加入云开发技术群。 最后 如果你有关于使用腾讯云云开发相关的技术故事/技术实战经验想要跟大家分享,欢迎留言联系我们~ 关注腾讯云云开发,后台回复【源码】,获取更多微信小程序云开发实战源码。 [图片] [图片] [图片] 关注「腾讯云云开发」,后台回复【 源码 】,获取更多微信小程序云开发实战源码。 持续更新中… [图片]
2020-01-16 - 云函数获取openid
代码如下: app.js: //如果担心openid的安全,就用这个函数 getCloudOpenid: async function () { return this.openid = this.openid || (await wx.cloud.callFunction({name: 'login'})).result.OPENID }, //最佳方案。 getOpenid: async function () { (this.openid = this.openid || wx.getStorageSync('openid')) || wx.setStorageSync('openid', await this.getCloudOpenid()) return this.openid }, 任何page: onLoad: async function () { console.log(this.openid = await getApp().getOpenid()) }, //在本page的其他函数里获得openid。 yourFunc: function(){ console.log(this.openid) } 云函数login: const cloud = require('wx-server-sdk') cloud.init() exports.main=async()=>{return cloud.getWXContext()}
2020-10-18 - (10)群聊能力
我们在后台收到很多朋友的反馈,希望更好的运用小程序群聊的能力。于是我们想写写群聊的故事。 微信群是小程序在微信这个社交工具下传播的重要途径,我们经常能通过群聊看见小程序的身影。我们希望开发者在实现小程序逻辑的时候,能理解每一个群聊,可实现小程序与各个群聊紧密相关的功能。 基于此,我们开放了群聊 ID(openGID)的功能,供开发者区分标识每个群聊。对于每个群聊而言,小程序所获取到的 openGID 是不变的。但对于同一个群,不同的小程序内获得的 openGID 是不一样的。这一特性类似于标识用户身份的 openID。 拥有了群聊 ID,开发者可以把用户的操作按照群聊 ID 来聚合、沉淀信息,实现群协作功能。此外,通过 openID+openGID 的方式,还可以实现群排行的功能。 例如“群影”小程序以群聊ID聚合用户上传的图片,实现群相册的功能。 [图片] (“群影”小程序) 01 如何获取群聊ID 开发者获取 openGID 要依托于用户转发到群聊的小程序卡片,具体步骤如下: 1 设置带 shareTicket 的分享 在小程序内,开发者调用接口wx.updateShareMenu 带参数withShareTicket:true ,设置当前页面分享到群聊时能获取 openGID。而shareTicket本身就是获取 openGID 的凭证。 [图片] 而 iOS/Android App 分享场景当中,微信SDK也支持把所分享的消息设置成带 shareTicket。 值得注意的是,带 shareTicket 的分享卡片会被固定在某个群聊的,也就是说分享卡片会变成不能被长按转发。 2 由启动参数获取 shareTicket 当用户从某个带 shareTicket 的卡片进入小程序时,开发者可以在App.onLaunch 或者App.onShow 获取 shareTicket,而在小游戏上开发者可以通过监听 wx.onShow 或者同步调用wx.getLaunchOptionsSync 获取shareTicket。 shareTicket 实际上是小程序启动时临时生成的变量,在小程序生命周期内仅作为调用接口的凭证。生命周期结束后 shareTicket 就没有意义了。 3 通过 shareTicket 获取 openGID 开发者调 wx.getShareInfo 接口以 shareTicket 换取 openGID 的加密数据包,这是为了保证开发者服务器收到的 openGID 是可信的,开发者需要把加密数据交由后台解密,拿到真实的 openGID。数据加密机制更多请参看[数据加密相关文档]。 注意事项 ▷▷ 由于2018年7月5日起,新提交发布的小程序版本将无法通过用户分享获得群ID,即开发者通过wx.onShareAppMessage获取群 ID 的方式将不再支持,后续仅支持通过启动参数获取群 ID。请开发者及时调整。 02 群聊名称组件 除了群聊 ID 以外,开发者还能使用群聊对应的名称。出于保护用户隐私的考虑,我们不会把真实的群聊名称暴露给开发者,而是通过 open-data 组件让开发者在小程序前端展示某个 openGID 对应的群名称。 [图片] 其中 openGID 就是小程序获取到的群聊 ID。 open-data 组件只展示那些用户所在群聊ID对应的名称。如果设置了非微信提供的群聊 ID,将无法展现群聊名称。 03 群聊功能 1 分享设置 wx.updateShareMenu : [查看文档] 2 小程序启动参数 App.onLaunch / App.onShow : [查看文档] 3 小游戏启动参数 wx.onShow : [查看文档] getLaunchOptionsSync : [查看文档] 4 获取 openGID wx.getShareInfo : [查看文档] 5 群名称组件 open-data>/open-data>: [查看文档]
2018-08-17 - 只有三行代码的神奇云函数的功能之五:获取群id
这是一个神奇的网站,哦不,神奇的云函数,它只有三行代码:(真的只有三行哦) 云函数:login index.js: const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event) => { return { ...event, ...cloud.getWXContext() } } 神奇功能之五:获取群id: 将小程序分享到某群里,可获得该群的群id, page.js: onShareAppMessage: function () { wx.showShareMenu({ withShareTicket: true }) let path = '/pages/xxx/xxx' let title = 'title' let imageUrl = `http://xxx.com/100.jpg` return {title,imageUrl,path} }, app.js: onLaunch: function (options) { options.shareTicket && this.getOpenGId(options.shareTicket) }, getOpenGId: function (shareTicket) { wx.getShareInfo({ shareTicket, success: function (res) { wx.cloud.callFunction({ name: 'login', data: { weRunData: wx.cloud.CloudID(res.cloudID) }, success: res=> { console.log(res.result.weRunData.data.openGId); } }) } }) }, 需要说明一下的是:从群里点击分享卡片进入小程序,必须是重启的小程序,不能是已经打开的小程序,否则得不到shareTicket。 其他功能: 神奇功能之一:获取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 神奇功能之四:获取电话号码: https://developers.weixin.qq.com/community/develop/article/doc/0006a8ec7ac860c94bf90a34f5d813 [图片]
2020-10-20 - 云开发,获取群ID——调试出来真的很简单。
1 app.js中 onLaunch: function (options) { if (!wx.cloud) console.error(‘请使用 2.2.3 或以上的基础库以使用云能力’) else wx.cloud.init({ traceUser: true, }) [代码]if (options.shareTicket) wx.getShareInfo({ shareTicket: options.shareTicket, success: function (res) { console.log('getShareTiket---shareTicket-->res', res) //获取cloudID let cID=res.cloudID //调用云函数mytest wx.cloud.callFunction({ name: 'mytest', // 这个 CloudID 值到云函数端会被替换 data: { weRunData: wx.cloud.CloudID(cID) }, success: function (res) { console.log('wx cloud mytest fun res', res); } }) } }) [代码] }, 2 云函数mytest const cloud = require(‘wx-server-sdk’) cloud.init() exports.main = (event, context) => { return { event } } /console.log(‘wx cloud mytest fun res’, res);查看打印出来的res, 真是一个惊喜。 不用npm,不用加密解密,不用传数据到自己开发服务器上。哎,一个群ID花了我好多时间啊,最后到底是迎来柳暗花明了。/
2019-09-24