likes
comments
collection
share

小程序隐私协议弹窗|Taro项目

作者站长头像
站长
· 阅读数 13

离9月16只剩最后一周了,还有多少小伙伴和我一样还没上线微信要求的 小程序隐私协议。趁今天不忙赶紧把这个开发了。

新提供的四个API

  • wx.onNeedPrivacyAuthorization 监听隐私接口需要用户授权事件。
  • wx.openPrivacyContract 跳转至隐私协议页面。
  • wx.getPrivacySetting 查询隐私授权情况。
  • wx.requirePrivacyAuthorize 模拟隐私接口调用,并触发隐私弹窗逻辑。

其中后面两个不是必须要用,看业务逻辑。一般我们用wx.onNeedPrivacyAuthorizationwx.openPrivacyContract 就可以。微信社区有更多文章介绍。在这里就说明常见流程:

用户触发隐私接口 --> wx.onNeedPrivacyAuthorization的回调函数能监听到了,开发者弹出隐私窗口 --> 弹窗提供隐私协议链接,通过 wx.openPrivacyContract 调用跳转 --> 用户可选择性看完后点 同意按钮:<button id="agree-btn" open-type="agreePrivacyAuthorization" bindagreeprivacyauthorization="handleAgree">同意</button> --> 触发按钮事件即成功。

Taro项目中使用

Taro项目中使用 openType 为 agreePrivacyAuthorization 的 Button 组件在新版本可以直接用,如果旧版需要做一些处理,github上有 issue

先安装 @tarojs/plugin-inject

npm i @tarojs/plugin-inject -D

然后 config/index.js 中增加 plugins 配置:

{
   // ...
   plugins:[
      [
        "@tarojs/plugin-inject",
        {
          
          components: {
            Button: {
              bindagreeprivacyauthorization: ""
            }
          }
        }
      ]
    ]
}

最后 Button 组件就可正常使用。

<Button
  // @ts-ignore
  openType="agreePrivacyAuthorization"
  // @ts-ignore
  onAgreePrivacyAuthorization={handleOnAgreePrivacyAuthorization}
>
  我同意
</Button>

组件封装

代码示例在官网文档已提供了demo。拿来修改下,改成React组件,效果图如下 小程序隐私协议弹窗|Taro项目

下面贴下弹窗代码,只需要在页面中引入即可。

jsx

import React, { memo, useEffect, useState } from 'react';
import { View, Text, Button } from '@tarojs/components';
import './index.less';

let privacyHandler;
let privacyResolves = new Set<Function>();

if (wx?.onNeedPrivacyAuthorization) {
  wx?.onNeedPrivacyAuthorization(resolve => {
    privacyHandler?.(resolve);
  });
}

