罗广辉
2026-04-25 114c3d78ff8be7ceb9880ab236e81c24ca6d256f
feat: 配置微信小程序隐私协议
2 files modified
1 files added
233 ■■■■■ changed files
src/components/GetPrivacy.vue 199 ●●●●● patch | view | raw | blame | history
src/manifest.json 3 ●●●● patch | view | raw | blame | history
src/pages/login/index.vue 31 ●●●●● patch | view | raw | blame | history
src/components/GetPrivacy.vue
New file
@@ -0,0 +1,199 @@
<template>
  <view v-if="showPrivacy" :class="privacyClass">
    <view :class="contentClass">
      <view class="title">用户隐私保护指引</view>
      <view class="des">
        感谢您选择使用掌控智飞小程序,我们非常重视您的个人信息安全和隐私保护。使用我们的产品前,请您仔细阅读“
        <text class="link" @tap="openPrivacyContract">{{privacyContractName}} </text>”,
        如您同意此隐私保护指引,请点击同意按钮,开始使用此小程序,我们将尽全力保护您的个人信息及合法权益,感谢您的信任!<br />
      </view>
      <view class="btns">
        <button class="item reject" @click="exitMiniProgram">拒绝</button>
        <button id="agree-btn" class="item agree" open-type="agreePrivacyAuthorization"
                @agreeprivacyauthorization="handleAgreePrivacyAuthorization">同意</button>
      </view>
    </view>
  </view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
const props = defineProps({
  position: {
    type: String,
    default: 'center'
  }
});
const emit = defineEmits(['allowPrivacy']);
const isRead = ref(false);
const showPrivacy = ref(false);
const privacyContractName = ref('');
const resolvePrivacyAuthorization = ref(null);
const privacyClass = computed(() => {
  return props.position === 'bottom' ? 'privacy privacy-bottom' : 'privacy';
});
const contentClass = computed(() => {
  return props.position === 'bottom' ? 'content content-bottom' : 'content';
});
onMounted(() => {
  if (uni.onNeedPrivacyAuthorization) {
    uni.onNeedPrivacyAuthorization((resolve) => {
      resolvePrivacyAuthorization.value = resolve;
    });
  }
  if (uni.getPrivacySetting) {
    uni.getPrivacySetting({
      success: (res) => {
        if (res.needAuthorization) {
          privacyContractName.value = res.privacyContractName;
          showPrivacy.value = true;
        } else {
          console.log('已经同意隐私授权,不需要再次授权')
          showPrivacy.value = false;
        }
      },
    });
  }
});
const openPrivacyContract = () => {
  uni.openPrivacyContract({
    success: () => {
      isRead.value = true;
    },
    fail: () => {
      uni.showToast({
        title: '遇到错误',
        icon: 'error',
      });
    },
  });
};
const exitMiniProgram = () => {
  wx.exitMiniProgram();
};
const handleAgreePrivacyAuthorization = () => {
  showPrivacy.value = false;
  emit('allowPrivacy');
  if (typeof resolvePrivacyAuthorization.value === 'function') {
    resolvePrivacyAuthorization.value({
      buttonId: 'agree-btn',
      event: 'agree',
    });
  }
};
const closePrivacy = () => {
  showPrivacy.value = false;
};
// 暴露给父组件使用
defineExpose({
  closePrivacy
});
</script>
<style scoped>
.privacy {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: rgba(0, 0, 0, .5);
  z-index: 9999999;
  display: flex;
  align-items: center;
  justify-content: center;
}
.privacy-bottom {
  align-items: flex-end;
}
.content {
  width: 632rpx;
  padding: 48rpx;
  box-sizing: border-box;
  background: #fff;
  border-radius: 16rpx;
}
.content-bottom {
  position: absolute;
  bottom: 0;
  width: 96%;
  padding: 36rpx;
  border-radius: 16rpx 16rpx 0 0;
}
.content .title {
  text-align: center;
  color: #333;
  font-weight: bold;
  font-size: 32rpx;
}
.content .des {
  font-size: 26rpx;
  color: #666;
  margin-top: 40rpx;
  text-align: justify;
  line-height: 1.6;
}
.content .des .link {
  color: #1989ff;
  text-decoration: underline;
}
.btns {
  margin-top: 48rpx;
  margin-bottom: 12rpx;
  display: flex;
}
.btns .item {
  width: 200rpx;
  height: 72rpx;
  overflow: visible;
  display: flex;
  align-items: center;
  justify-content: center;
  /* border-radius: 16rpx; */
  box-sizing: border-box;
  border: none !important;
}
.btns .reject {
  background: #f4f4f5;
  color: #1989ff;
  font-size: 14px;
  background: #edf5fe;
  font-weight: 300;
  margin-right: 16rpx;
}
.btns .agree {
  width: 200rpx;
  background: #1989ff;
  color: #fff;
  font-size: 16px;
}
.privacy-bottom .btns .agree {
  width: 440rpx;
}
</style>
src/manifest.json
@@ -79,7 +79,8 @@
        "setting" : {
            "urlCheck" : false
        },
        "usingComponents" : true
        "usingComponents" : true,
        "__usePrivacyCheck__" : true
    },
    "mp-alipay" : {
        "usingComponents" : true
src/pages/login/index.vue
@@ -30,6 +30,10 @@
    <button class="login-btn" :style="[inputStyle]" @tap="submit">登录</button>
    <image class="lowerRightCorner" :src="droneSvg" />
    <!-- #ifdef MP-WEIXIN -->
    <GetPrivacy ref="privacyComponent" position="center" @allowPrivacy="allowPrivacy"/>
    <!-- #endif -->
  </view>
</template>
@@ -38,7 +42,9 @@
import md5 from "js-md5";
import { loginByUsername } from "@/api/user/index.js";
import { useUserStore } from "@/store/index.js";
// #ifdef MP-WEIXIN
import GetPrivacy from '@/components/GetPrivacy.vue'
// #endif
import { HOME_PATH, LOGIN_PATH, removeQueryString } from "@/router";
import { onMounted } from "vue";
@@ -147,6 +153,29 @@
    redirect = decodeURIComponent(options.redirect);
  }
});
// #ifdef MP-WEIXIN
const allowPrivacy = () =>{
  // 同意隐私协议触发事件,有些接口需要同意授权后才能执行,比如获取手机号授权接口,可以在同意隐私协议后,再执行授权获取手机号接口,如果不需要可以不添加该方法
  console.log('同意隐私授权')
}
const privacyComponent =ref(null)
onShow(() =>{
  wx.getPrivacySetting({
    success: res => {
      console.log(res)
      if (!res.needAuthorization) {
        privacyComponent?.value?.closePrivacy()
        // 查询授权,针对有tab切换的页面,可以在onshow中查询隐私授权状态,判断在tab切换后是否需要关闭授权弹框
        console.log('已经同意隐私授权,不需要再次授权')
      }
    },
    fail: () => {},
    complete: () => {}
  })
})
// #endif
</script>
<style lang="scss" scoped>