Hexinkui 3 nedēļas atpakaļ
vecāks
revīzija
feee125672

+ 22 - 35
package-lock.json

@@ -15,7 +15,7 @@
         "postcss-pxtorem": "^6.1.0",
         "vant": "^4.9.21",
         "vue": "^3.2.13",
-        "vue-i18n": "^12.0.0-alpha.3",
+        "vue-i18n": "^9.14.5",
         "vue-router": "^4.0.3",
         "vuex": "^4.0.0"
       },
@@ -234,12 +234,13 @@
       }
     },
     "node_modules/@intlify/core-base": {
-      "version": "12.0.0-alpha.3",
-      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-12.0.0-alpha.3.tgz",
-      "integrity": "sha512-LEvBHBUbiOOtIBkp4IIQENVC5Fg2YHsvdXN1+WRIxQ8hzHbHSBiqZ2l68B/yg8sE1a4S7dqhkaAedunShWPH+Q==",
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz",
+      "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==",
+      "license": "MIT",
       "dependencies": {
-        "@intlify/message-compiler": "12.0.0-alpha.3",
-        "@intlify/shared": "12.0.0-alpha.3"
+        "@intlify/message-compiler": "9.14.5",
+        "@intlify/shared": "9.14.5"
       },
       "engines": {
         "node": ">= 16"
@@ -249,11 +250,12 @@
       }
     },
     "node_modules/@intlify/message-compiler": {
-      "version": "12.0.0-alpha.3",
-      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-12.0.0-alpha.3.tgz",
-      "integrity": "sha512-mDDTN3gfYOHhBnpnlby19UHyvMaOnzdlpsIrxUfs44R/vCATfn8pMOkE8PXD2t410xkocEj3FpDcC9XC/0v4Dg==",
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
+      "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==",
+      "license": "MIT",
       "dependencies": {
-        "@intlify/shared": "12.0.0-alpha.3",
+        "@intlify/shared": "9.14.5",
         "source-map-js": "^1.0.2"
       },
       "engines": {
@@ -264,9 +266,10 @@
       }
     },
     "node_modules/@intlify/shared": {
-      "version": "12.0.0-alpha.3",
-      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-12.0.0-alpha.3.tgz",
-      "integrity": "sha512-ryaNYBvxQjyJUmVuBBg+HHUsmGnfxcEUPR0NCeG4/K9N2qtyFE35C80S15IN6iYFE2MGWLN7HfOSyg0MXZIc9w==",
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz",
+      "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==",
+      "license": "MIT",
       "engines": {
         "node": ">= 16"
       },
@@ -274,22 +277,6 @@
         "url": "https://github.com/sponsors/kazupon"
       }
     },