const PrivacyPopup: React.FC = () => {
  const [visible, setVisible] = useState<boolean>(false);

  const handleDisagree = () => {
    setVisible(false);
    privacyResolves.forEach(resolve => {
      resolve({
        event: 'disagree'
      });
    });
    privacyResolves.clear();
  };

  const handleAgree = () => {
    setVisible(false);
    // 额外处理:例如有可能弹出授权弹窗,但是用户没点击 同意/拒绝 就回退页面,导致没有resolve,这里让所有pending中的wx隐私接口继续执行
    privacyResolves.forEach(resolve => {
      resolve({
        event: 'agree',
        buttonId: 'agree-btn'
      });
    });
    privacyResolves.clear();
  };

  useEffect(() => {
    privacyHandler = resolve => {
      privacyResolves.add(resolve);
      setVisible(true);
    };
  }, []);
  
  // useDidShow还需要调用的原因:比如当前页面需要隐私弹窗,但未触发弹出,允许用户点有些按钮跳转去其他需要隐私弹窗的页面。用户再回退当前页面,此时privacyHandler要重新设置成当前页面的弹窗
  useDidShow(() => {
    privacyHandler = resolve => {
      privacyResolves.add(resolve);
      setVisible(true);
    };
  });

  const openPrivacyContract = () => {
    wx?.openPrivacyContract({
      success: () => {
        console.log('openPrivacyContract success');
      },
      fail: res => {
        console.error('openPrivacyContract fail', res);
      }
    });
  };

  return (
    <View className="pp-container">
      {visible && <View className="pp-mask" />}
      {visible && (
        <View className="pp-wrap">
          <View className="pp-head">
            <View className="pp-head-title">用户隐私保护提示</View>
          </View>
          <View className="pp-content">
            感谢您使用xxx服务,您在使用该服务前,请仔细阅读
            <Text className="pp-link" onClick={openPrivacyContract}>
              {' '}
              用户隐私保护指引{' '}
            </Text>
            。当您点击同意并开始使用该服务时,即表示您已理解并同意该条款内容。
          </View>
          <View className="pp-footer">
            <Button id="disagree-btn" className="pp-btn pp-btn_refuse" onClick={handleDisagree}>
              拒绝
            </Button>
            <Button
              id="agree-btn"
              className="pp-btn"
              openType="agreePrivacyAuthorization"
              onAgreePrivacyAuthorization={handleAgree}
            >
              同意
            </Button>
          </View>
          <View className="pp-safearea" />
        </View>
      )}
    </View>
  );
};

export default memo(PrivacyPopup);

样式

.pp {
  &-container {
    position: fixed;
    z-index: 10;
  }

  &-mask {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    opacity: 0;
    animation: ppFadeIn 0.2s cubic-bezier(0.55, 0, 0.55, 0.2) forwards;
    background: rgba(4, 8, 18, 0.4);
  }

  &-wrap {
    max-height: 80vh;
    display: flex;
    flex-direction: column;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 36px 36px 0;
    border-radius: 40px 40px 0 0;
    animation: ppSlideUpIn 0.2s cubic-bezier(0.55, 0, 0.55, 0.2);
    background: #fff;
  }

  &-head {
    flex-shrink: 0;

    &-title {
      font-weight: 500;
      font-size: 40px;
      line-height: 48px;
      color: #1f242e;
    }
  }

  &-link {
    color: #3098ff;
  }

  &-content {
    margin: 28px 0;
    font-size: 32px;
    line-height: 48px;
    color: #4d535c;

  }

  &-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 0;
  }

  &-btn {
    display: block;
    width: 280px;
    padding: 28px;
    border: none;
    box-shadow: 0 4px 16px rgba(46, 87, 234, 0.2);
    background: radial-gradient(
          94.86% 3821.12% at 91.96% 50%,
          rgba(63, 220, 255, 0.38) 0%,
          rgba(26, 241, 255, 0) 100%
        ),
        linear-gradient(262.71deg, #3098ff 5.67%, #3c70ff 94.33%);
    border-radius: 16px;
    color: #fff;
    font-size: 36px;
    line-height: 42px;
    font-weight: 600;
    box-sizing: border-box;
    margin: 0 auto;
    
   &::after {
    border: none;
   }

    &_refuse {
      background: #fff;
      border: 1px solid #e9e9e9;
      color: #4d535c;
      box-shadow: none;
    }
  }
  
  &-safearea {
    display: block;
    width: 100%;
    padding-bottom: env(safe-area-inset-bottom);
  }

  @keyframes ppSlideUpIn {
    0% {
      transform: translate(0, 100%);
    }

    100% {
      transform: translate(0, 0);
    }
  }

  @keyframes ppFadeIn {
    0% {
      opacity: 0;
    }

    100% {
      opacity: 1;
    }
  }
}

官方演示的弹窗没有蒙层,弹窗显示时可以随意点击页面,所以官方演示代码里做了弹窗保持单例的逻辑。上面的封装的组件有蒙层,弹窗关闭前不能点击其他,就不做这处理了。

调试

代码写好后,开始调试。我们先去微信后台的 设置-基本设置-服务内容声明-用户隐私保护指引 填写你的小程序用到的隐私接口,例如

