- setData() 要设置超过 1M后页面后白屏了,如何解决?
当 setData() 要设置超过 1M 的数据时怎么办?
05-13 - 小程序实现高性能虚拟列表优化+节流+分页请求(固定高度)
场景引入 为什么需要用到高性能虚拟列表+节流+分页请求的优化? 当有场景需求为需要将大量数据(10000条)呈现在一页上,我们不断下拉访问,页面中有大量的数据列表的时候,用户会不会有不好的体验?会不会出现滚动不流畅而卡顿的情况?会不会因卡顿而出现短暂的白屏现象(数据渲染不成功)? 通过微信开发者工具自带的调试器->Network页面,我们可以观察到当有长列表时如果不使用高性能虚拟列表+节流+分页请求的优化,会出现以下问题: FPS:每秒帧数,图表上的红色块表示长时间帧,很可能会出现卡顿。 CPU:CPU消耗占用,实体图越多消耗越高。 NET:网络请求效率低,一次性请求10000条的渲染效率远远低于分1000次,每次请求10条数据 内存:滑动该列表时明显能看到内存消耗大。 总结:如果需要将大量数据(10000条)呈现在一页上,可以通过高性能虚拟列表+节流+按需请求分页数据并追加显示。 优化的具体实现可拆分为以下需求(将一个大问题拆分为一个个小问题并逐个去解决): 不把长列表数据一次性全部直接显示在页面上。 截取长列表一部分数据用来填充屏幕容器区域。 长列表数据不可视部分使用使用空白占位填充。 监听滚动事件根据滚动位置动态改变可视列表。 监听滚动事件根据滚动位置动态改变空白填充。 分页从服务器请求数据,将一次性请求所有数据变为滚动到底部才再次向服务器发送获取数据的请求 开始实战 此次实例我设定每个元素的固定高度为210rpx [图片] (1)首先计算屏幕内的容积最大容量(即屏幕一次性可以容纳多少个高度为210rpx的元素) 调用wx.createSelectorQuery()的api [图片] 上图是id为scrollContainer的组件,滚动时触发函数handleScroll() [代码]// 计算容器的最大容积,onReady中触发,即初次渲染时触发 getContainSize() { wx.createSelectorQuery().select('#scrollContainer').boundingClientRect(function (rect) { rect.id // 节点的ID rect.dataset // 节点的dataset rect.left // 节点的左边界坐标 rect.right // 节点的右边界坐标 rect.top // 节点的上边界坐标 rect.bottom // 节点的下边界坐标 rect.width // 节点的宽度 rect.height // 节点的高度 }).exec((option) => { // console.log(~~(option[0].height / this.data.oneHeight) + 2); this.data.containSize = ~~(option[0].height / this.data.oneHeight) + 2; }) }, [代码] 调用api后返回的option中的height就是小程序页面的视口高度,除以oneHeight(210rpx)就是能容纳的个数,用[代码]~~[代码]来对结果进行向下取整(实际能容纳的应+1),由于在滚动时会出现上一个元素在上边界还没完全消失,第四个元素就从下边界进入视口了,因此最大容纳量应再+1。即实际容纳量应该如图最后一行代码所示+2. [图片] (2)监听滚动事件动态截取数据 监听用户滚动、滑动事件,根据滚动位置,动态计算当前可视区域起始数据的索引位置 startIndex,再根据containsize,计算结束数据的索引位置 endIndex,最后根据 startIndex与endIndex截取长列表所有数据a11Datalist 中需显示的数据列表 showDatalist。 PS:下列代码中函数handleScroll()最下面的[代码]this.setDataStartIndex(data);[代码]才是滚动时真正进行的滚动事件动态截取数据,上面那些代码用途在文章后面部分再详细介绍(节流)。 [代码]// 定义滚动行为事件方法 handleScroll(data) { if (this.data.isScrollStatus) { this.data.isScrollStatus = false; // 节流,设置一个定时器,1秒以后,才允许进行下一次scroll滚动行为 let mytimer = setTimeout(() => { this.data.isScrollStatus = true; clearTimeout(mytimer); }, 17) this.setDataStartIndex(data); } }, [代码] [代码]// 执行数据设置的相关任务, 滚动事件的具体行为 setDataStartIndex(data) { // console.log("scroll active") this.data.startIndex = ~~(data.detail.scrollTop / this.data.oneHeight); // 通过scrollTop滑动后距离顶部的高度除以每个元素的高度,即可知道目前到第几个元素了 this.setData({ showDataList: this.data.allDatalist.slice(this.data.startIndex, this.data.endIndex) }) // 动态截取实际拥有10000条数据的数组中下标为startIndex到endIndex的数组出来呈现在前端页面上 // 容器最后一个元素的索引 if (this.data.startIndex + this.data.containSize <= this.data.allDatalist.length - 1) { this.setData({ endIndex: (!this.data.allDatalist[this.data.endIndex]) ? this.data.allDatalist.length - 1 : this.data.startIndex + this.data.containSize // 滚动到底部了吗,是的话那就将endIndex设置为9999,不然的话设置为startIndex+视口最大容量 }) } else { console.log("滚动到了底部"); this.data.pageNumNow++; // 例如一次性从数据库拿10条数据赋值到allDataList,如果滚动到底部(即allDataList所有数据都已经呈现了),那就再次向服务器发送请求获取数据库中的下10条数据 this.addMes(); // 该函数内就写你实际向数据库请求时的代码,请求成功后拼接到allDataList即可 console.log(this.data.allDatalist.length) } }, [代码] (3)使用计算属性动态设置上下空白占位 我们设置了根据容器滚动位移动态截取ShowDatalist 数据,现在我们滚动一下发现滚动2条列表数据后,就无法滚动了,这个原因是什么呢? 在容器滚动过程中,因为动态移除、添加数据节点丢失,进而强制清除了顶部列表元素DOM节点,导致滚动条定位向上移位一个列表元素高度,进而出现了死循环根据 startIndex和endIndex的位置,使用计算属性,动态的计算并设置,上下空白填充的高度样式blankFi11Sty1e,使用padding或者margin 进行空白占位都是可以的 PS:由于小程序没有computed,所以为了使用计算属性,得另外引入封装好computed的包,引入computed组件的教程我放在最后,我使用的是官方推荐的computed,且注意使用该插件时,不要加[代码]this -> this.data[代码],直接data即可 [代码]computed: { // 定义上空白高度 topBlankFill(data) { // console.log("change") return data.startIndex * data.oneHeight; }, // 定义下空白高度 BottomBlankFill(data) { return (data.allDatalist.length - data.endIndex) * data.oneHeight; }, // 定义一个 待显示的数组列表元素 showDataList(data) { // console.log(data.allDatalist.slice(data.startIndex, data.endIndex)) return data.allDatalist.slice(data.startIndex, data.endIndex) }, }, [代码] (4)下拉置底自动请求加载数据 下方代码中[代码]setDataStartIndex()[代码]函数末尾的if-else便是判断是否已经滚动到现有全部数据的allDataList数组是否已经滚动到底部,全部呈现完了。 如果是那就执行else部分,请求的页数pageNumNow+1,然后调用addMes()请求数据,然后将新请求到的数据进行拼接到allDataList上 [代码]// 执行数据设置的相关任务, 滚动事件的具体行为 setDataStartIndex(data) { // console.log("scroll active") this.data.startIndex = ~~(data.detail.scrollTop / this.data.oneHeight); this.setData({ showDataList: this.data.allDatalist.slice(this.data.startIndex, this.data.endIndex) }) // 容器最后一个元素的索引 if (this.data.startIndex + this.data.containSize <= this.data.allDatalist.length - 1) { this.setData({ endIndex: (!this.data.allDatalist[this.data.endIndex]) ? this.data.allDatalist.length - 1 : this.data.startIndex + this.data.containSize }) } else { console.log("滚动到了底部"); this.data.pageNumNow++; this.addMes(); console.log(this.data.allDatalist.length) } }, // 根据接口数据来给数组添加真实数据 addMes: function () { this.list() .then(res => { // console.log(res.result.data); // 将接口获取到得所有数据存储起来 this.data.allDatalist = this.data.allDatalist.concat(res.result.data); // 设置初始显示列表 this.setData({ showDataList: this.data.allDatalist.slice(0, 5) }) }) }, // 以下为获取数据 list: function () { let pageNum = this.data.pageNumNow; let pageSize = 20; // console.log('当前请求的页码为:' + pageNum); return new Promise((resolve, reject) => { wx.cloud.callFunction({ name: 'teacher', data: { action: 'list', pageNum, pageSize } }).then(res => { resolve(res) }).catch(err => { reject(err) }) }) }, [代码] 到此处,高性能虚拟列表+分页请求的优化已经搞定了,下面开始节流的优化 (5)滚动事件节流定时器优化 由于监听滚动事件触发对应函数方法的频率是极高的,因此页面节流优化是必须的。 方法:在data中声明一个属性scro11State用来记录滚动状态,只有scro11State值为true的时候才会具体执行 PS:下面代码中定时器设置为17ms的原因是,由于小程序没有web的requestAnimationFrame的api,无法作滚动事件节流请求动画帧优化,因此可以手动计算显示器的帧率,大概一帧在17ms,因此定时器设置为17ms [代码]// 定义滚动行为事件方法 handleScroll(data) { //节流部分代码 if (this.data.isScrollStatus) { this.data.isScrollStatus = false; // 节流,设置一个定时器,1秒以后,才允许进行下一次scroll滚动行为 let mytimer = setTimeout(() => { this.data.isScrollStatus = true; clearTimeout(mytimer); }, 17) //节流部分代码 this.setDataStartIndex(data); } }, [代码] 到这为止,节流也搞定啦,觉得从中学到了许多的小伙伴不妨点个赞。 小程序如何引入computed计算属性请参考我的这篇文章:https://developers.weixin.qq.com/community/develop/article/doc/000a4442bd44c84e740d6b6b051413 在后续我也会将高性能虚拟列表+节流的优化封装成一个插件,给小伙伴们直接使用,欢迎关注我以便及时获取到我文章的更新~ 在这里也推荐一篇防抖和节流的性能优化知识介绍的文章:https://segmentfault.com/a/1190000018428170 觉得有帮助的小伙伴欢迎点赞,有其他问题也欢迎在评论区提出
2021-11-11 - 使用wx.request发送multipart/form-data请求的方法
社区里有不少关于wx.request发送multipart/form-data请求的问题,我看了几个,基本都说wx.request不支持,得用wx.uploadFile来“曲线救国”。我花了些时间研究了一下,找到了如下方法,分享出来,希望能帮助到有需要的人。 首先,服务端,我用的是async-busboy来解析multipart/form-data请求,测试代码直接使用它提供的就可以:https://github.com/m4nuC/async-busboy/blob/master/examples/index.js 小程序端: 如果wx.request中仅指定'content-type'为'multipart/form-data',服务端会报"no multipart boundary was found"的错误,就像这个帖子里描述的:https://developers.weixin.qq.com/community/develop/doc/000e64476f8d80fd7b9765d5655c00 尽管指定了content-type为'mutipart/form-data',但是wx.request并不会为我们自动添加boundary,既然如此,我们自己加不就可以了吗?经过测试,确认了这个方法可行,前端示例代码如下: [代码]wx.request({[代码][代码] [代码][代码]url: [代码][代码]'http://localhost:8080/test/multipart-form'[代码][代码],[代码][代码] [代码][代码]method: [代码][代码]'POST'[代码][代码],[代码][代码] [代码][代码]header: {[代码][代码] [代码][代码]'content-type'[代码][代码]: [代码][代码]'multipart/form-data; boundary=XXX'[代码][代码] [代码][代码]},[代码][代码] [代码][代码]data: [代码][代码]'\r\n--XXX'[代码] [代码]+[代码][代码] [代码][代码]'\r\nContent-Disposition: form-data; name="field1"'[代码] [代码]+[代码][代码] [代码][代码]'\r\n'[代码] [代码]+[代码][代码] [代码][代码]'\r\nvalue1'[代码] [代码]+[代码][代码] [代码][代码]'\r\n--XXX'[代码] [代码]+[代码][代码] [代码][代码]'\r\nContent-Disposition: form-data; name="field2"'[代码] [代码]+[代码][代码] [代码][代码]'\r\n'[代码] [代码]+[代码][代码] [代码][代码]'\r\nvalue2'[代码] [代码]+[代码][代码] [代码][代码]'\r\n--XXX--'[代码][代码] [代码][代码]})[代码]这里,我向服务端传递了两个参数:field1=value1, field2=value2,服务端得到的结果如下: [图片] 如果对multipart/form-data的请求体格式不了解,可以参考这篇文章:https://ec.haxx.se/http-multipart.html wx.request中的data结构看起来有些复杂,有需要的可以封装一下,封装好的API欢迎跟帖分享出来。
2019-08-10 - 关于新版隐私协议接口wx.onNeedPrivacyAuthorization的适配解读以及实现代码
官方公告地址: https://developers.weixin.qq.com/community/develop/doc/00042e3ef54940ce8520e38db61801 目前,开发工具或者体验版的小程序,调试基础库如果是2.33.0及以上就得适配了,线上版本9月15日之后生效,所以这之前需要尽快改完,发布一版,否则到了9月15号之后 线上就会生效报错了。 其实改起来也很简单,以下是实现步骤和代码: 1、首先看一下这个网址,里边包含涉及到的隐私的接口,这些接口都要适配一下 https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/miniprogram-intro.html [图片] 在以上接口用到的页面,需要画一下类似上边的弹窗(这个弹窗可以全局定义个组件,方便多个页面共用),然后里边蓝字可以点击后调用wx.openPrivacyContract(Object object)接口即可,会自动跳转打开隐私协议页面。 拒绝按钮可以加一个点击事件,然后在事件里这样写 [图片] 同意按钮比较特殊,布局需要用button这样写,记得给button加一个Id [图片] 然后在handleAgreePrivacyAuthorization里就可以获取到点击事件,这样写 [图片] 2、最后需要在onLoad或者onShow里加上以下监听代码,在这里边让自定义的隐私弹窗显示出来即可。 [图片] 以上代码加上就可以了,如果业务逻辑用到了需要判断是否授权过,可以加上 wx.getPrivacySetting(Object object)去获取是否授权过,用不到可以不加这个判断。
2023-08-16 - 小程序隐私弹框组件
一、前言 文章这个编辑器真的太恶心了 代码块不仅不能设置缩进还不能使用tab增加缩进只能空格一个一个打 调个字体和代码格式太TM麻烦了 2023.08.22更新: 以下指南中涉及的 getPrivacySetting、onNeedPrivacyAuthorization、requirePrivacyAuthorize 等接口目前可以正常接入调试。调试说明: 在 2023年9月15号之前,在 app.json 中配置 [代码]__usePrivacyCheck__: true[代码] 后,会启用隐私相关功能,如果不配置或者配置为 false 则不会启用。在 2023年9月15号之后,不论 app.json 中是否有配置 [代码]__usePrivacyCheck__[代码],隐私相关功能都会启用首先了解官方提供demo: demo1: 演示使用 wx.getPrivacySetting 和 <button open-type="agreePrivacyAuthorization"> 在首页处理隐私弹窗逻辑 https://developers.weixin.qq.com/s/gi71sGm67hK0 demo2: 演示使用 wx.onNeedPrivacyAuthorization 和 <button open-type="agreePrivacyAuthorization"> 在多个页面处理隐私弹窗逻辑,同时演示了如何处理多个隐私接口同时调用。 https://developers.weixin.qq.com/s/4X7yyGmQ7EKp demo3: 演示 wx.onNeedPrivacyAuthorization、wx.requirePrivacyAuthorize、<button open-type="agreePrivacyAuthorization"> 和 <input type="nickname"> 组件如何结合使用 https://developers.weixin.qq.com/s/jX7xWGmA7UKa demo4: 演示使用 wx.onNeedPrivacyAuthorization 和 <button open-type="agreePrivacyAuthorization"> 在多个 tabBar 页面处理隐私弹窗逻辑。 https://developers.weixin.qq.com/s/g6BWZGmt7XK9 小程序后台隐私协议添加对应隐私协议(例如:剪贴板、手机号授权等)协议更新后有一定时间的缓存才能测试(大概是在半天之后可以测试)优先处理添加协议 (如果之前小程序已经添加过对应的协议,则需要重新提交审核同步信息才会返回正常用来测试)基础库设置3.23.3及以上 (微信版本:iOS 8.0.36、安卓 8.0.35会携带此基础库版本)app.json文件内这个配置__usePrivacyCheck__属性为true (uniapp开发参考下图) [图片]开发工具清除全部缓存之后重新编译、手机微信下拉删除最近小程序等进行隐私协议同步信息获取测试二、关于小程序隐私保护指引设置的公告为规范开发者的用户个人信息处理行为,保障用户的合法权益,自2023年9月15日起,对于涉及处理用户个人信息的小程序开发者, 微信要求,仅当开发者主动向平台同步用户已阅读并同意了小程序的隐私保护指引等信息处理规则后,方可调用微信提供的隐私接口。 开发者首先需确定小程序是否涉及处理用户个人信息,如涉及,则需配置用户隐私授权弹窗, 且仅有在平台《小程序用户隐私保护指引》中声明了所处理的用户个人信息,才可以调用平台提供的对应接口或组件。 三、设置《小程序用户隐私保护指引》 [图片] [图片] 四、填写《小程序用户隐私保护指引》[图片] 只有在指引中声明所处理的用户个人信息,才可以调用平台提供的对应接口或组件。若未声明,对应接口或组件将无法调用成功。 五、配置用户隐私授权弹窗 (触发方式:隐私API、组件) 1.了解隐私协议所需相关API 01.wx.requirePrivacyAuthorize() 用于模拟隐私接口调用 02.wx.openPrivacyContract() 用于打开下面封装好隐私弹框中的隐私协议 03.wx.onNeedPrivacyAuthorization() 用于监听用户是否吊起相关的隐私协议 04.wx.getPrivacySetting() 用于查询微信侧记录用户隐私协议状态 基本流程为:03 -----> 04 -----> 02 2.封装微信侧用户隐私协议状态 //获取微信侧同步的用户隐私协议开关 function getPrivacySetting(callback){ if(wx.getPrivacySetting){ wx.getPrivacySetting({ success: result => { console.log(result,"同步信息结果") // 返回结果为: result = { needAuthorization: true/false, privacyContractName: '《xxx隐私保护指引》' } if (result.needAuthorization) { // 需要弹出隐私协议 console.log("获取微信储存的用户协议同步信息-用户未同意,请弹框处理") callback && callback(result); } else { console.log("获取微信储存的用户协议同步信息-用户已同意,请忽略") } }, fail: () => {}, complete: () => {} }) } } 3.封装隐私协议弹框放置全局组件中 全局组件-wxml 用户隐私保护提示 亲爱的用户,感谢您的信任!您使用本小程序提供的产品服务前应当阅读并同意 {{urlTitle}} 当您点击同意并开始使用产品服务时,即表示你已理解并同息该条款内容,该条款将对您产生法律约束力。如您拒绝,将无法使用本小程序提供的相关产品及服务。 不同意 同意 全局组件-JS JS//引入相关模块 const { getPrivacySetting } = require("../utils/utils") //设置协议回调、关闭回调数组变量 let privacyHandler let privacyResolves = new Set() let closeOtherPagePopUpHooks = new Set() //注册并监听隐私API调用 if (wx.onNeedPrivacyAuthorization) { wx.onNeedPrivacyAuthorization(resolve => { //获取用户微信侧同步结果 getPrivacySetting(result=>{ console.log(result,"同步信息结果") // 返回结果为: result = { needAuthorization: true/false, privacyContractName: '《xxx隐私保护指引》' } if (result.needAuthorization) { // 需要弹出隐私协议 if (typeof privacyHandler === 'function') privacyHandler(resolve,result) console.log("获取微信储存的用户协议同步信息-用户未同意,请弹框处理-页面") } }) }) } //处理关闭页面其他弹框 const closeOtherPagePopUp = (closePopUp) => { closeOtherPagePopUpHooks.forEach(hook => { if (closePopUp !== hook) { hook() } }) } Component({ options: { styleIsolation: 'shared', multipleSlots: true }, data: { urlTitle: "", privacyFlag: false, height: 0, }, lifetimes: { attached: function() { const closePopUp = () => { this.disPopUp() } privacyHandler = (resolve,result) => { this.setUrlTitle(result); console.log(result,"999999") privacyResolves.add(resolve) this.popUp() // 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗 closeOtherPagePopUp(closePopUp) } closeOtherPagePopUpHooks.add(closePopUp) this.closePopUp = closePopUp }, detached: function() { closeOtherPagePopUpHooks.delete(this.closePopUp) } }, methods: { //处理隐私弹框内展示的隐私协议标题 setUrlTitle(result){ this.setData({urlTitle:result.privacyContractName}) }, //用户同意相关协议 handleAgree(e) { this.disPopUp() // 这里演示了同时调用多个wx隐私接口时要如何处理:让隐私弹窗保持单例, 点击一次同意按钮即可让所有pending中的wx隐私接口继续执行 (看page/index/index中的 wx.getClipboardData 和 wx.startCompass) privacyResolves.forEach(resolve => { resolve({ event: 'agree', buttonId: 'agree-btn' }) }) privacyResolves.clear() this.triggerEvent('handleAgree', {result:true}); }, //用户拒绝相关协议 handleDisagree(e) { this.disPopUp() privacyResolves.forEach(resolve => { resolve({ event: 'disagree', }) }) privacyResolves.clear() this.triggerEvent('handleAgree', {result:true}); }, //打开隐私弹框 popUp() { if (this.data.privacyFlag === false) { this.setData({ privacyFlag: true }) } }, //关闭隐私弹框 disPopUp() { if (this.data.privacyFlag === true) { this.setData({ privacyFlag: false }) } }, //打开微信提供的小程序侧隐私协议 openPrivacyContract() { wx.openPrivacyContract({ success: res => { console.log('openPrivacyContract success') }, fail: res => { console.error('openPrivacyContract fail', res) } }) }, checkPrivacyStatus() { let that = this; getPrivacySetting(result=>{ if (result.needAuthorization) { that.setUrlTitle(result); that.popUp(); } else { console.log("微信侧已记录用户同意"); } }) } } }) 父组件监听全局隐私弹框时间回调 父组件-wxml 父组件-JS //隐私协议弹框按钮回调 handleAgree(event){ let that = this; console.log(event); let {result} = event.detail; // result 根据用户选择可处理后续相关逻辑 true 用户同意相关隐私 例如:因昵称组件弹出的框 为true之后就可将昵称组件设置为聚焦状态 false 用户拒绝 例如:音视频组件 用户拒绝之后可返回上级页面、继续弹框进行处理等 }, 4.app.json中引入全局组件:"privacy-popup": "/components/privacyPopup" 5.全局使用(隐私弹框) 全局隐私弹框相关代码如下: 首页wxml引入组件 首页onReady时触发检测隐私弹框逻辑 that.selectComponent("#privacyPop") && that.selectComponent("#privacyPop").checkPrivacyStatus(); 6.按需使用(隐私弹框)进入单个隐私页面时优先检测(手机号授权API触发隐私弹框) 6-1.1.引入全局组件到手机号授权页面: 6-1.2.页面onReady的时候开始检测:that.selectComponent("#privacyPop") && that.selectComponent("#privacyPop").checkPrivacyStatus(); 6-2.1.在用户点击按钮即将吊起隐私弹框时使用如下代码: getPrivacySetting(result=>{ if (result.needAuthorization) { // 需要弹出隐私协议 that.selectComponent("#privacyPop") && that.selectComponent("#privacyPop").setUrlTitle(result); console.log("获取微信储存的用户协议同步信息-用户未同意,请弹框处理") } else { console.log("获取微信储存的用户协议同步信息-用户已同意,请忽略") //走隐私允许之后的逻辑 } }) 六、清空历史同步状态 1.微信下拉---最近---最近使用的小程序 2.开发工具---清除模拟器缓存---清除全部数据/授权数据 七、常见错误说明{ "errMsg": "A:fail api scope is not declared in the privacy agreement", "errno": 112 } 使用到了 A 隐私接口,但是开发者未在[mp后台-设置-服务内容声明-用户隐私保护指引]中声明收集 A 接口对应的隐私类型。还有个114的忘记统计是啥原因了(希望官方尽快能把错误码及对应的原因贴出来)
2023-10-12 - 《小程序隐私保护指引》开发指南
2024年4月26日 更新: 基础库 3.4.2 更新 API 有授权弹窗的隐私接口(例如 wx.getLocation),将不会再弹官方隐私弹窗,而是在授权弹窗上增加“隐私勾选”;无授权弹窗的隐私接口(如 wx.getClipboardData),将继续保留原来的官方隐私弹窗 2023年12月25日 更新: 未授权时,input type="nickname" 会自动降级为 input type="text" 且 input 无法拉起官方隐私授权弹窗,需要按开发步骤 4、5 自行处理 2023年10月19日 更新: 已经过了官方公告的 10 月 17 日了,线上依然未生效。除非在 app.json 中手动配置 "__usePrivacyCheck__": true 来开启。 目前的处理方式为,把报错信息转换为中文 if (message.match('privacy permission is not authorized')) message = '授权失败,请同意《用户隐私保护指引》' 2023年9月14日 更新: 可以不开发,直接使用系统弹窗,见图 3 。生效日期延长到 10 月 17日了。 《墨问便签》目前已回退了版本。 https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/PrivacyAuthorize.html#六、官方隐私弹窗功能说明 官方公告里目前的机制,目前还没生效上线,但看起来有 2 个坑: 1. 必须 wx.onNeedPrivacyAuthorization 上报,才能弹自定义弹窗。 意味着方案 1,如果用户取消授权了,是没有上报的,只能选择执行退出小程序。 否则调用隐私接口时,系统弹窗会出现。 2. 用户取消授权后,间隔 10s,才能再次请求授权。 意味着 10s 内功能不可用,需要给用户一个提示语。而系统默认的报错不会 toast,以及报错信息是英语,用户也看不懂呀。 ——————— 总体思路: 目前有两个方案可供选择 1. 小程序启动时,弹出,拒绝后直接退出小程序(只用 wx.getPrivacySetting 即可) 2. 调用隐私接口前,弹出,同意后才调用隐私接口(不用 wx.getPrivacySetting,需要使用 wx.requirePrivacyAuthorize 和 wx.onNeedPrivacyAuthorization) 推荐方案 2,下面具体说说 事前准备: 1. 在文档中查看是否使用了相关隐私接口 https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/miniprogram-intro.html 2. 在「小程序管理后台」设置《小程序用户隐私保护指引》 见图 1,具体位置是:小程序后台 - 设置 - 基本设置 - 服务内容声明 - 用户隐私保护指引 [图片] 3. 如果有用到隐私接口,则需要进行小程序开发。如果没有用到隐私接口,则无需开发 实际运行的流程为: 1. 发起授权 wx.requirePrivacyAuthorize 2. 触发授权事件 wx.onNeedPrivacyAuthorization 3. 弹窗,用户点击同意或拒绝 执行第 2 步的回调函数的 resolve 方法 4. 继续或终止流程 执行第 1 步的回调函数的 success 或者 fail 方法 5. 如果继续流程,调用隐私相关 api(录音、相册等等)或者 input (微信昵称)去聚焦 开发步骤: 开发前,先在小程序 app.json 配置中添加 "__usePrivacyCheck__": true 1. 开发弹窗,并且在需要的页面注册使用。“同意” 按钮需要符合规范 同意 不同意 如果需要查看隐私协议,可以使用 wx.openPrivacyContract({}) [图片] 2. 使用 wxToPromise 方法,便于使用 await 处理相关接口 function wxToPromise (api, option) { // API 存在判断 if (!api) { wx.showModal({ title: '提示', content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。' }) return Promise.reject() } return new Promise((resolve) => { api({ ...option, success (res) { resolve([res, undefined]) }, fail (err) { resolve([undefined, err]) }, }) }) } 3. app.ts 里在 onLaunch 方法里添加触发授权事件监听 // 导入弹窗的弹出方法 import { showModal } from './components/modal/showModal' // 需要用户同意隐私授权时 if (wx.onNeedPrivacyAuthorization) wx.onNeedPrivacyAuthorization(async (resolve) => { // 触发弹窗 const [res] = await wxToPromise(showModal, { title: '个人信息保护提示', }) // 点击同意 if (res?.confirm) { resolve({ buttonId: 'agree-btn', event: 'agree' }) // 点击拒绝 } else if (res?.cancel) { resolve({ buttonId: 'disagree-btn', event: 'disagree' }) } }) 4. 封装请求隐私授权方法 import { wxToPromise } from './wxToPromise' /** * 请求隐私授权 * @return 是否已授权 */ export const requirePrivacyAuthorize = async () => { if (typeof wx.requirePrivacyAuthorize === 'function') { const [res] = await wxToPromise(wx.requirePrivacyAuthorize) if (res) return true // < 2.33.2 基础库,无 api 默认已授权 } else { return true } return false } 5. 业务页面触发隐私授权,并且在回调里调用隐私接口 例如处理微信昵称时 // wxml 用户昵称 // js /** * 请求隐私授权 * 未授权时,input type="nickname" 会自动降级为 input type="text" */ async requirePrivacyAuthorize () { if (wx.requirePrivacyAuthorize) { [res, err] = await wxToPromise(wx.requirePrivacyAuthorize) if (res) // 成功 - 继续调用隐私接口 if (err) // 失败 - 终止流程,或者走兜底方案 } // PC 端 api 不存在,可以继续调用隐私接口 // 成功后去聚焦,失败后降级为普通文本框,依然去聚焦 this.setData({nickNameInputFocus: true}) } 如果只需要成功后,才执行后续逻辑。失败时不执行。 可以这样写: async requirePrivacyAuthorize () { // 请求隐私授权 if (!await requirePrivacyAuthorize()) return this.setData({nickNameInputFocus: true}) } 例外情况,无需主动调用 requirePrivacyAuthorize,也能正常触发 wx.onNeedPrivacyAuthorization: 1. 写入剪贴板:wx.setClipboardData 2. 读取剪贴板:wx.getClipboardData 3. 获取微信头像: 常见问题: 1. 已经同意过隐私协议,如何清除掉状态? 答:把最近使用中的小程序删除 2. 如果不同意隐私协议,下次调用隐私接口时,还能触发 onNeedPrivacyAuthorization 吗? 答:可以 3. onNeedPrivacyAuthorization 多次注册,会重复监听吗? 答:会的,所以只在 app.js 里启动小程序时注册一次 4. 如果不使用 requirePrivacyAuthorize 主动触发,隐私协议和权限弹窗,哪个会先弹? 答:权限弹窗会先弹。如果拒绝权限后,不会弹隐私协议(onNeedPrivacyAuthorization 无法触发) 5. 隐私协议需要每个权限都弹一次吗? 答:如果用户同意了,则只弹一次,后续不再弹出,除非移除了小程序。如果用户一直拒绝,则需要每次调用权限之前弹出。 6. 以前我记得写入相册是系统弹窗,现在要改成自己写弹窗吗,还是说两个都要弹? 答:如果没有同意过隐私协议,并且没有授权过权限,两个都弹。 7. 如果版本是2.32.3 以下(不含)是否可以使用button的agreePrivacyAuthorization进行上报? 答:从基础库 2.32.3 开始,需要做适配的。这个基础库以下的版本,没有新增的几个 API,也就不需要做隐私协议的开发适配。只需要使用 if 判断下有没有方法就行。 例如:typeof wx.getPrivacySetting === 'function' 在 2.32.3 以下(不含),会返回 false,就直接跳过相关的隐私协议逻辑,不处理 8. 可以直接调用隐私接口吗? 答:可以但不建议。原因是有个 [bug] wx.onNeedPrivacyAuthorization 在原有授权弹窗之后触发的 https://developers.weixin.qq.com/community/develop/doc/000042e44347a0a201308a5de61800 9. PC 端如何处理? 答:PC 基础库虽然达到 2.32.3 了但目前没有 wx.getPrivacySetting 等新增的方法,因此目前无需处理 10. 企业微信环境里的小程序需要调整吗? 答:企业微信小程序,基础库比较落后,现在不需要。等基础库自动升级后,未来是需要的 11. 关于小程序隐私保护指引设置的公告,是否包括小程序里嵌入的h5页面? A:暂不包括 相关资料地址: 官方 FAQ: https://developers.weixin.qq.com/community/develop/article/doc/000c648556cb40603e406c0ac6b013 关于小程序隐私保护指引设置的公告 https://developers.weixin.qq.com/community/develop/doc/00042e3ef54940ce8520e38db61801 小程序隐私协议开发指南 https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/PrivacyAuthorize.html
04-26 - SelectorQuery NodesRef.node 在 macOS 上返回 null ?
开发者工具没有问题,可以正常获取: [图片] 但是,macOS 上的预览就会出现问题: [图片] macOS:10.14.6 (18G103) 微信:Version. 2.4.2 (14931) Beta 10
2020-07-06 - 我想获取微信小程序个人用户信息(我是新手)?大佬跪谢
我看了一下相关资料。很迷茫。(刚写小程序) 据说以前能getUserProfile,能直接得到个人信息。说是2022年某月之后就不行了(no way)然后第二方法:open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar",(可行,但是不适合。需求,用户只点一次。所有的头像,nickname都要)据说wx.login 返回code,再通过/sns/jscode2session接口,返回并没有。union_id[图片] 就算拿到session_key和openid。不应该访问另一个类型于https://xxxx/getuserinfo 带上session_key和openid,直接取得用户头像和昵称? 其实根本问题就是:用户点一下授权。我就能取到。。用户的信息,大佬 们,如何搞?
2023-07-04 - wx.getUserProfile获取不到微信昵称跟头像问题?
wx.getUserProfile是停用了吗?但最近新发的小程序,有些用户还可以正常获取微信昵称和头像,部分用户可以获取微信昵称和头像
2023-01-13 - 【类知乎小房子】自定义返回键 自定义标题栏 自定义主页按钮 及参数计算
自定义顶部标题和左上角按钮方法解析及实践 前言 之前有兄台发过设置custom的方法 但是没有具体的实现方法 以至于很多不了解小程序的开发者不能循序渐进的理解制作自定义标题的方法 在这里详细总结了计算各参数的方法 我也写了一个自定义标题组件 只需要引用 直接在页面中调用即可 但因为掺杂了业务代码 需要整理过后会放出来 具体方法 首先在app.json中 将window.navigateionStyle 设置为custom [图片] 使用 wx.getSystemInfoSync 获取系统的属性 其中有顶部状态栏的高度 使用 wx.getMenuButtonBoundingClientRect 获取右上角胶囊菜单的相关属性 包括胶囊菜单的高度、相距上下左右屏幕的绝对位置 [图片] 如上图 我们需要获取四个参数 来确定整个标题栏的各项参数和左侧自定义胶囊的位置 获取顶部状态栏高度sys.statusBarHeight 具体代码 [代码]var sys= wx.getSystemInfoSync() var menu = wx.getMenuButtonBoundingClientRect() var statusHeight = menu.statusBarHeight var titleHeight = menu.height var titleRowWidth = sys.right - menu.right var titleColumnHeight = menu.top - menu.statusBarHeight [代码] 注意 小程序原生组件会遮挡自定义头部组件 如 canvas组件 input textarea的提示信息placeholder 该问题可以使用cover-view将头部定义为原生组件 设置层级解决 20191125后续更新 wx.getMenuButtonBoundingClientRect()返回undefined的情况 wx.getMenuButtonBoundingClientRect()在安卓和IOS端均会出现获取不到值的情况(返回undefined) 官方给出的答案是已经修复了该问题 但实际测试还是会出现类似问题 该问题与 平台 和 微信基础库 (随微信版本更新)无关 导致我们无法获取胶囊按钮的属性 进而无法计算header的高度 该问题极难复现 我在自己的真机上遇到过2次 在我的应用中出现概率不到1% 应对方法1: 官方建议延迟100MS 或 在返回undefined的情况下 重新获取一次 应对方法2: 判断平台 给与预估的默认值 IOS端和不同安卓端 IOS各机型的高度为44px 安卓端我测试最多的情况是48px 但安卓端实际情况需要具体测试 做进一步兼容 代码如下 [图片] 这里wx.getMenuButtonBoundingClientRect()方法在低版本微信中是不能用的 而且低版本的微信中不能使用wx.canIUse方法判断该方法是否存在 因此用捕获错误的方式兼容 在menu的属性返回undefined时 用我们预估的值去兼容 另外github.com有一个通过手机型号 返回手机各项参数的库 其中一项就是头部状态栏的高度 如果你想更准确的适配更多机型 可以使用这个库 无论哪种方法都不是最优的解决方案 大家酌情按照场景进行适配
2021-03-03