chore:要实现后台开启双语模式,微信小程序真机切换英文模式,此时显示的是英文,后台关闭双语模式,微信小程序刷新重新进入要是中文模式
This commit is contained in:
13
App.vue
13
App.vue
@@ -6,6 +6,11 @@
|
||||
import PrivacyManager from 'common/js/privacy.js';
|
||||
import splashState from 'common/js/splash-state.js';
|
||||
|
||||
// 记录 App 是否已至少展示过一次: 首次 onShow = 启动展示(由 onLaunch 完成初始化),
|
||||
// 之后每一次 onShow(含微信 ... 菜单"重新进入小程序"、从后台切回)都视为"重新进入"。
|
||||
// 微信"重新进入小程序"不一定触发 onHide,所以用"是否展示过"判断更稳妥。
|
||||
let _appHasShown = false;
|
||||
|
||||
import {
|
||||
Weixin
|
||||
} from 'common/js/wx-jssdk.js';
|
||||
@@ -491,6 +496,14 @@
|
||||
},
|
||||
onShow: function(options) {
|
||||
// #ifdef MP
|
||||
// 微信小程序"重新进入小程序"(...) 等场景: onLaunch 不重跑,但模块级内存缓存仍在,
|
||||
// 会让 cachedStorage / 语言包缓存继续返回进入前的旧值。首次 onShow 是启动展示,
|
||||
// 之后每次 onShow(含 ... 重新进入、从后台切回前台)都视为重新进入,主动失效缓存并重新对齐。
|
||||
if (_appHasShown) {
|
||||
try { Lang.onAppReentry(); } catch (e) {}
|
||||
}
|
||||
_appHasShown = true;
|
||||
|
||||
// 自动授权登录
|
||||
this.getAuthInfo();
|
||||
if (this.$store.state.token) {
|
||||
|
||||
@@ -284,6 +284,21 @@ export async function loadBundleTree(options = {}) {
|
||||
const version = String(payload.version || '');
|
||||
const sign = payload.sign || '';
|
||||
|
||||
// 诊断(真机定位用): tree 顶层结构不完整(非对象/为空/缺 common 或 components)时,
|
||||
// 打印实际拿到的 tree 顶层 key + 请求上下文, 便于排查为什么 isBundleComplete 失败
|
||||
// (真机上若看到这条 warn, 说明服务端返回了残缺 bundle, 这才是英文回退/ toast 的根因)
|
||||
if (!tree || typeof tree !== 'object' || !tree.common || !tree.components) {
|
||||
console.warn(
|
||||
'[lang-loader] bundle tree INCOMPLETE. ' +
|
||||
'topLevelKeys=' + JSON.stringify(Object.keys(tree || {})) +
|
||||
' lang=' + lang +
|
||||
' appType=' + appType +
|
||||
' uniacid=' + uniacid +
|
||||
' version=' + version +
|
||||
' hasPayloadTree=' + ((payload && payload.tree) !== undefined)
|
||||
);
|
||||
}
|
||||
|
||||
// 4) 签名校验(可选)
|
||||
const secret = langConfig.remote.signSecret || '';
|
||||
const signValid = validateSign(tree, version, sign, secret);
|
||||
|
||||
@@ -628,6 +628,26 @@ function loadLangPackSync(lang, path) {
|
||||
* 获得当前本地语言
|
||||
* @returns
|
||||
*/
|
||||
/**
|
||||
* 语言切换开关钩子(由后台 shop_info.ischia 决定,经 store/index.js 的 setShopInfo 写入)。
|
||||
*
|
||||
* 设计原则: 只"在关闭时清理持久化语种", 绝不在开启态加任何门控 —— 否则一旦 ischia
|
||||
* 取值/语义判断偏差, 就会误伤"开启双语后切换英文"的正常行为(回归)。
|
||||
*
|
||||
* 行为:
|
||||
* - 后台关闭双语/小程序(ischia 为假): 清掉用户此前持久化到 storage 的语种 + 缓存,
|
||||
* 使刷新/重新进入时 getCurrentLocale() 回退默认中文, 满足
|
||||
* "后台关闭双语后, 刷新重新进入必须是中文" 的需求。
|
||||
* - 后台开启双语(ischia 为真): 不做任何拦截, 完全保持原有"切换英文"行为不变。
|
||||
*/
|
||||
function setLangSwitchEnabled(enabled) {
|
||||
if (!enabled) {
|
||||
// 关闭语言切换: 清掉持久化语种 + 缓存, 强制回到默认中文
|
||||
invalidateStorageCache('lang');
|
||||
try { uni.removeStorageSync('lang'); } catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentLocale() {
|
||||
// 关键:使用 cachedStorageGet,防止 uni.getStorageSync 触发 Vue 事件导致 lang() 递归
|
||||
const val = cachedStorageGet('lang', null);
|
||||
@@ -973,6 +993,13 @@ export default {
|
||||
*/
|
||||
getCurrentLocale,
|
||||
|
||||
/**
|
||||
* 语言切换功能开关(后台 shop_info.ischia 决定)。
|
||||
* 关闭后 getCurrentLocale() 强制返回默认语言(中文),并清掉已持久化的语种。
|
||||
* 详见本文件 setLangSwitchEnabled 函数与 store/index.js 的 setShopInfo 钩子。
|
||||
*/
|
||||
setLangSwitchEnabled,
|
||||
|
||||
/**
|
||||
* 从后端数据中提取当前语言的文案(向后兼容)
|
||||
* 优先使用 xxx_map 多语言 map,没有则回退到 xxx / en_xxx 二元模式
|
||||
@@ -1054,6 +1081,13 @@ export default {
|
||||
// force 失败时 loadBundleTree 内部直接抛错(不回退 cache), 让这里进入 catch
|
||||
let isComplete1 = isBundleComplete(result.tree);
|
||||
if (!isComplete1) {
|
||||
// [debug] 首次拉取的 bundle 不完整: 打印顶层 key, 便于定位远端是否缺 common/components
|
||||
try {
|
||||
console.warn('[i18n:boot] bundle incomplete (1st fetch). ' +
|
||||
'lang=' + lang + ', appType=' + appType + ', uniacid=' + uniacid +
|
||||
', source=' + (result && result.source) +
|
||||
', topKeys=' + JSON.stringify(Object.keys(result && result.tree || {})));
|
||||
} catch (e) {}
|
||||
// 强制重试前清掉所有 cache, 避免 force 路径残留旧数据
|
||||
delete remoteTreeCache[_preloadMk];
|
||||
result = await loadBundleTree({ lang, appType, uniacid, force: true });
|
||||
@@ -1064,6 +1098,13 @@ export default {
|
||||
// 不加载本地 lang 兜底(remote-only 模式, 本地 lang 已被 webpack 排除)
|
||||
let isComplete2 = isBundleComplete(result.tree);
|
||||
if (!isComplete2) {
|
||||
// [debug] force 重试后仍不完整: 打印顶层 key, 确认远端确实返回残缺 tree
|
||||
try {
|
||||
console.warn('[i18n:boot] bundle incomplete (after force retry). ' +
|
||||
'lang=' + lang + ', appType=' + appType + ', uniacid=' + uniacid +
|
||||
', source=' + (result && result.source) +
|
||||
', topKeys=' + JSON.stringify(Object.keys(result && result.tree || {})));
|
||||
} catch (e) {}
|
||||
throw new Error('lang bundle unusable after force retry');
|
||||
}
|
||||
|
||||
@@ -1103,10 +1144,14 @@ export default {
|
||||
setRemoteStatus('fail', { reason: _reason, lang });
|
||||
bootCompleted = true;
|
||||
emitUpdated(lang);
|
||||
// 注意: 这里已用缓存兜底成功(UI 可用), 不应再标 status:'fail',
|
||||
// 否则 App.vue 会弹"Failed to load i18n"误报 toast。
|
||||
// remoteStatus 仍保持 'fail'(上方已 setRemoteStatus), 用于离线指示。
|
||||
return {
|
||||
ok: true,
|
||||
source: 'cache',
|
||||
status: 'fail',
|
||||
status: 'ok',
|
||||
degraded: true,
|
||||
reason: _errMsg || 'fallback to cache',
|
||||
lang,
|
||||
cacheAge: cached.ts ? Date.now() - cached.ts : 0,
|
||||
@@ -1176,6 +1221,42 @@ export default {
|
||||
loadOverrides(lang);
|
||||
},
|
||||
|
||||
/**
|
||||
* 微信小程序"重新进入小程序"(右上角 ... 菜单)场景调用:
|
||||
* 该操作销毁页面栈重新进首页,但小程序 JS 上下文往往不重置(热重启),
|
||||
* 于是 App.onLaunch(里面跑 boot 全量初始化)不会重跑,而模块级内存缓存仍在:
|
||||
* - _storageResultCache(cachedStorage): 可能返回进入前缓存的 lang/uniacid
|
||||
* - _mergedLangCache / _fieldResultCache: 沿用进入前的翻译结果
|
||||
* - remote-only 模式下远端 bundle 不会被重新拉取
|
||||
* 这里主动失效所有内存缓存,并重新对齐远端 bundle,使重新进入后按当前
|
||||
* storage 的 locale 重新读取并合并,不再沿用旧值。
|
||||
* 注意: 首次 onShow(启动展示)不应调用本方法,App.vue 负责区分。
|
||||
*/
|
||||
onAppReentry() {
|
||||
// [debug] 验证微信"重新进入小程序"是否真的命中本分支:真机复现后搜 i18n:reentry
|
||||
try {
|
||||
console.log('[i18n:reentry] onAppReentry triggered, IS_REMOTE_ONLY=' + IS_REMOTE_ONLY +
|
||||
', beforeInvalidateLang=' + (cachedStorageGet('lang', null)) +
|
||||
', beforeInvalidateUniacid=' + (cachedStorageGet('uniacid', null)));
|
||||
} catch (e) {}
|
||||
|
||||
// 1) cachedStorage 失效: 下次 cachedStorageGet 必须重新读 storage(locale/uniacid)
|
||||
invalidateStorageCache();
|
||||
// 2) 语言包合并/字段缓存失效,并 bump _overrideVersion → lang() 用当前 locale 重新合并
|
||||
invalidateLangCaches();
|
||||
// [debug] 失效后立刻重新读,确认 cachedStorage 已重新走 storage(而非命中旧缓存)
|
||||
try {
|
||||
console.log('[i18n:reentry] after invalidate, freshLang=' + getCurrentLocale() +
|
||||
', freshUniacid=' + getCurrentUniacid());
|
||||
} catch (e) {}
|
||||
// 3) remote-only: 重新进入时 onLaunch 不重跑,这里重新拉当前 locale 的 bundle 并推送重渲染
|
||||
if (IS_REMOTE_ONLY) {
|
||||
this.boot();
|
||||
} else {
|
||||
this.refresh(true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* * 订阅远端状态变化(status: 'unknown' | 'ok' | 'fail', detail.reason 有 fail 分类)
|
||||
* @param {(status:string, detail:object) => void} fn
|
||||
|
||||
@@ -9,6 +9,7 @@ import util from '../common/js/util.js'
|
||||
import ThemeOverride from '../common/js/theme-override.js'
|
||||
import { sanitizeFlatMap, mergeThemeBuckets } from '../common/js/merge-theme.js'
|
||||
import PrivacyManager from '../common/js/privacy.js'
|
||||
import LangConfig from '../common/js/lang.js'
|
||||
|
||||
// themeColor 结果缓存(避免每次都全量遍历 theme 对象)
|
||||
// key: JSON.stringify(theme) + tabBarHeight, value: CSS变量字符串
|
||||
@@ -163,6 +164,10 @@ const store = new Vuex.Store({
|
||||
setShopInfo(state, value) {
|
||||
state.shopInfo = value;
|
||||
uni.setStorageSync('shopInfo', value); // 初始化数据调用
|
||||
// 语言切换开关: 后台 shop_info.ischia 决定。
|
||||
// 关闭(ischia 为假)时强制返回默认中文,并清掉用户此前持久化到 storage 的语种,
|
||||
// 满足"后台关闭双语/小程序后,刷新重新进入必须是中文"的需求。
|
||||
try { LangConfig.setLangSwitchEnabled(!!(value && value.ischia)); } catch (e) {}
|
||||
},
|
||||
setCartChange(state) {
|
||||
state.cartChange += 1;
|
||||
|
||||
Reference in New Issue
Block a user