适配新版获取昵称头像、隐私保护协议接口
蛮记录一下,虽然官方新版获取昵称、头像的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();
}
}
});
弹窗效果 [图片]