- 新增隐私管理器核心逻辑,支持URL路由规则匹配、远程patch签名校验与融合 - 实现启动期资源等待机制,确保弹窗在i18n、主题配置就绪后触发 - 新增启动进度条组件,展示启动阶段进度与状态 - 重构隐私弹窗组件生命周期,修复事件丢失问题并支持规则动态重评估 - 适配多端平台隐私检查逻辑,支持微信小程序、鸿蒙元服务与H5 - 支持uniacid租户隔离的持久化规则存储
229 lines
8.8 KiB
JavaScript
229 lines
8.8 KiB
JavaScript
// #ifdef H5
|
||
import './common/js/pc'
|
||
// #endif
|
||
import Vue from 'vue'
|
||
import App from './App'
|
||
import store from './store'
|
||
import Util from './common/js/util.js'
|
||
import Http from './common/js/http.js'
|
||
import Lang from './common/js/lang.js'
|
||
import Config from './common/js/config.js'
|
||
import EventBus from './common/js/event-bus.js'
|
||
import DomEventBridge from './common/js/dom-event-bridge.js'
|
||
import globalConfig from './common/js/golbalConfig.js';
|
||
import {
|
||
uniStorage
|
||
} from './common/js/storage.js'
|
||
import configExternal from './common/js/config-external.js'
|
||
import PrivacyManager from './common/js/privacy.js'
|
||
import splashState from './common/js/splash-state.js'
|
||
import ThemeOverride from './common/js/theme-override.js'
|
||
|
||
Vue.prototype.$store = store //挂在vue
|
||
|
||
Vue.config.productionTip = false
|
||
|
||
Vue.prototype.$util = Util;
|
||
Vue.prototype.$api = Http;
|
||
|
||
Vue.prototype.$langConfig = Lang; //语言包对象
|
||
Vue.prototype.$lang = Lang.lang; //解析语言包
|
||
|
||
Vue.prototype.$config = Config;
|
||
|
||
Vue.prototype.$privacy = PrivacyManager; //全局隐私管理器
|
||
|
||
// 调试: 暴露 PrivacyManager 到 window, 便于 e2e 测试 / 调试 / 控制台排查
|
||
// 仅 H5 + dev mode 暴露, 生产构建会因条件编译 + webpack DefinePlugin 消除
|
||
// #ifdef H5
|
||
if (typeof window !== 'undefined') {
|
||
window.__privacy__ = PrivacyManager
|
||
// 隐私调试日志开关 (H5 dev only)
|
||
// 设为 false 关闭 privacy 内部 console.log
|
||
// 设 true: 走 verbose 日志 (reEvaluate 触发/调用, rules-updated 等)
|
||
// 注: 不在条件编译 H5 内, 因为微信/鸿蒙也可启用
|
||
try {
|
||
if (typeof globalThis !== 'undefined' && globalThis.__PRIVACY_DEBUG__ === undefined) {
|
||
globalThis.__PRIVACY_DEBUG__ = true // dev 默认开, 生产可通过 DefinePlugin 关闭
|
||
}
|
||
} catch (e) { }
|
||
}
|
||
// #endif
|
||
|
||
|
||
// #ifdef H5
|
||
EventBus.setDomBridge(DomEventBridge)
|
||
// #endif
|
||
Vue.prototype.$eventBus = EventBus;
|
||
|
||
Vue.mixin(globalConfig);
|
||
|
||
// #ifdef MP-WEIXIN
|
||
// 全局兜底分享 mixin:
|
||
// 为所有未显式声明 onShareAppMessage / onShareTimeline 的页面提供默认实现,
|
||
// 保证"全页面都支持转发 / 朋友圈 / 复制路径"。
|
||
// - 已显式声明的页面优先级更高(uni-app 规范),不受影响
|
||
// - 后端 /weapp/api/weapp/share 提供标题 / 封面,失败时退化到站点名 + 当前路径
|
||
import MpShareDefault from './common/js/mp-share-default.js';
|
||
Vue.mixin(MpShareDefault);
|
||
// #endif
|
||
|
||
// 远端 i18n 翻译热更新:复用项目原有"componentRefresh"机制(同 diy.js 下拉刷新)
|
||
// lang.js 在翻译覆写更新时 commit('setComponentRefresh'),
|
||
// golbalConfig.js 已把 componentRefresh 注册为全局 computed,
|
||
// 在此 watch 它并 $forceUpdate,触发所有页面内 {{$lang('xxx')}} 重新求值
|
||
Vue.mixin({
|
||
watch: {
|
||
componentRefresh() {
|
||
this.$forceUpdate();
|
||
}
|
||
}
|
||
});
|
||
|
||
// 鸿蒙元服务环境: 全局接管 onTabItemTap, 兜底系统 TabBar 点击
|
||
// 背景: 鸿蒙下 uni.switchTab 对系统 TabBar 点击静默失败
|
||
// 通过全局 mixin 覆盖所有未显式定义 onTabItemTap 的页面(子包动态 tab 页等),
|
||
// 显式定义 onTabItemTap 的页面(如 pages/empty/index.vue、index.js)优先级高于此 mixin
|
||
// #ifdef MP-HARMONY
|
||
Vue.mixin({
|
||
onTabItemTap(e) {
|
||
console.log('[TabBar][HARMONY] global onTabItemTap fired:', e);
|
||
if (!e || !e.pagePath) return;
|
||
const pages = getCurrentPages();
|
||
const currentPage = pages[pages.length - 1];
|
||
const currentPath = currentPage ? '/' + currentPage.route : '';
|
||
const targetPath = e.pagePath.startsWith('/') ? e.pagePath : '/' + e.pagePath;
|
||
console.log('[TabBar][HARMONY] onTabItemTap currentPath=', currentPath, 'targetPath=', targetPath);
|
||
// 已经在目标页则不重复跳转
|
||
if (currentPath === targetPath) return;
|
||
// 用干净路径(不带 query), 避免 HarmonyOS 对带 query 的 tabBar 跳转有兼容性问题
|
||
uni.reLaunch({
|
||
url: targetPath,
|
||
success: () => console.log('[TabBar][HARMONY] global reLaunch success:', targetPath),
|
||
fail: (err) => console.error('[TabBar][HARMONY] global reLaunch fail:', targetPath, err)
|
||
});
|
||
}
|
||
});
|
||
// #endif
|
||
|
||
App.mpType = 'app';
|
||
|
||
// 重写存储,增加前缀
|
||
uniStorage();
|
||
|
||
// 组件已改为按需引入,不再全局注册
|
||
|
||
// ============================================================
|
||
// 平台差异处理:
|
||
// H5: 可在 $mount() 前 await boot, 首屏直接有翻译
|
||
// 小程序/鸿蒙: App() 必须同步注册, $mount() 不能推迟
|
||
// → 同步 $mount() + lang() 短路返回空串 + boot 完成后 $forceUpdate
|
||
//
|
||
// 以下代码所有平台共享:
|
||
// - lang.js: bootCompleted 标志, boot 未完成时 lang() 返回空串
|
||
// - http.js: sendRequest 门控, boot 未完成时业务请求排队
|
||
// - splash-state.js: 统一启动屏状态 (H5 降级 uni.showLoading, 小程序/鸿蒙走 splash 组件)
|
||
// - App.vue: onLaunch 中 splashState.show() + await boot + splashState.hide()
|
||
// ============================================================
|
||
|
||
// #ifdef H5
|
||
// H5: 先 await boot 再 $mount(), 首屏直接有翻译数据
|
||
// splashState.show() 内部 H5 降级为 uni.showLoading (await 期间 Vue 还没挂载,组件层无法渲染)
|
||
// 关键: 无条件 show (不依赖 Lang.isRemoteOnly), 任何模式下都让 uni.showLoading 兜底
|
||
// - local 模式: Lang.boot() sync, splash 立即 hide, 用户几乎看不到
|
||
// - remote-only 模式: Lang.boot() 远端拉取, splash 显示整个 boot 期
|
||
async function bootstrap() {
|
||
if (Lang.isRemoteOnly && Lang.isRemoteOnly()) {
|
||
splashState.show();
|
||
try {
|
||
// 1) i18n: 拉远端语言包 (langConfig.source === 'remote-only')
|
||
await Lang.boot();
|
||
// 2) theme: 阻塞式预热 brand 桶覆写 (themeConfig.overrideRemote.source)
|
||
await ThemeOverride.boot();
|
||
// 3) theme(remote-only): 异步拉全量 8 个 preset 替换 stub
|
||
// 失败静默, 兜底用 webpack stub(只含 default),首屏不白屏
|
||
ThemeOverride.loadAllPresets().catch((e) => {
|
||
console.warn('[theme-presets] load fail', e);
|
||
});
|
||
} catch (e) {
|
||
console.warn('[i18n] boot fail', e);
|
||
} finally {
|
||
splashState.hide({ reason: 'boot-complete' });
|
||
}
|
||
}
|
||
|
||
const app = new Vue({
|
||
...App,
|
||
store
|
||
});
|
||
app.$mount();
|
||
|
||
setupResizeListener();
|
||
}
|
||
bootstrap()
|
||
// #endif
|
||
|
||
// #ifndef H5
|
||
// 小程序/鸿蒙: 同步 $mount(), boot 在 App.vue onLaunch 中异步执行
|
||
// lang() 在 boot 未完成时返回空串, boot 完成后 emitUpdated 触发 $forceUpdate
|
||
//
|
||
// 关键: splashState.show() 必须**最早期**同步触发 (这里, 不在 App.vue onLaunch)
|
||
// 原因: onLaunch 到 step 1.5 splashState.show() 之前, 用户看到的是白屏
|
||
// splash 状态机只需要 'visible=true', 实际渲染等组件 mount
|
||
splashState.show();
|
||
const app = new Vue({
|
||
...App,
|
||
store
|
||
});
|
||
app.$mount();
|
||
// #endif
|
||
|
||
// ============================================================
|
||
// 窗口尺寸变化监听
|
||
// ------------------------------------------------------------
|
||
// 背景: themeColorSet 会把 token 中的 rpx 转 px,转比例基于 window.innerWidth/750
|
||
// 缓存 key 已含 window.innerWidth,所以 cacheKey 不匹配时会自动重算;
|
||
// 但需要 resize 事件触发一次 themeColorSet 才会让 cacheKey 重新计算。
|
||
//
|
||
// 兼容点: 不同发布产物的 resize 来源不一样
|
||
// 1. 优先用 uni.onWindowResize — uni-app 跨端 API, H5 / App-Plus 支持
|
||
// 2. 兜底用 window.addEventListener('resize') — 纯 H5 产物中直接可用
|
||
// 3. 全部 try-catch,某条路径失败不影响其他路径
|
||
// 4. 用 200ms 去抖,避免拖动窗口时频繁触发(实测连续 resize 事件可达 60fps)
|
||
//
|
||
// 调用时机: 仅 H5 端在 $mount() 之后调用;小程序/鸿蒙端
|
||
// - 微信小程序: 屏幕尺寸由设备决定,运行时不会变化,无需监听
|
||
// - 鸿蒙: 待验证,目前不在这里注册以避免在不支持的环境下抛错
|
||
// ============================================================
|
||
function setupResizeListener() {
|
||
let resizeTimer = null;
|
||
const fire = () => {
|
||
if (resizeTimer) clearTimeout(resizeTimer);
|
||
resizeTimer = setTimeout(() => {
|
||
resizeTimer = null;
|
||
try {
|
||
store.dispatch('themeColorSet');
|
||
} catch (e) {
|
||
console.warn('[theme] resize -> themeColorSet fail', e);
|
||
}
|
||
}, 200);
|
||
};
|
||
|
||
// 路径 1: uni 跨端 API(H5 / App-Plus 上有效)
|
||
try {
|
||
if (typeof uni !== 'undefined' && typeof uni.onWindowResize === 'function') {
|
||
uni.onWindowResize(fire);
|
||
}
|
||
} catch (e) {
|
||
console.warn('[theme] uni.onWindowResize bind fail', e);
|
||
}
|
||
|
||
// 路径 2: 原生 window resize 事件(H5 兜底,某些自定义 H5 产物可能没装 uni)
|
||
try {
|
||
if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
|
||
window.addEventListener('resize', fire);
|
||
}
|
||
} catch (e) {
|
||
console.warn('[theme] window.resize bind fail', e);
|
||
}
|
||
} |