feat: 添加全局隐私协议管理器并适配鸿蒙元服务
1. 新增common/js/privacy.js全局隐私协议管理器,统一处理多平台隐私弹窗逻辑 2. 替换APP-HARMONY为MP-HARMONY平台判断标识,适配鸿蒙元服务 3. 在mp-html组件中增加MP-HARMONY平台兼容 4. 在App.vue中集成隐私弹窗初始化逻辑 5. 重构privacy-popup.vue,接入全局隐私管理器,优化弹窗互斥逻辑 6. 全局挂载$privacy实例到Vue原型链
This commit is contained in:
7
App.vue
7
App.vue
@@ -3,6 +3,7 @@
|
|||||||
import configExternal, { themeConfig } from 'common/js/config-external.js'
|
import configExternal, { themeConfig } from 'common/js/config-external.js'
|
||||||
import Lang from 'common/js/lang.js';
|
import Lang from 'common/js/lang.js';
|
||||||
import ThemeOverride from 'common/js/theme-override.js';
|
import ThemeOverride from 'common/js/theme-override.js';
|
||||||
|
import PrivacyManager from 'common/js/privacy.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Weixin
|
Weixin
|
||||||
@@ -78,6 +79,12 @@
|
|||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
|
// 隐私协议弹窗: boot 完成后统一触发,确保翻译数据已就绪
|
||||||
|
// 通过 PrivacyManager → uni.$emit('privacy:show') 通知当前页面的 privacy-popup 组件
|
||||||
|
// 只弹一次: 同意后写 storage,后续不再弹
|
||||||
|
// 异步查询平台隐私状态(微信/鸿蒙), H5 同步返回
|
||||||
|
try { await PrivacyManager.show(); } catch (e) { console.warn('[privacy] show fail', e); }
|
||||||
|
|
||||||
// ---- 3) 远端就绪后,主题再 apply 一次(preset + remote 合并 )
|
// ---- 3) 远端就绪后,主题再 apply 一次(preset + remote 合并 )
|
||||||
// 这一次 setThemeStyle + themeColorSet 是同步路径(preset 已在 cache,remote 也在 cache)
|
// 这一次 setThemeStyle + themeColorSet 是同步路径(preset 已在 cache,remote 也在 cache)
|
||||||
// 如果跟 step 2 的 apply 值相同,Vuex 会重复 commit 但页面无视觉变化
|
// 如果跟 step 2 的 apply 值相同,Vuex 会重复 commit 但页面无视觉变化
|
||||||
|
|||||||
241
common/js/privacy.js
Normal file
241
common/js/privacy.js
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
/**
|
||||||
|
* 全局隐私协议管理器(单例)
|
||||||
|
*
|
||||||
|
* 职责:
|
||||||
|
* 1. 统一判断是否需要弹窗(storage + 平台级隐私状态)
|
||||||
|
* 2. 通过 uni.$emit('privacy:show') 通知当前页面的 privacy-popup 组件弹窗
|
||||||
|
* 3. 用户同意后写 storage,拒绝后不写(允许下次再弹)
|
||||||
|
* 4. 只弹一次:同意后永不再弹
|
||||||
|
* 5. 事件补偿:若 emit 时无监听者,保留 _pendingShow,页面 mounted 时补偿弹窗
|
||||||
|
*
|
||||||
|
* 平台差异:
|
||||||
|
* H5: 仅查 storage
|
||||||
|
* 微信小程序: wx.getPrivacySetting() 查平台级隐私状态
|
||||||
|
* 鸿蒙元服务: has.getPrivacySetting() 查平台级隐私状态
|
||||||
|
*
|
||||||
|
* 事件:
|
||||||
|
* uni.$emit('privacy:show') — 通知当前页面组件弹窗
|
||||||
|
* uni.$emit('privacy:agreed') — 用户已同意
|
||||||
|
* uni.$emit('privacy:disagreed') — 用户已拒绝
|
||||||
|
* uni.$emit('privacy:closeOthers') — 通知其他页面关闭弹窗
|
||||||
|
*
|
||||||
|
* 使用:
|
||||||
|
* App.vue onLaunch (boot完成后): await PrivacyManager.show()
|
||||||
|
* privacy-popup.vue: mounted 中 uni.$on('privacy:show') + popPendingShow() 补偿
|
||||||
|
* privacy-popup.vue: handleAgree 中 PrivacyManager.agree()
|
||||||
|
* privacy-popup.vue: handleDisagree 中 PrivacyManager.disagree()
|
||||||
|
*/
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'privacyAgreed';
|
||||||
|
|
||||||
|
// 内部状态
|
||||||
|
let _needShow = false; // 是否需要弹窗
|
||||||
|
let _hasChecked = false; // 是否已执行过 check
|
||||||
|
let _pendingShow = false; // emit 时无监听者的补偿标志
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取 storage 中的同意状态
|
||||||
|
*/
|
||||||
|
function getStorageAgreed() {
|
||||||
|
try {
|
||||||
|
return !!uni.getStorageSync(STORAGE_KEY);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序: 查询平台级隐私设置
|
||||||
|
* @returns {Promise<{needAuthorization: boolean, privacyContractName?: string}>}
|
||||||
|
*/
|
||||||
|
function getWxPrivacySetting() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
try {
|
||||||
|
if (wx.getPrivacySetting) {
|
||||||
|
wx.getPrivacySetting({
|
||||||
|
success: (res) => {
|
||||||
|
resolve({
|
||||||
|
needAuthorization: !!res.needAuthorization,
|
||||||
|
privacyContractName: res.privacyContractName || ''
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fail: () => resolve({ needAuthorization: !getStorageAgreed() })
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 低版本基础库,回退到 storage
|
||||||
|
resolve({ needAuthorization: !getStorageAgreed() });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
resolve({ needAuthorization: !getStorageAgreed() });
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
resolve({ needAuthorization: !getStorageAgreed() });
|
||||||
|
// #endif
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快应用: 查询平台级隐私设置
|
||||||
|
* @returns {Promise<{needAuthorization: boolean, privacyContractName?: string}>}
|
||||||
|
*/
|
||||||
|
function getQuickAppPrivacySetting() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// #ifdef QUICKAPP-WEBVIEW
|
||||||
|
try {
|
||||||
|
const has = require('@system.has');
|
||||||
|
if (has && has.getPrivacySetting) {
|
||||||
|
has.getPrivacySetting({
|
||||||
|
success: (res) => {
|
||||||
|
resolve({
|
||||||
|
needAuthorization: !!res.needAuthorization,
|
||||||
|
privacyContractName: res.privacyContractName || ''
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fail: () => resolve({ needAuthorization: !getStorageAgreed() })
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
// #endif
|
||||||
|
// 非快应用环境: 仅查 storage
|
||||||
|
resolve({ needAuthorization: !getStorageAgreed() });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鸿蒙元服务: 查询平台级隐私设置
|
||||||
|
* 鸿蒙元服务的隐私授权由系统自动管理,应用层只需检查 storage
|
||||||
|
* @returns {Promise<{needAuthorization: boolean, privacyContractName?: string}>}
|
||||||
|
*/
|
||||||
|
function getHarmonyPrivacySetting() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// #ifdef MP-HARMONY
|
||||||
|
// 鸿蒙元服务: 隐私授权由系统管理,应用层仅检查 storage
|
||||||
|
// 系统会在用户首次使用隐私API时自动弹出系统级隐私授权弹窗
|
||||||
|
resolve({ needAuthorization: !getStorageAgreed() });
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-HARMONY
|
||||||
|
resolve({ needAuthorization: !getStorageAgreed() });
|
||||||
|
// #endif
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 综合查询: 平台级隐私状态 + storage 状态
|
||||||
|
* 任一判断为"需要授权"则弹窗
|
||||||
|
* @returns {Promise<{needAuthorization: boolean, privacyContractName?: string}>}
|
||||||
|
*/
|
||||||
|
async function checkPrivacy() {
|
||||||
|
// 微信小程序
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
return await getWxPrivacySetting();
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 快应用
|
||||||
|
// #ifdef QUICKAPP-WEBVIEW
|
||||||
|
return await getQuickAppPrivacySetting();
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 鸿蒙元服务
|
||||||
|
// #ifdef MP-HARMONY
|
||||||
|
return await getHarmonyPrivacySetting();
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// H5 / 其他平台: 仅查 storage
|
||||||
|
// #ifndef MP-WEIXIN || QUICKAPP-WEBVIEW || MP-HARMONY
|
||||||
|
return { needAuthorization: !getStorageAgreed() };
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrivacyManager = {
|
||||||
|
/**
|
||||||
|
* 同步检查 storage 中的同意状态(快速判断,不查平台)
|
||||||
|
* @returns {boolean} true=需要弹窗, false=不需要
|
||||||
|
*/
|
||||||
|
check() {
|
||||||
|
_needShow = !getStorageAgreed();
|
||||||
|
_hasChecked = true;
|
||||||
|
return _needShow;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发弹窗(如果需要)
|
||||||
|
* 由 App.vue onLaunch 在 boot 完成后调用
|
||||||
|
* 异步查询平台隐私状态,设置 _pendingShow 标志
|
||||||
|
* 页面组件 mounted 时通过 consumePendingShow() 消费标志并弹窗
|
||||||
|
* 不直接 emit 事件,避免 emit + 补偿双触发
|
||||||
|
*/
|
||||||
|
async show() {
|
||||||
|
const result = await checkPrivacy();
|
||||||
|
_needShow = result.needAuthorization;
|
||||||
|
_hasChecked = true;
|
||||||
|
|
||||||
|
if (!_needShow) return;
|
||||||
|
|
||||||
|
// 设置待弹窗标志
|
||||||
|
// 已 mounted 的页面组件通过事件接收
|
||||||
|
// 未 mounted 的页面组件 mounted 时通过 consumePendingShow 补偿
|
||||||
|
_pendingShow = true;
|
||||||
|
|
||||||
|
// 通知已 mounted 的页面组件(如果有监听者)
|
||||||
|
try { uni.$emit('privacy:show'); } catch (e) {}
|
||||||
|
|
||||||
|
// 若组件已接收事件并弹窗, 会在 handlePrivacyShow 中调 consumePendingShow 清除 _pendingShow
|
||||||
|
// 若无监听者, _pendingShow 保持 true, 等页面 mounted 时补偿
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面 mounted 时补偿检查: 若 show() 在页面 mounted 之前 emit 了事件
|
||||||
|
* 此方法返回 true 并清除标志,页面组件据此自动弹窗
|
||||||
|
* @returns {boolean} true=有补偿弹窗,false=无
|
||||||
|
*/
|
||||||
|
consumePendingShow() {
|
||||||
|
if (_pendingShow && _needShow) {
|
||||||
|
_pendingShow = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_pendingShow = false;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户同意:写 storage,标记不再弹
|
||||||
|
* 由 privacy-popup.vue 的 handleAgree 调用
|
||||||
|
*/
|
||||||
|
agree() {
|
||||||
|
try { uni.setStorageSync(STORAGE_KEY, true); } catch (e) {}
|
||||||
|
_needShow = false;
|
||||||
|
_pendingShow = false;
|
||||||
|
try { uni.$emit('privacy:agreed'); } catch (e) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户拒绝:不写 storage,允许下次再弹
|
||||||
|
* 由 privacy-popup.vue 的 handleDisagree 调用
|
||||||
|
*/
|
||||||
|
disagree() {
|
||||||
|
_needShow = false;
|
||||||
|
_pendingShow = false;
|
||||||
|
try { uni.$emit('privacy:disagreed'); } catch (e) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是否需要弹窗(供页面查询)
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
needShow() {
|
||||||
|
return _needShow;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已执行过 check
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
hasChecked() {
|
||||||
|
return _hasChecked;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PrivacyManager;
|
||||||
@@ -370,7 +370,7 @@ export default {
|
|||||||
// #ifdef APP-PLUS
|
// #ifdef APP-PLUS
|
||||||
return 'app';
|
return 'app';
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef APP-HARMONY
|
// #ifdef MP-HARMONY
|
||||||
return 'harmony';
|
return 'harmony';
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef QUICKAPP-WEBVIEW
|
// #ifdef QUICKAPP-WEBVIEW
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<view class="privacy-desc">
|
<view class="privacy-desc">
|
||||||
{{ $lang('components.privacyPopup.descPrefix', { appName: appName }) }}
|
{{ $lang('components.privacyPopup.descPrefix', { appName: appName }) }}
|
||||||
<text class="privacy-link" @tap="openPrivacyContract">{{ privacyContractName }}</text>
|
<text class="privacy-link" @tap="openPrivacyContract">{{ privacyContractName }}</text>
|
||||||
<!-- #ifdef QUICKAPP-WEBVIEW || H5 -->
|
<!-- #ifdef QUICKAPP-WEBVIEW || H5 || MP-HARMONY -->
|
||||||
<text class="privacy-link" @tap="openPrivacyService">{{ privacyServiceName }}</text>
|
<text class="privacy-link" @tap="openPrivacyService">{{ privacyServiceName }}</text>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
{{ $lang('components.privacyPopup.descSuffix', { appName: appName }) }}
|
{{ $lang('components.privacyPopup.descSuffix', { appName: appName }) }}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<button id="agree-btn" class="privacy-button-btn bg-agree" open-type="agreePrivacyAuthorization"
|
<button id="agree-btn" class="privacy-button-btn bg-agree" open-type="agreePrivacyAuthorization"
|
||||||
@agreeprivacyauthorization="handleAgree">{{ $lang('components.privacyPopup.agreeBtn') }}</button>
|
@agreeprivacyauthorization="handleAgree">{{ $lang('components.privacyPopup.agreeBtn') }}</button>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<!-- #ifdef WEB || H5 -->
|
<!-- #ifdef H5 || MP-HARMONY -->
|
||||||
<button id="agree-btn" class="privacy-button-btn bg-agree" @tap="handleAgree">{{ $lang('components.privacyPopup.agreeBtn') }}</button>
|
<button id="agree-btn" class="privacy-button-btn bg-agree" @tap="handleAgree">{{ $lang('components.privacyPopup.agreeBtn') }}</button>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
</view>
|
</view>
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
// #ifdef QUICKAPP-WEBVIEW
|
// #ifdef QUICKAPP-WEBVIEW
|
||||||
import has from '@system.has'
|
import has from '@system.has'
|
||||||
// #endif
|
// #endif
|
||||||
|
import PrivacyManager from '@/common/js/privacy.js';
|
||||||
|
|
||||||
const closeOtherPagePopUpHooks = new Set();
|
|
||||||
const privacyContractPage = '/pages_tool/agreement/contenr?type=0';
|
const privacyContractPage = '/pages_tool/agreement/contenr?type=0';
|
||||||
const privacyServicePage = '/pages_tool/agreement/contenr?type=1';
|
const privacyServicePage = '/pages_tool/agreement/contenr?type=1';
|
||||||
|
|
||||||
@@ -45,7 +45,12 @@ export default {
|
|||||||
privacyResolves: new Set(),
|
privacyResolves: new Set(),
|
||||||
privacyContractName: this.$lang('components.privacyPopup.defaultContractNameMp'),
|
privacyContractName: this.$lang('components.privacyPopup.defaultContractNameMp'),
|
||||||
appName: this.$lang('components.privacyPopup.defaultAppNameMp'),
|
appName: this.$lang('components.privacyPopup.defaultAppNameMp'),
|
||||||
// #ifdef WEB || H5
|
// #ifdef H5
|
||||||
|
appName: this.$lang('components.privacyPopup.defaultAppNameH5'),
|
||||||
|
privacyContractName: this.$lang('components.privacyPopup.defaultContractNameH5'),
|
||||||
|
privacyServiceName: this.$lang('components.privacyPopup.defaultServiceNameH5'),
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-HARMONY
|
||||||
appName: this.$lang('components.privacyPopup.defaultAppNameH5'),
|
appName: this.$lang('components.privacyPopup.defaultAppNameH5'),
|
||||||
privacyContractName: this.$lang('components.privacyPopup.defaultContractNameH5'),
|
privacyContractName: this.$lang('components.privacyPopup.defaultContractNameH5'),
|
||||||
privacyServiceName: this.$lang('components.privacyPopup.defaultServiceNameH5'),
|
privacyServiceName: this.$lang('components.privacyPopup.defaultServiceNameH5'),
|
||||||
@@ -55,7 +60,20 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.listenPrivacySettingChange()
|
this.listenPrivacySettingChange()
|
||||||
this.curPageShow()
|
this.curPageShow()
|
||||||
this.proactive()
|
|
||||||
|
// 监听全局隐私弹窗事件(由 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()
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
try {
|
try {
|
||||||
@@ -67,8 +85,8 @@ export default {
|
|||||||
});
|
});
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifdef QUICKAPP-WEBVIEW || H5
|
// #ifdef QUICKAPP-WEBVIEW
|
||||||
if (this.$util.isQuickApp() && typeof has != 'undefined' && has?.getPrivacySetting) {
|
if (typeof has != 'undefined' && has?.getPrivacySetting) {
|
||||||
has.getPrivacySetting({
|
has.getPrivacySetting({
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.privacyContractName) {
|
if (res.privacyContractName) {
|
||||||
@@ -78,17 +96,44 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-HARMONY
|
||||||
|
// 鸿蒙元服务: 隐私授权由系统管理, 无需应用层查询 privacyContractName
|
||||||
|
// #endif
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("=========低版本基础库==========", e)
|
console.error("=========低版本基础库==========", e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* 接收全局隐私弹窗事件
|
||||||
|
* 由 App.vue → PrivacyManager.show() → uni.$emit('privacy:show') 触发
|
||||||
|
* 多个实例同时监听时, 只有第一个调用 consumePendingShow 成功的实例才弹窗
|
||||||
|
*/
|
||||||
|
handlePrivacyShow() {
|
||||||
|
// 互斥: 只有一个实例能消费成功
|
||||||
|
if (!PrivacyManager.consumePendingShow()) return
|
||||||
|
this.showPrivacyPopup()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收其他实例的关闭通知
|
||||||
|
* 当另一个 privacy-popup 实例弹窗时, 本实例关闭自己的弹窗
|
||||||
|
* 避免多个实例同时弹窗导致需要点击两次
|
||||||
|
*/
|
||||||
|
handleForceClose() {
|
||||||
|
this.disPopUp()
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一弹窗 + 关闭其他页面的弹窗
|
* 统一弹窗 + 关闭其他页面的弹窗
|
||||||
|
* 通过 uni.$emit('privacy:forceClose') 通知其他实例关闭
|
||||||
*/
|
*/
|
||||||
showPrivacyPopup() {
|
showPrivacyPopup() {
|
||||||
|
// 先通知其他实例关闭
|
||||||
|
try { uni.$emit('privacy:forceClose'); } catch (e) {}
|
||||||
|
// 再弹出本实例
|
||||||
this.popUp()
|
this.popUp()
|
||||||
this.closeOtherPagePopUp(this.disPopUp)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,8 +159,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getPrivacySettingByQuickApp() {
|
getPrivacySettingByQuickApp() {
|
||||||
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
|
// #ifdef QUICKAPP-WEBVIEW
|
||||||
if (!this.$util.isQuickApp()) return;
|
|
||||||
if (typeof has != 'undefined' && has?.getPrivacySetting) {
|
if (typeof has != 'undefined' && has?.getPrivacySetting) {
|
||||||
has.getPrivacySetting({
|
has.getPrivacySetting({
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
@@ -130,50 +174,45 @@ export default {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听平台侧的隐私授权事件
|
* 监听平台侧的隐私授权事件
|
||||||
|
* 微信小程序: wx.onNeedPrivacyAuthorization 由当前页面实例注册
|
||||||
|
* 鸿蒙元服务: has.onPrivacySettingChange
|
||||||
|
* 与 PrivacyManager 协同:
|
||||||
|
* - 若 PrivacyManager 判断需要弹窗 → 弹自定义弹窗, resolve 存入 privacyResolves
|
||||||
|
* - 若已同意 → 直接 resolve 放行
|
||||||
*/
|
*/
|
||||||
listenPrivacySettingChange() {
|
listenPrivacySettingChange() {
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
if (wx.onNeedPrivacyAuthorization) {
|
if (wx.onNeedPrivacyAuthorization) {
|
||||||
wx.onNeedPrivacyAuthorization((resolve) => {
|
wx.onNeedPrivacyAuthorization((resolve) => {
|
||||||
if (typeof this.privacyAuthorization === 'function') {
|
// 检查是否已同意
|
||||||
this.privacyAuthorization(resolve)
|
if (!PrivacyManager.needShow()) {
|
||||||
|
// 已同意, 直接放行
|
||||||
|
resolve({ event: 'agree' })
|
||||||
|
} else {
|
||||||
|
// 需要弹窗, 存 resolve 等用户操作
|
||||||
|
if (typeof this.privacyAuthorization === 'function') {
|
||||||
|
this.privacyAuthorization(resolve)
|
||||||
|
} else {
|
||||||
|
// 兜底: 组件未就绪, 直接放行避免 API 卡死
|
||||||
|
resolve({ event: 'agree' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
|
// #ifdef QUICKAPP-WEBVIEW
|
||||||
if (this.$util.isQuickApp() && typeof has != 'undefined' && has?.onPrivacySettingChange) {
|
if (typeof has != 'undefined' && has?.onPrivacySettingChange) {
|
||||||
has.onPrivacySettingChange((res) => {
|
has.onPrivacySettingChange((res) => {
|
||||||
this.checkAndShow(res.needAuthorization)
|
if (res.needAuthorization && PrivacyManager.needShow()) {
|
||||||
|
this.showPrivacyPopup()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
// #ifdef MP-HARMONY
|
||||||
* 主动查询隐私授权状态并弹窗
|
// 鸿蒙元服务: 隐私授权由系统自动管理, 无需应用层监听
|
||||||
*/
|
|
||||||
proactive() {
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
if (wx.getPrivacySetting) {
|
|
||||||
wx.getPrivacySetting({
|
|
||||||
success: (res) => {
|
|
||||||
this.checkAndShow(res.needAuthorization)
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 低版本基础库直接检查本地存储
|
|
||||||
this.checkAndShow(!uni.getStorageSync('privacyAgreed'))
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef QUICKAPP-WEBVIEW
|
|
||||||
this.getPrivacySettingByQuickApp();
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef H5 || WEB
|
|
||||||
this.checkAndShow(!uni.getStorageSync('privacyAgreed'))
|
|
||||||
// #endif
|
// #endif
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -181,7 +220,6 @@ export default {
|
|||||||
* 初始化监听程序(页面级)
|
* 初始化监听程序(页面级)
|
||||||
*/
|
*/
|
||||||
curPageShow() {
|
curPageShow() {
|
||||||
closeOtherPagePopUpHooks.add(this.disPopUp)
|
|
||||||
this.privacyAuthorization = resolve => {
|
this.privacyAuthorization = resolve => {
|
||||||
this.privacyResolves.add(resolve)
|
this.privacyResolves.add(resolve)
|
||||||
this.showPrivacyPopup()
|
this.showPrivacyPopup()
|
||||||
@@ -192,11 +230,8 @@ export default {
|
|||||||
* 弹窗时关掉其他页面的隐私弹窗,避免重叠
|
* 弹窗时关掉其他页面的隐私弹窗,避免重叠
|
||||||
*/
|
*/
|
||||||
closeOtherPagePopUp(closePopUp) {
|
closeOtherPagePopUp(closePopUp) {
|
||||||
closeOtherPagePopUpHooks.forEach(hook => {
|
// 通过全局事件通知所有页面关闭弹窗
|
||||||
if (closePopUp !== hook) {
|
try { uni.$emit('privacy:closeOthers', this._uid); } catch (e) {}
|
||||||
hook()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
openPrivacyContract() {
|
openPrivacyContract() {
|
||||||
@@ -207,37 +242,46 @@ export default {
|
|||||||
});
|
});
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
|
// #ifdef QUICKAPP-WEBVIEW
|
||||||
if (this.$util.isQuickApp()) {
|
if (typeof has != 'undefined' && has?.openPrivacySetting) {
|
||||||
if (typeof has != 'undefined' && has?.openPrivacySetting) {
|
has.openPrivacySetting({
|
||||||
has.openPrivacySetting({
|
success: (res) => {},
|
||||||
success: (res) => {},
|
fail: (err) => {}
|
||||||
fail: (err) => {}
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$util.redirectTo(privacyContractPage);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.$util.redirectTo(privacyContractPage);
|
this.$util.redirectTo(privacyContractPage);
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-HARMONY
|
||||||
|
// 鸿蒙元服务: 使用应用内协议页面
|
||||||
|
this.$util.redirectTo(privacyContractPage);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
this.$util.redirectTo(privacyContractPage);
|
||||||
|
// #endif
|
||||||
},
|
},
|
||||||
|
|
||||||
openPrivacyService() {
|
openPrivacyService() {
|
||||||
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
|
// #ifdef QUICKAPP-WEBVIEW
|
||||||
if (this.$util.isQuickApp()) {
|
if (typeof has != 'undefined' && has?.openPrivacySetting) {
|
||||||
if (typeof has != 'undefined' && has?.openPrivacySetting) {
|
has.openPrivacySetting({
|
||||||
has.openPrivacySetting({
|
success: (res) => {},
|
||||||
success: (res) => {},
|
fail: (err) => {}
|
||||||
fail: (err) => {}
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$util.redirectTo(privacyServicePage);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.$util.redirectTo(privacyServicePage);
|
this.$util.redirectTo(privacyServicePage);
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-HARMONY
|
||||||
|
this.$util.redirectTo(privacyServicePage);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
this.$util.redirectTo(privacyServicePage);
|
||||||
|
// #endif
|
||||||
},
|
},
|
||||||
|
|
||||||
handleDisagree() {
|
handleDisagree() {
|
||||||
@@ -245,9 +289,14 @@ export default {
|
|||||||
resolve({ event: 'disagree' })
|
resolve({ event: 'disagree' })
|
||||||
})
|
})
|
||||||
this.privacyResolves.clear()
|
this.privacyResolves.clear()
|
||||||
|
|
||||||
|
// 先通知其他实例关闭, 再关闭自己, 避免叠层
|
||||||
|
try { uni.$emit('privacy:forceClose'); } catch (e) {}
|
||||||
this.disPopUp()
|
this.disPopUp()
|
||||||
|
|
||||||
uni.setStorageSync('privacyAgreed', true);
|
// 修复: 拒绝时不写 privacyAgreed, 允许下次再弹
|
||||||
|
// 由 PrivacyManager 管理 storage
|
||||||
|
PrivacyManager.disagree()
|
||||||
|
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
content: this.$lang('components.privacyPopup.disagreeTip'),
|
content: this.$lang('components.privacyPopup.disagreeTip'),
|
||||||
@@ -265,9 +314,13 @@ export default {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
this.privacyResolves.clear()
|
this.privacyResolves.clear()
|
||||||
|
|
||||||
|
// 先通知其他实例关闭, 再关闭自己
|
||||||
|
try { uni.$emit('privacy:forceClose'); } catch (e) {}
|
||||||
this.disPopUp()
|
this.disPopUp()
|
||||||
|
|
||||||
uni.setStorageSync('privacyAgreed', true);
|
// 由 PrivacyManager 管理 storage
|
||||||
|
PrivacyManager.agree()
|
||||||
this.$emit('agree')
|
this.$emit('agree')
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -281,7 +334,9 @@ export default {
|
|||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.privacyResolves.clear()
|
this.privacyResolves.clear()
|
||||||
closeOtherPagePopUpHooks.delete(this.disPopUp)
|
// 移除全局事件监听
|
||||||
|
uni.$off('privacy:show', this.handlePrivacyShow)
|
||||||
|
uni.$off('privacy:forceClose', this.handleForceClose)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -335,9 +390,9 @@ export default {
|
|||||||
.privacy-button-btn {
|
.privacy-button-btn {
|
||||||
color: #FFF;
|
color: #FFF;
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
|
/* #ifdef QUICKAPP-WEBVIEW || H5 || MP-HARMONY */
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
// #endif
|
/* #endif */
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 100rpx;
|
line-height: 100rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
3
main.js
3
main.js
@@ -15,6 +15,7 @@ import {
|
|||||||
uniStorage
|
uniStorage
|
||||||
} from './common/js/storage.js'
|
} from './common/js/storage.js'
|
||||||
import configExternal from './common/js/config-external.js'
|
import configExternal from './common/js/config-external.js'
|
||||||
|
import PrivacyManager from './common/js/privacy.js'
|
||||||
|
|
||||||
Vue.prototype.$store = store //挂在vue
|
Vue.prototype.$store = store //挂在vue
|
||||||
|
|
||||||
@@ -28,6 +29,8 @@ Vue.prototype.$lang = Lang.lang; //解析语言包
|
|||||||
|
|
||||||
Vue.prototype.$config = Config;
|
Vue.prototype.$config = Config;
|
||||||
|
|
||||||
|
Vue.prototype.$privacy = PrivacyManager; //全局隐私管理器
|
||||||
|
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
EventBus.setDomBridge(DomEventBridge)
|
EventBus.setDomBridge(DomEventBridge)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<!-- 表格中的图片,使用 rich-text 防止大小不正确 -->
|
<!-- 表格中的图片,使用 rich-text 防止大小不正确 -->
|
||||||
<rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style||'',src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" />
|
<rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style||'',src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" />
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<!-- #ifdef APP-HARMONY -->
|
<!-- #ifdef MP-HARMONY || APP-HARMONY -->
|
||||||
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+ctrl[i]+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':(n.m||'scaleToFill'))" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" show-menu-by-longpress/>
|
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+ctrl[i]+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':(n.m||'scaleToFill'))" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" show-menu-by-longpress/>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<!-- #ifndef H5 || APP-PLUS || MP-KUAISHOU -->
|
<!-- #ifndef H5 || APP-PLUS || MP-KUAISHOU -->
|
||||||
@@ -156,7 +156,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
// #ifndef ((H5 || APP-PLUS) && VUE3) || APP-HARMONY
|
// #ifndef ((H5 || APP-PLUS) && VUE3) || MP-HARMONY
|
||||||
node
|
node
|
||||||
// #endif
|
// #endif
|
||||||
},
|
},
|
||||||
@@ -268,10 +268,10 @@ export default {
|
|||||||
// #ifdef H5 || APP-PLUS
|
// #ifdef H5 || APP-PLUS
|
||||||
node.attrs.src = node.attrs.src || node.attrs['data-src']
|
node.attrs.src = node.attrs.src || node.attrs['data-src']
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef APP-HARMONY
|
// #ifndef MP-HARMONY
|
||||||
this.root.$emit('imgtap', node.attrs)
|
this.root.$emit('imgtap', node.attrs)
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef APP-HARMONY
|
// #ifdef MP-HARMONY
|
||||||
this.root.$emit('imgtap', {
|
this.root.$emit('imgtap', {
|
||||||
...node.attrs
|
...node.attrs
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user