评论

适配新版获取昵称头像、隐私保护协议接口

小结一下适配新旧版获取昵称头像api、隐私保护协议api方法

蛮记录一下,虽然官方新版获取昵称、头像的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  
点赞 9
收藏
评论

5 个评论

  • 孙宇
    孙宇
    07-01

    不错,请问有完整代码吗?谢谢大佬

    07-01
    赞同
    回复
  • 凉白开
    凉白开
    04-19

    先赞一个,晚点试试


    04-19
    赞同
    回复
  • 泰酷拉
    泰酷拉
    02-25

    不错,点赞。

    02-25
    赞同
    回复
  • 于正华
    于正华
    发表于移动端
    2023-09-06
    能帮我开发个小程序吗18329121524
    2023-09-06
    赞同
    回复
  • 阿巴阿巴
    阿巴阿巴
    2023-09-01

    不错,点赞。

    2023-09-01
    赞同
    回复
登录 后发表内容