- 小程序app.onLaunch与page.onLoad异步问题的最佳实践
场景: 在小程序中大家应该都有这样的场景,在onLaunch里用wx.login静默登录拿到code,再用code去发送请求获取token、用户信息等,整个过程都是异步的,然后我们在业务页面里onLoad去用的时候异步请求还没回来,导致没拿到想要的数据,以往要么监听是否拿到,要么自己封装一套回调,总之都挺麻烦,每个页面都要写一堆无关当前页面的逻辑。 直接上终极解决方案,公司内部已接入两年很稳定: 1.可完美解决异步问题 2.不污染原生生命周期,与onLoad等钩子共存 3.使用方便 4.可灵活定制异步钩子 5.采用监听模式实现,接入无需修改以前相关逻辑 6.支持各种小程序和vue架构 。。。 //为了简洁明了的展示使用场景,以下有部分是伪代码,请勿直接粘贴使用,具体使用代码看Github文档 //app.js //globalData提出来声明 let globalData = { // 是否已拿到token token: '', // 用户信息 userInfo: { userId: '', head: '' } } //注册自定义钩子 import CustomHook from 'spa-custom-hooks'; CustomHook.install({ 'Login':{ name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } }, globalData) // 正常走初始化逻辑 App({ globalData, onLaunch() { //发起异步登录拿token login((token)=>{ this.globalData.token = token //使用token拿用户信息 getUser((user)=>{ this.globalData.user = user }) }) } }) //关键点来了 //Page.js,业务页面使用 Page({ onLoadLogin() { //拿到token啦,可以使用token发起请求了 const token = getApp().globalData.token }, onLoadUser() { //拿到用户信息啦 const userInfo = getApp().globalData.userInfo }, onReadyUser() { //页面初次渲染完毕 && 拿到用户信息,可以把头像渲染在canvas上面啦 const userInfo = getApp().globalData.userInfo // 获取canvas上下文 const ctx = getCanvasContext2d() ctx.drawImage(userInfo.head,0,0,100,100) }, onShowUser() { //页面每次显示 && 拿到用户信息,我要在页面每次显示的时候根据userInfo走不同的逻辑 const userInfo = getApp().globalData.userInfo switch(userInfo.sex){ case 0: // 走女生逻辑 break case 1: // 走男生逻辑 break } } }) 具体文档和Demo见↓ Github:https://github.com/1977474741/spa-custom-hooks 祝大家用的愉快,记得star哦
2023-04-23 - channel-video 组件如何隐藏点赞数和视频号昵称?
[图片]如何隐藏视频号昵称和点赞数,并且控制视频logo大小,有赞的是如何实现的[图片]
2024-01-02 - 隐私协议相关接口实际使用方式
隐私协议相关接口官方在回滚了多天之后终于滚回来并且提供了demo。 我这边实际研究demo后总结了实际的三种使用方法 首先要在设计到隐私协议相关功能的页面中增加隐私协议弹窗,wxml代码如下,wxss可以参考官方的demo [代码]<view wx:if="{{showPrivacy}}" class="privacy"> <view class="popup"> <view>隐私弹窗内容....</view> <view bindtap="openPrivacyAgreement">点击查看隐私协议</view> <button id="disagreeBtn" bindtap="disagreePrivacy">不同意</button> <button id="agreeBtn" open-type="agreePrivacyAuthorization" bindagreeprivacyauthorization="agreePrivacy">同意</button> </view> </view> [代码] 1、全局控制 页面加载时自动弹窗,同意后可以使用对应功能,不同意退出页面或者隐藏相关功能。 这种情况下,需要在onLoad里使用wx.getPrivacySetting获取隐私授权情况,没授权时弹出隐私弹窗。完整代码如下 [代码]Page({ data: { showPrivacy: false // 控制隐私弹窗是否展示 }, openPrivacyAgreement() { // 查看隐私协议 wx.openPrivacyContract() }, disagreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) // 这里是不同意隐私协议的后续操作,比如退出页面、隐藏相关功能等 }, agreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) // 这里是同意隐私协议的后续操作,比如展示被隐藏的相关功能 }, onLoad() { if (wx.getPrivacySetting) { wx.getPrivacySetting({ success: res => { if (res.needAuthorization) { // 打开隐私弹窗 this.setData({ showPrivacy: true }) } else { // 用户已经同意过隐私协议,直接执行同意隐私协议的后续操作,比如展示被隐藏的相关功能 } } }) } } }) [代码] 2、按需使用 在使用到隐私接口时弹出隐私弹窗,同时隐私接口的流程会被挂起,用户同意后会继续执行,不同意则中止执行。 这种情况下,需要在onLoad里定义好监听隐私接口时的授权事件,也就是wx.onNeedPrivacyAuthorization,然后在用户点击同意或者不同意后调用回调接口 resolve 进行上报,完整代码如下 [代码]Page({ data: { showPrivacy: false // 控制隐私弹窗是否展示 }, openPrivacyAgreement() { // 查看隐私协议 wx.openPrivacyContract() }, disagreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) // 上报用户不同意隐私协议,隐私接口操作会被自动中止 this.resolvePrivacyAuthorization({ buttonId: 'disagreeBtn', event: 'disagree' }) }, agreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) // 上报用户同意隐私协议,隐私接口操作会被自动继续执行 this.resolvePrivacyAuthorization({ buttonId: 'agreeBtn', event: 'agree' }) }, onLoad() { if (wx.onNeedPrivacyAuthorization) { wx.onNeedPrivacyAuthorization(resolve => { // 打开隐私弹窗 this.setData({ showPrivacy: true, }) // 定义上报方法 this.resolvePrivacyAuthorization = resolve }) } } }) [代码] 3、全局控制和按需使用结合使用 这种情况是上面两种方式的结合,页面加载时弹出隐私弹窗,不管用户同意还是不同意都不需要做其他操作。然后用户在用到隐私接口时,根据用户是否同意再按需决定是否再次弹窗。 这种情况下,需要在onLoad里使用wx.getPrivacySetting获取隐私授权情况,没授权时弹出隐私弹窗。同时定义好监听隐私接口时的授权事件,也就是wx.onNeedPrivacyAuthorization,然后在用户点击同意或者不同意后调用回调接口 resolve 进行上报,完整代码如下 [代码]let pageOnload = true // 是否页面加载时弹窗 Page({ data: { showPrivacy: false // 控制隐私弹窗是否展示 }, openPrivacyAgreement() { // 查看隐私协议 wx.openPrivacyContract() }, disagreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) if (!pageOnload) { // 上报用户不同意隐私协议,隐私接口操作会被自动中止 this.resolvePrivacyAuthorization({ buttonId: 'disagreeBtn', event: 'disagree' }) } else { pageOnload = false } }, agreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) if (!pageOnload) { // 上报用户同意隐私协议,隐私接口操作会被自动继续执行 this.resolvePrivacyAuthorization({ buttonId: 'agreeBtn', event: 'agree' }) } else { pageOnload = false } }, onLoad() { if (wx.getPrivacySetting) { wx.getPrivacySetting({ success: res => { if (res.needAuthorization) { // 打开隐私弹窗 this.setData({ showPrivacy: true }) } } }) } if (wx.onNeedPrivacyAuthorization) { wx.onNeedPrivacyAuthorization(resolve => { // 打开隐私弹窗 this.setData({ showPrivacy: true, }) // 定义上报方法 this.resolvePrivacyAuthorization = resolve }) } } }) [代码] 4、open-type按钮藕合使用 这个是目前最简单的方式,按照官方文档,在用open-type方式调用隐私功能的按钮的open-type里加上[代码]|agreePrivacyAuthorization[代码]就可以了,js部分代码基本不用做任何修改,示例如下 [代码]<button open-type="getPhoneNumber|agreePrivacyAuthorization" bindgetphonenumber="handleGetPhoneNumber">同意隐私协议并授权手机号</button> [代码] 这种方式,不需要上报同意事件,不需要设计隐私协议弹窗,不需要“不同意”按钮。当然,我建议在现有页面上增加一个查看隐私协议的入口。
2023-09-08 - 适配新版获取昵称头像、隐私保护协议接口
蛮记录一下,虽然官方新版获取昵称、头像的api上线已久 目前的做法是封装了一个auth.js用来专门获取用户信息,想要实现的效果是 1、若用户没有授权过昵称、头像,则弹出授权弹窗 2、若用户有授权过,则直接返回昵称头像等其他用户信息 3、允许更新用户信息,也就是不管之前有没有授权过昵称头像,都会再次弹出授权弹窗 4、支持旧版api,主要是考虑到部分用户的微信客户端可能长期未更新,不支持新版api 另外也适配了最近的隐私接口功能。因为获取昵称组件是一个input,不会触发wx.onNeedPrivacyAuthorization,所以这里是给input加了一个catch:touchstart="requirePrivacyAuthorize",在requirePrivacyAuthorize里面调用wx.requirePrivacyAuthorize,从而触发wx.onNeedPrivacyAuthorization。具体代码可以看下improveUserProfile.wxml、improveUserProfile.js 主要代码如下,核心方法 authProcess: auth.js const api = require('/api.js'); const file = require("/file.js"); /** * 保存或者更新用户信息到云端 * @param userInfo * @returns {Promise<unknown>} * @private */ const _saveOrUpdateUserInfo = function (userInfo) { return new Promise((resolve, reject) => { api.router.callCloudUserCenterFunction("UserInfoHandler/saveOrUpdateUserInfo", userInfo, res => { console.log("保存用户信息到云端完毕=>", JSON.stringify(res)); resolve(res); }, res => { console.error("保存用户信息到云端失败=>", res); reject(); } ); }); }; /** * 发起后端请求前先获取用户信息 * 使用新版获取用户信息接口 * @param app * @param basePage * @param update 等于true时表示更新用户信息 * @returns {Promise<any>} */ const authProcess = function (app, basePage, update) { return new Promise(function (resolve, reject) { if (update) { console.log("更新用户信息,重新授权"); _getUserProfile(app, basePage).then(res => resolve(res)).catch(res => reject(res)); } else if (!app.globalData.userInfo.authEver) { console.log("没有用户授权信息,开始授权"); _getUserProfile(app, basePage).then(res => resolve(res)).catch(res => reject(res)); } else { console.log("用户有授权过,无需重新授权"); resolve(app.globalData.userInfo); } }); }; /** * 使用新版接口获取用户信息 * 兼容新版头像昵称填写能力 * @param app * @param basePage * @private */ const _getUserProfile = function (app, basePage) { return new Promise(function (resolve, reject) { const supportChooseAvatar = wx.canIUse('button.open-type.chooseAvatar'); if (supportChooseAvatar) { console.warn("支持头像昵称填写能力"); const improveUserProfile = basePage.improveUserProfile || basePage.selectComponent('#improveUserProfile'); const toast = basePage.toast || basePage.selectComponent('#toast'); if (!improveUserProfile) { console.error("完善用户资料组件缺失,无法继续"); reject("完善用户资料组件缺失"); } else if (!toast) { console.error("toast组件缺失,无法继续"); reject("toast组件缺失"); } else { improveUserProfile.show(({nickName, avatarUrl}) => { const {openid, avatarFileId: oldAvatarFileId} = app.globalData.userInfo; const targetPath = _getHeaderPath(openid); console.log("上传获取到的临时头像:", avatarUrl); toast.showLoadingToast({text: "开始上传头像"}); file.uploadCloudFile(targetPath, avatarUrl).then(fileId => { file.getTempFileURL(fileId).then(fileUrl => { toast.hideLoadingToast(); _getUserProfileCallback(app, basePage, { userInfo: { nickName, avatarUrl: fileUrl, avatarFileId: fileId } }).then(userInfo => resolve(userInfo)); if (oldAvatarFileId) { console.log("删除旧的云储存头像文件", oldAvatarFileId); file.deleteCloudFile([oldAvatarFileId]).then(); } }).catch(res => { toast.hideLoadingToast(); toast.showToast({text: "保存头像链接失败"}); reject("保存头像链接失败"); }) }).catch(res => { toast.hideLoadingToast(); toast.showToast({text: "头像上传失败"}); reject("头像上传失败"); }); file.onUploadProgressUpdate(progress => { toast.showLoadingToast({text: "上传了" + progress + "%"}); }); }, res => { console.log("用户拒绝授权信息:", res); reject(res); }); //reject("需要使用新api授权用户信息"); } } else { console.warn("不支持头像昵称填写能力"); api.getUserProfile( res => { _getUserProfileCallback(app, basePage, res).then(userInfo => resolve(userInfo)); }, res => { console.error("获取用户信息失败:", res); reject(res); } ); //reject("需要使用旧api授权用户信息"); } }); }; /** * 获取到用户信息的后续逻辑 * @param app * @param basePage * @param result * @returns {Promise<unknown>} * @private */ const _getUserProfileCallback = (app, basePage, result) => { return new Promise(function (resolve, reject) { console.log("获取用户信息结果:", JSON.stringify(result)); let {userInfo} = result; //authEver=true时后端做更新操作,false时新增操作 const {authEver} = app.globalData.userInfo || {}; userInfo = Object.assign(app.globalData.userInfo || {}, userInfo, {ready: true, authEver: true}); basePage.setData({ userInfo, }, () => { _saveOrUpdateUserInfo(Object.assign({}, userInfo, {authEver})).then(res => { console.log("用户信息保存完毕,更新后的用户信息:", res); const result = res.result; if (result.success) { userInfo = Object.assign(app.globalData.userInfo || {}, userInfo); basePage.setData({ userInfo, }); } resolve(userInfo); }).catch(res => reject()); }); }); } /** * 获取头像地址 * 不同项目可以自行调整 * @param openid * @returns {string} * @private */ const _getHeaderPath = (openid) => { return "images/" + openid + "/avatarUrl_" + new Date().getTime() + ".webp"; } module.exports = { setUserInfo2Data, authProcess }; 昵称、头像授权弹窗自定义组件 improveUserProfile.wxml <view class="container" wx:if="{{show}}"> <view class="cover {{showCoverAnimation?'cover-fade-in':''}}" catch:touchmove="return"></view> <view class="improve-box {{showBoxAnimation?'slade-in':''}} {{device.isPhoneX? 'phx_68':''}}" catch:touchmove="return"> <view class="title flex-start-horizontal"> <view class="logo"> <image class="icon" src="xxxx"></image> </view> <view class="mini-name">恋爱小清单 申请</view> </view> <view class="tips"> <view class="tip">获取你的昵称、头像</view> <view class="use-for">用于个人中心、共享清单、恋人圈以及分享时展示</view> </view> <form catchsubmit="saveUserInfo"> <view class="info-item flex-center"> <view class="label">头像</view> <view class="input-content flex-full more"> <button class="reset-btn flex-column-left" style="height:100%" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar"> <view class="choose-avatar before" wx:if="{{!avatarUrl}}"> <image class="icon" src="/images/me/choose-avatar.png"></image> </view> <image class="choose-avatar after" src="{{avatarUrl}}" wx:else></image> </button> </view> </view> <view class="info-item flex-center no-border"> <view class="label">昵称</view> <input class="input-content flex-full more" model:value="{{nickName}}" type="nickname" placeholder="输入昵称" placeholder-class="edit-placeholder" focus="{{nickNameFocus}}" always-embed="{{false}}" adjust-position="{{true}}" hold-keyboard="{{false}}" name="nickName" catch:touchstart="requirePrivacyAuthorize"></input> </view> <view class="buttons flex-center"> <button class="cancel reset-btn" form-type="reset" bindtap="close">拒绝</button> <button class="save reset-btn" form-type="submit">保存</button> </view> <!--隐藏参数区域开始--> <input disabled style="display:none;" name="avatarUrl" value="{{avatarUrl}}"></input> <!--隐藏参数区域结束--> </form> </view> </view> <!--toast--> <toast id="toast"></toast> improveUserProfile.wxss @import "/app.wxss"; .cover{ background-color: #111; opacity: 0; position: fixed; left: 0; top: 0; right: 0; bottom: 0; z-index: 5000; transition: opacity .3s; } .improve-box{ position: fixed; left: 0rpx; right: 0rpx; bottom: 0rpx; z-index: 5100; background-color: #fff; box-sizing: border-box; padding-top: 60rpx; padding-left: 50rpx; padding-right: 50rpx; border-radius: 30rpx 30rpx 0 0; transform: translateY(100%); transition: transform .3s, bottom .3s; } .cover-fade-in{ opacity: 0.7; } .slade-in{ transform: translateY(0); bottom: 0rpx; } .improve-box .title{ font-size: 32rpx; font-weight: 500; text-align: center; margin-bottom: 40rpx; color: #161616; } .improve-box .title .icon{ width: 46rpx; height: 46rpx; margin-right: 8rpx; vertical-align: bottom; border-radius: 50%; } .improve-box .title .mini-name{ /*margin-bottom: 2rpx;*/ } .improve-box .tips{ margin-bottom: 20rpx; } .improve-box .tips .tip{ color: #121212; font-size: 34rpx; font-weight: 500; margin-bottom: 12rpx; } .improve-box .tips .use-for{ color: #606266; font-size: 28rpx; margin-bottom: 20rpx; } .improve-box .info-item{ /*margin-bottom: 30rpx;*/ border-bottom: 1rpx solid #f0f0f0; } .improve-box .label{ margin-right: 40rpx; font-size: 32rpx; color: #404040; padding: 30rpx 0 30rpx; } .improve-box .choose-avatar.after{ width: 48rpx; height: 48rpx; display: block; border-radius: 10rpx; } .improve-box .choose-avatar.before{ background-color: #f0f0f0; width: 48rpx; height: 48rpx; display: block; border-radius: 10rpx; padding: 8rpx; box-sizing: border-box; } .improve-box .choose-avatar.before .icon{ width: 100%; height: 100%; display: block; } .improve-box .input-content{ width: 100%; font-size: 32rpx; min-height: 80rpx; height: 80rpx; line-height: 80rpx; border-radius: 10rpx; padding-left: 20rpx; box-sizing: border-box; /*border-bottom: 1rpx solid #dcdee1;*/ position: relative; padding-right: 30rpx; } .edit-placeholder{ color: #c7c7cc; font-size: 32rpx; } .improve-box .buttons{ margin-bottom: 40rpx; margin-top: 50rpx; text-align: center; font-size: 34rpx; font-weight: 550; } .improve-box .buttons .save{ width: 220rpx !important; height: 90rpx; line-height: 90rpx; border-radius: 14rpx; color: #fff; /*background:#fff linear-gradient(90deg,rgba(246, 120, 121,.9) 10%, rgb(246, 120, 121));*/ background-color: #07c160; margin-left: -50rpx; } .improve-box .buttons .cancel{ width: 220rpx !important; height: 90rpx; line-height: 90rpx; border-radius: 14rpx; color: #07c160; background-color: #F2F2F2; } .more:after{ content: " "; transform: rotate(45deg); height: 14rpx; width: 14rpx; border-width: 2rpx 2rpx 0 0; border-color: #b2b2b2; border-style: solid; position: absolute; margin-top: -8rpx; right: 4rpx; top: 50%; transition: transform .3s; } improveUserProfile.js //获取应用实例 const app = getApp(); const api = require('../../utils/api.js'); const tabbar = require('../../utils/tabbar.js'); Component({ /** * 组件的属性列表 */ properties: {}, /** * 组件的初始数据 */ data: { show: false, showCoverAnimation: false,//显示类别选择窗口动画 showBoxAnimation: false,//显示类别选择窗口动画 avatarUrl: "", nickNameFocus: false, }, attached() { this.toast = this.selectComponent('#toast'); }, observers: { 'show': function (show) { if (!show) { console.log("获取用户昵称头像弹窗关闭,昵称输入框状态复位"); this.setData({ nickNameFocus: false }) } } }, /** * 组件的方法列表 */ methods: { show(successCallback, failCallback) { const device = app.getSystemInfo(); this.setData({ show: true, device, successCallback, failCallback }, () => { this.setData({ showCoverAnimation: true, showBoxAnimation: true }); }) tabbar.hideTab(this); }, hide() { this.setData({ showCoverAnimation: false, showBoxAnimation: false }, () => { const that = this; setTimeout(function () { that.setData({ show: false }) tabbar.showTab(that); }, 300) }) }, close() { const {failCallback} = this.data; typeof failCallback == 'function' && failCallback("用户拒绝授权"); this.hide(); }, onChooseAvatar(e) { console.log("选择头像:", e); const {avatarUrl} = e.detail; this.setData({ avatarUrl }) }, /** * 模拟隐私接口调用,并触发隐私弹窗逻辑 */ requirePrivacyAuthorize() { if (wx.requirePrivacyAuthorize) { console.warn("当前基础库支持api wx.requirePrivacyAuthorize"); wx.requirePrivacyAuthorize({ success: res => { console.log('用户同意了隐私协议 或 无需用户同意隐私协议'); //用户同意隐私协议后给昵称输入框聚焦 this.setData({ nickNameFocus: true }) }, fail: res => { console.log('用户拒绝了隐私协议'); } }) } else { console.warn("当前基础库不支持api wx.requirePrivacyAuthorize"); this.setData({ nickNameFocus: true }) } }, saveUserInfo(e) { console.log("保存用户资料:", e); const {nickName, avatarUrl} = e.detail.value; if (!avatarUrl) { this.toast.showToast({text: "请选择头像"}); return; } if (!nickName) { this.toast.showToast({text: "请输入昵称"}); return; } const {successCallback} = this.data; typeof successCallback == 'function' && successCallback({nickName, avatarUrl}); this.hide(); } } }); 弹窗效果 [图片]
2023-08-30 - 小程序隐私保护授权弹窗组件(Taro和原生版本)
【原生】https://github.com/hsuna/miniprogram-privacy-popup 【Taro】https://github.com/hsuna/taro-privacy-popup 隐私政策弹窗封装: 项目引入privacyPopup组件,在所有使用到隐私授权的页面引入privacyPopup即可,组件内部注册了onNeedPrivacyAuthorization,且通过队列的方式统一管理隐私授权的回调,无需开发者在页面做其他特殊的处理。. 在 page.wxml 中使用组件 针对Input的封装组件 由于Input无法触发onNeedPrivacyAuthorization,使用插槽的形式,通过getPrivacySetting获取用户隐私状态,在用户未同意的情况下,通过catch:touchstart拦截用户点击交互,使用wx.requirePrivacyAuthorize触发onNeedPrivacyAuthorization事件。. 在 page.wxml 中使用组件,并包裹触点区域
2023-09-03 - 隐私协议授权的一种解决思路
现状 登录页使用了获取手机号接口,登录页为单独的页面(非弹窗),所有登录需求都会跳转到此页。 [图片] 分析 小程序提供两种接入方式:一种是主动查询授权状态,如果状态为未授权则进入授权流程,已授权则无需处理;另一种是被动监听何时需要授权,触发事件后进入授权流程,未触发则表示已授权。在登录的流程中,主动查询就是先发起授权后点击登录按钮,被动监听就是先点击登录按钮后发起授权。个人感觉,主动查询的方式先告知用户需要采集隐私数据,比较符合用户习惯,而被动监听的方式事前未告知用户需要授权,且打断了登录的流程,用户体验不佳,因此采取主动查询的方式。 官方的示例采用了弹窗授权的方式,但无论是在页面上覆盖一个模拟弹窗组件,还是使用page-container,控制弹窗都不能像VUE那样优雅,复用也不方便。鉴于登录页也是单独的页面,考虑到以后可能还有其它页面需要授权,因此授权页也采用单独的页面(PS.skyline的自定义路由可以很优雅地实现弹窗,但目前还没有适配,暂不考虑,之后对授权页进行改造即可)。 思路 在登录页的onLoad中主动查询授权状态,如果状态为未授权则调用授权接口跳转到授权页,授权页回调授权状态,如果授权失败则登录也失败。 [图片] 实现 [代码]// 封装的登录接口 function requestLogin() { return new Promise(async (resolve, reject) => { wx.navigateTo({ url: '/pages/user/user-login/user-login', // 登录页 events: { login: (loginStatus) => { // 登录状态回调 if (loginStatus) { // 成功 resolve() } else { // 失败 reject(new Error('login fail')) } } }, fail: () => reject(new Error('navigate fail')) }) }) } // 封装的授权接口 function requestProvicy() { return new Promise(async (resolve, reject) => { wx.getPrivacySetting({ success: (res) => { if (res.needAuthorization) { // 需要授权 wx.navigateTo({ url: '/pages/user/user-login/user-login', // 登录页 events: { privacy: (privacyStatus) => { // 授权状态回调 if (loginStatus) { // 成功 resolve() } else { // 失败 reject(new Error('privacy fail')) } } }, fail: () => reject(new Error('navigate fail')) }) } else { // 无需授权 resolve() } }, fail: () => reject(new Error('get privacy fail')) }) }) } // 第一步:调用封装的登录接口 // 需要登录才能进入的页面 Page({ onLoad: async function(options) { try { await wx.$auth.requestLogin() // 登录失败抛出错误 } catch { return wx.$router.back() // 返回上一页 } // 业务代码 } }) // 第二步:获取手机号登录 // 登录页 <!-- wxml > <button open-type="getPhoneNumber" bindgetphonenumber="handleGetPhoneNumber">获取手机号</button> Page({ handleGetPhoneNumber: async function(e) { const code = e.detail.code // 手机号code // 调用自己后台的登录接口 this._loginStatus = true // 登录成功状态,在onUnload回调 wx.$router.back() // 返回上一页 }, onLoad: async function() { try { await wx.$auth.requestProvicy() // 授权失败抛出错误 } catch { return wx.$router.back() // 返回上一页 } } onUnload: function() { const eventChannel = this.getOpenerEventChannel() if (!eventChannel) return eventChannel.emit('login', !!this._loginStatus) // 回调登录结果 } }) // 第三步:授权 // 授权页 <!-- wxml > <button id="agree-btn" open-type="agreePrivacyAuthorization" bindagreeprivacyauthorization="handleAgreePrivacyAuthorization">我已阅读并同意隐私协议</button> Page({ handleAgreePrivacyAuthorization: async function() { this._privacyStatus = true // 授权成功状态,在onUnload回调 wx.$router.back() // 返回上一页 }, onUnload: function() { const eventChannel = this.getOpenerEventChannel() if (!eventChannel) return eventChannel.emit('privacy', !!this._privacyStatus) // 回调登录结果 } }) [代码] 最终效果 未登录的状态下打开一个需要登录才能使用的页面 [图片] 可以进入小程序实际体验一下,欢迎各位一起讨论 [图片]
2023-09-01