feat: 实现隐私弹窗全链路功能,支持本地配置与远程patch融合

- 新增隐私管理器核心逻辑,支持URL路由规则匹配、远程patch签名校验与融合
- 实现启动期资源等待机制,确保弹窗在i18n、主题配置就绪后触发
- 新增启动进度条组件,展示启动阶段进度与状态
- 重构隐私弹窗组件生命周期,修复事件丢失问题并支持规则动态重评估
- 适配多端平台隐私检查逻辑,支持微信小程序、鸿蒙元服务与H5
- 支持uniacid租户隔离的持久化规则存储
This commit is contained in:
2026-07-11 18:28:36 +08:00
parent a2066285cd
commit a913961be5
8 changed files with 1471 additions and 148 deletions

View File

@@ -64,25 +64,10 @@ export default {
},
}
},
mounted() {
// 调试: 记录本实例的 ID 和当前总数
this.listenPrivacySettingChange()
this.curPageShow()
// 监听全局隐私弹窗事件(由 App.vue boot 完成后通过 PrivacyManager.show() 触发)
uni.$on('privacy:show', this.handlePrivacyShow)
// 监听关闭事件: 其他实例弹窗时通知本实例关闭
uni.$on('privacy:forceClose', this.handleForceClose)
// 补偿: 若 PrivacyManager.show() 在本组件 mounted 之前 emit 了事件
// 事件已丢失,此处通过 consumePendingShow 补偿弹窗
if (PrivacyManager.consumePendingShow()) {
this.$nextTick(() => {
this.showPrivacyPopup()
})
}
},
// 关键: 把 listener 注册放到 created (而非 mounted),
// 提前一个 microtask, 让 PrivacyManager.show() emit 的 privacy:show 不会丢
// showPrivacyPopup() 内部访问 this.showPop 等响应式属性, 必须等到 mounted,
// 所以 listener 可在 created 注册, 但 callback 仍要 nextTick 渲染
created() {
try {
// #ifdef MP-WEIXIN
@@ -90,7 +75,7 @@ export default {
success: (res) => {
this.platformContractName = res.privacyContractName || ''
}
});
})
// #endif
// #ifdef QUICKAPP-WEBVIEW
@@ -108,10 +93,28 @@ export default {
// #ifdef MP-HARMONY
// 鸿蒙元服务: 隐私授权由系统管理, 无需应用层查询 privacyContractName
// #endif
// 提前注册 listener (晚于 created 早于 mounted, 减少事件丢失窗口)
uni.$on('privacy:show', this.handlePrivacyShow)
uni.$on('privacy:forceClose', this.handleForceClose)
uni.$on('privacy:re-evaluate', this.handleReEvaluate)
} catch (e) {
console.error("=========低版本基础库==========", e)
}
},
mounted() {
// 调试: 记录本实例的 ID 和当前总数
this.listenPrivacySettingChange()
this.curPageShow()
// 补偿: 若 PrivacyManager.show() 在本组件 mounted 之前 emit 了事件
// listener 在 created 已注册, 大部分情况能直接命中, 但有些竞态导致 consumePendingShow 兜底
if (PrivacyManager.consumePendingShow()) {
this.$nextTick(() => {
this.showPrivacyPopup()
})
}
},
methods: {
/**
* 接收全局隐私弹窗事件
@@ -135,6 +138,43 @@ export default {
this.disPopUp()
},
/**
* 接收规则重评估事件 (由 PrivacyManager.applyMergedRules 在远程 patch 之后触发)
* action: 'show' → 当前不该弹 (白名单未命中) 但新规则要弹 → 重弹
* action: 'dismiss' → 当前在弹 (白名单命中) 但新规则不该弹 → 关闭
* action: 'noop' → 已同意, 无操作
* 互斥: 只有第一个响应事件的实例真正处理, 避免多实例同时响应
*
* 关键: 这个函数会被所有已 mounted 的实例同时调用 (uni.$on 全局)
* 必须用互斥机制保证只有一个实例实际弹窗/关窗。
*
* 设计: 复用 PrivacyManager 的 _pendingShow/_pendingHide 作为互斥 token:
* - show: 只有消费到 token 的实例才能 popUp
* - dismiss: 任何实例都允许 disPopUp (无副作用, 但要避免重复 emit forceClose)
*/
handleReEvaluate(info) {
if (!info) return
if (info.action === 'show') {
// 新规则要弹 → 互斥消费 token
if (!this.showPop) {
// 仿 consumePendingShow 行为: 只有 _pendingShow=true 才能弹
// 直接通过 showPrivacyPopup 内部 emit forceClose 通知其他实例
if (PrivacyManager.consumePendingShow()) {
this.showPrivacyPopup()
} else {
// 没有 token → 不弹 (被另一个实例接管了)
}
}
} else if (info.action === 'dismiss') {
// 新规则不该弹 → 只关闭已经弹窗的实例
// 通过 forceClose 协议做: 关闭所有已经显示的实例
if (this.showPop) {
this.disPopUp()
}
}
// noop: 用户已同意, 不动
},
/**
* 统一弹窗 + 关闭其他页面的弹窗
* 通过 uni.$emit('privacy:forceClose') 通知其他实例关闭
@@ -382,10 +422,14 @@ export default {
// $lang() 返回真实翻译(效果与 H5 一致)
this.prepareI18n();
this.showPop = true;
// 通知 PrivacyManager 弹窗实际显示状态 (用于 rules-updated 双阶段重评估)
try { uni.$emit && uni.$emit('privacy:popup-state', { showing: true }) } catch (e) {}
},
disPopUp() {
this.showPop = false;
// 通知 PrivacyManager 弹窗实际显示状态 (用于 rules-updated 双阶段重评估)
try { uni.$emit && uni.$emit('privacy:popup-state', { showing: false }) } catch (e) {}
},
},
beforeDestroy() {
@@ -393,6 +437,7 @@ export default {
// 移除全局事件监听
uni.$off('privacy:show', this.handlePrivacyShow)
uni.$off('privacy:forceClose', this.handleForceClose)
uni.$off('privacy:re-evaluate', this.handleReEvaluate)
},
}
</script>