!!!重要,如果还没做隐私弹窗不用往下看了,先看这里:
一、前言
开始之前,如果还没阅读过 隐私协议整改公告 的,其实也不要紧,官方公告指引写的那叫一个一言难尽,也不知道跟谁学的,貌似写了很多,有用的却不多,接下来以我的角度给大家解读一下本次更新的要点和具体的操作。
二、公告解读
公告其实可以很简单的理解:自2023年9月15日起,涉及用户隐私的接口如:获取你的头像昵称,选择地址wx.chooseAddress,获取手机号等隐私接口如果被调用,中间会触发一个监听回调:wx.onNeedPrivacyAuthorization,具体有哪些,可以参考以下:小程序用户隐私保护指引内容介绍
提供一段示例代码:
uni.onNeedPrivacyAuthorization((resolve, eventInfo) => {
console.log('onNeedPrivacyAuthorization', eventInfo);
toggleStore.privacyModal.resolvePrivacyAuthorization = resolve;
toggleStore.togglePrivacyModal(true);
});
这个函数的回调会提供一个 resolve 函数,只有调用 resolve 函数 resolve({event: ‘agree’}) 才能继续触发之前那些隐私接口,类似加了中间件。
另外提一句,第三方开发如果要配置隐私协议,需要通过接口配置,参考:第三方开发隐私协议配置
三、方案设计
1. 在启动页 app 设置监听函数,监听隐私接口,把回调的resolve函数挂在到全局,在用户点击同意的时候调用
App.vue
import { useToggleStore } from '@/store';
const listenPrivacyOpen = () => {
if (!uni.canIUse('onNeedPrivacyAuthorization')) return;
const toggleStore = useToggleStore();
uni.onNeedPrivacyAuthorization((resolve, eventInfo) => {
toggleStore.privacyModal.resolvePrivacyAuthorization = resolve;
toggleStore.togglePrivacyModal(true);
})
};
onLaunch(() => {
listenPrivacyOpen();
})
2. store 设计
import { defineStore } from 'pinia';
const useToggleStore = defineStore<
string,
IStore.Toggle.State,
Record<string, any>,
IStore.Toggle.Action
>('toggle', {
state: () => ({
privacyModal: {
show: false,
resolvePrivacyAuthorization: () => {},
},
}),
actions: {
togglePrivacyModal(value: boolean) {
this.privacyModal.show = value;
},
},
});
export default useToggleStore;
3. 提供全局组件 PrivacyModal,使用pinia全局变量控制显隐
main.ts
全局注册组件
import PrivacyModal from '@/components/privacy-modal/index.vue';
app.component('PrivacyModal', PrivacyModal); // 统一隐私协议弹窗
隐私弹窗
privacy-modal.vue
<template>
<u-popup
class="privacy-wrap"
:show="toggleStore.privacyModal.show"
mode="center"
round="10">
<view class="content">
<view class="title">隐私协议须知</view>
<view class="tips">
在使用本服务之前,请仔细阅读
<text class="privacy-link" @click="handleReadPrivacy">
{{ privacyContractName }}
</text>
。如果你同意{{ privacyContractName }},请点击“同意”开始使用服务。
</view>
<view class="btns">
<button class="btn cancel" @click="handleDisagreePrivacy">取消</button>
<button
:id="AGREE_ID"
class="btn"
open-type="agreePrivacyAuthorization"
@agreeprivacyauthorization="handleAgreePrivacy">
同意
</button>
</view>
</view>
</u-popup>
</template>
<script setup lang="ts">
import { promisify } from '@/helper/wx';
import { useToggleStore } from '@/store';
import { ref } from 'vue';
const getPrivacySetting = promisify(uni.getPrivacySetting);
// 同意按钮id
const AGREE_ID = 'agree-btn';
const toggleStore = useToggleStore();
const privacyContractName = ref('隐私保护协议');
// 初始化隐私协议
initPrivacyInfo();
async function initPrivacyInfo() {
const res = await getPrivacySetting();
privacyContractName.value = res.privacyContractName;
}
/**
* 阅读隐私协议
*/
function handleReadPrivacy() {
uni.openPrivacyContract();
}
/**
* 关闭弹窗
*/
function closeModal() {
toggleStore.togglePrivacyModal(false);
}
/**
* 拒绝隐私协议
*/
function handleDisagreePrivacy() {
closeModal();
toggleStore.privacyModal.resolvePrivacyAuthorization({
event: 'disagree',
});
}
/**
* 同意隐私协议
*/
function handleAgreePrivacy() {
closeModal();
toggleStore.privacyModal.resolvePrivacyAuthorization({
buttonId: AGREE_ID,
event: 'agree',
});
}
</script>
<style lang="scss" scoped>
.content {
width: 290px;
padding: 20px;
border-radius: 10px;
}
.tips {
margin-top: 16px;
.privacy-link {
color: #34a5fc;
}
}
.btns {
display: flex;
align-items: center;
justify-content: center;
margin-top: 30px;
.btn {
width: 120px;
height: 36px;
background: var(--bg-color);
text-align: center;
line-height: 36px;
color: #fff;
border-radius: 20px;
font-size: 16px;
&.cancel {
border: 1px solid var(--bg-color);
color: var(--bg-color);
background: #fff;
}
&:not(:last-child) {
margin-right: 20px;
}
}
}
</style>
4. 借 vite 的 transform 函数注入到所有页面(一次性注入到所有页面,避免未来使用到的时候踩坑)
vite.config.ts
/**
* 页面级注入全局组件
* @param comp 注入的组件
* @returns config
*/
const injectTemplateToPages = (comp: string): PluginOption => {
return {
name: 'injectTemplateToPages',
enforce: 'pre',
// code 代码,id 文件路径
transform(code, id) {
// vue文件,且不是App.vue,不是components目录下的文件
const shouldInject =
/\.vue$/.test(id) && !/App\.vue$/.test(id) && !/components/.test(id);
if (shouldInject) {
// 注入模板代码
code = code.replace(/\B<template>/, (_) => `${_}${comp}`);
}
return {
code,
map: null,
};
},
};
};
// https://vitejs.dev/config/
export default defineConfig({
mode: process.env.NODE_ENV,
// 注入隐私弹窗
plugins: [uni(), injectTemplateToPages('<PrivacyModal />')],
build: {
sourcemap: true,
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
});
四、用户昵称填写能力
由于用户昵称填写使用的是 input 组件,是不会触发上述的隐私事件监听事件的,需要用到主动查询 wx.getPrivacySetting,这部分就不写了,比较简单。
open-type="getPhoneNumber|agreePrivacyAuthorization" 官方button不是提供了这种耦合的方式么 直接在授权的时候加上不就好了?