评论

如何快速实现小程序用户隐私协议功能?

如何快速实现微信小程序隐私协议功能?

前言:

近期收到了微信小程序对于隐私协议的整改,在下午体验了下官方demo后整理下方案,需要的开发者可直接复制代码, 在IDE里调试加__usePrivacyCheck__就不说了,直接开整。

第一步: 实现一个隐私协议弹窗:

新建隐私协议弹窗组件:privacyDialog

// 组件wxml
<view class="privacy-dialog {{visible ? 'active' : ''}}">
  <view class="privacy-back {{visible ? 'active' : ''}}"></view>
  <view class="privacy-container {{visible ? 'active' : ''}} {{useSafeArea ? 'change' : ''}}">
    <view class="privacy-info">
        <image src="{{mini_logo}}" mode="widthFix" class="privacy-logo" />
        <text class="privacy-name">{{mini_name}}</text>
    </view>
    <view class="privacy-text">在您使用【{{mini_name}}】服务之前,请仔细阅读<text bindtap="openPrivacyContract">《{{mini_name}}隐私保护指引》</text>。如您同意,《{{mini_name}}隐私保护指引》,请点击“同意”开始使用【{{mini_name}}】</view>
    <view class="privacy-btns">
        <view id="disagree-btn" class="privacy-cancel-btn" bindtap="handleDisagree">不同意</view>
        <button id="agree-btn" open-type="agreePrivacyAuthorization" class="privacy-confirm-btn" bindagreeprivacyauthorization="handleAgree">同意</button>
    </view>
  </view>
</view>

// 组件js
let privacyHandler
let privacyResolves = new Set()
let closeOtherPageshowDialogHooks = new Set()

if (wx.onNeedPrivacyAuthorization) {
  wx.onNeedPrivacyAuthorization(resolve => {
    if (typeof privacyHandler === 'function') {
      privacyHandler(resolve)
    }
  })
}

const closeOtherPageshowDialog = (closeDialog) => {
  closeOtherPageshowDialogHooks.forEach(hook => {
    if (closeDialog !== hook) {
      hook()
    }
  })
}

Component({
  data: {
    visible: false,
    mini_logo: __wxConfig.accountInfo.icon,
    mini_name: __wxConfig.accountInfo.nickname
  },
  properties: {
    useSafeArea: { // 是否在非tabBar页面引用,适配tabbar底部      type: Boolean,
      value: true
    }
  },
  lifetimes: {
    attached() {
      const closeDialog = () => {
        this.hideDialog()
      }
      privacyHandler = resolve => {
        privacyResolves.add(resolve)
        this.showDialog()
        // 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
        closeOtherPageshowDialog(closeDialog)
      }
      this.closeDialog = closeDialog
      closeOtherPageshowDialogHooks.add(closeDialog)
    },
    detached() {
      closeOtherPageshowDialogHooks.delete(this.closeDialog)
    }
  },
  pageLifetimes: {
  // 解决首页有手机授权、进入二级页面再返回就无法弹出隐私协议的问题。
    show() {
      if (this.closeDialog) {
        privacyHandler = resolve => {
          privacyResolves.add(resolve)
          this.showDialog()
          // 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
          closeOtherPageshowDialog(this.closeDialog)
        }
      }
    }
  },
  methods: {
    handleAgree() {
      this.hideDialog()
      // 这里同时调用多个wx隐私接口:让隐私弹窗保持单例,点击一次同意按钮即可让所有pending中的wx隐私接口继续执行
      privacyResolves.forEach(resolve => {
        resolve({
          event: 'agree',
          buttonId: 'agree-btn'
        })
      })
      privacyResolves.clear()
    },
    // 拒绝协议
    handleDisagree(e) {
      this.hideDialog()
      privacyResolves.forEach(resolve => {
        resolve({
          event: 'disagree'
        })
      })
      privacyResolves.clear()
    },
    // 显示隐私协议弹窗
    showDialog() {
      if (!this.data.visible) {
        this.setData({
          visible: true
        })
        // 控制底部页面不可滚动
        wx.setPageStyle({
          style: {
            overflow: 'hidden'
          }
        })
      }
    },
    // 隐藏隐私协议弹窗
    hideDialog() {
      if (this.data.visible) {
        this.setData({
          visible: false
        })
        // 控制底部页面可以滚动
        wx.setPageStyle({
          style: {
            overflow: 'auto'
          }
        })
      }
    },
    // 跳转隐私协议
    openPrivacyContract() {
      wx.openPrivacyContract({
        success: res => {
          console.log('openPrivacyContract success', res)
        },
        fail: err => {
          console.error('openPrivacyContract fail', err)
        }
      })
    },
    // tabbar页面切换时关掉其他页面的隐私协议弹窗
    tabBarPageShow() {
      this.handleDisagree() // 切换tabbar默认用户点了拒绝,保持单例
      if (this.closeDialog) {
        privacyHandler = resolve => {
          privacyResolves.add(resolve)
          this.showDialog()
          // 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
          closeOtherPageshowDialog(this.closeDialog)
        }
      }
    }
  }
})
@import "/app.wxss";

