- map组件自定义气泡,带有cover-image,在ios上卡顿?
自定义的点位,使用了cover-image有图片,在ios上一次加载超过10个点位,map组件会卡着不动,等加载完成才能滑动;安卓没有这个问题。 是bug还是什么问题?有什么方法解决吗? 【哭死】 [图片]
2024-02-22 - 小程序性能优化实践
小程序性能优化课程基于实际开发场景,由资深开发者分享小程序性能优化的各项能力及应用实践,提升小程序性能表现,满足用户体验。
2024-10-09 - 调用wx.openPrivacyContract打开的页面为空白页面
调用wx.openPrivacyContract打开的页面无内容,为空白页,跳转后的链接如下 https://mp.weixin.qq.com/wxawap/waprivacyinfo?action=show&appid=wx91afd0234a369160#wechat_redirect
2023-08-16 - 小程序代码审核新手入门篇
微信小程序在注册完成,需提交代码审核通过才能顺利发布,小程序从开发到发布的具体流程:微信小程序完成注册、信息设置、类目设置后,代码提审从开发到发布一般要经过:预览-> 上传代码 -> 提交审核 -> 发布等步骤,以下为大家介绍注册到发布前必经流程及代码审核注意事项: 整体流程 [图片] 一、板块分点 信息设置:昵称(简称)、简介、头像设置 类目设置:小程序服务类目申请 代码审核:流程步骤 小程序功能:小程序的功能可用性、内容完整性等进行整体审核 二、板块审核注意事项 1、信息设置 ①昵称板块: 不直接使用广义归纳类、营销词、热门关键词、产品分类词汇命名 未经授权不得擅自使用他人已注册企业名称、商标、他人姓名命名 详情可参考:https://developers.weixin.qq.com/community/operate/doc/00060288824708b8d588e4ae25bc01 ②简介: 明确介绍小程序的功能点、表达的意思需与实际提供的功能一致 ③头像(logo):选用清晰度高,表达内容应与小程序名称、简介、功能相符,且未经授权不得使用腾讯、微信官方或其他官方头像 2、类目设置 1、①自身运营功能与类目保持一致性(提供社交属性服务,应选择社交类目;提供时政信息服务,应选择时政信息类目) 2e ②注册主体不一样,对应开放类目范围不一样,非个人主体、个人主体、境外主体开放详细可参考: https://developers.weixin.qq.com/miniprogram/product/material/ 3、③当所选类目当涉及需报备送属地网信办审核,建议至少上线前14天进行提交审核,避免因二次审核流程过长,延误项目上线时间,报备类目详情可参考: https://developers.weixin.qq.com/community/operate/doc/00002a6a0b8d98a965993666a51001?blockType=5 3、小程序从开发到发布的具体流程: 微信小程序完成注册、信息设置、类目设置后,从开发到发布一般要经过 预览-> 上传代码 -> 提交审核 -> 发布等步骤,以下为大家介绍代码提审流程的注意事项: 3.1预览 使用开发者工具可以预览小程序,帮助开发者检查小程序在移动客户端上的真实表现。 点击开发者工具顶部操作栏的预览按钮,开发者工具会自动打包当前项目,并上传小程序代码至微信的服务器,成功之后会在界面上显示一个二维码。使用当前小程序开发者的微信扫码即可看到小程序在手机客户端上的真实表现。 [图片] 3.2上传代码 同预览不同,上传代码是用于提交体验或者审核使用的。 点击开发者工具顶部操作栏的上传按钮,填写版本号以及项目备注,需要注意的是,这里版本号以及项目备注是为了方便管理员检查版本使用的,开发者可以根据自己的实际要求来填写这两个字段。 [图片] [图片] 上传成功之后,【登录小程序管理后台 - 版本管理 - 开发版本】就可以找到刚提交上传的版本了,可以将这个版本设置体验版或者是提交审核。 [图片] [图片] 3.3 提交审核 为了保证小程序的质量,以及符合相关的规范,小程序的发布是需要经过审核的。 在开发者工具中上传了小程序代码之后,【登录小程序管理后台 - 版本管理 - 开发版本】找到提交上传的版本。在开发版本的列表中,点击 提交审核 按照页面提示,填写相关的信息,即可以将小程序提交审核。 需要注意的是,请开发者严格测试了版本之后,再提交审核,过多的审核不通过,可能会影响后续的审核时长。 [图片] [图片] [图片] [图片] 3.4 发布审核通过之后,管理员的微信中会收到小程序通过审核的通知,此时可以通过电脑端和手机端两种方式来查看审核通知: ①电脑端:【登录小程序管理后台 -通知中心】中可以看到具体的审核结果 [图片] ②手机端:模板消息代码发布审核结果 [图片] 审核通过后,可以点击发布,即可发布小程序。小程序提供了两种发布模式:全量发布和分阶段发布。全量发布是指当点击发布之后,所有用户访问小程序时都会使用当前最新的发布版本。分阶段发布是指分不同时间段来控制部分用户使用最新的发布版本,分阶段发布我们也称为灰度发布。一般来说,普通小程序发布时采用全量发布即可,当小程序承载的功能越来越多,使用的用户数越来越多时,采用分阶段发布是一个非常好的控制风险的办法。 [图片] 4、小程序功能 ①可用性 a、功能运行稳定:确保功能可打开、运行、无报错 b、体验有登录限制:应在提审之前上传有效测试信息、运行录屏,用于辅助审核判断 详情可参考:https://developers.weixin.qq.com/community/develop/doc/0002caeb3c82583722f9cb09456409 ②登录规范合规性 a、特定范围:首页应明确文案提示,服务仅为特定人群使用 b、公开范围:授权个人信息功能后置,给予用户事前了解功能后,由用户主动使用功能时,再进一步进行授权提醒 详情可参考:https://developers.weixin.qq.com/community/operate/doc/000640bb8441b82900e89f48351401 ③小程序内容完整性:提审前确保运营内容的完整性,避免出现如下无法判断的运营内容导致审核失败 ,具体如下: 【测试商品】示例: [图片] 【功能无具体运营内容】示例: [图片] 本文档为初次提交代码审核的开发者了解提审前必经流程、提审注意事项,如存在上述问题应及时调整、修正,同时提前规划好提审时间,避免后续因存在上述问题审核失败或重复提审导致无法如期上线发布。
06-27 - 地图组件map报欠费,没有使用个性化,引用map就要收费?
<map></map> 这样也报欠费
2023-06-29 - 地图组件使用的时候,显示[Component]<Map>欠费,这个是什么原因?
组件名称:Map、微信版本号:Version 8.0.38 基础库版本号:2.32.3[图片],使用地图组件的时候,提示[Component]<map>收费,但是使用的是小程序内置的腾讯地图,不是第三方的地图,也没有说明欠了多少,去哪里充值。
2023-06-30 - 小程序图片安全审核方案与security.imgSecCheck不能校验超过1M图片的解决思路
背景需求:我个人做了一款校友交流的小程序。里面有校友相册,校友聚会,校友资讯,校友互助等功能,校友从本地相册选一张或者多张图片后,连同文章内容保存到服务器。这里就涉及到内容安全了,提交审核没有通过也是因为这个没有做内容安全。防止一些色情低俗的事情发生。 [图片] 当前可用的图片审核方案的优缺点:方案1 :小程序自带的同步同步API:security.imgSecCheck优点:小程序自带,无须额外申请,格式支持PNG、JPEG、JPG、GIF,单个 appId 调用上限为 2000 次/分钟,200,000 次/天,基本满足需要 缺点:图片大小限制1M ,图片尺寸不超过 750px x 1334px 虽然有图片尺寸限制,但是在实际应用中发现,尺寸到4000 x 4000都可以,但是4000以上会出现问题 方案2 :小程序自带的异步API: security.mediaCheckAsync优点:单个文件大小不超过10M,解决手机拍照或者相册里的大图片限制 缺点:属于异步调用,必须先上传图片,处理不及时,最多可能30分钟才返回结果,需要提供回调URL来接受处理结果,不健康的图片无法实时处理,容易造成风险 方案3: 小程序服务市场的珊瑚图片安全优点:非常好用,但是已经下架(2021年4月9日),无比郁闷 [图片] 方案4: 小程序服务市场的天御才上线没多久,按官方给出的文档无法正常调用成功,而且只有30天的免费使用时长 [图片] 方案5 :腾讯云:T-Sec 天御 图片内容安全 优点:能精准识别图片中出现可能令人反感、不安全或不适宜内容,支持配置图片黑名单,识别自定义的图片类型。 缺点:文档调用复杂,价格高昂 [图片] 方案6 :百度云:内容审核平台 优点:基于深度学习的智能内容审核方案,准确过滤图像和视频中的色情、暴恐、政治敏感、广告、恶心、不良场景等违规内容,也能从美观、 清晰等维度对图像进行筛选,紧贴业务需求,释放审核人力 缺点:1)还是价格!!!2)接入成本 我的解决思路综合上述方案优缺点,我还是选择方案1(security.imgSecCheck),原因如下 1 小程序自带的API,调用方便,尤其是云函数调用方便 2 没有额外的申请流程 3 没有费用开销 4 量度次数上限基本满足我的校友录小程序需求 security.imgSecCheck 亟待解决的问题:1. 图片大小限制1M 2. 图片尺寸限制的问题(官方表述750px x 1334px) 我的解决方案虽然官方文档有些图片尺寸的上限,这也是当时我犹豫不决的原因, 但是在实际应用中发现,尺寸到4000 x 4000都可以实现检测,但是4000px以上会出现问题, 实际图片中出现上述大尺寸的少,所以可以暂且忽略, 只需要针对图片大于1M的进行压缩处理 大于1M的图片 思路:校友会小程序相册选图片 --> 判断图片是否大于1M --> 压缩图片 --> 上传图片流给security.imgSecCheck检测-->通过-->保存 小于1M的图片 思路:校友会小程序相册选图片 --> 判断图片是否大于1M --> 不压缩图片 --> 上传图片流给security.imgSecCheck检测-->通过-->保存 [图片] 代码实现:小程序端/** * 选择上传图片 */ bindChooseImgTap: function (e) { wx.chooseImage({ count: this.data.imgMax - this.data.imgList.length, //默认9 sizeType: ['compressed'], //可以指定是原图还是压缩图,默认二者都有 sourceType: ['album', 'camera'], //从相册选择 success: async (res) => { wx.showLoading({ title: '图片校验中', mask: true }); for (let k = 0; k < res.tempFiles.length; k++) { let size = res.tempFiles[k].size; let path = res.tempFiles[k].path; if (!contentCheckHelper.imgTypeCheck(path)) { wx.hideLoading(); return pageHelper.showNoneToast('只能上传png、jpg、jpeg格式', 3000); } let imageMaxSize = 1024 * 1000 * this.data.imgUploadSize; console.log('IMG SIZE=' + size + ',' + size / 1024 + 'K'); if (!contentCheckHelper.imgSizeCheck(size, imageMaxSize)) { wx.hideLoading(); return pageHelper.showNoneToast('单张图片大小不能超过 ' + this.data.imgUploadSize + 'M', 3000); } // 图片校验 if (this.data.isCheck) { let that = this; let callback = async function (path) { let check = await contentCheckHelper.imgCheckCloud(path); if (!check) { wx.hideLoading(); return false; //return pageHelper.showNoneToast('存在不合适的图片, 已屏蔽', 3000); } that.setData({ imgList: that.data.imgList.concat(path) }); that.triggerEvent('myImgUploadEvent', that.data.imgList); return true; } // 图片大于1M,压缩 if (size > 1000 * 1000) { wx.compressImage({ src: path, // 图片路径 quality: 50, // 压缩质量 fail: function () { pageHelper.showModal('上传失败,请重新上传'); }, success: async function (res) { await callback(res.tempFilePath); } }); } else { // 小于1M,不压缩 if (!await callback(path)) return; } } else { //图片无须审核 this.setData({ imgList: this.data.imgList.concat(path) }); this.triggerEvent('myImgUploadEvent', this.data.imgList); } } wx.hideLoading(); } }); }, 云端async function checkImg(imgData, mine) { // 下载CDN图片进行检查 const axios = require('axios'); let buffer = null; await axios({ method: 'get', url: imgData, responseType: 'arraybuffer' }).then(res => { buffer = res.data; }); console.log('buffer SIZE=' + buffer.length / 1024 + 'K'); let cloud = cloudBase.getCloud(); try { const result = await cloud.openapi.security.imgSecCheck({ media: { contentType: 'image/png', value: buffer //value: Buffer.from(imgData, 'base64') // 这里必须要将小程序端传过来的进行Buffer转化,否则就会报错,接口异常 } }); console.log('imgcheck', result); if (!result || result.errCode !== 0) throw new AppError('图片内容不合适,请修改'); } catch (err) { console.log('imgcheck ex', err); if (err.errMsg && err.errMsg.includes('invalid media size')) throw new AppError('图片过大,请修改'); else if (err.errMsg && err.errMsg.includes('content size out of limit')) throw new AppError('图片尺寸过大,请修改'); else if (err.errMsg && err.errMsg.includes('604102')) throw new AppError('图片过大,请上传较小图片'); else throw new AppError('图片内容不合适,请修改'); } } 测试情况:校友会小程序中上传正常图片不含违法违规,测试50次,全部通过。校友会小程序上线后暂无发现检测失败情况。 [图片]
2021-07-12 - 内容安全检测图片API:openapi.security.imgSecCheck完美解决方案。
背景需求: 我个人做了一款小程序的小游戏,本质是小程序。里面有个自定义图片的功能。用户从本地相册选一张图片进行裁剪,之后保存到缓存中或者上传到服务器。然后用户再用这张图片作为素材进行其它操作。这里就涉及到内容安全了,提交审核没有通过也是因为这个没有做内容安全。防止一些色情低俗的事情发生。 正文: 思路:相册选图片 --> 裁剪小的图片 --> 内容安全检测 --> 通过 --> 裁剪大的图片 --> 保存。 失败的原因:绝大多数是因为检测图片不能大于1M,而导致超时,或者是errCode:-1,又或者是其它问题。 [图片] [图片] 核心代码图片: [代码]默认裁剪小尺寸图片 (我的业务需求是正方形图片,也可动态计算宽高比例) [代码] [图片] 检测图片 部分iOS不兼容encoding: ‘ucs2’。注释掉就好了 [图片] [图片] 云函数 [图片] 测试情况: 正常图片不含违法违规,测试20次,全部通过。小程序上线后暂无发现检测失败情况。百度搜索的“人体油画”等等均可通过。 PS:第一次写经验分享哈,看不懂可以问我。体验一下我的小程序想问我这个小程序其它的功能点也可以喔! 技术会迭代更新,用到的技术会有时效性,看编辑时间,可能当时的技术现在不适用了
2020-10-22 - 2020-08-16
- scroll-view需要可以按x和y方向拖动,但需要每次拖动我只要一个方向可拖动,如何实现?
我在微信小程序上,手机真机模拟,会发现斜向拖动,两个方向都会变,效果不是很好请问该如何调整呢?感谢各位! 另外,也试过onscroll中根据当前的滚动距离,动态设置scroll-x和scroll-y为true或false,但是在实际手机体验,设置完后总归要慢一拍,体验并不好 如下是当时的部分代码,都是在onsroll事件中返回的 if(Math.abs(e.detail.deltaY)>=Math.abs(e.detail.deltaX)) { this.scroll_x=false; this.scroll_y=true; } else { this.scroll_x=true; this.scroll_y=false; } this.$nextTick(() => { that.scroll_x=true; that.scroll_y=true; })
2023-05-16 - 小程序scroll-view翻转后 scroll-into-view的替代方案
背景 腾讯云医小程序有医患聊天会话的场景,由于会话场景存在查询历史消息的场景,小程序中按照常规思路加载历史消息时会出现跳动的问题;跳动的原因是由于在’顶部’插入dom,会使得后面的dom被往后面推,然后重新设置scroll-top或者scrol-into-view从而导页面出现跳动;我们尝试采用【 前端开发中聊天场景的体验优化】文章中的方案处理跳动的场景。该文章的核心观点将scroll-view元素通过设置css样式 transform: rotateX(180deg); 进行翻转,这样将历史消对应的dom结构放在尾部,当添加更多的历史消息(dom)时,由于dom是添加在尾部很优雅的绕过了插入历史消息跳动的场景。但是当我们按照这种方式实现后,发现scroll-view元素提供的scroll-into-view属性不好使了。因此有了本文通过计算scrollTop值设置scrollTop来达到相同目的。 复现该问题的小程序代码片段:代码片段 目前已经反馈给官方(官方已确认是内部组件实现暂不支持翻转的场景 基础知识介绍 计算scrollTop涉及到一些web和小程序的基础知识,后面针对这些基础点进行简单介绍 .scroll-into-view 微信小程序提供的scroll-view元素提供了属性 scroll-into-view,该属性的作用是可以将指定dom滚动到scroll-view可见区域内 [图片] 关于boundingClientRect 下图是MDN解释该属性时提供的,从下图中可以看到top/bottom/left/right的值是元素的左上角和右下角相对于视口左上角的水/垂直距离 [图片] 为了更深入理解这些值。给出了一个简易的demo(代码片段),获取实例元素的的boundingClientRect的值后,可以看到这些值是根据元素的border边界进行计算的 [图片] [图片] [图片] 值得注意的是,当元素处于一个滚动区域内部,left/top值是考虑滚动操作的即包含滚动距离的(参考MDN 另外,当我们把容器元素又或者元素自身设置 transform: rotateX/Y(180deg):不会导致top和bottom的值互换(left与right的值互换); 总会有这样的结论,当dom元素的宽度和高度不为0时,top值一定小于bottom值,left值一定小于right值 关于scrollTop 当一个容器的内容的高度大于其容器高度时,overflow不为visible/hidden时,则会出现滚动条。出现滚动条后,内容区域则可以滚动,此时scrollTop的值是容器可视区域的顶部到内容区域顶部的距离,见下面示意图。 [图片] 值得注意的是,滚动条出现在盒模型中的content区域,见下图滚动条不会覆盖padding/border部分。因此上面说到内容区域高度超过容器高度并不严谨,严谨的说法应该是超过容器的content区域的高度。 [图片] 如果此时给容器设置css样式: transform: rotateX(180deg); 即沿垂直方向翻转180度,scrollTop的值会发生变化吗。 下面我们看下实际的对比效果,为了方便查看滚动条的效果,给滚动条轨道(红色部分)以及滑块(黑色部分)添加了背景色,发现整个元素包括滚动条在内一并进行了翻转。 正常情况(左侧),应用翻转css样式(右侧) [图片] [图片] 翻转后的scrollTop值示意图 [图片] 通过计算scrollTop值来模拟scroll-into-view效果(针对scroll-view翻转的场景j) 由于boundingClinetRect的值是包含border边界的,因此当数据项包含padding,border等区域不会影响这里的计算过程,可以认为下面示意图中的数据项部分的边界是border边界; 由于滚动条是出现在content区域,因此容器元素的的border-top/padding-top不为0时,会影响计算流程,因此这里分为两种情况进行介绍: 2.1 假设scroll-view元素的的border-top/padding-top为0 2.2 假设scroll-view元素的的border-top/padding-top不为0 border-top/padding-top为0的情况 为了方便说明计算过程,我定义三种状态,初始态、中间态、最终态 示意图中的区域说明 白色背景的为视口, 绿色背景的是容器(scroll-view)的可视区域, 灰色区域是内容区域,并且内容区域的高度超过了容器的高度, 红色区域是一个数据项 [图片] 现在的目标是将数据项从初始态滚动到最终态即scroll-into-view的效果:border的上边界与可视区域上边界对齐 第一步:从初始态达到中间态 根据上面关于scrollTop的描述,这里如果scrollTop的值是targetDistance即数据项的底部到内容区域的底部的距离,就可以达到中间态,因此现在的目标是求targetDistance 初始状态的已知变量 初始状态下的的scrollTop值:currentScrollTop (由于容器发生翻转,所以scrollTop视觉上指向容器下方) 数据项的boundingClientRect.bottom为 itemBottom 容器的boundingClientRect.bottom为 contianerBottom 通过示意图很容易得出 [代码]targetDistance = currentScrollTop + (containerBottom - itemBottom) [代码] 第二步:从中间到达最终态 已知变量:容器高度:containerHeight、数据项高度:itemHeight 最终态是数据项的顶部距离容器顶部,从示意图中看到中间态到最终态的scrollTop是减少了的,减少的值其实就是cotainerHeight - itemHeight 经过第一步和第二步我们就可以得到scrollTop的计算公式 [代码]let itemScrollTop = currentScrollTop + containerBottom - itemBottom itemScrollTop -= (containerHeight - itemHeight) => itemScrollTop = currentScrollTop + containerBottom - itemBottom - (containerHeight - itemHeight) [代码] border-top/padding-top不为0的情况 [图片] 根据上面第一种情况的介绍的思路,很容易得到下面结果,不再赘述(X 就是容器padding-top + border-top的值) [代码]let itemScrollTop = currentScrollTop + containerBottom - itemBottom - X itemScrollTop -= (containerHeight - itemHeight - X) => itemScrollTop = currentScrollTop + containerBottom - itemBottom - X - (containerHeight - itemHeight - X) => itemScrollTop = currentScrollTop + containerBottom - itemBottom - (containerHeight - itemHeight) [代码] 【结论】两种情况最终的计算过程是一样的,因此在实现的过程中不需要进行区分 代码实现 代码片段见:https://developers.weixin.qq.com/s/y1X11dmr7AqC 视图层代码 [代码]{{item.content}} #scroll-view { position: absolute; top: 50px; bottom: 50px; width: 100%; background-color: rgba(0, 0, 0, 0.1); // 关键case transform: rotateX(180deg); } [代码] 逻辑层核心代码 [代码]scrollTo () { const itemId = '#item_id_50' const containerId = '#scroll-view' Promise.all([this._queryBoundingClient(itemId), this._getScrollInfo(containerId)]) .then((res = [[[{}]], {}]) => { const [[[ { bottom: itemBottom, height: itemHeight }]], { bottom: containerBottom, scrollTop, height: containerHeight }] = res let itemScrollTop = containerBottom - itemBottom + scrollTop itemScrollTop -= (containerHeight - itemHeight) this.setData({ scrollTop: itemScrollTop }) }) }, _queryBoundingClient (selector) { // 获取目标dom的相关位置/尺寸信息 return new Promise(resolve => { const query = this.createSelectorQuery(); query.selectAll(selector).boundingClientRect(); query.exec(resolve); }) }, _getScrollInfo (idSelector) { // 用来获取容器层相关位置/尺寸信息 return new Promise(resolve => { const query = this.createSelectorQuery() query.select(idSelector).boundingClientRect() query.select(idSelector).scrollOffset() query.exec((res = [{}, {}]) => { const [{ top, bottom, height }, { scrollHeight, scrollTop }] = res const scrollInfo = { scrollTop, scrollHeight, top, bottom, height } resolve(scrollInfo) }) }) } [代码]
2023-03-23 - 如何使用scroll-view制作左右滚动导航条效果
最新:2020/06/13。修改为scroll-view与swiper联动效果,新增下拉刷新以及上拉加载效果。。具体效果查看代码片段,以下文章内容和就不改了 刚刚在社区里看到 有老哥在问如何做滚动的导航栏。这里简单给他写了个代码片段,需要的大哥拿去随便改改,先看效果图: [图片] 代码如下: wxml [代码]<scroll-view class="scroll-wrapper" scroll-x scroll-with-animation="true" scroll-into-view="item{{currentTab < 4 ? 0 : currentTab - 3}}" > <view class="navigate-item" id="item{{index}}" wx:for="{{taskList}}" wx:key="{{index}}" data-index="{{index}}" bindtap="handleClick"> <view class="names {{currentTab === index ? 'active' : ''}}">{{item.name}}</view> <view class="currtline {{currentTab === index ? 'active' : ''}}"></view> </view> </scroll-view> [代码] wxss [代码].scroll-wrapper { white-space: nowrap; -webkit-overflow-scrolling: touch; background: #FFF; height: 90rpx; padding: 0 32rpx; box-sizing: border-box; } ::-webkit-scrollbar { width: 0; height: 0; color: transparent; } .navigate-item { display: inline-block; text-align: center; height: 90rpx; line-height: 90rpx; margin: 0 16rpx; } .names { font-size: 28rpx; color: #3c3c3c; } .names.active { color: #00cc88; font-weight: bold; font-size: 34rpx; } .currtline { margin: -8rpx auto 0 auto; width: 100rpx; height: 8rpx; border-radius: 4rpx; } .currtline.active { background: #47CD88; transition: all .3s; } [代码] JS [代码]const app = getApp() Page({ data: { currentTab: 0, taskList: [{ name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, ] }, onLoad() { }, handleClick(e) { let currentTab = e.currentTarget.dataset.index this.setData({ currentTab }) }, }) [代码] 最后奉上代码片段: https://developers.weixin.qq.com/s/nkyp64mN7fim
2020-06-13 - 前端架构之路:小程序 Log 日志
前言 后续我会在 [代码]github[代码] 开放源码,并打包至 [代码]npm[代码] ,开发者后续可自行 [代码]install[代码] 调用。 后续 源码地址 及 npm安装方法 将会在该页面更新。 开放时间基于大家需求而定。 通常情况下,日志系统是开发中重要的一环。 但出于种种原因,在前端开发中做日志打印和上报系统却不常见。 但有些特定情况下,日志系统往往有奇效。 比如一个聊天系统中遇到了以下问题: 语音通话中,用户听不到声音 即时通讯中,部分场景用户反馈,消息发送不出去 即时通讯中, A 回复 B 消息时,偶尔对话框不显示 即时通讯中, A 给 B 连续发送两条消息后, B 接收不到第二条的提示 即时通讯中,发送语音消息发送时,用户以为语音已经发送,但实际上录音还在继续。这时用户以为是网络卡了,最后发现自己和其他人说话的声音被录制进去 但是以上几种错误,在后台接口中并没有体现。再加上部分用户手机型号的问题,导致问题很难被定位。 如果我们这里有 [代码]log[代码] ,我们就能很快定位到出问题的代码。 如果不是代码问题,也更有底气回复用户不是我们系统的问题。 如何使用小程序 Log 日志系统 小程序侧提供了两种小程序 Log 日志接口: LogManager ( 普通日志 ) RealtimeLogManager ( 实时日志 ) 官方并没有介绍两者的具体区别,只是强调了 Realtime 的实时性质。 在我看来他们的最大区别就是: [代码]LogManager[代码] 可以让用户有种心安的感觉,因为 [代码]LogManager[代码] 是用户手动反馈的问题。 [代码]RealtimeLogManager[代码] 则对开发者更友好,可以在用户不知情的情况下收集到问题信息,并在用户无感的情况下对问题进行修复。 LogManager 小程序提供的 [代码]Log[代码] 日志接口,通过 [代码]wx.getLogManager()[代码] 获取实例。 注意: 最多保存5M的日志内容,超过5M后,旧的日志内容会被删除。 对于 小程序 ,用户可以通过使用 [代码]button[代码] 组件的 [代码]open-type="feedback"[代码] 来上传打印的日志。 对于 小游戏 ,用户可以通过使用 [代码]wx.createFeedbackButton[代码] 来创建上传打印的日志的按钮。 开发者可以通过小程序管理后台左侧菜单 反馈管理 页面查看相关打印日志。 创建 LogManager 实例 你可以通过 [代码]wx.getLogManager()[代码] 获取日志实例。 括号中可以传参 [代码]{ level: 0 | 1 }[代码] 来决定是否写入 [代码]Page[代码] 的生命周期函数, [代码]wx[代码] 命名空间下的函数日志。 0: 写入 1: 不写入 [代码]const logger = wx.getLogManager({ level: 0 }) [代码] 使用 LogManager 实例 [代码]const logger = wx.getLogManager({ level: 0 }) 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]) [代码] 用户反馈上传 LogManager 记录的日志 当日志记录后, 用户可以在小程序的 [代码]profile[代码] 页面,单击 反馈与投诉 ,在点击 功能异常 进行日志上传。 开发者处理用户反馈及和用户沟通 开发者可以在小程序后台 管理 -> 用户反馈 -> 功能异常 查看用户反馈的信息。 开发者可以在 功能 -> 客服 下绑定客服微信,绑定后可以在 48小时 内通过微信和反馈用户沟通。 注:沟通需要用户反馈时勾选:允许开发者在 48 小时内通过客服消息联系我。 RealtimeLogManager 小程序提供的 [代码]实时Log[代码] 日志接口,通过 [代码]wx.getRealtimeLogManager()[代码] 获取实例。 注意: [代码]wx.getRealtimeLogManager()[代码] 基础库 2.7.1 开始支持 官方给出实时日志每条的容量上限是 [代码]5kb[代码] 官方对每条日志的定义:在一个页面 onShow -> onHide 之间,会聚合成一条日志上报 开发者可从小程序管理后台: 开发 -> 运维中心 -> 实时日志 进入小程序端日志查询页面 为了定位问题方便,日志是按页面划分的,某一个页面,在onShow到onHide(切换到其它页面、右上角圆点退到后台)之间打的日志,会聚合成一条日志上报,并且在小程序管理后台上可以根据页面路径搜索出该条日志 创建 RealtimeLogManager 实例 你可以通过 [代码]wx.getRealtimeLogManager()[代码] 获取实时日志实例。 [代码]const logger = wx.getRealtimeLogManager() [代码] 使用 RealtimeLogManager 实例 [代码]const logger = wx.getRealtimeLogManager() logger.debug({str: 'hello world'}, 'debug log', 100, [1, 2, 3]) logger.info({str: 'hello world'}, 'info log', 100, [1, 2, 3]) logger.error({str: 'hello world'}, 'error log', 100, [1, 2, 3]) logger.warn({str: 'hello world'}, 'warn log', 100, [1, 2, 3]) [代码] 查看实时日志 与普通日志不同的是,实时日志不再需要用户反馈,可以直接通过以下方式查看实例。 登录小程序后台 通过路径 开发 -> 开发管理 -> 运维中心 -> 实时日志 查看实时日志 如何搭建小程序 Log 日志系统 上面我们知道了小程序的 [代码]Log[代码] 日志怎么使用,我们当然可以不进行封装直接使用。 但是我们直接使用起来会感觉到十分的别扭,因为这不符合我们程序员单点调用的习惯。 那么接下来让我们对这套 Log 系统进行初步的封装以及全局的方法的日志注入。 后续我会在 github 开放源码,并打包至 npm ,需要的开发者可自行 install 调用。 封装小程序 Log 方法 封装 Log 方法前,我们需要整理该方法需要考虑什么内容: 打印格式:统一打印格式有助于我们更快的定位问题 版本号:方便我们清晰的知道当前用户使用的小程序版本,避免出现旧版本问题在新代码中找不到问题 兼容性:我们需要考虑用户小程序版本不足以支持 [代码]getLogManager[代码] 、 [代码]getRealtimeLogManager[代码] 的情况 类型:我们需要兼容 [代码]debug[代码] 、 [代码]log[代码] 、 [代码]error[代码] 类型的 [代码]log日志[代码] 版本问题 我们需要一个常量用以定义版本号,以便于我们定位出问题的代码版本。 如果遇到版本问题,我们可以更好的引导用户 [代码]const VERSION = "1.0.0" const logger = wx.getLogManager({ level: 0 }) logger.log(VERSION, info) [代码] 打印格式 我们可以通过 [代码][version] file | content[代码] 的统一格式来更快的定位内容。 [代码]const VERSION = "1.0.0" const logger = wx.getLogManager({ level: 0 }) logger.log(`[${VERSION}] ${file} | `, ...args) [代码] 兼容性 我们需要考虑用户小程序版本不足以支持 [代码]getLogManager[代码] 、 [代码]getRealtimeLogManager[代码] 的情况 [代码]const VERSION = "0.0.18"; const canIUseLogManage = wx.canIUse("getLogManager"); const logger = canIUseLogManage ? wx.getLogManager({ level: 0 }) : null; const realtimeLogger = wx.getRealtimeLogManager ? wx.getRealtimeLogManager() : null; export function RUN(file, ...args) { console.log(`[${VERSION}]`, file, " | ", ...args); if (canIUseLogManage) { logger.log(`[${VERSION}]`, file, " | ", ...args); } realtimeLogger && realtimeLogger.info(`[${VERSION}]`, file, " | ", ...args); } [代码] 类型 我们需要兼容 [代码]debug[代码] 、 [代码]log[代码] 、 [代码]error[代码] 类型的 [代码]log日志[代码] [代码]export function RUN(file, ...args) { ... } export function DEBUG(file, ...args) { ... } export function ERROR(file, ...args) { ... } export function getLogger(fileName) { return { DEBUG: function (...args) { DEBUG(fileName, ...args) }, RUN: function (...args) { RUN(fileName, ...args) }, ERROR: function (...args) { ERROR(fileName, ...args) } } } [代码] 完整代码 以上都做到了,就完成了一套 [代码]Log[代码] 系统的基本封装。 [代码]const VERSION = "0.0.18"; const canIUseLogManage = wx.canIUse("getLogManager"); const logger = canIUseLogManage ? wx.getLogManager({ level: 0 }) : null; const realtimeLogger = wx.getRealtimeLogManager ? wx.getRealtimeLogManager() : null; export function DEBUG(file, ...args) { console.debug(`[${VERSION}] ${file} | `, ...args); if (canIUseLogManage) { logger.debug(`[${VERSION}]`, file, " | ", ...args); } realtimeLogger && realtimeLogger.info(`[${VERSION}]`, file, " | ", ...args); } export function RUN(file, ...args) { console.log(`[${VERSION}]`, file, " | ", ...args); if (canIUseLogManage) { logger.log(`[${VERSION}]`, file, " | ", ...args); } realtimeLogger && realtimeLogger.info(`[${VERSION}]`, file, " | ", ...args); } export function ERROR(file, ...args) { console.error(`[${VERSION}]`, file, " | ", ...args); if (canIUseLogManage) { logger.error(`[${VERSION}]`, file, " | ", ...args); } realtimeLogger && realtimeLogger.error(`[${VERSION}]`, file, " | ", ...args); } export function getLogger(fileName) { return { DEBUG: function (...args) { DEBUG(fileName, ...args) }, RUN: function (...args) { RUN(fileName, ...args) }, ERROR: function (...args) { ERROR(fileName, ...args) } } } [代码] 全局注入 Log 通过该章节的名称,我们就可以知道全局注入。 全局注入的意思就是,不通过手动调用的形式,在方法写完后自动注入 [代码]log[代码] ,你只需要在更细节的地方考虑打印 [代码]log[代码] 即可。 为什么要全局注入 虽然我们实现了全局 [代码]log[代码] 的封装,但是很多情况下,一些新同学没有好的打 [代码]log[代码] 的习惯,尤其是前端同学(我也一样)。 所以我们需要做一个全局注入,以方便我们的代码书写,也避免掉手动打 [代码]log[代码] 会出现遗漏的问题。 如何进行全局注入 小程序提供了 [代码]behaviors[代码] 参数,用以让多个页面拥有相同的数据字段和方法。 需要注意的是, [代码]page[代码] 级别的 [代码]behaviors[代码] 在 2.9.2 之后开始支持 我们可以通过封装一个通用的 [代码]behaviors[代码] ,然后在需要 [代码]log[代码] 的页面进行引入即可。 [代码]import * as Log from "./log-test"; export default Behavior({ definitionFilter(defFields) { console.log(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) { 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) } }) } }) [代码] 总结 连着开发带整理,林林总总的也有了 [代码]2000+[代码] 字,耗费了三天的时间,整体感觉还是比较值得的,希望可以带给大家一些帮助。 也希望大家更重视前端的 [代码]log[代码] 一点。这基于我自身的感觉,尤其是移动端用户。 在很多时候由于 手机型号 、 弱网环境 等导致的问题。 在没有 [代码]log[代码] 时,找不到问题的着力点,导致问题难以被及时解决。
2022-01-17 - 小程序奇技淫巧之 -- 日志能力
日志与反馈 前端开发在进行某个问题定位的时候,日志是很重要的。因为机器兼容性问题、环境问题等,我们常常无法复现用户的一些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 - LogManager 和 RealtimeLogManager 推荐使用方式?
LogManager 记录的日志需要用户主动反馈后在mp后台方可下载,RealtimeLogManager 不需要用户主动反馈只要记录了在mp后台即可查看,既然如此,那么是不是就可以直接舍弃logManager方式来记录日志?LogManager还有啥存在必要吗?
2020-07-15 - wx.navigateToMiniProgram如何在插件里使用?
https://developers.weixin.qq.com/miniprogram/dev/api/navigate/wx.navigateToMiniProgram.html 需要页面权限:当前是插件页面时,宿主小程序不能调用该接口,反之亦然 这句话啥意思?我是在插件自定义组件里使用,提示“navigateToMiniProgram:fail rejected due to no permission currently in plugin” 如何添加权限?
2022-06-08 - 小程序开发起步
学习 5 节课程,从 0 至 1 做第一个属于你的小程序,深入浅出了解小程序开发。本系列视频,由腾讯课堂 NEXT 学院、微信学堂联合出品。
2022-03-24 - 扫码登录开发者工具时,提示:【调试过程中开发者可通过以下公众号获得你的相关信息】。怎么取消这个公众号呢?
发现跟多人遇到跟我同样的问题,于是就根据可爱的伙伴们提供的线索,找到了解决办法,不过前提还是有公众号后台的权限。 操作如下: 登录公众号-开发-开发者工具,这时可以看到有这么一个web开发者工具 [图片] 点进去,把授权给你的取消即可。到此,重新扫码登录小程序开发者工具,就不会再出现授权给某公众号的信息了,有帮助的点个赞呗!
2020-08-17 - wx.getUserProfile 返回的iv信息等字段无法解密?
通过 wx.getUserProfile 获取的信息 iv encryptedData 无法解密 版本库2.10.4
2021-04-13 - 登录优化:2.0:让你的小程序与用户做朋友
上一篇小程序登录相关的学院课程,我们围绕小程序如何通过优化登录“拉好感”提了几点建议。 前方再次预告,开发人员请及时到场:9月1日起,小程序登录规范规则正式开始执行,未满足登录规范要求的小程序将会被代码审核拦截,请尽快优化。 下面,我们来复习下正确的登录优化姿势。 从陌生人到“知己” 想要“拉好感”并不难,秘诀就在于跟用户做朋友。开发者可以站在用户的角度体验小程序,从而更了解自己的产品。 攻略一:互相认识从自我介绍开始。对于线上仅提供注册功能,服务依赖其他方式提供的小程序,可先让用户知悉小程序功能,再引导用户进行授权。 下面这个ETC小程序,就“秀”了一波:用户可在首页了解小程序相关功能及使用帐号登录的原因,并可先办理ETC再登录。 [图片] [图片] [图片] [图片] [图片] 攻略二:做完介绍,先别急着登录,让对方花点时间了解你。对于服务范围开放的小程序,可先让用户体验了解小程序功能,与其只给用户一个登录选项,不如用“个人魅力”吸引他们。 例如,在这个DJ小程序中,用户可先听音乐蹦迪授权登录后,还能关注作者、风格及电台,或购买周边商品,享受更多服务。 [图片] [图片] [图片] 不过,如果服务范围是特定的,可以直接让用户登录,毕竟你和用户已经是知根知底的“老熟人”了。 攻略三:退一步,给对方留点拒绝的余地。在小程序登录页提供可取消或拒绝的选项按钮,反而更能留下好印象。 以这个拼单小程序为例,用户可先浏览商品信息,查看商品详情。如果用户心动了,点击“我的”进入个人中心授权登录即可下单;如果用户犹豫不决,还可以退出登录页面继续逛。 [图片] [图片] [图片] [图片] 登录优化的Q&A 对规则仍然有疑惑的开发人员,我们也补充了详细的问题解答,希望对你们有帮助—— Q:9月1日后,开发者该如何获得用户的UnionID?用户不登录就没有UnionID,怎么办? UnionID是作为微信体系内,用户帐号在同一主体下不同公众号、小程序及App之间实现数据互通的识别凭证,这个信息本身是匿名化、不敏感且不可反推的。但用户如果在不知情的情况下被获取,并且开发者完成了跨平台或者跨产品的打通,会使得用户产生困惑,为什么同一个用户在帐号A的信息会在帐号B中被展示。 站在用户体验和隐私保护的角度来看,每个人都不希望自己的个人信息在不知情的情况下被获取。因此,开发者要在用户授权登录后,才能获取UnionID 如果用户曾经授权登录过同主体App或关注了同主体公众号,则表示用户已经知情并同意登录,这时开发者可以直接获得UnionID。点击这里查看详情。 如果用户未授权登录,开发者可根据前面的攻略,进一步优化登录体验,让用户更乐意登录使用你的小程序。 unionid就像大型连锁超市(微信内同主体帐号)给客户(用户)发放会员卡(nionid),客户持有会员卡可在连锁超市内享受会员权益。 而用户不知情即被获取unionid,就好比在连锁超市购物后,没有任何信息说明,超市就要求留下手机号码等身份凭证识别信息。下次用户去另一家连锁超市时,超市已清晰记录用户之前的消费记录。这是我们不提倡的。 因此,我们希望在用户对小程序的业务有了解之后,开发者明确告知用户会在什么功能或业务使用unionid打通不同帐号之间的数据。 Q:怎么划分服务范围开放和服务范围特定呢? 举个简单的例子,当你在餐厅、商店消费时,服务员不会要求查看你的身份证明,再为你提供服务,但当你进入学校或公司时,则需先登记才能进去。 因此,服务范围开放是指完全对外开放注册,无需进行特殊身份验证,注册后即可提供线上服务体验的小程序,例如电商、外卖等小程序;服务范围特定则需要进行特殊身份验证,且线上服务仅供特殊身份用户体验,例如学校教学系统、公司员工系统等小程序。 Q:服务范围开放的小程序,可以使用仅提供注册功能小程序的调整方案吗? 如果小程序服务范围属于完全开放,线上仅提供注册功能,其他服务均需以其他方式提供的,例如ETC小程序,可以在首页介绍小程序的服务功能,说明要求使用帐号登录功能的原因后,让用户主动选择登录。 反之则需要让用户进入小程序体验并了解平台功能,在使用需要注册登录才能体验的功能时,才触发登录注册流程。 Q:如果小程序不调用微信个人信息授权,只使用帐号密码或手机号码登录,需要优化吗? 小程序帐号登录,是指开发者在小程序内提供的帐号登录功能,包括但不限于手机号登录、getuserinfo形式登录、邮箱登录等形式。因此,开发者也需根据规范进行优化 再次划重点,请未满足小程序帐号登录规范的开发者们尽快完成优化,为你的小程序增加更多好感度。 未来,我们还将不断优化小程序使用体验,希望开发者与我们一起,让平台生态更加绿色健康。如果你对新规范还有什么疑惑,欢迎在评论区聊聊。
2021-06-23 - 登录优化1.0:你的小程序将会赢得更多用户的青睐!
今天,说一个能为你“拉好感”的改动。 小程序帐号登录功能进行全新的规范升级,包括但不限于手机号登录、邮箱登录等。划重点:从2019年9月1日开始,对于未满足登录规范要求的小程序,我们将会在后续的代码审核环节进行规则提示和修改要求反馈。 用户使用登录功能就像“面基”,第一印象很重要。这几个小改动在提升小程序使用的流畅体验、避免用户对数据采集授权担忧的同时,也将驱动用户更乐意尝试使用小程序服务。来,通过三个问题来解锁正确启用帐号登录的姿势。 跟败好感”的姿势告别 问题一 : 在用户清楚知悉、了解小程序的功能之前,就要求用户进行帐号登录会怎样? [图片] (错误示范:在用户打开小程序后立刻弹出授权登录页) 用户好感-1。刚见面就要牵手未免太尴尬,与其打开小程序后立即跳转提示登录或打开小程序后立即强制弹窗要求登录,不如把主动权交给用户。 用户体验小程序功能后,可以主动点击登录按钮来触发登录流程,也可以选择不登录。 [图片] Tips: 适用于对外开放用户注册流程、无需验证特定范围用户,且注册后即可提供线上服务的小程序。 有个特例, 为学校系统、员工系统、社保卡信息系统等提供服务的小程序,倒是可以直接引导用户进行帐号登录。 [图片] 问题二 : 线上仅提供注册功能,服务依赖其他方式提供的小程序,如ETC注册申请、信用卡申请,在用户未获取任何信息时,首页直接弹框要求登录注册会怎样? [图片] (错误示范:用户进入ETC小程序时立刻弹出授权登录页) 用户好感再-1 。“神秘感”能营造好氛围,但你的小程序都如此优秀了,可以直接告诉用户原因再让他们接受—— 用户在小程序首页了解要求使用帐号登录功能的原因后,可通过登录或注册按钮进行登录操作。 [图片] 问题三 : 在需要登录环节直接跳转登录页面,只给用户一个登录选项会怎样? [图片] (错误示范:登录页面只有登录选项) 用户好感再-N。“霸道总裁”不要太用力,多给些温柔的选项,用户会更快爱上你。 在需帐号登录的环节中,用户可以主动点击登录,或点击取消登录,没有强制登录的行为。 [图片] 姿势掌握了吗?我们希望帮助开发者们能根据正确姿势示范,调整小程序的帐号登录功能,优化用户使用帐号登录功能的体验,更好地与用户相处,同时赢得更多用户的青睐。
2021-06-23 - “分享监听”能力调整
近期我们收到了很多用户对小程序/小游戏中分享功能的投诉:在某些小程序/小游戏中,分享并非是用户主动自发的行为,而是受到了某类利益的诱惑,或是被迫分享。这样的内容充斥在群里、小程序里,对用户造成了骚扰。 分享功能,旨在帮助用户更流畅地与好友分享内容和服务,应是用户自发的行为。在原来的分享接口中,用户发起分享动作之后,可以通过 [代码]success[代码] 、[代码]fail[代码]、[代码]complete[代码]等回调来判断用户是否完成了最后的分享动作。通过这个能力,开发者可以将产品交互在分享这个能力上做得比较自然和顺畅。现在为鼓励用户自发分享喜爱的内容,减少“强制分享至不同群”等滥用分享能力,破坏用户体验的行为,在我们权衡了分享功能带来的利弊后,分享功能将进行以下调整: 10月10日起新提交发布的版本,不再支持分享回调参数 [代码]success[代码] 、[代码]fail[代码] 、[代码]complete[代码],即用户从小程序/小游戏中分享消息给好友时,开发者将无法获知用户是否分享完成,也无法在分享后立即获得分享成功后的回调参数[代码]shareTicket[代码]。该调整可以在基础库 2.3.0及以上版本体验。 此次调整可能影响到三种分享功能的用法。 第一种:判断用户是否分享成功,进而给予用户奖励。 例如:小程序提示用户“分享到5个群,可以获得一张20元的优惠券”。 这类诱导用户分享的行为是我们平台所不倡导的,后续将没有办法实现。 第二种:分享完成后变更当前的页面状态 例如:赠送礼品场景下,用户点击“赠送”按钮,将礼品分享出去,分享成功后,界面展示“等待领取”。 这类场景,我们建议可以适当调整交互方案。例如在分享后继续保留“赠送”按钮,但在页面上提示用户一个礼品只能被一人领取,重复赠送无效。 第三种:通过用户分享之后的 [代码]shareTicket[代码] 获取群唯一标识 [代码]openGId[代码] ,以显示对应群的相关信息。 例如:通过分享小程序到某个群里,可以查看该群内成员的排行榜。 此次调整后,用户分享完成后无法立刻显示该群的排行榜信息,但仍可在用户从群消息点击进入小程序时显示该群的排行榜信息。 10月10日起新提交发布的版本将会受到此调整的影响。 需要各位开发者注意,10月10日起新提交发布的版本将会受到此策略的影响,请及时调整分享相关能力,考虑兼容上述调整带来的影响。 调整策略在基础库 2.3.0 及以上版本生效,该基础库版本对应微信客户端6.7.2版本。另外,考虑到兼容性等问题,在基础库版本 2.3.0 以下的环境中不受此策略影响,小程序/小游戏可继续获取分享回调事件。
2018-09-13 - 修改微信用户信息后,getUserInfo信息什么时候更新?
更新时间:2小时
2020-09-04 - 登录接口又双叕变了,三行代码挑战全网最少修改工作量
小程序登录、用户信息样关接口又双叕变了。 https://developers.weixin.qq.com/community/develop/doc/000cacfa20ce88df04cb468bc52801 几家悲伤几家愁。。。 微信的一小步,人猿的一大步。。。 没办法,改吧。。。 翻出以前小程序这部分的代码,惊喜地发现,只需要三行代码,就能平滑过渡; 感谢我以前看似丑陋却很省事的登录代码逻辑!!! 登录逻辑如下: 1、判断库里有用户的信息没有,没有,则wx.navigateTo一个专门的授权页面:auth 2、授权成功后获得userInfo,保存到库里; auth页代码修改如下: auth.wxml: 修改一行代码 <button style='margin:15px;font-size:16px' type='primary' size="mini" bindtap='getUserProfile'>授权微信头像和昵称</button> auth.js: 修改两行代码 //原wx.getUserInfo接口 getUserInfo: function (e) { let userInfo = e.detail.userInfo if (userInfo) this.onSaveUserInfo(userInfo) }, //新增wx.getUserProfile接口 getUserProfile: function (e) { wx.getUserProfile({ desc: '业务需要', success: res => this.onSaveUserInfo(res.userInfo) }) }, //保存userInfo到DB onSaveUserInfo:function(userInfo){ console.log(app.globalData.userInfo = userInfo) db.collection('user') .where({ _id: this.openid }) .count() .then(res => { if (res.total > 0) { //doc.update db.collection('user').doc(this.openid).update({ data: userInfo }).then(res => console.log(res)) } else { //doc.add db.collection('user').doc(this.openid).add({ data: userInfo }).then(res => console.log(res)) } }) wx.navigateBack() }, 以下是判断用户信息是否存在的代码: xxxx.js: onSubmit:async function () { if (await app.hasUserInfo()) { } else return //其他代码 }, app.js: hasUserInfo: async function () { if (this.globalData.userInfo && this.globalData.userInfo.nickName && this.globalData.userInfo.avatarUrl) return true let res = await wx.cloud.database().collection('user').doc(this.openid).get().catch(err => console.log(err)) if (res && res.data && res.data.nickName && res.data.avatarUrl) { this.globalData.userInfo = res.data return true } else { wx.navigateTo({ url: '/base/auth/auth' }) return false } }, 关于用户信息自动更新: 我们一直以来的方法如下: 1、留给用户手动授权的入口,用户更换头像后,发现自己的头像不显示,则需要手动授权刷新userInfo; 2、一般会在这个页面:我的--个人信息--授权微信头像和昵称,用户点击后,wx.navigateTo到授权页。 笔者团队认为:用户信息自动更新其实是个伪需求。理由如下: 假设某用户修改了头像: 1、用户自己打开小程序,发现头像和昵称怎么没有改过来,那么手动更新一下。用户体验没毛病,没必要非要自动更新; 2、用户如果后来不再进入小程序,别人看到的都是一张碎的头像,那么此时,自动更新也毫无作用,因为该用户都不打开小程序。 3、微信团队肯定考虑过自动更新这种要求,但他们宁愿千夫所指,也依然坚持推出新的登录接口,那就肯定是已经经过了中国最牛逼团队的全面考衡了。 补充: 登录和授权其实是两码事,可以毫无关系这么说,以上的内容主要都是关于授权微信用户信息的,下面补充一下登录的内容: 登录其实就是获取用户的openid,我们一直采用云函数来获取openid。方案如下: 在每个页面:page.js: onLoad: async function (options) { this.openid = await app.getOpenid() }, 在app.js: getOpenid: async function () { if (this.openid) return this.openid let res = await this.globalData.cloud.callFunction({ name: 'login' }) console.log(res) return this.openid = res.result.FROM_OPENID||res.result.OPENID },
2021-04-07 - 小程序与小游戏获取用户信息接口调整,请开发者注意升级。
为优化用户体验,使用 wx.getUserInfo 接口直接弹出授权框的开发方式将逐步不再支持。从2018年4月30日开始,小程序与小游戏的体验版、开发版调用 wx.getUserInfo 接口,将无法弹出授权询问框,默认调用失败。正式版暂不受影响。开发者可使用以下方式获取或展示用户信息: 一、小程序: 1、使用 button 组件,并将 open-type 指定为 getUserInfo 类型,获取用户基本信息。 详情参考文档: https://developers.weixin.qq.com/miniprogram/dev/component/button.html 2、使用 open-data 展示用户基本信息。 详情参考文档: https://developers.weixin.qq.com/miniprogram/dev/component/open-data.html 二、小游戏: 1、使用用户信息按钮 UserInfoButton。 详情参考文档: https://developers.weixin.qq.com/minigame/dev/document/open-api/user-info/wx.createUserInfoButton.html 2、开放数据域下的展示用户信息。 详细参考文档: https://developers.weixin.qq.com/minigame/dev/document/open-api/data/wx.getUserInfo.html 请各位开发者注意及时调整接口。
2018-04-16 - 小程序登录、用户信息相关接口调整说明
公告更新时间:2021年04月15日考虑到近期开发者对小程序登录、用户信息相关接口调整的相关反馈,为优化开发者调整接口的体验,回收wx.getUserInfo接口可获取用户授权的个人信息能力的截止时间由2021年4月13日调整至2021年4月28日24时。为优化用户的使用体验,平台将进行以下调整: 2021年2月23日起,若小程序已在微信开放平台进行绑定,则通过wx.login接口获取的登录凭证可直接换取unionID2021年4月28日24时后发布的小程序新版本,无法通过wx.getUserInfo与<button open-type="getUserInfo"/>获取用户个人信息(头像、昵称、性别与地区),将直接获取匿名数据(包括userInfo与encryptedData中的用户个人信息),获取加密后的openID与unionID数据的能力不做调整。此前发布的小程序版本不受影响,但如果要进行版本更新则需要进行适配。新增getUserProfile接口(基础库2.10.4版本开始支持),可获取用户头像、昵称、性别及地区信息,开发者每次通过该接口获取用户个人信息均需用户确认。具体接口文档:《getUserProfile接口文档》由于getUserProfile接口从2.10.4版本基础库开始支持(覆盖微信7.0.9以上版本),考虑到开发者在低版本中有获取用户头像昵称的诉求,对于未支持getUserProfile的情况下,开发者可继续使用getUserInfo能力。开发者可参考getUserProfile接口文档中的示例代码进行适配。请使用了wx.getUserInfo接口或<button open-type="getUserInfo"/>的开发者尽快适配。开发者工具1.05.2103022版本开始支持getUserProfile接口调试,开发者可下载该版本进行改造。 小游戏不受本次调整影响。 一、调整背景很多开发者在打开小程序时就通过组件方式唤起getUserInfo弹窗,如果用户点击拒绝,无法使用小程序,这种做法打断了用户正常使用小程序的流程,同时也不利于小程序获取新用户。 二、调整说明通过wx.login接口获取的登录凭证可直接换取unionID 若小程序已在微信开放平台进行绑定,原wx.login接口获取的登录凭证若需换取unionID需满足以下条件: 如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用2月23日后,开发者调用wx.login获取的登录凭证可以直接换取unionID,无需满足以上条件。 回收wx.getUserInfo接口可获取用户个人信息能力 4月28日24时后发布的新版本小程序,开发者调用wx.getUserInfo或<button open-type="getUserInfo"/>将不再弹出弹窗,直接返回匿名的用户个人信息,获取加密后的openID、unionID数据的能力不做调整。 具体变化如下表: [图片] 即wx.getUserInfo接口的返回参数不变,但开发者获取的userInfo为匿名信息。 [图片] 此外,针对scope.userInfo将做如下调整: 若开发者调用wx.authorize接口请求scope.userInfo授权,用户侧不会触发授权弹框,直接返回授权成功若开发者调用wx.getSetting接口请求用户的授权状态,会直接读取到scope.userInfo为true新增getUserProfile接口 若开发者需要获取用户的个人信息(头像、昵称、性别与地区),可以通过wx.getUserProfile接口进行获取,该接口从基础库2.10.4版本开始支持,该接口只返回用户个人信息,不包含用户身份标识符。该接口中desc属性(声明获取用户个人信息后的用途)后续会展示在弹窗中,请开发者谨慎填写。开发者每次通过该接口获取用户个人信息均需用户确认,请开发者妥善保管用户快速填写的头像昵称,避免重复弹窗。 插件用户信息功能页 插件申请获取用户头像昵称与用户身份标识符仍保留功能页的形式,不作调整。用户在用户信息功能页中授权之后,插件就可以直接调用 wx.login 和 wx.getUserInfo 。 三、最佳实践调整后,开发者如需获取用户身份标识符只需要调用wx.login接口即可。 开发者若需要在界面中展示用户的头像昵称信息,可以通过<open-data>组件进行渲染,该组件无需用户确认,可以在界面中直接展示。 在部分场景(如社交类小程序)中,开发者需要在获取用户的头像昵称信息,可调用wx.getUserProfile接口,开发者每次通过该接口均需用户确认,请开发者妥善处理调用接口的时机,避免过度弹出弹窗骚扰用户。 微信团队 2021年4月15日
2021-04-15 - [拆弹时刻]4月13日前更新wx.getUserInfo和getUserProfile授权获取问题的解决方案
[图片] 论坛里有不少人疑惑新版的getUserProfile是不是已经上,同时发现开发环境中原有的老方法已经不支持了,这里我为大家集中解决下疑惑~ 1、线上是否已经不支持wx.getUserInfo老方法了? 支持!目前,根据官方文档说明:在4月13日前发布的,线上环境2.16.0基础库以下已经不支持老版方法。(4月8日更新,怀疑官方已提前发布) [图片] 本人今天4月6日10点半线上支持老方法,但是4月8日发布2.16.0以下低版本已经去掉弹窗授权了。请大家尽快更新发布新版方法 2、哪些环境已经是新方法了? 开发环境(包括但不限于IDE工具,真机调试),微信后台提供的体验版环境,且已不支持老办法。 3、新老两种方法是否并行? 线上环境:目前并行(4月13日前),但getUserProfile新方法 只在2.10.4以上版本支持。 开发环境和体验版:不并行,不支持左右横跳哈。 4、如何解决兼容性适配? 上才艺啦,呸,上代码。 先来看下原本代码的授权逻辑 [代码]//老的逻辑 wx.getSetting({ async success(res) { console.log(res.authSetting); //判断小程序用户是否授权 if (res.authSetting['scope.userInfo']) { //已授权 } else { //未授权情况 } } }) [代码] 之前主要通过wx.getSetting的方法来判断,而现在重大的改变是老方法getUserInfo不再弹窗,就算改成getUserProfile弹窗授权,新方法中getSetting中scope.userInfo 这个值并没有返回(这里跟文档有些出入,不知道官方后面会不会修正) [图片] [图片] 同时,这里需要做兼容判断,把获取到内容存在数据库中,避免反复弹窗骚扰用户。 [代码]//根据官方文档 做了一些修改 Page({ data: { userInfo: {}, hasUserInfo: false, canIUseGetUserProfile: false, }, onLoad() { //先请求自定义接口,获取上次存的useInfo wx.request({ url: 'test.api', //仅为示例,并非真实的接口地址 data: {}, success (res) { console.log(res.useInfo) //判断之前是否已获取并存储过用户信息 if(res.useInfo){ }else { //这里不要使用 wx.canIuse来判断,避免一些适配问题 if (wx.getUserProfile) { //直接使用官方推荐的方法 this.setData({ canIUseGetUserProfile: true }) } } } }) }, getUserProfile(e) { // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认 // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗 wx.getUserProfile({ desc: '需要你的信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写 success: (res) => { //这里需要将获取的 res.userInfo 存起来,你可以存在数据库,也可以存在local storage里 //wx.request...请求接口 this.setData({ userInfo: res.userInfo, hasUserInfo: true }) } }) }, getUserInfo(e) { // 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息 this.setData({ userInfo: e.detail.userInfo, hasUserInfo: true }) }, }) [代码] [代码]<!-- html部分 ---> <block wx:if="{{!hasUserInfo}}"> <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button> <button wx:else open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button> </block> <block wx:else> <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </block> [代码] [图片] 5、如何更新用户信息? 首先,目前的规则如果不弹窗,肯定是无法每次拿到用户的最新信息,为了避免每次弹窗请求授权骚扰用户,所以最好根据产品规划,定期获取用户的信息(看心情)。 6、官方接口文档 https://developers.weixin.qq.com/community/develop/doc/000cacfa20ce88df04cb468bc52801 觉得有用,请点个赞哦,让我继续分享更有动力~
2021-04-08 - 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 - wx.request 经 Promise 封装后,如何拿到requestTask
大家会用 promise 将 wx.request 包装一层。但经过这么一层包装后,就拿到不到 requestTask,从而调用不了 abort 方法。大家都是如何解决的? 代码来自:https://www.kancloud.cn/xiaoyulive/wechat/526990 [代码]class Request {[代码][代码] [代码][代码]constructor (parms) {[代码][代码] [代码][代码]this[代码][代码].withBaseURL = parms.withBaseURL[代码][代码] [代码][代码]this[代码][代码].baseURL = parms.baseURL[代码][代码] [代码][代码]}[代码][代码] [代码][代码]get (url, data) {[代码][代码] [代码][代码]return[代码] [代码]this[代码][代码].request([代码][代码]'GET'[代码][代码], url, data)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]post (url, data) {[代码][代码] [代码][代码]return[代码] [代码]this[代码][代码].request([代码][代码]'POST'[代码][代码], url, data)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]put (url, data) {[代码][代码] [代码][代码]return[代码] [代码]this[代码][代码].request([代码][代码]'PUT'[代码][代码], url, data)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]request (method, url, data) {[代码][代码] [代码][代码]const vm = [代码][代码]this[代码][代码] [代码][代码]return[代码] [代码]new[代码] [代码]Promise((resolve, reject) => {[代码][代码] [代码][代码]wx.request({[代码][代码] [代码][代码]url: vm.withBaseURL ? vm.baseURL + url : url,[代码][代码] [代码][代码]data,[代码][代码] [代码][代码]method,[代码][代码] [代码][代码]success (res) {[代码][代码] [代码][代码]resolve(res)[代码][代码] [代码][代码]},[代码][代码] [代码][代码]fail () {[代码][代码] [代码][代码]reject({[代码][代码] [代码][代码]msg: [代码][代码]'请求失败'[代码][代码],[代码][代码] [代码][代码]url: vm.withBaseURL ? vm.baseURL + url : url,[代码][代码] [代码][代码]method,[代码][代码] [代码][代码]data[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码]}[代码] [代码]const request = [代码][代码]new[代码] [代码]Request({[代码][代码] [代码][代码]baseURL: [代码][代码]'http://test'[代码][代码],[代码][代码] [代码][代码]withBaseURL: [代码][代码]true[代码][代码]})[代码] [代码]module.exports = request[代码]
2019-03-22