- 小程序云函数中的环境变量怎么获取
[图片] 希望哪位大神路过时,详细解答下
2019-03-09 - 严重bug,云开发云数据库Command.push()的sort参数,多次执行数据结果不一致
Command.push(values: Object): Commandhttps://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/command/Command.push.html 在使用sort参数排序时,意外发现,同样的sort规则,多次执行的数据结果不一致! 一下描述是在开发者工具(stable v1.02.1911180)的云控制平台调试发现的 一、代码: db.collection('goods') .doc('c48bd6c8-60f5-4fba-a7de-ddc3407370dd') .update({ data: { push: _.push({ each: [], //position: 2, //slice:-2, sort:{ a:-1, b:-1 }, }) } }) 二、数据集goods(本回只针对 "_id": "c48bd6c8-60f5-4fba-a7de-ddc3407370dd" 进行了测试发现的问题): [ { "_id": "c48bd6c8-60f5-4fba-a7de-ddc3407370dd", "cost": -12, "discount": 0.95, "price": 100, "push": [ { "a": 30, "b": 555 }, { "a": 1300, "b": 15 }, { "a": 1112, "b": 11 }, { "a": 30, "b": 5 }, { "a": 1200, "b": 1 }, 8, 9, 1 ], "style": { "size": "small" } }, { "_id": "f37a8787-1efa-4cc3-a74e-f4fa50c43c96", "cost": -50, "discount": 0.8, "price": 1600 } ] 三、过程如下: (1)初始设置: sort规则{ a:-1,b:-1 },多次执行相同代码,完成初始化测试准备[图片] 数据集goods,在sort规则{ a:-1,b:-1 }情况下,多次执行后的截图如下[图片] (2)开始修改规则进行测试 第1次修改sort规则: { a:-1,b:-1 } 变更为 { a:-1,b:1 },并第1次执行[图片](截图的标注写错了,变化点是 b:-1 --> b:1 ) 观察数据变化情况: 数据按a:-1的规则排序了,并且在a字段排序的基础上,对b字段也正确排序了 【注意:这只是第1次执行,下面有对比】[图片] 接上面的sort规则(无变化): { a:-1,b:1 },并第2次执行[图片] 同样的规则,第2次执行后的数据集与第1次没有变化【猜测:{a:-1,b:1} 组合规则不受执行次数影响?】[图片] 【注意要修改规则了】修改sort规则: { a:-1,b:1 } 变更为 { a:-1,b:-1 },并第1次执行[图片] 观察sort规则修改为{a:-1,b:-1},第1次执行后的数据集变化情况【这次符合预期,优先按a:-1排序,其次按b:-1排序】[图片] 观察sort规则{a:-1,b:-1}(与第1次无变化),第2次执行[图片] 观察sort规则{a:-1,b:-1}(与第1次无变化),第2次执行后的数据集变化情况【这回按完全b:-1规则排序的,(测试数据有限)a:-1不知道有没有起作用】[图片] 观察sort规则{a:-1,b:-1}(与第1次无变化),第3次执行[图片] 观察sort规则{a:-1,b:-1}(与第1次无变化),第3次执行后的数据集变化情况【排序与第2次一样】[图片]四、测试小结 我崩溃了,迷茫了,能不用就先别用这个API参数了,慎用慎用慎用!!! 大胆自信一下,修改sort规则后的第一次排序是正确的,但千万别#¥%……&*(*&脑子短路了不知道什么时候又执行了1次。。。 恳请官方重视以上反馈!
2020-02-28 - [填坑手册]小程序新版订阅消息+云开发实战与跳坑
[图片] 老版本的订阅消息在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 - 云开发云数据库 Command.nor()示例1程序与描述是错误的
Command.nor(expressions: any[]): Commandhttps://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/command/Command.nor.html 官方文档截图如下: [图片] 但我在云控制台的调试结果如下: 1、使用nor()时,必须前置使用_.exists(),否则出现异常错误(正式环境里是否产生错误,未知) [图片] 2、如果_.exists()没有参数时,会显示所有查找到的数据【与文档解释正好相反】 [图片] 4、如果_.exists()参数设置为“0”或“false”时,显示“字段存在”的查询结果 [图片] 5、如果_.exists()参数设置为“1”或“true”时,显示“字段不存在”的查询结果 [图片] ===================== 补充: 测试用到的云数据库“items”的数据如下: [ { "_id": "1", "category": "a", "name": "apple", "price": 10, "size": [ "S", "L", "", "M", null ] }, { "_id": "2", "category": "a", "name": "pear", "price": 50 }, { "_id": "3", "category": "b", "name": "apple", "price": 20, "size": [ "M", "S", "L" ] }, { "_id": "4", "category": "b", "name": "banana", "size": [] }, { "_id": "5", "category": "a", "name": "pear", "price": 200, "size": null } ]
2020-02-27 - wxs使用setStyle方法transform,按住向下滑动时卡顿,其他方向没有问题
我现在wxs使用setStyle方法,transform 按住向下滑动时卡顿 其他方向都是好的 哪怕斜着向下都是没有问题的,唯独直线下滑卡顿 我是小米9,调试库2.9.4,开发工具是稳定版v1.02.1911180 是bug?还是在特定手机存在的问题? function touchmove(e, ownerInstance) { var state = ownerInstance.getState() var ins = ownerInstance.selectComponent('.dd' ins.setStyle({ 'transform': 'translateX(' + (e.changedTouches[0].clientX - state.cc.X) + 'px) translateY(' + (e.changedTouches[0].clientY - state.cc.Y) + 'px)' 'display': 'block' }) }
2019-12-31 - touchmove 写拖动事件卡顿不流畅
页面上有个fixed悬浮按钮,希望可以在页面上任意拖动位置,但是现在的问题是写出来的拖动效果特别不流畅(反应延迟),请问有什么好的办法解决? <view class='helpbox' style="bottom:{{bottom}}px;right:{{right}}px;" catchtouchmove='setTouchMove'> <image class='helpimg' src='/images/help.png'></image> </view> js代码: setTouchMove:function(e){ var that = this; var width = 35 * rpx; var screenWidth = wx.getSystemInfoSync().windowWidth; var screenHeight = wx.getSystemInfoSync().windowHeight; var clientX = e.touches[0].clientX; var clientY = e.touches[0].clientY; //此处clientY与clientX为拖动悬浮窗超过设定的大小会返回默认显示位置 if (clientX < (screenWidth - width) && clientY < (screenHeight - width) && clientX > width && clientY > width) { that.setData({ right: screenWidth - clientX - width, bottom: screenHeight - clientY - width }) } else { return; // that.setData({ // right: 0, //默认显示位置 left距离 // bottom: 0 //默认显示位置 top距离 // }) } }
2018-12-11 - 请教wxs里的getState,和triggerEvent这两个方法是怎么使用?
万能的大神社区帮忙解答下怎么使用,研究半天这两个方法不会使用
2019-08-10 - 自定义标题栏
使用效果 [图片][图片][图片][图片] 使用方法 属性介绍 属性名 类型 默认值 是否必须 说明 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 - 怎么调试插件功能页
测试版 Beta Build 更新日志里程碑版本,包含大的特性;通过内部测试,稳定性尚可 1.02.1903211Windows x64 、 Windows ia32 、 macOS 2019.03.21[代码]A[代码] 新增 云函数本地调试 文档 [代码]A[代码] 新增 企业微信模拟器插件 文档 [代码]A[代码] 新增 CLI/HTTP 调用关闭项目窗口、关闭开发者工具 详情 [代码]A[代码] 新增 小程序支持 [代码]pageOrientation: "landscape"[代码] [代码]A[代码] 新增 分包配置中新增的页面配置会自动生成对应的页面结构 反馈详情 [代码]A[代码] 新增 真机调试支持调试 functionalPage 文档: 用户信息功能页https://developers.weixin.qq.com/miniprogram/dev/framework/plugin/functional-pages/user-info.html 这是官方片段 https://developers.weixin.qq.com/s/Uof4Iomt731Z 我也按提示修改appid了 在微信开发者工具中查看示例: 由于插件需要 appid 才能工作,请填入一个 appid; 由于当前代码片段的限制,打开该示例后请 手动将 appid 填写到 [代码]miniprogram/app.json[代码] 中(如下图)使示例正常运行。 为什么点击没有反应呢? 看不懂 真机开发测试的常规步骤目前,功能页的跳转目前不支持在开发者工具中调试,请在真机上测试。初次进行真机开发测试时,通常步骤如下: 在开发者工具上打开插件所有者小程序项目,并点击“预览”; 用测试用的真机扫一下预览二维码,此时会进入插件所有者小程序,进入后就可以直接退出这个小程序; 在开发者工具上打开插件项目,将插件中 [代码]<functional-page-navigator>[代码] 中的 [代码]version[代码] 属性设置为 [代码]develop[代码]; 点击预览可以生成插件预览二维码,用测试用的真机扫码即可预览功能页;如果更改了插件代码,重新生成并扫描插件的预览二维码即可; 如果过了一段时间之后,跳转功能页时出现“开发版已过期”这样的提示,从第1步开始重试一次。 怎么试都不行啊,急等
2019-03-25 - 路由的封装
小程序提供了路由功能来实现页面跳转,但是在使用的过程中我们还是发现有些不方便的地方,通过封装,我们可以实现诸如路由管理、简化api等功能。 页面的跳转存在哪些问题呢? 与接口的调用一样面临url的管理问题; 传递参数的方式不太友好,只能拼装url; 参数类型单一,只支持string。 alias 第一个问题很好解决,我们做一个集中管理,比如新建一个[代码]router/routes.js[代码]文件来实现alias: [代码]// routes.js module.exports = { // 主页 home: '/pages/index/index', // 个人中心 uc: '/pages/user_center/index', }; [代码] 然后使用的时候变成这样: [代码]const routes = require('../../router/routes.js'); Page({ onReady() { wx.navigateTo({ url: routes.uc, }); }, }); [代码] query 第二个问题,我们先来看个例子,假如我们跳转[代码]pages/user_center/index[代码]页面的同时还要传[代码]userId[代码]过去,正常情况下是这么来操作的: [代码]const routes = require('../../router/routes.js'); Page({ onReady() { const userId = '123456'; wx.navigateTo({ url: `${routes.uc}?userId=${userId}`, }); }, }); [代码] 这样确实不好看,我能不能把参数部分单独拿出来,不用拼接到url上呢? 可以,我们试着实现一个[代码]navigateTo[代码]函数: [代码]const routes = require('../../router/routes.js'); function navigateTo({ url, query }) { const queryStr = Object.keys(query).map(k => `${k}=${query[k]}`).join('&'); wx.navigateTo({ url: `${url}?${queryStr}`, }); } Page({ onReady() { const userId = '123456'; navigateTo({ url: routes.uc, query: { userId, }, }); }, }); [代码] 嗯,这样貌似舒服一点。 参数保真 第三个问题的情况是,当我们传递的参数argument不是[代码]string[代码],而是[代码]number[代码]或者[代码]boolean[代码]时,也只能在下个页面得到一个[代码]string[代码]值: [代码]// pages/index/index.js Page({ onReady() { navigateTo({ url: routes.uc, query: { isActive: true, }, }); }, }); // pages/user_center/index.js Page({ onLoad(options) { console.log(options.isActive); // => "true" console.log(typeof options.isActive); // => "string" console.log(options.isActive === true); // => false }, }); [代码] 上面这种情况想必很多人都遇到过,而且感到很抓狂,本来就想传递一个boolean,结果不管传什么都会变成string。 有什么办法可以让数据变成字符串之后,还能还原成原来的类型? 好熟悉,这不就是json吗?我们把要传的数据转成json字符串([代码]JSON.stringify[代码]),然后在下个页面把它转回json数据([代码]JSON.parse[代码])不就好了嘛! 我们试着修改原来的[代码]navigateTo[代码]: [代码]const routes = require('../../router/routes.js'); function navigateTo({ url, data }) { const dataStr = JSON.stringify(data); wx.navigateTo({ url: `${url}?jsonStr=${dataStr}`, }); } Page({ onReady() { navigateTo({ url: routes.uc, data: { isActive: true, }, }); }, }); [代码] 这样我们在页面中接受json字符串并转换它: [代码]// pages/user_center/index.js Page({ onLoad(options) { const json = JSON.parse(options.jsonStr); console.log(json.isActive); // => true console.log(typeof json.isActive); // => "boolean" console.log(json.isActive === true); // => true }, }); [代码] 这里其实隐藏了一个问题,那就是url的转义,假如json字符串中包含了类似[代码]?[代码]、[代码]&[代码]之类的符号,可能导致我们参数解析出错,所以我们要把json字符串encode一下: [代码]function navigateTo({ url, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); wx.navigateTo({ url: `${url}?encodedData=${dataStr}`, }); } // pages/user_center/index.js Page({ onLoad(options) { const json = JSON.parse(decodeURIComponent(options.encodedData)); console.log(json.isActive); // => true console.log(typeof json.isActive); // => "boolean" console.log(json.isActive === true); // => true }, }); [代码] 这样使用起来不方便,我们封装一下,新建文件[代码]router/index.js[代码]: [代码]const routes = require('./routes.js'); function navigateTo({ url, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); wx.navigateTo({ url: `${url}?encodedData=${dataStr}`, }); } function extract(options) { return JSON.parse(decodeURIComponent(options.encodedData)); } module.exports = { routes, navigateTo, extract, }; [代码] 页面中我们这样来使用: [代码]const router = require('../../router/index.js'); // page home Page({ onLoad(options) { router.navigateTo({ url: router.routes.uc, data: { isActive: true, }, }); }, }); // page uc Page({ onLoad(options) { const json = router.extract(options); console.log(json.isActive); // => true console.log(typeof json.isActive); // => "boolean" console.log(json.isActive === true); // => true }, }); [代码] route name 这样貌似还不错,但是[代码]router.navigateTo[代码]不太好记,[代码]router.routes.uc[代码]有点冗长,我们考虑把[代码]navigateTo[代码]换成简单的[代码]push[代码],至于路由,我们可以使用[代码]name[代码]的方式来替换原来[代码]url[代码]参数: [代码]const routes = require('./routes.js'); function push({ name, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); const url = routes[name]; wx.navigateTo({ url: `${url}?encodedData=${dataStr}`, }); } function extract(options) { return JSON.parse(decodeURIComponent(options.encodedData)); } module.exports = { push, extract, }; [代码] 在页面中使用: [代码]const router = require('../../router/index.js'); Page({ onLoad(options) { router.push({ name: 'uc', data: { isActive: true, }, }); }, }); [代码] navigateTo or switchTab 页面跳转除了navigateTo之外还有switchTab,我们是不是可以把这个差异抹掉?答案是肯定的,如果我们在配置routes的时候就已经指定是普通页面还是tab页面,那么程序完全可以切换到对应的跳转方式。 我们修改一下[代码]router/routes.js[代码],假设home是一个tab页面: [代码]module.exports = { // 主页 home: { type: 'tab', path: '/pages/index/index', }, uc: { path: '/pages/a/index', }, }; [代码] 然后修改[代码]router/index.js[代码]中[代码]push[代码]的实现: [代码]function push({ name, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); const route = routes[name]; if (route.type === 'tab') { wx.switchTab({ url: `${route.path}`, // 注意tab页面是不支持传参的 }); return; } wx.navigateTo({ url: `${route.path}?encodedData=${dataStr}`, }); } [代码] 搞定,这样我们一个[代码]router.push[代码]就能自动切换两种跳转方式了,而且之后一旦页面类型有变动,我们也只需要修改[代码]route[代码]的定义就可以了。 直接寻址 alias用着很不错,但是有一点挺麻烦得就是每新建一个页面都要写一个alias,即使没有别名的需要,我们是不是可以处理一下,如果在alias没命中,那就直接把name转化成url?这也是阔以的。 [代码]function push({ name, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); const route = routes[name]; const url = route ? route.path : name; if (route.type === 'tab') { wx.switchTab({ url: `${url}`, // 注意tab页面是不支持传参的 }); return; } wx.navigateTo({ url: `${url}?encodedData=${dataStr}`, }); } [代码] 在页面中使用: [代码]Page({ onLoad(options) { router.push({ name: 'pages/user_center/a/index', data: { isActive: true, }, }); }, }); [代码] 注意,为了方便维护,我们规定了每个页面都必须存放在一个特定的文件夹,一个文件夹的当前路径下只能存在一个index页面,比如[代码]pages/index[代码]下面会存放[代码]pages/index/index.js[代码]、[代码]pages/index/index.wxml[代码]、[代码]pages/index/index.wxss[代码]、[代码]pages/index/index.json[代码],这时候你就不能继续在这个文件夹根路径存放另外一个页面,而必须是新建一个文件夹来存放,比如[代码]pages/index/pageB/index.js[代码]、[代码]pages/index/pageB/index.wxml[代码]、[代码]pages/index/pageB/index.wxss[代码]、[代码]pages/index/pageB/index.json[代码]。 这样是能实现功能,但是这个name怎么看都跟alias风格差太多,我们试着定义一套转化规则,让直接寻址的name与alias风格统一一些,[代码]pages[代码]和[代码]index[代码]其实我们可以省略掉,[代码]/[代码]我们可以用[代码].[代码]来替换,那么原来的name就变成了[代码]user_center.a[代码]: [代码]Page({ onLoad(options) { router.push({ name: 'user_center.a', data: { isActive: true, }, }); }, }); [代码] 我们再来改进[代码]router/index.js[代码]中[代码]push[代码]的实现: [代码]function push({ name, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); const route = routes[name]; const url = route ? route.path : `pages/${name.replace(/\./g, '/')}/index`; if (route.type === 'tab') { wx.switchTab({ url: `${url}`, // 注意tab页面是不支持传参的 }); return; } wx.navigateTo({ url: `${url}?encodedData=${dataStr}`, }); } [代码] 这样一来,由于支持直接寻址,跳转home和uc还可以写成这样: [代码]router.push({ name: 'index', // => /pages/index/index }); router.push({ name: 'user_center', // => /pages/user_center/index }); [代码] 这样一来,除了一些tab页面以及特定的路由需要写alias之外,我们也不需要新增一个页面就写一条alias这么麻烦了。 其他 除了上面介绍的navigateTo和switchTab外,其实还有[代码]wx.redirectTo[代码]、[代码]wx.navigateBack[代码]以及[代码]wx.reLaunch[代码]等,我们也可以做一层封装,过程雷同,所以我们就不再一个个介绍,这里贴一下最终简化后的api以及原生api的映射关系: [代码]router.push => wx.navigateTo router.replace => wx.redirectTo router.pop => wx.navigateBack router.relaunch => wx.reLaunch [代码] 最终实现已经在发布在github上,感兴趣的朋友可以移步了解:mp-router。
2019-04-26 - (已解决)小程序搜索查询数据库多字段正则匹配问题
where({ description1: db.RegExp({ regexp: keyValue, options: 'i', }) }) 如果还要以‘’或‘’的方式匹配description2该怎么写呢? 文档说不能用db.command。 求解?
2019-03-13 - 新富文本组件
mp-html小程序富文本组件 news欢迎加入 QQ 交流群:699734691示例小程序添加获取组件包功能[图片] 功能介绍 支持在多个平台使用 支持丰富的标签(包括 table、video、svg 等) 支持丰富的事件效果(自动预览图片、链接处理等) 支持锚点跳转、长按复制等丰富功能 支持大部分 html 实体 丰富的插件(关键词搜索、内容编辑等) 效率高、容错性强且轻量化使用方法1. npm 方式 在项目根目录下执行 npm install mp-html 开发者工具中勾选 使用 npm 模块 并点击 工具 - 构建 npm 在需要使用页面的 json 文件中添加 { "usingComponents": { "mp-html": "mp-html" } } 在需要使用页面的 wxml 文件中添加 <mp-html content="{{html}}" /> 在需要使用页面的 js 文件中添加 Page({ onLoad() { this.setData({ html: 'Hello World!' }) } }) 2. 源码方式 将源码中的代码包(dist/mp-weixin)拷贝到 components 目录下,更名为 mp-html 在需要使用页面的 json 文件中添加 { "usingComponents": { "mp-html": "/components/mp-html/index" } } 后续步骤同上 获取github 链接:https://github.com/jin-yufeng/mp-html npm 链接:https://www.npmjs.com/package/mp-html 文档链接:https://jin-yufeng.gitee.io/mp-html
2022-03-04 - 利用云函数绕过域名校验和HTTPS配置,实现内网加端口访问
闲来无事,无意中发现云函数中的request网络请求可以不用配置校验域名和https,也就是说可以通过云函数封装一个请求通用函数来处理没有域名和https的网络请求(甚至包括内网穿透,可以用非80端口进行实验)。 适用场景: A、没有域名或使用局域网(直接使用IP访问); B、使用花生壳动态域名解析(内网穿透); C、有域名但不想申请配置HTTPS(懒人); D、连自己的服务器都没有,接口直接使用开源或者第三方接口且不能添加域名校验的情况(空壳); E、不愿意直接在小程序中直接暴露自己逻辑API实际请求地址的(安全); ······ 具体步骤如下: 1、给项目添加云函数支持(https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html) 2、新建名为“proxy”的云函数,配置支持request-promise [代码]// package.json[代码][代码]{[代码][代码] [代码][代码]"name"[代码][代码]: [代码][代码]"proxy"[代码][代码],[代码][代码] [代码][代码]"version"[代码][代码]: [代码][代码]"1.0.0"[代码][代码],[代码][代码] [代码][代码]"description"[代码][代码]: [代码][代码]""[代码][代码],[代码][代码] [代码][代码]"main"[代码][代码]: [代码][代码]"index.js"[代码][代码],[代码][代码] [代码][代码]"scripts"[代码][代码]: {[代码][代码] [代码][代码]"test"[代码][代码]: [代码][代码]"echo \"Error: no test specified\" && exit 1"[代码][代码] [代码][代码]},[代码][代码] [代码][代码]"author"[代码][代码]: [代码][代码]""[代码][代码],[代码][代码] [代码][代码]"license"[代码][代码]: [代码][代码]"ISC"[代码][代码],[代码][代码] [代码][代码]"dependencies"[代码][代码]: {[代码][代码] [代码][代码]"wx-server-sdk"[代码][代码]: [代码][代码]"latest"[代码][代码],[代码][代码] [代码][代码]"request"[代码][代码]: [代码][代码]"latest"[代码][代码],[代码][代码] [代码][代码]"request-promise"[代码][代码]: [代码][代码]"latest"[代码][代码] [代码][代码]}[代码][代码]}[代码][代码]// 云函数入口文件index.js[代码] [代码]const cloud = require([代码][代码]'wx-server-sdk'[代码][代码])[代码][代码]const rq = require([代码][代码]'request-promise'[代码][代码])[代码][代码]cloud.init()[代码][代码]// 云函数入口函数[代码][代码]// event为小程序调用的时候传递参数,包含请求参数uri、headers、body[代码][代码]exports.main = async (event, context) => {[代码][代码] [代码][代码]return[代码] [代码]await rq({[代码][代码] [代码][代码]method: [代码][代码]'POST'[代码][代码],[代码][代码] [代码][代码]uri: event.uri,[代码][代码] [代码][代码]headers: event.headers ? event.headers : {},[代码][代码] [代码][代码]body: event.body[代码][代码] [代码][代码]}).then(body => {[代码][代码] [代码][代码]return[代码] [代码]body[代码][代码] [代码][代码]}).[代码][代码]catch[代码][代码](err => {[代码][代码] [代码][代码]return[代码] [代码]err[代码][代码] [代码][代码]})[代码][代码]}[代码]3、在小程序中调用云函数请求数据请求 [代码]onLoad: [代码][代码]function[代码][代码](){[代码][代码] [代码][代码]// 初始化[代码][代码] [代码][代码]wx.cloud.init()[代码][代码]},[代码][代码]onGetItemList: [代码][代码]function[代码][代码](){[代码][代码] [代码][代码]wx.cloud.callFunction({[代码][代码] [代码][代码]name: [代码][代码]'proxy'[代码][代码],[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]// http域名 https域名 第三方域名 非验证域名 IP[:prot] 内网IP或花生壳域名[代码][代码] [代码][代码]uri: [代码][代码]'http://192.168.1.100:8081'[代码][代码],[代码][代码] [代码][代码]headers: {[代码][代码] [代码][代码]'Content-Type'[代码][代码]: [代码][代码]'application/json'[代码][代码] [代码][代码]},[代码][代码] [代码][代码]body: {[代码][代码] [代码][代码]uid: 1[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}).then(res => {[代码][代码] [代码][代码]console.log(res)[代码][代码] [代码][代码]const data = res.result[代码][代码] [代码][代码]console.log(data)[代码][代码] [代码][代码]// do something[代码][代码] [代码][代码]})[代码][代码]}[代码]然后你会发现你已经无所不能了。 个人见解,如有不妥之处,望各位大神指正!~
2018-12-03 - 快手的小程序把导航栏去掉了,很个性,如今我也给他去掉了
先给个图片,以前我在网上查过,问小程序如何去掉导航栏,网上一致回答说不能去掉,因为这是小程序的统一性,谁知过了不久开发文档里页面配置里就开放了这一项,手机本身版面就不大,这导航栏确实有点浪费,如今我用它做菜单,觉得很方便,如同地球有了一个空间站,我做的象棋棋谱就利用上了.把图发上来让大家看一下,不怕费时的就上小程序里搜象棋棋谱看一下,整个程序是用画布作的,走棋可以生成棋谱术语,很好玩. [图片]
2019-03-31 - beta版本geo型数据查询bug
- 当前 Bug 的表现(可附上截图) 使用geo型存储地理位置数据,在查询时会出现location无法解析的问题 - 预期表现 [图片] 返回数据成功 [图片] - 复现路径 但是一旦请求解析geojson [图片] [图片] - 提供一个最简复现 Demo 由于云端不和mongodb一样返回distance,所以无法进行距离信息显示,必须在服务器端进行计算,但是现在无法取出经纬度数据
2019-03-28 - 小程序蓝牙打印爬坑之旅
因为公司要在小程序上加蓝牙打印标签功能,所以就开始接触小程序的蓝牙打印,看文档还是蛮详细的,而且还有demo,顺着demo,一步一步下来还是蛮顺畅的,原以为很快就能完成。没想到坑来了,由于demo中writeBLECharacteristicValue只是写入了一个16进制的数据,而现实中是需要发送字符串的,而且小程序必须要是arrayBuffer,就必须将字符串转arrayBuffer了,好,网上搜下,准备打印了吱吱吱咦,怎么有乱码啊,怎么中文都乱码了。。这下可糟了!于是就去各种找答案。最后知道问题了:原来是因为我们公司用的打印机是智能支持GB2312编码格式的二进制的,但是字符串是utf-8,诶,又得爬坑。经过一天的努力,终于找到解决方法啦,感谢csdn的大大们。实现的代码如下 //计算arraybuffer的长度 sumStrLength(str) { var length = 0; var data = str.toString(); for (var i = 0; i < data.length; i++) { if (this.isCN(data[i])) { //是中文 length += 2; } else { length += 1; } } return length; }, //混杂 hexStringToBuff(str) { //str=‘中国:WXHSH’ const buffer = new ArrayBuffer((this.sumStrLength(str)) + 1); const dataView = new DataView(buffer) var data = str.toString(); var p = 0; //ArrayBuffer 偏移量 for (var i = 0; i < data.length; i++) { if (this.isCN(data[i])) { //是中文 //调用GBK 转码 var t = gbk.$URL.encode(data[i]); for (var j = 0; j < 2; j++) { var temp = parseInt(t[j * 2] + t[j * 2 + 1], 16) dataView.setUint8(p++, temp) } } else { var temp = parseInt(data.charCodeAt(i).toString(16), 16) dataView.setUint8(p++, temp) } } console.log(String.fromCharCode.apply(null, new Uint8Array(buffer))); return buffer; }, //js正则验证中文 isCN(str) { if (/[1]+$/.test(str)) { return true; } else { return false; } }, 将中文转化为GB2312编码格式再转成arrayBuffer就大功告成啦,把这个文章记录下来,希望可以帮助到其他小程程们。如有需要,加我Q:786914253 \u3220-\uFA29 ↩︎
2019-03-19 - 【微信小程序】性能优化
为什么要做性能优化? 一切性能优化都是为了体验优化 1. 使用小程序时,是否会经常遇到如下问题? 打开是一直白屏 打开是loading态,转好几圈 我的页面点了怎么跳转这么慢? 我的列表怎么越滑越卡? 2. 我们优化的方向有哪些? 启动加载性能 渲染性能 3. 启动加载性能 1. 首次加载 你是否见过小程序首次加载时是这样的图? [图片] 这张图中的三种状态对应的都是什么呢? 小程序启动时,微信会为小程序展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。此时,微信会在背后完成几项工作:[代码]下载小程序代码包[代码]、[代码]加载小程序代码包[代码]、[代码]初始化小程序首页[代码]。下载到的小程序代码包不是小程序的源代码,而是编译、压缩、打包之后的代码包。 2. 加载顺序 小程序加载的顺序是如何? 微信会在小程序启动前为小程序准备好通用的运行环境。这个运行环境包括几个供小程序使用的线程,并在其中完成小程序基础库的初始化,预先执行通用逻辑,尽可能做好小程序的启动准备。这样可以显著减少小程序的启动时间。 [图片] 通过2,我们知道了,问题1中第一张图是[代码]资源准备[代码](代码包下载);第二张图是[代码]业务代码的注入以及落地页首次渲染[代码];第三张图是[代码]落地页数据请求时的loading态[代码](部分小程序存在) 3. 控制包大小 提升体验最直接的方法是控制小程序包的大小,这是最显而易见的 勾选开发者工具中“上传代码时,压缩代码”选项; 及时清理无用的代码和资源文件(包括无用的日志代码) 减少资源包中的图片等资源的数量和大小(理论上除了小icon,其他图片资源从网络下载),图片资源压缩率有限 从开发者的角度看,控制代码包大小有助于减少小程序的启动时间。对低于1MB的代码包,其下载时间可以控制在929ms(iOS)、1500ms(Android)内。 4. 采用分包加载机制 根据业务场景,将用户访问率高的页面放在主包里,将访问率低的页面放入子包里,按需加载; [图片] 使用分包时需要注意代码和资源文件目录的划分。启动时需要访问的页面及其依赖的资源文件应放在主包中。 5 采用分包预加载技术 在4的基础上,当用户点击到子包的目录时,还是有一个代码包下载的过程,这会感觉到明显的卡顿,所以子包也不建议拆的太大,当然我们可以采用子包预加载技术,并不需要等到用户点击到子包页面后在下载子包,而是可以根据后期数据,做子包预加载,将用户在当先页可能点击的子包页面先加载,当用户点击后直接跳转; [图片] 这种基于配置的子包预加载技术,是可以根据用户网络类型来判断的,当用户处于网络条件好时才预加载;是灵活可控的 6. 采用独立分包技术 目前很多小程序[代码]主包+子包[代码](2M+6M)的方式,但是在做很多运营活动时,我们会发现活动(红包)是在子包里,但是运营、产品投放的落地页链接是子包链接,这是的用户在直达落地时,必须先下载主包内容(一般比较大),在下载子包内容(相对主包,较小),这使得在用户停留时间比较短的小程序场景中,用户体验不是很好,而且浪费了很大部分流量; [图片] 可以采用独立分包技术,区别于子包,和主包之间是无关的,在功能比较独立的子包里,使用户只需下载分包资源; 7. 首屏加载的优化建议 7.1 提前请求 异步请求可以在页面onLoad就加载,不需要等页面ready后在异步请求数据;当然,如果能在前置页面点击跳转时预请求当前页的核心异步请求,效果会更好; 7.2 利用缓存 利用storage API, 对变动频率比较低的异步数据进行缓存,二次启动时,先利用缓存数据进行初始化渲染,然后后台进行异步数据的更新,这不仅优化了性能,在无网环境下,用户也能很顺畅的使用到关键服务; 7.3 避免白屏 可以在前置页面将一些有用的字段带到当前页,进行首次渲染(列表页的某些数据–> 详情页),没有数据的模块可以进行骨架屏的占位,使用户不会等待的很焦虑,甚至走了; 7.4 及时反馈 及时的对需要用户等待的交互操作进行反馈,避免用户以为小程序卡了,无响应 渲染性能优化 1. 小程序渲染原理 双线程下的界面渲染,小程序的逻辑层和渲染层是分开的两个线程。在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面。 [图片] 分析这个流程不难得知:页面初始化的时间大致由页面初始数据通信时间和初始渲染时间两部分构成。其中,数据通信的时间指数据从逻辑层开始组织数据到视图层完全接收完毕的时间,数据量小于64KB时总时长可以控制在30ms内。传输时间与数据量大体上呈现正相关关系,传输过大的数据将使这一时间显著增加。因而减少传输数据量是降低数据传输时间的有效方式。 [图片] 2. 避免使用不当setData 在数据传输时,逻辑层会执行一次[代码]JSON.stringify[代码]来去除掉[代码]setData[代码]数据中不可传输的部分,之后将数据发送给视图层。同时,逻辑层还会将[代码]setData[代码]所设置的数据字段与[代码]data[代码]合并,使开发者可以用[代码]this.data[代码]读取到变更后的数据。因此,为了提升数据更新的性能,开发者在执行[代码]setData[代码]调用时,最好遵循以下原则: 2.1 不要过于频繁调用setData,应考虑将多次setData合并成一次setData调用; [图片] 2.2 数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串,则不应使用[代码]setData[代码]来设置这些数据; [图片] 2.3 与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下 [图片] 提升数据更新性能方式的代码示例 [代码]Page({ onShow: function() { // 不要频繁调用setData this.setData({ a: 1 }) this.setData({ b: 2 }) // 绝大多数时候可优化为 this.setData({ a: 1, b: 2 }) // 不要设置不在界面渲染时使用的数据,并将界面无关的数据放在data外 this.setData({ myData: { a: '这个字符串在WXML中用到了', b: '这个字符串未在WXML中用到,而且它很长…………………………' } }) // 可以优化为 this.setData({ 'myData.a': '这个字符串在WXML中用到了' }) this._myData = { b: '这个字符串未在WXML中用到,而且它很长…………………………' } } }) [代码] 利用setData进行列表局部刷新 在一个列表中,有[代码]n[代码]条数据,采用上拉加载更多的方式,假如这个时候想对其中某一个数据进行点赞操作,还能及时看到点赞的效果 解决方法 1、可以采用setData全局刷新,点赞完成之后,重新获取数据,再次进行全局重新渲染,这样做的优点是:方便,快捷!缺点是:用户体验极其不好,当用户刷量100多条数据后,重新渲染量大会出现空白期(没有渲染过来) 2、说到重点了,就是利用[代码]setData[代码]局部刷新 [代码]> a.将点赞的`id`传过去,知道点的是那一条数据, 将点赞的`id`传过去,知道点的是那一条数据 [代码] [代码]<view wx:if="{{!item.status}}" class="btn" data-id="{{index}}" bindtap="couponTap">立即领取</view> [代码] [代码]> b.重新获取数据,查找相对应id的那条数据的下标(`index`是不会改变的) > c.用setData进行局部刷新 [代码] [代码]this.setData({ list[index] = newList[index] }) [代码] 其实这个小操作对刚刚接触到微信小程序的人来说应该是不容易发现的,不理解setData还有这样的写法。 2.4 切勿在后台页面进行setData 在一些页面会进行一些操作,而到页面跳转后,代码逻辑还在执行,此时多个[代码]webview[代码]是共享一个js进程;后台的[代码]setData[代码]操作会抢占前台页面的渲染资源; [图片] [图片] 3. 用户事件使用不当 视图层将事件反馈给逻辑层时,同样需要一个通信过程,通信的方向是从视图层到逻辑层。因为这个通信过程是异步的,会产生一定的延迟,延迟时间同样与传输的数据量正相关,数据量小于64KB时在30ms内。降低延迟时间的方法主要有两个。 1.去掉不必要的事件绑定(WXML中的[代码]bind[代码]和[代码]catch[代码]),从而减少通信的数据量和次数; 2.事件绑定时需要传输[代码]target[代码]和[代码]currentTarget[代码]的[代码]dataset[代码],因而不要在节点的[代码]data[代码]前缀属性中放置过大的数据。 [图片] 4. 视图层渲染原理 4.1首次渲染 初始渲染发生在页面刚刚创建时。初始渲染时,将初始数据套用在对应的WXML片段上生成节点树。节点树也就是在开发者工具WXML面板中看到的页面树结构,它包含页面内所有组件节点的名称、属性值和事件回调函数等信息。最后根据节点树包含的各个节点,在界面上依次创建出各个组件。 [图片] 在这整个流程中,时间开销大体上与节点树中节点的总量成正比例关系。因而减少WXML中节点的数量可以有效降低初始渲染和重渲染的时间开销,提升渲染性能。 简化WXML代码的例子 [代码]<view data-my-data="{{myData}}"> <!-- 这个 view 和下一行的 view 可以合并 --> <view class="my-class" data-my-data="{{myData}}" bindtap="onTap"> <text> <!-- 这个 text 通常是没必要的 --> {{myText}} </text> </view> </view> <!-- 可以简化为 --> <view class="my-class" data-my-data="{{myData}}" bindtap="onTap"> {{myText}} </view> [代码] 4.2 重渲染 初始渲染完毕后,视图层可以多次应用[代码]setData[代码]的数据。每次应用[代码]setData[代码]数据时,都会执行重渲染来更新界面。初始渲染中得到的data和当前节点树会保留下来用于重渲染。每次重渲染时,将[代码]data[代码]和[代码]setData[代码]数据套用在WXML片段上,得到一个新节点树。然后将新节点树与当前节点树进行比较,这样可以得到哪些节点的哪些属性需要更新、哪些节点需要添加或移除。最后,将[代码]setData[代码]数据合并到[代码]data[代码]中,并用新节点树替换旧节点树,用于下一次重渲染。 [图片] 在进行当前节点树与新节点树的比较时,会着重比较[代码]setData[代码]数据影响到的节点属性。因而,去掉不必要设置的数据、减少[代码]setData[代码]的数据量也有助于提升这一个步骤的性能。 5. 使用自定义组件 自定义组件的更新只在组件内部进行,不受页面其他不能分内容的影响;比如一些运营活动的定时模块可以单独抽出来,做成一个定时组件,定时组件的更新并不会影响页面上其他元素的更新;各个组件也将具有各自独立的逻辑空间。每个组件都分别拥有自己的独立的数据、setData调用。 [图片] 6. 避免不当的使用onPageScroll 每一次事件监听都是一次视图到逻辑的通信过程,所以只在必要的时候监听pageSrcoll [图片] 总结 小程序启动加载性能 控制代码包的大小 分包加载 首屏体验(预请求,利用缓存,避免白屏,及时反馈 小程序渲染性能 避免不当的使用setData 合理利用事件通信 避免不当的使用onPageScroll 优化视图节点 使用自定义组件
2019-03-07 - 微信小程序前端生成图片用于分享朋友圈最终解决方案
前段时间一直在做微信小程序的,遇到了许多的坑,其中遇到了需要前端合成图片保存到相册用于分享到朋友圈。借简书记录一下最终解决方案,先看一下最终效果 [图片] 该文章的所有演示代码托管与github,代码地址,微信调试工具中访问请[代码]关闭合法域名检查[代码],[代码]开启es6转换[代码],真机调试请打开调试[代码]vconsole[代码] 该文章解决的问题如下: 微信小程序生成图片,并保存到相册 微信小程序生成图片实现响应式 微信小程序canvas原生组件如何给画布添加css动画 保存高清分享图方案 微信小程序生成图片实现单屏适应 微信小程序生成图片,并保存到相册 首先,我们希望能实现如下功能,点击用户头像,从底部弹出一个分享弹窗,可以保存合成图片到相册,可以关闭弹层 我们将该功能封装成一个Component自定义组件 定义wxml基本结构 [代码]<view class="share {{visible ? 'show' : ''}}"> <view class="content"> <canvas class="canvas" canvas-id="share" /> <view class="footer"> <view class="save">保存到相册</view> <view class="close">关闭</view> </view> </view> </view> [代码] 定义wxss样式 [代码].share { position: fixed; top: 0; left: 0; min-height: 100vh; width: 100%; background: rgba(61, 61, 61, 0.5); visibility: hidden; opacity: 0; transition: opacity 0.2s ease-in-out; z-index: 99999; } .share.show { visibility: visible; opacity: 1; } .share .content { display: flex; flex-direction: column; justify-content: center; align-items: center; } .share .content .footer { width: 562rpx; height: 100rpx; background: #fff; border-top: 2rpx solid #e9e9e9; display: flex; flex-direction: row; justify-content: center; align-items: center; font-size: 28rpx; } .share .content .footer .close { width: 100rpx; height: 100rpx; line-height: 100rpx; flex-grow: 0; flex-shrink: 0; text-align: center; border-left: 2rpx solid #e9e9e9; } .share .content .footer .save { height: 100rpx; line-height: 100rpx; flex-grow: 1; flex-shrink: 1; text-align: center; } .share.show .content .canvas { display: inline-block; } .share .content .canvas { display: inline-block; background: #fff; margin: 60rpx 0 0 0; width: 562rpx; height: 1000rpx; } [代码] 定义json [代码]{ "component": true } [代码] 定义组件构造器 [代码]Component({ properties: { visible: { type: Boolean, value: false }, // 由于需要绘制用户信息,由页面传入 userInfo: { type: Object, value: false } }, methods: { draw() { // 实际绘制函数,后续绘制代码放于此处 } } }) [代码] 基本结构和样式定义完成,接下来开始可一开始我们绘制之旅了,合成图片需要用到微信小程序wx.getImageInfo函数,我们先对它进行Promise化方便后期调用 [代码]function getImageInfo(url) { return new Promise((resolve, reject) => { wx.getImageInfo({ src: url, success: resolve, fail: reject, }) }) } [代码] 前期的准备工作建立完成,我们开始定义绘制方法draw [代码]const { userInfo } = this.data const { avatarUrl, nickName } = userInfo // 获取头像图像信息 const avatarPromise = getImageInfo(avatarUrl) // 获取背景图像信息 const backgroundPromise = getImageInfo('https://img.xiaomeipingou.com/_assets_home-share-bg.jpg') Promise.all([avatarPromise, backgroundPromise]) .then(([avatar, background]) => { // 创建绘图上下文 const ctx = wx.createCanvasContext('share', this) const canvasWidth = 281 const canvasHeight = 500 // 绘制背景,填充满整个canvas画布 ctx.drawImage(background.path, 0, 0, canvasWidth, canvasHeight) const avatarWidth = 60 const avatarHeight = 60 const avatarTop = 40 // 绘制头像 ctx.drawImage( avatar.path, canvasWidth / 2 - avatarWidth / 2, avatarTop - avatarHeight / 2, avatarWidth, avatarHeight ) // 绘制用户名 ctx.setFontSize(20) ctx.setTextAlign('center') ctx.setFillStyle('#ffffff') ctx.fillText( nickName, canvasWidth / 2, avatarTop + 50, ) ctx.stroke() // 完成作画 ctx.draw() }) [代码] 接下来,我们需要监测visible属性的变化,决定是否开始绘制 [代码]Component({ properties: { visible: { type: Boolean, value: false, observer(visible) { // 当开始显示分享弹窗时开始绘制 if (visible) { this.draw() } } }, }, ....省略其他代码 }) [代码] 此时,前端的绘制已基本成型,运行小程序变可看见合成图,由于我们的绘制尺寸是基于iphone6s进行绘制的,在iphone6s及部分相同分辨率查看,尺寸完全吻合,没有任何问题,然而当我们用iphone6s plus或者其他不同分辨率的手机打开时却变成了下面这个样子 [图片] 绘制的图像没有完全占满画布了,为什么呢?这个是遇到的第二个问题 微信小程序生成图片实现响应式 其实我们的画布宽高单位都是基于rpx单位,因此在不同分辨率的手机上,实际的尺寸也就不同,然而我们绘制图片的尺寸都是以px为单位,自然无法实现响应式,因此我们需要一个js方法用于转换rpx值为px值 解读微信官方文档我们定义如下一个简单的转换方法 [代码]function createRpx2px() { const { windowWidth } = wx.getSystemInfoSync() return function(rpx) { return windowWidth / 750 * rpx } } const rpx2px = createRpx2px() [代码] 定义好了单位转换函数,我们只需转换相关值即可 [代码]const { userInfo } = this.data const { avatarUrl, nickName } = userInfo // 获取头像图像信息 const avatarPromise = getImageInfo(avatarUrl) // 获取背景图像信息 const backgroundPromise = getImageInfo('https://img.xiaomeipingou.com/_assets_home-share-bg.jpg') Promise.all([avatarPromise, backgroundPromise]) .then(([avatar, background]) => { // 创建绘图上下文 const ctx = wx.createCanvasContext('share', this) const canvasWidth = rpx2px(281 * 2) const canvasHeight = rpx2px(500 * 2) // 绘制背景,填充满整个canvas画布 ctx.drawImage(background.path, 0, 0, canvasWidth, canvasHeight) const avatarWidth = rpx2px(60 * 2) const avatarHeight = rpx2px(60 * 2) const avatarTop = rpx2px(40 * 2) // 绘制头像 ctx.drawImage( avatar.path, canvasWidth / 2 - avatarWidth / 2, avatarTop - avatarHeight / 2, avatarWidth, avatarHeight ) // 绘制用户名 ctx.setFontSize(rpx2px(20 * 2)) ctx.setTextAlign('center') ctx.setFillStyle('#ffffff') ctx.fillText( nickName, canvasWidth / 2, avatarTop + rpx2px(50 * 2), ) ctx.stroke() // 完成作画 ctx.draw() [代码] 此时不管在什么分辨率下的手机都能正常显示了 微信小程序canvas原生组件如何给画布添加css动画 我们都知道微信小程序的canvas是原生组件,对于原生组件有许多的限制,比如不可以使用css动画,官方文档如下: [图片] 首先我们试着给canvas父层标签View.content标签添加弹出动画,修改样式如下: [代码].share .content { display: flex; flex-direction: column; justify-content: center; align-items: center; // 新增动画控制 transform: translate3d(0, 100%, 0); transition: transform 0.2s ease-in-out; } // 新增动画控制 .share.show .content { transform: translate3d(0, 0, 0); } [代码] 在调试器中使用,一切都很美好,完全按着预期由底部弹出,然后淡隐,不过当你用真机调试,canvas部分的效果变得不那么顺畅,流畅,没有弹出动画,没有淡隐效果,一切都变得那么的僵硬,那我们该怎么办呢? 解决办法的思路如下: 提供一个canvas标签,不可以做隐藏(隐藏会导致绘制失效),通过css tansform属性移除屏幕让其不可见 用image标签代替canvas标签显示给用户查看 当画布绘制完成后,我们保存绘制的图像到临时目录中,并获取图片地址 将地址提供给image标签用于展示 基于以上思路,首先改造我们的文档结构 [代码]<view class="share {{ visible ? 'show' : '' }}"> <canvas class="canvas-hide" canvas-id="share" /> <view class="content"> <image class="canvas" src="{{imageFile}}" /> <view class="footer"> <view class="save">保存到相册</view> <view class="close" bindtap="handleClose">关闭</view> </view> </view> </view> [代码] 新增样式 [代码].share .canvas-hide { position: fixed; top: 0; left: 0; transform: translateX(-100%); width: 562rpx; height: 1000rpx; } [代码] 想要保存canvas绘制的图像到临时目录,我们需要利用微信小程序的一个api接口wx.canvasToTempFilePath,因此首先我们还是对其进行Promise化 [代码]function canvasToTempFilePath(option, context) { return new Promise((resolve, reject) => { wx.canvasToTempFilePath({ ...option, success: resolve, fail: reject, }, context) }) } [代码] 在组件的data属性中新增imageFile [代码]// 仅列出新增部分,省略之前的代码 Component({ data: { imageFile: '' } }) [代码] 修改我们的绘制方法 [代码]// 仅列出新增部分,省略之前的代码 // 修改画布的draw函数如下 ctx.draw(false, () => { canvasToTempFilePath({ canvasId: 'share', }, this).then(({ tempFilePath }) => this.setData({ imageFile: tempFilePath })) }) [代码] 此时在真机上运行调试,可以看到完美的满足我们的需求(沾沾自喜) 保存高清分享图方案 接下来我们需要实现保存到相册中,用于分享给朋友圈或者其他微博 保存图片到相册需要调用微信小程序api,wx.saveImageToPhotosAlbum,依照惯例进行Promise化 [代码]function saveImageToPhotosAlbum(option) { return new Promise((resolve, reject) => { wx.saveImageToPhotosAlbum({ ...option, success: resolve, fail: reject, }) }) } [代码] 我们为保存相册新增点击事件 [代码]<view class="save" bindtap="handleSave">保存到相册</view> [代码] 最后定义我们的保存方法 [代码]// 仅列出新增部分,省略之前的代码 Component({ methods: { handleSave() { const { imageFile } = this.data if (imageFile) { saveImageToPhotosAlbum({ filePath: imageFile, }).then(() => { wx.showToast({ icon: 'none', title: '分享图片已保存至相册', duration: 2000, }) }) } } } }) [代码] 至此保存到相册功能完成了,但是有点瑕疵,原本我们用于绘制的图片非常的高清,可以绘制后保存的图片变得模糊了,没那么高清,这是过不了UED小姐姐那关的 那如何保证保存的图片不会失真呢,我们可以考虑把canvas大小放大到3倍,绘制3倍的图 修改样式 [代码].share .content .canvas { display: inline-block; background: #fff; margin: 60rpx 0 0 0; width: 1686rpx; // 修改为之前的3倍 height: 3000rpx; // 修改为之前的3倍 } [代码] 修改绘制函数,增长绘制大小为3倍 [代码]const { userInfo } = this.data const { avatarUrl, nickName } = userInfo // 获取头像图像信息 const avatarPromise = getImageInfo(avatarUrl) // 获取背景图像信息 const backgroundPromise = getImageInfo('https://img.xiaomeipingou.com/_assets_home-share-bg.jpg') Promise.all([avatarPromise, backgroundPromise]) .then(([avatar, background]) => { // 创建绘图上下文 const ctx = wx.createCanvasContext('share', this) const canvasWidth = rpx2px(281 * 2 * 3) // 扩大3倍 const canvasHeight = rpx2px(500 * 2 * 3) // 扩大3倍 // 绘制背景,填充满整个canvas画布 ctx.drawImage(background.path, 0, 0, canvasWidth, canvasHeight) const avatarWidth = rpx2px(60 * 2 * 3) // 扩大3倍 const avatarHeight = rpx2px(60 * 2 * 3) // 扩大3倍 const avatarTop = rpx2px(40 * 2 * 3) // 扩大3倍 // 绘制头像 ctx.drawImage( avatar.path, canvasWidth / 2 - avatarWidth / 2, avatarTop - avatarHeight / 2, avatarWidth, avatarHeight ) // 绘制用户名 ctx.setFontSize(rpx2px(20 * 2 * 3)) // 扩大3倍 ctx.setTextAlign('center') ctx.setFillStyle('#ffffff') ctx.fillText( nickName, canvasWidth / 2, avatarTop + rpx2px(50 * 2 * 3), // 扩大3倍 ) ctx.stroke() // 完成作画 ctx.draw(false, () => { canvasToTempFilePath({ canvasId: 'share', }, this).then(({ tempFilePath }) => this.setData({ imageFile: tempFilePath })) }) [代码] 我们重新保存图片,发现图片变得高清了,hu~~~ 最后我们可以兴高采烈的把成果交给小测试了,一切看起来都很顺利,可惜终究过不了各种机型分辨率的测试,由于我们的设计基于iphone6s尺寸设计,在部分宽高比不同的机型,高度会超出屏幕高度,变成下面这个样子 [图片] 按钮被挡住了,这下无奈了 微信小程序生成图片实现单屏适应 我们希望分享弹窗内容能在一个屏幕下显示完全,那可以根据当前手机宽高比与设计稿尺寸宽高比求出一个缩放比例对整体内容进行缩放即可 定义缩放比例计算 [代码]// 仅列出新增部分,省略之前的代码 Component({ data: { responsiveScale: 1, // 缩放比例默认为1 }, lifetimes: { ready() { const designWidth = 375 const designHeight = 603 // 这是在顶部位置定义,底部无tabbar情况下的设计稿高度 // 以iphone6为设计稿,计算相应的缩放比例 const { windowWidth, windowHeight } = wx.getSystemInfoSync() const responsiveScale = windowHeight / ((windowWidth / designWidth) * designHeight) if (responsiveScale < 1) { this.setData({ responsiveScale, }) } }, }, }) [代码] 修改wxml文档 [代码]<view class="share {{ visible ? 'show' : '' }}"> <canvas class="canvas-hide" canvas-id="share" /> <view class="content" style="transform:scale({{responsiveScale}});-webkit-transform:scale({{responsiveScale}});"> <image class="canvas" src="{{imageFile}}" /> <view class="footer"> <view class="save" bindtap="handleSave">保存到相册</view> <view class="close" bindtap="handleClose">关闭</view> </view> </view> </view> [代码] 修改wxss样式表 [代码].share .content { // 省略其他定义 // 新增缩放中心控制为顶部中心 transform-origin: 50% 0; } [代码] 整体分享遇到的坑都得到了解决,代码较多,所有的代码都托管到了github,欢迎访问运行代码地址,只有亲力亲为才能真正的掌握知识
2019-01-14 - 自定义导航栏所有机型的适配方案
写在前面的话 大家看到这个文章时一定会感觉这是在炒剩饭,社区中已经有那么多分享自定义导航适配的文章了,为什么我还要再写一个呢? 主要原因就是,社区中大部分的适配方案中给出的大小是不精确的,并不能完美适配各种场景。 社区中大部分文章给到的值是 iOS -> 44px , Android -> 48px 思路 正常来讲,iOS和Android下的胶囊按钮的位置以及大小都是相同且不变的,我们可以通过胶囊按钮的位置和大小再配合 wx.getSystemInfo 或者 wx.getSystemInfoSync 中得到的 [代码]statusBarHeight[代码] 来计算出导航栏的位置和大小。 小程序提供了一个获取菜单按钮(右上角胶囊按钮)的布局位置信息的API,可以通过这个API获取到胶囊按钮的位置信息,但是经过实际测试,这个接口目前存在BUG,得到的值经常是错误的(通过特殊手段可以偶尔拿到正确的值),这个接口目前是无法使用的,等待官方修复吧。 下面是我经过实际测试得到的准确数据: 真机和开发者工具模拟器上的胶囊按钮不一样 [代码]# iOS top 4px right 7px width 87px height 32px # Android top 8px right 10px width 95px height 32px # 开发者工具模拟器(iOS) top 6px right 10px width 87px height 32px # 开发者工具模拟器(Android) top 8px right 10px width 87px height 32px [代码] [代码]top[代码] 的值是从 [代码]statusBarHeight[代码] 作为原点开始计算的。 使用上面数据中胶囊按钮的高度加 [代码]top[代码] * 2 上再加上 [代码]statusBarHeight[代码] 的高度就可以得到整个导航栏的高度了。 为什么 [代码]top[代码] * 2 ?因为胶囊按钮是垂直居中在 title 那一栏中的,上下都要有边距。 扩展 通过胶囊按钮的 [代码]right[代码] 可以准确的算出自定义导航的 [代码]左边距[代码]。 通过胶囊按钮的 [代码]right[代码] + [代码]width[代码] 可以准确的算出自定义导航的 [代码]右边距[代码] 。 通过 wx.getSystemInfo 或者 wx.getSystemInfoSync 中得到的 [代码]windowWidth[代码] - 胶囊按钮的 [代码]right[代码] + [代码]width[代码] 可以准确的算出自定义导航的 [代码]width[代码] 。 再扩展 wx.getSystemInfo 或者 wx.getSystemInfoSync 中得到的 [代码]statusBarHeight[代码] 每个机型都不一样,刘海屏得到的数据也是准确的。 如果是自定义整个页面,iPhone X系列的刘海屏,底部要留 [代码]68px[代码] ,不要问我为什么! 代码片段 https://developers.weixin.qq.com/s/Q79g6kmo7w5J
2019-02-25 - 微信小程序开发常见问题(四)
一、判断小程序版本号 小程序的API是不断更新的,你可能使用某个API时,文档里会说明,此API在1.x.x版本开始支持,需要自己做兼容处理。 如果你使用小程序版本号做兼容,就必须了解小程序的基础库版本号规则,在这里介绍一下。 小程序基础库版本号使用 semver 规范,格式为 Major.Minor.Patch,Major、Minor、Patch 均为整数,1.9.901、2.44.322、10.32.44 都是符合 semver 风格的版本号。 以下是官方提供的兼容代码: [图片] 二、设置仅发起者可转发 有些场景,需要仅发起者可转发,参与者不能转发。比如老师上课点名签到,老师如果把签到小程序分享到群内,只希望现场的同学可以正常签到,即使其他同学想分享,也没有权限。 方法一:禁止显示分享按钮 小程序中有个API,wx.hideShareMenu,如下: [图片] 在page.js中,正常写入onShareAppMessage,然后判断用户是否为发起者,如果是发起者,调用wx.showShareMenu,如果不是发起者,调用wx.hideShareMenu。 方法二:单独做一个分享后的页面 这个办法比较low,但也能实现。思路是在onShareAppMessage里面的写入一个默认path,打开之后就是一个提示界面。如果是发起者,就把path修改成正常的路径~ [图片] 三、swiper禁止手动滑动 最简单的方式,在swiper上面加一个透明的蒙层~ 四、使用switchTab跳转后页面不刷新的问题 方法一:通过getCurrentPages获取获取当前的页面栈,调用对应的方法 [图片] switchTab成功跳转后调用success,此时可以拿到跳转后页面的page对象,从而调用页面onLoad方法重载页面; 方法二:把还tab的页面,代码逻辑放在onShow里面 [图片] 五、e.target和e.currentTarget的区别 简单的说,e.currentTarge是指注册了事件监听器的对象,e.target是指对象里的子对象,实际触发这个事件的对象。 [图片] 小程序开发过程中,如果通过event未获取到值时,或许是你自己用错了。 六、图片设置为圆角,会快速从方形闪烁一下 解决方法:给父元素设置圆角,或者给图片添加透明边框 七、input中使用手写输入法的坑 之前总有“报名工具”的小程序用户反馈,说是报名内容都输入完整了,但是保存不完整。 经排查,发现所有使用手写输入法的用户,都会遇到这现象。排查代码发现绑定的input事件,用户如果不点击手写输入法上的确认键,就不会触发bindinput。 这种情况下,有两种解决方式: 1、改用event.detail.value的形式来获取form表单数据; 2、再添加一个bindblur事件,保证事件能够正常执行; 因报名工具小程序中,有用户自定义字段,所以不确定用户会添加多少个字段,使用event获取的话,需要给每个input添加一个name属性,相对比较费劲,我改用了第二种方案。 [图片] 八、IOS下用户授权后,头像和昵称显示问题 这个问题其实是图片src是一个data中的变量,然后这个变量又发生了变化。但是在IOS设备上,就是没办法显示更改后的图片。(或者编辑图文投票时,也会有这现象) 解决办法:通过wx:if和wx:else判断,展示不同的image组件 [图片] 九、一键退出(隐藏)小程序 首先要说,这个需求不合理,右上角有退出按钮,但既然有同学问这问题,我也自己折腾了一下,基本可以实现一键退出。 先在每个page中添加隐藏page的方法: [图片] 有退出button的页面,对应的JS添加方法: [图片] 虽然可以实现,但也很low,其实就是隐藏了所有的page而已~
2019-01-31 - 微信小程序开发常见问题(三)
一、获取formId 相信使用过小程序的同学,多少都收到过小程序的通过消息,如下: [图片] [图片] 这类通知消息,是和好友消息一样展示在微信的聊天列表中,所以,点击率还是比较高的。想实现这种小程序的模板消息,就必须要获取用户的formid才可以(如何发消息,请仔细查阅小程序官方文档) 我们来说一下如何获取formId: a、必须通过form组件提交才能获取到formId; b、给form组件设置report-submit="true"属性; c、给form组件添加bindsubmit事件绑定,携带 form 中的数据触发 submit 事件,event.detail = {value : {‘name’: ‘value’} , formId: ‘’}; d、必须用户手动触发提交表单,不能JS模拟提交,所以,页面上必须要有提交按钮; 看一下示例代码: <form report-submit=‘true’ bindsubmit=‘userSubmit’> <button class=‘button’ bindtap=‘copy’ form-type=‘submit’>复制</button> </form> 以上示例就可以在userSubmit里获取到formId了: userSubmit: function (e) { console.log(e.detail.formId); }, 需要注意一点,开发工具里面是没办法查看到真实的formId的,会是这样一句提示"the formId is a mock one",提交给服务端就可以拿到了~ 最暴力的方式:整个页面最外层套一个button,点击页面任何地方,都可以获取到formId 二、区分转发的是群聊还是好友 这个其实就是场景值的判断,先看一张图: [图片] 上图可以看出,从好友聊天窗口和群聊窗口点击小程序卡片后,场景值是不一样的,分别是1007和1008,所以,我们可以在app的onLuanch或者onShow方法中去获取到scene值,这样就能知道用户是通过哪种方式进入小程序的~ 之前分享成功,可以获取群ID,这种方式已经被微信官方禁掉了。可以看这篇文章: 三、有哪些开源的小程序框架 wepy wepy是最早推出的一款小程序框架,基于vue进行封装,作者龚澄是腾讯的工程师,早期集累了很多习惯使用vue进行开发的小程序人员。github地址:https://github.com/Tencent/wepy mpvue mpvue也是一个使用 Vue.js 开发小程序的前端框架,美团点评下的一个部门开发的,有胡成全带队开发。github地址:https://github.com/Meituan-Dianping/mpvue taro Taro 是由京东 - 凹凸实验室打造的一套遵循 React 语法规范的多端统一开发框架。统于有一款使用React语法开发小程序的框架了,github地址:https://github.com/NervJS/taro 以上三个框架,都有自己的官方交流群,大家可以添加对应的小助手,然后会自动拉你进群。
2019-01-31 - 小程序开发常见问题(二)
1、wx.setStorageSync和wx.getStorageSync报错问题 为什么说这个问题,是因为这个API确实会报错,并且调用越频繁,报错会越多,先看一下截图: [图片] 所以,怀疑微信官方API也有出错的机率,这里没有根治的办法,只能做一些缓解报错次数的办法:减少调用频次,不要在公用方法里面去频繁调用set和get本地缓存;添加try catch,出错之后,可以再调用一次或多次,减少报错的可能性~ **2、picker下拉列表为什么获取不到长度 ** [图片] 如上图,如果是设置了key的数组,会发现,此时的array的length是0,这可能是小程序的一个bug,length只能自己处理了~ 3、如何获取音频文件的长度 如果调用的是新API,wx.createInnerAudioContext可以直接获取duration;如果是低版本,调用wx.startRecord方法时,只能自己写个计数器来处理duration了。 在部分机型上会有个蛋痛的问题,自己写个定时器,和微信内部录音的时间对不上,当录音600秒(十分钟)时,会相关1~3秒。并且在部分安卓手机上,InnerAudioContext.stop()不会自动调用,需要手动去调用stop 4、如何获取微信群名称? 小程序中是没办法直接获取到微信群名称,只有一种方法获取open-gid,然后再通过open-data组件来显示群名称: <open-data type=“groupName” open-gid=“xxxxxx”></open-data> open-gid的获取方法: 用户把小程序分享到微信群,会在分享成功后返回shareTickets(因为可以分享到多个群,所以这里是一个数组); 如果用户是从群内点击的小程序卡片,会在小程序的app.onshow里面获取了shareTicket。拿到shareTicket后,再到服务端解密,就可以拿到open-gid~ 5、小程序最多支持多少个节点? 小程序可以理解为,被微信包装了一层的H5,页面会有最大节点,建议不要在页面做无限翻页,或者超大数据渲染,这些都可能导致小程序崩溃(如果内存不够时,微信优先杀掉小程序)。 [图片] 节点数过多时,就直接报错了:invokeWebviewMethod 数据传输长度为 1233778 已经超过最大长度 1048576 1048576是个神奇的数字,大家可以自行百度~ 待续…
2019-01-29 - 小程序开发常见问题(一)
1、域名必须是HTTPS 小程序后台配置的域名,有服务器域名、业务域名、消息推送域名、普通二维码域名,前三者必须是HTTPS域名,普通二维码域名可以是HTTP域名 2、input组件placeholder字体颜色 写在placeholder-class里面的color并不生效,需要写在placeholder-style里面就可以了 3、wx.navigateTo跳转不生效? 带有tabbar的页面,必须使用wx.switchTab进行跳转 4、tabbar在切换时页面数据无法刷新 tabbar的实现可能是显示和隐藏view,所以,不会一直调用page.onLoad()方法,可以尝试把代码逻辑写在page.onShow()里面,或者在onTabItemTap方法中处理 5、如何获取shareTickets(可以解密微信群ID) 获取shareTickets需要在app.onLaunch或者app.onShow里面才能获取到,而不是page.onShow,请一定要注意。 注:建议在app.onShow里面去获取,app.onLaunch不是一直会执行 6、getPhoneNumber获取手机号 目前该接口针对非个人开发者,且完成了认证的小程序开放。个人开发者是没办法调用这个API的 7、wx.previewImage图片预览 预览的图片URL必须是HTTPS开头,不能是本地图片 8、wx.playVoice音频播放 必须保证音频文件已经在本地,比如在wx.startRecord后,可以获取到本地临时的tempPath。或者提前调用wx.downloadFile来下载资源文件,然后再播放 9、API老版本兼容 可以用wx.canIUse或者wx.getSystemInfoSync来获取version和SDKversion进行判断,老版本给出相应提示即可 10、获取系统信息 wx.getSystemInfo,可得到系统语言、屏幕宽高、微信版本号、操作系统、设备像素比、客户端甚础库版本等信息 11、如何去掉自定义button灰色的圆角边框 主要是button的伪元素设置了样式,去掉即可: button::after{ display: none;} 12、回到页面顶部 回到页面顶部,有两种方式: 1、使用scroll-view设置为纵向滚动,然后设置scroll-top值; 2、使用wx.pageScrollTo方法,此方法是1.4.0开始支持,所以要做低版本兼容; 13、textarea是APP的原生组件,层级最高 这是个大坑,在有textarea的页面,不要做弹出框设计,建议在输入大段文本时,单独成一个新页面。 14、image组件底部有间隙 image组件默认底部会有间隙,可以设置为块元素(display: block),也可以设置vertical-align: top; 15、一段文字如何换行 小程序中唯一可以实现换行的标签组件是text 注:text中不支持<br>,只能使用\n进行换行 16、设置最外层标签的margin-bottom在IOS下不生效 margin-bottom在安卓和开发工具里面都正常,就是在IOS下不起效,建议改成padding-bottom 17、小程序中canvas的图片不支持base64格式 base64格式图片,在开发工具里面可以正常显示,真机上没有显示。建议修改成带https开头的url形式 18、删除体验版,仍然有缓存? 开发过程中,可能会遇到,删除小程序的体验版,但是缓存依然存在。建议把开发版和线上版都删除,因为小程序缓存是共用的。 19、开发者工具无法复制、粘贴代码 开发者工具中,经常会遇到无法复制、粘贴、搜索代码,有时还会出现,在A文件输入,内部跑到了B文件中,大写的尴尬。遇到这种情况,建议重启微信开发者工作。 20、小程序告警群 小程序后台可以查看所有错误信息,但是,为了方便第一时间了解错误报警,建议使用官方“客户端告警群”,如下图。告警的阀值,可以自己设置。 [图片] [图片] [图片]
2023-12-25 - 微信服务号/小程序提醒消息机制简述
日常的微信H5营销活动/微信小程序中,经常会碰到需要定时通知/提醒用户的场景,本文主要帮你了解各种通知用户的方法和场景. 各类消息功能对比 先上各类消息功能和限制上的对比结果,如下表 [图片] 服务号也有客服消息,但触发条件比较苛刻,需要 1、用户发送信息 2、点击自定义菜单(仅有点击推事件、扫码推事件、扫码推事件且弹出“消息接收中”提示框这3种菜单类型是会触发客服接口的) 3、关注公众号 4、扫描二维码 5、支付成功 6、用户维权 跟平时H5跟小程序的场景不符合,本文暂不讨论服务号的客服消息 服务号发送模板消息流程 1.第一次使用模板消息需要申请模板: 登陆服务号后台,访问功能->模板消息,设置对应的行业,并申请模板,并获得模板id和模板格式 [图片] 2.调用发送接口 [图片] 用户必须关注才能收到服务号模板消息! 用户必须关注才能收到服务号模板消息! 用户必须关注才能收到服务号模板消息! 发送给未关注的用户会得到类似的返回结果: {“errcode”:43004,“errmsg”:“require subscribe hint: [l8Xf.a0132dsz1]”} 详细参数接口请看: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277 3.用户接收消息效果图 会话列表:[图片] 会话内容页: [图片] 发送一次性订阅消息流程 1.获取用户授权 控制页面跳转到https://mp.weixin.qq.com/mp/subscribemsg?action=get_confirm&appid=你的AppId&scene=1000&template_id=1uDxHNXwYQfBmXOfPJcjAS3FynHArD8aWMEFNRGSbCc&redirect_url=跳转回来的链接&reserved=test#wechat_redirect 参数说明: appid:服务号appid scene:场景值id,一般一个活动页面用一个唯一的即可,1-10000的int类型 reserved:防CSRF的参数,随机值就可以,回调地址再校验一次,可选参数 redirect_url:接收回调参数的地址,urlencode,第一次接入必须在设置-公众号设置-功能设置 添加业务域名,如下图 [图片] template_id:在公众号后台获取即可,接口权限->一次性订阅消息->查看模板id,如下图 [图片] [图片] 2.用户跳转到页面后会微信显示如下内容 [图片] 用户确认后,会跳转到上面传过去的redirect_url,会带上以下参数 openid:用户id,用户拒绝没有此参数 template_id:模板id action:用户确认则是confirm,反之为cancel scene:场景值 reserved:防csrf攻击的参数 后端保存openid,template和scene,用于发送数据 3.后端调用接口发送消息 到了需要发送的时候(活动到点提醒/抽奖结果通知等等),后端再调用接口发送: [图片] 可发送小程序或网页url,网页的url无域名限制,用户无需关注也可以发送消息,订阅号也可以发送此类消息. 更多参数以官方文档为准: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1500374289_66bvB 3.发送效果图 已关注服务号的用户会出现在服务号会话里,如下: [图片] 未关注服务号的用户会出现在服务通知: [图片] 小程序发送客服消息流程 由于前期开发者对客服消息的滥用,微信封禁了用户进入会话的事件,现在需要用户从客服会话窗口主动发送消息,服务端收到事件才可以对用户推送消息. 自4月9日起,用户通过客服消息按钮进入会话,该动作不再支持开发者给用户下发客服消息,改由平台统一给用户展示接入提示。开发者仅可在用户主动咨询后进行回复,否则将收到45047的错误码返回:客服接口下行条数超过上限。请尽快进行适配。 1.设置消息推送URL 如果是第一次接入客服消息,需要设置消息推送url 设置->开发设置->消息推送 [图片] 接入的详细文档请看:https://developers.weixin.qq.com/miniprogram/dev/api/custommsg/callback_help.html 2.引导用户发送消息到客服消息 引导用户点击’open-type=“contact”'的button进入客服会话聊天窗口,并让用户发送对应文字 <button class="download" open-type="contact" session-from="wesixpuzzleapp"></button> 如下图,点击后即进入客服会话 [图片] 服务端接收微信的消息推送后,可以下发消息到聊天窗,该消息可以打开任意url和小程序,可拉起应用下载 [图片] 小程序发送模板消息流程 1.一如既往地先申请/添加模板 [图片] 2.引导用户提交form 一般是让用户点击form里面的button,如签到/留言等功能按钮,form标签的report-submit属性必须设置为true,如: <form bindsubmit="formSubmit" report-submit="{{true}}"> <button class="download" formType="submit"></button> </form> (样式也是继续使用上面客服消息的例子) [图片] 3.向后台传递formId 用户点击后,触发bindsubmit的事件,callback的参数会带上formId,传递这个formId到后台 formSubmit: function(e) { wx.request({ url: ‘https://xxxx.xxx/miniapp/push-message’, method: “post”, data: { session_id: app.globalData.sessionId, form_id: e.detail.formId, //formId,重要 }, success(e) { console.log(e) }, }) }, [代码]4.后台发送模板消息 [代码] 注意,formId七天内有效,超过其他就不能再用该formId向用户发送模板消息,该消息只能打开原有的小程序 [图片] 后台的api文档请看这里https://developers.weixin.qq.com/miniprogram/dev/api/notice.html#%E5%8F%91%E9%80%81%E6%A8%A1%E6%9D%BF%E6%B6%88%E6%81%AF 5.用户将在服务通知收到推送的模板消息 效果如下: [图片]
2019-01-28 - 借助公众号快速推广小程序
借助公众号快速推广小程序 小程序开发者,都会有个疑问:我做的小程序该如何推广?流量从何处来,别人都是怎么拉新的? 连胜老师这两天看到官方推出的“生成小程序码”工具,顺便说一下,如何借助公众号提升小程序的用户。 公众号和小程序各有优势,小程序的优点是,触手可及,用完即走。而公众号正好可以沉淀用户,通过公众号推文跟粉丝互动。 生成小程序码 & 获取小程序路径 还不知道“如何获取小程序路径”的同学,可以先看这里:http://kf.qq.com/faq/180725biaAn2180725VnQjYF.html 2018年底(具体日期不详,官方未发公告),微信公众号不用关联小程序,也可以配置公众号菜单和文章内嵌入小程序卡片。竟味着,小程序之前的500个关联限制彻底突破了。 换句话说,如果有一个百万粉丝的公众号,配置了一个自定义菜单,跳转到自家的小程序,那你小程序的用户是不是就多了。所以,越多的公众号菜单或者文章跳到你家小程序,你的小程序用户就会越多。 先来看一下,公众号内如何获取其他小程序的页面路径: 1、创建公众号文章后,有个插入小程序入口 [图片] 2、输入小程序名称,或者选择已经关联的小程序 [图片] 3、小程序路径,可以默认首页,也可以选择任何一个小程序页面,具体看下图 [图片] [图片] 复制出来的页面路径,去掉.html即可正常使用,希望官方后续修复这个bug 建议你写篇公众号文章介绍一下接入步骤,然后把文章通过web-view嵌入到小程序内,提供给需要这样操作的用户。 注:上面的方式,可以找到任何一个小程序的页面路径,有些同学已经有想法了,比如抓取别人数据?此处略过… 上面这种方式,似乎是个福利,能帮助公众号作者快速获取优秀小程序的路径,但实际操作起来,还是太过复杂。下面连胜老师说一种更简洁的方式:一键复制路径。 一键复制小程序路径 毕竟,小白用户还是挺多,很多人都有自己的公众号,但是,他们并不懂技术,上面的方式操作,我相信还是很多公众号作者不会折腾。 最好的方式,把用户当小白,点击一个button,自动复制页面路径。看截图: [图片] 小程序之前是有500个名额限制的,所以,连胜老师还专门做了一个H5版本,功能和小程序一样,支持直接通过公众号菜单配置一个H5的链接。 后续有时间再分享一下普通二维码的玩儿法~ 欢迎讨论技术问题:mianhuabingbei
2019-02-13 - getCurrentPages()的用法
getCurrentPages()的用法 getCurrentPages()是个好东西,今天来说说他的用法。 先看看官方文档: [路由 · 小程序]:https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/route.html [图片] getCurrentPages() 函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。 简单说,就是可以获取到当前小程序的页面栈 那么,获取到页面栈,有什么用处呢? 1、判断页面栈是否超过10级,超过10级,将不能打开新页面(主要是不用用navigateTo方式打开)。 2、可以修改某个页面栈的data数据,或者方法。 这里给大家分享一个实际应用场景:仅发起者可分享。 有些投票、通知、抽奖、签到等,发起者会在私密的圈子内进行,比如,仅会员群才能参与的抽奖、公司内部的通知公告、班级内部的投票等。发起者是不希望别人分享出去,那么小程序里面要怎么做? 说到分享,在小程序内,应该是想到onShareAppMessage这个方法。只要page.js中有这个方法,不管你是否在内部写了代码逻辑,小程序默认就是可以分享的,如果没有这个方法,小程序右上角的“…”就不会出现“转发”的选项。 问题是,是否允许分享,一般都是小程序内的一个开关设置项,可以看下图: [图片] 用户在加载内容时,需要先从服务端获取到这个开关状态,再决定是否出现“转发”的选项。 此时,我们默认不给page添加onShareAppMessage方法,这样,你转发出去的小程序卡片,别人将无法通过长按进行分享(群聊无法长按分享,私聊还是个坑,看下图)。 [图片] [图片] 然后再动态设置当前page的onShareAppMessage方法,用this,或者getCurrentPages()都能解决,看下图: [图片] 目前私聊的卡片,长按依然可以转发,似乎不是很完美,但是,功能基本实现了。 如果想让私聊卡片的转发无效,你也可以变通一下,比如做个限群成员可见功能,即使私聊卡片被转发,可以判断小程序场景值,不展示内容即可~ 1、用wx.getLaunchOptionsSync() 获取小程序启动时的参数: [图片] 2、判断群聊和私聊的场景值: [图片] 3、如果是微信私聊中打开,给用户提示即可~ [图片] 欢迎各位一起讨论技术问题:mianhuabingbei
2019-02-20 - 小程序更改数组或对象中的值
需求 小程序更改数组或对象中的值 实现 废话不多说,直接上代码 1、更改数组中的值 例 goodsList 为 [代码]data{ goodsList : [ {id:'123',title:'goodsA'}, {id:'124',title:'goodsB'} ] } [代码] 使用如下代码改变数组中某一特定的值 [代码]changeTitle(id){ var _this = this var choseTitle= "goodsList[" + id + "].title" _this.setData({ [choseTitle]: 'othertitle' }) } [代码] 2、更改对象中的值 例 userInfo 为 [代码]data{ userInfo: { name: '张三' age:25 } } [代码] 使用如下代码单独更改userInfo对象中name的值 [代码]var _this = this let userName = "userInfo.name" _this.setData({ [userName]: '李四' }) [代码]
2020-11-02 - 小程序自定义tabBar(类似咸鱼)
在App上做类似咸鱼的Tabbar时,只能用自定义的方法,考虑小程序中如果想自定义像咸鱼这样的Tabbar,该如何实现呢?网上搜索的大多资料的tabbar都会在页面切换的时候重新渲染,下面的方法页面跳转时不会闪。 [图片] 效果图 下载地址:https://github.com/dt8888/tabbar 具体实现方法: 1.分装一个tabbar的组件属性列表实现项目的Tabbar的个数,文字,颜色,图片大小最好用官网推荐的81px*81px的icon。 JS关键代码为: [代码]properties[代码][代码]:[代码] [代码]{[代码][代码] [代码][代码]tabbar[代码][代码]:[代码] [代码]{[代码][代码] [代码][代码]type[代码][代码]:[代码] [代码]Object[代码][代码],[代码][代码] [代码][代码]value[代码][代码]:[代码] [代码]{[代码][代码] [代码][代码]"backgroundColor"[代码][代码]:[代码] [代码]"#ffffff"[代码][代码],[代码][代码] [代码][代码]"color"[代码][代码]:[代码] [代码]"#979795"[代码][代码],[代码][代码] [代码][代码]"selectedColor"[代码][代码]:[代码] [代码]"#1c1c1b"[代码][代码],[代码][代码] [代码][代码]"list"[代码][代码]:[代码] [代码][[代码][代码] [代码][代码]{[代码][代码] [代码][代码]"pagePath"[代码][代码]:[代码] [代码]"pages/index/index"[代码][代码],[代码][代码] [代码][代码]"iconPath"[代码][代码]:[代码] [代码]"icon/icon_home.png"[代码][代码],[代码][代码] [代码][代码]"selectedIconPath"[代码][代码]:[代码] [代码]"icon/icon_home_HL.png"[代码][代码],[代码][代码] [代码][代码]"text"[代码][代码]:[代码] [代码]"首页"[代码][代码] [代码][代码]}[代码][代码],[代码][代码] [代码][代码]{[代码][代码] [代码][代码]"pagePath"[代码][代码]:[代码] [代码]"pages/middle/middle"[代码][代码],[代码][代码] [代码][代码]"iconPath"[代码][代码]:[代码] [代码]"icon/icon_release.png"[代码][代码],[代码][代码] [代码][代码]"isSpecial"[代码][代码]:[代码] [代码]true[代码][代码],[代码][代码] [代码][代码]"text"[代码][代码]:[代码] [代码]"发布"[代码][代码] [代码][代码]}[代码][代码],[代码][代码] [代码][代码]{[代码][代码] [代码][代码]"pagePath"[代码][代码]:[代码] [代码]"pages/mine/mine"[代码][代码],[代码][代码] [代码][代码]"iconPath"[代码][代码]:[代码] [代码]"icon/icon_mine.png"[代码][代码],[代码][代码] [代码][代码]"selectedIconPath"[代码][代码]:[代码] [代码]"icon/icon_mine_HL.png"[代码][代码],[代码][代码] [代码][代码]"text"[代码][代码]:[代码] [代码]"我的"[代码][代码] [代码][代码]}[代码][代码] [代码][代码]][代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码]}[代码][代码],[代码] 2.在App.js中的onLaunch方法中 用wx.hideTabBar();隐藏系统自带的tabbar,点击时作为按钮选中的判断方法为: [代码]editTabbar[代码][代码]:[代码] [代码]function [代码][代码]([代码][代码])[代码] [代码]{[代码][代码] [代码][代码]let tabbar [代码][代码]=[代码] [代码]this.globalData.tabBar;[代码][代码] [代码][代码]let currentPages [代码][代码]=[代码] [代码]getCurrentPages[代码][代码]([代码][代码])[代码][代码];[代码][代码] [代码][代码]let _this [代码][代码]=[代码] [代码]currentPages[currentPages.length [代码][代码]-[代码] [代码]1[代码][代码]];[代码][代码] [代码][代码]let pagePath [代码][代码]=[代码] [代码]_this.route;[代码][代码] [代码][代码]if[代码][代码]([代码][代码]pagePath.indexOf[代码][代码]([代码][代码]'[代码][代码]/[代码][代码]'[代码][代码])[代码] [代码]![代码][代码]=[代码] [代码]0[代码][代码])[代码][代码]{[代码][代码] [代码][代码]pagePath [代码][代码]=[代码] [代码]'[代码][代码]/[代码][代码]' [代码][代码]+[代码] [代码]pagePath;[代码][代码] [代码][代码]}[代码][代码] [代码][代码]for[代码] [代码]([代码][代码]let i [代码][代码]in[代码] [代码]tabbar.[代码][代码]list[代码][代码])[代码] [代码]{[代码][代码] [代码][代码]tabbar.[代码][代码]list[代码][代码][i].selected [代码][代码]=[代码] [代码]false[代码][代码];[代码][代码] [代码][代码]([代码][代码]tabbar.[代码][代码]list[代码][代码][i].pagePath [代码][代码]=[代码][代码]=[代码] [代码]pagePath[代码][代码])[代码] [代码]&[代码][代码]&[代码] [代码]([代码][代码]tabbar.[代码][代码]list[代码][代码][i].selected [代码][代码]=[代码] [代码]true[代码][代码])[代码][代码];[代码][代码] [代码][代码]}[代码][代码] [代码][代码]_this.setData[代码][代码]([代码][代码]{[代码][代码] [代码][代码]tabbar[代码][代码]:[代码] [代码]tabbar[代码][代码] [代码][代码]}[代码][代码])[代码][代码];[代码][代码] [代码][代码]}[代码][代码],[代码] 如何引用该项目实现自己的自定义Tabbar: 1.找到项目中的tabbarComponent目录,放到自己的工程中,然后将tabbarComponent->icon图标替换成你自己的tabbar图片,文字颜色根据需求做适当的更改。 2.app.json中配置tabBar,因为点击发布时做的页面跳转,不配置在tabBar的list中。 3.在app.js中的globalData中加入自定义tabbar的参数,再加入一个方法给tabBar.list配置中的页面使用。 4.在页面的JS中的data中加入tabbar:{},并在onload方法中调用app.editTabbar(); 5.页面的.json文件中加入代码 [代码]"usingComponents"[代码][代码]:[代码] [代码]{[代码][代码]"tabbar"[代码][代码]:[代码] [代码]"../../tabbarComponent/tabbar"[代码][代码]}[代码] 6.在页面的.wxml文件中加入<tabbar tabbar="{{tabbar}}"></tabbar> 作者: honey缘木鱼
2019-01-10 - 【优化】解决swiper渲染很多图片时的卡顿
相信各位在开发的时候应该有遇到这样一个场景,比如商品的图片浏览,有时图片的浏览会很大,多的时候达几百张或上千张,这样就需要swiper里需要很多swiper-item,如此一来渲染的时候就会很消耗性能,渲染时会有一大段的空白时间,有时还会造成卡顿,体验非常差,下面给大家介绍一下我的解决方案。 首先是wxml结构: [图片] js: [图片] [图片] 主要是利用current属性,swiper里面只放3个swiper-item,要显示的图片放在第二,第一和第三放的是加载的动画背景,步骤如下: 1. 将请求到的数据存入一个数组picListAll内,这里不需要setData,只需要在data外面定义一个变量就行了,以减少渲染性能。 2. 把要显示的图片路径赋值给picUrl, 3. 切换的时候根据bindchange获取current属性,当current改变时判断当前图片在picListAll的index,根据index拿到图片再赋值给picUrl 主要实现步骤就是以上3 步,比较简单,要注意的是当切换到第一张和最后一张的时候要判断一下,把loding动画去掉,请求的时候还可以传入index参数以显示不同的图片,方便从前一页点击图片进入到此页面时能定位到该图片,例子里我是自己mock数据的,只是为了展示,如果你有服务器的话可以弄几百张看看效果,对比直接渲染和用以上方式渲染的差异。当然,这只是我的解决方案,如果各位有更好的方案欢迎一起讨论,一起进步. 系甘先,得闲饮茶 完整代码:https://github.com/HaveYuan/swiper
2019-01-25 - 【技巧】swiper仿tab切换
大家好,上次给大家分享了swiper多图片的解决方案:https://developers.weixin.qq.com/community/develop/doc/000068ff25ccf0bae4e76eab156c04 今天再给大家分享一个关于swiper的小技巧,利用swiper仿tab切换。 相信大家在app或浏览器上阅读新闻时,比如今日头条,会有这样一个场景,左右滑动的时候可以切换不同栏目,体验非常好,但是小程序好像没有提供相关组件,如果想实现这种效果该怎么做呢今天就给大家介绍一下在小程序里是怎么实现的。 首先先看下效果 [图片] 实现原理很简单,利用小程序swiper再配合scroll-view就能实现,不过这里面有几点需要注意一下: 1.scroll-view一定要给一个高度,不然会有问题; 2.切换的时候只显示当前的swiper-item里的内容,其它swiper-item里的内容可以先隐藏掉,这是因为如果你的swiper-item里的图片太多的话可能会造成页面回收,因为新闻列表大多是图文列表,而tab经常是不止两个的,可能是7、8个或更多,如果每个tab都显示的话到时上拉加载页面会非常庞大,所以这里我建议不用显示的内容先隐藏,记住是swiper-item里的内容不是swiper-item,到时切换回来时再重新渲染,如果你要保存滚动的位置还要做其它的一些处理,这里就不仔细讲解了; 3.这里适用的是整个页面都是tab切换的,如果只是在页面的某处实现tab切换,还要考虑高度的问题,加载数据的时候根据数据个数长度来计算高度,每次加载数据都要计算高度,切换到不同的tab也是,这部分比较麻烦,因为要计算,不过并不难,只要 计算正确的话是没有问题的; 大概就是这样,基本实现思路,大家可以根据这个思路去拓展,在上面加上自己的功能,over! 代码片段:https://developers.weixin.qq.com/s/89OO1smX736d 系甘先,得闲饮茶
2019-02-26 - 社区经验分享与问题总结
1. 插件开发中,使用自定义组件需用到相对路径 "usingComponents":{ "alert":"../../components/alert/alert" } 注:选择自定义组件需要加上in(this) wx.createSelectorQuery().in(this).select('.xxxxx') 2. 获取小程序码相关 通过接口生成的小程序码page必须是已经发布的 生成小程序码后可在客户端开发工具中,通过二维码编译进行测试scene参数 [图片] 3. swiper组件current不重置问题(bug) 如:通过arr=[1,2]遍历swiper-item组件,当swiper滑动到current=1时,setData({arr:[2]});此时swiper会出现空白。 原因:current未重置为0,需自己去设置current。 https://developers.weixin.qq.com/community/develop/doc/00066c8beacfa05f24d7d144056800 4. 模板消息相关 时效性:1次支付可下发3条,1次提交表单可下发1条。(7天内有效) 对应性:发送消息的对象openId和formId是匹配的。 获取方式:发起支付或表单提交 5. 分包加载大小限制问题:使用分包加载时,如果在分包中使用插件,插件大小只会算在分包大小2MB与整包8MB内,不算入主包2MB。(之前算在了主包内,目前已修复)。 6. 获取unionId(包括openId)流程(前提:小程序 或 其主体公众号 与 微信开发平台账号关联) 开放平台关联同主体的公众号且 用户已经关注公众号: wx.login()=> 获取到code,后端通过appid+appserect+code,拿到openId+ session_key+unionId 开放平台关联小程序: 用户授权后通过wx. getUserInfo(需要授权)获取iv、encryptedData,然后解密(需要用到上面的 session_key),appid+ session_key + encryptedData + iv解密 得到unionId、openId及用户信息 [图片] 注:如果再次获取code会导致之前的session_key过期 7. h5与小程序跳转问题 公众号=>小程序:公众号自定义菜单可配置跳转到小程序 小程序=>h5:webview(需配置业务域名、webview不支持个人账号) 注:目前不支持h5与小程序的直接跳转 8. canvas原生组件覆盖自定义弹层的解决方案 用css样式控制器显示或隐藏,如hidden 纯显示性的canvas可以生成图片之后展示 9. 自定义弹层背景滚动问题 方法一:打开的函数中,如果自定义弹框当前显示,则isScroll设为true,否则设为false <scroll-viewclass="scanInvoice_content" height="100%"scroll-y="{{isScroll}}"> //设置Page的overflow-y属性值为hidden </scroll-view> 方法二:事件捕获,顶层加上catchtouchmove 10. 小程序图片分享截取变形或显示不全:保持分享的图片是5:4。 11. 授权问题 wx.authorize可以对除scope.userinfo之外的权限进行授权,scope.userinfo需要用<button open-type="getUserInfo"/>组件进行授权。 统一小程序下的用户拒绝授权之后会直接进入失败回调,这种情况可使用wx.openSetting引导用户授权。(用户手动删除小程序才会重新提示授权) 12. 数据绑定是双括号内只能是data里面的变量或者wxs里声明的函数。 13.不要用wx.request去访问微信接口,官方限制且不能把[代码]api.weixin.qq.com[代码]配置为服务器域名 基础库版本:v2.4.0 欢迎更新指正
2018-12-28 - 组件加载外部类externalClasses和addGlobalClass到底是
组件引用外部类,文档是这么说的: https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html 可是externalClasses和addGlobalClass到底是什么逻辑 官方能否在文档详细说明一下 经过NNNNNN验证,发现这2个是互斥的,同一个类不能同时使用2种方法 addGlobalClass引用外部类的时候, 为什么有的时候起作用,有时候有不可以呢(需要在类前面写上'view.'才能生效) 头大头大啦~~~~~~~~~~~
2018-12-27 - 访问本地虚拟机图片无法加载问题
- 前台请求虚拟主机数据库里的图片路径报错了 一直显示 Failed to load local image resource /pages/index/www.miaodan.com/static/1.jpg the server responded with a status of 404 (HTTP/1.1 404 Not Found) 我后来试了一下单独使用image的src请求图片地址也是报错 www.miaodan.com 是虚拟主机 后台用的是tp框架 图片的路径是tp框架里的static目录下 - 希望提供的能力 如何解决请求虚拟主机的图片 ps:不把图片作为小程序前端代码静态使用[图片]
2018-12-27 - 自定义组件(插件)的多层嵌套引用,怎么传递外部class
高手留步,说说你们是怎么传递外部class的 小弟的做法是按文档的外部样式类 (https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html) [代码][代码][代码]< custom-component class="my-class" > 这段文本的颜色由组件外的 class 决定 </ custom-component >[代码] [代码] [代码] [代码]==> [代码][代码]my-[代码]class="my-class"[代码] 循环指向[代码] [代码] [代码] [代码]有没有外部直达的方法 [代码] 求官方指导!
2018-12-26 - 小程序-canvas绘制文字实现自动换行
[代码] [代码][代码]const context = wx.createCanvasContext([代码][代码]'myCanvas'[代码][代码])[代码] [代码] [代码][代码][代码][代码]//这是要绘制的文本[代码] [代码] [代码][代码]var[代码] [代码]text = [代码][代码]'这是一段文字用于文本自动换行文本长度自行设置欢迎大家指出缺陷'[代码][代码];[代码] [代码] [代码][代码]var[代码] [代码]chr =text.split([代码][代码]""[代码][代码]);[代码][代码]//这个方法是将一个字符串分割成字符串数组[代码] [代码] [代码][代码]var[代码] [代码]temp = [代码][代码]""[代码][代码];[代码] [代码] [代码][代码]var[代码] [代码]row = [];[代码] [代码] [代码][代码]context.setFontSize(18)[代码] [代码] [代码][代码]context.setFillStyle([代码][代码]"#000"[代码][代码])[代码] [代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]a = 0; a < chr.length; a++) {[代码] [代码] [代码][代码]if[代码] [代码](context.measureText(temp).width < 250) {[代码] [代码] [代码][代码]temp += chr[a];[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]else[代码] [代码]{[代码] [代码] [代码][代码]a--; [代码][代码]//这里添加了a-- 是为了防止字符丢失,效果图中有对比[代码] [代码] [代码][代码]row.push(temp);[代码] [代码] [代码][代码]temp = [代码][代码]""[代码][代码];[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]row.push(temp); [代码] [代码][代码] [代码] [代码][代码]//如果数组长度大于2 则截取前两个[代码] [代码] [代码][代码]if[代码] [代码](row.length > 2) {[代码] [代码] [代码][代码]var[代码] [代码]rowCut = row.slice(0, 2);[代码] [代码] [代码][代码]var[代码] [代码]rowPart = rowCut[1];[代码] [代码] [代码][代码]var[代码] [代码]test = [代码][代码]""[代码][代码];[代码] [代码] [代码][代码]var[代码] [代码]empty = [];[代码] [代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]a = 0; a < rowPart.length; a++) {[代码] [代码] [代码][代码]if[代码] [代码](context.measureText(test).width < 220) {[代码] [代码] [代码][代码]test += rowPart[a];[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]else[代码] [代码]{[代码] [代码] [代码][代码]break[代码][代码];[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]empty.push(test);[代码] [代码] [代码][代码]var[代码] [代码]group = empty[0] + [代码][代码]"..."[代码][代码]//这里只显示两行,超出的用...表示[代码] [代码] [代码][代码]rowCut.splice(1, 1, group);[代码] [代码] [代码][代码]row = rowCut;[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]b = 0; b < row.length; b++) {[代码] [代码] [代码][代码]context.fillText(row[b], 10, 30 + b * 30, 300);[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]context.draw()[代码]
2018-12-25 - 好的经验要分享:chooseImage转base64
好的经验必须要分享:chooseImage后转base64 现在网上各种帖子的解决方案存在各种各样的问题,不说了,直接贴代码,手机亲测,没有问题 const fileManager = wx.getFileSystemManager(); [图片]
2018-08-31 - 适配刘海屏和全面屏的一些小心得
今年开始各路刘海和全面屏手势的手机已经开始霸占市场,全面屏和刘海屏的适配也必须提上日程。 相信大家也一定会有第一次将未适配的小程序放到全面屏或刘海屏手机上的尴尬体验。 尤其是在导航栏设置为custom时,标题与胶囊对不齐简直逼死强迫症。。 微信官方也没有出一个官方的指导贴帮助开发者。 这里仅总结一下个人关于这个问题的一些处理方式,如有疏漏烦请指正补充。 适配的关键在两个位置即额头和下巴,头不用说自然是关于刘海的。 小程序的头的高度主要分为2个部分 1.statusBarHeight 该值可以在app onLaunch 调用wx.getSystemInfoSync() 获取到 a)刘海 高度44 [图片] b)无刘海 ios高度20 安卓各不相同 [图片] 2.胶囊高度 即下图高度 [图片] 在查阅社区问答后了解到小程序给到的策略是ios在模拟器下统一是44px,ios在真机下统一是40px(感谢指正@bug之所措 ),而安卓下统一是48px,因此我们又可以在wx.getSystemInfoSync() 中获取到系统之后得到胶囊高度。 总的导航栏高度即这两个高度之合。本人项目中是将导航做成组件并给到slot,方便各个页面配置。 开发者工具 1.02.1810190 及以上版本支持在 app.json 中声明 usingComponents 字段,在此处声明的自定义组件视为全局自定义组件,在小程序内的页面或自定义组件中可以直接使用而无需再声明。 目前小程序还支持在单个页面配置custom,也可以配合使用~ 另一个需要关注的则是底部,参考的文章是 https://www.jianshu.com/p/a1e8c7cf8821 重点是在于在全面屏的手机的底部需要流出34px的空白给到全面屏返回手势操作,此外由于全面屏屏幕圆边还可能使一些按钮或功能无法正常使用。 那么首先如何判断是否是全面屏呢?个人的做法是判断屏幕高度是否大于750,iphone的plus系列高度在736,正好在这个范围之内,当然750不一定准确,如果出现疏漏烦请补充。 涉及到底部的主要是弹出的操作菜单、tabBar和底部定位的按钮等。这里做了一个简单的代码片段。 https://developers.weixin.qq.com/s/fnU0n8mv7o5M 希望能够帮助到大家,也欢迎交流~
2019-01-03 - 越过山丘-微盟小程序开发问题的解决之道
本文整理了微盟小程序开发过程中遇到的一些问题,快看看你遇到的问题这里有没有答案吧! 问题一: iOS时间转换问题怎么解决?pc端调试、安卓机都能正常执行,iOS上time的结果是null var resData = '2017-3-14 10:03:45' console.log("返回时间:" + resData) var time = Date.parse(new Date(resData)) ; console.log(time); 解决方案: 将 '-' 替换成 '/' resData = resData.replace(/-/g, '/'); 问题二: 直播过程中可能会开始、暂停、继续、结束,客户端需要相应的做暂停和播放、结束,我们是希望通过消息的发送来通知到客户端,但是腾讯云IM的API调接口发群消息,限制 100次/秒,满足不了我们的需求 解决方案: 两点。 1、客户端轮询我们的接口,查询播放状态,舍弃。因为客户端数量过大时,服务端扛不住。 2、操作管理后台界面做轮询,一旦暂停,发送消息。客户端接口通过特定code跟普通消息做区分。 问题三: storage不可以共用,小程序和插件之间数据共享有问题怎么解决呢? 解决方案: 插件使用小程序的共用方法(该方法由插件初始化时从构造函数传入),可以通过此方法从小程序发起请求、存储数据等。 问题四: 小程序的10层跳转限制怎么破? 解决方案: 判断一下当前页面长度是否达到10级,达到10级用redirectTo,未达到用navigateTo var jumpUrl = function (data) { let pages = getCurrentPages(); if (pages.length >= 10) { wx.redirectTo({ url: data }) } else { wx.navigateTo({ url: data }) } } 问题五: 浮点运算的精度问题一般怎么解决? 解决方案: 先转成整数,再进行运算,最后进行除法运算。 列举一个减法运算: function accSub(arg1, arg2) { var r1, r2, m, n; try { r1 = arg1.toString().split(".")[1].length; } catch (e) { r1 = 0; } try { r2 = arg2.toString().split(".")[1].length; } catch (e) { r2 = 0; } m = Math.pow(10, Math.max(r1, r2)); //动态控制精度长度 n = (r1 >= r2) ? r1 : r2; return ((arg1 * m - arg2 * m) / m).toFixed(n); } 问题六: 小程序二维码scene的长度限制最多32位可见字符,这个有什么妙招吗 解决方案: 1、中间页 + 短参数 新建一个中间空白跳转页面,每次生成的二维码都是这个页面, 访问这个页面时,将参数中的scene的值,去指定接口获取完整的 带参数的链接, 然后跳转过去,适用于一个解决方案中有很多个页面需要生成二维码来跳转; 2、短参数 二维码指向到特定页面,scene值为短参数,进入页面时请求接口获取完整的参数(json格式); 问题七: 还有一个图片预加载问题,有些活动图片太多太大,且不确定使用哪张图片,接口返回数据时即刻显示图片可能出现短暂空白的问题 解决方案: 分三步,你需要建立一个图片预加载器 1、js /** * 图片预加载组件 */ class ImgLoader { /** * 初始化方法,在页面的 onLoad 方法中调用,传入 Page 对象及图片加载完成的默认回调 */ constructor(pageContext, defaultCallback) { this.page = pageContext this.defaultCallback = defaultCallback || function () { } this.callbacks = {} this.imgInfo = {} this.page.data.imgLoadList = [] //下载队列 this.page._imgOnLoad = this._imgOnLoad.bind(this) this.page._imgOnLoadError= this._imgOnLoadError.bind(this) } /** * 加载图片 * * @param {String} src 图片地址 * @param {Function} callback 加载完成后的回调(可选),第一个参数个错误信息,第二个为图片信息 */ load(src, callback) { if (!src) return; let list = this.page.data.imgLoadList, imgInfo = this.imgInfo[src] if (callback) this.callbacks[src] = callback //已经加载成功过的,直接回调 if (imgInfo) { this._runCallback(null, { src: src, width: imgInfo.width, height: imgInfo.height }) //新的未在下载队列中的 } else if (list.indexOf(src) == -1) { list.push(src) this.page.setData({ 'imgLoadList': list }) } } _imgOnLoad(ev) { let src = ev.currentTarget.dataset.src, width = ev.detail.width, height = ev.detail.height //记录已下载图片的尺寸信息 this.imgInfo[src] = { width, height } this._removeFromLoadList(src) this._runCallback(null, { src, width, height }) } _imgOnLoadError(ev) { let src = ev.currentTarget.dataset.src this._removeFromLoadList(src) this._runCallback('Loading failed', { src }) } //将图片从下载队列中移除 _removeFromLoadList(src) { let list = this.page.data.imgLoadList list.splice(list.indexOf(src), 1) this.page.setData({ 'imgLoadList': list }) } //执行回调 _runCallback(err, data) { let callback = this.callbacks[data.src] || this.defaultCallback callback(err, data) delete this.callbacks[data.src] } } module.exports = ImgLoader 2、wxml <template name="img-loader"> <image mode="aspectFill" wx:for="{{ imgLoadList }}" wx:key="*this" src="{{ item }}" data-src="{{ item }}" bindload="_imgOnLoad" binderror="_imgOnLoadError" style="width:0;height:0;opacity:0" /> </template> 3、使用 let images = [ 'http://cdn.weimob.com/saas/activity/bargain/images/arms/shoulie.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/shandian.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/fengbao.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/yingren.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/leiming.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/caidao.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/liehen.png' ] //初始化图片预加载组件,并指定统一的加载完成回调 this.imgLoader = new ImgLoader(this, null); images.forEach(item => { this.imgLoader.load(item) }) 问题八: 服务端时间和客户端时间问题,实际项目中有很多倒计时存在,服务端和客户端时间不对应会引起错乱。 解决方案: 将服务器时间通过接口下发给客户端,计算出二者的差值,计算倒计时的时候,可以将客户端时间加上这个差值来,再跟开始时间或结束时间计算倒计时。 如果你还有什么问题,欢迎在下方留言,我们会尽力为你解答。
2018-10-09 - 云开发中用request的问题
[图片] 求助,我想在云函数中获取到access_token,代码如上图,然后云开发控制台没有打印任何东西(如下图) [图片] 求教,是我写法问题还是不能这样用QAQ
2018-12-11 - 叠式轮播图
开发工具和iOS测过,android我没测过。。哈哈哈哈哈 https://developers.weixin.qq.com/s/kh8HhjmA7A4D 注释不知道写啥,简单描述了下 [图片]
2018-11-30 - 微信朋友圈选图效果
中秋快乐,帮社区里的小伙伴写的选图效果,仿造的微信朋友圈选图,长按拖动排序主要是用交互监听来实现的。还有几个需要处理的地方。另外在真机上的动画速度总感觉快那么一丢丢。没做预览图,详情见代码片段咯。安卓机效果没试过。。 今天是9月23,这两天接受定制改动~ 第一次发经验分享。。轻点喷。。 哦,对了,这里 wx.createSelectorQuery 有个bug。。如果选择的基础库版本在2.0.9以上,第一次打开开发者工具,要先直接点一次编译,不然会出错。。真机上没问题 代码片段:wechatide://minicode/RFLmzDmL7I2a 有小伙伴反馈,在片段上面增加内容会乱位,是因为没计算顶部偏移量,方便懒人,完善了下 新的片段:wechatide://minicode/fOTEM3mI7l3B 终于拿到安卓机器试了,果然卡得很。。要控制频率。这很尴尬,太频繁的话,安卓会卡,太不频繁呢,刚开始移动那阵子会卡。。有同学反馈,说点快了会出问题,于是又加了点击频率的限制 现在的代码片段:wechatide://minicode/xBDdCom17R3c(要重新拿安卓机测试了再看看。。) 附上gif: [图片]
2018-10-24 - 如何获取大量的formId,求助大神解答!!
如题所问,因为业务需要大量的推送消息,所以要获取大量的formId, 因为通过事件冒泡来实现获取formId改动很大,所以打算做个button的遮罩层组件来获取formId, 1.通过冒泡事件实现 <form> <button> <页面数据></页面数据> </button> </form> 2.<页面数据></页面数据> <form> <button></button> </form> button的样式需要要写成隐藏遮罩一样 通过冒泡事件来实现获取formId可行,但是代码改动麻烦,所以想通过第二种做个button组件来实现, 请问大神们如何实现点击事件穿透???新人求助
2018-07-03 - 图片设置圆角会先加载有圆角的图片,闪一下会变成直角 然后再闪一下变成圆角
拜托管理员不要让我提供可以复现问题的代码片段好吗 = = 直接自己弄个图片加个border-radius属性 或者 给图片的父元素加个border-radius 这些我都试过了 不行 这个问题一直都有 网上好多人这个问题都已经问烂了 希望可以给个解决方案好吗 跪求!~ ┭┮﹏┭┮
2018-08-21 - 小程序在wifi下能打开,4G网络却不行【已解决】
不知道各位朋友有遇到这样情况没,上线的小程序,在WIFI网络下能够正常访问,4G网络却不行? 其实问题很简单,域名解析的问题!! 不能用 CNAME 类型解析, 必须要用 A 类型 IP地址解析。 重新配置一下就可以了
2018-11-06