-    "node_modules/@intlify/vue-i18n-core": {
-      "version": "12.0.0-alpha.3",
-      "resolved": "https://registry.npmmirror.com/@intlify/vue-i18n-core/-/vue-i18n-core-12.0.0-alpha.3.tgz",
-      "integrity": "sha512-YwAfTQILHN+VoK0P/Yv47GbKnEf1lhfbliyVyW3knAL1EmT8m0m3rwffXJnwyQhYw8Jpx85CpL49WkSgyi6d/g==",
-      "dependencies": {
-        "@intlify/core-base": "12.0.0-alpha.3",
-        "@intlify/shared": "12.0.0-alpha.3",
-        "@vue/devtools-api": "^6.5.0"
-      },
-      "engines": {
-        "node": ">= 16"
-      },
-      "peerDependencies": {
-        "vue": "^3.0.0"
-      }
-    },
     "node_modules/@jridgewell/gen-mapping": {
       "version": "0.3.13",
       "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -7793,13 +7780,13 @@
       "dev": true
     },
     "node_modules/vue-i18n": {
-      "version": "12.0.0-alpha.3",
-      "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-12.0.0-alpha.3.tgz",
-      "integrity": "sha512-+KQgD9LJoHfGCdJh3gaLdVS/Sps1n860+6wsjyeNLWJeEofjdVH7KPjz4rAeBlTAUaIDlIjHoXQY0Lk+8B6S9w==",
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz",
+      "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==",
+      "license": "MIT",
       "dependencies": {
-        "@intlify/core-base": "12.0.0-alpha.3",
-        "@intlify/shared": "12.0.0-alpha.3",
-        "@intlify/vue-i18n-core": "12.0.0-alpha.3",
+        "@intlify/core-base": "9.14.5",
+        "@intlify/shared": "9.14.5",
         "@vue/devtools-api": "^6.5.0"
       },
       "engines": {

+ 1 - 1
package.json

@@ -14,7 +14,7 @@
     "postcss-pxtorem": "^6.1.0",
     "vant": "^4.9.21",
     "vue": "^3.2.13",
-    "vue-i18n": "^12.0.0-alpha.3",
+    "vue-i18n": "^9.14.5",
     "vue-router": "^4.0.3",
     "vuex": "^4.0.0"
   },

+ 110 - 72
src/locales/index.js

@@ -1,105 +1,143 @@
 import { createI18n } from 'vue-i18n'
 import { Locale } from 'vant'
 
-// 1. 静态引入:确保路径正确 (根据你的截图 ./lang/ 是对的)
-import userZhCN from './lang/zh-CN.js'
-import vantZhCN from 'vant/es/locale/lang/zh-CN'
-
-// 2. 数据清洗:确保拿到的是纯净的对象,而不是 Module
-const userData = userZhCN.default || userZhCN
-const vantData = vantZhCN.default || vantZhCN
-
-// 3. 深度合并:将 Vant 和 User 数据合并
-// ⚠️ 注意顺序:userData 在后,确保用户自定义的优先级更高
-const finalZhCN = { ...vantData, ...userData }
-
-// 🔍 再次确认日志
-console.log('🚀 i18n Data Ready:', {
-  hasCommon: !!finalZhCN.common,
-  switchLang: finalZhCN.common?.switchLang
-})
+// --- 1. 静态引入默认语言 (兜底保障) ---
+// 使用相对路径,确保构建工具能正确解析
+import defaultZhCN from './lang/zh-CN.js'
+import defaultVantZhCN from 'vant/es/locale/lang/zh-CN'
+
+// --- 2. 工具函数:模块标准化 ---
+// 解决 import() 导入后可能是 Module 对象也可能是直接对象的问题
+const unwrap = (module) => (module && module.default ? module.default : module) || {}
+
+// --- 3. 初始数据准备 ---
+const userMessages = unwrap(defaultZhCN)
+const vantMessages = unwrap(defaultVantZhCN)
+
+// 初始加载时仅包含中文,其他语言按需加载
+const messages = {
+  'zh-CN': userMessages
+}
+
+// 记录已加载的语言,避免重复网络请求
+const loadedLanguages = ['zh-CN']
 
-const savedLang = localStorage.getItem('app-lang') || 'zh-CN'
+// --- 4. Vant 语言包映射表 (配置中心) ---
+// 将 Vant 的语言包路径映射到你的 app-lang 代码
+// 生产环境建议按需配置,不要全部引入
+const vantLocalesMap = {
+  'zh-CN': () => Promise.resolve({ default: defaultVantZhCN }), // 静态
+  'en-US': () => import('vant/es/locale/lang/en-US'),
+  'de-DE': () => import('vant/es/locale/lang/de-DE'),
+  'es-ES': () => import('vant/es/locale/lang/es-ES'),
+  'fr-FR': () => import('vant/es/locale/lang/fr-FR'),
+  'pt-PT': () => import('vant/es/locale/lang/pt-BR'), // Vant 可能只有 pt-BR
+  'id-ID': () => import('vant/es/locale/lang/id-ID'),
+  'ru-RU': () => import('vant/es/locale/lang/ru-RU'),
+}
 
-const i18n = createI18n({
-  legacy: false,
-  locale: savedLang,
-  fallbackLocale: 'zh-CN',
-  globalInjection: true,
-  // 4. 初始化消息:这里是关键,确保 zh-CN 永远有值
-  messages: {
-    'zh-CN': finalZhCN
+// --- 5. 初始化 i18n 实例 ---
+// 优先从 sessionStorage 读取,无则回退到 zh-CN
+const getInitialLang = () => {
+  const cached = sessionStorage.getItem('app-lang')
+  if (cached && cached !== 'null' && cached !== 'undefined') {
+    return cached
   }
+  return 'zh-CN'
+}
+
+const savedLang = getInitialLang()
+
+const i18n = createI18n({
+  legacy: false, // 组合式 API 模式
+  locale: savedLang, // 初始语言
+  fallbackLocale: 'zh-CN', // 翻译缺失时回退
+  globalInjection: true, // 全局注入 $t
+  messages // 初始消息池
 })
 
-// 记录已加载语言
-const loadedLanguages = ['zh-CN']
+// 初始应用 Vant 配置 (如果是中文直接应用,其他语言由 main.js 调用 loadLanguageAsync 触发)
+if (savedLang === 'zh-CN') {
+  Locale.use('zh-CN', vantMessages)
+}
 
-// 5. 设置 Vant 语言的辅助函数
-function setVantLanguage(lang, messages) {
-  if (lang === 'zh-CN') {
-    Locale.use('zh-CN', vantData)
-  } else {
-    Locale.use(lang, messages)
+// --- 6. 核心逻辑:设置语言环境 (副作用管理) ---
+function setI18nLanguage(lang, vantMsg = null) {
+  // 1. 切换 Vue I18n
+  i18n.global.locale.value = lang
+
+  // 2. 切换 HTML 属性 (SEO & 浏览器识别)
+  if (typeof document !== 'undefined') {
+    document.querySelector('html').setAttribute('lang', lang)
+  }
+
+  // 3. 持久化存储
+  sessionStorage.setItem('app-lang', lang)
+
+  // 4. 切换 Vant 组件语言
+  // 如果没传 vantMsg,尝试去内存找,或者回退到默认
+  if (vantMsg) {
+    Locale.use(lang, vantMsg)
+  } else if (lang === 'zh-CN') {
+    Locale.use('zh-CN', vantMessages)
   }
+
+  return lang
 }
 
+// --- 7. 导出:异步加载函数 ---
 export async function loadLanguageAsync(lang) {
-  // 空值检查
-  if (!lang || lang === 'undefined' || lang === 'null') {
+  // 参数校验
+  if (!lang || typeof lang !== 'string') {
+    // console.warn(`[i18n] Invalid lang: ${lang}, fallback to zh-CN`)
     return loadLanguageAsync('zh-CN')
   }
 
-  // A. 如果是中文:直接切过去,千万不要重新加载,防止覆盖
-  if (lang === 'zh-CN') {
-    i18n.global.locale.value = 'zh-CN'
-    setVantLanguage('zh-CN')
-    document.querySelector('html').setAttribute('lang', 'zh-CN')
-    localStorage.setItem('app-lang', 'zh-CN')
-    return 'zh-CN'
-  }
-
-  // B. 如果已加载:直接切
+  // 情况 A: 语言已加载 (包含静态的 zh-CN)
   if (loadedLanguages.includes(lang)) {
-    i18n.global.locale.value = lang
-    setVantLanguage(lang, i18n.global.getLocaleMessage(lang)) // 尝试从 i18n 实例获取 vant 数据有点难,这里简化处理
-    document.querySelector('html').setAttribute('lang', lang)
-    localStorage.setItem('app-lang', lang)
+    // 如果还没切换过去,执行切换
+    if (i18n.global.locale.value !== lang) {
+      // 尝试重新获取 Vant 语言配置 (通常不需要,因为 Vant 是全局单例,但为了保险)
+      return setI18nLanguage(lang)
+    }
     return lang
   }
 
-  // C. 动态加载新语言
+  // 情况 B: 新语言,需要动态加载
   try {
-    // 动态导入映射
-    // ⚠️ 确保这些文件在 src/locales/lang/ 目录下存在
-    const userImport = import(`./lang/${lang}.js`)
+    // 并行加载:用户业务翻译 + Vant 组件翻译
+    // 这里的 import 路径必须能被 Vite/Webpack 静态分析到
+    const userMsgPromise = import(`./lang/${lang}.js`)
 
-    // Vant 映射
-    let vantImportPromise = Promise.resolve({ default: {} })
-    if (lang === 'en-US') vantImportPromise = import('vant/es/locale/lang/en-US')
-    // ... 可以按需添加其他 Vant 语言 ...
+    // 查找 Vant 映射,如果没有则返回空对象防止报错
+    const vantMsgPromise = vantLocalesMap[lang]
+      ? vantLocalesMap[lang]()
+      : Promise.resolve({ default: {} })
 
-    const [userMod, vantMod] = await Promise.all([userImport, vantImportPromise])
+    const [userMsgModule, vantMsgModule] = await Promise.all([
+      userMsgPromise,
+      vantMsgPromise
+    ])
 
-    const userMsg = userMod.default || userMod
-    const vantMsg = vantMod.default || vantMod
+    const finalUserMsg = unwrap(userMsgModule)
+    const finalVantMsg = unwrap(vantMsgModule)
 
-    const finalMsg = { ...vantMsg, ...userMsg }
+    // 将用户翻译注入 i18n
+    i18n.global.setLocaleMessage(lang, finalUserMsg)
 
-    i18n.global.setLocaleMessage(lang, finalMsg)
+    // 标记已加载
     loadedLanguages.push(lang)
 
-    // 切换
-    i18n.global.locale.value = lang
-    setVantLanguage(lang, vantMsg)
-    document.querySelector('html').setAttribute('lang', lang)
-    localStorage.setItem('app-lang', lang)
+    // 应用切换
+    return setI18nLanguage(lang, finalVantMsg)
 
-    return lang
   } catch (e) {
-    console.error(`❌ Load lang ${lang} failed:`, e)
-    // 失败回退到中文
-    return loadLanguageAsync('zh-CN')
+    // console.error(`[i18n] Failed to load language: ${lang}`, e)
+    // 加载失败时,如果目标不是中文,则回退到中文
+    if (lang !== 'zh-CN') {
+      return loadLanguageAsync('zh-CN')
+    }
+    throw e // 如果中文也挂了,抛出异常让 main.js 处理
   }
 }
 

+ 2 - 3
src/locales/lang/en-US.js

@@ -1,7 +1,5 @@
 export default {
   common: {
-      // switchLang: '*** ENGLISH ***',
-    // confirm: '*** SUBMIT ***',
     switchLang: 'Switch Language',
     confirm: 'Submit',
     cancel: 'Cancel',
@@ -29,4 +27,5 @@ export default {
     pleaseSelect: 'Select',
     pleaseInput: 'Enter'
   }
-}
+}
+

+ 38 - 26
src/main.js

@@ -2,12 +2,11 @@ import { createApp } from "vue";
 import App from "./App.vue";
 import 'amfe-flexible';
 import router from "./router";
-// import store from "./store";
 import 'vant/lib/index.css';
 import "./assets/less/index.less";
 import "./assets/h5-reset.css";
 
-// 引入 i18n
+// 引入 i18n(确保 locales/index.js 导出默认 i18n 实例和 loadLanguageAsync)
 import i18n, { loadLanguageAsync } from './locales/index.js';
 
 import {
@@ -16,27 +15,40 @@ import {
 
 import api from "./utils/api.js";
 
-const savedLang = localStorage.getItem('app-lang') || 'zh-CN';
-
-// 🚀 启动逻辑
-loadLanguageAsync(savedLang)
-  .catch(err => {
-    // 即使加载语言失败,也要强行启动 App,避免白屏
-    console.error('Failed to load language, falling back to rendering...', err);
-  })
-  .then(() => {
-    const app = createApp(App);
-
-    // Vant 组件注册
-    app.use(Button).use(Popup).use(Form).use(Field).use(NavBar).use(Picker).use(Icon).use(Toast);
-
-    // 核心插件
-    app.use(i18n);   // ✅ 此时 i18n 内部应该已经有数据了
-    app.use(router);
-    // app.use(store);
-
-    app.config.globalProperties.$api = api;
-
-    app.mount("#app");
-    console.log('🚀 App Mounted Successfully');
-  });
+// ✅ 改为 sessionStorage
+const savedLang = sessionStorage.getItem('app-lang') || 'zh-CN';
+// console.log('🚀 [main.js] 启动语言:', savedLang);
+
+async function bootstrap() {
+  try {
+    // 等待语言包加载并注入 i18n(很关键)
+    await loadLanguageAsync(savedLang);
+    // console.log('🚀 [main.js] 语言加载完成:', savedLang);
+  } catch (err) {
+    // console.error('❌ [main.js] 语言加载失败,回退到 zh-CN', err);
+    // 尝试回退到默认中文(如果 loadLanguageAsync 能接受直接传 'zh-CN')
+    // try { await loadLanguageAsync('zh-CN'); } catch (e) { console.error('❌ 回退中文也失败', e); }
+  }
+
+  console.log('🚀 [main.js] 语言准备就绪,挂载 App...');
+  const app = createApp(App);
+
+  // 先挂载 i18n,确保组件内 t() 能拿到消息
+  if (i18n) {
+    app.use(i18n);
+  } else {
+    // console.error('❌ [main.js] i18n 实例丢失!');
+  }
+
+  // 注册 Vant 组件
+  app.use(Button).use(Popup).use(Form).use(Field).use(NavBar).use(Picker).use(Icon).use(Toast);
+
+  // 再挂载路由和其它
+  app.use(router);
+  app.config.globalProperties.$api = api;
+
+  app.mount("#app");
+  console.log('✅ [main.js] App 挂载成功');
+}
+
+bootstrap();

+ 1 - 1
src/views/bitcoin/lever/TradeSeconds.vue

@@ -65,7 +65,7 @@
       <!-- 引入封装好的组件,只需传数据和高度 -->
       <KlineChart
         :data="kLineData"
-        height="100%"
+        height="70vh"
       />
     </div>
 

+ 2 - 1
src/views/market/details/MarketConditions.vue

@@ -332,5 +332,6 @@ const getUpDownClass = (c) => (c >= 0 ? "fc45B26B" : "fcF6465D");
     .right-number-top, .right-number-bottom { display: flex; justify-content: flex-end; width: 100%; height: 32px; .right-number-top-price, .right-number-top-number { margin-left: 10px; text-align: right; div { height: 16px; line-height: 16px; text-align: end; } } } .right-number-bottom {  } }
 }
 .k-line-main { height: 50vh; min-height: 350px; width: 100%; padding: 0 15px; }
-.notifi-classifi { display: flex; align-items: flex-end; margin-top: 15px; width: 100%; padding: 0 15px; box-sizing: border-box; height: 24px; border-bottom: 1px solid #f5f5f5; padding-bottom: 5px; .sys-notifi { margin-left: 47px; } }
+.notifi-classifi { display: flex; align-items: flex-end; margin-top: 140px; width: 100%; padding: 0 15px;
+  box-sizing: border-box; height: 24px; border-bottom: 1px solid #f5f5f5; padding-bottom: 5px; .sys-notifi { margin-left: 47px; } }
 </style>

+ 14 - 7
src/views/user/LanguageSwitch.vue

@@ -1,14 +1,13 @@
 <template>
   <div class="page-container">
     <van-nav-bar
-      :title="$t('common.confirm')"
+      :title="t('common.switchLang')"
       left-arrow
       fixed
       placeholder
       @click-left="router.back()"
       class="custom-nav"
     />
-
     <div class="content-body">
       <div
         v-for="item in langList"
@@ -33,12 +32,13 @@
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, onMounted } from 'vue';
 import { useRouter } from 'vue-router';
 import { useI18n } from 'vue-i18n';
 import { showLoadingToast, closeToast, Icon as VanIcon } from 'vant';
-import { loadLanguageAsync } from '@/locales/index.js';
-
+// 🚨 路径修复:如果你的 index.js 在 src/locales/,则应该使用绝对路径 @/
+import { loadLanguageAsync } from '../../locales/index';
+const { t } = useI18n();
 const router = useRouter();
 const { locale } = useI18n();
 const currentLang = ref(locale.value);
@@ -65,14 +65,21 @@ const onSelectLang = async (item) => {
   });
 
   try {
-    await loadLanguageAsync(langCode);
-    currentLang.value = langCode;
+    // loadLanguageAsync 内部已经处理了 locale.value 的切换
+    const finalLang = await loadLanguageAsync(langCode);
+
+    // 确保组件状态与全局状态同步
+    locale.value = finalLang; // 这一行是必要的保险
+    currentLang.value = finalLang;
+
     closeToast();
+
     setTimeout(() => {
       // router.back();
       }, 200);
   } catch (error) {
     closeToast();
+    console.error('Language selection failed:', error);
   }
 };
 </script>