微信小程序从2023年9月15日开始,涉及处理用户个人信息的小程序开发者,需通过弹窗等明显方式提示用户阅读隐私政策等收集使用规则。为规范开发者的用户个人信息处理行为,保障用户合法权益,微信要求开发者主动同步微信当前用户已阅读并同意小程序的隐私政策等收集使用规则,方可调用微信提供的隐私接口。简而言之,就是调用某些涉及用户隐私api比如获取用户位置(wx.getLocation)、选择地址(wx.chooseAddress)、选择照片(wx.chooseImage)等api(隐私接口)前需要弹出隐私政策告知用户取得同意后方可正常使用功能。
需要注意的是所有涉及到隐私接口整个小程序只要同意一次隐私保护后所有api都能正常使用,本地也有相应生成缓存记录,如果删除小程序就需要重新同意。
可查阅微信官方说明:
用户隐私保护指引填写说明
小程序隐私协议开发指南
需要隐私协议的隐私接口(api)
对于弹窗时机设计可以分为两种,第一中比较暴力做法就是在首页进入立即弹出隐私同意窗口,
不同意直接退出小程序。第二种比较友好方式在每次需要调用隐私api前检查根据需要弹窗。
ps: 本文将主要采用第二种方式进行开发,并以uniapp开发举例说明
需要在小程序后台-设置-服务内容声明-用户隐私保护指引进行更新填写,具体可查看上面相关文章——用户隐私保护指引填写说明,提交并审核通过后才能正常使用
在 2023年10月17日之前,在 app.json 中配置 usePrivacyCheck: true 后,会启用隐私相关功能,如果不配置或者配置为 false 则不会启用。在 2023年10月17日之后,不论 app.json 中是否有配置 usePrivacyCheck,隐私相关功能都会启用。
如果是uniapp开发在manifest.json切换到源码试图在mp-weixin字段内添加"usePrivacyCheck": true
"mp-weixin" : {
"__usePrivacyCheck__": true
},
相关api介绍
1.wx.getPrivacySetting(Object object)
查询隐私授权情况,成功回调参数needAuthorization字段表示是否需要用户授权隐私协议
wx.getPrivacySetting({
success: res => {
console.log(res) // 返回结果为: res = { needAuthorization: true/false, privacyContractName: '《xxx隐私保护指引》' }
if (res.needAuthorization) {
// 需要弹出隐私协议
} else {
// 用户已经同意过隐私协议,所以不需要再弹出隐私协议,也能调用隐私接口
}
},
fail: () => {},
complete: () => {}
})
2.wx.openPrivacyContract(Object object)
跳转至隐私协议页面
wx.openPrivacyContract({
success: () => {}, // 打开成功
fail: () => {}, // 打开失败
complete: () => {}
})
3.
对于隐私弹窗需要封装一个全局组件,在需要的页面引入。调用隐私api前根据wx.getPrivacySetting的needAuthorization判断是否需要弹窗,如果值为flase不需要,true就需要弹窗等待用户同意,在同意按钮bindagreeprivacyauthorization回调后实现隐私api逻辑。
假如很多页面需要用到隐私api,每个页面要单独引入组件标签,有没有什么方法可以全局引入不用单独写呢,答案是有——vue-template-compiler,关于vue-template-compiler有很多文章可以自行查阅这里不在细述。
组件引入问题解决了,但是每个页面逻辑还要重复写,是否有更好更便捷调用方式呢?于是我们想到了类似加载组件的使用,uni.showToast()通过全局api就能控制是否显示。
于是我们最终封装目标如下:
this.$privacyCheck({
agree:()=>{},//已同意回调
disAgree:()=>{},//不同意回调
complete:()=>{}//完成回调
})
在每个页面只需通过this.$privacyCheck()很方便调用
components/privacyDialog/index.vue
<!-- 隐私授权对话框 -->
<template>
<view class="comp-container" v-if="visible">
<view class="dialog">
<view class="title">用户隐私保护提示</view>
<view class="desc">
感谢您使用本小程序,在使用前您应当阅读并同意<text class="privacy"
@click="handleOpenPrivacyContract">《用户隐私保护指引》</text>,当点击同意,即表示您已理解并同意该条款内容,该条款将对您产生法律约束力;如您不同意,将无法继续使用小程序相关功能。
</view>
<view class="footer">
<button type="default" class="btn disagree" @click="handleDisagree">不同意</button>
<button type="default" open-type="agreePrivacyAuthorization" class="btn agree"
@agreeprivacyauthorization="handleAgree">同意</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
visible: false,
agreeCallBack: null, //同意回调函数
disAgreeCallBack: null, //不同意回调函数
completeCallBack: null //完成回调函数
}
},
methods: {
//检查根据是否需要并弹出隐私窗口
check(options = {}) {
let {
agree,
disAgree,
complete
} = options
if (uni.getPrivacySetting) {
if (typeof complete === 'function') {
this.completeCallBack = complete
}
uni.getPrivacySetting({
success: res => {
if (typeof agree === 'function') {
this.agreeCallBack = agree
}
if (typeof disAgree === 'function') {
this.disAgreeCallBack = disAgree
}
//需要隐私同意显示对话框
if (res.needAuthorization) {
this.visible = true
} else {
this.handleAgree()
}
},
fail: () => {
this.handleComplete()
}
})
}
},
//隐藏对话框
hide() {
this.visible = false
},
//不同意
handleDisagree() {
this.visible = false
if (typeof this.disAgreeCallBack === 'function') {
this.disAgreeCallBack()
}
this.handleComplete()
},
//同意
handleAgree() {
this.visible = false;
if (typeof this.agreeCallBack === 'function') {
this.agreeCallBack()
}
this.handleComplete()
},
//完成
handleComplete() {
if (typeof this.completeCallBack === 'function') {
this.completeCallBack()
}
},
//打开隐私政策页面
handleOpenPrivacyContract() {
uni.openPrivacyContract()
}
}
};
</script>
<style lang="scss" scoped>
.comp-container {
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 999;
}
.dialog {
color: #333;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
border-radius: 20rpx;
padding: 50rpx 40rpx;
width: 560rpx;
box-sizing: border-box;
.title {
width: 100%;
color: #000;
text-align: center;
font-size: 32rpx;
font-weight: 650;
box-sizing: border-box;
}
.desc {
line-height: 40rpx;
box-sizing: border-box;
margin-top: 50rpx;
font-size: 26rpx;
.privacy {
color: #2f80ed;
}
}
.footer {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 30rpx;
.btn {
display: flex;
align-items: center;
color: #FFF;
font-size: 28rpx;
font-weight: 500;
line-height: 80rpx;
text-align: center;
height: 80rpx;
border-radius: 10rpx;
border: none;
background: #07c160;
flex: 1;
justify-content: center;
&:nth-last-child(1) {
margin-left: 30rpx;
}
&.disagree {
color: #333;
background: #f2f2f2;
}
}
}
}
</style>
components/privacyDialog/privacyCheck.js
检测是否需要隐私弹窗
/**隐私授权弹窗全局调用,agree同意或者已同意过(不需要弹窗)回调,disagree不同意回调,complete:所有情况都走该回调
* this.$privacyCheck({
agree:()=>{},
disAgree:()=>{},
complete:()=>{}
})
*/
const privacyCheck = (options = {}) => {
const pages = getCurrentPages();
let curPage = pages[pages.length - 1];//当前页面
curPage.$vm.$refs.privacyDialog.check(options)
}
export default privacyCheck
import privacyCheck from '@/components/privacyDialog/privacyCheck.js'
import privacyDialog from '@/components/privacyDialog/index.vue'
Vue.component('privacyDialog',privacyDialog)
Vue.prototype.$privacyCheck=privacyCheck
此处为vue-template-compiler处理,在每个页面templae加入隐私弹窗组件标签 。如果没有这个文件,新建一个写入如下代码:
module.exports = {
chainWebpack: config => {
config.module.rule('vue').use('vue-loader').loader('vue-loader').tap(options => {
const compile = options.compiler.compile
options.compiler.compile = (template, ...args) => {
if (args[0].resourcePath.match(/^pages/)) {
template = template.replace(/[\s\S]+?<[\d\D]+?>/, _ => `${_}
`)
}
return compile(template, ...args)
}
return options
})
}
}
manifest.json
/* 小程序特有相关 */
"mp-weixin" : {
"requiredPrivateInfos" : [ "chooseAddress", "getLocation" ],
"permission" : {
"scope.userLocation" : {
"desc" : "你的位置信息将用于小程序位置接口的效果展示"
}
},
"__usePrivacyCheck__": true
},
index.vue
<template>
<view class="content">
<button @click="handleChoseAddress">选择地址</button>
</view>
</template>
<script>
export default {
data() {
return {
}
},
mounted(){
//定位
this.handleLocation()
},
methods: {
//定位
handleLocation() {
this.$privacyCheck({
//已同意
agree:()=>{
uni.getLocation({
success(e) {
console.log(e)
}
})
},
//不同意
disAgree:()=>{
console.log('不同意')
}
})
},
//选择地址
handleChoseAddress(){
this.$privacyCheck({
//已同意
agree:()=>{
uni.chooseAddress({
success(e) {
console.log(e)
}
})
},
//不同意
disAgree:()=>{
console.log('不同意')
}
})
}
}
}
</script>
注意:this.$privacyCheck必须在mounted周期函数后(组件渲染完成)使用
通过上述封装就能很方便快速在页面调用隐私弹窗,目前隐私弹窗组件需要自行实现,根据官方说明后续更新可能会提供官方组件,到时又多了一种选择。