263 lines
7.5 KiB
JavaScript
263 lines
7.5 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 并清除标志,页面组件据此自动弹窗
|
||
*
|
||
* 鸿蒙元服务兼容:
|
||
* Lang.boot() 可能 hang, PrivacyManager.show() 可能从未被调用过;
|
||
* 此时 _pendingShow 仍是默认 false, 但用户从未同意过隐私, 必须强制弹一次。
|
||
* 所以增加 storage 检测: 用户未同意也返回 true (补偿弹窗)。
|
||
* @returns {boolean} true=需要弹窗,false=不需要
|
||
*/
|
||
consumePendingShow() {
|
||
if (_pendingShow && _needShow) {
|
||
_pendingShow = false;
|
||
return true;
|
||
}
|
||
// 鸿蒙兜底: PrivacyManager.show() 没跑过 + 用户没同意过 storage → 必须弹
|
||
// 同一次进程内只补一次,避免隐私弹窗被反复触发
|
||
if (!_hasChecked && !_pendingShow && !getStorageAgreed()) {
|
||
_pendingShow = false;
|
||
_needShow = true;
|
||
_hasChecked = true;
|
||
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;
|