1. add cache for privacy check result to reduce repeated calls 2. refactor lang bundle boot process with integrity checks 3. add timeout recovery for late fetch responses 4. improve error handling and fallback logic 5. add comments for better maintainability
250 lines
6.9 KiB
JavaScript
250 lines
6.9 KiB
JavaScript
/**
|
||
* 全局隐私协议管理器(单例)
|
||
*
|
||
* 职责:
|
||
* 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';
|
||
|
||
// 缓存上一次 getPrivacySetting 的结果, 避免每个组件 mounted 都重新检查
|
||
let _lastCheckResult = null;
|
||
let _lastCheckTs = 0;
|
||
|
||
// 内部状态
|
||
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 _storageAgreed = getStorageAgreed();
|
||
|
||
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;
|