.privacy-dialog {
  width: 100%;
  visibility: hidden;
  position: relative;
  overflow: hidden;
  z-index: 1000;
}

.privacy-dialog.active {
  visibility: visible;
}

.privacy-back {
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.6);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 8888;
  opacity: 0;
  transition: all 0.3s;
}

.privacy-back.active {
  opacity: 1;
  transition: all 0.3s;
}

.privacy-container {
  width: 100%;
  background: #fff;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 9999;
  border-radius: 32rpx 32rpx 0 0;
  padding: 48rpx;
  transform: translateY(100%);
  transition: all 0.3s;
  overflow: hidden;
}

.privacy-container.active {
  transform: translateY(0%);
  transition: all 0.3s;
}

.privacy-container.change {
  padding-bottom: calc(env(safe-area-inset-bottom) + 32rpx);
}

.privacy-info {
    width: 100%;
    display: flex;
    align-items: center;
}

.privacy-logo {
    width: 48rpx;
    height: 48rpx;
    border-radius: 50%;
}

.privacy-name {
    margin-left: 8rpx;
    font-size: 32rpx;
    font-weight: bold;
}

.privacy-text {
    font-size: 28rpx;
    color: #808999;
    margin: 32rpx 0;
    letter-spacing: 2rpx;
}

.privacy-text text {
    color: #1277FF;
}

.privacy-btns {
    width: 470rpx;
    display: flex;
    margin: 0 auto;
    justify-content: space-between;
    align-items: center;
}

.privacy-cancel-btn {
  width: 220rpx;
  height: 90rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 14rpx;
  color: #07c160;
  background-color: #F2F2F2;
  font-weight: bold;
  margin: 0;
  padding: 0;
}

.privacy-confirm-btn {
  width: 220rpx;
  height: 90rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 14rpx;
  color: #fff;
  background: #07c160;
  font-size: 32rpx;
  font-weight: bold;
  margin: 0;
  padding: 0;
}

第二步:如何使用?

这里有两种情况需要处理:一种是tabbar页面,一种是普通页面。
首先在app.json全局引入自定义组件,无需在单个页面json中额外引入:

  "usingComponents": {
    "privacy-dialog": "/components/privacyDialog/index"
  },
  • tabbar页面:存在tabbar页面使用手机号授权提示invoke getPhoneNumber too frequently操作频繁的问题。
  • 原因是切换tabbar并不会导致组件实例被销毁,上个tabbar页面手机号授权会被隐私协议pending住,在当前页面再次请求手机号授权就会提示该错误。可按下方方式处理:
// tabbar的wxml引入组件,id自定义:
<privacy-dialog id="im-login" />

// js处理:
// tabbar item点击后触发隐私协议组件内的tabBarPageShow方法销毁上个页面的隐私协议弹窗实例。
onTabItemTap() {
   this.selectComponent('#im-login').tabBarPageShow()
}
  • 普通页面无需特殊处理,根据下面贴出来的官方文档全局搜索使用到的API,在该API对应的页面直接引用隐私协议弹窗组件即可。

https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/miniprogram-intro.html#开发者处理的信息

// 使用到了文档中的api,就需要在wxml引入隐私协议组件:
<privacy-dialog />

老规矩,结尾放代码片段,有问题评论区讨论:

https://developers.weixin.qq.com/s/ArnN74mh7BLG

