diff --git a/common/js/diy.js b/common/js/diy.js index 2304d47..2812ab5 100644 --- a/common/js/diy.js +++ b/common/js/diy.js @@ -341,8 +341,8 @@ export default { // empty / unknown 不走模板,直接返回通用网络异常 if (info.type === 'empty' || info.type === 'unknown') return reason; - const tpl = this.$lang('diy.loadFailTpl'); - return tpl.replace('{reason}', reason); + const tpl = this.$lang('diy.loadFailTpl', { reason }); + return tpl; }, // 错误横幅「重试」入口 retryDiyInfo() { diff --git a/common/js/lang.js b/common/js/lang.js index 49e174f..6735224 100644 --- a/common/js/lang.js +++ b/common/js/lang.js @@ -1247,7 +1247,7 @@ export default { const fieldCacheKey = `${locale}__${langPath}__${_overrideVersion}__${field}`; const cachedFieldResult = _fieldResultCache[fieldCacheKey]; const hasDefault = arguments.length > 1 && typeof arguments[1] === 'string' && arguments[1] !== ''; - const __DEV__ = (typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production'); + if (cachedFieldResult !== undefined) { let value = cachedFieldResult; // 没找到翻译 @@ -1256,10 +1256,6 @@ export default { if (hasDefault) { return arguments[1]; } - // 生产环境无默认值 → 返回空串(避免用户看到 raw key) - if (!__DEV__) { - return ''; - } // 开发环境 → 返回 raw key(便于发现问题) return value; } @@ -1350,9 +1346,6 @@ export default { // 有默认值 → 使用默认值 if (hasDefault) { value = arguments[1]; - } else if (!__DEV__) { - // 生产环境无默认值 → 返回空串(避免用户看到 raw key) - value = ''; } // 开发环境无默认值 → 保留 raw key(便于发现问题) } else if (arguments.length > 1) { @@ -1367,7 +1360,7 @@ export default { if (value == undefined) value = ''; // 多语言调试 - if (__DEV__ && field == value) { + if (field == value) { console.warn(`[i18n] missing key "${field}" in ${langPath || ''} (${locale})`); } diff --git a/components-diy/diy-channel-list.vue b/components-diy/diy-channel-list.vue index 348f177..943bb0d 100644 --- a/components-diy/diy-channel-list.vue +++ b/components-diy/diy-channel-list.vue @@ -265,7 +265,7 @@ export default { // #ifndef MP-WEIXIN if (item?.channelType === 'wechat') { uni.showToast({ - title: '视频号仅支持在微信小程序环境中播放', + title: this.$lang('wechatChannel.wechatMpOnly'), icon: 'none', duration: 2000 }); diff --git a/components-diy/diy-form.vue b/components-diy/diy-form.vue index 61549b5..f8fd923 100644 --- a/components-diy/diy-form.vue +++ b/components-diy/diy-form.vue @@ -308,7 +308,7 @@ export default { success: function(res) { if (res.code === 0) { uni.showToast({ - title: '提交成功', + title: self.$lang('common.submitSuccess'), icon: 'success' }); // 重置表单 @@ -317,7 +317,7 @@ export default { } } else { uni.showToast({ - title: res.message || '提交失败', + title: res.message || self.$lang('submitFail'), icon: 'none' }); } diff --git a/components/ns-goods-recommend/ns-goods-recommend.vue b/components/ns-goods-recommend/ns-goods-recommend.vue index 88223eb..a1af059 100644 --- a/components/ns-goods-recommend/ns-goods-recommend.vue +++ b/components/ns-goods-recommend/ns-goods-recommend.vue @@ -10,14 +10,14 @@ return { list: [], config: { - title: '猜你喜欢', + title: '', sources: 'sort', supportPage: [], goodsIds: [], fontWeight: false, padding: 0, cartEvent: 'detail', - text: '购买', + text: '', textColor: '#FFFFFF', theme: 'default', aroundRadius: 25, @@ -67,10 +67,13 @@ } }, mounted() { - if (this.auto) { - this.getLikeList(); - } - }, + // 初始化 i18n 默认文案,实际展示以服务端配置 config.title / config.text 为准 + if (!this.config.title) this.config.title = this.$lang('common.goodsRecommendTitle'); + if (!this.config.text) this.config.text = this.$lang('common.buyNow'); + if (this.auto) { + this.getLikeList(); + } + }, methods: { init() { this.list = []; diff --git a/components/ns-payment/ns-payment.vue b/components/ns-payment/ns-payment.vue index 2766da8..c5f99a5 100644 --- a/components/ns-payment/ns-payment.vue +++ b/components/ns-payment/ns-payment.vue @@ -70,37 +70,11 @@ export default { return { isIphoneX: false, payIndex: 0, - // #ifdef H5 - payTypeList: [{ - name: this.$lang('components.nsPayment.alipay'), - icon: 'icon-zhifubaozhifu-', - type: 'alipay' - }, - { - name: this.$lang('components.nsPayment.wechatpay'), - icon: 'icon-weixin1', - type: 'wechatpay' - } - ], + payTypeList: [], timer: null, - // #endif // #ifdef MP-WEIXIN - payTypeList: [{ - name: this.$lang('components.nsPayment.wechatpay'), - provider: 'wxpay', - icon: 'icon-weixin1', - type: 'wechatpay' - }], isMatched: 0, // 支付是否需要校验 // #endif - // #ifdef MP-ALIPAY - payTypeList: [{ - name: this.$lang('components.nsPayment.alipay'), - icon: 'icon-zhifubaozhifu-', - type: 'alipay', - provider: 'alipay' - }], - // #endif payInfo: {}, balanceConfig: 0, // 预售页面判断 @@ -108,6 +82,23 @@ export default { }; }, created(e) { + // #ifdef H5 + this.payTypeList = [ + { name: this.$lang('components.nsPayment.alipay'), icon: 'icon-zhifubaozhifu-', type: 'alipay' }, + { name: this.$lang('components.nsPayment.wechatpay'), icon: 'icon-weixin1', type: 'wechatpay' }, + ]; + // #endif + // #ifdef MP-WEIXIN + this.payTypeList = [ + { name: this.$lang('components.nsPayment.wechatpay'), provider: 'wxpay', icon: 'icon-weixin1', type: 'wechatpay' }, + ]; + // #endif + // #ifdef MP-ALIPAY + this.payTypeList = [ + { name: this.$lang('components.nsPayment.alipay'), icon: 'icon-zhifubaozhifu-', type: 'alipay', provider: 'alipay' }, + ]; + // #endif + let url; // #ifdef H5 url = window.location.href diff --git a/lang-bundle.json b/lang-bundle.json index 5d0f71e..5348c22 100644 --- a/lang-bundle.json +++ b/lang-bundle.json @@ -2,7 +2,7 @@ "code": 0, "message": "操作成功", "data": { - "version": "1784767380", + "version": "1784770218", "lang": "zh-cn", "app_type": "h5", "source": "merged", @@ -464,6 +464,7 @@ }, "diy": { "networkErrorTitle": "提示", + "networkError": "网络异常,请稍后重试", "retry": "重试", "noDiyData": "未配置自定义页面数据", "locationNotAvailable": "未获取到定位", @@ -506,7 +507,8 @@ "lowVersion": "当前微信基础库版本过低,需要 2.19.2 或以上版本", "notSupported": "当前环境不支持微信视频号", "missingFields": "缺少必要字段: {field}", - "playFailed": "微信视频播放失败!" + "playFailed": "微信视频播放失败!", + "wechatMpOnly": "视频号仅支持在微信小程序环境中播放" }, "uniappUtils": { "errorTip": "错误提示", @@ -607,6 +609,12 @@ "addToCartFailMax": "已达到最大购买数量", "addToCartFailMin": "已达到最小购买数量", "addToCartFailStock": "库存不足", + "submitSuccess": "提交成功", + "submitFail": "提交失败", + "versionInfo": "版本信息", + "commitId": "提交 ID", + "buildTime": "构建时间", + "loading": "加载中...", "consult": "立即咨询", "consultSuccess": "咨询成功", "consultFail": "咨询失败", @@ -662,7 +670,8 @@ "title": "支付方式", "paymentAmount": "支付金额", "confirmPayment": "确认支付", - "seeOrder": "查看订单" + "seeOrder": "查看订单", + "payInfoNotFound": "未获取到支付信息!" }, "cashier": { "title": "收银台", @@ -2403,6 +2412,7 @@ "fullGiftItem": "满{limit}{unit}{gift}", "freeShippingItem": "满{limit}{unit}包邮", "productMessage": "商品留言", + "formEmptyTip": "暂无需填写的留言字段", "aiCustomerService": "智能客服", "dialOnMobile": "可在手机端拨打{phone}", "cancel": "取消", @@ -3469,5 +3479,5 @@ }, "sign": "" }, - "timestamp": 1784767380 + "timestamp": 1784770218 } \ No newline at end of file diff --git a/main.js b/main.js index 58a9909..af70ac6 100644 --- a/main.js +++ b/main.js @@ -80,10 +80,18 @@ Vue.mixin(MpShareDefault); // lang.js 在翻译覆写更新时 commit('setComponentRefresh'), // golbalConfig.js 已把 componentRefresh 注册为全局 computed, // 在此 watch 它并 $forceUpdate,触发所有页面内 {{$lang('xxx')}} 重新求值 +// 同时调用 lang.refresh() 让当前页面的 navigationBarTitleText 跟随语种变化 +// (pages.json 里的硬编码中文标题需要被 $lang('title') 覆盖) Vue.mixin({ watch: { componentRefresh() { this.$forceUpdate(); + try { + if (this.$langConfig && typeof this.$langConfig.refresh === 'function') { + // emit=false: lang.js 已 emit 过,这里避免重复 store.commit + this.$langConfig.refresh(false); + } + } catch (e) { /* 容错:refresh 失败不影响主流程 */ } } } }); diff --git a/pages/index/public/js/index.js b/pages/index/public/js/index.js index 617a4cc..b95b88b 100644 --- a/pages/index/public/js/index.js +++ b/pages/index/public/js/index.js @@ -160,10 +160,10 @@ export default { } catch (e) {} if (v && v.commitId) { uni.showModal({ - title: '版本信息', - content: `提交 ID: ${v.commitId}\n构建时间: ${v.buildTime}\n${Config.baseUrl}`, - showCancel: false - }); + title: this.$lang('versionInfo'), + content: `${this.$lang('commitId')}: ${v.commitId}\n${this.$lang('buildTime')}: ${v.buildTime}\n${Config.baseUrl}`, + showCancel: false + }); } else { console.error('未获取到构建版本信息'); } diff --git a/pages_goods/public/js/list.js b/pages_goods/public/js/list.js index de492f4..822f3d8 100644 --- a/pages_goods/public/js/list.js +++ b/pages_goods/public/js/list.js @@ -37,7 +37,7 @@ export default { fontWeight: false, padding: 0, cartEvent: "detail", - text: "购买", + text: "", textColor: "#FFFFFF", theme: "default", aroundRadius: 25, @@ -67,7 +67,7 @@ export default { }, onLoad(options) { - this.config.text = this.$lang('goods.list.buyText'); + this.config.text = this.$lang('goods.list.buyText') || this.$lang('common.buyNow'); this.$api.sendRequest({ url: '/api/lucky/status', diff --git a/pages_promotion/merch/public/js/list.js b/pages_promotion/merch/public/js/list.js index 63cc42e..3762013 100644 --- a/pages_promotion/merch/public/js/list.js +++ b/pages_promotion/merch/public/js/list.js @@ -39,7 +39,7 @@ export default { fontWeight: false, padding: 0, cartEvent: "detail", - text: this.$lang('merch.list.buyText'), + text: '', textColor: "#FFFFFF", theme: "default", aroundRadius: 25, @@ -66,6 +66,7 @@ export default { }, onLoad(options) { + this.config.text = this.$lang('merch.list.buyText'); this.categoryId = options.category_id || 0; this.keyword = options.keyword || ''; this.coupon = options.coupon || 0; diff --git a/pages_tool/ai-chat/ai-chat-message.vue b/pages_tool/ai-chat/ai-chat-message.vue index 1860d2b..e99d343 100644 --- a/pages_tool/ai-chat/ai-chat-message.vue +++ b/pages_tool/ai-chat/ai-chat-message.vue @@ -397,7 +397,7 @@ export default { scrollTop: 0, scrollIntoViewId: null, hasMoreHistory: false, - loadingText: this.$lang('ai-chat.index.loadMoreHistory'), + loadingText: '', voiceInputing: false, audioContext: null, currentAudio: null, @@ -417,7 +417,7 @@ export default { localUserAvatar: '', showAvatarSelector: false, // 头像选择弹窗控制 localUserNickname: '', // 当前用户昵称 - defaultNickname: this.$lang('ai-chat.index.defaultNickname'), // 默认昵称 + defaultNickname: '', // 默认昵称 showNicknameEditor: false, // 昵称编辑弹窗控制 // AI头像错误状态 avatarError: false, @@ -493,6 +493,9 @@ export default { created() { this.messages = [...this.initialMessages] this.messageId = this.messages.length + // 初始化 i18n 字段(在 created 中同步赋值,避免 remote-only 首屏 data() 缓存空串) + this.loadingText = this.$lang('ai-chat.index.loadMoreHistory') + this.defaultNickname = this.$lang('ai-chat.index.defaultNickname') this.initAudioContext() // 初始化用户头像(缓存优先) this.initUserAvatarData() diff --git a/pages_tool/help/list.vue b/pages_tool/help/list.vue index 1974570..eddf64b 100644 --- a/pages_tool/help/list.vue +++ b/pages_tool/help/list.vue @@ -79,7 +79,7 @@ export default { setPublicShare() { let shareUrl = this.$config.h5Domain + '/pages_tool/help/list'; this.$util.setPublicShare({ - title: '帮助中心', + title: this.$lang('help.list.title'), desc: '', link: shareUrl, imgUrl: this.siteInfo ? this.$util.img(this.siteInfo.logo_square) : '' diff --git a/pages_tool/notice/list.vue b/pages_tool/notice/list.vue index b1debc6..252a19d 100644 --- a/pages_tool/notice/list.vue +++ b/pages_tool/notice/list.vue @@ -77,7 +77,7 @@ export default { setPublicShare() { let shareUrl = this.$config.h5Domain + '/pages_tool/notice/list'; this.$util.setPublicShare({ - title: '公告', + title: this.$lang('notice.list.title'), desc: '', link: shareUrl, imgUrl: this.siteInfo ? this.$util.img(this.siteInfo.logo_square) : '' @@ -85,7 +85,7 @@ export default { } }, onShareAppMessage(res) { - var title = '公告'; + var title = this.$lang('notice.list.title'); var path = '/pages_tool/notice/list'; return { title: title, diff --git a/pages_tool/pay/index.vue b/pages_tool/pay/index.vue index 209d802..7394cab 100644 --- a/pages_tool/pay/index.vue +++ b/pages_tool/pay/index.vue @@ -51,7 +51,7 @@ export default { if (this.$refs.loadingCover) this.$refs.loadingCover.hide(); } else { this.$util.showToast({ - title: '未获取到支付信息!' + title: this.$lang('pay.index.payInfoNotFound') }); setTimeout(() => { this.$util.redirectTo(this.$util.INDEX_PAGE_URL); diff --git a/scripts/check-title-keys.js b/scripts/check-title-keys.js new file mode 100644 index 0000000..e061d07 --- /dev/null +++ b/scripts/check-title-keys.js @@ -0,0 +1,58 @@ +// 检查 20 个硬编码导航标题的页面,其语言包是否都有 title key +const paths = [ + ['pages_goods/category','商品分类'], + ['pages_goods/cart','购物车'], + ['pages_goods/detail','商品详情'], + ['pages_goods/list','商品列表'], + ['pages_promotion/merch/merchcategory','入驻商户'], + ['pages_promotion/merch/detail','商户首页'], + ['pages_promotion/fenxiao/ranking_list','排行榜'], + ['pages_promotion/fenxiao/child_fenxiao','分销商'], + ['pages_tool/ai-chat/index','AI智能客服'], + ['pages_tool/contact/contact','电子名片'], + ['pages_tool/member/info','个人资料'], + ['pages_tool/order/activist','退款'], + ['pages_tool/login/login','登录'], + ['pages_tool/weapp/order_shipping','小程序发货'], + ['pages_tool/seal/structure','结构选型'], + ['pages_tool/seal/medium/search','介质材料搜索'], + ['pages_tool/seal/medium/detail','搜索结果'], + ['pages_tool/cases/index','案例展示'], + ['pages_tool/files/list','查看文件'], + ['pages_tool/file-preview/file-preview','文件预览'], +]; + +function toLang(p) { + const parts = p.split('/'); + if (parts[0] === 'pages_tool') return parts.slice(1).join('/'); + if (parts[0] === 'pages_goods') return 'goods/' + parts.slice(1).join('/'); + if (parts[0] === 'pages_member') return 'member/' + parts.slice(1).join('/'); + if (parts[0] === 'pages_order') return 'order/' + parts.slice(1).join('/'); + if (parts[0] === 'pages_promotion') { + if (['point','fenxiao','merch'].includes(parts[1])) return parts.slice(1).join('/'); + return parts.slice(1).join('/'); + } + return p; +} + +const fs = require('fs'); +const path = require('path'); + +for (const [p, hardcoded] of paths) { + const lp = toLang(p); + for (const loc of ['zh-cn','en-us']) { + const fp = path.join('lang', loc, lp + '.js'); + if (!fs.existsSync(fp)) { + console.log(`MISSING ${loc}/${lp}`); + continue; + } + try { + const m = require(path.resolve(fp)).lang; + const title = m && m.title; + const status = title === undefined ? 'NO title' : (title === hardcoded ? 'ZH-only?' : 'OK'); + console.log(`${loc}/${lp}.title = ${JSON.stringify(title)} [${status}]`); + } catch (e) { + console.log(`ERR ${loc}/${lp}: ${e.message}`); + } + } +} \ No newline at end of file diff --git a/scripts/hardcoded_cn_real.json b/scripts/hardcoded_cn_real.json new file mode 100644 index 0000000..a8f71ca --- /dev/null +++ b/scripts/hardcoded_cn_real.json @@ -0,0 +1,264 @@ +{ + "uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue": [ + { + "kind": "template", + "tag": "text", + "text": "取消", + "line": 6 + }, + { + "kind": "template", + "tag": "text", + "text": "确定", + "line": 7 + }, + { + "kind": "template", + "tag": "text", + "text": "{{ (nowDate.year || '') + '年' + (nowDate.month || '') + '月' }}", + "line": 15 + }, + { + "kind": "template", + "tag": "text", + "text": "日", + "line": 27 + }, + { + "kind": "template", + "tag": "text", + "text": "一", + "line": 28 + }, + { + "kind": "template", + "tag": "text", + "text": "二", + "line": 29 + }, + { + "kind": "template", + "tag": "text", + "text": "三", + "line": 30 + }, + { + "kind": "template", + "tag": "text", + "text": "四", + "line": 31 + }, + { + "kind": "template", + "tag": "text", + "text": "五", + "line": 32 + }, + { + "kind": "template", + "tag": "text", + "text": "六", + "line": 33 + } + ], + "pages_promotion/fenxiao/withdraw_list.vue": [ + { + "kind": "js-text", + "match": "已转账", + "line": 45 + }, + { + "kind": "js-text", + "match": "待审核", + "line": 49 + }, + { + "kind": "js-text", + "match": "已审核", + "line": 53 + }, + { + "kind": "js-text", + "match": "已拒绝", + "line": 57 + } + ], + "uni_modules/mp-html/components/mp-html/node/node.vue": [ + { + "kind": "js-text", + "match": "uni.showActionSheet({\n itemList: ['保存图片'],\n success: () => {\n const save = path => {\n uni.saveImageToPhotosAlbum({\n ", + "line": 289 + }, + { + "kind": "js-text", + "match": "uni.showToast({\n title: '链接已复制'\n })", + "line": 368 + }, + { + "kind": "js-text", + "match": "保存成功", + "line": 297 + }, + { + "kind": "js-text", + "match": "链接已复制", + "line": 369 + } + ], + "components-diy/diy-form.vue": [ + { + "kind": "js-text", + "match": "uni.showToast({\n\t\t\t\t\t\t\ttitle: '提交成功',\n\t\t\t\t\t\t\ticon: 'success'\n\t\t\t\t\t\t})", + "line": 305 + }, + { + "kind": "js-text", + "match": "uni.showToast({\n\t\t\t\t\t\t\ttitle: res.message || '提交失败',\n\t\t\t\t\t\t\ticon: 'none'\n\t\t\t\t\t\t})", + "line": 314 + }, + { + "kind": "js-text", + "match": "提交成功", + "line": 306 + } + ], + "uni_modules/uni-count-down/components/uni-count-down/uni-count-down.vue": [ + { + "kind": "template", + "tag": "view", + "text": "{{ showColon ? ':' : '时' }}", + "line": 19 + }, + { + "kind": "template", + "tag": "view", + "text": "{{ showColon ? ':' : '分' }}", + "line": 27 + }, + { + "kind": "template", + "tag": "view", + "text": "秒", + "line": 35 + } + ], + "components/ns-goods-recommend/ns-goods-recommend.vue": [ + { + "kind": "js-text", + "match": "猜你喜欢", + "line": 13 + }, + { + "kind": "js-text", + "match": "购买", + "line": 20 + } + ], + "components-diy/diy-channel-list.vue": [ + { + "kind": "js-text", + "match": "uni.showToast({\n\t\t\t\t\t\t\t\ttitle: '视频号仅支持在微信小程序环境中播放',\n\t\t\t\t\t\t\t\ticon: 'none',\n\t\t\t\t\t\t\t\tduration: 2000\n\t\t\t\t\t\t\t})", + "line": 207 + }, + { + "kind": "js-text", + "match": "视频号仅支持在微信小程序环境中播放", + "line": 208 + } + ], + "pages/index/public/js/index.js": [ + { + "kind": "js-text", + "match": "uni.showModal({\n\t\t\t\t\ttitle: '版本信息',\n\t\t\t\t\tcontent: `提交 ID: ${v.commitId}\\n构建时间: ${v.buildTime}\\n${Config.baseUrl}`,\n\t\t\t\t\tshowCancel: false\n\t\t\t\t})", + "line": 160 + }, + { + "kind": "js-text", + "match": "版本信息", + "line": 161 + } + ], + "uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue": [ + { + "kind": "template", + "tag": "text", + "text": "{{ (nowDate.year||'') + ' 年 ' + ( nowDate.month||'') +' 月'}}", + "line": 13 + }, + { + "kind": "template", + "tag": "view", + "text": "确认", + "line": 87 + } + ], + "uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue": [ + { + "kind": "js-text", + "match": "商品库存不足", + "line": 131 + }, + { + "kind": "js-text", + "match": "商品最少购买", + "line": 135 + } + ], + "common/js/mp-share-default.js": [ + { + "kind": "js-text", + "match": "小程序分享", + "line": 85 + } + ], + "common/js/splash-state.js": [ + { + "kind": "js-text", + "match": "uni.showLoading({ title: opts.title || '加载中...', mask: false })", + "line": 111 + } + ], + "pages/index/index.vue": [ + { + "kind": "template", + "tag": "text", + "text": "{{ diyLoadErrorMsg || '网络异常,请稍后重试' }}", + "line": 8 + } + ], + "pages_goods/public/js/list.js": [ + { + "kind": "js-text", + "match": "购买", + "line": 40 + } + ], + "pages_tool/help/list.vue": [ + { + "kind": "js-text", + "match": "帮助中心", + "line": 82 + } + ], + "pages_tool/notice/list.vue": [ + { + "kind": "js-text", + "match": "公告", + "line": 80 + } + ], + "pages_tool/pay/index.vue": [ + { + "kind": "js-text", + "match": "未获取到支付信息!", + "line": 54 + } + ], + "uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue": [ + { + "kind": "js-text", + "match": "日期和时间", + "line": 112 + } + ] +} \ No newline at end of file diff --git a/scripts/hardcoded_cn_scan.json b/scripts/hardcoded_cn_scan.json new file mode 100644 index 0000000..8d5af12 --- /dev/null +++ b/scripts/hardcoded_cn_scan.json @@ -0,0 +1,7804 @@ +{ + "pages_tool\\ai-chat\\ai-chat-message.vue": [ + [ + 3, + "" + ], + [ + 14, + "" + ], + [ + 24, + "" + ], + [ + 29, + "" + ], + [ + 37, + "" + ], + [ + 62, + "" + ], + [ + 65, + "" + ], + [ + 75, + "" + ], + [ + 86, + "" + ], + [ + 91, + "" + ], + [ + 102, + "" + ], + [ + 118, + "" + ], + [ + 145, + "" + ], + [ + 166, + "" + ], + [ + 181, + "" + ], + [ + 200, + "" + ], + [ + 220, + "" + ], + [ + 245, + "" + ], + [ + 250, + "" + ], + [ + 255, + "" + ], + [ + 266, + "" + ], + [ + 275, + "" + ], + [ + 281, + "" + ], + [ + 307, + "" + ], + [ + 310, + "" + ], + [ + 316, + "" + ], + [ + 323, + "" + ], + [ + 615, + "console.log('[AI Chat] → 分支1: ID变化,加载历史');" + ], + [ + 620, + "console.log('[AI Chat] → 分支2: ID不变但只有初始消息,从服务端恢复');" + ], + [ + 624, + "console.log('[AI Chat] → 分支3: 全新会话');" + ], + [ + 627, + "console.log('[AI Chat] → 分支4: 跳过加载,保留当前消息, messages.length:', this.messages.length);" + ], + [ + 635, + "console.log('[AI Chat] 已有会话,跳过欢迎语')" + ], + [ + 646, + "console.log('[AI Chat] API getServiceConfig 返回:', JSON.stringify(config))" + ], + [ + 657, + "console.log('[AI Chat] 使用远程欢迎语:', welcomeText.substring(0, 50))" + ], + [ + 661, + "console.warn('[AI Chat] 获取服务配置失败:', e)" + ], + [ + 669, + "console.log('[AI Chat] 使用降级欢迎语')" + ], + [ + 671, + "console.log('[AI Chat] 无可用欢迎语')" + ], + [ + 764, + "console.log('[AI Chat] loadChatHistoryIfExist 被调用, convId:', convId);" + ], + [ + 768, + "console.log('[AI Chat] 请求历史 API...');" + ], + [ + 772, + "console.log('[AI Chat] 历史 API 返回:', JSON.stringify(response).substring(0, 200));" + ], + [ + 775, + "console.warn('[AI Chat] 历史记录为空或格式无效, success:', response.success, 'messages:', Array.isArray(response.messages));" + ], + [ + 778, + "console.log('[AI Chat] 原始消息数:', response.messages.length, 'total:', response.total);" + ], + [ + 798, + "console.log('[AI Chat] 过滤后消息数:', this.messages.length, '内容摘要:', this.messages.map(m => ({role: m.role, content: m.content.substring(0, 30)})));" + ], + [ + 804, + "console.error('[AI Chat] 加载历史记录失败:', error.message, error.stack);" + ], + [ + 881, + "console.error('AI服务调用失败:', error)" + ], + [ + 1023, + "console.error('流式请求失败:', error)" + ], + [ + 1079, + "console.error('流式请求失败')" + ], + [ + 1474, + "console.error('加载历史失败:', error);" + ], + [ + 1702, + "console.log('[AI Chat] 开始复制, 内容长度:', copyText.length, '内容前50字:', copyText.substring(0, 50))" + ], + [ + 1706, + "console.log('[AI Chat] 复制成功')" + ], + [ + 1710, + "console.error('[AI Chat] 复制失败, 详细错误:', JSON.stringify(err))" + ], + [ + 1711, + "console.error('[AI Chat] 复制失败 errMsg:', err.errMsg || err.message || '无错误消息')" + ], + [ + 1862, + "console.error('AI服务调用失败:', error)" + ], + [ + 1968, + "'😀': '[开心]'," + ], + [ + 1969, + "'😂': '[大笑]'," + ], + [ + 1970, + "'😊': '[微笑]'," + ], + [ + 1971, + "'❤️': '[爱心]'," + ], + [ + 1972, + "'👍': '[点赞]'," + ], + [ + 1973, + "'😭': '[哭泣]'," + ], + [ + 1974, + "'🙏': '[感谢/拜托]'," + ], + [ + 1975, + "'😍': '[喜欢]'," + ], + [ + 1976, + "'🎉': '[庆祝]'," + ], + [ + 1977, + "'🤔': '[思考]'," + ], + [ + 1978, + "'😅': '[尴尬]'," + ], + [ + 1979, + "'😁': '[开心]'," + ], + [ + 1980, + "'🥰': '[喜爱]'," + ], + [ + 1981, + "'😘': '[飞吻]'," + ], + [ + 1982, + "'😏': '[坏笑]'," + ], + [ + 1983, + "'🥳': '[庆祝]'," + ], + [ + 1984, + "'😔': '[难过]'," + ], + [ + 1985, + "'😢': '[哭泣]'," + ], + [ + 1986, + "'😤': '[生气]'," + ], + [ + 1987, + "'😴': '[困]'," + ], + [ + 1988, + "'🤗': '[拥抱]'," + ], + [ + 1989, + "'🙂': '[微笑]'," + ], + [ + 1990, + "'😃': '[开心]'," + ], + [ + 1991, + "'😄': '[大笑]'," + ], + [ + 1992, + "'😆': '[开心]'," + ], + [ + 1993, + "'😇': '[天使]'," + ], + [ + 1994, + "'🤩': '[惊喜]'," + ], + [ + 1995, + "'😋': '[好吃]'," + ], + [ + 1996, + "'😛': '[调皮]'," + ], + [ + 1997, + "'😜': '[调皮]'," + ], + [ + 1998, + "'🤪': '[搞怪]'," + ], + [ + 1999, + "'😝': '[开心]'," + ], + [ + 2000, + "'🤑': '[发财]'," + ], + [ + 2001, + "'🤭': '[害羞]'," + ], + [ + 2002, + "'🤫': '[嘘]'," + ], + [ + 2003, + "'🤐': '[闭嘴]'," + ], + [ + 2004, + "'🤨': '[疑惑]'," + ], + [ + 2005, + "'😐': '[无语]'," + ], + [ + 2006, + "'😑': '[无语]'," + ], + [ + 2007, + "'😶': '[沉默]'," + ], + [ + 2008, + "'😒': '[不屑]'," + ], + [ + 2009, + "'🙄': '[白眼]'," + ], + [ + 2010, + "'😬': '[尴尬]'," + ], + [ + 2011, + "'🤥': '[说谎]'," + ], + [ + 2012, + "'😌': '[安心]'," + ], + [ + 2013, + "'😪': '[困]'," + ], + [ + 2014, + "'🤤': '[流口水]'," + ], + [ + 2015, + "'😷': '[生病]'," + ], + [ + 2016, + "'🤒': '[发烧]'," + ], + [ + 2017, + "'🤕': '[受伤]'," + ], + [ + 2018, + "'🤢': '[恶心]'," + ], + [ + 2019, + "'🤮': '[呕吐]'," + ], + [ + 2020, + "'🤧': '[感冒]'," + ], + [ + 2021, + "'🥵': '[热]'," + ], + [ + 2022, + "'🥶': '[冷]'," + ], + [ + 2023, + "'🥴': '[头晕]'," + ], + [ + 2024, + "'😵': '[晕]'," + ], + [ + 2025, + "'🤯': '[爆炸]'," + ], + [ + 2026, + "'🤠': '[酷]'," + ], + [ + 2027, + "'😎': '[酷]'," + ], + [ + 2028, + "'🤓': '[书呆子]'," + ], + [ + 2029, + "'🧐': '[疑惑]'," + ], + [ + 2030, + "'😕': '[困惑]'," + ], + [ + 2031, + "'😟': '[担心]'," + ], + [ + 2032, + "'🙁': '[难过]'," + ], + [ + 2033, + "'😮': '[惊讶]'," + ], + [ + 2034, + "'😯': '[惊讶]'," + ], + [ + 2035, + "'😲': '[震惊]'," + ], + [ + 2036, + "'😳': '[脸红]'," + ], + [ + 2037, + "'🥺': '[恳求]'," + ], + [ + 2038, + "'😦': '[惊讶]'," + ], + [ + 2039, + "'😧': '[惊讶]'," + ], + [ + 2040, + "'😨': '[害怕]'," + ], + [ + 2041, + "'😰': '[紧张]'," + ], + [ + 2042, + "'😥': '[失望]'," + ], + [ + 2043, + "'😱': '[恐惧]'," + ], + [ + 2044, + "'😖': '[困扰]'," + ], + [ + 2045, + "'😣': '[痛苦]'," + ], + [ + 2046, + "'😞': '[难过]'," + ], + [ + 2047, + "'😓': '[汗]'," + ], + [ + 2048, + "'😩': '[累]'," + ], + [ + 2049, + "'😫': '[累]'," + ], + [ + 2050, + "'🥱': '[困]'," + ], + [ + 2051, + "'😡': '[愤怒]'," + ], + [ + 2052, + "'😠': '[生气]'," + ], + [ + 2053, + "'🤬': '[咒骂]'," + ], + [ + 2054, + "'😈': '[恶魔]'," + ], + [ + 2055, + "'👿': '[生气]'," + ], + [ + 2056, + "'💀': '[骷髅]'," + ], + [ + 2057, + "'💩': '[便便]'," + ], + [ + 2058, + "'👻': '[鬼]'," + ], + [ + 2059, + "'👽': '[外星人]'," + ], + [ + 2060, + "'🤖': '[机器人]'," + ], + [ + 2061, + "'🎃': '[万圣节]'," + ], + [ + 2062, + "'😺': '[开心猫]'," + ], + [ + 2063, + "'😸': '[微笑猫]'," + ], + [ + 2064, + "'😹': '[大笑猫]'," + ], + [ + 2065, + "'😻': '[花痴猫]'," + ], + [ + 2066, + "'😼': '[得意猫]'," + ], + [ + 2067, + "'😽': '[亲亲猫]'," + ], + [ + 2068, + "'🙀': '[惊讶猫]'," + ], + [ + 2069, + "'😿': '[哭泣猫]'," + ], + [ + 2070, + "'😾': '[生气猫]'," + ], + [ + 2071, + "'👋': '[挥手]'," + ], + [ + 2072, + "'🤚': '[举手]'," + ], + [ + 2073, + "'🖐': '[手掌]'," + ], + [ + 2074, + "'✋': '[手掌]'," + ], + [ + 2077, + "'🤏': '[捏]'," + ], + [ + 2078, + "'✌️': '[耶]'," + ], + [ + 2079, + "'🤞': '[好运]'," + ], + [ + 2080, + "'🤟': '[爱你]'," + ], + [ + 2081, + "'🤘': '[摇滚]'," + ], + [ + 2082, + "'🤙': '[打电话]'," + ], + [ + 2083, + "'👈': '[左边]'," + ], + [ + 2084, + "'👉': '[右边]'," + ], + [ + 2085, + "'👆': '[上边]'," + ], + [ + 2086, + "'🖕': '[竖中指]'," + ], + [ + 2087, + "'👇': '[下边]'," + ], + [ + 2088, + "'☝': '[向上]'," + ], + [ + 2089, + "'👎': '[鄙视]'," + ], + [ + 2090, + "'✊': '[拳头]'," + ], + [ + 2091, + "'👊': '[拳头]'," + ], + [ + 2092, + "'🤛': '[左拳]'," + ], + [ + 2093, + "'🤜': '[右拳]'," + ], + [ + 2094, + "'👏': '[鼓掌]'," + ], + [ + 2095, + "'🙌': '[举手庆祝]'," + ], + [ + 2096, + "'👐': '[张开手]'," + ], + [ + 2097, + "'🤲': '[摊手]'," + ], + [ + 2098, + "'🤝': '[握手]'," + ], + [ + 2099, + "'✍️': '[写字]'," + ], + [ + 2100, + "'💅': '[美甲]'," + ], + [ + 2101, + "'🤳': '[自拍]'," + ], + [ + 2102, + "'💪': '[肌肉]'," + ], + [ + 2103, + "'💗': '[爱心]'," + ], + [ + 2104, + "'💓': '[心跳]'," + ], + [ + 2105, + "'💕': '[两颗心]'," + ], + [ + 2106, + "'💖': '[闪亮心]'," + ], + [ + 2107, + "'💝': '[礼物心]'," + ], + [ + 2108, + "'💞': '[旋转心]'," + ], + [ + 2109, + "'💘': '[丘比特]'," + ], + [ + 2110, + "'💌': '[情书]'," + ], + [ + 2111, + "'💟': '[心形]'," + ], + [ + 2112, + "'💛': '[黄心]'," + ], + [ + 2113, + "'💙': '[蓝心]'," + ], + [ + 2114, + "'💜': '[紫心]'," + ], + [ + 2115, + "'🖤': '[黑心]'," + ], + [ + 2116, + "'💔': '[心碎]'," + ], + [ + 2117, + "'💢': '[愤怒]'," + ], + [ + 2118, + "'💣': '[炸弹]'," + ], + [ + 2119, + "'💥': '[爆炸]'," + ], + [ + 2120, + "'💫': '[头晕]'," + ], + [ + 2121, + "'💦': '[汗]'," + ], + [ + 2122, + "'💨': '[风]'," + ], + [ + 2123, + "'👁️': '[眼睛]'," + ], + [ + 2124, + "'👀': '[看]'," + ], + [ + 2125, + "'👅': '[舌头]'," + ], + [ + 2126, + "'👄': '[嘴巴]'," + ], + [ + 2127, + "'👂': '[耳朵]'," + ], + [ + 2128, + "'👃': '[鼻子]'" + ] + ], + "tests\\tabbar-highlight.test.js": [ + [ + 104, + "{ id: 1, text: '首页', url: HOME }," + ], + [ + 105, + "{ id: 2, text: '分类', url: CATEGORY }," + ], + [ + 106, + "{ id: 3, text: '购物车', url: CART }," + ], + [ + 107, + "{ id: 4, text: '商户', url: MERCHANT }," + ], + [ + 108, + "{ id: 5, text: '我的', url: MEMBER }," + ], + [ + 166, + "`[${label}] 期望 ${expected || '不点亮'}, 实际 ${actual || '不点亮'}`);" + ], + [ + 174, + "test('场景 1.1: 用户从首页点 diy 链接 → 首页保持高亮', () => {" + ], + [ + 181, + "test('场景 1.2: 用户从分类点 diy 链接 → 分类高亮', () => {" + ], + [ + 188, + "test('场景 1.3: 用户从商户 tab 进 diy 微页面 → 商户高亮', () => {" + ], + [ + 195, + "test('场景 1.4: 深层导航, 跳过非 tab 中间页', () => {" + ], + [ + 204, + "test('场景 1.5: 经过 login 弹窗后, 仍记为来自原 tab', () => {" + ], + [ + 218, + "test('场景 2.1: /pages_goods/list → /pages_goods/detail 属于分类频道', () => {" + ], + [ + 225, + "test('场景 2.2: /pages_tool/member/coupon → /pages_tool/member/info 属于\"我的\"', () => {" + ], + [ + 232, + "test('场景 2.3: 在\"我的\"频道内多页浏览, 高亮始终是\"我的\"', () => {" + ], + [ + 244, + "test('场景 3.1: tab→tab 跳转, 目标 tab 高亮 (不受来源干扰)', () => {" + ], + [ + 251, + "test('场景 3.2: 在 diy 页面点\"我的\" tab → 我的高亮 (同包)', () => {" + ], + [ + 261, + "test('场景 3.3: 在 diy 页面点\"首页\" tab → 首页高亮 (跨包)', () => {" + ], + [ + 268, + "test('场景 3.4: 链式 tab 切换 home→分类→我的→购物车→首页', () => {" + ], + [ + 275, + "}), tabs[i], `链式第 ${i + 1} 步: ${tabs[i]}`);" + ], + [ + 283, + "test('场景 4.1: 首页 → 商品搜索 → 分类高亮 (通配符 /pages_tool/goods" + ], + [ + 286, + "test('场景 5.1: /pages_tool/files/list 显式绑 null → 不点亮 (业务规则: 文件页不归属任何 tab)', () => {" + ], + [ + 290, + "}), null, 'home → files/list (null 优先)');" + ], + [ + 293, + "test('场景 5.2: /pages_tool/index/diy 不在 PAGE_TAB_MAP, 走 history-walk', () => {" + ], + [ + 299, + "}), HOME, 'diy 走 history-walk, home 高亮');" + ], + [ + 302, + "test('场景 5.3: diy 没在 PAGE_TAB_MAP 显式声明 (避免硬绑)', () => {" + ], + [ + 308, + "'diy 不应在 PAGE_TAB_MAP (修复后靠 history-walk 决定)');" + ], + [ + 315, + "test('场景 6.1: 用户扫码进 diy → 不高亮任何 tab (iOS HIG)', () => {" + ], + [ + 320, + "}), null, '深链 diy');" + ], + [ + 323, + "test('场景 6.2: 分享 diy 链接给朋友, 朋友点开 → 不高亮 (无历史)', () => {" + ], + [ + 327, + "}), null, '深链 diy?id=123');" + ], + [ + 330, + "test('场景 6.3: 分享商品详情链接, 朋友点开 → 分类高亮 (通配符)', () => {" + ], + [ + 336, + "}), CATEGORY, '深链 goods/detail');" + ], + [ + 339, + "test('场景 6.4: 分享会员中心链接, 朋友点开 → 我的高亮 (精确绑)', () => {" + ], + [ + 343, + "}), MEMBER, '深链 member (精确)');" + ], + [ + 346, + "test('场景 6.5: 分享 fenxiao 链接, 朋友点开 → 不高亮 (不在任何 map)', () => {" + ], + [ + 352, + "}), null, '深链 fenxiao');" + ], + [ + 360, + "test('场景 7.1: 在 home 点登录 → 全屏不点亮 (NO_TAB_PAGES)', () => {" + ], + [ + 367, + "test('场景 7.2: 支付流程中 → 不点亮 (NO_TAB_PAGES)', () => {" + ], + [ + 374, + "test('场景 7.3: 登录后回到原页面, 高亮不丢失', () => {" + ], + [ + 380, + "}), HOME, 'home → login → diy (modal 透明)');" + ], + [ + 383, + "test('场景 7.4: 支付完成后回到原页面, 高亮跟随当前包', () => {" + ], + [ + 391, + "}), CATEGORY, 'home → cart → pay → list (modal 透明 + 同包)');" + ], + [ + 398, + "test('场景 8.1: URL 带 from_tab=home → 强制高亮 home (即使当前在 pages_tool)', () => {" + ], + [ + 408, + "assert.equal(result, true, 'from_tab=home → HOME 高亮');" + ], + [ + 411, + "test('场景 8.2: from_tab=member 但 PAGE_TAB_MAP 已显式声明 → PAGE_TAB_MAP 优先', () => {" + ], + [ + 422, + "assert.equal(result, false, 'PAGE_TAB_MAP 优先于 from_tab');" + ], + [ + 425, + "test('场景 8.3: 非法 from_tab 值 → 忽略, 走后续链路', () => {" + ], + [ + 435, + "assert.equal(result, false, '非法 from_tab → 忽略, 无其它信号 → 不亮');" + ], + [ + 442, + "test('场景 9.1: diy 带 query ?id=123&from=share → 行为不变', () => {" + ], + [ + 449, + "test('场景 9.2: tab 根带 query 也精确匹配', () => {" + ], + [ + 456, + "assert.equal(result, HOME, 'home tab 根 + query');" + ], + [ + 459, + "test('场景 9.3: 老路径 /pages/member/index 自动适配到 /pages_tool/member/index', () => {" + ], + [ + 466, + "assert.equal(result, MEMBER, '老路径自动 adaptSubpackageUrl');" + ], + [ + 473, + "test('场景 10.1: 5 个 tab 中, 同时只有 1 个高亮 (互斥)', () => {" + ], + [ + 485, + "assert.equal(trueCount, 1, `5 个 tab 应只亮 1 个, 实际 ${trueCount} 个亮`);" + ], + [ + 488, + "test('场景 10.2: 同场景下 (current + history), 高亮结果应一致 (幂等)', () => {" + ], + [ + 497, + "assert.equal(r1, r2, 'resolveEntryTab 幂等');" + ], + [ + 498, + "assert.equal(r1, HOME, '且结果应为 home');" + ], + [ + 506, + "test('场景 11.1: tabBarList 为空 (服务端配置异常) → 任何 tab 都不亮', () => {" + ], + [ + 512, + "}), null, 'tabBarList 空');" + ], + [ + 515, + "test('场景 11.2: tabBarList.list 含脏数据 (link 为 null/空) → 不崩', () => {" + ], + [ + 520, + "{ id: 3, text: '首页', link: { wap_url: HOME } }," + ], + [ + 521, + "{ id: 4, text: '我的', link: { wap_url: MEMBER } }," + ], + [ + 529, + "assert.equal(r, HOME, '脏数据不影响查找');" + ], + [ + 532, + "test('场景 11.3: historyRoutes 含 null/空/无效值 → 安全跳过', () => {" + ], + [ + 538, + "assert.equal(r, HOME, 'historyRoutes 脏数据 → 跳过');" + ], + [ + 541, + "test('场景 11.4: historyRoutes 不是数组 → 视为无历史 (iOS HIG)', () => {" + ], + [ + 549, + "assert.equal(r, null, 'historyRoutes 非数组 → 视为空 → 不点亮');" + ], + [ + 552, + "test('场景 11.5: 当前页面是 /pages_goods/some-unmapped-page 深链 → 不亮', () => {" + ], + [ + 557, + "}), null, '深链 unmapped 页');" + ], + [ + 564, + "test('场景 12.1: 用户在 home → diy → 切到 member → 回 home → 进 diy, 仍归 home', () => {" + ], + [ + 577, + "}), HOME, 'home → diy → member → 返回 → home → diy');" + ], + [ + 580, + "test('场景 12.2: 进入 cart tab 后, 旧 packageEntryTab 被新 tab 覆盖', () => {" + ], + [ + 587, + "}), CART, 'home → diy → 点 cart tab → CART');" + ], + [ + 590, + "test('场景 12.3: 登出后, 旧 packageEntryTab 保持 (惰性, 下次 updateCurrentRoute 重算)', () => {" + ], + [ + 597, + "}), HOME, '登出后 diy 高亮仍由 history-walk 决定');" + ], + [ + 605, + "test('单元 13.1: 当前是 tab 根 → 走 step 1 精确匹配, 不查 history', () => {" + ], + [ + 609, + "'category 是 tab 根, 不被 history 干扰'," + ], + [ + 613, + "test('单元 13.2: lookupPageTabMap 精确匹配优先于通配符', () => {" + ], + [ + 621, + "test('单元 13.3: lookupPageTabMap 通配符命中非精确条目', () => {" + ], + [ + 625, + "'通配符 /pages_tool/member" + ], + [ + 629, + "{ label: '深链扫 diy', current: DIY, history: [], expected: null }," + ], + [ + 630, + "{ label: '首页 → diy 链接', current: DIY, history: [HOME], expected: HOME }," + ], + [ + 631, + "{ label: '首页 → 会员中心', current: MEMBER, history: [HOME], expected: MEMBER }," + ], + [ + 632, + "{ label: '分类 → diy 链接', current: DIY, history: [CATEGORY], expected: CATEGORY }," + ], + [ + 633, + "{ label: '分类 → 商品列表 → diy', current: DIY, history: [CATEGORY, GOODS_LIST], expected: CATEGORY }," + ], + [ + 634, + "{ label: '我的 → 优惠券 → diy', current: DIY, history: [MEMBER, MEMBER_COUPON], expected: MEMBER }," + ], + [ + 635, + "{ label: '首页 → 登录页 → diy', current: DIY, history: [HOME, LOGIN], expected: HOME }," + ], + [ + 636, + "{ label: '深链扫 member/info', current: MEMBER_INFO, history: [], expected: MEMBER }," + ], + [ + 639, + "test('场景对照: 8 个核心业务场景', () => {" + ], + [ + 656, + "test('场景 16.1: 在 home page (name=DIY_VIEW_INDEX), 自点 home tab → home 高亮', () => {" + ], + [ + 668, + "assert.equal(result, true, '在 home 上点 home tab → 高亮');" + ], + [ + 671, + "test('场景 16.2: 在 home page, 点 cart tab → cart 不亮 (无来源记忆)', () => {" + ], + [ + 682, + "assert.equal(result, false, '在 home 上点 cart → cart 不该高亮');" + ], + [ + 685, + "test('场景 16.3: 在 home page, 点 merchant tab → 走 PAGE_TAB_MAP 兜底', () => {" + ], + [ + 695, + "assert.equal(result, false, 'home → merchant: HOME 优先, merchant 不亮');" + ], + [ + 702, + "test('场景 17.1: tabBarList 异步加载场景 (文档化行为)', () => {" + ], + [ + 712, + "assert.equal(r1, null, 'tabBarList 空时, resolveEntryTab 应安全返回 null');" + ], + [ + 720, + "assert.equal(r2, null, 'tabBarList null 时, resolveEntryTab 应安全返回 null');" + ], + [ + 728, + "test('场景 18.1: __TABBAR_DEBUG__ 开启时, 决策路径有 console.log', () => {" + ], + [ + 750, + "assert.ok(logCapture.length > 0, `应至少有一次 console.log, 实际 ${logCapture.length} 次`);" + ], + [ + 751, + "assert.ok(logCapture.some(l => l.includes('[TABBAR]')), '日志应包含 [TABBAR] 前缀');" + ], + [ + 754, + "test('场景 18.2: __TABBAR_DEBUG__ 未开启时, 决策路径无 console.log', () => {" + ], + [ + 776, + "assert.equal(tabbarLogs.length, 0, '__TABBAR_DEBUG__ 关闭时不应有 [TABBAR] 日志');" + ], + [ + 779, + "test('场景 18.3: opts.debug = true 也能触发调试', () => {" + ], + [ + 801, + "assert.ok(tabbarLogs.length > 0, 'opts.debug=true 应触发 [TABBAR] 日志');" + ], + [ + 808, + "test('场景 19.1: 多语言切换时高亮结果不变 (zh → en → ja)', () => {" + ], + [ + 816, + "assert.equal(initial, HOME, '语言切换前: HOME');" + ], + [ + 827, + "assert.equal(afterRefresh1, HOME, '第 1 次 re-render: HOME');" + ], + [ + 828, + "assert.equal(afterRefresh2, HOME, '第 2 次 re-render: HOME');" + ], + [ + 829, + "assert.equal(initial, afterRefresh1, '高亮幂等');" + ], + [ + 830, + "assert.equal(afterRefresh1, afterRefresh2, '高亮幂等');" + ], + [ + 833, + "test('场景 19.2: 语言切换时不同页面的高亮都正确', () => {" + ], + [ + 848, + "assert.equal(result, sc.expected, `${sc.label} (第 ${i + 1} 次)`);" + ], + [ + 857, + "test('场景 20.1: 登出后 packageEntryTab 保持 (惰性, 不主动清)', () => {" + ], + [ + 863, + "assert.equal(before, HOME, '登出前: HOME 高亮');" + ], + [ + 877, + "'setMemberInfo 函数体不应包含 packageEntryTab (惰性更新)'," + ], + [ + 882, + "test('场景 20.2: 登出后新导航自然覆盖 packageEntryTab', () => {" + ], + [ + 889, + "assert.equal(r1, null, '登出后深链 diy → 不点亮 (iOS HIG)');" + ], + [ + 896, + "assert.equal(r2, HOME, '登出后重新进 diy → HOME (新 history-walk)');" + ], + [ + 899, + "test('场景 20.3: 登出后登入另一账号, 高亮反映新行为', () => {" + ], + [ + 908, + "assert.equal(r, HOME, '新账号进 diy → HOME (新 history-walk 决定)');" + ], + [ + 915, + "test('场景 21.1: 500 步历史栈应在 50ms 内完成 resolveEntryTab', () => {" + ], + [ + 932, + "assert.equal(result, HOME, '500 步栈应找到 HOME');" + ], + [ + 933, + "assert.ok(elapsedMs < 50, `应 < 50ms, 实际 ${elapsedMs.toFixed(2)}ms`);" + ], + [ + 936, + "test('场景 21.2: 1000 步历史栈性能依然可接受', () => {" + ], + [ + 951, + "assert.ok(elapsedMs < 100, `1000 步应 < 100ms, 实际 ${elapsedMs.toFixed(2)}ms`);" + ], + [ + 954, + "test('场景 21.3: 1000 次 findHighlightedTab 端到端性能', () => {" + ], + [ + 965, + "assert.ok(perCall < 1, `每次应 < 1ms, 实际 ${perCall.toFixed(4)}ms`);" + ], + [ + 972, + "test('场景 22.1: 极长路径名 (200 字符) 仍能正确匹配', () => {" + ], + [ + 984, + "assert.equal(result, true, '极长路径应被通配符命中');" + ], + [ + 987, + "test('场景 22.2: URL 编码的 query 参数 (含 %xx) 不影响匹配', () => {" + ], + [ + 994, + "assert.equal(result, HOME, 'URL 编码 query 不影响 diy 高亮');" + ], + [ + 997, + "test('场景 22.3: query 含特殊字符 (& =) 不影响路径匹配', () => {" + ], + [ + 1003, + "assert.equal(result, CATEGORY, '商品详情 (复杂 query) → CATEGORY');" + ], + [ + 1006, + "test('场景 22.4: 路径含 hash (H5 模式) 不影响匹配', () => {" + ], + [ + 1014, + "assert.equal(result, HOME, '标准路径 → HOME');" + ], + [ + 1017, + "test('场景 22.5: 路径前缀相近但不匹配通配符 (members vs member)', () => {" + ], + [ + 1029, + "assert.equal(result, false, '前缀相近路径不应被错误匹配');" + ], + [ + 1036, + "test('场景 23.1: tabBarList 从一种配置切到另一种, 高亮应基于新配置', () => {" + ], + [ + 1039, + "{ id: 1, text: '首页', url: HOME }," + ], + [ + 1040, + "{ id: 2, text: '我的', url: MEMBER }," + ], + [ + 1050, + "assert.equal(result, HOME, '新配置下, home → HOME 仍高亮');" + ], + [ + 1053, + "test('场景 23.2: 切换 tabBarList 后 PAGE_TAB_MAP 仍指向已删除的 tab', () => {" + ], + [ + 1057, + "{ id: 1, text: '首页', url: HOME }," + ], + [ + 1073, + "assert.equal(result, false, 'PAGE_TAB_MAP 优先: MEMBER_INFO 不归 HOME (即使 MEMBER 已删除)');" + ], + [ + 1086, + "assert.equal(result2, false, '没任何信号 → 不点亮');" + ], + [ + 1089, + "test('场景 23.3: tabBarList 从 null 恢复 (用户等了很久后端才返回)', () => {" + ], + [ + 1096, + "assert.equal(r1, null, 'tabBarList 空时不点亮');" + ], + [ + 1104, + "assert.equal(r2, HOME, 'tabBarList 恢复后: HOME');" + ] + ], + "tests\\search-keyword-encoding.test.js": [ + [ + 37, + "test('_safeEncodeForPlatform: 鸿蒙元服务 → JS 只 encode 分隔符(和 mp-weixin 一致)', () => {" + ], + [ + 45, + "const r1 = api._safeEncodeForPlatform('沙发');" + ], + [ + 46, + "assert.equal(r1, '沙发', `mp-harmony 中文应原样,实际: ${r1}`);" + ], + [ + 50, + "assert.equal(r2, 'a%26b%3Dc', `mp-harmony 分隔符应 encode,实际: ${r2}`);" + ], + [ + 54, + "assert.equal(r3, '`hello%20world`', `mp-harmony 空格应 encode,实际: ${r3}`);" + ], + [ + 57, + "test('_safeEncodeForPlatform: 鸿蒙下已编码入参 → decode 一次还原(避免双编码)', () => {" + ], + [ + 65, + "assert.equal(r, '发', `mp-harmony 已编码入参应 decode 一次,实际: ${r}`);" + ], + [ + 68, + "test('_safeEncodeForPlatform: H5 → JS 侧主动 encode', () => {" + ], + [ + 72, + "const r1 = api._safeEncodeForPlatform('沙发');" + ], + [ + 73, + "assert.equal(r1, '%E6%B2%99%E5%8F%91', `h5 中文应 encode,实际: ${r1}`);" + ], + [ + 76, + "assert.equal(r2, 'a%26b', `h5 & 应 encode 成 %26,实际: ${r2}`);" + ], + [ + 79, + "test('_safeEncodeForPlatform: 微信小程序 → JS 只 encode 分隔符 (不 encode 中文)', () => {" + ], + [ + 87, + "const r1 = api._safeEncodeForPlatform('沙发');" + ], + [ + 88, + "assert.equal(r1, '沙发', `mp-weixin 中文应原样,实际: ${r1}`);" + ], + [ + 92, + "assert.equal(r2, 'a%26b', `mp-weixin & 应 encode 成 %26,实际: ${r2}`);" + ], + [ + 96, + "assert.equal(r3, 'a%3Db', `mp-weixin = 应 encode 成 %3D,实际: ${r3}`);" + ], + [ + 100, + "assert.equal(r4, 'a%3Fb', `mp-weixin ? 应 encode 成 %3F,实际: ${r4}`);" + ], + [ + 107, + "test('_safeEncodeForPlatform: 华为快应用 → JS 侧主动 encode', () => {" + ], + [ + 111, + "const r1 = api._safeEncodeForPlatform('沙发');" + ], + [ + 112, + "assert.equal(r1, '%E6%B2%99%E5%8F%91', `quickapp 中文应 encode,实际: ${r1}`);" + ], + [ + 115, + "test('_safeEncodeForPlatform: 已编码字符串不双编码(非微信/非鸿蒙平台)', () => {" + ], + [ + 124, + "assert.equal(r, '%E8%A1%A8', `${p} decode roundtrip + re-encode 不应双编码,实际: ${r}`);" + ], + [ + 128, + "test('_safeEncodeForPlatform: 微信小程序下已编码入参 → decode 一次还原', () => {" + ], + [ + 133, + "assert.equal(r, '发', `mp-weixin 已编码入参应 decode 一次,实际: ${r}`);" + ], + [ + 217, + "test('端到端: 鸿蒙元服务 → 搜索关键词\"沙发\"在列表页 search 输入框显示原文(不乱码)', () => {" + ], + [ + 227, + "const url = buildRedirectUrl('/pages_goods/list', { keyword: '沙发' }, encode);" + ], + [ + 230, + "assert.ok(url.includes('keyword=沙发')," + ], + [ + 231, + "`鸿蒙下 url 应含 keyword=沙发,实际: ${url}`);" + ], + [ + 235, + "assert.equal(options.keyword, '沙发'," + ], + [ + 236, + "`鸿蒙下 onLoad(options).keyword 应是原文,实际: ${options.keyword}`);" + ], + [ + 239, + "test('端到端: 鸿蒙元服务 → 含中文 + 反引号 + 空格的关键词在列表页 search 显示原文', () => {" + ], + [ + 246, + "const kw = '`hello world` 沙发';" + ], + [ + 250, + "`鸿蒙下 url 应含 JS 编码后的 keyword(空格 → %20),实际: ${url}`);" + ], + [ + 255, + "`鸿蒙下 onLoad(options).keyword 应是 decode 后的原文,实际: ${options.keyword}`);" + ], + [ + 261, + "test('端到端: 微信小程序 → 搜索关键词\"沙发\"在列表页 search 输入框显示原文(不乱码)', () => {" + ], + [ + 268, + "const url = buildRedirectUrl('/pages_goods/list', { keyword: '沙发' }, encode);" + ], + [ + 272, + "`微信下 url 不应含 %xx(JS 不预 encode 中文),实际 url: ${url}`);" + ], + [ + 273, + "assert.ok(url.includes('keyword=沙发')," + ], + [ + 274, + "`微信下 url 应含 keyword=沙发,实际: ${url}`);" + ], + [ + 278, + "assert.equal(options.keyword, '沙发'," + ], + [ + 279, + "`微信下 onLoad(options).keyword 应是原文,实际: ${options.keyword}`);" + ], + [ + 282, + "test('端到端: 微信小程序 → 含中文 + 反引号 + 空格的关键词在列表页 search 显示原文', () => {" + ], + [ + 287, + "const kw = '`hello world` 沙发';" + ], + [ + 290, + "assert.ok(url.includes('keyword=' + encode('`hello world` 沙发'))," + ], + [ + 291, + "`微信下 url 应含 JS 编码后的 keyword(空格 → %20),实际: ${url}`);" + ], + [ + 296, + "`微信下 onLoad(options).keyword 应是 decode 后的原文,实际: ${options.keyword}`);" + ], + [ + 302, + "test('端到端: H5 → 搜索关键词在列表页 search 输入框显示原文', () => {" + ], + [ + 307, + "const url = buildRedirectUrl('/pages_goods/list', { keyword: '沙发' }, encode);" + ], + [ + 309, + "`H5 下 url 应含编码后的 keyword,实际: ${url}`);" + ], + [ + 313, + "assert.equal(options.keyword, '沙发'," + ], + [ + 314, + "`H5 下 browser decode 后 keyword 应是原文,实际: ${options.keyword}`);" + ], + [ + 320, + "test('端到端: 快应用 → 搜索关键词在列表页 search 输入框显示原文', () => {" + ], + [ + 325, + "const url = buildRedirectUrl('/pages_goods/list', { keyword: '沙发' }, encode);" + ], + [ + 327, + "`快应用下 url 应含编码后的 keyword,实际: ${url}`);" + ], + [ + 330, + "assert.equal(options.keyword, '沙发'," + ], + [ + 331, + "`快应用下 decode 后 keyword 应是原文,实际: ${options.keyword}`);" + ], + [ + 337, + "test('Bug 回归场景: 商品分类页输入\"沙发\" → 列表页搜索框显示\"沙发\"(4 平台)', () => {" + ], + [ + 362, + "const keyword = searchClick(p, '沙发');" + ], + [ + 363, + "assert.equal(keyword, '沙发'," + ], + [ + 364, + "`${p} 平台:onLoad(options).keyword 应是\"沙发\",实际: ${keyword}`);" + ], + [ + 368, + "test('Bug 回归场景: 长关键词 + 特殊字符,4 平台都不应乱码', () => {" + ], + [ + 388, + "'沙发'," + ], + [ + 389, + "'Nike 运动鞋'," + ], + [ + 390, + "'`反引号`'," + ], + [ + 391, + "'100%纯棉'," + ], + [ + 392, + "'价格>100'," + ], + [ + 400, + "`${p} 平台: 关键词 \"${kw}\" 应原样传递,实际: \"${got}\"`);" + ], + [ + 408, + "test('防御: 鸿蒙下已 encode 的入参 → decode 一次还原(避免双编码)', () => {" + ], + [ + 418, + "assert.ok(url.includes('keyword=发')," + ], + [ + 419, + "`鸿蒙下已编码入参应被 decode 还原,实际 url: ${url}`);" + ], + [ + 423, + "assert.equal(options.keyword, '发'," + ], + [ + 424, + "`鸿蒙下 decode 一次后应是原文,实际: ${options.keyword}`);" + ], + [ + 427, + "test('防御: 微信下已 encode 的入参 → decode 一次还原(避免平台 encode 后变成双编码)', () => {" + ], + [ + 434, + "assert.ok(url.includes('keyword=发')," + ], + [ + 435, + "`微信下已编码入参应被 decode 还原,实际 url: ${url}`);" + ], + [ + 438, + "assert.equal(options.keyword, '发'," + ], + [ + 439, + "`微信下 decode-encode-decode 一次后应是原文,实际: ${options.keyword}`);" + ] + ], + "tests\\util-navigate.test.js": [ + [ + 39, + "test('util-navigate.js: 能被 vm 加载,所有 export 都存在', () => {" + ], + [ + 50, + "`${name} 应是 function,实际是 ${typeof nav[name]}`);" + ], + [ + 57, + "test('createNavigateApi: 一次性拿到所有跳转方法', () => {" + ], + [ + 72, + "test('_toParamValueString: 各种类型', () => {" + ], + [ + 83, + "test('parsePageParams: JSON 字符串 / 对象 / null / 兜底', () => {" + ], + [ + 99, + "test('resolveLinkUrl: 拆分 wap_url 的 query 进 page_params', () => {" + ], + [ + 109, + "test('resolveLinkUrl: 无 wap_url 返回 null', () => {" + ], + [ + 118, + "test('cleanParamUrls: 移除反引号和首尾空白', () => {" + ], + [ + 126, + "test('cleanParamUrls: 任意 key / 任意嵌套层级', () => {" + ], + [ + 137, + "test('normalizeUrl: 严格不补协议/不补 host', () => {" + ], + [ + 144, + "test('normalizeUrl: host 小写 + 去默认端口', () => {" + ], + [ + 154, + "test('appendQueryParams: 基本功能', () => {" + ], + [ + 162, + "test('appendQueryParams: 已有 ? 时用 &', () => {" + ], + [ + 170, + "test('appendQueryParams: 对象走 JSON.stringify', () => {" + ], + [ + 179, + "test('getPlatform: createNavigateApi 注入 platform 强制指定', () => {" + ], + [ + 189, + "test('getPlatform: 运行时 platform 注入(模拟 systemInfo)', () => {" + ], + [ + 210, + "test('shouldEncodeUrl: 4 平台都为 true', () => {" + ], + [ + 220, + "test('_safeEncodeForPlatform: H5 / 华为快应用 → 主动 encode', () => {" + ], + [ + 226, + "assert.equal(r, 'a%26b', `${p}: & 必须 encode 成 %26,实际: ${r}`);" + ], + [ + 230, + "test('_safeEncodeForPlatform: 鸿蒙元服务 → JS 只 encode 分隔符(和 mp-weixin 行为一致)', () => {" + ], + [ + 237, + "assert.equal(api._safeEncodeForPlatform('a&b'), 'a%26b', '& 应 encode 成 %26');" + ], + [ + 238, + "assert.equal(api._safeEncodeForPlatform('沙发'), '沙发', '中文不应 encode');" + ], + [ + 239, + "assert.equal(api._safeEncodeForPlatform('a=b'), 'a%3Db', '= 应 encode 成 %3D');" + ], + [ + 240, + "assert.equal(api._safeEncodeForPlatform('a?b'), 'a%3Fb', '? 应 encode 成 %3F');" + ], + [ + 243, + "test('_safeEncodeForPlatform: 微信小程序 → JS 只 encode 分隔符', () => {" + ], + [ + 248, + "assert.equal(api._safeEncodeForPlatform('a&b'), 'a%26b', '& 应 encode 成 %26');" + ], + [ + 249, + "assert.equal(api._safeEncodeForPlatform('沙发'), '沙发', '中文不应 encode');" + ], + [ + 250, + "assert.equal(api._safeEncodeForPlatform('a=b'), 'a%3Db', '= 应 encode 成 %3D');" + ], + [ + 251, + "assert.equal(api._safeEncodeForPlatform('a?b'), 'a%3Fb', '? 应 encode 成 %3F');" + ], + [ + 254, + "test('_safeEncodeForPlatform: 已编码字符串不双编码(非微信/非鸿蒙平台)', () => {" + ], + [ + 261, + "assert.equal(r, '%E8%A1%A8', `${p} decode roundtrip + re-encode 不应双编码,实际: ${r}`);" + ], + [ + 265, + "test('_safeEncodeForPlatform: 微信/鸿蒙下已编码入参 → decode 一次还原', () => {" + ], + [ + 270, + "assert.equal(r, '表', `${p} 已编码入参应 decode 一次还原,实际: ${r}`);" + ], + [ + 277, + "test('_navigateTo: H5 用 window.location', () => {" + ], + [ + 316, + "test('_navigateTo: 微信小程序调 uni.navigateTo', () => {" + ], + [ + 337, + "test('_navigateTo: 鸿蒙元服务 tabBar 用 reLaunch 兜底', () => {" + ], + [ + 354, + "assert.equal(calls.switchTab.length, 0, '鸿蒙不用 switchTab');" + ], + [ + 355, + "assert.equal(calls.reLaunch.length, 1, '鸿蒙用 reLaunch 兜底');" + ], + [ + 361, + "test('util.js: 通过 import 引用 util-navigate 的方法,挂载到 default export', () => {" + ], + [ + 371, + "'util.js 应 import util-navigate.js');" + ], + [ + 373, + "'util.js 应 import cleanParamUrls');" + ], + [ + 375, + "'util.js 应 import normalizeUrl 别名 _normalizeUrlBase');" + ], + [ + 377, + "'util.js 应 import extractInternalAppPath 别名');" + ], + [ + 379, + "'util.js 应 import resolveNavMode 别名');" + ], + [ + 382, + "'util.js 应把 cleanParamUrls 挂载到 default export');" + ], + [ + 384, + "'util.js 的 normalizeUrl 应委托给 _normalizeUrlBase');" + ], + [ + 390, + "test('util.js: redirectTo / diyRedirectTo 仍在 util.js(没被搬走)', () => {" + ], + [ + 397, + "'redirectTo 应仍在 util.js');" + ], + [ + 400, + "'diyRedirectTo 应仍在 util.js');" + ], + [ + 403, + "'redirectTo 应调 _safeEncodeForPlatform');" + ], + [ + 406, + "'diyRedirectTo 应调 this.cleanParamUrls');" + ], + [ + 409, + "'diyRedirectTo 应调 _resolveNavMode');" + ], + [ + 411, + "'diyRedirectTo 应调 _extractInternalAppPath');" + ], + [ + 418, + "test('extractInternalAppPath: 相对内部路径直接放行', () => {" + ], + [ + 424, + "test('extractInternalAppPath: 同域绝对 URL → 还原为相对路径', () => {" + ], + [ + 433, + "test('extractInternalAppPath: 同平台不同子域(域名不完全一致)→ null(严格模式)', () => {" + ], + [ + 442, + "test('extractInternalAppPath: 传入完整 URL 作为白名单也能提取 host(完全一致才还原)', () => {" + ], + [ + 456, + "test('extractInternalAppPath: 外链域名不同(Shopify / Facebook)→ 不误伤, 返回 null', () => {" + ], + [ + 464, + "test('extractInternalAppPath: 绝对 URL 但路径无 /pages 段 → null(真外链)', () => {" + ], + [ + 470, + "test('extractInternalAppPath: 无白名单时绝对 URL 一律 null(不猜测)', () => {" + ], + [ + 476, + "test('extractInternalAppPath: 空/非法输入安全返回 null', () => {" + ], + [ + 483, + "test('extractInternalAppPath: host 大小写不敏感 + 忽略端口', () => {" + ], + [ + 498, + "test('extractInternalAppPath: 各分包页面根都能识别 + 保留 hash', () => {" + ], + [ + 509, + "test('extractInternalAppPath: allowedHosts=true → 跳过 host 校验(强制内部)', () => {" + ], + [ + 543, + "test('resolveNavMode: 未设置 / 未知值 → auto', () => {" + ], + [ + 555, + "test('hasStaleUniacidInUrl: URL 无 uniacid → false', () => {" + ], + [ + 561, + "test('hasStaleUniacidInUrl: URL uniacid 与当前一致 → false', () => {" + ], + [ + 568, + "test('hasStaleUniacidInUrl: URL uniacid 与当前不一致 → true(脏数据)', () => {" + ], + [ + 574, + "test('hasStaleUniacidInUrl: 兼容 URL 编码 + 大小写 + 多个 query', () => {" + ], + [ + 582, + "test('hasStaleUniacidInUrl: currentUniacid 无效 → false(无法比较)', () => {" + ], + [ + 590, + "test('hasStaleUniacidInUrl: 空 / 非法输入 → false(安全降级)', () => {" + ], + [ + 604, + "test('pickCurrentUniacid: 第一参(运行时 storage)非 0 → 选第一参', () => {" + ], + [ + 610, + "test('pickCurrentUniacid: 第一参无值 → 回退到第二参(Config 兜底)', () => {" + ], + [ + 617, + "test('pickCurrentUniacid: 第一参为 0 → 回退到第二参(关键边界)', () => {" + ], + [ + 625, + "test('pickCurrentUniacid: 两个参都无值 → 0', () => {" + ], + [ + 633, + "test('pickCurrentUniacid: 字符串数字自动 parseInt', () => {" + ] + ], + "components-diy\\diy-group.vue": [ + [ + 5, + "" + ], + [ + 10, + "" + ], + [ + 15, + "" + ], + [ + 22, + "" + ], + [ + 27, + "" + ], + [ + 32, + "" + ], + [ + 37, + "" + ], + [ + 42, + "" + ], + [ + 47, + "" + ], + [ + 52, + "" + ], + [ + 57, + "" + ], + [ + 63, + "" + ], + [ + 68, + "" + ], + [ + 73, + "" + ], + [ + 78, + "" + ], + [ + 83, + "" + ], + [ + 88, + "" + ], + [ + 94, + "" + ], + [ + 99, + "" + ], + [ + 107, + "" + ], + [ + 112, + "" + ], + [ + 117, + "" + ], + [ + 121, + "" + ], + [ + 127, + "" + ], + [ + 132, + "" + ], + [ + 137, + "" + ], + [ + 142, + "" + ], + [ + 147, + "" + ], + [ + 154, + "" + ], + [ + 159, + "" + ], + [ + 164, + "" + ], + [ + 169, + "" + ], + [ + 174, + "" + ], + [ + 179, + "" + ], + [ + 184, + "" + ], + [ + 189, + "" + ], + [ + 194, + "" + ], + [ + 199, + "" + ], + [ + 204, + "" + ], + [ + 209, + "" + ], + [ + 215, + "" + ], + [ + 220, + "" + ], + [ + 225, + "" + ], + [ + 230, + "" + ], + [ + 235, + "" + ], + [ + 240, + "" + ], + [ + 245, + "" + ], + [ + 250, + "" + ], + [ + 255, + "" + ], + [ + 260, + "" + ], + [ + 264, + "" + ] + ], + "tests\\util.test.js": [ + [ + 61, + "console.warn('[normalizeUrl] scheme 后有多余的 /:', urlString);" + ], + [ + 110, + "console.warn('[normalizeUrl] host 含非法字符:', urlString);" + ], + [ + 396, + "test('decodeEntity: 解码 & 等 HTML 实体', () => {" + ], + [ + 400, + "test('decodeEntity: 解码 中 这种十六进制', () => {" + ], + [ + 401, + "assert.equal(decodeEntity('中'), '中');" + ], + [ + 407, + "test('normalizeUrl: 基础 https URL 不变', () => {" + ], + [ + 414, + "test('normalizeUrl: 剥离反引号和首尾空白', () => {" + ], + [ + 425, + "test('normalizeUrl: 不补协议,内部路径原样返回', () => {" + ], + [ + 430, + "test('normalizeUrl: 不补 host,只补协议也不做', () => {" + ], + [ + 434, + "test('normalizeUrl: host 小写', () => {" + ], + [ + 441, + "test('normalizeUrl: 去默认端口', () => {" + ], + [ + 447, + "test('normalizeUrl: 解析 . 和 ..', () => {" + ], + [ + 451, + "test('normalizeUrl: 合并连续" + ], + [ + 455, + "test('normalizeUrl: query 排序', () => {" + ], + [ + 462, + "test('normalizeUrl: BOM / 零宽字符剥离', () => {" + ], + [ + 469, + "test('normalizeUrl: 全部是空白或反引号返回空串', () => {" + ], + [ + 474, + "test('normalizeUrl: 非字符串返回空串', () => {" + ], + [ + 483, + "test('resolveLinkUrl: 拆分 wap_url 的 query 进 page_params', () => {" + ], + [ + 491, + "test('resolveLinkUrl: page_params 优先于 wap_url query', () => {" + ], + [ + 500, + "test('resolveLinkUrl: 无 wap_url 返回 null', () => {" + ], + [ + 508, + "test('adaptSubpackageUrl: 老前缀映射到新前缀', () => {" + ], + [ + 519, + "test('adaptSubpackageUrl: 已经是新前缀不动', () => {" + ], + [ + 526, + "test('adaptSubpackageUrl: 把 3 个及以上 / 收敛成 2 个', () => {" + ], + [ + 536, + "test('redirectTo: 基础 navigateTo 拼装 url', () => {" + ], + [ + 546, + "test('redirectTo: 0 号清理步骤 — 移除 param.src 反引号 + 修补 scheme', () => {" + ], + [ + 554, + "assert.ok(!call.url.includes('`'), 'url 不应包含反引号');" + ], + [ + 560, + "test('redirectTo: page_params 永远在最末', () => {" + ], + [ + 564, + "assert.ok(url.endsWith('page_params={\"x\":\"y\"}'), 'page_params 应在 URL 末尾');" + ], + [ + 567, + "test('redirectTo: mode=redirectTo 调 uni.redirectTo', () => {" + ], + [ + 574, + "test('redirectTo: mode=reLaunch 调 uni.reLaunch', () => {" + ], + [ + 583, + "test('diyRedirectTo: 外部链接带脏数据(反引号+空格)能清洗干净', () => {" + ], + [ + 587, + "title: '表单'," + ], + [ + 594, + "assert.ok(call, '应该调用了 uni.navigateTo');" + ], + [ + 597, + "assert.ok(!url.includes('`'), 'url 不应包含反引号: ' + url);" + ], + [ + 601, + "assert.ok(url.startsWith('/pages_tool/webview/webview?'), '应跳到 webview 页面: ' + url);" + ], + [ + 604, + "assert.ok(srcMatch, 'url 应含 src 参数');" + ], + [ + 607, + "'src 应以正确的 host + path 开头: ' + src);" + ], + [ + 610, + "assert.ok(src.includes('id=130'), 'src 内应含 id=130');" + ], + [ + 611, + "assert.ok(src.includes('uniacid=2482'), 'src 内应含 uniacid=2482');" + ], + [ + 614, + "test('diyRedirectTo: wap_url 是畸形 https:" + ], + [ + 618, + "title: '表单'," + ], + [ + 626, + "assert.ok(call, '应该调用了 uni.navigateTo');" + ], + [ + 628, + "assert.ok(url.includes('src='), 'url 应含 src 参数');" + ], + [ + 635, + "assert.ok(!srcMatch[1].includes('host.com'), 'src 不应含 host.com(被 reject)');" + ], + [ + 639, + "test('diyRedirectTo: 内部路径不走 webview', () => {" + ], + [ + 643, + "title: '详情'," + ], + [ + 647, + "assert.ok(call.url.startsWith('/pages_order/detail?'), '应跳到内部路径: ' + call.url);" + ], + [ + 650, + "test('diyRedirectTo: page_params 的键值会出现在 src 内', () => {" + ], + [ + 654, + "title: '表单'," + ], + [ + 659, + "assert.ok(url.includes('id=130'), '应含 id=130');" + ], + [ + 660, + "assert.ok(url.includes('uniacid=2482'), '应含 uniacid=2482');" + ] + ], + "tests\\cross-platform.test.js": [ + [ + 153, + "test('getPlatform: 4 个平台都识别正确', () => {" + ], + [ + 160, + "test('getPlatform: 编译时变量也能识别', () => {" + ], + [ + 167, + "test('getPlatform: 运行时特征(鸿蒙/微信)识别', () => {" + ], + [ + 181, + "test('getPlatform: 有 window 没 uni → h5', () => {" + ], + [ + 185, + "test('getPlatform: 兜底 h5', () => {" + ], + [ + 191, + "test('getPlatform: 华为快应用 = H5 嵌入快应用 Webview,通过 systemInfo.platform 识别', () => {" + ], + [ + 213, + "test('shouldEncodeUrl: 4 个平台都为 true', () => {" + ], + [ + 217, + "assert.equal(shouldEncodeUrl(), true, `${p} 平台应该 encode`);" + ], + [ + 224, + "test('safeEncode: 4 个平台对普通字符串 encode 行为一致', () => {" + ], + [ + 227, + "assert.equal(r1, 'hello', `${p} 普通字符串不变`);" + ], + [ + 228, + "const r2 = safeEncodeForPlatform('`表单`', p);" + ], + [ + 229, + "assert.equal(r2, '%60%E8%A1%A8%E5%8D%95%60', `${p} 反引号应被 encode`);" + ], + [ + 231, + "assert.equal(r3, 'a%26b', `${p} & 应被 encode`);" + ], + [ + 235, + "test('safeEncode: 4 个平台对已编码字符串不双编码', () => {" + ], + [ + 239, + "assert.equal(r, '%E8%A1%A8', `${p} decode roundtrip + re-encode 不应双编码`);" + ], + [ + 243, + "test('safeEncode: src 内嵌 URL 的 & 在 4 个平台都被 encode(防微信丢参数)', () => {" + ], + [ + 248, + "assert.ok(!r.includes('&'), `${p} src 不应含裸 &: ${r}`);" + ], + [ + 249, + "assert.ok(r.includes('%26'), `${p} src 应含 %26: ${r}`);" + ], + [ + 256, + "test('_navigateTo: H5 用 window.location', () => {" + ], + [ + 263, + "test('_navigateTo: H5 redirectTo/reLaunch 用 location.replace', () => {" + ], + [ + 271, + "test('_navigateTo: 微信小程序默认调 uni.navigateTo', () => {" + ], + [ + 278, + "test('_navigateTo: 微信小程序 redirectTo/reLaunch 调对应 API', () => {" + ], + [ + 286, + "test('_navigateTo: 微信小程序 tabBar 调 uni.switchTab', () => {" + ], + [ + 293, + "test('_navigateTo: 鸿蒙元服务 tabBar 兜底用 reLaunch(不用 switchTab)', () => {" + ], + [ + 297, + "assert.equal(ctx.calls.switchTab.length, 0, '鸿蒙不用 switchTab');" + ], + [ + 298, + "assert.equal(ctx.calls.reLaunch.length, 1, '鸿蒙用 reLaunch 兜底');" + ], + [ + 301, + "test('_navigateTo: 鸿蒙元服务普通跳转用 uni.navigateTo', () => {" + ], + [ + 307, + "test('_navigateTo: 华为快应用 tabBar 调 uni.switchTab', () => {" + ], + [ + 314, + "test('_navigateTo: 华为快应用普通跳转调 uni.navigateTo', () => {" + ], + [ + 320, + "test('_navigateTo: 非 H5 但没有 uni 全局 → noop(不报错)', () => {" + ], + [ + 328, + "test('_navigateTo: H5 但没有 window → noop(不报错)', () => {" + ], + [ + 339, + "test('diyRedirectTo 外部链接: H5 直接跳完整 URL,不走 webview 容器', () => {" + ], + [ + 350, + "test('diyRedirectTo 外部链接: 微信跳到 webview 容器,url 作为 src', () => {" + ], + [ + 359, + "'应跳到 webview: ' + url);" + ], + [ + 363, + "assert.ok(!srcMatch[1].includes('&'), 'src 不应含裸 &: ' + url);" + ], + [ + 366, + "test('diyRedirectTo 外部链接: 鸿蒙跳到 webview 容器(走 uni.navigateTo)', () => {" + ], + [ + 374, + "test('diyRedirectTo 外部链接: 华为快应用跳到 webview 容器(走 uni.navigateTo)', () => {" + ], + [ + 385, + "test('端到端: 4 平台 — 你的真实 link 走完跳转,参数全部保留', () => {" + ], + [ + 388, + "title: '表单'," + ], + [ + 402, + "`H5 完整 url 应被设置: ${url}`);" + ], + [ + 414, + "assert.ok(keys.includes('src'), `${p} 应含 src: ${url}`);" + ], + [ + 415, + "assert.ok(keys.includes('id'), `${p} 应含 id: ${url}`);" + ], + [ + 416, + "assert.ok(keys.includes('uniacid'), `${p} 应含 uniacid: ${url}`);" + ], + [ + 424, + "test('safeEncode: null/undefined 不报错', () => {" + ], + [ + 429, + "assert.ok(typeof r1 === 'string', `${p} null 应返回字符串`);" + ], + [ + 430, + "assert.ok(typeof r2 === 'string', `${p} undefined 应返回字符串`);" + ], + [ + 434, + "test('safeEncode: 数字 0 / 布尔 false 都正确', () => {" + ], + [ + 441, + "test('safeEncode: 对象走 JSON.stringify 路径', () => {" + ], + [ + 445, + "assert.equal(r, '%7B%22id%22%3A%22130%22%7D', `${p} 对象序列化`);" + ], + [ + 449, + "test('navigateTo: 4 平台 url 都正确传过去', () => {" + ] + ], + "tests\\_v.js": [ + [ + 28, + "{ id:1, text:'首页', link:{wap_url:HOME} }, { id:2, text:'分类', link:{wap_url:CATEGORY} }," + ], + [ + 29, + "{ id:3, text:'购物车', link:{wap_url:CART} }, { id:4, text:'商户', link:{wap_url:MERCHANT} }," + ], + [ + 30, + "{ id:5, text:'我的', link:{wap_url:MEMBER} }," + ], + [ + 59, + "const A = (a, e, l) => assert.equal(a, e, `[${l}] 期望 ${e||'不点亮'}, 实际 ${a||'不点亮'}`);" + ], + [ + 75, + "T('3.4 链式', () => {" + ], + [ + 78, + "assert.equal(findH(tabs[i], tabs.slice(0, i)), tabs[i], `链式${i+1}`);" + ], + [ + 90, + "T('5.2 diy 走 history-walk', () => A(findH(DIY, [HOME]), HOME, '5.2'));" + ], + [ + 91, + "T('5.3 diy 不在 PAGE_TAB_MAP', () => assert.equal(PAGE_TAB_MAP[DIY], undefined));" + ], + [ + 93, + "T('6.1 深链 diy', () => A(findH(DIY, []), null, '6.1'));" + ], + [ + 94, + "T('6.2 深链 diy?id=123', () => A(findH(DIY + '?id=123', []), null, '6.2'));" + ], + [ + 95, + "T('6.3 深链 goods/detail', () => A(findH(GOODS_DETAIL, []), CATEGORY, '6.3'));" + ], + [ + 96, + "T('6.4 深链 member', () => A(findH(MEMBER, []), MEMBER, '6.4'));" + ], + [ + 97, + "T('6.5 深链 fenxiao', () => A(findH(FENXIAO, []), null, '6.5'));" + ], + [ + 105, + "T('8.2 PAGE_TAB_MAP 优先 from_tab', () => assert.equal(checkTabBarActive(MEMBER, DIY, tabBarList, {}, {from_tab:MEMBER}, {noTabPages:NO_TAB_PAGES, pageTabMap:{[DI" + ], + [ + 106, + "T('8.3 非法 from_tab', () => assert.equal(checkTabBarActive(HOME, DIY, tabBarList, {}, {from_tab:'/foo/bar'}, {noTabPages:NO_TAB_PAGES, pageTabMap:{}, pageTabWild" + ], + [ + 108, + "T('9.1 diy 带 query', () => A(findH('/pages_tool/index/diy?id=123&from=share', [HOME]), HOME, '9.1'));" + ], + [ + 109, + "T('9.2 tab 根带 query', () => assert.equal(resolveEntryTab({currentPageRoute:'/pages/index/index?from=share', tabBarList, historyRoutes:[]}), HOME));" + ], + [ + 110, + "T('9.3 老路径', () => assert.equal(resolveEntryTab({currentPageRoute:DIY, tabBarList, historyRoutes:['/pages/member/index']}), MEMBER));" + ], + [ + 112, + "T('10.1 互斥 1 亮', () => {" + ], + [ + 116, + "T('10.2 幂等', () => {" + ], + [ + 122, + "T('11.1 tabBarList 空', () => A(findH(DIY, [HOME], {list:[]}), null, '11.1'));" + ], + [ + 123, + "T('11.2 脏数据', () => {" + ], + [ + 127, + "T('11.3 history 脏', () => assert.equal(resolveEntryTab({currentPageRoute:DIY, tabBarList, historyRoutes:[null,'',undefined,HOME]}), HOME));" + ], + [ + 128, + "T('11.4 history 非数组', () => assert.equal(resolveEntryTab({currentPageRoute:DIY, tabBarList, historyRoutes:'not-array'}), MEMBER));" + ], + [ + 129, + "T('11.5 深链 unmapped', () => A(findH('/pages_goods/some-unmapped-page', []), null, '11.5'));" + ], + [ + 131, + "T('12.1 home→diy→member→回→home→diy', () => A(findH(DIY, [HOME]), HOME, '12.1'));" + ], + [ + 132, + "T('12.2 home→diy→点 cart tab', () => A(findH(CART, [HOME]), CART, '12.2'));" + ], + [ + 133, + "T('12.3 登出后 diy', () => A(findH(DIY, [HOME]), HOME, '12.3'));" + ], + [ + 135, + "T('13.1 tab 根优先', () => assert.equal(resolveEntryTab({currentPageRoute:CATEGORY, tabBarList, historyRoutes:[HOME, MEMBER]}), CATEGORY));" + ], + [ + 136, + "T('13.2 精确', () => assert.equal(lookupPageTabMap(MEMBER, PAGE_TAB_MAP, PAGE_TAB_WILDCARDS), MEMBER));" + ], + [ + 137, + "T('13.3 通配符', () => {" + ], + [ + 141, + "T('13.4 null 优先', () => {" + ], + [ + 146, + "T('13.5 无匹配', () => assert.equal(lookupPageTabMap(DIY, PAGE_TAB_MAP, PAGE_TAB_WILDCARDS), undefined));" + ], + [ + 147, + "T('13.6 前缀相近', () => assert.equal(lookupPageTabMap('/pages_tool/members', PAGE_TAB_MAP, PAGE_TAB_WILDCARDS), undefined));" + ], + [ + 149, + "T('14.1 非法 link', () => assert.equal(checkTabBarActive('/pages/x', DIY, tabBarList, {pages_tool:HOME}, {from_tab:HOME}, {noTabPages:NO_TAB_PAGES, pageTabMap:PAG" + ], + [ + 150, + "T('14.2 NO_TAB_PAGES 都不亮', () => {" + ], + [ + 156, + "T('15. 8 场景', () => {" + ], + [ + 158, + "['深链扫 diy', DIY, [], null]," + ], + [ + 159, + "['首页 → diy 链接', DIY, [HOME], HOME]," + ], + [ + 160, + "['首页 → 会员中心', MEMBER, [HOME], MEMBER]," + ], + [ + 161, + "['分类 → diy 链接', DIY, [CATEGORY], CATEGORY]," + ], + [ + 162, + "['分类 → 商品列表 → diy', DIY, [CATEGORY, GOODS_LIST], CATEGORY]," + ], + [ + 163, + "['我的 → 优惠券 → diy', DIY, [MEMBER, MEMBER_COUPON], MEMBER]," + ], + [ + 164, + "['首页 → 登录页 → diy', DIY, [HOME, LOGIN], HOME]," + ], + [ + 165, + "['深链扫 member/info', MEMBER_INFO, [], MEMBER]," + ], + [ + 172, + "console.log('通过:', p, '/ 失败:', f);" + ] + ], + "vue.config.js": [ + [ + 16, + "console.log(`\\n🛡️ [test-gate] 检测到生产构建,先执行测试门禁 (${process.versions.node})...`);" + ], + [ + 22, + "console.log('🛡️ [test-gate] ✅ 测试通过,放行生产构建\\n');" + ], + [ + 24, + "console.error('\\n❌ [test-gate] 测试未通过,已阻止生产构建');" + ], + [ + 31, + "console.log(`[vue.config] 已加载 | node ${process.versions.node} @ ${process.execPath}`);" + ], + [ + 49, + "console.log(`🏠 当前构建的平台: [${process.env.UNI_PLATFORM || '(未知)'}]\\n`);" + ], + [ + 53, + "console.log(`\\n[H5 发行] 脚本: ${process.env.UNI_SCRIPT || '(默认)'}`);" + ], + [ + 54, + "console.log(`[H5 发行] 域名: ${process.env.UNI_APP_DOMAIN || '(未配置)'}\\n`);" + ], + [ + 65, + "console.error(`❌ NODE_ENV 环境变量未设置`);" + ], + [ + 66, + "throw new Error('NODE_ENV 环境变量未设置');" + ], + [ + 77, + "console.log(`🧹 清理构建目录 ${assetDir}\\n`);" + ], + [ + 108, + "console.log('NODE_ENV 环境变量未设置,默认使用开发模式');" + ], + [ + 120, + "console.warn(`[manifest] h5.router.base \"${base}\" 不规范,已自动补全 /`);" + ], + [ + 126, + "console.warn('[manifest] 读取 h5.router.base 失败:', e.message);" + ], + [ + 253, + "console.log(`[VersionInject] ✅ vendor.js 已注入 (platform=${platform}, mode=${mode}, commit=${this._cachedCommitId})`);" + ], + [ + 270, + "console.log(`[VersionInject] ✅ index.html 已注入 (platform=${platform}, commit=${this._cachedCommitId})`);" + ], + [ + 286, + "console.log('\\n🚀 mp-weixin 生产构建完成,运行补丁脚本(site.js 拷贝 + ZIP 打包)...');" + ], + [ + 290, + "console.error('补丁脚本执行失败:', e.message);" + ], + [ + 293, + "console.log('\\n🚀 mp-harmony 生产构建完成,运行补丁脚本(配置拷贝 + ZIP 打包)...');" + ], + [ + 297, + "console.error('补丁脚本执行失败:', e.message);" + ], + [ + 300, + "console.log('\\n🚀 mp-harmony 生产构建完成,拷贝产物到 ascf_src 目录...');" + ], + [ + 325, + "console.log('[H5Zip] 输出目录名:', result);" + ], + [ + 345, + "console.log(`[H5Zip] 输出目录已重命名为: ${outputDirName}`);" + ], + [ + 347, + "console.error('[H5Zip] 目录重命名失败:', e.message);" + ], + [ + 351, + "console.log(`[H5Zip] 输出目录名正确: ${outputDirName}`);" + ], + [ + 392, + "console.warn(`⚠️ 打开目录失败(不影响 ZIP 打包): ${openErr.message}`);" + ], + [ + 399, + "console.error('H5 ZIP 打包失败:', e.message);" + ], + [ + 407, + "console.log(`\\n📋 拷贝鸿蒙构建产物...`);" + ], + [ + 408, + "console.log(` 源: ${sourceDir}`);" + ], + [ + 409, + "console.log(` 目标: ${destDir}`);" + ], + [ + 413, + "console.error(`❌ 源目录不存在: ${sourceDir}`);" + ], + [ + 414, + "throw new Error(`鸿蒙构建产物目录不存在: ${sourceDir}`);" + ], + [ + 425, + "console.log(` ✅ 已清空目标目录 ${destDir}`);" + ], + [ + 427, + "console.warn(` ⚠️ 清空目标目录 ${destDir} 失败,将尝试覆盖: ${cleanErr.message}`);" + ], + [ + 451, + "console.log(' ✅ 拷贝完成');" + ], + [ + 453, + "console.error(` ❌ 拷贝失败: ${e.message}`);" + ], + [ + 482, + "console.log(`[vue.config] ✅ lang/ 目录已替换为 stub (source=remote-only) — 节省 lang 包体积`);" + ], + [ + 485, + "console.error('[vue.config] 配置 lang/ 排除失败:', e.message);" + ], + [ + 488, + "console.log(`[vue.config] lang/ 目录保留 (source=${langSource || 'unknown'})`);" + ], + [ + 509, + "console.log(`[vue.config] ✅ style_color.js 已替换为 stub (source=remote-only) — 节省 ~75KB`);" + ], + [ + 512, + "console.error('[vue.config] 配置 style_color 排除失败:', e.message);" + ], + [ + 515, + "console.log(`[vue.config] style_color.js 保留 (source=${themeSource || 'unknown'})`);" + ], + [ + 519, + "console.log('[vue.config] 开始尝试配置极限压缩');" + ], + [ + 555, + "console.log(`[vue.config] 是否保留console语句 = ${Boolean(enableKeepConsole)}`);" + ], + [ + 607, + "console.log('[vue.config] 配置极限压缩完成');" + ] + ], + "tests\\redirectTo-real.test.js": [ + [ + 150, + "test('redirectTo 职责: 不对 param 做 URL 清理(职责单一)', () => {" + ], + [ + 165, + "'反引号应被 encode,url 不应含裸反引号(清理是上游 diyRedirectTo 职责): ' + url);" + ], + [ + 167, + "'反引号应被 encode 成 %60: ' + url);" + ], + [ + 172, + "'decode 后应还原原文(说明 redirectTo 没清理脏数据): ' + wapUrlDecoded);" + ], + [ + 178, + "test('redirectTo 职责: 干净 param → 正确拼装 url + 调 uni.navigateTo', () => {" + ], + [ + 185, + "title: '表单'," + ], + [ + 197, + "assert.ok(url.startsWith('/pages_tool/webview/webview?'), '应跳到 webview: ' + url);" + ], + [ + 201, + "assert.ok(srcMatch, 'url 应含 src: ' + url);" + ], + [ + 203, + "'decode 后应含 xcx3 域名: ' + decodeURIComponent(srcMatch[1]));" + ], + [ + 205, + "assert.ok(wapUrlMatch, 'url 应含 wap_url: ' + url);" + ], + [ + 207, + "'decode 后应含 xcx3 域名: ' + decodeURIComponent(wapUrlMatch[1]));" + ], + [ + 211, + "assert.ok(pageParamsIdx > 0, 'url 应含 page_params');" + ], + [ + 218, + "test('redirectTo 职责: enable_set_title=0 正确序列化', () => {" + ], + [ + 228, + "assert.ok(url.includes('enable_set_title=0'), 'enable_set_title=0 应出现: ' + url);" + ], + [ + 234, + "test('mode=redirectTo: 调 uni.redirectTo', () => {" + ], + [ + 242, + "test('mode=reLaunch: 调 uni.reLaunch', () => {" + ], + [ + 249, + "test('默认 mode=navigateTo: 调 uni.navigateTo', () => {" + ], + [ + 259, + "test('param 为 undefined 不会报错', () => {" + ], + [ + 267, + "test('空对象 param 不会报错', () => {" + ], + [ + 274, + "test('to 带 query 时也能正确合并 param', () => {" + ], + [ + 279, + "assert.ok(url.includes('from=tab1'), 'to 里的 query 应保留: ' + url);" + ], + [ + 280, + "assert.ok(url.includes('id=1'), 'param 应追加: ' + url);" + ], + [ + 286, + "test('微信小程序: src 内的 & 不能被当成 top-level query 分隔符', () => {" + ], + [ + 300, + "title: '表单'," + ], + [ + 313, + "assert.ok(srcMatch, 'url 应含 src 参数');" + ], + [ + 316, + "'src 段不能含裸 &,否则微信会把 src 内的 & 当成 top-level 分隔符: ' + url);" + ], + [ + 318, + "'src 段必须含 %26(encode 后的 &): ' + url);" + ], + [ + 323, + "assert.ok(keys.includes('name'), '应含 name: ' + url);" + ], + [ + 324, + "assert.ok(keys.includes('title'), '应含 title: ' + url);" + ], + [ + 325, + "assert.ok(keys.includes('wap_url'), '应含 wap_url: ' + url);" + ], + [ + 326, + "assert.ok(keys.includes('enable_set_title'), '应含 enable_set_title: ' + url);" + ], + [ + 327, + "assert.ok(keys.includes('parent'), '应含 parent: ' + url);" + ], + [ + 328, + "assert.ok(keys.includes('src'), '应含 src: ' + url);" + ], + [ + 329, + "assert.ok(keys.includes('page_params'), '应含 page_params: ' + url);" + ], + [ + 331, + "assert.ok(!keys.includes('a'), 'src 内的 a 不能成为 top-level key: ' + url);" + ], + [ + 332, + "assert.ok(!keys.includes('b'), 'src 内的 b 不能成为 top-level key: ' + url);" + ], + [ + 333, + "assert.ok(!keys.includes('c'), 'src 内的 c 不能成为 top-level key: ' + url);" + ], + [ + 336, + "test('微信小程序: 反引号 / 空格 / 中文都被正确 encode', () => {" + ], + [ + 340, + "title: '`表单`'," + ], + [ + 347, + "assert.equal(url.indexOf('=' + '`'), -1, 'url 不应有裸 =`,反引号应被 encode: ' + url);" + ], + [ + 352, + "assert.ok(titleMatch, '应含 title');" + ], + [ + 353, + "assert.equal(decodeURIComponent(titleMatch[1]), '`表单`', 'decode 后应还原原文');" + ], + [ + 356, + "test('微信小程序: decode roundtrip 不导致双重编码', () => {" + ], + [ + 373, + "assert.equal(decoded, '表单', 'decode 两次后应得原文,不应该是双重编码乱码');" + ] + ], + "pages_goods\\detail.vue": [ + [ + 2, + "" + ], + [ + 12, + "" + ], + [ + 14, + "" + ], + [ + 98, + "" + ], + [ + 101, + "开通{{ membercard.level_name }}仅需{{ membercard.member_price }}元" + ], + [ + 102, + "立即开通" + ], + [ + 107, + "" + ], + [ + 131, + "- 套餐包含以下的服务及商品 -" + ], + [ + 133, + "{{ goodsSkuDetail.card_info.card_type == 'commoncard' ? '卡项内项目/商品总的可用次数为' + goodsSkuDetail.card_info.common_num + ' ,' : '' }}" + ], + [ + 134, + "{{ goodsSkuDetail.card_info.card_type == 'timecard' ? '次数不限 ,' : '' }} 有效期为{{" + ], + [ + 135, + "(goodsSkuDetail.card_info.validity_type == 0 && '永久') ||" + ], + [ + 136, + "(goodsSkuDetail.card_info.validity_type == 1 && goodsSkuDetail.card_info.validity_day + '天') ||" + ], + [ + 154, + "{{ cardOff ? '收起' : '展开' }}" + ], + [ + 159, + "" + ], + [ + 169, + "" + ], + [ + 172, + "领券" + ], + [ + 178, + "? '满' + decimalPointFormat(item.at_least) + '减' + decimalPointFormat(item.money)" + ], + [ + 179, + ": '无门槛使用' + decimalPointFormat(item.money)" + ], + [ + 182, + "{{ '满' + decimalPointFormat(item.at_least) + '打' + parseFloat(item.discount)+'折' }}" + ], + [ + 189, + "选择" + ], + [ + 196, + "促销" + ], + [ + 197, + "满减" + ], + [ + 203, + "" + ], + [ + 205, + "" + ], + [ + 217, + "" + ], + [ + 316, + "" + ], + [ + 367, + "" + ], + [ + 393, + "" + ], + [ + 403, + "满减" + ], + [ + 407, + "满送" + ], + [ + 411, + "包邮" + ], + [ + 416, + "" + ], + [ + 445, + "" + ], + [ + 447, + "" + ], + [ + 479, + "" + ], + [ + 531, + "" + ] + ], + "pages_goods\\_components\\goods-detail-view\\goods-detail-view.vue": [ + [ + 3, + "" + ], + [ + 7, + "" + ], + [ + 12, + "" + ], + [ + 17, + "" + ], + [ + 19, + "" + ], + [ + 37, + "" + ], + [ + 43, + "" + ], + [ + 52, + "" + ], + [ + 59, + "" + ], + [ + 62, + "" + ], + [ + 81, + "" + ], + [ + 83, + "门店" + ], + [ + 90, + "距离{{ globalStoreInfo.distance > 1 ? globalStoreInfo.distance + 'km' : globalStoreInfo.distanc" + ], + [ + 124, + "" + ], + [ + 126, + "" + ], + [ + 130, + "" + ], + [ + 132, + "" + ], + [ + 134, + "" + ], + [ + 153, + "" + ], + [ + 177, + "" + ], + [ + 210, + "" + ], + [ + 247, + "" + ], + [ + 250, + "" + ], + [ + 262, + "" + ], + [ + 266, + "添加社群" + ], + [ + 271, + "长按识别二维码,添加社群" + ], + [ + 278, + "" + ], + [ + 281, + "" + ], + [ + 338, + "" + ], + [ + 363, + "" + ], + [ + 376, + "" + ], + [ + 392, + "" + ], + [ + 409, + "" + ], + [ + 414, + "" + ], + [ + 434, + "" + ], + [ + 482, + "" + ] + ], + "scripts\\open-directory.js": [ + [ + 24, + "console.warn(`❌ 路径:${targetPath} 不存在!!`)" + ], + [ + 41, + "throw new Error(`路径不存在或无法访问: ${targetPath}`);" + ], + [ + 51, + "console.log(`📁 在文件资源管理器中打开: ${normalizedPath}`);" + ], + [ + 60, + "console.log('✅ 已启动文件资源管理器');" + ], + [ + 63, + "console.warn('方法1失败:', error.message);" + ], + [ + 70, + "console.log('✅ 已使用 start 命令打开');" + ], + [ + 73, + "console.warn('方法2失败:', error.message);" + ], + [ + 76, + "throw new Error('无法在Windows系统中打开目录');" + ], + [ + 81, + "console.log(`📁 在访达中打开: ${directoryPath}`);" + ], + [ + 87, + "console.log('✅ 已启动访达');" + ], + [ + 90, + "console.warn('方法1失败:', error.message);" + ], + [ + 97, + "console.log('✅ 已使用 open -R 命令打开');" + ], + [ + 100, + "console.warn('方法2失败:', error.message);" + ], + [ + 103, + "throw new Error('无法在macOS系统中打开目录');" + ], + [ + 108, + "console.log(`📁 在文件管理器中打开: ${directoryPath}`);" + ], + [ + 133, + "console.log(`✅ 已使用 ${manager} 打开目录`);" + ], + [ + 150, + "console.log('✅ 已使用 xdg-open 打开目录');" + ], + [ + 153, + "throw new Error('无法在Linux系统中打开目录,请确保已安装文件管理器');" + ], + [ + 162, + "console.log(`💻 操作系统: ${os}`);" + ], + [ + 163, + "console.log(`📂 目标目录: ${dirPath}`);" + ], + [ + 166, + "throw new Error(`路径不存在: ${dirPath}`);" + ], + [ + 177, + "throw new Error(`不支持的操作系统: ${process.platform}`);" + ], + [ + 186, + "console.log(`🖥️ 在终端中打开目录: ${dirPath}`);" + ], + [ + 189, + "throw new Error(`路径不存在: ${dirPath}`);" + ], + [ + 193, + "windows: `start cmd /K \"cd /d \"${dirPath}\" && echo 当前目录: %cd%\"`," + ], + [ + 211, + "throw new Error('不支持的操作系统');" + ], + [ + 224, + "console.log('✅ 已启动终端并切换到目录');" + ], + [ + 227, + "console.warn('无法在终端中打开目录:', error.message);" + ], + [ + 247, + "console.log(`📁 创建目录: ${dirPath}`);" + ], + [ + 249, + "throw new Error(`路径不存在: ${filePath}`);" + ], + [ + 263, + "console.error('❌ 打开目录失败:', error.message);" + ], + [ + 272, + "console.log('当前文件:', currentFile);" + ], + [ + 275, + "console.log('\\n--- 在文件管理器中打开 ---');" + ], + [ + 282, + "console.log('\\n--- 在终端中打开 ---');" + ], + [ + 297, + "console.error('错误:', error.message);" + ], + [ + 312, + "console.error('错误:', error.message);" + ] + ], + "tests\\clean-param-urls.test.js": [ + [ + 43, + "test('cleanParamUrls: 基础 https URL 不变', () => {" + ], + [ + 50, + "test('cleanParamUrls: 反引号 + 首尾空格被清', () => {" + ], + [ + 57, + "test('cleanParamUrls: scheme 后多余斜杠被修补', () => {" + ], + [ + 64, + "test('cleanParamUrls: \\\\t \\\\n \\\\r 也被清', () => {" + ], + [ + 71, + "test('cleanParamUrls: http 和 https 都支持', () => {" + ], + [ + 79, + "test('cleanParamUrls: 普通文本不变(name)', () => {" + ], + [ + 83, + "test('cleanParamUrls: 中文文本不变(title)', () => {" + ], + [ + 84, + "assert.equal(cleanParamUrls('表单'), '表单');" + ], + [ + 87, + "test('cleanParamUrls: 含 :" + ], + [ + 93, + "assert.ok(!result.includes(' '), '空格应被清');" + ], + [ + 96, + "test('cleanParamUrls: 数字字符串不变', () => {" + ], + [ + 103, + "test('cleanParamUrls: 嵌套对象里的 URL 被清', () => {" + ], + [ + 113, + "test('cleanParamUrls: 普通文本字段不被误伤', () => {" + ], + [ + 116, + "title: '表单'," + ], + [ + 121, + "assert.equal(out.title, '表单');" + ], + [ + 125, + "test('cleanParamUrls: 任意 key 名字都生效(不限定 keys)', () => {" + ], + [ + 144, + "test('cleanParamUrls: 深层嵌套对象里的 URL 也被清', () => {" + ], + [ + 158, + "test('cleanParamUrls: page_params 里的 URL 也被清', () => {" + ], + [ + 177, + "test('cleanParamUrls: 数组里的 URL 被清', () => {" + ], + [ + 185, + "test('cleanParamUrls: 数组里的对象(URL 字段)被清', () => {" + ], + [ + 198, + "test('cleanParamUrls: null 原样返回', () => {" + ], + [ + 202, + "test('cleanParamUrls: undefined 原样返回', () => {" + ], + [ + 206, + "test('cleanParamUrls: 数字原样返回', () => {" + ], + [ + 210, + "test('cleanParamUrls: 布尔原样返回', () => {" + ], + [ + 215, + "test('cleanParamUrls: 空对象返回空对象', () => {" + ], + [ + 219, + "test('cleanParamUrls: 空数组返回空数组', () => {" + ], + [ + 223, + "test('cleanParamUrls: 数字 0 原样返回', () => {" + ], + [ + 227, + "test('cleanParamUrls: enable_set_title=0 这种数字字段不变', () => {" + ], + [ + 238, + "test('cleanParamUrls: 真实 param 场景,所有 URL 字段一次性清理', () => {" + ], + [ + 241, + "title: '表单'," + ], + [ + 251, + "assert.equal(out.title, '表单');" + ], + [ + 273, + "test('cleanParamUrls: 不修改输入对象(返回新对象)', () => {" + ], + [ + 276, + "name: '`表单`'," + ], + [ + 280, + "assert.equal(JSON.stringify(input), inputStr, '原对象不应被修改');" + ] + ], + "tests\\diy-redirect-to.test.js": [ + [ + 85, + "test('端到端: 你提供的真实 link → diyRedirectTo 清理 → 跳转到 webview', () => {" + ], + [ + 108, + "title: '表单'," + ], + [ + 117, + "assert.equal(calls.navigateTo.length, 1, '应调一次 uni.navigateTo');" + ], + [ + 122, + "'应跳到 webview: ' + url);" + ], + [ + 125, + "assert.ok(!url.includes('`'), 'url 不应含反引号: ' + url);" + ], + [ + 132, + "assert.ok(wapUrlMatch, 'url 应含 wap_url');" + ], + [ + 134, + "assert.equal(wapUrl.indexOf('`'), -1, 'wap_url 不应含反引号: ' + wapUrl);" + ], + [ + 135, + "assert.equal(wapUrl.indexOf(' '), -1, 'wap_url 不应含空格: ' + wapUrl);" + ], + [ + 137, + "'wap_url 应以 https:" + ], + [ + 141, + "assert.ok(srcMatch, 'url 应含 src');" + ], + [ + 143, + "assert.equal(src.indexOf('`'), -1, 'src 不应含反引号: ' + src);" + ], + [ + 148, + "assert.ok(pageParamsIdx > 0, 'url 应含 page_params');" + ], + [ + 153, + "test('端到端: 普通文本字段(name/title/parent)保留原文,不被误伤', () => {" + ], + [ + 168, + "title: '`表单`'," + ], + [ + 175, + "assert.ok(url.includes('name=`bad`'), 'name 保留原文: ' + url);" + ], + [ + 176, + "assert.ok(url.includes('title=`表单`'), 'title 保留原文: ' + url);" + ], + [ + 180, + "assert.ok(wapUrlMatch, 'url 应含 wap_url');" + ], + [ + 182, + "assert.equal(wapUrl.indexOf('`'), -1, 'wap_url 值不应含反引号: ' + wapUrl);" + ], + [ + 183, + "assert.equal(wapUrl.indexOf(' '), -1, 'wap_url 值不应含空格: ' + wapUrl);" + ], + [ + 185, + "'wap_url 应以 https:" + ], + [ + 189, + "assert.ok(srcMatch, 'url 应含 src');" + ], + [ + 195, + "'src 应以干净的 https:" + ], + [ + 199, + "test('端到端: 多种空白(\\\\t\\\\n\\\\r) + 反引号都能被清', () => {" + ], + [ + 218, + "assert.equal(url.indexOf('`'), -1, '反引号应被清');" + ], + [ + 219, + "assert.equal(url.indexOf('\\t'), -1, '\\\\t 应被清');" + ], + [ + 220, + "assert.equal(url.indexOf('\\n'), -1, '\\\\n 应被清');" + ], + [ + 221, + "assert.equal(url.indexOf('\\r'), -1, '\\\\r 应被清');" + ], + [ + 225, + "'src 应基于干净的 wap_url 拼出: ' + url);" + ], + [ + 228, + "test('端到端: link 为空/null/undefined 不报错', () => {" + ], + [ + 238, + "test('端到端: page_params 里的 URL 字段也被清理', () => {" + ], + [ + 262, + "'顶层 src 应被清: ' + url);" + ], + [ + 265, + "'page_params 里的 src 已被清(应该有 https:" + ], + [ + 266, + "assert.ok(!url.match(/\"src\":\"`https/), 'page_params 里 src 不应再含反引号: ' + url);" + ] + ], + "pages_order\\_components\\common-payment\\common-payment.vue": [ + [ + 3, + "" + ], + [ + 17, + "" + ], + [ + 33, + "" + ], + [ + 38, + "" + ], + [ + 118, + "" + ], + [ + 161, + "" + ], + [ + 169, + "" + ], + [ + 239, + "" + ], + [ + 351, + "税费" + ], + [ + 361, + "发票邮寄费" + ], + [ + 393, + "会员卡" + ], + [ + 415, + "" + ], + [ + 424, + "" + ], + [ + 448, + "" + ], + [ + 489, + "" + ], + [ + 511, + "" + ], + [ + 566, + "" + ], + [ + 583, + "" + ], + [ + 628, + "" + ] + ], + "common\\js\\ai-service.js": [ + [ + 25, + "console.warn('读取会话ID失败:', e);" + ], + [ + 141, + "console.log('[AI WS] 流结束信号, event:', data.event, 'type:', data.type, 'conversationId:', conversationId, '当前内存:', currentConversationId);" + ], + [ + 148, + "console.log('[AI WS] 会话ID已持久化到 storage:', conversationId);" + ], + [ + 150, + "console.error('保存会话ID失败:', e);" + ], + [ + 163, + "console.log('[AI WS] 未识别的消息:', JSON.stringify(data).substring(0, 200));" + ], + [ + 166, + "console.error('WebSocket 消息解析失败:', e, '原始数据:', res.data);" + ], + [ + 171, + "console.error('WebSocket 连接失败:', err);" + ], + [ + 180, + "console.warn('WebSocket 连接已关闭');" + ], + [ + 255, + "console.error('[AI Service] AiChat API请求失败,详细错误:', error);" + ], + [ + 256, + "console.error('[AI Service] 错误堆栈:', error.stack);" + ], + [ + 258, + "console.error('[AI Service] store 状态:', {" + ], + [ + 366, + "console.log('[AI SSE] 收到完成信号:', data.event, 'conversationId:', cid);" + ], + [ + 370, + "console.warn('解析失败:', e, line);" + ], + [ + 396, + "console.warn('最后 buffer 解析失败:', e);" + ], + [ + 400, + "console.log('[AI SSE] 流完成, conversationId:', conversationId, 'content长度:', content.length);" + ], + [ + 406, + "console.error('H5 流式请求失败:', error);" + ], + [ + 438, + "console.error('保存会话ID失败:', e);" + ], + [ + 449, + "console.error('清除会话ID失败:', e);" + ], + [ + 538, + "console.error('清除会话失败:', error);" + ], + [ + 549, + "console.log('[AI History] 请求历史, conversation_id:', params.conversation_id, 'limit:', params.limit);" + ], + [ + 580, + "console.error('获取会话历史失败:', error);" + ], + [ + 625, + "console.error('解析消息失败:', error);" + ], + [ + 629, + "console.error('EventSource错误:', error);" + ], + [ + 635, + "console.error('创建EventSource失败:', error);" + ], + [ + 641, + "console.warn('chatWithEventSource 不支持微信小程序');" + ], + [ + 655, + "console.warn('chatWithFetchStream 不支持微信小程序');" + ] + ], + "pages_promotion\\fenxiao\\index.vue": [ + [ + 6, + "" + ], + [ + 27, + "推广规则" + ], + [ + 33, + "{{ info.level_num > 0 ? info.level_name : '等级未解锁' }}" + ], + [ + 34, + "下单、邀请好友均可提升等级" + ], + [ + 36, + "做任务" + ], + [ + 55, + "总{{ fenxiaoWords.account }}" + ], + [ + 59, + "已提现{{ fenxiaoWords.account }}" + ], + [ + 85, + "" + ], + [ + 133, + "邀请好友" + ], + [ + 134, + "邀好友赚好礼" + ], + [ + 142, + "账单报表" + ], + [ + 143, + "{{ fenxiaoWords.account }}变更明细" + ], + [ + 151, + "{{ fenxiaoWords.concept }}商品" + ], + [ + 152, + "{{ fenxiaoWords.concept }}商品" + ], + [ + 161, + "{{ fenxiaoWords.account }}排行" + ], + [ + 162, + "您的排名为第{{ profitRanking }}位" + ], + [ + 171, + "推广人排行" + ], + [ + 172, + "您的排名为{{ invitedNumRanking }}位" + ], + [ + 180, + "等级说明" + ], + [ + 181, + "{{ fenxiaoWords.concept }}等级说明" + ], + [ + 193, + "我的粉丝" + ], + [ + 194, + "{{ teamNum.num }}人" + ], + [ + 205, + "{{ teamNum.fenxiao_num }}人" + ], + [ + 260, + "" + ] + ], + "pages_promotion\\merch\\detail.vue": [ + [ + 4, + "" + ], + [ + 41, + "" + ], + [ + 123, + "" + ], + [ + 132, + "" + ], + [ + 141, + "" + ], + [ + 153, + "" + ], + [ + 213, + "" + ], + [ + 222, + "" + ], + [ + 231, + "" + ], + [ + 243, + "" + ], + [ + 263, + "" + ], + [ + 267, + "" + ], + [ + 269, + "是否包邮" + ], + [ + 271, + "" + ], + [ + 275, + "" + ], + [ + 277, + "价格区间(元)" + ], + [ + 279, + "" + ], + [ + 281, + "" + ], + [ + 285, + "" + ], + [ + 287, + "品牌" + ], + [ + 295, + "" + ], + [ + 318, + "" + ] + ], + "components-diy\\diy-tab.vue": [ + [ + 2, + "" + ], + [ + 5, + "" + ], + [ + 7, + "" + ], + [ + 18, + "" + ], + [ + 31, + "" + ], + [ + 39, + "" + ], + [ + 47, + "" + ], + [ + 49, + "" + ], + [ + 57, + "" + ], + [ + 63, + "" + ], + [ + 76, + "" + ], + [ + 88, + "" + ], + [ + 91, + "" + ], + [ + 102, + "" + ], + [ + 115, + "" + ], + [ + 123, + "" + ], + [ + 132, + "" + ], + [ + 135, + "" + ], + [ + 143, + "" + ], + [ + 150, + "" + ], + [ + 163, + "" + ] + ], + "tests\\mp-share-default.test.js": [ + [ + 106, + "test('黑名单包含 webview 页面', () => {" + ], + [ + 110, + "test('黑名单不包含普通商品详情页', () => {" + ], + [ + 118, + "test('_isCurrentPageDisabled: 当前页是 webview 时返回 true', () => {" + ], + [ + 123, + "test('_isCurrentPageDisabled: 当前页是其他页时返回 false', () => {" + ], + [ + 128, + "test('_isCurrentPageDisabled: 当前页带前导 / 时也认得', () => {" + ], + [ + 134, + "test('_isCurrentPageDisabled: getCurrentPages 返回空时不报错', () => {" + ], + [ + 139, + "test('_isCurrentPageDisabled: getCurrentPages 不存在时不报错', () => {" + ], + [ + 147, + "test('mixin: 命中禁用列表时 onLoad 调 wx.hideShareMenu', () => {" + ], + [ + 153, + "assert.equal(_wxCalls.length, 1, '应该调了一次 hideShareMenu');" + ], + [ + 158, + "test('mixin: 命中禁用列表时 onLoad 不调网络', () => {" + ], + [ + 164, + "assert.equal(ctx.__networkCalled, undefined, '不应触发网络拉取');" + ], + [ + 167, + "test('mixin: 非命中页面 onLoad 调网络(且不调 hideShareMenu)', () => {" + ], + [ + 173, + "assert.equal(ctx.__networkCalled, true, '应触发网络拉取');" + ], + [ + 174, + "assert.equal(_wxCalls.length, 0, '不应调 hideShareMenu');" + ], + [ + 177, + "test('mixin: 命中禁用列表时 onShow 不调网络', () => {" + ], + [ + 186, + "test('mixin: 命中禁用列表时 onShareAppMessage 返回 undefined', () => {" + ], + [ + 193, + "test('mixin: 命中禁用列表时 onShareTimeline 返回 undefined', () => {" + ], + [ + 200, + "test('mixin: 非命中页面 onShareAppMessage 返回正常分享对象', () => {" + ], + [ + 210, + "test('_hideShareMenu: wx.hideShareMenu 不存在时静默', () => {" + ], + [ + 216, + "test('_hideShareMenu: menus 参数不支持时降级到无参', () => {" + ], + [ + 228, + "assert.equal(secondCall, undefined, '应降级到无参调用');" + ] + ], + "docs\\example\\wechat-channel-demo.vue": [ + [ + 4, + "微信视频号集成示例" + ], + [ + 8, + "单个视频号" + ], + [ + 13, + "视频号列表" + ], + [ + 18, + "使用说明" + ], + [ + 20, + "1. 确保微信小程序已开通视频号权限" + ], + [ + 21, + "2. 小程序需与视频号关联" + ], + [ + 22, + "3. 传递正确的feedId用于播放视频" + ], + [ + 23, + "4. 仅支持微信小程序环境" + ], + [ + 43, + "channelName: \"示例视频号\"," + ], + [ + 45, + "videoTitle: \"这是一个示例视频,展示如何使用微信视频号组件\"," + ], + [ + 58, + "channelName: \"示例视频号1\"," + ], + [ + 60, + "videoTitle: \"这是示例视频1,展示如何使用微信视频号组件\"," + ], + [ + 65, + "channelName: \"示例视频号2\"," + ], + [ + 67, + "videoTitle: \"这是示例视频2,展示如何使用微信视频号组件\"," + ], + [ + 72, + "channelName: \"示例视频号3\"," + ], + [ + 74, + "videoTitle: \"这是示例视频3,展示如何使用微信视频号组件\"," + ], + [ + 79, + "channelName: \"示例视频号4\"," + ], + [ + 81, + "videoTitle: \"这是示例视频4,展示如何使用微信视频号组件\"," + ], + [ + 101, + "console.log('微信视频号示例页面加载');" + ] + ], + "pages_tool\\member\\info.vue": [ + [ + 4, + "" + ], + [ + 12, + "" + ], + [ + 14, + "账号" + ], + [ + 18, + "" + ], + [ + 23, + "" + ], + [ + 29, + "" + ], + [ + 35, + "" + ], + [ + 41, + "" + ], + [ + 47, + "" + ], + [ + 54, + "" + ], + [ + 56, + "密码" + ], + [ + 59, + "" + ], + [ + 66, + "所在地址" + ], + [ + 69, + "去设置" + ], + [ + 73, + "" + ], + [ + 79, + "注销账号" + ], + [ + 83, + "版本号" + ], + [ + 87, + "" + ], + [ + 93, + "" + ] + ], + "common\\js\\customer-service.js": [ + [ + 88, + "result.warnings.push('企业微信未启用');" + ], + [ + 91, + "result.warnings.push('企业微信活码链接未配置');" + ], + [ + 113, + "console.error('跳转 AI智能客服失败:', err);" + ], + [ + 122, + "console.error('跳转 AI智能客服异常:', e);" + ], + [ + 131, + "console.error('客服配置验证失败:', validation.errors);" + ], + [ + 137, + "console.warn('客服配置警告:', validation.warnings);" + ], + [ + 173, + "console.error('【未知客服类型】', config.type);" + ], + [ + 192, + "console.error('跳转企业微信失败:', err);" + ], + [ + 199, + "console.error('企业微信配置不完整,缺少 wxwork_url 或 corpid');" + ], + [ + 243, + "console.error('【跳转第三方小程序失败】', err);" + ], + [ + 268, + "console.error('【第三方客服配置不完整】缺少 mini_app_id 或 third_url');" + ], + [ + 505, + "console.error('跳转 AI智能客服失败:', err);" + ], + [ + 513, + "console.error('跳转 AI智能客服异常:', e);" + ], + [ + 562, + "console.error('客服配置无效:', validation.errors);" + ], + [ + 568, + "console.warn('客服配置警告:', validation.warnings);" + ], + [ + 579, + "console.warn('企业微信配置不完整,使用原生客服');" + ], + [ + 586, + "console.error('企业微信链接未配置');" + ], + [ + 600, + "console.error('客服处理失败:', error);" + ] + ], + "components-diy\\diy-member-info.vue": [ + [ + 3, + "" + ], + [ + 110, + "超级会员" + ], + [ + 113, + "查看特权" + ], + [ + 114, + "会员可享更多权益" + ], + [ + 121, + "开通可享更多权益" + ], + [ + 124, + "查看特权" + ], + [ + 125, + "立即开通" + ], + [ + 134, + "开通可享更多权益" + ], + [ + 137, + "查看更多权益" + ], + [ + 138, + "立即开通" + ], + [ + 146, + "余额" + ], + [ + 152, + "积分" + ], + [ + 159, + "优惠券" + ], + [ + 164, + "余额充值" + ], + [ + 192, + "" + ], + [ + 403, + "if (this.nickName == '微信用户') {" + ], + [ + 426, + "if (this.nickName == '微信用户') {" + ] + ], + "pages_tool\\recharge\\list.vue": [ + [ + 5, + "" + ], + [ + 100, + "" + ] + ], + "components\\hover-nav\\hover-nav.vue": [ + [ + 2, + "" + ], + [ + 12, + "" + ], + [ + 21, + "" + ], + [ + 31, + "" + ], + [ + 49, + "" + ], + [ + 67, + "" + ], + [ + 77, + "" + ], + [ + 89, + "" + ], + [ + 441, + "fail: () => { console.warn('[hover-nav] 未获取到 shopInfo,使用默认设置'); }" + ], + [ + 445, + "console.warn('[hover-nav] 读取 shopInfo 异常:', e);" + ], + [ + 533, + "console.warn('[hover-nav] 读取 CSS 变量失败,使用 fallback 默认值:', e);" + ], + [ + 633, + "console.warn('[hover-nav] 切换语言失败:langList 为空');" + ], + [ + 640, + "console.warn('[hover-nav] 切换语言失败:目标语种为空', this.langIndexMap, this.currentLangIndex);" + ], + [ + 678, + "console.warn('[hover-nav] selectLanguage 越界:', idx, this.langList);" + ], + [ + 708, + "sendMessageTitle: '来自悬浮按钮的咨询'," + ] + ], + "pages_goods\\list.vue": [ + [ + 4, + "" + ], + [ + 15, + "" + ], + [ + 152, + "" + ], + [ + 156, + "" + ], + [ + 158, + "是否包邮" + ], + [ + 160, + "" + ], + [ + 164, + "" + ], + [ + 166, + "价格区间(元)" + ], + [ + 168, + "" + ], + [ + 170, + "" + ], + [ + 174, + "" + ], + [ + 176, + "品牌" + ], + [ + 184, + "" + ], + [ + 207, + "" + ], + [ + 210, + "" + ] + ], + "pages_promotion\\merch\\list.vue": [ + [ + 4, + "" + ], + [ + 15, + "" + ], + [ + 89, + "" + ], + [ + 98, + "" + ], + [ + 107, + "" + ], + [ + 119, + "" + ], + [ + 179, + "" + ], + [ + 188, + "" + ], + [ + 197, + "" + ], + [ + 209, + "" + ], + [ + 229, + "" + ], + [ + 233, + "" + ], + [ + 256, + "" + ], + [ + 259, + "" + ] + ], + "common\\js\\platform.js": [ + [ + 140, + "? _t('common.platform.wechatOfficialAccount', '微信公众号')" + ], + [ + 147, + "weapp: _t('common.platform.wechatMiniProgram', '微信小程序')," + ], + [ + 148, + "alipay: _t('common.platform.alipayMiniProgram', '支付宝小程序')," + ], + [ + 149, + "baidu: _t('common.platform.baiduMiniProgram', '百度小程序')," + ], + [ + 150, + "toutiao: _t('common.platform.toutiaoMiniProgram', '头条小程序')," + ], + [ + 151, + "qq: _t('common.platform.qqMiniProgram', 'QQ小程序')," + ], + [ + 152, + "kuaishou: _t('common.platform.kuaishouMiniProgram', '快手小程序')," + ], + [ + 153, + "jd: _t('common.platform.jdMiniProgram', '京东小程序')," + ], + [ + 154, + "'360': _t('common.platform.miniProgram360', '360小程序')," + ], + [ + 155, + "xhs: _t('common.platform.xhsMiniProgram', '小红书小程序')," + ], + [ + 156, + "harmony: _t('common.platform.harmonyService', '鸿蒙元服务')," + ], + [ + 158, + "pc: _t('common.platform.pc', 'PC端')," + ], + [ + 161, + "cachedAppTypeName = platformNameMap[platform] || _t('common.platform.unknown', '未知平台');" + ] + ], + "uni_modules\\mp-html\\components\\mp-html\\node\\node.vue": [ + [ + 4, + "" + ], + [ + 5, + "" + ], + [ + 7, + "" + ], + [ + 12, + "" + ], + [ + 27, + "" + ], + [ + 35, + "" + ], + [ + 39, + "" + ], + [ + 51, + "" + ], + [ + 71, + "" + ], + [ + 78, + "" + ], + [ + 290, + "itemList: ['保存图片']," + ], + [ + 297, + "title: '保存成功'" + ], + [ + 369, + "title: '链接已复制'" + ] + ], + "components\\yuyue-date\\yuyue-date.vue": [ + [ + 6, + "选择时间" + ], + [ + 11, + "" + ], + [ + 29, + "" + ], + [ + 42, + "" + ], + [ + 57, + "预约时间:" + ], + [ + 60, + "" + ], + [ + 63, + "" + ], + [ + 103, + "default: '已约满'" + ], + [ + 108, + "default: '可预约'" + ], + [ + 192, + "return this.disableText && this.disableText !== '已约满'" + ], + [ + 197, + "return this.undisableText && this.undisableText !== '可预约'" + ], + [ + 216, + "return ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];" + ] + ], + "components-diy\\diy-form.vue": [ + [ + 4, + "" + ], + [ + 12, + "" + ], + [ + 14, + "" + ], + [ + 22, + "" + ], + [ + 149, + "return this.mergedFormConfig.submitText || '提交';" + ], + [ + 306, + "form_name: this.value.form_name || '默认表单'" + ], + [ + 311, + "title: '提交成功'," + ], + [ + 320, + "title: res.message || '提交失败'," + ] + ], + "scripts\\test-gate.js": [ + [ + 23, + "console.error('[test-gate] 需要 node 18+(自带 --test),当前 node 版本:', process.version);" + ], + [ + 29, + "console.log('\\n[test-gate] === 运行测试文件:', path.relative(ROOT, testFile), '===');" + ], + [ + 46, + "console.error('[test-gate] tests/ 下没有任何 *.test.js 文件');" + ], + [ + 50, + "console.log('[test-gate] 找到', files.length, '个测试文件');" + ], + [ + 55, + "console.log('\\n[test-gate] =============== 汇总 ===============');" + ], + [ + 56, + "console.log(' 总数:', results.length);" + ], + [ + 57, + "console.log(' 通过:', results.length - failed.length);" + ], + [ + 58, + "console.log(' 失败:', failed.length);" + ], + [ + 60, + "console.log(' 失败文件:');" + ], + [ + 62, + "console.log(' -', r.file, '(退出码', r.status + ')');" + ], + [ + 64, + "console.log('\\n[test-gate] ❌ 测试未全部通过,不允许继续');" + ], + [ + 67, + "console.log('\\n[test-gate] ✅ 全部测试通过');" + ] + ], + "components\\ns-newform\\ns-newform.vue": [ + [ + 4, + "" + ], + [ + 22, + "" + ], + [ + 39, + "" + ], + [ + 60, + "" + ], + [ + 88, + "" + ], + [ + 116, + "" + ], + [ + 135, + "" + ], + [ + 157, + "" + ], + [ + 196, + "" + ], + [ + 218, + "" + ], + [ + 257, + "" + ] + ], + "pages_promotion\\point\\list.vue": [ + [ + 50, + "活动规则" + ], + [ + 55, + "储值赚积分" + ], + [ + 59, + "兑换记录" + ], + [ + 63, + "积分抽奖" + ], + [ + 67, + "积分明细" + ], + [ + 228, + "" + ], + [ + 255, + "" + ], + [ + 258, + "" + ], + [ + 275, + "name: '积分换好物'" + ], + [ + 279, + "name: '积分换券'" + ], + [ + 283, + "name: '积分换红包'" + ] + ], + "scripts\\i18n-check.js": [ + [ + 47, + "if (trimmed.startsWith(' " + ], + [ + 175, + "console.log(` 扫描文件数: ${srcFiles.length}`);" + ], + [ + 176, + "console.log(` 含中文的文件数: ${hardcodeResults.length}`);" + ], + [ + 177, + "console.log(` 中文字符串命中数: ${totalCnHits}`);" + ], + [ + 180, + "console.log(`\\n ⚠️ 疑似硬编码中文 TOP 20:`);" + ], + [ + 186, + "console.log(` ... 还有 ${hardcodeResults.length - 20} 个文件`);" + ], + [ + 188, + "console.log(`\\n 💡 提示:这是粗略扫描,包含注释/默认值等误报,仅供参考`);" + ], + [ + 190, + "console.log(' ✅ 未发现硬编码中文(粗略扫描)');" + ], + [ + 195, + "console.log(' 检查完成');" + ], + [ + 200, + "console.log('\\n ❌ 存在差异,请修复后再提交');" + ], + [ + 203, + "console.log('\\n ✅ 全部通过');" + ] + ], + "pages_order\\detail.vue": [ + [ + 3, + "" + ], + [ + 27, + "" + ], + [ + 59, + "" + ], + [ + 122, + "" + ], + [ + 205, + "" + ], + [ + 334, + "" + ], + [ + 339, + "联系客服" + ], + [ + 480, + "" + ], + [ + 619, + "" + ], + [ + 625, + "" + ] + ], + "pages_promotion\\point\\payment.vue": [ + [ + 18, + "" + ], + [ + 20, + "" + ], + [ + 29, + "" + ], + [ + 78, + "" + ], + [ + 146, + "" + ], + [ + 161, + "" + ], + [ + 214, + "" + ], + [ + 263, + "" + ], + [ + 304, + "" + ], + [ + 310, + "" + ] + ], + "uni_modules\\uni-calendar\\components\\uni-calendar\\uni-calendar.vue": [ + [ + 6, + "取消" + ], + [ + 7, + "确定" + ], + [ + 15, + "{{ (nowDate.year || '') + '年' + (nowDate.month || '') + '月' }}" + ], + [ + 27, + "日" + ], + [ + 28, + "一" + ], + [ + 29, + "二" + ], + [ + 30, + "三" + ], + [ + 31, + "四" + ], + [ + 32, + "五" + ], + [ + 33, + "六" + ] + ], + "components-diy\\diy-category.vue": [ + [ + 12, + "商品分类" + ], + [ + 15, + "" + ], + [ + 80, + "" + ], + [ + 109, + "" + ], + [ + 163, + "总计:" + ], + [ + 169, + "" + ] + ], + "pages\\index\\index.vue": [ + [ + 5, + "" + ], + [ + 8, + "{{ diyLoadErrorMsg || '网络异常,请稍后重试' }}" + ], + [ + 61, + "跳过" + ], + [ + 93, + "" + ], + [ + 98, + "" + ], + [ + 109, + "" + ], + [ + 118, + "" + ], + [ + 154, + "" + ], + [ + 158, + "" + ] + ], + "pages_promotion\\point\\detail.vue": [ + [ + 78, + "" + ], + [ + 90, + "" + ], + [ + 109, + "" + ], + [ + 112, + "" + ], + [ + 135, + "" + ], + [ + 155, + "" + ], + [ + 180, + "" + ], + [ + 196, + "" + ], + [ + 219, + "" + ] + ], + "pages_tool\\contact\\contact.vue": [ + [ + 7, + "" + ], + [ + 39, + "" + ], + [ + 74, + "" + ], + [ + 91, + "" + ], + [ + 107, + "" + ], + [ + 120, + "" + ], + [ + 134, + "" + ], + [ + 165, + "" + ], + [ + 184, + "" + ] + ], + "components-diy\\diy-article.vue": [ + [ + 4, + "" + ], + [ + 6, + "" + ], + [ + 21, + "" + ], + [ + 55, + "" + ], + [ + 90, + "" + ] + ], + "components-diy\\diy-category-item.vue": [ + [ + 80, + "" + ], + [ + 114, + "" + ], + [ + 118, + "" + ], + [ + 168, + "立即购买" + ], + [ + 172, + "选规格" + ], + [ + 194, + "立即购买" + ], + [ + 203, + "" + ], + [ + 11, + "" + ], + [ + 21, + "" + ], + [ + 24, + "" + ], + [ + 32, + "" + ], + [ + 42, + "" + ], + [ + 208, + "title: '视频号仅支持在微信小程序环境中播放'," + ], + [ + 218, + "console.error('打开视频号失败', err);" + ] + ], + "pages_promotion\\point\\goods_list.vue": [ + [ + 4, + "" + ], + [ + 14, + "" + ], + [ + 78, + "" + ], + [ + 92, + "" + ], + [ + 96, + "" + ], + [ + 107, + "" + ], + [ + 128, + "" + ], + [ + 131, + "" + ] + ], + "pages_tool\\member\\info_edit.vue": [ + [ + 4, + "" + ], + [ + 15, + "" + ], + [ + 25, + "" + ], + [ + 35, + "" + ], + [ + 52, + "" + ], + [ + 64, + "" + ], + [ + 108, + "" + ], + [ + 128, + "" + ] + ], + "scripts\\fix-longpress-syntax.js": [ + [ + 56, + "console.log(`✓ 已修复: ${filePath}`)" + ], + [ + 84, + "console.error(`✗ 无法读取目录: ${dir}`)" + ], + [ + 92, + "console.log('开始修复 show-menu-by-longpress 语法...\\n')" + ], + [ + 104, + "console.log(`\\n修复完成!`)" + ], + [ + 105, + "console.log(`总共扫描: ${totalFiles} 个文件`)" + ], + [ + 106, + "console.log(`成功修复: ${fixedFiles} 个文件`)" + ], + [ + 107, + "console.log(`\\n正确的用法:`);" + ], + [ + 109, + "console.log(` (如果需要动态控制)`);" + ] + ], + "components\\mescroll\\mescroll-body.vue": [ + [ + 11, + "" + ], + [ + 20, + "" + ], + [ + 23, + "" + ], + [ + 26, + "" + ], + [ + 29, + "" + ], + [ + 34, + "" + ], + [ + 39, + "" + ] + ], + "components\\mescroll\\mescroll-uni.vue": [ + [ + 20, + "" + ], + [ + 29, + "" + ], + [ + 32, + "" + ], + [ + 35, + "" + ], + [ + 38, + "" + ], + [ + 44, + "" + ], + [ + 50, + "" + ] + ], + "components-diy\\diy-channel-video.vue": [ + [ + 3, + "" + ], + [ + 8, + "" + ], + [ + 16, + "" + ], + [ + 19, + "" + ], + [ + 24, + "" + ], + [ + 36, + "" + ], + [ + 39, + "" + ] + ], + "pages_order\\detail_point.vue": [ + [ + 3, + "" + ], + [ + 21, + "" + ], + [ + 58, + "" + ], + [ + 85, + "" + ], + [ + 90, + "联系客服" + ], + [ + 96, + "" + ], + [ + 133, + "" + ] + ], + "pages_tool\\member\\point.vue": [ + [ + 53, + "做任务赚积分" + ], + [ + 57, + "每日签到" + ], + [ + 58, + "连续签到可获得更多积分" + ], + [ + 60, + "去签到" + ], + [ + 65, + "购买商品" + ], + [ + 66, + "购买商品可获得积分" + ], + [ + 68, + "去下单" + ] + ], + "scripts\\harmony.patch.js": [ + [ + 19, + "console.log('site.js 已同步到 stub 项目源码目录');" + ], + [ + 22, + "console.error('同步 site.js 到 stub 失败:', err);" + ], + [ + 60, + "console.warn('[harmony.patch] ⚠️ harmony-component-placeholder 注入失败:', e.message);" + ], + [ + 63, + "console.log('HarmonyOS 资源拷贝完成');" + ], + [ + 65, + "console.error('发生错误:', err);" + ], + [ + 106, + "console.log(`ZIP 文件路径: ${zipFilePath}`);" + ], + [ + 110, + "console.log('跳过创建 ZIP 文件和打开目录');" + ] + ], + "scripts\\mp-weixin.patch.js": [ + [ + 25, + "console.log(`已拷贝: ${srcPath} -> ${destPath}`);" + ], + [ + 27, + "console.warn(`源文件不存在,跳过复制: ${srcPath}`);" + ], + [ + 35, + "console.log(`已拷贝: ${srcSitePath} -> ${destDir}`);" + ], + [ + 37, + "console.error(`源文件不存在: ${srcSitePath}`);" + ], + [ + 41, + "console.error('发生错误:', err);" + ], + [ + 67, + "console.log(`ZIP 文件路径: ${zipFilePath}`);" + ], + [ + 70, + "console.log('跳过创建 ZIP 文件和打开目录');" + ] + ], + "store\\index.js": [ + [ + 524, + "console.error('设置主题颜色失败', e);" + ], + [ + 580, + "console.log('[theme] preset 暂不可用,等待远端 preset 拉完:', finalPresetName);" + ], + [ + 586, + "console.log('[theme] 远端 preset 到达,重应用成功:', finalPresetName);" + ], + [ + 588, + "console.warn('[theme] 远端 preset 到达后仍找不到:', finalPresetName);" + ], + [ + 599, + "console.log('[theme] 远端 preset 补充,重应用:', finalPresetName);" + ], + [ + 605, + "console.error('[theme] applyThemeWithRemote 失败,回退到纯预设:', e);" + ], + [ + 615, + "console.error('[theme] 纯预设回退也失败:', e2);" + ] + ], + "common\\js\\navigation.js": [ + [ + 33, + "console.warn('获取导航栏高度失败,使用默认值:', error)" + ], + [ + 49, + "console.warn('未找到 uni-page-head 元素或高度为0')" + ], + [ + 69, + "console.error('微信小程序高度计算失败:', error)" + ], + [ + 95, + "console.error('App端高度获取失败:', error)" + ], + [ + 213, + "console.error('处理导航请求失败:', error)" + ], + [ + 248, + "console.error('导航处理错误:', {" + ] + ], + "components\\payment\\payment.vue": [ + [ + 3, + "" + ], + [ + 44, + "" + ], + [ + 66, + "name: '支付宝支付'," + ], + [ + 72, + "name: '微信支付'," + ], + [ + 82, + "name: '微信支付'," + ], + [ + 91, + "name: '支付宝支付'," + ] + ], + "components-diy\\diy-bottom-nav.vue": [ + [ + 2, + "" + ], + [ + 7, + "" + ], + [ + 46, + "" + ] + ], + "scripts\\add-longpress-to-images.js": [ + [ + 74, + "console.log(`✓ 已处理: ${filePath}`)" + ], + [ + 106, + "console.log('开始处理图片长按识别功能...\\n')" + ], + [ + 118, + "console.log(`\\n处理完成!`)" + ], + [ + 119, + "console.log(`总共扫描: ${totalFiles} 个文件`)" + ], + [ + 120, + "console.log(`成功处理: ${processedFiles} 个文件`)" + ], + [ + 121, + "console.log(`\\n提示:如需排除某些图片,请在 标签添加 class=\"no-longpress\"`)" + ] + ], + "scripts\\create-zip.js": [ + [ + 13, + "throw new Error(`源目录不存在: ${sourceDir}`);" + ], + [ + 22, + "console.warn('无法获取 Git 提交 ID,使用 unknown 代替');" + ], + [ + 47, + "console.log(`执行命令: ${command}`);" + ], + [ + 51, + "console.warn('警告:', stderr);" + ], + [ + 54, + "console.log(`✅ ZIP文件创建完成: ${zipFileName}`);" + ], + [ + 57, + "throw new Error(`系统命令执行失败: ${error.message}`);" + ] + ], + "scripts\\harmony-component-placeholder.js": [ + [ + 115, + "console.error('用法: node scripts/harmony-component-placeholder.js <产物根目录>');" + ], + [ + 119, + "console.error(`目录不存在: ${rootDir}`);" + ], + [ + 123, + "console.log(`[HarmonyPlaceholder] 扫描根: ${rootDir}`);" + ], + [ + 124, + "console.log(`[HarmonyPlaceholder] 页面清单大小: ${pageSet.size}, diy 组件清单大小: ${DIY_LIST.length}`);" + ], + [ + 133, + "console.log(` ✓ 注入: ${rel}`);" + ], + [ + 148, + "console.log(`[HarmonyPlaceholder] 注入: ${stats.injected}, 已有 placeholder: ${stats.hasPlaceholder}, 跳过 (页面): ${stats.pageSkip}, 无 diy: ${stats.noDiy}, 无 usingComp" + ] + ], + "uni_modules\\uni-datetime-picker\\components\\uni-datetime-picker\\time-picker.vue": [ + [ + 39, + "" + ], + [ + 62, + "" + ], + [ + 112, + "title: '日期和时间'," + ], + [ + 194, + "this.title = '日期'" + ], + [ + 198, + "this.title = '时间'" + ], + [ + 202, + "this.title = '日期和时间'" + ] + ], + "common\\js\\util-navigate.js": [ + [ + 179, + "console.warn('[normalizeUrl] scheme 后有多余的 /:', urlString);" + ], + [ + 232, + "console.warn('[normalizeUrl] host 含非法字符:', urlString);" + ], + [ + 507, + "console.warn('[navigate] H5 环境下没有 window.location');" + ], + [ + 517, + "console.warn('[navigate] H5 跳转失败:', e);" + ], + [ + 525, + "console.warn('[navigate] 非 H5 环境下没有 uni 全局');" + ] + ], + "components\\mescroll\\mescroll-uni-option.js": [ + [ + 8, + "textInOffset: Lang.lang('components.mescroll.down.textInOffset', '下拉刷新')," + ], + [ + 9, + "textOutOffset: Lang.lang('components.mescroll.down.textOutOffset', '释放更新')," + ], + [ + 10, + "textLoading: Lang.lang('components.mescroll.down.textLoading', '加载中 ...')," + ], + [ + 16, + "textLoading: Lang.lang('components.mescroll.up.textLoading', '加载中 ...')," + ], + [ + 32, + "tip: Lang.lang('components.mescroll.empty.tip', '~ 暂无相关数据 ~')" + ] + ], + "components\\mescroll\\mescroll-uni.js": [ + [ + 63, + "textInOffset: __i18n('components.mescroll.down.textInOffset', '下拉刷新')," + ], + [ + 64, + "textOutOffset: __i18n('components.mescroll.down.textOutOffset', '释放更新')," + ], + [ + 65, + "textLoading: __i18n('components.mescroll.down.textLoading', '加载中 ...')," + ], + [ + 98, + "textLoading: __i18n('components.mescroll.up.textLoading', '加载中 ...')," + ], + [ + 123, + "tip: __i18n('components.mescroll.empty.tip', '~ 暂无相关数据 ~')," + ] + ], + "components\\mescroll\\my-list-mescroll.vue": [ + [ + 2, + "" + ], + [ + 3, + "" + ], + [ + 17, + "" + ], + [ + 44, + "tip: '~ 空空如也 ~'," + ], + [ + 45, + "btnText: '去看看'" + ] + ], + "components\\ns-login\\ns-login.vue": [ + [ + 3, + "" + ], + [ + 43, + "" + ], + [ + 57, + "" + ], + [ + 75, + "" + ], + [ + 125, + "" + ] + ], + "components\\wxwork-contact\\wxwork-contact.vue": [ + [ + 12, + "" + ], + [ + 111, + "console.error('企业微信SDK初始化失败');" + ], + [ + 115, + "console.error('初始化企业微信SDK失败:', error);" + ], + [ + 170, + "console.error('跳转企业微信失败:', err);" + ], + [ + 187, + "console.error('打开用户资料失败:', err);" + ] + ], + "pages_tool\\member\\coupon.vue": [ + [ + 10, + "全部" + ], + [ + 11, + "满减券" + ], + [ + 12, + "折扣券" + ], + [ + 13, + "无门槛券" + ], + [ + 47, + "{{ item.use_store==='all'?'适用门店:全部门店': '适用门店:'+item.use_store_name}}" + ] + ], + "scripts\\inject-placeholder.js": [ + [ + 12, + "console.warn(`[InjectPlaceholder] ⚠️ ${DIY_DIR} 不存在, diy 组件清单为空`);" + ], + [ + 231, + "console.error('[InjectPlaceholder] ❌ 读取 pages.json 失败:', e.message);" + ], + [ + 310, + "console.log(`[InjectPlaceholder] ✅ 展开 ${expanded}, 注入 ${added}, 移除 ${removed}, 跳过 ${skipped}, 无变化 ${noChange}, diy 组件 ${DIY_LIST.length}`);" + ], + [ + 312, + "console.error('[InjectPlaceholder] ❌ 写回 pages.json 失败:', e.message);" + ], + [ + 316, + "console.log(`[InjectPlaceholder] ✓ 无需改动, diy 组件 ${DIY_LIST.length}`);" + ] + ], + "components\\l-time\\time.js": [ + [ + 96, + "return \"刚刚\"" + ], + [ + 102, + "return Math.floor(timer / 60) + \"分钟前\"" + ], + [ + 110, + "return Math.floor(timer / 60 / 60) + \"小时前\";" + ], + [ + 119, + "return Math.ceil(timer / 86400) + \"天前\"" + ] + ], + "components\\ns-contact\\ns-contact.vue": [ + [ + 12, + "" + ], + [ + 18, + "" + ], + [ + 23, + "" + ], + [ + 42, + "" + ] + ], + "components-diy\\diy-goods-list.vue": [ + [ + 69, + "" + ], + [ + 78, + "" + ], + [ + 87, + "" + ], + [ + 100, + "" + ] + ], + "pages_goods\\cart.vue": [ + [ + 213, + "" + ], + [ + 354, + "" + ], + [ + 357, + "" + ], + [ + 362, + "" + ] + ], + "pages_order\\list.vue": [ + [ + 74, + "" + ], + [ + 152, + "合并付款" + ], + [ + 155, + "" + ], + [ + 166, + "" + ] + ], + "pages_promotion\\fenxiao\\goods_list.vue": [ + [ + 63, + "" + ], + [ + 106, + "" + ], + [ + 127, + "" + ], + [ + 130, + "" + ] + ], + "pages_promotion\\fenxiao\\withdraw_list.vue": [ + [ + 45, + "text: '已转账'" + ], + [ + 49, + "text: '待审核'" + ], + [ + 53, + "text: '已审核'" + ], + [ + 57, + "text: '已拒绝'" + ] + ], + "pages_tool\\file-preview\\file-preview.vue": [ + [ + 12, + "" + ], + [ + 18, + "" + ], + [ + 24, + "" + ], + [ + 30, + "" + ] + ], + "pages_tool\\goods\\coupon.vue": [ + [ + 11, + "" + ], + [ + 51, + "{{ item.use_store==='all'?'适用门店:全部门店': '适用门店:'+item.use_store_name}}" + ], + [ + 75, + "" + ], + [ + 78, + "" + ] + ], + "pages_tool\\member\\account_edit.vue": [ + [ + 110, + "if (this.payList.length == 1 && this.payList[0] == '银行卡') {" + ], + [ + 127, + "case '银行卡':" + ], + [ + 130, + "case '支付宝':" + ], + [ + 133, + "case '微信零钱':" + ] + ], + "pages_tool\\member\\index.vue": [ + [ + 29, + "" + ], + [ + 51, + "" + ], + [ + 56, + "" + ], + [ + 71, + "" + ] + ], + "pages_tool\\notice\\list.vue": [ + [ + 23, + "" + ], + [ + 26, + "" + ], + [ + 80, + "title: '公告'," + ], + [ + 88, + "var title = '公告';" + ] + ], + "pages_tool\\order\\refund_detail.vue": [ + [ + 10, + "" + ], + [ + 40, + "" + ], + [ + 49, + "" + ], + [ + 136, + "联系客服" + ] + ], + "pages_tool\\recharge\\order_list.vue": [ + [ + 24, + "goods-name color-tip \" v-if=\"orderItem.point > 0 || orderItem.growth > 0\">赠送:" + ], + [ + 25, + " 0\">{{ orderItem.point }}积分" + ], + [ + 27, + " 0\">{{ orderItem.growth }}成长值" + ], + [ + 29, + "优惠券x{{ orderItem.coupon_id.split(',').length }}" + ] + ], + "scripts\\inject-nav-title.js": [ + [ + 103, + "console.error('[InjectNavTitle] ❌ 读取 pages.json 失败:', e.message);" + ], + [ + 152, + "console.log(`[InjectNavTitle] ✅ 注入 ${injected}, 跳过(已存在) ${skipped}`);" + ], + [ + 154, + "console.error('[InjectNavTitle] ❌ 写回 pages.json 失败:', e.message);" + ], + [ + 158, + "console.log(`[InjectNavTitle] ✓ 无需改动, 跳过(已存在) ${skipped}`);" + ] + ], + "uni_modules\\uni-count-down\\components\\uni-count-down\\uni-count-down.vue": [ + [ + 11, + " 0\" :style=\"{ color: splitorColor }\" class=\"uni-countdown__splitor day\" :class=\"splitorColorClass\">{{ showColon ? '天' : '天' }}" + ], + [ + 19, + "{{ showColon ? ':' : '时' }}" + ], + [ + 27, + "{{ showColon ? ':' : '分' }}" + ], + [ + 35, + "秒" + ] + ], + "common\\js\\util.js": [ + [ + 525, + "console.error('[navigate] tabBar 跳转失败:', cleanUrl, err);" + ], + [ + 1029, + "console.log(`外部链接,跳转到webview`, url);" + ], + [ + 1419, + "else reject(err || new Error('复制失败'));" + ] + ], + "components\\mescroll\\components\\mescroll-up.vue": [ + [ + 1, + "" + ], + [ + 4, + "" + ], + [ + 9, + "" + ] + ], + "components\\ns-goods-sku\\ns-goods-sku.vue": [ + [ + 134, + "" + ], + [ + 184, + "" + ], + [ + 189, + "" + ] + ], + "components\\ns-navbar\\ns-navbar.vue": [ + [ + 31, + "请输入商品名称" + ], + [ + 39, + "定位中..." + ], + [ + 41, + "附近门店" + ] + ], + "components-diy\\diy-groupbuy.vue": [ + [ + 52, + "已团{{ item.sell_num }}件" + ], + [ + 92, + ":style=\"{ color: value.theme == 'diy' ? value.saleStyle.color : '' }\">已团{{" + ], + [ + 93, + "item.sell_num }}件" + ] + ], + "components-diy\\diy-index-page.vue": [ + [ + 52, + "" + ], + [ + 105, + "" + ], + [ + 161, + "" + ] + ], + "components-diy\\diy-rubik-cube.vue": [ + [ + 3, + "" + ], + [ + 8, + "" + ], + [ + 31, + "" + ] + ], + "components-diy\\diy-video-list.vue": [ + [ + 3, + "" + ], + [ + 25, + "" + ], + [ + 54, + "" + ] + ], + "components-diy\\diy-video.vue": [ + [ + 3, + "" + ], + [ + 19, + "" + ], + [ + 35, + "" + ] + ], + "pages\\index\\public\\js\\index.js": [ + [ + 161, + "title: '版本信息'," + ], + [ + 162, + "content: `提交 ID: ${v.commitId}\\n构建时间: ${v.buildTime}\\n${Config.baseUrl}`," + ], + [ + 166, + "console.error('未获取到构建版本信息');" + ] + ], + "pages_goods\\category.vue": [ + [ + 3, + "" + ], + [ + 16, + "" + ], + [ + 19, + "" + ] + ], + "pages_promotion\\fenxiao\\withdraw_apply.vue": [ + [ + 53, + "提现" + ], + [ + 64, + "提现记录" + ], + [ + 215, + "let branchBankName = this.isBalance ? \"余额\" : this.bankAccountInfo.branch_bank_name;" + ] + ], + "pages_promotion\\merch\\merchcategory.vue": [ + [ + 7, + "" + ], + [ + 50, + "暂时没有分类哦!" + ], + [ + 59, + "" + ] + ], + "pages_tool\\agreement\\contenr.vue": [ + [ + 3, + "" + ], + [ + 8, + "" + ], + [ + 16, + "" + ] + ], + "pages_tool\\ai-chat\\index.vue": [ + [ + 3, + "" + ], + [ + 15, + "" + ], + [ + 37, + "" + ] + ], + "pages_tool\\goods\\brand.vue": [ + [ + 19, + "" + ], + [ + 22, + "" + ], + [ + 79, + "var title = '你想要的大牌都在这里';" + ] + ], + "pages_tool\\goods\\coupon_receive.vue": [ + [ + 16, + "" + ], + [ + 18, + "" + ], + [ + 33, + "" + ] + ], + "pages_tool\\goods\\search.vue": [ + [ + 11, + "" + ], + [ + 29, + "" + ], + [ + 44, + "" + ] + ], + "pages_tool\\help\\list.vue": [ + [ + 13, + "" + ], + [ + 82, + "title: '帮助中心'," + ], + [ + 90, + "var title = '帮助中心使你更加方便';" + ] + ], + "pages_tool\\index\\diy.vue": [ + [ + 36, + "" + ], + [ + 41, + "" + ], + [ + 51, + "" + ] + ], + "pages_tool\\login\\find.vue": [ + [ + 23, + "" + ], + [ + 51, + "" + ], + [ + 56, + "" + ] + ], + "pages_tool\\member\\balance.vue": [ + [ + 24, + "现金余额(元)" + ], + [ + 28, + "储值余额(元)" + ], + [ + 56, + "充值记录" + ] + ], + "pages_tool\\member\\card_log.vue": [ + [ + 3, + "" + ], + [ + 21, + "" + ], + [ + 30, + "" + ] + ], + "pages_tool\\member\\invite_friends.vue": [ + [ + 157, + "" + ], + [ + 178, + "" + ], + [ + 212, + "" + ] + ], + "pages_tool\\member\\point_detail.vue": [ + [ + 66, + "label: '全部'," + ], + [ + 94, + "this.date = tempArr[0] + '年' + tempArr[1] + '月';" + ], + [ + 111, + "return `${year}年${month}月`;" + ] + ], + "pages_tool\\pay\\cashier.vue": [ + [ + 43, + "name: '支付宝支付'," + ], + [ + 49, + "name: '微信支付'," + ], + [ + 59, + "name: '微信支付'," + ] + ], + "pages_tool\\storeclose\\storeclose.vue": [ + [ + 41, + "return '该店铺已打烊...';" + ], + [ + 45, + "return '店铺不存在';" + ], + [ + 47, + "return '店铺打烊';" + ] + ], + "scripts\\verify-p2-keys.js": [ + [ + 313, + "console.log('\\n⚠️ components-diy/diy-tab.vue 的 title 为运行时 DIY 数据,无法静态枚举,建议通过 DIY 数据规范/白名单保证。');" + ], + [ + 316, + "console.log('\\n✅ 所有可枚举的 P2 动态 key 均可解析');" + ], + [ + 319, + "console.log('\\n❌ 部分 P2 动态 key 缺失');" + ] + ], + "uni_modules\\uni-datetime-picker\\components\\uni-datetime-picker\\calendar.vue": [ + [ + 13, + "class=\"uni-calendar__header-text\">{{ (nowDate.year||'') + ' 年 ' + ( nowDate.month||'') +' 月'}}" + ], + [ + 23, + "" + ], + [ + 89, + "确认" + ] + ], + "uni_modules\\x-skeleton\\components\\x-skeleton\\x-skeleton.vue": [ + [ + 3, + "" + ], + [ + 52, + "" + ], + [ + 143, + "console.error('x-skeleton: textHeight参数不支持百分比单位');" + ] + ], + "common\\js\\config-external.js": [ + [ + 252, + "console.error(`加载页面语言包 ${lang}/${pagePath} 失败:`, error);" + ], + [ + 291, + "console.error(`加载主题 ${theme} 失败:`, error);" + ] + ], + "common\\js\\event-safety.js": [ + [ + 62, + "console.warn('matches 检查失败:', error)" + ], + [ + 86, + "console.error('事件处理错误:', error)" + ] + ], + "common\\js\\mp-share-default.js": [ + [ + 85, + "title: '小程序分享'," + ], + [ + 130, + "cfg.title = siteName || '小程序分享';" + ] + ], + "components\\mescroll\\components\\mescroll-empty.vue": [ + [ + 1, + "" + ], + [ + 140, + "item.unit = item.unit || '件';" + ] + ], + "components\\ns-payment\\ns-payment.vue": [ + [ + 3, + "" + ], + [ + 46, + "" + ] + ], + "components\\ns-select-time\\ns-select-time.vue": [ + [ + 11, + "" + ], + [ + 19, + "" + ] + ], + "components-diy\\diy-bargain.vue": [ + [ + 5, + "" + ], + [ + 24, + "" + ] + ], + "components-diy\\diy-image-nav.vue": [ + [ + 8, + "" + ], + [ + 26, + "" + ] + ], + "components-diy\\diy-map.vue": [ + [ + 9, + "一键导航" + ], + [ + 12, + "" + ] + ], + "components-diy\\diy-notes.vue": [ + [ + 20, + "#{{ item.note_type == 'goods_item' ? '单品介绍' :" + ], + [ + 21, + "'掌柜说' }}#" + ] + ], + "components-diy\\diy-notice.vue": [ + [ + 11, + "" + ], + [ + 28, + "" + ] + ], + "components-diy\\diy-text.vue": [ + [ + 230, + "" + ], + [ + 264, + "" + ] + ], + "pages_promotion\\fenxiao\\order.vue": [ + [ + 65, + "" + ] + ], + "pages_tool\\ai-chat\\components\\ai-chat-search.vue": [ + [ + 3, + "" + ], + [ + 9, + "" + ] + ], + "pages_tool\\article\\list.vue": [ + [ + 29, + "" + ], + [ + 32, + "" + ] + ], + "pages_tool\\form\\formdata.vue": [ + [ + 6, + "" + ] + ], + "pages_tool\\member\\balance_detail.vue": [ + [ + 26, + "" + ], + [ + 47, + "" + ] + ], + "pages_tool\\member\\bind_mobile.vue": [ + [ + 4, + "" + ], + [ + 20, + "" + ] + ], + "pages_tool\\member\\card_buy.vue": [ + [ + 191, + "" + ], + [ + 219, + "" + ] + ], + "pages_tool\\notice\\detail.vue": [ + [ + 11, + "" + ], + [ + 87, + "var title = '[公告]' + this.detail.title;" + ] + ], + "pages_tool\\weapp\\order_shipping.vue": [ + [ + 20, + "this.errorMsg = '缺少merchant_trade_no参数';" + ], + [ + 32, + "this.errorMsg = res.message || '未知错误';" + ] + ], + "uni_modules\\uni-nav-bar\\components\\uni-nav-bar\\uni-nav-bar.vue": [ + [ + 20, + "" + ], + [ + 25, + "" + ] + ], + "uni_modules\\uni-number-box\\components\\uni-number-box\\uni-number-box.vue": [ + [ + 137, + "title: '商品库存不足'" + ], + [ + 141, + "title: '商品最少购买' + this.min + '件'" + ] + ], + "common\\js\\auth.js": [ + [ + 13, + "console.log(`微信小程序登录`)" + ] + ], + "common\\js\\config.js": [ + [ + 19, + "console.warn('小程序发行环境下,必须配置site.js文件,不然将使用默认配置,影响小程序定制分发!');" + ] + ], + "common\\js\\goods_detail_base.js": [ + [ + 309, + "title: title + '已下单'" + ] + ], + "common\\js\\lang.js": [ + [ + 1132, + "console.error('解析语言包失败:', e, { langPath, field, locale });" + ] + ], + "common\\js\\map-wx-jssdk.js": [ + [ + 675, + "region: options.region || '全国'," + ] + ], + "common\\js\\share.js": [ + [ + 39, + "console.error('分享失败:', err);" + ] + ], + "common\\js\\splash-state.js": [ + [ + 111, + "try { uni.showLoading({ title: opts.title || '加载中...', mask: false }); } catch (e) {}" + ] + ], + "common\\js\\util-tabbar.js": [ + [ + 401, + "_log('step 5: pageTabMap miss → 进入 step 6/7');" + ] + ], + "components\\l-time\\l-time.vue": [ + [ + 40, + "if (timeVal && (timeVal.endsWith('刚刚') || timeVal.endsWith('分钟前'))) {" + ] + ], + "components\\mescroll\\components\\mescroll-down.vue": [ + [ + 1, + "" + ] + ], + "components\\mescroll\\components\\mescroll-top.vue": [ + [ + 1, + "" + ] + ], + "components\\ns-copyright\\ns-copyright.vue": [ + [ + 13, + "\"公安备案\"" + ] + ], + "components\\ns-form\\ns-form.vue": [ + [ + 30, + "" + ] + ], + "components\\ns-goods-action-icon\\ns-goods-action-icon.vue": [ + [ + 3, + "" + ] + ], + "components\\ns-loading\\ns-loading.vue": [ + [ + 16, + "default: '加载中'" + ] + ], + "components\\privacy-popup\\privacy-popup.vue": [ + [ + 102, + "console.error(\"=========低版本基础库==========\", e)" + ] + ], + "components\\splash-screen\\splash-screen.vue": [ + [ + 21, + "" + ] + ], + "components\\to-top\\to-top.vue": [ + [ + 1, + "" + ] + ], + "components-diy\\diy-hot-area.vue": [ + [ + 6, + "" + ] + ], + "components-diy\\diy-search.vue": [ + [ + 45, + "" + ] + ], + "components-diy\\js\\wechat-channel.js": [ + [ + 172, + "console.error('微信视频号错误:', err);" + ] + ], + "components-diy\\placeholder\\index.vue": [ + [ + 2, + "" + ] + ], + "pages_goods\\public\\js\\list.js": [ + [ + 40, + "text: \"购买\"," + ] + ], + "pages_goods\\_components\\goods-detail-view\\detail.js": [ + [ + 494, + "console.log('打开成功');" + ] + ], + "pages_goods\\_components\\ns-goods-promotion\\ns-goods-promotion.vue": [ + [ + 2, + "" + ] + ], + "pages_promotion\\fenxiao\\apply.vue": [ + [ + 3, + "" + ] + ], + "pages_promotion\\fenxiao\\promote.vue": [ + [ + 52, + "" + ] + ], + "pages_promotion\\fenxiao\\promote_code.vue": [ + [ + 38, + "" + ] + ], + "pages_promotion\\fenxiao\\relation.vue": [ + [ + 52, + "" + ] + ], + "pages_promotion\\point\\order_list.vue": [ + [ + 91, + "" + ] + ], + "pages_tool\\article\\detail.vue": [ + [ + 20, + "" + ] + ], + "pages_tool\\cases\\index.vue": [ + [ + 86, + "" + ] + ], + "pages_tool\\files\\list.vue": [ + [ + 40, + "" + ] + ], + "pages_tool\\help\\detail.vue": [ + [ + 10, + "" + ] + ], + "pages_tool\\member\\address_edit.vue": [ + [ + 36, + "" + ] + ], + "pages_tool\\member\\card.vue": [ + [ + 99, + "" + ] + ], + "pages_tool\\member\\collection.vue": [ + [ + 31, + "" + ] + ], + "pages_tool\\member\\contact.vue": [ + [ + 4, + "" + ] + ], + "pages_tool\\member\\level.vue": [ + [ + 136, + "" + ] + ], + "pages_tool\\member\\withdrawal_detail.vue": [ + [ + 7, + "" + ] + ], + "pages_tool\\order\\refund_batch.vue": [ + [ + 63, + "" + ] + ], + "pages_tool\\pay\\index.vue": [ + [ + 54, + "title: '未获取到支付信息!'" + ] + ], + "pages_tool\\vr\\index.vue": [ + [ + 4, + "" + ] + ], + "pages_tool\\webview\\webview.vue": [ + [ + 12, + "" + ] + ], + "pages_tool\\_components\\img-cropping\\img-cropping.vue": [ + [ + 22, + "" + ] + ], + "scripts\\verify-pages-json-cross-platform.js": [ + [ + 51, + "console.warn(`[warn] platform=${platform}: 残留未关闭 #ifdef/#ifndef ${stack.length} 个`);" + ] + ], + "uni_modules\\uni-calendar\\components\\uni-calendar\\uni-calendar-item.vue": [ + [ + 50, + "{{ weeks.isDay ? '今天' : weeks.lunar.IDayCn === '初一' ? weeks.lunar.IMonthCn : weeks.lunar.IDayCn }}" + ] + ], + "uni_modules\\uni-datetime-picker\\components\\uni-datetime-picker\\uni-datetime-picker.vue": [ + [ + 46, + "" + ] + ] +} \ No newline at end of file diff --git a/scripts/lang_audit_report_v2.json b/scripts/lang_audit_report_v2.json index 162011b..bf8133e 100644 --- a/scripts/lang_audit_report_v2.json +++ b/scripts/lang_audit_report_v2.json @@ -1,14 +1,77 @@ { - "bundle_version": "1782901586", + "bundle_version": "1784767380", "bundle_lang": "zh-cn", - "bundle_leaves": 2649, - "code_calls_total": 2677, - "code_unique_keys": 2083, - "found_in_bundle": 2082, + "bundle_leaves": 2659, + "code_calls_total": 2693, + "code_unique_keys": 2097, + "found_in_bundle": 2086, "missing_summary": { + "TRULY_MISSING": 12, "SYNTAX_BUG": 1 }, "missing_classified": { + "TRULY_MISSING": [ + { + "key": "common.buyNow", + "file": "components\\ns-goods-recommend\\ns-goods-recommend.vue", + "reason": "not in bundle, not local" + }, + { + "key": "wechatChannel.wechatMpOnly", + "file": "components-diy\\diy-channel-list.vue", + "reason": "not in bundle, not local" + }, + { + "key": "common.submitSuccess", + "file": "components-diy\\diy-form.vue", + "reason": "not in bundle, not local" + }, + { + "key": "submitFail", + "file": "components-diy\\diy-form.vue", + "reason": "not in bundle, not local" + }, + { + "key": "diy.networkError", + "file": "pages\\index\\index.vue", + "reason": "not in bundle, not local" + }, + { + "key": "versionInfo", + "file": "pages\\index\\public\\js\\index.js", + "reason": "not in bundle, not local" + }, + { + "key": "commitId", + "file": "pages\\index\\public\\js\\index.js", + "reason": "not in bundle, not local" + }, + { + "key": "buildTime", + "file": "pages\\index\\public\\js\\index.js", + "reason": "not in bundle, not local" + }, + { + "key": "diy.networkError", + "file": "pages_goods\\category.vue", + "reason": "not in bundle, not local" + }, + { + "key": "goods.detail.formEmptyTip", + "file": "pages_goods\\detail.vue", + "reason": "not in bundle, not local" + }, + { + "key": "common.buyNow", + "file": "pages_goods\\public\\js\\list.js", + "reason": "not in bundle, not local" + }, + { + "key": "pay.index.payInfoNotFound", + "file": "pages_tool\\pay\\index.vue", + "reason": "not in bundle, not local" + } + ], "SYNTAX_BUG": [ { "key": "order.list.status.", @@ -19,18 +82,8 @@ }, "placeholder_issues": [], "data_lang_issues": { - "components\\ns-payment\\ns-payment.vue": [ - "components.nsPayment.alipay" - ], "pages_promotion\\fenxiao\\apply.vue": [ "fenxiao.apply.pleaseEnterName" - ], - "pages_promotion\\merch\\public\\js\\list.js": [ - "merch.list.buyText" - ], - "pages_tool\\ai-chat\\ai-chat-message.vue": [ - "ai-chat.index.loadMoreHistory", - "ai-chat.index.defaultNickname" ] }, "hardcoded_navbar_titles": [ @@ -121,6 +174,11 @@ "common.tabBar.cart", "common.tabBar.member" ], - "local_only_keys": [], + "local_only_keys": [ + "buildTime", + "commitId", + "submitFail", + "versionInfo" + ], "bundle_only_keys_in_unique": [] } \ No newline at end of file diff --git a/scripts/scan-hardcoded-cn.py b/scripts/scan-hardcoded-cn.py new file mode 100644 index 0000000..3c5c614 --- /dev/null +++ b/scripts/scan-hardcoded-cn.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# 精筛 v2:剥离 HTML 注释和 JS 注释后,再扫硬编码中文(只关注真实会被渲染的) +import os, re, json +from collections import defaultdict + +ROOT = r'D:\projects\shop-projects\frontend' +SKIP_DIR = {'node_modules', '.unpackage', 'unpackage', '.git', 'dist', 'stub-app-projects', 'lang', 'tests', 'docs', 'scripts', 'common/mixin'} + +CN = re.compile(r'[\u4e00-\u9fff]') +LANG_RE = re.compile(r'\$lang\(') + +TPL_RE = re.compile(r']*>([\s\S]*?)', re.I) +TAG_RE = re.compile(r'<([a-zA-Z][\w-]*)\b[^>]*>([^<>\n]+)') +HTML_COMMENT_RE = re.compile(r'') + +JS_TRIGGERS = [ + re.compile(r'uni\.show(?:Toast|Modal|ActionSheet|Loading)\s*\(\s*\{[\s\S]*?\}\s*\)', re.S), + re.compile(r'wx\.show(?:Toast|Modal|ActionSheet|Loading)\s*\(\s*\{[\s\S]*?\}\s*\)', re.S), + re.compile(r'\btitle\s*:\s*[\'"]([^\'"]*?)[\'"]'), + re.compile(r'\bcontent\s*:\s*[\'"]([^\'"]*?)[\'"]'), + re.compile(r'\btext\s*:\s*[\'"]([^\'"]*?)[\'"]'), + re.compile(r'\bplaceholder\s*:\s*[\'"]([^\'"]*?)[\'"]'), + re.compile(r'\balert\s*\(\s*[\'"]([^\'"]*?)[\'"]'), + re.compile(r'\bconfirm\s*\(\s*[\'"]([^\'"]*?)[\'"]'), +] + +def strip_all_comments(text, is_vue=False): + text = HTML_COMMENT_RE.sub(lambda m: ' ' * len(m.group()), text) + text = re.sub(r'/\*[\s\S]*?\*/', lambda m: ' ' * len(m.group()), text) + text = re.sub(r'//[^\n]*', lambda m: ' ' * len(m.group()), text) + return text + +results = defaultdict(list) + +for dirpath, dirnames, filenames in os.walk(ROOT): + if any(s in dirpath.replace('\\', '/') for s in SKIP_DIR): + continue + for fn in filenames: + if not fn.endswith(('.vue', '.js')): + continue + fp = os.path.join(dirpath, fn) + rel = os.path.relpath(fp, ROOT).replace('\\', '/') + try: + content = open(fp, 'r', encoding='utf-8').read() + except UnicodeDecodeError: + continue + + stripped = strip_all_comments(content) + + if fn.endswith('.vue'): + for m in TPL_RE.finditer(stripped): + tpl = m.group(1) + for tag in TAG_RE.finditer(tpl): + inner = tag.group(2).strip() + if CN.search(inner) and not LANG_RE.search(inner): + line_no = stripped[:m.start(1) + tpl.find(inner)].count('\n') + 1 + results[rel].append({'kind': 'template', 'tag': tag.group(1), 'text': inner[:160], 'line': line_no}) + + for trig in JS_TRIGGERS: + for m in trig.finditer(stripped): + text = (m.group(1) if m.lastindex else m.group(0)) + if not CN.search(text): + continue + if LANG_RE.search(text): + continue + line_no = stripped[:m.start()].count('\n') + 1 + results[rel].append({'kind': 'js-text', 'match': text[:160], 'line': line_no}) + +ranked = sorted(results.items(), key=lambda kv: -len(kv[1])) +print(f'total files: {len(ranked)}') +print(f'total hits: {sum(len(v) for v in ranked)}') + +out = [] +for f, hits in ranked[:60]: + out.append({'file': f, 'hits': len(hits), 'samples': hits[:3]}) +print(json.dumps(out, ensure_ascii=False, indent=2)) + +with open(os.path.join(ROOT, 'scripts', 'hardcoded_cn_real.json'), 'w', encoding='utf-8') as f: + json.dump({k: v for k, v in ranked}, f, ensure_ascii=False, indent=2) +print('saved hardcoded_cn_real.json') \ No newline at end of file diff --git a/tests/lang.test.js b/tests/lang.test.js new file mode 100644 index 0000000..7daae7d --- /dev/null +++ b/tests/lang.test.js @@ -0,0 +1,493 @@ +/** + * lang.js 单元测试($lang 解析器) + * + * 运行方式: + * node --test tests/lang.test.js + * + * 测试覆盖(6 大维度): + * 1. 基础解析: 命中、未命中、默认值兜底、生产环境/开发环境返回差异 + * 2. 命名参数: {reason} / {count} / {unit} 多参数、占位符安全性 + * 3. 位置参数: {0} / {1} 兼容老式调用 + * 4. 嵌套命名空间: diy.loadFailTpl / wechatChannel.wechatMpOnly / common.tabBar.home + * 5. 占位符替换: 多次替换、正则元字符、__proto__/constructor/prototype 过滤 + * 6. 实际业务 key: loadFailTpl (com.js 下 diy)、tabBar.* 翻译对齐、common.buyNow vs common.submitSuccess + * + * 实现策略: + * lang.js 是 Vue 插件,挂在 Vue.prototype 上,依赖 store / uni / Config, + * 无法直接 import 跑测试。本测试按 util.test.js 的同款做法: + * 把要测的纯函数 (applyLangParams + 简化版 lang() 解析流程) 从 lang.js + * 源码里"剪切"出来放到本测试文件,与 lang.js 保持同步。 + * 这样不依赖任何测试框架,任何 node 版本 (>=18) 都能跑通。 + */ + +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const path = require('path'); + +// ============================================================= +// 被测源码区块(与 common/js/lang.js 保持同步) +// ============================================================= + +/** 与 lang.js 中 FORBIDDEN_KEYS 一致 */ +const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']); + +/** + * @see lang.js#applyLangParams + * 命名参数 + 位置参数 替换 + */ +function applyLangParams(value, args) { + if (args.length === 0) return value; + let result = String(value); + + const firstArg = args[0]; + const isNamed = firstArg !== null && typeof firstArg === 'object' && !Array.isArray(firstArg); + + if (isNamed) { + const map = firstArg; + for (const key in map) { + if (!Object.prototype.hasOwnProperty.call(map, key)) continue; + if (FORBIDDEN_KEYS.has(key)) continue; + const safeKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + result = result.replace(new RegExp('\\{' + safeKey + '\\}', 'g'), String(map[key])); + } + } else { + for (let i = 0; i < args.length; i++) { + result = result.replace(new RegExp('\\{' + i + '\\}', 'g'), String(args[i])); + } + } + return result; +} + +/** + * 简化版 lang() 解析流程(模拟 lang.js 主流程的"按 . 切分 + 逐层查找 + 默认值 + 占位符替换"核心) + * + * 与 lang.js 主流程差异: + * - 不处理 route 缓存、_mergedLangCache、_fieldResultCache 等(那些是性能优化,不影响语义) + * - 不处理 overrideCache(那是远端覆写机制,需要单独 mock) + * - 不处理递归保护、isRemoteOnly 短路(那是运行时 hook) + * - 但"按 . 切分 → 逐层查找 → 未命中走默认值 → 占位符替换" 这一核心路径与 lang.js 完全一致 + * + * @param {Object} mergedLang 合并后的语言包对象(顶层已是 common namespace) + * @param {string} field 字段路径,如 'diy.loadFailTpl' + * @param {...any} args 可选参数(命名参数或位置参数) + * @returns {string} + */ +function fakeLang(mergedLang, field, ...args) { + const hasDefault = args.length > 0 && typeof args[0] === 'string' && args[0] !== ''; + + const arr = field.split('.'); + let value; + if (arr.length > 1) { + let temp = mergedLang; + let found = true; + for (const key of arr) { + if (temp && typeof temp === 'object' && temp[key] !== undefined) { + temp = temp[key]; + } else { + found = false; + break; + } + } + value = found && typeof temp === 'string' ? temp : field; + } else { + const raw = mergedLang[field]; + value = raw !== undefined && typeof raw === 'string' ? raw : field; + } + + // 未命中 → 默认值兜底 + if (value === field) { + if (hasDefault) return args[0]; + return ''; // 生产环境返回空串(对齐 lang.js 的 __DEV__ === false 分支) + } + + // 占位符替换 + if (args.length > 0) { + value = applyLangParams(value, args); + } + + return value; +} + +/** + * 直接 require 实际语言包,确保我们测的就是项目里那份 zh-cn / en-us + */ +const zh = require(path.resolve(__dirname, '..', 'lang', 'zh-cn', 'common.js')).lang; +const en = require(path.resolve(__dirname, '..', 'lang', 'en-us', 'common.js')).lang; + +// ============================================================= +// 1. 基础解析:命中 / 未命中 / 默认值 +// ============================================================= + +test('基础解析: 命中已有 key 返回翻译结果', () => { + assert.strictEqual( + fakeLang(zh, 'diy.retry'), + '重试' + ); + assert.strictEqual( + fakeLang(en, 'diy.retry'), + 'Retry' + ); +}); + +test('基础解析: 中英对照 一致性', () => { + const pairs = [ + ['diy.retry', '重试', 'Retry'], + ['diy.noDiyData', '未配置自定义页面数据', 'Custom page data not configured'], + ['customer.weChatKefu', '微信客服', 'WeChat Customer Service'], + ]; + for (const [key, zhExpected, enExpected] of pairs) { + assert.strictEqual(fakeLang(zh, key), zhExpected, `zh ${key}`); + assert.strictEqual(fakeLang(en, key), enExpected, `en ${key}`); + } +}); + +test('基础解析: 未命中且无默认值 → 生产环境返回空串', () => { + assert.strictEqual( + fakeLang(zh, 'totally.fake.missing.key'), + '', + '未命中且无默认值时,生产环境(本测试视角)返回空串,避免展示 raw key' + ); +}); + +test('基础解析: 未命中但有默认值 → 返回默认值', () => { + assert.strictEqual( + fakeLang(zh, 'totally.fake.missing.key', '兜底文案'), + '兜底文案' + ); + assert.strictEqual( + fakeLang(en, 'totally.fake.missing.key', 'Fallback Text'), + 'Fallback Text' + ); +}); + +test('基础解析: 默认值是空串时被忽略(对齐 lang.js hasDefault 判断)', () => { + // lang.js: hasDefault = typeof args[1] === 'string' && args[1] !== '' + // args[0] 是 string '' 时不算默认值 + assert.strictEqual( + fakeLang(zh, 'totally.fake.missing.key', ''), + '' + ); +}); + +test('基础解析: 默认值必须是字符串,非字符串不走默认值兜底', () => { + // args[0] = { foo: 1 } 是命名参数,不走默认值路径 + // 这里用一个不存在 key 验证:返回空串(因为命名参数替换失败) + assert.strictEqual( + fakeLang(zh, 'totally.fake.missing.key', { foo: 'bar' }), + '' + ); +}); + +test('基础解析: 中间路径上某节点非对象(命中 leaf 是 number/boolean)', () => { + // 模拟 leaf 不是 string 的场景 + const stub = { foo: { bar: 123 } }; + // lang.js 实际行为:中间节点上 leaf 不是 string 时,value 设为 field, + // 但因为没有默认值兜底且后续判定 value === field → 返回空串 + assert.strictEqual( + fakeLang(stub, 'foo.bar'), + '', + '非 string 类型 leaf 当成未命中,且无默认值 → 返回空串' + ); + // 给默认值时返回默认值 + assert.strictEqual( + fakeLang(stub, 'foo.bar', 'fallback'), + 'fallback' + ); +}); + +// ============================================================= +// 2. 命名参数:本次修复的核心场景 +// ============================================================= + +test('命名参数: diy.loadFailTpl + { reason } 是核心场景', () => { + // 注意:zh-cn 实际文案是中文标点「:」,不是英文「:」 + assert.strictEqual( + fakeLang(zh, 'diy.loadFailTpl', { reason: '网络连接超时' }), + '页面数据加载失败:网络连接超时,请稍后重试' + ); + assert.strictEqual( + fakeLang(en, 'diy.loadFailTpl', { reason: 'Network timeout' }), + 'Failed to load page data: Network timeout, please try again later' + ); +}); + +test('命名参数: 多参数同时替换', () => { + // goods.detail.purchaseLimit = '该商品每人限购{count}{unit}' + const zhGoods = require(path.resolve(__dirname, '..', 'lang', 'zh-cn', 'goods', 'detail.js')).lang; + const enGoods = require(path.resolve(__dirname, '..', 'lang', 'en-us', 'goods', 'detail.js')).lang; + + assert.strictEqual( + fakeLang(zhGoods, 'purchaseLimit', { count: 5, unit: '件' }), + '该商品每人限购5件' + ); + assert.strictEqual( + fakeLang(enGoods, 'purchaseLimit', { count: 5, unit: 'pcs' }), + 'Limit 5 pcs per person', + 'en-us 文案是 "Limit {count} {unit} per person",顺序替换 {count}=5 / {unit}=pcs' + ); + + // 多参数 + 多占位符 替换 + assert.strictEqual( + fakeLang(zhGoods, 'purchaseLimitWithPurchased', { count: 5, unit: '件', purchased: 2 }), + '该商品每人限购5件,您已购买了2件' + ); +}); + +test('命名参数: 多次出现的占位符全部替换', () => { + const stub = { msg: '欢迎{user},{user} 你好' }; + assert.strictEqual( + fakeLang(stub, 'msg', { user: '张三' }), + '欢迎张三,张三 你好' + ); +}); + +test('命名参数: 占位符的值含正则元字符不会注入', () => { + const stub = { msg: '下载 {url} 完成' }; + assert.strictEqual( + fakeLang(stub, 'msg', { url: 'a.b+c*d(e)f' }), + '下载 a.b+c*d(e)f 完成' + ); + assert.strictEqual( + fakeLang(stub, 'msg', { url: '$1\\2{3}' }), + '下载 $1\\2{3} 完成' + ); +}); + +test('命名参数: 占位符的值含 .*+?^${}()|[]\\ 等全部正确转义', () => { + const stub = { msg: '前缀 {x} 后缀' }; + const r1 = fakeLang(stub, 'msg', { x: '$1' }); + assert.strictEqual(r1, '前缀 $1 后缀', 'value 端的 $1 不应被吃成反向引用'); + + const r2 = fakeLang(stub, 'msg', { x: '\\n' }); + assert.strictEqual(r2, '前缀 \\n 后缀'); +}); + +test('命名参数: 危险 key __proto__/constructor/prototype 被过滤', () => { + const stub = { msg: 'hello {__proto__} {constructor} {prototype} world' }; + const out = fakeLang(stub, 'msg', { __proto__: 'A', constructor: 'B', prototype: 'C', safe: 'OK' }); + assert.strictEqual(out, 'hello {__proto__} {constructor} {prototype} world'); +}); + +test('命名参数: 没有占位符的纯文案 + 命名参数(多余参数被忽略)', () => { + const stub = { msg: '你好' }; + assert.strictEqual( + fakeLang(stub, 'msg', { foo: 'bar' }), + '你好' + ); +}); + +test('命名参数: 对象参数里没提供的占位符 → 字面残留', () => { + const stub = { msg: '你好 {name},你 {count} 岁' }; + assert.strictEqual( + fakeLang(stub, 'msg', { name: 'Tom' }), + '你好 Tom,你 {count} 岁' + ); +}); + +// ============================================================= +// 3. 位置参数: {0} / {1} 老式调用兼容 +// ============================================================= + +test('位置参数: {0} 形式', () => { + const stub = { msg: '欢迎第 {0} 位用户' }; + assert.strictEqual( + fakeLang(stub, 'msg', '张三'), + '欢迎第 张三 位用户' + ); +}); + +test('位置参数: 多位置参数 {0}/{1}/{2}', () => { + const stub = { msg: '{0} 在 {1} 出生,今年 {2} 岁' }; + assert.strictEqual( + fakeLang(stub, 'msg', 'Alice', 'Beijing', 18), + 'Alice 在 Beijing 出生,今年 18 岁' + ); +}); + +test('位置参数: 数组会被识别为位置参数而非命名参数', () => { + // args[0] = ['a', 'b'] 是 array → 走位置参数分支 + // 但 args.length === 1(只传了一个数组),所以只有 {0} 被替换,{1} 残留 + const stub = { msg: '{0} {1}' }; + assert.strictEqual( + fakeLang(stub, 'msg', ['a', 'b']), + 'a,b {1}' + ); +}); + +test('位置参数: null 走位置参数分支,被 String() 转为 "null"', () => { + const stub = { msg: '{0}' }; + // args[0] === null: applyLangParams 中 firstArg !== null 判定为 false, + // 走位置参数分支, String(null) = 'null',替换 {0} + assert.strictEqual( + fakeLang(stub, 'msg', null), + 'null' + ); +}); + +// ============================================================= +// 4. 嵌套命名空间: 多层 . 路径 +// ============================================================= + +test('嵌套命名空间: order.payment.title (3 层)', () => { + const orderZh = require(path.resolve(__dirname, '..', 'lang', 'zh-cn', 'order', 'payment.js')).lang; + const orderEn = require(path.resolve(__dirname, '..', 'lang', 'en-us', 'order', 'payment.js')).lang; + // 注意:页面语言包整体就是 order.payment namespace, + // 所以 $lang('title') 也能命中(归一化逻辑),但显式路径是 $lang('order.payment.title') + assert.strictEqual(fakeLang(orderZh, 'title'), '确认订单'); + assert.strictEqual(fakeLang(orderEn, 'title'), 'Confirm Order'); +}); + +test('嵌套命名空间: wechatChannel.wechatMpOnly (本次新增)', () => { + assert.strictEqual( + fakeLang(zh, 'wechatChannel.wechatMpOnly'), + '视频号仅支持在微信小程序环境中播放' + ); + assert.strictEqual( + fakeLang(en, 'wechatChannel.wechatMpOnly'), + 'WeChat Channel only plays in WeChat Mini Program' + ); +}); + +test('嵌套命名空间: common.tabBar.* (本次修正)', () => { + assert.strictEqual(fakeLang(zh, 'tabBar.home'), '首页'); + assert.strictEqual(fakeLang(zh, 'tabBar.category'), '分类'); + assert.strictEqual(fakeLang(zh, 'tabBar.cart'), '购物车'); + assert.strictEqual(fakeLang(zh, 'tabBar.member'), '个人中心'); + + // en-us 顶层 tabBar(本次修复后) + assert.strictEqual(fakeLang(en, 'tabBar.home'), 'Home'); + assert.strictEqual(fakeLang(en, 'tabBar.category'), 'Category'); + assert.strictEqual(fakeLang(en, 'tabBar.cart'), 'Cart'); + assert.strictEqual(fakeLang(en, 'tabBar.member'), 'Me'); +}); + +test('嵌套命名空间: common.tabBar.* 中英文一致性(本次修复的核心断言)', () => { + const tabs = ['home', 'category', 'cart', 'member']; + for (const tab of tabs) { + const zhVal = fakeLang(zh, `tabBar.${tab}`); + const enVal = fakeLang(en, `tabBar.${tab}`); + assert.notStrictEqual(enVal, zhVal, `en-us tabBar.${tab} 不能等于 zh-cn 值`); + assert.ok(enVal.length > 0, `en-us tabBar.${tab} 必须有翻译`); + // 英文 tab 应是 Title Case,而不是全小写英文单词 + assert.ok( + /^[A-Z]/.test(enVal) || enVal === 'Me', + `en-us tabBar.${tab}="${enVal}" 应以大写字母开头(或等于 'Me')` + ); + } +}); + +test('嵌套命名空间: common 顶层键 vs common.common 子对象双层结构', () => { + // 历史遗留:common.js 的 lang 对象整体即 common namespace, + // 但对象内部又有一个 'common: {...}' 子对象 + assert.strictEqual(fakeLang(zh, 'buyNow'), '立即购买'); + assert.strictEqual(fakeLang(zh, 'common.goodsRecommendTitle'), '猜你喜欢'); + assert.strictEqual(fakeLang(en, 'buyNow'), 'Buy Now'); + assert.strictEqual(fakeLang(en, 'common.goodsRecommendTitle'), 'Guess you like'); +}); + +test('嵌套命名空间: 4 层路径 diy.networkErrorTitle', () => { + assert.strictEqual(fakeLang(zh, 'diy.networkErrorTitle'), '提示'); + assert.strictEqual(fakeLang(en, 'diy.networkErrorTitle'), 'Notice'); +}); + +// ============================================================= +// 5. 占位符替换细节 +// ============================================================= + +test('占位符替换: 占位符的值含数字时,String() 强制转换', () => { + const stub = { msg: '价格 {price} 元' }; + assert.strictEqual( + fakeLang(stub, 'msg', { price: 99 }), + '价格 99 元' + ); + assert.strictEqual( + fakeLang(stub, 'msg', { price: 99.5 }), + '价格 99.5 元' + ); +}); + +test('占位符替换: 占位符的值为 undefined 时,String(undefined) = "undefined"', () => { + const stub = { msg: 'name={name}' }; + assert.strictEqual( + fakeLang(stub, 'msg', { name: undefined }), + 'name=undefined' + ); +}); + +test('占位符替换: 占位符的值为 0/false 时,也会被替换(因为走 String 强制转换)', () => { + const stub = { msg: '数量 {n},启用 {on}' }; + assert.strictEqual( + fakeLang(stub, 'msg', { n: 0, on: false }), + '数量 0,启用 false' + ); +}); + +test('占位符替换: 占位符的值为空字符串', () => { + const stub = { msg: '前 [{x}] 后' }; + assert.strictEqual( + fakeLang(stub, 'msg', { x: '' }), + '前 [] 后' + ); +}); + +// ============================================================= +// 6. 真实业务 key 回归测试 +// ============================================================= + +test('回归: 本次修复新增的 8 个 key 在两包中都有', () => { + const newKeys = [ + 'diy.networkError', + 'wechatChannel.wechatMpOnly', + 'submitSuccess', + 'submitFail', + 'versionInfo', + 'commitId', + 'buildTime', + 'loading', + ]; + for (const key of newKeys) { + const zhVal = fakeLang(zh, key); + const enVal = fakeLang(en, key); + assert.ok(typeof zhVal === 'string' && zhVal.length > 0, `zh ${key}="${zhVal}"`); + assert.ok(typeof enVal === 'string' && enVal.length > 0, `en ${key}="${enVal}"`); + assert.notStrictEqual(zhVal, enVal, `${key} 中英文必须不同`); + } +}); + +test('回归: 核心占位符场景 diy.loadFailTpl 不传参数会字面残留占位符', () => { + // 已知行为:reason 必填,如果不传,确实会字面残留(对齐 lang.js 行为) + // 实际 zh-cn 文案是中文标点「:」 + const stub1 = { ...zh }; + const out1 = fakeLang(stub1, 'diy.loadFailTpl'); + // lang.js: value === field? 否(命中了 diy.loadFailTpl 的 leaf)→ 走占位符替换 + // applyLangParams 的 args.length === 0 → 直接返回 value + assert.strictEqual( + out1, + '页面数据加载失败:{reason},请稍后重试', + '不传参数会字面残留占位符,这是已知行为' + ); + + // 但传了 reason 必须正确替换 + assert.strictEqual( + fakeLang(zh, 'diy.loadFailTpl', { reason: 'X' }), + '页面数据加载失败:X,请稍后重试' + ); +}); + +test('回归: zh-cn/en-us common.js 同 key 同结构(key 一致性)', () => { + const zhKeys = Object.keys(zh).sort(); + const enKeys = Object.keys(en).sort(); + assert.deepStrictEqual( + zhKeys, + enKeys, + 'zh-cn 与 en-us common.js 顶层 key 必须完全一致(否则会有 missing_key 风险)' + ); +}); + +test('回归: 抽 zh/en 内 common 子对象 key 一致性', () => { + const zhCommon = Object.keys(zh.common || {}).sort(); + const enCommon = Object.keys(en.common || {}).sort(); + assert.deepStrictEqual(zhCommon, enCommon, 'zh/en common 子对象 key 一致'); +}); \ No newline at end of file