refactor(privacy-popup): 抽取通用延迟i18n混入并重构隐私弹窗
1. 新建通用的i18n延迟初始化混入,解决小程序/鸿蒙环境下i18n空串缓存问题 2. 将隐私弹窗原有的prepareI18n逻辑迁移到混入中,统一实现i18nPrepareFn 3. 简化弹窗组件内的i18n处理代码,优化可维护性
This commit is contained in:
71
common/mixin/i18n-lazy.js
Normal file
71
common/mixin/i18n-lazy.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* 延迟初始化 i18n mixin
|
||||||
|
*
|
||||||
|
* 问题背景:
|
||||||
|
* 微信小程序/鸿蒙元服务环境下, main.js 同步 $mount() 会导致组件 data() 在
|
||||||
|
* Lang.boot() 完成前求值. 此时 $lang() 在 remote-only 模式下返回空串,
|
||||||
|
* 把空串缓存到 data 字段后, 后续 boot 完成的 $forceUpdate 也无法让它重算.
|
||||||
|
*
|
||||||
|
* 解决思路:
|
||||||
|
* data() 中不能调 $lang(), 否则空串被永久缓存.
|
||||||
|
* 模板只读 this.i18n.xxx(始终是字符串, 初值为空).
|
||||||
|
* 需要显示前, 显式调一次 this.prepareI18n() — 此时 boot 必然已完成.
|
||||||
|
*
|
||||||
|
* 用法:
|
||||||
|
*
|
||||||
|
* // 1) 在 data() 中:
|
||||||
|
* data() {
|
||||||
|
* return {
|
||||||
|
* i18n: {
|
||||||
|
* title: '',
|
||||||
|
* descPrefix: '',
|
||||||
|
* privacyContractName: '',
|
||||||
|
* },
|
||||||
|
* // 准备文案时需要的动态上下文(可选)
|
||||||
|
* platformContractName: '',
|
||||||
|
* };
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // 2) 在 methods 中实现 i18nPrepareFn, 返回一个新对象用于填充 this.i18n
|
||||||
|
* methods: {
|
||||||
|
* i18nPrepareFn() {
|
||||||
|
* return {
|
||||||
|
* title: this.$lang('components.foo.title'),
|
||||||
|
* descPrefix: this.$lang('components.foo.descPrefix', { appName: '本应用' }),
|
||||||
|
* privacyContractName: this.platformContractName || this.$lang('components.foo.default'),
|
||||||
|
* };
|
||||||
|
* },
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // 3) 在需要显示前调 this.prepareI18n()(来自 mixin)
|
||||||
|
* // 此时 Lang.boot() 必然已完成, $lang() 返回真实翻译
|
||||||
|
* popUp() {
|
||||||
|
* this.prepareI18n();
|
||||||
|
* this.showPop = true;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* 模板中只读 i18n.xxx:
|
||||||
|
* ```html
|
||||||
|
* <view>{{ i18n.title }}</view>
|
||||||
|
* <view>{{ i18n.descPrefix }}</view>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 一次性计算所有 i18n 文案并写入 this.i18n
|
||||||
|
* 关键: 调用时机必须是 boot 完成之后(否则会缓存空串)
|
||||||
|
* 模板中读 this.i18n.xxx 永远拿到字符串
|
||||||
|
*
|
||||||
|
* 业务组件需要实现 i18nPrepareFn() 方法,
|
||||||
|
* 返回一个对象, key 与 data().i18n 字段一一对应
|
||||||
|
*/
|
||||||
|
prepareI18n() {
|
||||||
|
if (typeof this.i18nPrepareFn !== 'function') {
|
||||||
|
console.warn('[i18n-lazy] i18nPrepareFn() not implemented; this.i18n will not be filled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.i18n = this.i18nPrepareFn();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -31,12 +31,14 @@
|
|||||||
import has from '@system.has'
|
import has from '@system.has'
|
||||||
// #endif
|
// #endif
|
||||||
import PrivacyManager from '@/common/js/privacy.js';
|
import PrivacyManager from '@/common/js/privacy.js';
|
||||||
|
import i18nLazyMixin from '@/common/mixin/i18n-lazy.js';
|
||||||
|
|
||||||
const privacyContractPage = '/pages_tool/agreement/contenr?type=0';
|
const privacyContractPage = '/pages_tool/agreement/contenr?type=0';
|
||||||
const privacyServicePage = '/pages_tool/agreement/contenr?type=1';
|
const privacyServicePage = '/pages_tool/agreement/contenr?type=1';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PrivacyPopup',
|
name: 'PrivacyPopup',
|
||||||
|
mixins: [i18nLazyMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
agree: false,
|
agree: false,
|
||||||
@@ -44,12 +46,12 @@ export default {
|
|||||||
privacyAuthorization: null,
|
privacyAuthorization: null,
|
||||||
privacyResolves: new Set(),
|
privacyResolves: new Set(),
|
||||||
// 平台提供的隐私协议名称(由 wx.getPrivacySetting 等异步设置)
|
// 平台提供的隐私协议名称(由 wx.getPrivacySetting 等异步设置)
|
||||||
// 弹窗前才参与计算 i18n.privacyContractName
|
// 弹窗前才参与 i18n.privacyContractName 的填充
|
||||||
platformContractName: '',
|
platformContractName: '',
|
||||||
// 弹窗文案: 在 popUp() 之前由 prepareI18n() 一次性计算
|
// 弹窗文案: 模板只读 i18n.xxx
|
||||||
// 关键: 不在 data() 中调 $lang(避免 boot 未完成时缓存空串)
|
// 初值为空串, 弹窗前在 popUp() 中**同步**填充
|
||||||
// 也不在 mounted/computed 中调(method/computed 响应式不稳定)
|
// 此时 App.vue 已 await boot() + await PrivacyManager.show() 完成,
|
||||||
// 此时 Lang.boot() 必然已完成(PrivacyManager.show() 在 await boot() 后才执行)
|
// Lang.boot() 必然已完成, $lang() 返回真实翻译
|
||||||
i18n: {
|
i18n: {
|
||||||
title: '',
|
title: '',
|
||||||
descPrefix: '',
|
descPrefix: '',
|
||||||
@@ -110,57 +112,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
|
||||||
* 在弹窗前**一次性**计算所有 i18n 文案
|
|
||||||
* 关键修复: 微信小程序首次启动时, main.js 同步 $mount() 导致 data() 求值时
|
|
||||||
* Lang.boot() 尚未完成, $lang() 返回空串; 即使 boot 完成后 emit $forceUpdate,
|
|
||||||
* data 字段不会重新求值, computed 依赖追踪也可能被压缩器优化掉。
|
|
||||||
* 此方法在 popUp() 之前同步执行, 此时 Lang.boot() 必然已完成,
|
|
||||||
* 一次性把 i18n 字段填好, 模板只读 data 即可, 效果与 H5 一致。
|
|
||||||
*/
|
|
||||||
prepareI18n() {
|
|
||||||
const $lang = this.$lang.bind(this);
|
|
||||||
// 应用名称(按平台取值)
|
|
||||||
// #ifdef H5 || MP-HARMONY
|
|
||||||
const appName = $lang('components.privacyPopup.defaultAppNameH5');
|
|
||||||
// #endif
|
|
||||||
// #ifndef H5 || MP-HARMONY
|
|
||||||
const appName = $lang('components.privacyPopup.defaultAppNameMp');
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 隐私协议名称: 平台提供的优先, 否则回退 i18n 默认值
|
|
||||||
let privacyContractName;
|
|
||||||
if (this.platformContractName) {
|
|
||||||
privacyContractName = this.platformContractName;
|
|
||||||
} else {
|
|
||||||
// #ifdef H5 || MP-HARMONY
|
|
||||||
privacyContractName = $lang('components.privacyPopup.defaultContractNameH5');
|
|
||||||
// #endif
|
|
||||||
// #ifndef H5 || MP-HARMONY
|
|
||||||
privacyContractName = $lang('components.privacyPopup.defaultContractNameMp');
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// 服务协议名称(仅 H5/鸿蒙端显示)
|
|
||||||
// #ifdef H5 || MP-HARMONY
|
|
||||||
const privacyServiceName = $lang('components.privacyPopup.defaultServiceNameH5');
|
|
||||||
// #endif
|
|
||||||
// #ifndef H5 || MP-HARMONY
|
|
||||||
const privacyServiceName = '';
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
this.i18n = {
|
|
||||||
title: $lang('components.privacyPopup.title'),
|
|
||||||
descPrefix: $lang('components.privacyPopup.descPrefix', { appName }),
|
|
||||||
descSuffix: $lang('components.privacyPopup.descSuffix', { appName }),
|
|
||||||
privacyContractName,
|
|
||||||
privacyServiceName,
|
|
||||||
disagreeBtn: $lang('components.privacyPopup.disagreeBtn'),
|
|
||||||
agreeBtn: $lang('components.privacyPopup.agreeBtn'),
|
|
||||||
disagreeTip: $lang('components.privacyPopup.disagreeTip'),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接收全局隐私弹窗事件
|
* 接收全局隐私弹窗事件
|
||||||
* 由 App.vue → PrivacyManager.show() → uni.$emit('privacy:show') 触发
|
* 由 App.vue → PrivacyManager.show() → uni.$emit('privacy:show') 触发
|
||||||
@@ -380,11 +331,41 @@ export default {
|
|||||||
this.$emit('agree')
|
this.$emit('agree')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mixin i18n-lazy 要求的方法
|
||||||
|
* 返回一个对象, key 与 data().i18n 字段一一对应
|
||||||
|
* 调用时机在 popUp() 中(此时 boot 已完成, $lang() 返回真实翻译)
|
||||||
|
*/
|
||||||
|
i18nPrepareFn() {
|
||||||
|
// 关键: appName 必须先解析为字符串, 才能作为 descPrefix/descSuffix 的参数
|
||||||
|
// #ifdef H5 || MP-HARMONY
|
||||||
|
const appName = this.$lang('components.privacyPopup.defaultAppNameH5');
|
||||||
|
const privacyContractName = this.platformContractName || this.$lang('components.privacyPopup.defaultContractNameH5');
|
||||||
|
const privacyServiceName = this.$lang('components.privacyPopup.defaultServiceNameH5');
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
const appName = this.$lang('components.privacyPopup.defaultAppNameMp');
|
||||||
|
const privacyContractName = this.platformContractName || this.$lang('components.privacyPopup.defaultContractNameMp');
|
||||||
|
const privacyServiceName = '';
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: this.$lang('components.privacyPopup.title'),
|
||||||
|
descPrefix: this.$lang('components.privacyPopup.descPrefix', { appName }),
|
||||||
|
descSuffix: this.$lang('components.privacyPopup.descSuffix', { appName }),
|
||||||
|
privacyContractName,
|
||||||
|
privacyServiceName,
|
||||||
|
disagreeBtn: this.$lang('components.privacyPopup.disagreeBtn'),
|
||||||
|
agreeBtn: this.$lang('components.privacyPopup.agreeBtn'),
|
||||||
|
disagreeTip: this.$lang('components.privacyPopup.disagreeTip'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
popUp() {
|
popUp() {
|
||||||
// 弹窗前**一次性**计算所有 i18n 文案
|
// 弹窗前**一次性**计算所有 i18n 文案
|
||||||
// 此时 App.vue 已 await boot() + await PrivacyManager.show() 完成,
|
// 此时 App.vue 已 await boot() + await PrivacyManager.show() 完成,
|
||||||
// Lang.boot() 必然已完成, $lang() 返回真实翻译(效果与 H5 一致)
|
// Lang.boot() 必然已完成, $lang() 返回真实翻译(效果与 H5 一致)
|
||||||
this.prepareI18n()
|
this.prepareI18n();
|
||||||
this.showPop = true;
|
this.showPop = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user