记录:8月30日及31日上午正常,8月31日下午灰度了基础库,tabBarPageShow方法目前测试无效。最新的解决方案是页面onHide时wx:if移除组件实例,onShow再挂载。

<privacy-dialog id="privacy-dialog-page1" useSafeArea="{{false}}"  wx:if="{{showPrivacy}}"  />

data: {
  showPrivacy: true,
},
onShow() {
  this.setData({
    showPrivacy: true
  })
},
onHide() {
  this.setData({
    showPrivacy: false
  })
},

9月2日官方说已恢复正常,测试发现已恢复,改回tabbarPageShow方法。

https://developers.weixin.qq.com/community/develop/doc/000ae292b88fb092374030b6d6ec00

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

6 个评论

  • Long.
    Long.
    03-05

    这玩意现在还有用吗?

    03-05
    赞同
    回复 1
    • TNT
      TNT
      03-05
      不用自行处理了。如果你没加 会弹官方的隐私协议。我们老的小程序加了 也准备慢慢删了
      03-05
      回复
  • 玫瑰向海
    玫瑰向海
    2023-09-05

    大佬,想请问下这个:

    首页index页面首先弹“获取位置信息”的弹框,点击拒绝,之后点击“到非tabbar页面”到page3,页面没有弹框,直接走fail,我想page3页面设置一个按钮,点击再次弹“获取位置信息”的弹框,请问应该在点击事件里面怎么写啊?

    2023-09-05
    赞同
    回复 6
    • TNT
      TNT
      2023-09-05
      点击事件里 wx.getLocation
      2023-09-05
      回复
    • 玫瑰向海
      玫瑰向海
      2023-09-05回复TNT
      有试过,会直接走fail
      2023-09-05
      回复
    • TNT
      TNT
      2023-09-05回复玫瑰向海
      fail的 errMsg是什么?你小程序隐私协议是否配置了 获取用户地理位置的说明
      2023-09-05
      回复
    • 玫瑰向海
      玫瑰向海
      2023-09-05回复TNT
      我是直接把基础库版本调低到2.2几试的...
      2023-09-05
      回复
    • 玫瑰向海
      玫瑰向海
      2023-09-05回复TNT
      最新的3.0.1没有在隐私协议中声明
      2023-09-05
      回复
    查看更多(1)
  • 邵宇飞
    邵宇飞
    2023-09-05

    大佬,我再uniapp中使用,将组件放在wx模块下,然后对应页面引用,但是在tabBar的vue页面加载后点击手机号授权就一直提示:invoke getPhoneNumber too frequently,按照上面的方法也不行

    2023-09-05
    赞同
    回复 6
    • TNT
      TNT
      2023-09-05
      基础库升级到3.0.2官方修复了这个问题。同时需要使用tabBarPageShow方法
      2023-09-05
      回复
    • 邵宇飞
      邵宇飞
      2023-09-05回复TNT
      基础库3.0.2好像还没能拉取到吧
      2023-09-05
      回复
    • TNT
      TNT
      2023-09-05回复邵宇飞
      灰度到最新的3.0.1推送到手机上 你再试试。我测试 都正常了。
      2023-09-05
      回复
    • 邵宇飞
      邵宇飞
      2023-09-08回复TNT
      大佬,我这边试了还是这样,我看提供的示例里会再调用的tabBar页面进行onTabItemTap,但是我这边是vue的,我应该怎么弄
      2023-09-08
      回复
    • 饶冬辉
      饶冬辉
      2023-11-06回复TNT
      大哥,有没有uniapp的版本呀,小程序原生的写法看不懂
      2023-11-06
      回复
    查看更多(1)
  • Kev
    Kev
    2023-09-04

    👍

    2023-09-04
    赞同
    回复
  • 路漫漫兮
    路漫漫兮
    2023-09-01

    大佬。我贴的你的代码,tabbar里面用,为啥我是先执行的页面的 wx.getLocation 呢

    2023-09-01
    赞同
    回复 1
    • TNT
      TNT
      2023-09-04
      你的appId里 有填写getLocation的描述吗?用测试号不行的哈。
      2023-09-04
      回复
  • 阿巴阿巴
    阿巴阿巴
    2023-09-01

    赞,直接贴源码啦

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