小程序隐私协议弹窗|Taro项目

Taro项目中 app.config.ts 加上 __usePrivacyCheck__: true

另外,微信开发者工具的调试基础库最好设置成3.0.0

然后在需要的页面引入组件即可。

其他

最近微信又更新了,隐私同意按钮支持与手机号快速验证组件手机号实时验证组件耦合使用。

<button id="agree-btn1" open-type="getPhoneNumber|agreePrivacyAuthorization" bindgetphonenumber="handleGetPhoneNumber" bindagreeprivacyauthorization="handleAgreePrivacyAuthorization">同意隐私协议并授权手机号</button>

<button id="agree-btn2" open-type="getRealtimePhoneNumber|agreePrivacyAuthorization" bindgetrealtimephonenumber="handleGetRealtimePhoneNumber" bindagreeprivacyauthorization="handleAgreePrivacyAuthorization">同意隐私协议并授权手机号</button>

<button id="agree-btn3" open-type="getUserInfo|agreePrivacyAuthorization" bindgetuserinfo="handleGetUserInfo" bindagreeprivacyauthorization="handleAgreePrivacyAuthorization">同意隐私协议并获取头像昵称信息</button>

就是说获取手机号和同意隐私同一个按钮,点同意后即可调通隐私接口。

我们可以另外封装个弹窗组件

import React, { memo } from 'react';

import { View, Text, Button } from '@tarojs/components';

import './index.less';

interface PrivacyPopupProps {
  visible: boolean;
  onClose: () => void;
}

const PrivacyUserPopup: React.FC<PrivacyPopupProps> = ({ visible, onClose }) => {
  const handleGetPhone = (e: any) => {
    onClose();
    if (e.detail.errMsg !== 'getPhoneNumber:ok') {
      return;
    }
    // 下面写登录等逻辑
  };

  const handleAgree = () => {
    console.log('同意');
  };

  const openPrivacyContract = () => {
    wx?.openPrivacyContract({
      success: () => {
        console.log('openPrivacyContract success');
      },
      fail: res => {
        console.error('openPrivacyContract fail', res);
      }
    });
  };

  return (
    <View className="pp-container">
      {visible && <View className="pp-mask" />}
      {visible && (
        <View className="pp-wrap">
          <View className="pp-head">
            <View className="pp-head-title">用户隐私保护提示</View>
          </View>
          <View className="pp-content">
            感谢您使用xxx服务,您在使用该服务前,请仔细阅读
            <Text className="pp-link" onClick={openPrivacyContract}>
              {' '}
              用户隐私保护指引{' '}
            </Text>
            。当您点击同意并开始使用该服务时,即表示您已理解并同意该条款内容。
          </View>
          <View className="pp-footer">
            <Button id="disagree-btn" className="pp-btn pp-btn_refuse" onClick={onClose}>
              拒绝
            </Button>
            <Button
              id="agree-btn"
              className="pp-btn"
              // @ts-ignore
              openType="getPhoneNumber|agreePrivacyAuthorization"
              onAgreePrivacyAuthorization={handleAgree}
              onGetPhoneNumber={handleGetPhone}
            >
              同意隐私协议并授权手机号
            </Button>
          </View>
          <View className="pp-safearea" />
        </View>
      )}
    </View>
  );
};

export default memo(PrivacyUserPopup);

效果图如下,注意调试时 开发者工具基础库要 3.0.1

小程序隐私协议弹窗|Taro项目

如果你的小程序只需要获取手机号/用户信息的隐私接口,或者说只有获取手机号/用户信息后才会用到其他隐私接口,那么用这个弹窗最好了。

但是作者开发的小程序还涉及粘贴板、获取位置信息等隐私接口,假设用户先使用了粘贴板隐私接口,还是得用第一个用法才能隐私授权。所以懒得分开处理,为方便统一,还是用第一个弹窗组件吧。