- uniapp (vue3) 小程序隐私协议方案分享
!!!重要,如果还没做隐私弹窗不用往下看了,先看这里: 新版隐私协议弹窗说明 一、前言 开始之前,如果还没阅读过 隐私协议整改公告 的,其实也不要紧,官方公告指引写的那叫一个一言难尽,也不知道跟谁学的,貌似写了很多,有用的却不多,接下来以我的角度给大家解读一下本次更新的要点和具体的操作。 二、公告解读 公告其实可以很简单的理解:自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 - 隐私授权弹窗在调用手机号授权时会提示invoke getPhoneNumber too frequ?
隐私授权弹窗,在调用 手机号授权时,多页面会相互阻塞手机号获取 例如 首页中 点击 微信授权手机号登陆 按钮,获取手机号授权,弹出了隐私弹窗,此时不操作弹窗 进入一个详情页, 点击 微信授权手机号登陆 按钮,获取手机号授权,弹出了隐私弹窗,此时不操作弹窗, 返回 首页,再次点击微信授权手机号登陆 按钮, 此时会提示 invoke getPhoneNumber too frequently [图片] [图片] [图片] 代码片段 https://developers.weixin.qq.com/s/yOmtrGmY7lKW
2023-08-23 - 如何实现加入购物车的抛物线效果
一、场景分析 在一些如商城、点餐小程序中实现购物车抛物线效果可以提升界面趣味性增加小程序用户体验。 二、效果预览 效果图压缩后速度有点快,请下载代码片段预览 [图片] 三、实现原理 当用户点击物品时记录当前触摸点,根据触摸点计算抛物线运动的顶点位置,通过触摸点、顶点、购物车的位置计算出抛物线运动轨迹,然后控制 icon 运动。 计算购物车在当前手机内的位置 [代码]/** 设置购物车的坐标位置 **/ wx.getSystemInfo({ success: (res) => { let busPos = {} // x y 坐标分别取屏幕百分之八十的位置 busPos['x'] = res.windowWidth * 0.8 busPos['y'] = res.windowHeight * 0.8 this.setData({ busPos }) } }) [代码] 商品点击事件的处理 点击物品后记录点击的位置,然后根据点击位置计算出抛物线的顶点位置,计算方式为点击位置的上方+150,右边+150(需要根据点击位置是否在购物左边还是右边进行判断)。 根据点击,顶点,购物车三个位置计算出抛物线运动轨迹 以3个控制点为例,点A、B、C、AB 上设置点 D、BC 上设置点 E、DE 连线上设置点 F,则最终的贝塞尔曲线是点F的坐标轨迹; 计算相邻控制点间距; 根据完成时间,计算每次执行时 D 在AB方向上移动的距离,E 在 BC 方向上移动的距离; 时间每递增 100ms,则 D、E 在指定方向上发生位移,F 在 DE 上的位移则可通过 AD/AB = DF/DE 得出; 根据 DE 的正余弦值和 DE 的值计算出F的坐标。 开启定时器,依次按照贝塞尔曲线位置做动画位移 使用定时器将抛物线运动轨迹做动画位移。 定时器执行完动画后将购物车角标+1 老规矩,结尾放代码片段 https://developers.weixin.qq.com/s/PnYfitmG7Hxv
2022-03-04