评论

uniapp (vue3) 小程序隐私协议方案分享

隐私协议弹窗 (uniapp)开发指南

!!!重要,如果还没做隐私弹窗不用往下看了,先看这里:

新版隐私协议弹窗说明

一、前言

开始之前,如果还没阅读过 隐私协议整改公告 的,其实也不要紧,官方公告指引写的那叫一个一言难尽,也不知道跟谁学的,貌似写了很多,有用的却不多,接下来以我的角度给大家解读一下本次更新的要点和具体的操作。

二、公告解读

公告其实可以很简单的理解:自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,这部分就不写了,比较简单。

最后一次编辑于  2023-09-15  
点赞 2
收藏
评论

1 个评论

  • Wei老徐🍖
    Wei老徐🍖
    2023-09-08
    open-type="getPhoneNumber|agreePrivacyAuthorization"
    官方button不是提供了这种耦合的方式么 直接在授权的时候加上不就好了?
    
    2023-09-08
    赞同
    回复 1
    • optimistic
      optimistic
      2023-09-08
      还有其他 api 的,定位,头像,诸如此类,以后新加功能涉及隐私接口,又要处理一次,不如一次性处理完。未来开发的人也不需要去了解某个接口是否属于隐私协议,属于一劳永逸的做法
      2023-09-08
      回复
登录 后发表内容