Files
lucky_shop/common/js/privacy.js
ZF sun 7045a05a73 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原型链
2026-07-01 09:39:04 +08:00

242 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 全局隐私协议管理器(单例)
*
* 职责:
* 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;