12 次代碼提交 c19c39b1a7 ... df18ce6799

作者 SHA1 備註 提交日期
  yankun df18ce6799 顶部 header 新增 “App Store” 和 “Play Store” 下载按钮 4 天之前
  yankun 4cec0b0cf6 提交 6 天之前
  yankun b6ab79742f 页面底部 footer 内容优化 3 周之前
  yankun 9a5419ee62 submit 3 周之前
  yankun d88f48ab60 提交 3 周之前
  yankun b57458aff6 隐藏页面底部 Right 内容 3 周之前
  yankun e9419c4db7 提交 1 月之前
  yankun bed96f7ca7 项目代码&内容优化 1 月之前
  yankun e2d48f720f 2025年06月06日:增加 “App 下载” 按钮 1 月之前
  yankun 7de914e00b 2025年06月06日:01)完成 “常见问题” 和 “文章详情” 页开发;02)网页内容优化 1 月之前
  yankun 7bd3ccd7b1 2025年06月04日:01)完成 “文章详情” 和 “常见问题” 静态页面开发;02)完成 “@/utils/request.ts” 文件内容填充。 2 月之前
  yankun e2992c667b 2025年6月3日:01)新增“文章详情”和“常见问题”静态页面;02)部分代码文件内容优化 2 月之前
共有 65 個文件被更改,包括 3040 次插入274 次删除
  1. 2 1
      .env.development
  2. 1 1
      .env.production
  3. 69 11
      index.html
  4. 2 2
      package.json
  5. 21 0
      src/App.vue
  6. 32 0
      src/apis/article.ts
  7. 9 0
      src/apis/system.ts
  8. 13 1
      src/assets/animation.css
  9. 13 2
      src/assets/base.css
  10. 40 3
      src/assets/variable.css
  11. 147 0
      src/components/LoadingSpinner.vue
  12. 6 4
      src/locale/index.ts
  13. 76 7
      src/locale/lang/en.ts
  14. 133 0
      src/locale/lang/es.ts
  15. 71 10
      src/locale/lang/zh.ts
  16. 6 2
      src/main.ts
  17. 二進制
      src/public/images/avatar/avatar_1.jpg
  18. 二進制
      src/public/images/avatar/avatar_10.jpg
  19. 二進制
      src/public/images/avatar/avatar_11.jpg
  20. 二進制
      src/public/images/avatar/avatar_2.jpg
  21. 二進制
      src/public/images/avatar/avatar_3.jpg
  22. 二進制
      src/public/images/avatar/avatar_4.jpg
  23. 二進制
      src/public/images/avatar/avatar_5.jpg
  24. 二進制
      src/public/images/avatar/avatar_6.jpg
  25. 二進制
      src/public/images/avatar/avatar_7.jpg
  26. 二進制
      src/public/images/avatar/avatar_8.jpg
  27. 二進制
      src/public/images/avatar/avatar_9.jpg
  28. 0 0
      src/public/images/avatar_2.png
  29. 二進制
      src/public/images/download_qrcode.png
  30. 二進制
      src/public/images/icons/aodaliya_logo_icon.png
  31. 二進制
      src/public/images/icons/app_store_icon.png
  32. 二進制
      src/public/images/icons/download_icon.png
  33. 二進制
      src/public/images/icons/menu_icon.png
  34. 二進制
      src/public/images/icons/play_store_icon.png
  35. 二進制
      src/public/images/icons/xinxilan_logo_icon.png
  36. 二進制
      src/public/images/icons/yidali_logo_icon.png
  37. 二進制
      src/public/images/logo_icon.png
  38. 14 3
      src/router/index.ts
  39. 116 0
      src/stores/useFooterStore.ts
  40. 45 15
      src/stores/useSystemStore.ts
  41. 3 0
      src/utils/rem.ts
  42. 46 5
      src/utils/request.ts
  43. 112 0
      src/views/ArticleDetails/hooks/useData.ts
  44. 223 0
      src/views/ArticleDetails/index.vue
  45. 1 0
      src/views/FAQ/css/styles@1024.less
  46. 1 0
      src/views/FAQ/css/styles@1200.less
  47. 32 0
      src/views/FAQ/css/styles@768.less
  48. 125 0
      src/views/FAQ/hooks/useData.ts
  49. 916 0
      src/views/FAQ/index.vue
  50. 9 7
      src/views/Home/index.vue
  51. 8 7
      src/views/Home/modules/Banners/index.vue
  52. 90 16
      src/views/Home/modules/WrapFive/index.vue
  53. 7 2
      src/views/Home/modules/WrapFour/css/styles@1024.less
  54. 8 4
      src/views/Home/modules/WrapFour/css/styles@768.less
  55. 72 6
      src/views/Home/modules/WrapFour/index.vue
  56. 26 3
      src/views/Home/modules/WrapTwo/index.vue
  57. 6 0
      src/views/Layout/index.vue
  58. 12 4
      src/views/Layout/modules/Footer/css/footer@512.less
  59. 147 78
      src/views/Layout/modules/Footer/index.vue
  60. 8 0
      src/views/Layout/modules/Header/css/header@768.less
  61. 78 10
      src/views/Layout/modules/Header/hooks/useLinkList.ts
  62. 286 70
      src/views/Layout/modules/Header/index.vue
  63. 4 0
      src/views/Layout/modules/Main/index.vue
  64. 二進制
      vava_buy_official.zip
  65. 4 0
      vite.config.ts

+ 2 - 1
.env.development

@@ -1,5 +1,6 @@
 # 请求 url
 # 请求 url
-VITE_BASE_URL=http://bigtotoro.cn/
+# VITE_BASE_URL=https://vvbuy.csvip.top/api
+VITE_BASE_URL=https://get.vavabuy.com/api/
 
 
 # 标题
 # 标题
 VITE_APP_TITLE="VAVA BUY"
 VITE_APP_TITLE="VAVA BUY"

+ 1 - 1
.env.production

@@ -1,5 +1,5 @@
 # 请求 url
 # 请求 url
-VITE_BASE_URL=http://bigtotoro.cn/
+VITE_BASE_URL=https://get.vavabuy.com/api/
 
 
 # 标题
 # 标题
 VITE_APP_TITLE="VAVA BUY"
 VITE_APP_TITLE="VAVA BUY"

+ 69 - 11
index.html

@@ -1,13 +1,71 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="">
 <html lang="">
-  <head>
-    <meta charset="UTF-8">
-    <link rel="icon" href="/favicon.ico">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>%VITE_APP_TITLE%</title>
-  </head>
-  <body>
-    <div id="app"></div>
-    <script type="module" src="/src/main.ts"></script>
-  </body>
-</html>
+
+<head>
+  <meta charset="UTF-8">
+  <link rel="icon" href="/favicon.ico">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>%VITE_APP_TITLE%</title>
+</head>
+
+<body>
+  <div id="app"></div>
+  <script type="module" src="/src/main.ts"></script>
+
+  <!-- Twitter conversion tracking base code -->
+  <script>
+    !function (e, t, n, s, u, a) {
+      e.twq || (s = e.twq = function () {
+        s.exe ? s.exe.apply(s, arguments) : s.queue.push(arguments);
+      }, s.version = '1.1', s.queue = [], u = t.createElement(n), u.async = !0, u.src = 'https://static.ads-twitter.com/uwt.js',
+        a = t.getElementsByTagName(n)[0], a.parentNode.insertBefore(u, a))
+    }(window, document, 'script');
+    twq('config', 'prkh1');
+  </script>
+  <!-- End Twitter conversion tracking base code -->
+
+  <!-- Twitter conversion tracking event code -->
+  <script type="text/javascript">
+    // Insert Twitter Event ID
+    twq('event', 'tw-prkh1-prkh3', {
+      contents: [ // use this to pass an array of products or content
+        // add all items to the array
+        // use this for the first item
+        {
+          content_type: null,
+          content_id: null,
+          content_name: null,
+          content_price: null,
+          num_items: null,
+          content_group_id: null
+        },
+        // use this for the second item
+        {
+          content_type: null,
+          content_id: null,
+          content_name: null,
+          content_price: null,
+          num_items: null,
+          content_group_id: null
+        }
+      ]
+    });
+  </script>
+  <!-- End Twitter conversion tracking event code -->
+
+
+  <!-- Google tag (gtag.js) -->
+  <script async src="https://www.googletagmanager.com/gtag/js?id=AW-17218823610"></script>
+  <script>
+    window.dataLayer = window.dataLayer || [];
+    function gtag() { dataLayer.push(arguments); }
+    gtag('js', new Date());
+
+    gtag('config', 'AW-17218823610');
+  </script>
+
+  <script
+    type="text/javascript">document.write(unescape("%3Cspan id='cnzz_stat_icon_1281425567'%3E%3C/span%3E%3Cscript src='https://s9.cnzz.com/z.js%3Fid%3D1281425567%26show%3Dpic' type='text/javascript'%3E%3C/script%3E"));</script>
+</body>
+
+</html>

+ 2 - 2
package.json

@@ -5,7 +5,7 @@
   "type": "module",
   "type": "module",
   "scripts": {
   "scripts": {
     "dev": "vite",
     "dev": "vite",
-    "build": "run-p type-check \"build-only {@}\" --",
+    "build": "vite build",
     "preview": "vite preview",
     "preview": "vite preview",
     "build-only": "vite build",
     "build-only": "vite build",
     "type-check": "vue-tsc --build",
     "type-check": "vue-tsc --build",
@@ -41,4 +41,4 @@
     "vite-plugin-vue-devtools": "^7.7.2",
     "vite-plugin-vue-devtools": "^7.7.2",
     "vue-tsc": "^2.2.8"
     "vue-tsc": "^2.2.8"
   }
   }
-}
+}

+ 21 - 0
src/App.vue

@@ -1,5 +1,26 @@
 <script setup lang="ts">
 <script setup lang="ts">
+import { onMounted, onBeforeMount } from 'vue'
 import { RouterView } from 'vue-router'
 import { RouterView } from 'vue-router'
+import useSystemStore from '@/stores/useSystemStore'
+
+const systemStore = useSystemStore()
+const { updateScreenWidth, fetchLocaleList } = systemStore
+
+fetchLocaleList()
+
+onMounted(() => {
+  window.addEventListener('resize', onWindowResize)
+})
+
+onBeforeMount(() => {
+  window.removeEventListener('resize', onWindowResize)
+})
+
+function onWindowResize() {
+  const clientWidth = document.documentElement.clientWidth
+  updateScreenWidth(clientWidth)
+}
+onWindowResize()
 </script>
 </script>
 
 
 <template>
 <template>

+ 32 - 0
src/apis/article.ts

@@ -0,0 +1,32 @@
+import request from '@/utils/request'
+
+
+
+export const getContentHelpApi = (params: Object) => request({
+  url: '/content/footer',
+  method: 'get',
+  params: params
+})
+
+
+export const getContentDetailApi = (params: Object) => request({
+  url: '/content/detail',
+  method: 'get',
+  params: params
+})
+
+export const getContentDetailByKeywordApi = (params: Object) => request({
+  url: '/other/about',
+  method: 'get',
+  params: params
+})
+
+
+
+export const getFAQCateListApi = (params: Object) => request({
+  url: '/content/faq',
+  method: 'get',
+  params: params
+})
+
+

+ 9 - 0
src/apis/system.ts

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+
+
+export const getLocaleListApi = (params: Object) => request({
+  url: '/third/rate/list',
+  method: 'get',
+  params: params
+})

+ 13 - 1
src/assets/animation.css

@@ -2,10 +2,12 @@
 .text_hover_underline {
 .text_hover_underline {
   position: relative;
   position: relative;
 }
 }
+
 .text_hover_underline:hover {
 .text_hover_underline:hover {
-  color: var(--color-active)!important;
+  color: var(--color-active) !important;
   cursor: pointer;
   cursor: pointer;
 }
 }
+
 .text_hover_underline::after {
 .text_hover_underline::after {
   content: "";
   content: "";
   position: absolute;
   position: absolute;
@@ -18,9 +20,19 @@
   border-radius: 2px;
   border-radius: 2px;
   transition: all .25s;
   transition: all .25s;
 }
 }
+
 .text_hover_underline:hover::after {
 .text_hover_underline:hover::after {
   width: 100%;
   width: 100%;
 }
 }
+
+.text_hover_underline--active {
+  color: var(--color-active);
+}
+
+.text_hover_underline--active.text_hover_underline::after {
+  width: 100%;
+}
+
 /* -------------------- 文本 hover 下划线 end -------------------- */
 /* -------------------- 文本 hover 下划线 end -------------------- */
 
 
 
 

+ 13 - 2
src/assets/base.css

@@ -4,7 +4,9 @@
   box-sizing: border-box;
   box-sizing: border-box;
 }
 }
 
 
-html, body, #app {
+html,
+body,
+#app {
   /* min-width: 375px; */
   /* min-width: 375px; */
   font-size: var(--font-size);
   font-size: var(--font-size);
   color: var(--font-color);
   color: var(--font-color);
@@ -15,11 +17,20 @@ html, body, #app {
 
 
 
 
 
 
-ul, ol, dl, li {
+ul,
+ol,
+dl,
+li {
   list-style: none;
   list-style: none;
 }
 }
 
 
 
 
+input,
+textarea,
+button {
+  outline: none;
+}
+
 
 
 .width-1200 {
 .width-1200 {
   min-width: 1200px;
   min-width: 1200px;

+ 40 - 3
src/assets/variable.css

@@ -1,6 +1,11 @@
-:root {
+:root,
+html {
   --font-size: 16px;
   --font-size: 16px;
   --font-size: 1rem;
   --font-size: 1rem;
+
+  --title-size: 30px;
+  --title-size: 1.875rem;
+
   --font-color: #333;
   --font-color: #333;
   --color-active: #3670f7;
   --color-active: #3670f7;
 
 
@@ -10,8 +15,40 @@
 
 
 
 
 
 
+@media screen and (max-width: 1024px) {
+
+  :root,
+  html {
+    --font-size: 16px;
+    --font-size: 1rem;
+
+    --title-size: 26px;
+    --title-size: 1.625rem;
+
+    --font-color: #333;
+    --color-active: #3670f7;
+
+    --layout-width-1200: calc(100vw * 0.625);
+    --layout-width-1024: 100vw;
+  }
+}
+
+
+
 @media screen and (max-width: 768px) {
 @media screen and (max-width: 768px) {
-  :root {
-    /* --font-size: 14px; */
+
+  :root,
+  html {
+    --font-size: 16px;
+    --font-size: 1rem;
+
+    --title-size: 24px;
+    --title-size: 1.5rem;
+
+    --font-color: #333;
+    --color-active: #3670f7;
+
+    --layout-width-1200: calc(100vw * 0.625);
+    --layout-width-1024: 100vw;
   }
   }
 }
 }

+ 147 - 0
src/components/LoadingSpinner.vue

@@ -0,0 +1,147 @@
+<template>
+  <div v-if="isLoading" class="loading-spinner">
+    <div class="spinner-container">
+      <div class="logo-container">
+        <div class="logo-circle"></div>
+        <div class="logo-text">VAVA</div>
+      </div>
+      <div class="loading-dots">
+        <span class="dot"></span>
+        <span class="dot"></span>
+        <span class="dot"></span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const isLoading = ref(true)
+</script>
+
+<style scoped>
+.loading-spinner {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #000;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 9999;
+  -webkit-tap-highlight-color: transparent;
+  touch-action: none;
+}
+
+.spinner-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 24px;
+}
+
+.logo-container {
+  position: relative;
+  width: 80px;
+  height: 80px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.logo-circle {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  border: 2px solid rgba(255, 255, 255, 0.1);
+  border-top-color: #fff;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+.logo-text {
+  color: #fff;
+  font-size: 24px;
+  font-weight: 600;
+  letter-spacing: 2px;
+  animation: pulse 2s ease-in-out infinite;
+}
+
+.loading-dots {
+  display: flex;
+  gap: 8px;
+}
+
+.dot {
+  width: 8px;
+  height: 8px;
+  background-color: #fff;
+  border-radius: 50%;
+  opacity: 0.6;
+  animation: fade 1.4s infinite;
+}
+
+.dot:nth-child(2) {
+  animation-delay: 0.2s;
+}
+
+.dot:nth-child(3) {
+  animation-delay: 0.4s;
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes pulse {
+  0%,
+  100% {
+    opacity: 0.6;
+    transform: scale(0.95);
+  }
+  50% {
+    opacity: 1;
+    transform: scale(1.05);
+  }
+}
+
+@keyframes fade {
+  0%,
+  100% {
+    opacity: 0.2;
+    transform: scale(0.8);
+  }
+  50% {
+    opacity: 1;
+    transform: scale(1.2);
+  }
+}
+
+/* 移动端适配 */
+@media screen and (max-width: 768px) {
+  .logo-container {
+    width: 60px;
+    height: 60px;
+  }
+
+  .logo-text {
+    font-size: 20px;
+  }
+
+  .dot {
+    width: 6px;
+    height: 6px;
+  }
+}
+
+/* 防止iOS橡皮筋效果 */
+.loading-spinner {
+  overscroll-behavior: none;
+  -webkit-overflow-scrolling: touch;
+}
+</style> 

+ 6 - 4
src/locale/index.ts

@@ -1,11 +1,12 @@
 import { createI18n } from 'vue-i18n'
 import { createI18n } from 'vue-i18n'
 import en from './lang/en'
 import en from './lang/en'
+import es from './lang/es'
 import zh from './lang/zh'
 import zh from './lang/zh'
 
 
 
 
 // 获取当前语言
 // 获取当前语言
 export function getCurrentLang(): string | null {
 export function getCurrentLang(): string | null {
-  return !!localStorage.getItem('locale') && localStorage.getItem('locale') !== 'null' && localStorage.getItem('locale') !== 'undefine' ? localStorage.getItem('locale') : 'zhCN'
+  return !!localStorage.getItem('locale') && localStorage.getItem('locale') !== 'null' && localStorage.getItem('locale') !== 'undefine' ? localStorage.getItem('locale') : 'en'
 }
 }
 
 
 
 
@@ -13,10 +14,11 @@ export function getCurrentLang(): string | null {
 const i18n = createI18n({
 const i18n = createI18n({
   legacy: false,
   legacy: false,
   locale: (getCurrentLang() as string),
   locale: (getCurrentLang() as string),
-  fallbackLocale: 'zhCN',
+  fallbackLocale: 'en',
   messages: {
   messages: {
-    en: en,
-    zhCN: zh,
+    en,
+    es,
+    zh,
   },
   },
 });
 });
 
 

+ 76 - 7
src/locale/lang/en.ts

@@ -1,6 +1,5 @@
 export default {
 export default {
   system: {
   system: {
-    "中文": "English",
     "登录": "Log in",
     "登录": "Log in",
     "立即免费注册": "Register immediately for free",
     "立即免费注册": "Register immediately for free",
     "可以在数千家商店购物和支付": "You can shop and make payments in thousands of stores",
     "可以在数千家商店购物和支付": "You can shop and make payments in thousands of stores",
@@ -18,7 +17,7 @@ export default {
     "加入VAVA BUY 以获取您的收货地址": "Join VAVA BUY to obtain your delivery address",
     "加入VAVA BUY 以获取您的收货地址": "Join VAVA BUY to obtain your delivery address",
     "下单购买": "Place an order for purchase",
     "下单购买": "Place an order for purchase",
     "在商店购物,运送到您的新VAVA BUY地址": "Make a purchase at the store and have it delivered to your new VAVA BUY address",
     "在商店购物,运送到您的新VAVA BUY地址": "Make a purchase at the store and have it delivered to your new VAVA BUY address",
-    "收货转运": "Delivery transfer",
+    "收货转运": "Delivery and transfer",
     "合并包裹并节省高达80%的运费": "Combine packages and save up to 80% on shipping costs",
     "合并包裹并节省高达80%的运费": "Combine packages and save up to 80% on shipping costs",
     "VAVA BUY 优势对比": "VAVA BUY Advantage Comparison",
     "VAVA BUY 优势对比": "VAVA BUY Advantage Comparison",
     "包裹运送到全球,而且还在不断增加...": "Packages are being delivered worldwide, and the number is constantly increasing...",
     "包裹运送到全球,而且还在不断增加...": "Packages are being delivered worldwide, and the number is constantly increasing...",
@@ -27,8 +26,19 @@ export default {
     "新西兰": "New Zealand",
     "新西兰": "New Zealand",
     "澳大利亚": "Australia",
     "澳大利亚": "Australia",
     "意大利": "Italy",
     "意大利": "Italy",
-    "囧二": "Guang Er",
-    
+    "Lawrence": "Lawrence",
+    "Anna": "Anna",
+    "John": "John",
+    download: 'Download',
+    updateTimeTxt: 'Updated on {updateTime}',
+    companyName: 'Guangzhou Wawamai Trading Co., Ltd.',
+    addressLabel: 'Address',
+    companyAddress: 'Room D390, 401-466, No. 401-1, Tianyuan Road, Tianhe District, Guangzhou City',
+    hongkongAddressLabel: 'Address (Hong Kong)',
+    hongkongCompanyAddress: 'UNIT 60 3/F YAU LEE CENTER NO.45 HOI YUEN ROAD Kwun Tong Hong Kong',
+    emailLabel: 'Email',
+    hongkongEmailLabel: 'Email (Hong Kong)',
+
   },
   },
   textLink: {
   textLink: {
     "常见问题": "FAQ",
     "常见问题": "FAQ",
@@ -36,7 +46,7 @@ export default {
     "如何运作": "How it works",
     "如何运作": "How it works",
     "为什么选择 VAVA BUY": "Why Choose VAVA BUY",
     "为什么选择 VAVA BUY": "Why Choose VAVA BUY",
     "优势": "Advantage",
     "优势": "Advantage",
-    "在 VAVA BUY 购物": "Shopping at VAVA BUY",
+    "开始在 VAVA BUY 购物": "Start shopping at VAVA BUY",
     "VAVA BUY 是如何工作的": "How VAVA BUY Works",
     "VAVA BUY 是如何工作的": "How VAVA BUY Works",
     "您所在国家/地区的运费和价格": "Shipping & Pricing for your Country",
     "您所在国家/地区的运费和价格": "Shipping & Pricing for your Country",
     "报名": "Sign Up",
     "报名": "Sign Up",
@@ -58,10 +68,69 @@ export default {
     "获取 VAVA BUY 应用程序": "Get the VAVA BUY app",
     "获取 VAVA BUY 应用程序": "Get the VAVA BUY app",
   },
   },
   paragraph: {
   paragraph: {
-    "国际运转·一站解决": "International operation · One-stop solution",
-    "连接全球优质商品,一键下单无忧,实时查看订单进程,享受便捷购物体验。": "Connect to high-quality products from all over the world. Place orders with one click and enjoy worry-free shopping. View the order progress in real time and experience the convenient shopping experience.",
+    "bannerTitle": "International operation · One-stop solution",
+    "bannerDesc": `
+      <div>Access high-quality products from around the world.</div>
+      <div>Place one-click orders and enjoy worry-free shopping.</div>
+      <div>Track your order in real time and enjoy a seamless shopping experience.</div>
+    `,
     "如果你想买其他东西,没有比 Vava buy 更好的了": "If you want to buy anything else, there's no better option than Vava Buy",
     "如果你想买其他东西,没有比 Vava buy 更好的了": "If you want to buy anything else, there's no better option than Vava Buy",
+    "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻": "Great! Everything went smoothly. The speed of receiving orders and shipping them quickly was impressive",
     "非常易于使用,包装很精美,会收获很多惊喜": "Very easy to use, with exquisite packaging, and you will receive many surprises",
     "非常易于使用,包装很精美,会收获很多惊喜": "Very easy to use, with exquisite packaging, and you will receive many surprises",
     "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。": "We use cookies to provide a better online experience. By visiting and using VAVA BUY.com, you are agreeing to our use of cookies. Please read our terms and conditions, usage terms, and privacy policy.",
     "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。": "We use cookies to provide a better online experience. By visiting and using VAVA BUY.com, you are agreeing to our use of cookies. Please read our terms and conditions, usage terms, and privacy policy.",
+  },
+  questionPage: {
+    bannerTitle: 'FAQ',
+    bannerDesc: 'We will be here to answer all your questions',
+    bannerSearchPlaceholder: 'Please enter the key words of your question...',
+    bannerSearchBtn: 'Search',
+    searchResultTitle: 'Search results ({count} items)',
+    questionCategoryMenuBtn: 'Classification',
+    questionCategoryCountTxt: '{count} questions',
+    questionDrawerTitle: 'Problem classification',
+    questionTagHot: 'Hot',
+    questionTagNew: 'New',
+    questionReadCount: 'Read {count} times',
+    questionDetailBackbtn: 'Back',
+    contactTitle: "Didn't find the answer you need?",
+    contactDesc: 'Our customer service team is always at your service',
+    contactForPhoneTitle: 'Telephone support',
+    contactForPhoneWorkTime: 'Monday to Sunday 9:00-18:00',
+    contactForEmailTitle: 'Email',
+    contactForEmailWorkTime: 'We will reply within 24 hours',
+  },
+  comments: {
+    "马库斯": "Marcus",
+    "皮埃尔": "Pierre",
+    "杰森": "Jason",
+    "伊莎贝拉": "Isabella",
+    "杰克": "Jack",
+    "埃里克": "Eric",
+    "亚历山德罗": "Alessandro",
+    "卢卡": "Luca",
+    "劳伦斯": "Lawrence",
+    "安娜": "Anna",
+    "约翰": "John",
+    "奥地利": "Austria",
+    "法国": "France",
+    "英国": "United Kingdom",
+    "西班牙": "Spain",
+    "美国": "United States",
+    "瑞典": "Sweden",
+    "新西兰": "New Zealand",
+    "澳大利亚": "Australia",
+    "意大利": "Italy",
+
+    "如果你想买其他东西,没有比 Vava buy 更好的了": "If you want to buy other things, there's no better option than Vava buy",
+    "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻": "Amazing! Everything went smoothly, and the speed of receiving orders and shipping them out quickly is impressive",
+    "非常易于使用,包装很精美,会收获很多惊喜": "Very easy to use, the packaging is exquisite, and you will get many surprises",
+    "商品易于使用,质量很棒。物流给力,包装精美,客服态度热情!": "The product is easy to use and of great quality. Logistics is efficient, packaging is exquisite, and the customer service is enthusiastic!",
+    "飞快送达,包装妥帖,商品完美。会再来光顾的!": "Delivered quickly, packaging is proper, product is perfect. Will come again!",
+    "国际物流送货上门太赞了!商品没瑕疵,包装抗摔,全程追踪清晰~": "International logistics delivering to the door is awesome! The product has no defects, the packaging is drop-resistant, and the whole process tracking is clear~",
+    "这商品太好用了,操作简单。国际物流全程顺畅,包装精美又牢固!": "This product is very easy to use and simple to operate. International logistics went smoothly throughout, and the packaging is exquisite and sturdy!",
+    "到货比想的快,包装抗造,东西超棒。": "Arrived faster than expected, packaging is durable, product is great.",
+    "商品易于使用,完全满足需求。物流比预期快,客服响应迅速,体验非常好!": "The product is easy to use and fully meets the needs. Logistics is faster than expected, customer service responds quickly, great experience!",
+    "清关过程很顺利,物流速度很快。包装处理得很棒,连说明书都套了保护袋!": "Customs clearance process was smooth, logistics speed is fast. Packaging is handled well, even the manual is covered with a protective bag!",
+    "物流全程透明,从出库到派送都能看到。包装很结实,拆开后商品崭新如初!": "Logistics is transparent throughout, visible from outbound to delivery. Packaging is strong, and the product is as good as new after unpacking!"
   }
   }
 }
 }

+ 133 - 0
src/locale/lang/es.ts

@@ -0,0 +1,133 @@
+export default {
+  system: {
+    "登录": "Inicio",
+    "立即免费注册": "Regístrate ahora gratis",
+    "可以在数千家商店购物和支付": "Posibilidad de comprar y pagar en miles de tiendas",
+    "全球数百万会员享受简单且经济的全球运输": "Millones de miembros en todo el mundo disfrutan de envíos simples y económicos a todo el mundo",
+    "3000 万+": "30 millones +",
+    "600 万+": "6 millones +",
+    "25 +": "25 +",
+    "220 +": "220 +",
+    "运送的包裹": "El paquete entregado",
+    "全球会员": "Membresía global",
+    "服务的国家和地区": "Países y territorios atendidos",
+    "服务年限": "Años de servicio",
+    "VAVA BUY 如何运作": "Cómo funciona VAVA BUY",
+    "注册账户": "Registrar una cuenta",
+    "加入VAVA BUY 以获取您的收货地址": "Únete a VAVA BUY para obtener tu dirección de envío",
+    "下单购买": "Ordenar para comprar",
+    "在商店购物,运送到您的新VAVA BUY地址": "Compra en la tienda, envío a tu nueva dirección de VAVA BUY",
+    "收货转运": "Recepción y transferencia",
+    "合并包裹并节省高达80%的运费": "Combine paquetes y ahorre hasta un 80% en gastos de envío",
+    "VAVA BUY 优势对比": "Comparación de ventajas de VAVA BUY",
+    "包裹运送到全球,而且还在不断增加...": "Paquetes enviados a todo el mundo y contando...",
+    "自 Vava buy.com 成立以来": "Desde la fundación de Vava buy.com",
+    "深受全球购物者的喜爱": "Amado por compradores de todo el mundo",
+    "新西兰": "neozelandés",
+    "澳大利亚": "australiano",
+    "意大利": "italiano",
+    "Lawrence": "laurentino",
+    "Anna": "anna",
+    "John": "john",
+    download: 'Descarga la App',
+    updateTimeTxt: 'Actualizado en {updateTime}',
+    companyName: 'Guangzhou wahou comprar comercio co., LTD',
+    addressLabel: 'dirección',
+    companyAddress: '401-466 casa D390 uno de no.401 tianyuan road, distrito de tianhe, guangzhou',
+    hongkongAddressLabel: 'Dirección (Hong Kong)',
+    hongkongCompanyAddress: 'Habitación 60, 3er piso, centro youli, 45 haiyuan road, guang tang, Hong Kong',
+    emailLabel: 'dirección',
+    hongkongEmailLabel: 'Correo electrónico (Hong Kong)',
+
+  },
+  textLink: {
+    "常见问题": "PREGUNTAS FRECUENTES",
+    "与 VAVA BUY 合作": "Colaboración con VAVA BUY",
+    "如何运作": "Cómo funciona",
+    "为什么选择 VAVA BUY": "Por qué elegir VAVA BUY",
+    "优势": "La ventaja",
+    "开始在 VAVA BUY 购物": "Empieza a comprar en VAVA BUY",
+    "VAVA BUY 是如何工作的": "Cómo funciona VAVA BUY",
+    "您所在国家/地区的运费和价格": "Gastos de envío y precios para su país/región",
+    "报名": "registrarse",
+    "汇率": "Los tipos de cambio",
+    "不能寄送的物品": "Artículos que no pueden ser enviados",
+    "日志": "registro",
+    "您的隐私权利": "Sus derechos de privacidad",
+    "关于 VAVA BUY": "Acerca de VAVA comprar",
+    "VAVA BUY 评论": "Reseñas de VAVA BUY",
+    "新闻与动态": "Noticias y novedades",
+    "VAVA BUY.com 的招聘信息": "Ofertas de empleo en VAVA BUY.com",
+    "联系我们": "Contacta con nosotros",
+    "网站地图": "Mapa del sitio",
+    "奖学金": "beca",
+    "顶级商店": "Las mejores tiendas",
+    "Facebook": "Facebook",
+    "社交平台": "Instagram",
+    "X(原 Twitter)": "X (anteriormente Twitter)",
+    "获取 VAVA BUY 应用程序": "Obtenga la aplicación VAVA BUY",
+  },
+  paragraph: {
+    "bannerTitle": "Operaciones internacionales · una parada para resolver",
+    "bannerDesc": "Conecte productos de calidad global, realice pedidos sin preocupaciones con un solo clic, vea el proceso de pedido en tiempo real y disfrute de una experiencia de compra conveniente.",
+    "如果你想买其他东西,没有比 Vava buy 更好的了": "Si buscas algo más, no hay nada mejor que Vava buy",
+    "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻": "¡Genial! Todo fue muy bien, impresionante la velocidad con la que se recibió el pedido y se envió rápidamente",
+    "非常易于使用,包装很精美,会收获很多惊喜": "Muy fácil de usar, bellamente embalado, cosechará muchas sorpresas",
+    "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。": "Utilizamos cookies para ofrecer una mejor experiencia en línea. Al acceder y utilizar VAVA BUY.com, usted acepta nuestro uso de cookies. Al leer nuestros términos y condiciones, términos de uso y política de privacidad.",
+  },
+  questionPage: {
+    bannerTitle: 'PREGUNTAS FRECUENTES',
+    bannerDesc: 'Estamos aquí para resolver todas tus dudas',
+    bannerSearchPlaceholder: 'Por favor, introduzca la palabra clave de su pregunta...',
+    bannerSearchBtn: 'búsqueda',
+    searchResultTitle: 'Resultados de la búsqueda ({count} items)',
+    questionCategoryMenuBtn: 'clasificación',
+    questionCategoryCountTxt: '{count} preguntas',
+    questionDrawerTitle: 'Preguntas frecuentes por categoría',
+    questionTagHot: 'botines',
+    questionTagNew: 'nuevo',
+    questionReadCount: 'Leído {count} veces',
+    questionDetailBackbtn: 'Volver al listado',
+    contactTitle: '¿No ha encontrado la respuesta que necesita?',
+    contactDesc: 'Nuestro equipo de atención al cliente está a su disposición',
+    contactForPhoneTitle: 'Soporte por teléfono',
+    contactForPhoneWorkTime: 'Lunes a domingo 9:00-18:00',
+    contactForEmailTitle: 'Correo electrónico',
+    contactForEmailWorkTime: 'Respondemos en menos de 24 horas',
+  },
+
+  comments: {
+    "马库斯": "Marcos",
+    "皮埃尔": "Pierre",
+    "杰森": "Jason",
+    "伊莎贝拉": "Isabel",
+    "杰克": "Jack",
+    "埃里克": "Erik",
+    "亚历山德罗": "Alessandro",
+    "卢卡": "Luca",
+    "劳伦斯": "Lorenzo",
+    "安娜": "Ana",
+    "约翰": "Juan",
+    "奥地利": "Austria",
+    "法国": "Francia",
+    "英国": "Reino Unido",
+    "西班牙": "España",
+    "美国": "Estados Unidos",
+    "瑞典": "Suecia",
+    "新西兰": "Nueva Zelanda",
+    "澳大利亚": "Australia",
+    "意大利": "Italia",
+
+    "如果你想买其他东西,没有比 Vava buy 更好的了": "Si quieres comprar otras cosas, no hay nada mejor que Vava buy",
+    "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻": "¡Excelente! Todo fue muy fluido, la velocidad en la recepción del pedido y el envío rápido es impresionante",
+    "非常易于使用,包装很精美,会收获很多惊喜": "Muy fácil de usar, el empaque es muy elegante, habrá muchas sorpresas",
+    "商品易于使用,质量很棒。物流给力,包装精美,客服态度热情!": "El producto es fácil de usar y de excelente calidad. La logística es eficiente, el empaque es hermoso y el servicio al cliente es cálido!",
+    "飞快送达,包装妥帖,商品完美。会再来光顾的!": "Llegó muy rápido, el empaque está bien hecho, el producto es perfecto. Volveré a comprar!",
+    "国际物流送货上门太赞了!商品没瑕疵,包装抗摔,全程追踪清晰~": "¡Genial que la logística internacional haga envíos a domicilio! El producto no tiene defectos, el empaque es resistente a caídas y el seguimiento completo es claro~",
+    "这商品太好用了,操作简单。国际物流全程顺畅,包装精美又牢固!": "Este producto es muy fácil de usar y 操作简单. La logística internacional fue fluida en todo momento, y el empaque es elegante y resistente!",
+    "到货比想的快,包装抗造,东西超棒。": "Llegó más rápido de lo esperado, el empaque es resistente y el producto es excelente.",
+    "商品易于使用,完全满足需求。物流比预期快,客服响应迅速,体验非常好!": "El producto es fácil de usar y satisface completamente las necesidades. La logística es más rápida de lo esperado, el servicio al cliente responde rápidamente, excelente experiencia!",
+    "清关过程很顺利,物流速度很快。包装处理得很棒,连说明书都套了保护袋!": "El proceso de aduana fue muy fluido, la logística es muy rápida. El empaque está muy bien tratado, incluso la hoja de instrucciones está en una bolsa protectora!",
+    "物流全程透明,从出库到派送都能看到。包装很结实,拆开后商品崭新如初!": "La logística es transparente en todo el proceso, se puede ver desde la salida del almacén hasta la entrega. El empaque es muy resistente, y el producto está como nuevo después de desempacarlo!"
+  }
+}

+ 71 - 10
src/locale/lang/zh.ts

@@ -1,6 +1,5 @@
 export default {
 export default {
   system: {
   system: {
-    "中文": "中文",
     "登录": "登录",
     "登录": "登录",
     "立即免费注册": "立即免费注册",
     "立即免费注册": "立即免费注册",
     "可以在数千家商店购物和支付": "可以在数千家商店购物和支付",
     "可以在数千家商店购物和支付": "可以在数千家商店购物和支付",
@@ -19,15 +18,23 @@ export default {
     "下单购买": "下单购买",
     "下单购买": "下单购买",
     "在商店购物,运送到您的新VAVA BUY地址": "在商店购物,运送到您的新VAVA BUY地址",
     "在商店购物,运送到您的新VAVA BUY地址": "在商店购物,运送到您的新VAVA BUY地址",
     "收货转运": "收货转运",
     "收货转运": "收货转运",
-    "合并包裹并节省高达80%的运费": "合并包裹并节省高达80%的运费",
+    "合并包裹并节省高达80%的运费": "合并包裹并节省高达 80% 的运费",
     "VAVA BUY 优势对比": "VAVA BUY 优势对比",
     "VAVA BUY 优势对比": "VAVA BUY 优势对比",
     "包裹运送到全球,而且还在不断增加...": "包裹运送到全球,而且还在不断增加...",
     "包裹运送到全球,而且还在不断增加...": "包裹运送到全球,而且还在不断增加...",
     "自 Vava buy.com 成立以来": "自 Vava buy.com 成立以来",
     "自 Vava buy.com 成立以来": "自 Vava buy.com 成立以来",
     "深受全球购物者的喜爱": "深受全球购物者的喜爱",
     "深受全球购物者的喜爱": "深受全球购物者的喜爱",
-    "新西兰": "新西兰",
-    "澳大利亚": "澳大利亚",
-    "意大利": "意大利",
-    "囧二": "囧二",
+
+
+    download: '下载 App',
+    updateTimeTxt: '更新于 {updateTime}',
+    companyName: '广州哇哇买商贸有限公司',
+    addressLabel: '地址',
+    companyAddress: '广州市天河区天源路401号之一401-466房D390',
+    hongkongAddressLabel: '地址(香港)',
+    hongkongCompanyAddress: '香港观塘海园道45号友利中心3楼60室',
+    emailLabel: '邮箱',
+    hongkongEmailLabel: '邮箱(香港)',
+
   },
   },
   textLink: {
   textLink: {
     "常见问题": "常见问题",
     "常见问题": "常见问题",
@@ -35,7 +42,7 @@ export default {
     "如何运作": "如何运作",
     "如何运作": "如何运作",
     "为什么选择 VAVA BUY": "为什么选择 VAVA BUY",
     "为什么选择 VAVA BUY": "为什么选择 VAVA BUY",
     "优势": "优势",
     "优势": "优势",
-    "在 VAVA BUY 购物": "在 VAVA BUY 购物",
+    "开始在 VAVA BUY 购物": "开始在 VAVA BUY 购物",
     "VAVA BUY 是如何工作的": "VAVA BUY 是如何工作的",
     "VAVA BUY 是如何工作的": "VAVA BUY 是如何工作的",
     "您所在国家/地区的运费和价格": "您所在国家/地区的运费和价格",
     "您所在国家/地区的运费和价格": "您所在国家/地区的运费和价格",
     "报名": "报名",
     "报名": "报名",
@@ -57,10 +64,64 @@ export default {
     "获取 VAVA BUY 应用程序": "获取 VAVA BUY 应用程序",
     "获取 VAVA BUY 应用程序": "获取 VAVA BUY 应用程序",
   },
   },
   paragraph: {
   paragraph: {
-    "国际运转·一站解决": "国际运转·一站解决",
-    "连接全球优质商品,一键下单无忧,实时查看订单进程,享受便捷购物体验。": "连接全球优质商品,一键下单无忧,实时查看订单进程,享受便捷购物体验。",
+    "bannerTitle": "国际运转·一站解决",
+    "bannerDesc": "连接全球优质商品,一键下单无忧,实时查看订单进程,享受便捷购物体验。",
+    "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。": "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。",
+  },
+  questionPage: {
+    bannerTitle: '常见问题',
+    bannerDesc: '我们将在这里为您解答所有疑问',
+    bannerSearchPlaceholder: '请输入您的问题关键词...',
+    bannerSearchBtn: '搜索',
+    searchResultTitle: '搜索结果 ({count}条)',
+    questionCategoryMenuBtn: '分类',
+    questionCategoryCountTxt: '{count}个问题',
+    questionDrawerTitle: '常见问题分类',
+    questionTagHot: '热门',
+    questionTagNew: '新',
+    questionReadCount: '阅读 {count} 次',
+    questionDetailBackbtn: '返回列表',
+    contactTitle: '没有找到您需要的答案?',
+    contactDesc: '我们的客服团队随时为您服务',
+    contactForPhoneTitle: '电话支持',
+    contactForPhoneWorkTime: '周一至周日 9:00-18:00',
+    contactForEmailTitle: '电子邮件',
+    contactForEmailWorkTime: '我们会在24小时内回复',
+  },
+  comments: {
+    "马库斯": "马库斯",
+    "皮埃尔": "皮埃尔",
+    "杰森": "杰森",
+    "伊莎贝拉": "伊莎贝拉",
+    "杰克": "杰克",
+    "埃里克": "埃里克",
+    "亚历山德罗": "亚历山德罗",
+    "卢卡": "卢卡",
+    "劳伦斯": "劳伦斯",
+    "安娜": "安娜",
+    "约翰": "约翰",
+
+
+    "奥地利": "奥地利",
+    "法国": "法国",
+    "英国": "英国",
+    "西班牙": "西班牙",
+    "美国": "美国",
+    "瑞典": "瑞典",
+    "新西兰": "新西兰",
+    "澳大利亚": "澳大利亚",
+    "意大利": "意大利",
+
     "如果你想买其他东西,没有比 Vava buy 更好的了": "如果你想买其他东西,没有比 Vava buy 更好的了",
     "如果你想买其他东西,没有比 Vava buy 更好的了": "如果你想买其他东西,没有比 Vava buy 更好的了",
+    "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻": "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻",
     "非常易于使用,包装很精美,会收获很多惊喜": "非常易于使用,包装很精美,会收获很多惊喜",
     "非常易于使用,包装很精美,会收获很多惊喜": "非常易于使用,包装很精美,会收获很多惊喜",
-    "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。": "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。",
+    "商品易于使用,质量很棒。物流给力,包装精美,客服态度热情!": "商品易于使用,质量很棒。物流给力,包装精美,客服态度热情!",
+    "飞快送达,包装妥帖,商品完美。会再来光顾的!": "飞快送达,包装妥帖,商品完美。会再来光顾的!",
+    "国际物流送货上门太赞了!商品没瑕疵,包装抗摔,全程追踪清晰~": "国际物流送货上门太赞了!商品没瑕疵,包装抗摔,全程追踪清晰~",
+    "这商品太好用了,操作简单。国际物流全程顺畅,包装精美又牢固!": "这商品太好用了,操作简单。国际物流全程顺畅,包装精美又牢固!",
+    "到货比想的快,包装抗造,东西超棒。": "到货比想的快,包装抗造,东西超棒。",
+    "商品易于使用,完全满足需求。物流比预期快,客服响应迅速,体验非常好!": "商品易于使用,完全满足需求。物流比预期快,客服响应迅速,体验非常好!",
+    "清关过程很顺利,物流速度很快。包装处理得很棒,连说明书都套了保护袋!": "清关过程很顺利,物流速度很快。包装处理得很棒,连说明书都套了保护袋!",
+    "物流全程透明,从出库到派送都能看到。包装很结实,拆开后商品崭新如初!": "物流全程透明,从出库到派送都能看到。包装很结实,拆开后商品崭新如初!",
   }
   }
 }
 }

+ 6 - 2
src/main.ts

@@ -10,12 +10,16 @@ import "swiper/css/swiper.min.css"
 
 
 import "@/utils/rem"
 import "@/utils/rem"
 
 
+import "@/utils/request"
+
 import App from './App.vue'
 import App from './App.vue'
 import router from './router'
 import router from './router'
 
 
 
 
-createApp(App)
-  .use(createPinia())
+const app = createApp(App)
+
+
+app.use(createPinia())
   .use(router)
   .use(router)
   .use(Antd)
   .use(Antd)
   .use(i18n)
   .use(i18n)

二進制
src/public/images/avatar/avatar_1.jpg


二進制
src/public/images/avatar/avatar_10.jpg


二進制
src/public/images/avatar/avatar_11.jpg


二進制
src/public/images/avatar/avatar_2.jpg


二進制
src/public/images/avatar/avatar_3.jpg


二進制
src/public/images/avatar/avatar_4.jpg


二進制
src/public/images/avatar/avatar_5.jpg


二進制
src/public/images/avatar/avatar_6.jpg


二進制
src/public/images/avatar/avatar_7.jpg


二進制
src/public/images/avatar/avatar_8.jpg


二進制
src/public/images/avatar/avatar_9.jpg


+ 0 - 0
src/public/images/avatar01.png → src/public/images/avatar_2.png


二進制
src/public/images/download_qrcode.png


二進制
src/public/images/icons/aodaliya_logo_icon.png


二進制
src/public/images/icons/app_store_icon.png


二進制
src/public/images/icons/download_icon.png


二進制
src/public/images/icons/menu_icon.png


二進制
src/public/images/icons/play_store_icon.png


二進制
src/public/images/icons/xinxilan_logo_icon.png


二進制
src/public/images/icons/yidali_logo_icon.png


二進制
src/public/images/logo_icon.png


+ 14 - 3
src/router/index.ts

@@ -10,13 +10,24 @@ const routes: Array<RouteRecordRaw> = [
     path: '/',
     path: '/',
     name: 'layout',
     name: 'layout',
     component: Layout,
     component: Layout,
-    redirect: '/home',
+    // redirect: '/home',
     children: [
     children: [
       {
       {
-        path: 'home',
+        // path: 'home',
+        path: '',
         name: 'home',
         name: 'home',
         component: Home
         component: Home
-      }
+      },
+      {
+        path: 'faq',
+        name: 'FAQ',
+        component: () => import("@/views/FAQ/index.vue")
+      },
+      {
+        path: 'article/article_details',
+        name: 'article_details',
+        component: () => import("@/views/ArticleDetails/index.vue")
+      },
     ]
     ]
   },
   },
 ]
 ]

+ 116 - 0
src/stores/useFooterStore.ts

@@ -0,0 +1,116 @@
+import { defineStore } from "pinia"
+import { useRoute, useRouter } from 'vue-router'
+import { getContentHelpApi } from '@/apis/article'
+
+
+
+export interface ILinkInfo {
+  links: string;
+  title: string;
+  id: string | number;
+}
+
+export interface IData {
+  id: number;
+  pid: number;
+  name: string;
+  sort: number;
+  code: string | null;
+  lists: Array<ILinkInfo>; 
+}
+
+// store state
+export interface IFooterState {
+  linkList: Array<IData>;
+  activeLinkId: string | number;
+  activeLinkInfo: ILinkInfo | null;
+  activeLinkCateInfo: IData | null;
+}
+
+
+
+const useFooterStores = defineStore('footer', {
+  state: (): IFooterState => ({
+    linkList: [],
+    activeLinkId: '',
+    activeLinkInfo: null,
+    activeLinkCateInfo: null,
+  }),
+
+  getters: {
+    activeInfo(): ILinkInfo | null {
+      if (this.activeLinkInfo) return this.activeLinkInfo
+      if (this.activeLinkCateInfo && this.activeLinkId) {
+        const result = this.activeLinkCateInfo.lists.find((info: ILinkInfo) => (info.id == this.activeLinkId))
+        return result ? result : null
+      }
+      return null
+    }
+  },
+
+  actions: {
+
+    // 获取数据列表
+    fetchHelpDatas(lang: string) {
+      const route = useRoute()
+      const cateId = route.query.cateId ? route.query.cateId : ''
+      const id = route.query.id ? route.query.id : ''
+  
+      getContentHelpApi({ lang })
+        .then((result) => {
+          this.linkList = result.data
+          console.log('this.linkList', this.linkList)
+          if (this.linkList.length > 0) {
+            if (cateId) {
+              const cateInfo: IData | undefined = this.linkList.find((item: IData) => (`${item.id}` == cateId))
+              const linkInfo: ILinkInfo | undefined = cateInfo ? cateInfo.lists.find((info: ILinkInfo) => (`${info.id}` == id)) : undefined
+              this.updateActiveInfo(cateInfo ? cateInfo : null, linkInfo ? linkInfo : null)
+            }
+            else if (id) {
+              let linkInfo: ILinkInfo | null = null
+              for (let i = 0; i < this.linkList.length; i++) {
+                for (let j = 0; j < this.linkList[i].lists.length; j++) {
+                  if (`${this.linkList[i].lists[j].id}` == id) {
+                    linkInfo = this.linkList[i].lists[j]
+                    break
+                  }
+                }
+                if (linkInfo) {
+                  break
+                }
+              }
+              this.updateActiveInfo(null, linkInfo)
+            }
+          }
+        })
+        .catch((err) => {
+  
+        })
+    },
+
+    // 更新 active 信息
+    updateActiveInfo(cateInfo: IData | null, linkInfo: ILinkInfo | null, callback?: Function) {
+      if (!linkInfo) {
+        this.activeLinkId = ''
+        this.activeLinkInfo = null
+        this.activeLinkCateInfo = null
+      }
+      else if (linkInfo.links) {
+        console.log(linkInfo.links)
+        window.open(linkInfo.links, '__blank')
+        return
+      }
+      else {
+        this.activeLinkId = linkInfo.id
+        this.activeLinkInfo = linkInfo
+        this.activeLinkCateInfo = cateInfo
+      }
+      callback && callback()
+    }
+  }
+})
+
+
+
+
+export default useFooterStores

+ 45 - 15
src/stores/useSystemStore.ts

@@ -1,19 +1,25 @@
 import { defineStore } from 'pinia'
 import { defineStore } from 'pinia'
 import { getCurrentLang } from '@/locale/index'
 import { getCurrentLang } from '@/locale/index'
-
+import { getLocaleListApi } from '@/apis/system'
 
 
 
 
 export interface ISystemState {
 export interface ISystemState {
   locale: string;
   locale: string;
-  localeList: ILocale[];
+  localeList: ILangs[];
+  screenWidth: number;
 }
 }
 
 
-
-export interface ILocale {
-  name: string;
-  value: string;
+export interface ILangs {
+  code: string;
+  title: string; 
 }
 }
 
 
+export interface ICurrencys {
+  code: string;
+  title: string;
+  symbol: string;
+  rate: string | number; 
+}
 
 
 
 
 
 
@@ -21,24 +27,48 @@ const useSystemStore = defineStore('system', {
   state: (): ISystemState => ({
   state: (): ISystemState => ({
     locale: getCurrentLang() as string,
     locale: getCurrentLang() as string,
     localeList: [
     localeList: [
-      { name: '简体中文', value: 'zhCN' },
-      { name: 'English', value: 'en' },
-    ]
+      { title: '中文(简体)', code: 'zh' },
+      { title: 'English', code: 'en' },
+    ],
+    screenWidth: 1920
   }),
   }),
 
 
   getters: {
   getters: {
-    localeInfo: function(): ILocale {
-      const result = this.localeList.find((item: ILocale) => (item.value == this.locale))
-      return result ? result : this.localeList[0]
+    localeInfo: function (): ILangs {
+      const result = this.localeList.find((item: ILangs) => (item.code == this.locale))
+      return result ? result : this.localeList[1]
     }
     }
   },
   },
 
 
   actions: {
   actions: {
-    toggleLocale(localeInfo: ILocale) {
+    toggleLocale(localeInfo: ILangs) {
       if (localeInfo) {
       if (localeInfo) {
-        this.locale = localeInfo.value
-        localStorage.setItem('locale', localeInfo.value)
+        this.locale = localeInfo.code
+        localStorage.setItem('locale', localeInfo.code)
+      }
+    },
+
+    updateScreenWidth(width: number) {
+      if (width < 325) {
+        this.screenWidth = 325
       }
       }
+      else {
+        this.screenWidth = width
+      }
+    },
+
+    fetchLocaleList() {
+      type resultType = Object & {
+        data: { langs: ILangs[] }
+      }
+      getLocaleListApi({})
+        .then((result: resultType) => {
+          console.log('---- result -----', result)
+          this.localeList = result.data.langs && result.data.langs.length > 0 ? result.data.langs : this.localeList
+        })
+        .catch((err: Error) => {
+
+        })
     }
     }
   }
   }
 })
 })

+ 3 - 0
src/utils/rem.ts

@@ -1,3 +1,6 @@
+
+
+
 // 基准大小
 // 基准大小
 const baseSize = 16
 const baseSize = 16
 
 

+ 46 - 5
src/utils/request.ts

@@ -1,10 +1,51 @@
-import axios from "axios";
+import axios from 'axios'
 
 
 
 
-const baseUrl = import.meta.env.VITE_BASE_URL
+export const BASE_URL = import.meta.env.VITE_BASE_URL
 
 
 
 
-const instance = axios.create({
-  baseURL: baseUrl,
+//用来拦截用的
+axios.defaults.headers.post["Content-Type"] = "application/json;charset=utf-8";
+
+const http = axios.create({
+  baseURL: BASE_URL,
   timeout: 1000 * 60 * 5,
   timeout: 1000 * 60 * 5,
-})
+})
+
+
+ 
+// 请求拦截器
+http.interceptors.request.use(
+  config => {
+    let token = localStorage.getItem('token')
+    if (token) {
+      config.headers.token = token
+    }
+    let lang = localStorage.getItem('locale')
+    lang = lang
+    config.headers.lang = lang ? lang : 'en'
+    return config
+  },
+  err => {
+    return Promise.reject(err)
+  }
+)
+ 
+// 响应拦截器
+http.interceptors.response.use(
+  res => {
+    // console.log('res', res)
+    if (res.data.ret == 1) {
+      return Promise.resolve(res.data)
+    }
+    else{
+      return Promise.reject(res.data)
+    }
+  },
+  err => {
+    return Promise.reject(err)
+  }
+)
+ 
+
+export default http

+ 112 - 0
src/views/ArticleDetails/hooks/useData.ts

@@ -0,0 +1,112 @@
+import { ref, watch } from 'vue'
+import { useRoute } from 'vue-router'
+import { getContentDetailApi, getContentDetailByKeywordApi } from '@/apis/article'
+
+
+export interface IDataInfo {
+  id: number;
+  title: string;
+  desc: string;
+  content: string;
+  picture: string;
+  inorder: number;
+  show: number;
+  indate: number;
+  update: number;
+  links: any;
+  linkType: any;
+  position: string;
+  type: string;
+  category: number;
+  lang: string; 
+}
+
+
+function useData() {
+
+  // route 对象
+  const route = useRoute()
+  console.log('route', route)
+  const articleId = ref<string | number>('')
+  const articleType = ref<string>('')
+  const dataInfo = ref<IDataInfo | string | null>(null)
+  const loading = ref<boolean>(false)
+
+
+  // 监听路由 id 变化
+  watch(
+    () => route.query,
+    (newVal) => {
+      console.log('newVal', newVal)
+      if (newVal.type) {
+        articleType.value = newVal.type as string
+      }
+      else {
+        articleType.value = ''
+      }
+      if (newVal.id) {
+        articleId.value = (newVal.id as (string | number))
+        fetchData()
+      }
+    },
+    {
+      immediate: true
+    }
+  )
+
+  function fetchData() {
+    switch(articleType.value) {
+      case 'header':
+        fetchArticleDetailByKeyword()
+        break
+      default:
+        fetchArticleDetailById()
+        break
+    }
+  }
+
+
+  // 根据 id 获取文章详情
+  function fetchArticleDetailById() {
+    loading.value = true
+    getContentDetailApi({ id: articleId.value })
+      .then(result => {
+        dataInfo.value = result.data
+      })
+      .catch(err => {
+
+      })
+      .finally(() => {
+        loading.value = false
+      })
+  }
+
+
+  // 根据关键词获取文章详情
+  function fetchArticleDetailByKeyword() {
+    loading.value = true
+    getContentDetailByKeywordApi({ type: articleId.value })
+      .then(result => {
+        dataInfo.value = result.data
+      })
+      .catch(err => {
+
+      })
+      .finally(() => {
+        loading.value = false
+      })
+  }
+
+
+  return {
+    articleId,
+    articleType,
+    dataInfo,
+    loading,
+    fetchArticleDetailById
+  }
+}
+
+
+
+export default useData

+ 223 - 0
src/views/ArticleDetails/index.vue

@@ -0,0 +1,223 @@
+<template>
+  <section class="article-container">
+    <div class="loading-box" v-show="loading">
+      <a-spin :spinning="loading" size="large" />
+    </div>
+    <template v-if="!loading">
+      <template v-if="articleType != 'header'">
+        <!-- 文章标题 -->
+        <h1 class="article-title">{{ dataInfo ? dataInfo.title : '' }}</h1>
+
+        <!-- 文章元信息 -->
+        <!-- <div class="article-meta">
+          <div class="author-info">
+            <img :src="article.author.avatar" class="author-avatar" alt="作者头像" />
+            <span class="author-name">{{ article.author.name }}</span>
+          </div>
+          <div class="publish-time" v-if="activeInfo">
+            <span>{{
+              $t('system["updateTimeTxt"]', {
+                updateTime: formatDate(activeInfo.publishTime, locale),
+              })
+            }}</span>
+          </div>
+        </div> -->
+      </template>
+
+      <!-- 富文本内容区域 -->
+      <div class="article-content" v-html="dataInfo" v-if="articleType == 'header'"></div>
+      <div class="article-content" v-html="dataInfo ? dataInfo.content : ''" v-else></div>
+
+      <!-- 标签 -->
+      <!-- <div class="article-tags" v-if="activeInfo.tags && activeInfo.tags.length">
+        <div class="tag" v-for="(tag, i) in activeInfo.tags" :key="i">
+          <span>{{ tag }}</span>
+        </div>
+      </div> -->
+    </template>
+  </section>
+</template>
+
+<script setup lang="ts">
+import { storeToRefs } from 'pinia'
+import useSystemStore from '@/stores/useSystemStore'
+import useData from './hooks/useData'
+import type { IDataInfo } from './hooks/useData'
+
+// system store
+const systemStore = useSystemStore()
+const { locale } = storeToRefs(systemStore)
+
+const { dataInfo, articleType, loading } = useData()
+
+// 格式化日期
+const formatDate = (dateString: string, lang: string) => {
+  const options = { year: 'numeric', month: 'long', day: 'numeric' }
+  return new Date(dateString).toLocaleDateString(lang, options as Object)
+}
+</script>
+
+<style scoped lang="less">
+.article-container {
+  max-width: 800px;
+  max-width: 75rem;
+  max-width: 50rem;
+  margin: 0 auto;
+  padding: 60px 15px;
+  padding: 3.75rem 0.9375rem;
+  font-family: 'Helvetica Neue', Arial, sans-serif;
+  color: #333;
+}
+
+.loading-box {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
+.article-title {
+  font-size: var(--title-size);
+  margin-bottom: 20px;
+  margin-bottom: 1.25rem;
+  color: #222;
+  text-align: center;
+}
+
+.article-meta {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-wrap: wrap;
+  gap: 20px;
+  gap: 1.25rem;
+  margin-bottom: 30px;
+  margin-bottom: 1.875rem;
+  padding-bottom: 20px;
+  padding-bottom: 1.25rem;
+  border-bottom: 1px solid #eee;
+  color: #666;
+  font-size: 0.9rem;
+}
+
+.author-info {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  gap: 0.625rem;
+}
+
+.author-avatar {
+  width: 40px;
+  width: 2.5rem;
+  height: 40px;
+  height: 2.5rem;
+  border-radius: 50%;
+  object-fit: cover;
+}
+
+.author-name {
+  font-weight: bold;
+}
+
+.article-content {
+  margin-bottom: 40px;
+  margin-bottom: 2.5rem;
+  font-size: 1rem;
+  line-height: 1.6;
+}
+
+.article-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  gap: 0.625rem;
+  margin-top: 30px;
+  margin-top: 1.875rem;
+  padding-top: 20px;
+  padding-top: 1.25rem;
+  border-top: 1px solid #eee;
+}
+
+.article-tags .tag {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 4px 12px;
+  padding: 0.25rem 0.75rem;
+  background-color: #f0f0f0;
+  border-radius: 20px;
+  border-radius: 1.25rem;
+  font-size: 0.8rem;
+  color: #555;
+}
+</style>
+
+<style>
+/* 全局样式用于富文本内容 */
+.article-content h2 {
+  font-size: 1.8rem;
+  margin: 30px 0 15px;
+  margin: 1.875rem 0 0.9375rem;
+  color: #222;
+  border-bottom: 1px solid #eee;
+  padding-bottom: 10px;
+  padding-bottom: 0.625rem;
+}
+
+.article-content h3 {
+  font-size: 1.4rem;
+  margin: 25px 0 12px;
+  margin: 1.5625rem 0 0.75rem;
+  color: #333;
+}
+
+.article-content p {
+  margin: 15px 0;
+  margin: 0.9375rem 0;
+  font-size: 1rem;
+}
+
+.article-content ul,
+.article-content ol {
+  margin: 15px 0;
+  margin: 0.9375rem 0;
+  padding-left: 30px;
+  padding-left: 1.875rem;
+}
+
+.article-content li {
+  margin: 8px 0;
+  margin: 0.5rem 0;
+}
+
+.article-content a {
+  color: var(--color-active);
+  text-decoration: none;
+}
+
+.article-content a:hover {
+  text-decoration: underline;
+}
+
+.article-content img {
+  width: 100%;
+  max-width: 100%;
+  height: auto;
+  margin: 20px 0;
+  margin: 1.25rem 0;
+  border-radius: 4px;
+  border-radius: 0.25rem;
+}
+
+.article-content pre {
+  background-color: #f6f8fa;
+  padding: 16px;
+  padding: 1rem;
+  border-radius: 6px;
+  border-radius: 0.375rem;
+  overflow: auto;
+  margin: 20px 0;
+  margin: 1.25rem 0;
+}
+</style>

+ 1 - 0
src/views/FAQ/css/styles@1024.less

@@ -0,0 +1 @@
+@media screen and (max-width: 1024px) {}

+ 1 - 0
src/views/FAQ/css/styles@1200.less

@@ -0,0 +1 @@
+@media screen and (max-width: 1200px) {}

+ 32 - 0
src/views/FAQ/css/styles@768.less

@@ -0,0 +1,32 @@
+@media screen and (max-width: 768px) {
+  .question-banner {
+
+    margin-bottom: 8px;
+    margin-bottom: 0.5rem;
+
+    h1 {
+      font-size: 32px;
+      font-size: 2rem;
+    }
+
+    p {
+      font-size: 1rem;
+    }
+  }
+
+  .question-main-768 {
+    display: block;
+  }
+
+  .question-main-normal {
+    display: none;
+  }
+
+  .detail-content h3 {
+    font-size: 1.375rem !important;
+  }
+
+  .detail-content h4 {
+    font-size: 1.125rem !important;
+  }
+}

+ 125 - 0
src/views/FAQ/hooks/useData.ts

@@ -0,0 +1,125 @@
+import { ref, computed } from 'vue'
+import { getFAQCateListApi } from '@/apis/article'
+
+
+// 问题
+export interface IFAQInfo {
+  id: number;
+  title: string;
+  links: string;
+  content: string; 
+}
+
+// 分类列表
+export interface ICateInfo {
+  id: number;
+  pid: number;
+  name: string;
+  sort: number;
+  code: number | string;
+  lists: Array<IFAQInfo>; 
+}
+
+
+function useData() {
+
+  // 分类列表
+  const categoryList = ref<ICateInfo[]>([])
+  const isLoading = ref<boolean>(false)
+  // 当前选中的分类
+  const activeCategoryId = ref<string | number>(0)          // id
+  const activeCategoryInfo = ref<ICateInfo | null>(null)    // 分类信息
+   // 当前查看的问题详情
+   const currentQuestion = ref<IFAQInfo | null>(null)
+
+  // 搜索查询词
+  const searchQuery = ref<string>('')
+  // 搜索结果
+  const searchResults = ref<Array<IFAQInfo>>([])
+
+
+  // 切换分类
+  const changeCategory = (categoryInfo: ICateInfo) => {
+    activeCategoryInfo.value = categoryInfo
+    activeCategoryId.value = categoryInfo.id
+    currentQuestion.value = null
+    searchResults.value = []
+  }
+
+
+  // 搜索问题
+  const searchQuestions = () => {
+    if (!searchQuery.value.trim()) {
+      searchResults.value = []
+      return
+    }
+
+    const query = searchQuery.value.toLowerCase()
+    const list: IFAQInfo[] = []
+    for (let i = 0; i < categoryList.value.length; i++) {
+      let cateInfo: ICateInfo = categoryList.value[i]
+      for (let j = 0; j < cateInfo.lists.length; j++) {
+        let faqInfo: IFAQInfo = cateInfo.lists[j]
+        if (faqInfo.title.toLowerCase().includes(query) || faqInfo.content.toLowerCase().includes(query)) {
+          list.push(faqInfo)
+        }
+      }
+    }
+    searchResults.value = list
+    currentQuestion.value = null
+  }
+
+
+  // 显示问题详情
+  const showQuestionDetail = (question: IFAQInfo) => {
+    currentQuestion.value = question
+  }
+
+  // 返回问题列表
+  const backToList = () => {
+    currentQuestion.value = null
+  }
+
+
+  // 获取问题列表
+  function fetchFAQList() {
+    isLoading.value = true
+    getFAQCateListApi({})
+      .then((result) => {
+        // console.log('result', result)
+        if (result.data && result.data.length > 0) {
+          categoryList.value =  result.data
+          activeCategoryInfo.value = result.data[0]
+          activeCategoryId.value = result.data[0].id
+        }
+        else {
+          categoryList.value =  []
+          activeCategoryInfo.value = null
+          activeCategoryId.value = ''
+        }
+      })
+      .catch(err => {})
+      .finally(() => {
+        isLoading.value = false
+      })
+  }
+  fetchFAQList()
+
+  return {
+    categoryList,
+    isLoading,
+    activeCategoryId,
+    activeCategoryInfo,
+    currentQuestion,
+    searchQuery,
+    searchResults,
+    changeCategory,
+    showQuestionDetail,
+    searchQuestions,
+    backToList,
+  }
+}
+
+
+
+export default useData

+ 916 - 0
src/views/FAQ/index.vue

@@ -0,0 +1,916 @@
+<template>
+  <div class="question-container">
+    <!-- 顶部横幅 -->
+    <div class="question-banner">
+      <h1>{{ $t('questionPage["bannerTitle"]') }}</h1>
+      <p>{{ $t('questionPage["bannerDesc"]') }}</p>
+
+      <!-- 搜索框 -->
+      <div class="question__search-box">
+        <input
+          type="text"
+          v-model="searchQuery"
+          :placeholder="$t(`questionPage['bannerSearchPlaceholder']`)"
+          @keyup.enter="searchQuestions"
+        />
+        <button @click="searchQuestions">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="16"
+            height="16"
+            fill="currentColor"
+            viewBox="0 0 16 16"
+          >
+            <path
+              d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
+            />
+          </svg>
+          {{ $t(`questionPage['bannerSearchBtn']`) }}
+        </button>
+      </div>
+    </div>
+
+    <!-- 主要内容区--移动端 -->
+    <section class="width-1200 question-main-768">
+      <div class="question-main__container question-main__container-768">
+        <!-- 问题列表&内容 -->
+        <div class="question-content">
+          <template v-if="!currentQuestion">
+            <!-- 搜索结果列表 -->
+            <div v-if="searchResults.length > 0" class="search-results question-list">
+              <h2>
+                <span style="flex: 1; padding-right: 16px; padding-right: 1rem; line-height: 1.3">{{
+                  $t('questionPage["searchResultTitle"]', { count: searchResults.length })
+                }}</span>
+                <div class="category-menu-btn" @click="showCategoryDrawer">
+                  <span>{{ $t('questionPage["questionCategoryMenuBtn"]') }}</span>
+                  <img src="@/public/images/icons/menu_icon.png" alt="" />
+                </div>
+              </h2>
+              <div
+                v-for="item in searchResults"
+                :key="item.id"
+                class="question-item"
+                @click="showQuestionDetail(item)"
+              >
+                <div class="question-title">
+                  {{ item.title }}
+                </div>
+                <div class="question-preview" v-html="item.content"></div>
+              </div>
+            </div>
+
+            <!-- 问题列表 -->
+            <div v-else class="question-list">
+              <h2>
+                <span style="flex: 1; padding-right: 16px; padding-right: 1rem; line-height: 1.3">{{
+                  activeCategoryInfo ? activeCategoryInfo.name : ''
+                }}</span>
+                <div class="category-menu-btn" @click="showCategoryDrawer">
+                  <span>{{ $t('questionPage["questionCategoryMenuBtn"]') }}</span>
+                  <img src="@/public/images/icons/menu_icon.png" alt="" />
+                </div>
+              </h2>
+
+              <template v-if="activeCategoryInfo">
+                <div
+                  v-for="(question, index) in activeCategoryInfo.lists"
+                  :key="index"
+                  class="question-item"
+                  @click="showQuestionDetail(question)"
+                >
+                  <div class="question-title">
+                    {{ question.title }}
+                    <!-- <div v-if="question.isHot" class="question-tag hot-tag">
+                      {{ $t('questionPage["questionTagHot"]') }}
+                    </div>
+                    <div v-if="question.isNew" class="question-tag new-tag">
+                      {{ $t('questionPage["questionTagNew"]') }}
+                    </div> -->
+                  </div>
+                  <div class="question-preview" v-html="question.content"></div>
+                </div>
+              </template>
+            </div>
+          </template>
+
+          <!-- 问题详情 -->
+          <div v-else class="question-detail">
+            <div class="detail-footer">
+              <button class="back-btn" @click="backToList">
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  width="16"
+                  height="16"
+                  fill="currentColor"
+                  viewBox="0 0 16 16"
+                >
+                  <path
+                    fill-rule="evenodd"
+                    d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"
+                  />
+                </svg>
+                {{ $t('questionPage["questionDetailBackbtn"]') }}
+              </button>
+
+              <!-- <div class="feedback">
+                <span>这篇回答对您有帮助吗?</span>
+                <button class="feedback-btn yes">有帮助</button>
+                <button class="feedback-btn no">没帮助</button>
+              </div> -->
+            </div>
+
+            <div class="detail-header">
+              <h2>{{ currentQuestion.title }}</h2>
+            </div>
+
+            <div class="detail-content" v-html="currentQuestion.content"></div>
+          </div>
+        </div>
+
+        <div class="loading-box" v-show="isLoading">
+          <a-spin :spinning="isLoading" size="large" />
+        </div>
+      </div>
+
+      <!-- 分类导航侧边抽屉 -->
+      <a-drawer
+        :open="visible"
+        :closable="false"
+        :title="$t(`questionPage['questionDrawerTitle']`)"
+        placement="right"
+        width="70%"
+        getContainer=".question-main__container-768"
+        class=""
+        @close="closeCategoryDrawer"
+      >
+        <div class="question-sidebar">
+          <div
+            v-for="category in categoryList"
+            :key="category.id"
+            class="category-item"
+            :class="{ active: activeCategoryId === category.id }"
+            @click="changeCategory(category)"
+          >
+            <label class="category-item__name">{{ category.name }}</label>
+            <span class="count">{{
+              $t('questionPage["questionCategoryCountTxt"]', { count: category.lists.length })
+            }}</span>
+          </div>
+        </div>
+      </a-drawer>
+    </section>
+
+    <!-- 主要内容区 -->
+    <div class="question-main__container question-main-normal width-1200">
+      <!-- 左侧分类导航 -->
+      <div class="question-sidebar">
+        <a-skeleton-button
+          :active="true"
+          size="large"
+          shape="default"
+          block="true"
+          v-if="isLoading"
+        />
+        <template v-else>
+          <div
+            v-for="category in categoryList"
+            :key="category.id"
+            class="category-item"
+            :class="{ active: activeCategoryId === category.id }"
+            @click="changeCategory(category)"
+          >
+            <label class="category-item__name">{{ category.name }}</label>
+            <span class="count">{{
+              $t('questionPage["questionCategoryCountTxt"]', { count: category.lists.length })
+            }}</span>
+          </div>
+        </template>
+      </div>
+
+      <!-- 右侧内容 -->
+      <div class="question-content">
+        <!-- 问题列表 -->
+        <template v-if="!currentQuestion">
+          <!-- 搜索结果列表 -->
+          <div v-if="searchResults.length > 0" class="search-results">
+            <h3>{{ $t('questionPage["searchResultTitle"]', { count: searchResults.length }) }}</h3>
+            <div
+              v-for="item in searchResults"
+              :key="item.id"
+              class="question-item"
+              @click="showQuestionDetail(item)"
+            >
+              {{ item.title }}
+            </div>
+          </div>
+
+          <!-- 问题列表 -->
+          <div v-else class="question-list">
+            <h2>{{ activeCategoryInfo ? activeCategoryInfo.name : '' }}</h2>
+
+            <template v-if="activeCategoryInfo">
+              <div
+                v-for="question in activeCategoryInfo.lists"
+                :key="question.id"
+                class="question-item"
+                @click="showQuestionDetail(question)"
+              >
+                <div class="question-title">
+                  {{ question.title }}
+                  <!-- <div v-if="question.isHot" class="question-tag hot-tag">
+                    {{ $t('questionPage["questionTagHot"]') }}
+                  </div>
+                  <div v-if="question.isNew" class="question-tag new-tag">
+                    {{ $t('questionPage["questionTagNew"]') }}
+                  </div> -->
+                </div>
+                <div class="question-preview" v-html="question.content"></div>
+              </div>
+            </template>
+          </div>
+        </template>
+
+        <!-- 问题详情 -->
+        <div v-else class="question-detail">
+          <div class="detail-header">
+            <h2>{{ currentQuestion.title }}</h2>
+          </div>
+
+          <div class="detail-content" v-html="currentQuestion.content"></div>
+
+          <div class="detail-footer">
+            <button class="back-btn" @click="backToList">
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                width="16"
+                height="16"
+                fill="currentColor"
+                viewBox="0 0 16 16"
+              >
+                <path
+                  fill-rule="evenodd"
+                  d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"
+                />
+              </svg>
+              {{ $t('questionPage["questionDetailBackbtn"]') }}
+            </button>
+
+            <!-- <div class="feedback">
+              <span>这篇回答对您有帮助吗?</span>
+              <button class="feedback-btn yes">有帮助</button>
+              <button class="feedback-btn no">没帮助</button>
+            </div> -->
+          </div>
+        </div>
+
+        <div class="loading-box" v-show="isLoading">
+          <a-spin :spinning="isLoading" size="large" />
+        </div>
+      </div>
+    </div>
+
+    <!-- 联系客服 -->
+    <div class="contact-section width-1200">
+      <h3>{{ $t('questionPage["contactTitle"]') }}</h3>
+      <p>{{ $t('questionPage["contactDesc"]') }}</p>
+      <div class="contact-methods">
+        <div class="contact-method">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="24"
+            height="24"
+            fill="currentColor"
+            viewBox="0 0 16 16"
+          >
+            <path
+              d="M3.654 1.328a.678.678 0 0 0-1.015-.063L1.605 2.3c-.483.484-.661 1.169-.45 1.77a17.568 17.568 0 0 0 4.168 6.608 17.569 17.569 0 0 0 6.608 4.168c.601.211 1.286.033 1.77-.45l1.034-1.034a.678.678 0 0 0-.063-1.015l-2.307-1.794a.678.678 0 0 0-.58-.122l-2.19.547a1.745 1.745 0 0 1-1.657-.459L5.482 8.062a1.745 1.745 0 0 1-.46-1.657l.548-2.19a.678.678 0 0 0-.122-.58L3.654 1.328zM1.884.511a1.745 1.745 0 0 1 2.612.163L6.29 2.98c.329.423.445.974.315 1.494l-.547 2.19a.678.678 0 0 0 .178.643l2.457 2.457a.678.678 0 0 0 .644.178l2.189-.547a1.745 1.745 0 0 1 1.494.315l2.306 1.794c.829.645.905 1.87.163 2.611l-1.034 1.034c-.74.74-1.846 1.065-2.877.702a18.634 18.634 0 0 1-7.01-4.42 18.634 18.634 0 0 1-4.42-7.009c-.362-1.03-.037-2.137.703-2.877L1.885.511z"
+            />
+          </svg>
+          <div>
+            <strong>{{ $t('questionPage["contactForPhoneTitle"]') }}</strong>
+            <p>+852-30623063</p>
+            <p>{{ $t('questionPage["contactForPhoneWorkTime"]') }}</p>
+          </div>
+        </div>
+
+        <div class="contact-method">
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="24"
+            height="24"
+            fill="currentColor"
+            viewBox="0 0 16 16"
+          >
+            <path
+              d="M.05 3.555A2 2 0 0 1 2 2h12a2 2 0 0 1 1.95 1.555L8 8.414.05 3.555ZM0 4.697v7.104l5.803-3.558L0 4.697ZM6.761 8.83l-6.57 4.027A2 2 0 0 0 2 14h12a2 2 0 0 0 1.808-1.144l-6.57-4.027L8 9.586l-1.239-.757Zm3.436-.586L16 11.801V4.697l-5.803 3.546Z"
+            />
+          </svg>
+          <div>
+            <strong>{{ $t('questionPage["contactForEmailTitle"]') }}</strong>
+            <p>vavabuy@163.com</p>
+            <p>{{ $t('questionPage["contactForEmailWorkTime"]') }}</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+import { ref } from 'vue'
+import { storeToRefs } from 'pinia'
+import useSystemStore from '@/stores/useSystemStore'
+import useData from './hooks/useData'
+
+const { t } = useI18n()
+const systemStore = useSystemStore()
+const { locale } = storeToRefs(systemStore)
+
+const {
+  categoryList,
+  isLoading,
+  activeCategoryId,
+  activeCategoryInfo,
+  currentQuestion,
+  searchQuery,
+  searchResults,
+  changeCategory,
+  showQuestionDetail,
+  searchQuestions,
+  backToList,
+} = useData()
+
+const visible = ref<boolean>(false)
+
+const showCategoryDrawer = () => {
+  visible.value = true
+}
+
+const closeCategoryDrawer = () => {
+  visible.value = false
+}
+
+// 格式化日期
+const formatDate = (dateString: string, lang: string) => {
+  const options = { year: 'numeric', month: 'long', day: 'numeric' }
+  return new Date(dateString).toLocaleDateString(lang, options as Object)
+}
+</script>
+
+<style scoped lang="less">
+.question-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  font-family: 'Helvetica Neue', Arial, sans-serif;
+  color: #333;
+  margin: 0 auto 50px;
+  margin: 0 auto 3.125rem;
+}
+
+.loading-box {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
+.width-1200 {
+  padding: 0 15px 60px;
+  padding: 0 0.9375rem 3.75rem;
+}
+
+.question-banner {
+  width: 100%;
+  background: rgba(0, 0, 0, 1);
+  color: white;
+  padding: 40px 15px;
+  padding: 2.5rem 0.9375rem;
+  text-align: center;
+  margin-bottom: 30px;
+  margin-bottom: 1.875rem;
+
+  h1 {
+    font-size: 40px;
+    font-size: 2.5rem;
+    margin-bottom: 10px;
+    margin-bottom: 0.625rem;
+  }
+
+  p {
+    font-size: 1.125rem;
+    margin-bottom: 25px;
+    margin-bottom: 1.5625rem;
+    opacity: 0.9;
+  }
+}
+
+.question__search-box {
+  width: 100%;
+  max-width: 600px;
+  max-width: 37.5rem;
+  margin: 0 auto;
+  display: flex;
+
+  input {
+    flex: 1;
+    padding: 12px 15px;
+    padding: 0.75rem 0.9375rem;
+    border: none;
+    border-radius: 4px 0 0 4px;
+    border-radius: 0.25rem 0 0 0.25rem;
+    font-size: 1rem;
+    color: #333;
+  }
+
+  button {
+    padding: 0 20px;
+    padding: 0 1.25rem;
+    background: var(--color-active);
+    color: white;
+    border: none;
+    border-radius: 0 4px 4px 0;
+    border-radius: 0 0.25rem 0.25rem 0;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    gap: 0.5rem;
+    font-weight: 500;
+    letter-spacing: 2px;
+  }
+}
+
+.question-main__container {
+  display: flex;
+  gap: 30px;
+  gap: 1.875rem;
+
+  .question-sidebar {
+    min-width: 250px;
+    min-width: 15.625rem;
+    max-width: 300px;
+    max-width: 18.75rem;
+    flex-shrink: 0;
+
+    .category-item {
+      flex: 1;
+      padding: 12px 15px;
+      padding: 0.75rem 0.9375rem;
+      border-left: 4px solid transparent;
+      border-left: 0.25rem solid transparent;
+      cursor: pointer;
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 5px;
+      margin-bottom: 0.3125rem;
+      border-radius: 4px;
+      border-radius: 0.25rem;
+    }
+
+    .category-item:hover {
+      background: #f5f5f5;
+    }
+
+    .category-item.active {
+      border-left-color: var(--color-active);
+      background: #e3f2fd;
+      // font-weight: bold;
+    }
+
+    .category-item__name {
+      flex: 1;
+      padding-right: 12px;
+      padding-right: 0.75rem;
+      /*  white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis; */
+    }
+
+    .count {
+      min-width: 64px;
+      min-width: 4rem;
+      color: #757575;
+      font-size: 0.9rem;
+    }
+
+    .category-item.active .count {
+      color: var(--color-active);
+    }
+  }
+
+  .question-content {
+    flex: 1;
+    background: white;
+    border-radius: 8px;
+    border-radius: 0.5rem;
+    padding: 18px;
+    padding: 1.125rem;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  }
+
+  .search-results {
+    h3 {
+      font-size: 20px;
+      font-size: 1.25rem;
+      margin-bottom: 15px;
+      margin-bottom: 0.9375rem;
+      color: var(--color-active);
+    }
+  }
+
+  .question-list {
+    h2 {
+      margin-bottom: 20px;
+      margin-bottom: 1.25rem;
+      padding: 10px 0;
+      padding: 0.625rem 0;
+      border-bottom: 1px solid #eee;
+      font-size: 24px;
+      font-size: 1.5rem;
+    }
+  }
+
+  .question-item {
+    padding: 15px;
+    padding: 0.9375rem;
+    border-bottom: 1px solid #f0f0f0;
+    cursor: pointer;
+  }
+
+  .question-item:hover {
+    background: #f9f9f9;
+  }
+
+  .question-title {
+    font-weight: bold;
+    margin-bottom: 8px;
+    margin-bottom: 0.5rem;
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    gap: 0.625rem;
+  }
+
+  .question-preview {
+    max-height: 24px;
+    max-height: 1.5rem;
+    color: #666;
+    font-size: 0.9375rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .question-tag {
+    height: 20px;
+    padding: 0 10px;
+    line-height: 20px;
+    color: white;
+    font-size: 0.75rem;
+    border-radius: 12px;
+    border-radius: 0.75rem;
+  }
+
+  .hot-tag {
+    background: #ff5722;
+  }
+
+  .new-tag {
+    background: #4caf50;
+  }
+
+  .question-detail {
+    .detail-header {
+      margin-bottom: 24px;
+      margin-bottom: 1.5rem;
+      padding-bottom: 15px;
+      padding-bottom: 0.9375rem;
+      border-bottom: 1px solid #eee;
+
+      h2 {
+        font-size: 28px;
+        font-size: 1.75rem;
+        margin-bottom: 10px;
+        margin-bottom: 0.625rem;
+        text-align: center;
+      }
+    }
+
+    .detail-footer {
+      margin-top: 36px;
+      margin-top: 2.25rem;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding-top: 20px;
+      padding-top: 1.25rem;
+      border-top: 1px solid #eee;
+
+      .back-btn {
+        background: #f5f5f5;
+        border: none;
+        padding: 8px 15px;
+        padding: 0.5rem 0.9375rem;
+        border-radius: 4px;
+        border-radius: 0.25rem;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        gap: 5px;
+        gap: 0.3125rem;
+      }
+
+      .back-btn:hover {
+        background: #eee;
+      }
+
+      /* .feedback {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+      }
+
+      .feedback span {
+        color: #757575;
+      }
+
+      .feedback-btn {
+        padding: 5px 12px;
+        border-radius: 4px;
+        border: 1px solid #ddd;
+        cursor: pointer;
+      }
+
+      .feedback-btn.yes {
+        background: #e8f5e9;
+        color: #2e7d32;
+      }
+
+      .feedback-btn.no {
+        background: #ffebee;
+        color: #c62828;
+      } */
+    }
+  }
+}
+
+.question-main-normal {
+  position: relative;
+  min-height: 370px;
+}
+
+.question-main-768 {
+  position: relative;
+  display: none;
+  padding-bottom: 0;
+  min-height: 320px;
+}
+
+.question-main__container-768 {
+  flex-direction: column;
+  align-items: center;
+
+  .question-sidebar {
+    width: 100%;
+  }
+
+  .question-content {
+    width: 100%;
+    border-radius: 0px;
+    border-radius: 0rem;
+    padding: 0px 0;
+    padding: 0rem 0;
+    box-shadow: none;
+  }
+
+  .question-list {
+    h2 {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      align-items: flex-start;
+      margin-bottom: 8px;
+      margin-bottom: 0.5rem;
+      color: var(--color-active);
+      font-size: 18px;
+      font-size: 1.125rem;
+    }
+
+    .category-menu-btn {
+      min-width: 60px;
+      min-width: 3.75rem;
+      display: flex;
+      align-items: center;
+      color: #333;
+      font-size: 16px;
+      font-size: 1rem;
+      cursor: pointer;
+    }
+
+    .category-menu-btn img {
+      width: 28px;
+      width: 1.75rem;
+      height: 28px;
+      height: 1.75rem;
+      margin-left: 6px;
+      margin-left: 0.375rem;
+    }
+  }
+
+  :deep(.ant-drawer-body) {
+    padding: 15px 12px;
+    padding: 0.9375rem 0.75rem;
+  }
+
+  .question-detail {
+    .detail-header {
+      margin-bottom: 24px;
+      margin-bottom: 1.5rem;
+      padding-bottom: 15px;
+      padding-bottom: 0.9375rem;
+      border-bottom: 1px solid #eee;
+
+      h2 {
+        padding-top: 15px;
+        padding-top: 0.9375rem;
+        margin-bottom: 4px;
+        margin-bottom: 0.25rem;
+        font-size: 24px;
+        font-size: 1.5rem;
+      }
+    }
+
+    .detail-footer {
+      margin-top: 0;
+      padding-top: 0;
+      border-bottom: 1px solid #eee;
+      border-top: 0;
+
+      .back-btn {
+        margin: 12px 0;
+        margin: 0.75rem 0;
+        color: var(--color-active);
+        background-color: rgba(0, 0, 0, 0.02125);
+      }
+    }
+  }
+}
+
+.contact-section {
+  margin-top: 32px;
+  margin-top: 2rem;
+  text-align: center;
+  padding: 30px;
+  padding: 1.875rem;
+  background: #f9f9f9;
+  // border-radius: 8px;
+  // border-radius: 0.5rem;
+
+  h3 {
+    font-size: 24px;
+    font-size: 1.5rem;
+    margin-bottom: 10px;
+    margin-bottom: 0.625rem;
+  }
+
+  p {
+    color: #666;
+    margin-bottom: 25px;
+    margin-bottom: 1.5625rem;
+  }
+
+  .contact-methods {
+    display: flex;
+    justify-content: center;
+    gap: 30px;
+    gap: 1.875rem;
+    flex-wrap: wrap;
+  }
+
+  .contact-method {
+    display: flex;
+    align-items: flex-start;
+    gap: 15px;
+    gap: 0.9375rem;
+    text-align: left;
+    max-width: 300px;
+    max-width: 18.75rem;
+    padding: 15px;
+    padding: 0.9375rem;
+    background: white;
+    border-radius: 8px;
+    border-radius: 0.5rem;
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+
+    svg {
+      color: var(--color-active);
+      margin-top: 3px;
+    }
+
+    strong {
+      display: block;
+      margin-bottom: 5px;
+    }
+
+    p {
+      margin: 3px 0;
+      font-size: 0.9375rem;
+      color: #666;
+    }
+  }
+}
+</style>
+
+<style>
+.question-sidebar .ant-skeleton.ant-skeleton-element,
+.question-sidebar .ant-skeleton.ant-skeleton-element .ant-skeleton-button {
+  height: 100%;
+}
+
+/* 富文本内容样式 */
+.detail-content h3,
+.detail-content h4 {
+  margin: 25px 0 15px;
+  margin: 1.5625rem 0 0.9375rem;
+  color: #333;
+}
+
+.detail-content h3 {
+  font-size: 1.5rem;
+}
+
+.detail-content h4 {
+  font-size: 1.25rem;
+}
+
+.detail-content p {
+  margin: 15px 0;
+  margin: 0.9375rem 0;
+  line-height: 1.6;
+}
+
+.detail-content ol,
+.detail-content ul {
+  margin: 15px 0;
+  margin: 0.9375rem 0;
+  padding-left: 30px;
+  padding-left: 1.875rem;
+}
+
+.detail-content li {
+  margin: 0.5rem 0;
+}
+
+.question-content img,
+.detail-content img {
+  width: 100%;
+}
+
+.payment-method {
+  margin: 20px 0;
+  margin: 1.25rem 0;
+}
+
+.status {
+  display: inline-block;
+  padding: 2px 6px;
+  padding: 2px 0.375rem;
+  border-radius: 3px;
+  font-size: 0.875rem;
+}
+
+.status.waiting {
+  background: #fff3e0;
+  color: #e65100;
+}
+
+.status.paid {
+  background: #e3f2fd;
+  color: #1565c0;
+}
+
+.status.shipped {
+  background: #e8f5e9;
+  color: #2e7d32;
+}
+
+.status.completed {
+  background: #f1f8e9;
+  color: #558b2f;
+}
+
+.status.cancelled {
+  background: #ffebee;
+  color: #c62828;
+}
+</style>
+
+
+<style lang="less" scoped>
+@import url('./css/styles@1200.less');
+@import url('./css/styles@1024.less');
+@import url('./css/styles@768.less');
+</style>

+ 9 - 7
src/views/Home/index.vue

@@ -1,11 +1,13 @@
 <template>
 <template>
-  <Banners></Banners>
-  <Brands></Brands>
-  <WrapOne></WrapOne>
-  <WrapTwo></WrapTwo>
-  <WrapThree></WrapThree>
-  <WrapFour></WrapFour>
-  <WrapFive></WrapFive>
+  <div>
+    <Banners></Banners>
+    <Brands></Brands>
+    <WrapOne></WrapOne>
+    <WrapTwo></WrapTwo>
+    <WrapThree></WrapThree>
+    <WrapFour></WrapFour>
+    <WrapFive></WrapFive>
+  </div>
 </template>
 </template>
 
 
 
 

+ 8 - 7
src/views/Home/modules/Banners/index.vue

@@ -6,14 +6,10 @@
     <!-- content -->
     <!-- content -->
     <div class="banners__content">
     <div class="banners__content">
       <div class="banners__content-title">
       <div class="banners__content-title">
-        <span>{{ $t('paragraph["国际运转·一站解决"]') }}</span>
+        <span>{{ $t('paragraph["bannerTitle"]') }}</span>
       </div>
       </div>
-      <div class="banners__content-desc">
-        {{
-          $t('paragraph["连接全球优质商品,一键下单无忧,实时查看订单进程,享受便捷购物体验。"]')
-        }}
-      </div>
-      <div class="banners__content-btn">
+      <div class="banners__content-desc" v-html="$t(`paragraph['bannerDesc']`)"></div>
+      <div class="banners__content-btn" @click="goRegister">
         <span>{{ $t('system["立即免费注册"]') }}</span>
         <span>{{ $t('system["立即免费注册"]') }}</span>
       </div>
       </div>
     </div>
     </div>
@@ -22,6 +18,10 @@
 
 
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
+// 注册跳转
+const goRegister = function () {
+  window.open('https://vavabuy.net/#/pages/login/register', '__blank')
+}
 </script>
 </script>
 
 
 
 
@@ -67,6 +67,7 @@
     font-size: 18px;
     font-size: 18px;
     font-size: 1.125rem;
     font-size: 1.125rem;
     color: rgba(255, 255, 255, 0.8);
     color: rgba(255, 255, 255, 0.8);
+    line-height: 2;
     transition: all 0.3s;
     transition: all 0.3s;
   }
   }
 
 

+ 90 - 16
src/views/Home/modules/WrapFive/index.vue

@@ -10,16 +10,26 @@
         <div class="swiper-wrapper">
         <div class="swiper-wrapper">
           <div v-for="(item, i) in dataList" :key="i" class="swiper-slide swiper-slide__card">
           <div v-for="(item, i) in dataList" :key="i" class="swiper-slide swiper-slide__card">
             <div class="swiper-slide__card-head">
             <div class="swiper-slide__card-head">
-              <div class="swiper-slide__card-content">{{ $t(`paragraph["${item.content}"]`) }}</div>
+              <div class="swiper-slide__card-content">{{ $t(`comments["${item.content}"]`) }}</div>
             </div>
             </div>
             <div class="swiper-slide__card-body">
             <div class="swiper-slide__card-body">
               <div class="swiper-slide__card-avatar">
               <div class="swiper-slide__card-avatar">
-                <img src="@/public/images/avatar01.png" alt="" />
-                <img src="@/public/images/avatar02.png" alt="" />
+                <img src="@/public/images/avatar/avatar_1.jpg" alt="" v-if="i == 0" />
+                <img src="@/public/images/avatar/avatar_2.jpg" alt="" v-if="i == 1" />
+                <img src="@/public/images/avatar/avatar_3.jpg" alt="" v-if="i == 2" />
+                <img src="@/public/images/avatar/avatar_4.jpg" alt="" v-if="i == 3" />
+                <img src="@/public/images/avatar/avatar_5.jpg" alt="" v-if="i == 4" />
+                <img src="@/public/images/avatar/avatar_6.jpg" alt="" v-if="i == 5" />
+                <img src="@/public/images/avatar/avatar_7.jpg" alt="" v-if="i == 6" />
+                <img src="@/public/images/avatar/avatar_8.jpg" alt="" v-if="i == 7" />
+                <img src="@/public/images/avatar/avatar_9.jpg" alt="" v-if="i == 8" />
+                <img src="@/public/images/avatar/avatar_10.jpg" alt="" v-if="i == 9" />
+                <img src="@/public/images/avatar/avatar_11.jpg" alt="" v-if="i == 10" />
+                <img src="../../../../public/images/logo_icon.png" />
               </div>
               </div>
               <div>
               <div>
-                <div class="swiper-slide__card-name">{{ $t(`system["${item.name}"]`) }}</div>
-                <div class="swiper-slide__card-desc">{{ $t(`system["${item.address}"]`) }}</div>
+                <div class="swiper-slide__card-name">{{ $t(`comments["${item.name}"]`) }}</div>
+                <div class="swiper-slide__card-desc">{{ $t(`comments["${item.address}"]`) }}</div>
               </div>
               </div>
             </div>
             </div>
           </div>
           </div>
@@ -40,9 +50,14 @@ import Swiper from 'swiper'
 onMounted(() => {
 onMounted(() => {
   new Swiper('.mySwiper', {
   new Swiper('.mySwiper', {
     speed: 600,
     speed: 600,
-    // loop: true,
+    loop: true,
     slidesPerView: 'auto',
     slidesPerView: 'auto',
-    // centeredSlides: true,
+    centeredSlides: true,
+    autoplay: {
+      delay: 1500,
+      disableOnInteraction: false,
+      stopOnLastSlide: false,
+    },
     navigation: {
     navigation: {
       nextEl: '.swiper-button-next',
       nextEl: '.swiper-button-next',
       prevEl: '.swiper-button-prev',
       prevEl: '.swiper-button-prev',
@@ -52,23 +67,72 @@ onMounted(() => {
 
 
 const dataList = [
 const dataList = [
   {
   {
-    name: '囧二',
+    name: '马库斯',
+    address: '奥地利',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '商品易于使用,质量很棒。物流给力,包装精美,客服态度热情!',
+  },
+  {
+    name: '劳伦斯',
     address: '新西兰',
     address: '新西兰',
-    avatar: [],
+    avatar: ['../../../../public/images/icons/xinxilan_logo_icon.png'],
     content: '如果你想买其他东西,没有比 Vava buy 更好的了',
     content: '如果你想买其他东西,没有比 Vava buy 更好的了',
+    locale: '',
   },
   },
   {
   {
-    name: '囧二',
+    name: '皮埃尔',
+    address: '法国',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '飞快送达,包装妥帖,商品完美。会再来光顾的!',
+  },
+  {
+    name: '约翰',
     address: '澳大利亚',
     address: '澳大利亚',
-    avatar: [],
-    content: '如果你想买其他东西,没有比 Vava buy 更好的了',
+    avatar: ['../../../../public/images/icons/aodaliya_logo_icon.png'],
+    content: '太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻',
   },
   },
   {
   {
-    name: '囧二',
+    name: '安娜',
     address: '意大利',
     address: '意大利',
-    avatar: [],
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
     content: '非常易于使用,包装很精美,会收获很多惊喜',
     content: '非常易于使用,包装很精美,会收获很多惊喜',
   },
   },
+  {
+    name: '杰森',
+    address: '英国',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '国际物流送货上门太赞了!商品没瑕疵,包装抗摔,全程追踪清晰~',
+  },
+  {
+    name: '伊莎贝拉',
+    address: '西班牙',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '这商品太好用了,操作简单。国际物流全程顺畅,包装精美又牢固!',
+  },
+  {
+    name: '杰克',
+    address: '美国',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '到货比想的快,包装抗造,东西超棒。',
+  },
+  {
+    name: '埃里克',
+    address: '瑞典',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '商品易于使用,完全满足需求。物流比预期快,客服响应迅速,体验非常好!',
+  },
+  {
+    name: '亚历山德罗',
+    address: '意大利',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '清关过程很顺利,物流速度很快。包装处理得很棒,连说明书都套了保护袋!',
+  },
+  {
+    name: '卢卡',
+    address: '意大利',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '物流全程透明,从出库到派送都能看到。包装很结实,拆开后商品崭新如初!',
+  },
 ]
 ]
 </script>
 </script>
 
 
@@ -101,8 +165,8 @@ const dataList = [
   .wrap__five-box {
   .wrap__five-box {
     display: inline-block;
     display: inline-block;
     width: auto;
     width: auto;
-    max-width: 1400px;
-    max-width: 87.5rem;
+    max-width: 1024px;
+    max-width: 64rem;
   }
   }
 
 
   .mySwiper {
   .mySwiper {
@@ -172,10 +236,20 @@ const dataList = [
     }
     }
 
 
     .swiper-slide__card-avatar img {
     .swiper-slide__card-avatar img {
+      width: 56px;
+      width: 3.5rem;
+      height: 56px;
+      height: 3.5rem;
+      border-radius: 50%;
       position: relative;
       position: relative;
       z-index: 1;
       z-index: 1;
     }
     }
 
 
+    .swiper-slide__card-avatar img:nth-child(1) {
+      box-sizing: content-box;
+      border: 3px solid #fff;
+    }
+
     .swiper-slide__card-avatar img:nth-child(2) {
     .swiper-slide__card-avatar img:nth-child(2) {
       position: absolute;
       position: absolute;
       left: 30px;
       left: 30px;

+ 7 - 2
src/views/Home/modules/WrapFour/css/styles@1024.less

@@ -1,11 +1,16 @@
 @media screen and (max-width: 1024px) {
 @media screen and (max-width: 1024px) {
   .wrap__four {
   .wrap__four {
-    
+
     .wrap__four-body {
     .wrap__four-body {
-  
+
       .wrap__four-total {
       .wrap__four-total {
         font-size: 80px;
         font-size: 80px;
         font-size: 5rem;
         font-size: 5rem;
+
+        :deep(.ant-statistic-content-value-int) {
+          font-size: 80px;
+          font-size: 5rem;
+        }
       }
       }
 
 
       .wrap__four-desc {
       .wrap__four-desc {

+ 8 - 4
src/views/Home/modules/WrapFour/css/styles@768.less

@@ -1,13 +1,17 @@
 @media screen and (max-width: 768px) {
 @media screen and (max-width: 768px) {
   .wrap__four {
   .wrap__four {
-    
+
     .wrap__four-body {
     .wrap__four-body {
-  
+
       .wrap__four-total {
       .wrap__four-total {
-        font-size: 70px;
-        font-size: 4.375rem;
+
         margin-bottom: 20px;
         margin-bottom: 20px;
         margin-bottom: 1.25rem;
         margin-bottom: 1.25rem;
+
+        :deep(.ant-statistic-content-value-int) {
+          font-size: 70px;
+          font-size: 4.375rem;
+        }
       }
       }
 
 
       .wrap__four-desc {
       .wrap__four-desc {

+ 72 - 6
src/views/Home/modules/WrapFour/index.vue

@@ -4,7 +4,9 @@
     <div class="mask-bg"></div>
     <div class="mask-bg"></div>
     <div class="wrap__four-body">
     <div class="wrap__four-body">
       <div class="wrap__four-total trans-300">
       <div class="wrap__four-total trans-300">
-        <span>10,117,195</span>
+        <!-- <span>10,117,195</span> -->
+        <!-- <a-statistic title="" :value="statisticNumber" /> -->
+        <a-statistic title="" :value="computeStatisticValue(Date.now())" />
       </div>
       </div>
       <div class="wrap__four-desc trans-300">
       <div class="wrap__four-desc trans-300">
         <span>{{ $t('system["包裹运送到全球,而且还在不断增加..."]') }}</span>
         <span>{{ $t('system["包裹运送到全球,而且还在不断增加..."]') }}</span>
@@ -19,6 +21,67 @@
 
 
 
 
 <script setup lang="ts">
 <script setup lang="ts">
+import { ref, onBeforeUnmount, onMounted } from 'vue'
+
+const statisticNumber = ref<number>(0)
+const statisticTimer = ref<number>(-1)
+const computeDelay = ref<number>(1000 * 60 * 60 * 1)
+const statisticStorageKey: string = 'statistic_time'
+
+// 获取统计值
+function initForStatistic() {
+  statisticNumber.value = getStatisticValue()
+  const nowTime = Date.now()
+  const oldTime = !!localStorage.getItem(statisticStorageKey)
+    ? Number(localStorage.getItem(statisticStorageKey))
+    : nowTime
+  let step_temp = Math.floor((nowTime - oldTime) / computeDelay.value)
+  console.log('(nowTime - oldTime)', nowTime - oldTime)
+  console.log('step_temp', step_temp)
+  if (step_temp > 0) {
+    statisticNumber.value = computeStatisticValue(oldTime + computeDelay.value * step_temp)
+    localStorage.setItem(statisticStorageKey, `${oldTime + computeDelay.value * step_temp}`)
+  } else {
+    !localStorage.getItem(statisticStorageKey) &&
+      localStorage.setItem(statisticStorageKey, `${nowTime}`)
+  }
+}
+
+function getStatisticValue(): number {
+  let oldVal: string | null = localStorage.getItem(statisticStorageKey)
+  const nowTime: number = Date.now()
+  let result: number = computeStatisticValue(!!oldVal ? Number(oldVal) : nowTime)
+  return result
+}
+
+// 计算统计值
+function computeStatisticValue(value: number): number {
+  return Number((value / (1000 * 60 * 60)).toFixed(0))
+}
+
+// 添加计时器
+function addInterval(delay = 1000 * 30) {
+  statisticTimer.value = setInterval(() => {
+    initForStatistic()
+  }, delay)
+}
+
+// 移除计时器
+function removeInterval() {
+  clearInterval(statisticTimer.value)
+  statisticTimer.value = -1
+}
+
+/* ------------------------------ 生命周期钩子 ------------------------------ */
+
+onMounted(() => {
+  initForStatistic()
+  addInterval()
+})
+
+onBeforeUnmount(() => {
+  removeInterval()
+})
 </script>
 </script>
 
 
 
 
@@ -59,11 +122,14 @@
     .wrap__four-total {
     .wrap__four-total {
       margin-bottom: 30px;
       margin-bottom: 30px;
       margin-bottom: 1.875rem;
       margin-bottom: 1.875rem;
-      font-family: Poppins;
-      font-size: 120px;
-      font-size: 7.5rem;
-      font-weight: 600;
-      color: #ffffff;
+
+      :deep(.ant-statistic-content-value-int) {
+        font-family: Poppins;
+        font-size: 120px;
+        font-size: 7.5rem;
+        font-weight: 600;
+        color: #ffffff;
+      }
     }
     }
 
 
     .wrap__four-desc {
     .wrap__four-desc {

+ 26 - 3
src/views/Home/modules/WrapTwo/index.vue

@@ -8,7 +8,12 @@
       </div>
       </div>
       <div class="wrap__two-content">
       <div class="wrap__two-content">
         <div class="wrap__two-steps">
         <div class="wrap__two-steps">
-          <div v-for="(step, i) in stepList" :key="i" class="wrap__two-step-item">
+          <div
+            v-for="(step, i) in stepList"
+            :key="i"
+            class="wrap__two-step-item"
+            :class="[locale == 'en' ? 'wrap__two-step-item__en' : '']"
+          >
             <img
             <img
               v-if="i == 0"
               v-if="i == 0"
               src="@/public/images/icons/address_icon.png"
               src="@/public/images/icons/address_icon.png"
@@ -33,7 +38,7 @@
         </div>
         </div>
       </div>
       </div>
       <div class="wrap__two-foot">
       <div class="wrap__two-foot">
-        <div class="wrap__two-btn">{{ $t('system["立即免费注册"]') }}</div>
+        <div class="wrap__two-btn" @click="goRegister">{{ $t('system["立即免费注册"]') }}</div>
       </div>
       </div>
     </div>
     </div>
   </section>
   </section>
@@ -43,12 +48,17 @@
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { reactive } from 'vue'
 import { reactive } from 'vue'
+import { storeToRefs } from 'pinia'
+import useSystemStore from '@/stores/useSystemStore'
 
 
 interface IStep {
 interface IStep {
   name: string
   name: string
   desc: string
   desc: string
 }
 }
 
 
+const systemStore = useSystemStore()
+const { locale } = storeToRefs(systemStore)
+
 const stepList = reactive<IStep[]>([
 const stepList = reactive<IStep[]>([
   {
   {
     name: '注册账户',
     name: '注册账户',
@@ -63,6 +73,11 @@ const stepList = reactive<IStep[]>([
     desc: '合并包裹并节省高达80%的运费',
     desc: '合并包裹并节省高达80%的运费',
   },
   },
 ])
 ])
+
+// 注册跳转
+const goRegister = function () {
+  window.open('https://vavabuy.net/#/pages/login/register', '__blank')
+}
 </script>
 </script>
 
 
 
 
@@ -112,11 +127,19 @@ const stepList = reactive<IStep[]>([
     flex-direction: column;
     flex-direction: column;
     align-items: center;
     align-items: center;
     padding: 0 15px;
     padding: 0 15px;
-    padding: 0 1.875rem;
+    padding: 0 1rem;
     color: #fff;
     color: #fff;
     text-align: center;
     text-align: center;
   }
   }
 
 
+  .wrap__two-step-item__en {
+    flex: 3;
+  }
+
+  .wrap__two-step-item__en:nth-child(2) {
+    flex: 4;
+  }
+
   .wrap__two-step-icon {
   .wrap__two-step-icon {
     height: 58px;
     height: 58px;
     height: 3.625rem;
     height: 3.625rem;

+ 6 - 0
src/views/Layout/index.vue

@@ -1,5 +1,8 @@
 <template>
 <template>
   <div id="layout__container">
   <div id="layout__container">
+    <!-- 全局加载动画 -->
+    <!-- <LoadingSpinner /> -->
+
     <!-- header -->
     <!-- header -->
     <header class="layout__header">
     <header class="layout__header">
       <Header></Header>
       <Header></Header>
@@ -22,6 +25,7 @@
 import Header from './modules/Header/index.vue'
 import Header from './modules/Header/index.vue'
 import Main from './modules/Main/index.vue'
 import Main from './modules/Main/index.vue'
 import Footer from './modules/Footer/index.vue'
 import Footer from './modules/Footer/index.vue'
+// import LoadingSpinner from '@/components/LoadingSpinner.vue'
 </script>
 </script>
 
 
 
 
@@ -42,6 +46,8 @@ import Footer from './modules/Footer/index.vue'
 
 
   .layout__main {
   .layout__main {
     flex: 1;
     flex: 1;
+    display: flex;
+    flex-direction: column;
     width: 100%;
     width: 100%;
     height: auto;
     height: auto;
   }
   }

+ 12 - 4
src/views/Layout/modules/Footer/css/footer@512.less

@@ -1,13 +1,21 @@
 @media screen and (max-width: 512px) {
 @media screen and (max-width: 512px) {
-  
+
   .footer-friendlink {
   .footer-friendlink {
 
 
-    .footer-wrap {
-      
-    }
+    .footer-wrap {}
 
 
     .footer-friendlink__box {
     .footer-friendlink__box {
       width: 100%;
       width: 100%;
     }
     }
   }
   }
+
+  .footer-all-right {
+
+    .footer__box {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      text-align: center;
+    }
+  }
 }
 }

+ 147 - 78
src/views/Layout/modules/Footer/index.vue

@@ -1,40 +1,80 @@
 <template>
 <template>
-  <div class="footer__container footer-friendlink">
-    <div class="footer__box footer-wrap width-1200">
-      <div class="footer-friendlink__box">
-        <img src="@/public/images/logo.png" class="footer-friendlink__logo" />
-        <div class="footer-friendlink__baseinfo">
-          <div v-for="(item, i) in baseInfoList" :key="i" class="footer-friendlink__baseinfo-item">
-            <span class="footer-friendlink__baseinfo-label">{{ item.label }}</span>
-            <span class="footer-friendlink__baseinfo-content">{{ item.content }}</span>
+  <section>
+    <div class="footer__container footer-friendlink">
+      <div class="footer__box footer-wrap width-1200">
+        <div class="footer-friendlink__box">
+          <img src="@/public/images/logo.png" class="footer-friendlink__logo" />
+          <div class="footer-friendlink__baseinfo">
+            <div
+              class="footer-friendlink__baseinfo-item"
+              style="font-size: 18px; font-size: 1.125rem; margin-bottom: 10px"
+            >
+              <span class="footer-friendlink__baseinfo-label">{{ $t('system.companyName') }} </span>
+            </div>
+            <div class="footer-friendlink__baseinfo-item">
+              <span class="footer-friendlink__baseinfo-label">{{ $t('system.emailLabel') }}: </span>
+              <span class="footer-friendlink__baseinfo-content">vavabuy@163.com</span>
+            </div>
+            <div class="footer-friendlink__baseinfo-item">
+              <span class="footer-friendlink__baseinfo-label"
+                >{{ $t('system.hongkongEmailLabel') }}:
+              </span>
+              <span class="footer-friendlink__baseinfo-content">cbtplatform@vavabuy.com</span>
+            </div>
+            <div class="footer-friendlink__baseinfo-item">
+              <span class="footer-friendlink__baseinfo-label"
+                >{{ $t('system.addressLabel') }}:
+              </span>
+              <span class="footer-friendlink__baseinfo-content"
+                >{{ $t('system.companyAddress') }}
+              </span>
+            </div>
+            <div class="footer-friendlink__baseinfo-item">
+              <span class="footer-friendlink__baseinfo-label"
+                >{{ $t('system.hongkongAddressLabel') }}:
+              </span>
+              <span class="footer-friendlink__baseinfo-content"
+                >{{ $t('system.hongkongCompanyAddress') }}
+              </span>
+            </div>
           </div>
           </div>
         </div>
         </div>
-      </div>
-      <div class="footer-friendlink__box">
-        <div v-for="(link, i) in linkList1" :key="i" class="footer-friendlink__item">
-          <span class="text_hover_underline">{{ $t(`textLink["${link.text}"]`) }}</span>
-        </div>
-      </div>
-      <div class="footer-friendlink__box">
-        <div v-for="(link, i) in linkList2" :key="i" class="footer-friendlink__item">
-          <span class="text_hover_underline">{{ $t(`textLink["${link.text}"]`) }}</span>
-        </div>
-      </div>
-      <div class="footer-friendlink__box">
-        <div v-for="(link, i) in linkList3" :key="i" class="footer-friendlink__item">
-          <span class="text_hover_underline">{{ $t(`textLink["${link.text}"]`) }}</span>
+        <div class="footer-friendlink__box" v-for="(itemInfo, i) in [...linkList]" :key="i">
+          <div class="footer-friendlink__box-head">{{ itemInfo.name }}</div>
+          <div
+            v-for="(info, index) in itemInfo.lists"
+            :key="index"
+            class="footer-friendlink__item"
+            @click="updateActiveInfo(itemInfo, info, goArticleDetailPage)"
+          >
+            <span
+              class="text_hover_underline"
+              :class="[
+                activeLinkId && activeLinkId == info.id ? 'text_hover_underline--active' : '',
+              ]"
+              >{{ info.title }}</span
+            >
+          </div>
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
-  </div>
 
 
-  <div class="footer__container footer-right">
-    <div class="footer__box width-1200">
-      <span>©1997-2025 VAVA BUY.com All Rights Reserved</span>
+    <div class="footer__container footer-all-right">
+      <div class="footer__box width-1200">
+        <span style="color: rgba(255, 255, 255, 0.6)"
+          >©1997-2025 VAVA BUY.com All Rights Reserved</span
+        >
+        <a
+          style="color: #fffc; text-decoration: none"
+          href="https://beian.miit.gov.cn/"
+          target="_blank"
+        >
+          粤ICP备2025434699号-1
+        </a>
+      </div>
     </div>
     </div>
-  </div>
 
 
-  <!-- <div class="footer__container footer-footnote">
+    <!-- <div class="footer__container footer-footnote">
     <div class="footer__box width-1200">
     <div class="footer__box width-1200">
       <span>{{
       <span>{{
         $t(
         $t(
@@ -43,49 +83,60 @@
       }}</span>
       }}</span>
     </div>
     </div>
   </div> -->
   </div> -->
+  </section>
 </template>
 </template>
 
 
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-const baseInfoList = [
-  { label: '', content: '4299 Express Lane' },
-  { label: '', content: 'Sarasota,FL 34249 USA' },
-  { label: 'WhatsApp Message: ', content: '1.941.225.7374' },
-  { label: 'Phone: ', content: '1.941.227.4444' },
-  { label: 'Fax: ', content: '1.941.827.2985' },
-  { label: 'Local Time: ', content: '04:19' },
-]
-
-const linkList1 = [
-  { link: '', text: 'VAVA BUY 是如何工作的' },
-  { link: '', text: '为什么选择 VAVA BUY' },
-  { link: '', text: '您所在国家/地区的运费和价格' },
-  { link: '', text: '报名' },
-  { link: '', text: '汇率' },
-  { link: '', text: '不能寄送的物品' },
-  { link: '', text: '常见问题' },
-  { link: '', text: '日志' },
-  { link: '', text: '您的隐私权利' },
-]
-
-const linkList2 = [
-  { link: '', text: '关于 VAVA BUY' },
-  { link: '', text: 'VAVA BUY 评论' },
-  { link: '', text: '新闻与动态' },
-  { link: '', text: 'VAVA BUY.com 的招聘信息' },
-  { link: '', text: '与 VAVA BUY 合作' },
-  { link: '', text: '联系我们' },
-  { link: '', text: '网站地图' },
-  { link: '', text: '奖学金' },
-  { link: '', text: '顶级商店' },
-]
-
-const linkList3 = [
-  { link: '', text: 'Facebook' },
-  { link: '', text: '社交平台' },
-  { link: '', text: 'X(原 Twitter)' },
-  { link: '', text: '获取 VAVA BUY 应用程序' },
-]
+import { watch, computed } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { storeToRefs } from 'pinia'
+import useSystemStore from '@/stores/useSystemStore'
+import useFooterStore from '@/stores/useFooterStore'
+
+// system store
+const systemStore = useSystemStore()
+const { locale, screenWidth } = storeToRefs(systemStore)
+// footer store
+const footerStore = useFooterStore()
+const { linkList, activeLinkId, activeLinkInfo, activeLinkCateInfo } = storeToRefs(footerStore)
+const { fetchHelpDatas, updateActiveInfo } = footerStore
+// 路由对象
+const route = useRoute()
+const router = useRouter()
+
+const linkListCount = computed<number>(() => {
+  let count = 4
+  if (screenWidth.value >= 1200) {
+    count = 4
+  } else if (screenWidth.value > 824) {
+    count = 3
+  } else if (screenWidth.value > 524) {
+    count = 2
+  } else {
+    count = 1
+  }
+  return count
+})
+
+// 获取数据列表
+fetchHelpDatas(locale.value)
+
+// 跳转文章详情页
+const goArticleDetailPage = function () {
+  if (!activeLinkInfo.value && !activeLinkId.value) return
+  router.push({
+    path: '/article/article_details',
+    query: {
+      id: activeLinkId.value
+        ? activeLinkId.value
+        : activeLinkInfo.value
+        ? activeLinkInfo.value.id
+        : '',
+      cateId: activeLinkCateInfo.value ? activeLinkCateInfo.value.id : '',
+    },
+  })
+}
 </script>
 </script>
 
 
 
 
@@ -106,7 +157,9 @@ const linkList3 = [
   }
   }
 
 
   .footer-friendlink__box {
   .footer-friendlink__box {
-    width: auto;
+    // width: 25%;
+    max-width: 360px;
+    max-width: 22.5rem;
     padding: 6px 10px 10px;
     padding: 6px 10px 10px;
     padding: 0.375rem 0.625rem 0.625rem;
     padding: 0.375rem 0.625rem 0.625rem;
   }
   }
@@ -118,13 +171,27 @@ const linkList3 = [
   .footer-friendlink__box .footer-friendlink__logo {
   .footer-friendlink__box .footer-friendlink__logo {
     width: 150px;
     width: 150px;
     width: 9.375rem;
     width: 9.375rem;
-    margin-bottom: 36px;
-    margin-bottom: 2.25rem;
+    margin-bottom: 20px;
+    margin-bottom: 1.25rem;
+  }
+
+  .footer-friendlink__box .footer-friendlink__box-head {
+    margin-bottom: 24px;
+    margin-bottom: 1.5rem;
+    font-size: 18px;
+    font-size: 1.125rem;
+    font-weight: 500;
+    color: #222;
+  }
+
+  .footer-friendlink__baseinfo {
+    max-width: 360px;
+    max-width: 22.5rem;
   }
   }
 
 
   .footer-friendlink__baseinfo-item {
   .footer-friendlink__baseinfo-item {
-    margin-bottom: 4px;
-    margin-bottom: 0.25rem;
+    margin-bottom: 6px;
+    margin-bottom: 0.375rem;
     font-family: Poppins;
     font-family: Poppins;
     font-size: 14px;
     font-size: 14px;
     font-size: 0.875rem;
     font-size: 0.875rem;
@@ -132,10 +199,12 @@ const linkList3 = [
     color: #3d3d3d;
     color: #3d3d3d;
   }
   }
 
 
-  .footer-friendlink__baseinfo-item:last-child {
-    margin-bottom: 0;
-    margin-top: 20px;
-    margin-top: 1.25rem;
+  .footer-friendlink__baseinfo-label {
+    color: #000;
+  }
+
+  .footer-friendlink__baseinfo-content {
+    color: #555;
   }
   }
 
 
   .footer-friendlink__item {
   .footer-friendlink__item {
@@ -148,7 +217,7 @@ const linkList3 = [
   }
   }
 }
 }
 
 
-.footer-right {
+.footer-all-right {
   background-color: #000;
   background-color: #000;
 
 
   .footer__box {
   .footer__box {
@@ -176,4 +245,4 @@ const linkList3 = [
 @import url(./css/footer@1024.less);
 @import url(./css/footer@1024.less);
 @import url(./css/footer@768.less);
 @import url(./css/footer@768.less);
 @import url(./css/footer@512.less);
 @import url(./css/footer@512.less);
-</style>
+</style>./hooks/useData

+ 8 - 0
src/views/Layout/modules/Header/css/header@768.less

@@ -11,4 +11,12 @@
       display: block;
       display: block;
     }
     }
   }
   }
+
+  .download-btn-normal {
+    display: none;
+  }
+
+  .download-btn-768 {
+    display: block;
+  }
 }
 }

+ 78 - 10
src/views/Layout/modules/Header/hooks/useLinkList.ts

@@ -1,35 +1,103 @@
 
 
-import { reactive } from 'vue'
+import { reactive, ref, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { storeToRefs } from 'pinia'
+import useSystemStore from '@/stores/useSystemStore'
 
 
 
 
 
 
 export interface ITextLink {
 export interface ITextLink {
   text: string;
   text: string;
-  link: string;
+  id?: string | number;
+  path?: string;
+  url?: string;
+  textColor?: string;
 }
 }
 
 
 
 
 function useLinkList() {
 function useLinkList() {
   const textLinkList = reactive<Array<ITextLink>>([
   const textLinkList = reactive<Array<ITextLink>>([
-    { text: '常见问题', link: '' },
-    { text: '与 VAVA BUY 合作', link: '' },
+    { text: '常见问题', path: '/faq' },
+    {
+      text: '与 VAVA BUY 合作',
+      path: '/article/article_details',
+      id: 'connect',
+    },
   ])
   ])
 
 
   const textLinkList2 = reactive<Array<ITextLink>>([
   const textLinkList2 = reactive<Array<ITextLink>>([
-    { text: '如何运作', link: '' },
-    { text: '为什么选择 VAVA BUY', link: '' },
-    { text: '优势', link: '' },
-    { text: '在 VAVA BUY 购物', link: '' },
+    {
+      text: '开始在 VAVA BUY 购物',
+      url: 'https://vavabuy.net/#/',
+      textColor: '#FF0F23'
+    },
+    {
+      text: '如何运作',
+      path: '/article/article_details',
+      id: 'works',
+    },
+    {
+      text: '为什么选择 VAVA BUY',
+      path: '/article/article_details',
+      id: 'about',
+    },
+    {
+      text: '优势',
+      path: '/article/article_details',
+      id: 'superiority',
+    },
   ])
   ])
 
 
+  const route = useRoute()
+  const router = useRouter()
+  const currentPath = ref<string>('')
+  const currentId = ref<string | number | null>(null)
 
 
-  const handleTextLinkClick = function(linkInfo: ITextLink) {
-    if (!linkInfo || !linkInfo.link) return
+
+  // 获取最新路由信息(路径&id)
+  watch(
+    () => route.fullPath,
+    (newVal, oldVal) => {
+      currentPath.value = route.path
+      currentId.value = !!route.query.id ? (route.query.id as (string | number)) : null
+    },
+    { immediate: true }
+  )
+
+
+  // 跳转回调
+  const handleTextLinkClick = function (linkInfo: ITextLink, id?: string | number) {
+
+    if (!linkInfo || (!linkInfo.path && !linkInfo.url)) return
+
+    if (linkInfo.url) {
+      const systemStore = useSystemStore()
+      const { locale } = storeToRefs(systemStore)
+      window.open(`${linkInfo.url}?lang=${locale.value}`, '_blank')
+    }
+    else if (linkInfo.id) {
+      router.push({
+        path: linkInfo.path,
+        query: {
+          id: linkInfo.id ? linkInfo.id : '',
+          type: 'header'
+        }
+      })
+    }
+    else {
+      router.push({ path: linkInfo.path })
+    }
   }
   }
 
 
+
+
   return {
   return {
     textLinkList,
     textLinkList,
     textLinkList2,
     textLinkList2,
+    route,
+    router,
+    currentPath,
+    currentId,
     handleTextLinkClick
     handleTextLinkClick
   }
   }
 }
 }

+ 286 - 70
src/views/Layout/modules/Header/index.vue

@@ -1,83 +1,184 @@
 <template>
 <template>
-  <!-- navbar -->
-  <div class="nav__container navbar">
-    <div class="nav__box navbar__box">
-      <!-- left -->
-      <div class="nav__left navbar__left">
-        <ul v-if="textLinkList" class="text-link__list">
-          <li v-for="(item, index) in textLinkList" :key="index" class="text-link__item">
-            <span class="text_hover_underline">{{ $t(`textLink["${item.text}"]`) }}</span>
-          </li>
-        </ul>
-      </div>
-
-      <!-- right -->
-      <div class="nav__right navbar__right">
-        <a-dropdown>
-          <div class="language-toggle">
-            <img src="@/public/images/icons/language-toggle__icon.png" />
-            <span>{{ localeInfo && localeInfo.name }}</span>
-          </div>
-          <template #overlay>
-            <a-menu>
-              <a-menu-item v-for="(item, index) in systemStore.localeList" :key="index">
-                <div @click="onLocaleChange(item)">
-                  <span class="text_hover_underline">{{ item.name }}</span>
-                </div>
-              </a-menu-item>
-            </a-menu>
-          </template>
-        </a-dropdown>
-        <div class="login-btn">
-          <span>{{ $t('system["登录"]') }}</span>
+  <section>
+    <!-- navbar -->
+    <div class="nav__container navbar">
+      <div class="nav__box navbar__box">
+        <!-- left -->
+        <div class="nav__left navbar__left">
+          <ul v-if="textLinkList" class="text-link__list">
+            <li
+              v-for="(item, index) in textLinkList"
+              :key="index"
+              class="text-link__item"
+              @click="handleTextLinkClick(item, item.id ? item.id : '')"
+            >
+              <span
+                class="text_hover_underline"
+                :class="[
+                  (currentId && currentId == item.id && currentPath == item.path) ||
+                  (!currentId && currentPath == item.path)
+                    ? 'text_hover_underline--active'
+                    : '',
+                ]"
+                >{{ $t(`textLink["${item.text}"]`) }}</span
+              >
+            </li>
+          </ul>
         </div>
         </div>
-      </div>
-    </div>
-  </div>
-
-  <!-- subnav -->
-  <div class="nav__container subnav">
-    <div class="nav__box subnav__box">
-      <!-- left -->
-      <div class="nav__left subnav__left">
-        <img src="@/public/images/logo.png" />
-      </div>
 
 
-      <!-- right -->
-      <div class="nav__right subnav__right">
-        <!-- link list -->
-        <ul v-if="textLinkList" class="text-link__list">
-          <li v-for="(item, index) in textLinkList2" :key="index" class="text-link__item">
-            <span class="text_hover_underline">{{ $t(`textLink["${item.text}"]`) }}</span>
-          </li>
-        </ul>
-
-        <!-- menu -->
-        <div class="nav-menus">
+        <!-- right -->
+        <div class="nav__right navbar__right">
           <a-dropdown>
           <a-dropdown>
-            <div class="menus__btn">
-              <MenuOutlined style="font-size: 20px; font-size: 1.25rem; color: #000" />
+            <div class="language-toggle">
+              <img src="@/public/images/icons/language-toggle__icon.png" />
+              <span>{{ localeInfo && localeInfo.title }}</span>
             </div>
             </div>
             <template #overlay>
             <template #overlay>
               <a-menu>
               <a-menu>
-                <a-menu-item v-for="(item, index) in textLinkList2" :key="index">
-                  <div>
-                    <span class="text_hover_underline">{{ $t(`textLink["${item.text}"]`) }}</span>
-                  </div>
-                </a-menu-item>
-                <a-menu-divider />
-                <a-menu-item v-for="(item, index) in textLinkList" :key="index">
-                  <div>
-                    <span class="text_hover_underline">{{ $t(`textLink["${item.text}"]`) }}</span>
+                <a-menu-item v-for="(item, index) in systemStore.localeList" :key="index">
+                  <div @click="onLocaleChange(item)">
+                    <span class="text_hover_underline">{{ item.title }}</span>
                   </div>
                   </div>
                 </a-menu-item>
                 </a-menu-item>
               </a-menu>
               </a-menu>
             </template>
             </template>
           </a-dropdown>
           </a-dropdown>
+          <!-- <div class="login-btn" @click="goLogin">
+            <span>{{ $t('system["登录"]') }}</span>
+          </div> -->
+          <!-- <div class="download-btn-normal">
+            <a-popconfirm placement="bottom" :showCancel="false">
+              <template #okButton></template>
+              <template #icon><question-circle-outlined style="color: red" /></template>
+              <template #title>
+                <div style="display: flex; flex-direction: column; align-items: center">
+                  <img
+                    src="@/public/images/download_qrcode.png"
+                    style="width: 10rem; height: 10rem"
+                  />
+                  <span>VavaBuy App</span>
+                </div>
+              </template>
+              <div class="download-btn">
+                <img src="@/public/images/icons/download_icon.png" alt="" />
+                <span>{{ $t('system["download"]') }}</span>
+              </div>
+            </a-popconfirm>
+          </div> -->
+          <!-- <div class="download-btn-768">
+            <div class="download-btn" @click="handleDownload('apkLinks')">
+              <img src="@/public/images/icons/download_icon.png" alt="" />
+              <span>{{ $t('system["download"]') }}</span>
+            </div>
+          </div> -->
+
+          <div
+            class="store-download-btn"
+            style="margin-right: 10px"
+            @click="handleDownload('appleLink')"
+          >
+            <img
+              src="@/public/images/icons/app_store_icon.png"
+              alt=""
+              class="store-download-btn__icon"
+            />
+            <span>App Store</span>
+          </div>
+          <div class="store-download-btn" @click="handleDownload('googleLink')">
+            <img src="@/public/images/icons/play_store_icon.png" alt="" />
+            <span>Play Store</span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- subnav -->
+    <div class="nav__container subnav">
+      <div class="nav__box subnav__box">
+        <!-- left -->
+        <div class="nav__left subnav__left">
+          <img src="@/public/images/logo.png" @click="goHome" />
+        </div>
+
+        <!-- right -->
+        <div class="nav__right subnav__right">
+          <!-- link list -->
+          <ul v-if="textLinkList" class="text-link__list">
+            <li
+              v-for="(item, index) in textLinkList2"
+              :key="index"
+              class="text-link__item"
+              @click="handleTextLinkClick(item, item.id ? item.id : '')"
+            >
+              <span
+                class="text_hover_underline"
+                :class="[
+                  (currentId && currentId == item.id && currentPath == item.path) ||
+                  (!currentId && currentPath == item.path)
+                    ? 'text_hover_underline--active'
+                    : '',
+                ]"
+                :style="{
+                  color: item.textColor ? item.textColor : '',
+                }"
+                >{{ $t(`textLink["${item.text}"]`) }}</span
+              >
+            </li>
+          </ul>
+
+          <!-- menu -->
+          <div class="nav-menus">
+            <a-dropdown>
+              <div class="menus__btn">
+                <MenuOutlined style="font-size: 20px; font-size: 1.25rem; color: #000" />
+              </div>
+              <template #overlay>
+                <a-menu>
+                  <a-menu-item
+                    v-for="(item, index) in textLinkList2"
+                    :key="index"
+                    @click="handleTextLinkClick(item, item.id ? item.id : '')"
+                  >
+                    <div>
+                      <span
+                        class="text_hover_underline"
+                        :class="[
+                          (currentId && currentId == item.id && currentPath == item.path) ||
+                          (!currentId && currentPath == item.path)
+                            ? 'text_hover_underline--active'
+                            : '',
+                        ]"
+                        >{{ $t(`textLink["${item.text}"]`) }}</span
+                      >
+                    </div>
+                  </a-menu-item>
+                  <a-menu-divider />
+                  <a-menu-item
+                    v-for="(item, index) in textLinkList"
+                    :key="index"
+                    @click="handleTextLinkClick(item, item.id ? item.id : '')"
+                  >
+                    <div>
+                      <span
+                        class="text_hover_underline"
+                        :class="[
+                          (currentId && currentId == item.id && currentPath == item.path) ||
+                          (!currentId && currentPath == item.path)
+                            ? 'text_hover_underline--active'
+                            : '',
+                        ]"
+                        >{{ $t(`textLink["${item.text}"]`) }}</span
+                      >
+                    </div>
+                  </a-menu-item>
+                </a-menu>
+              </template>
+            </a-dropdown>
+          </div>
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
-  </div>
+  </section>
 </template>
 </template>
 
 
 
 
@@ -88,18 +189,58 @@ import { useI18n } from 'vue-i18n'
 import { storeToRefs } from 'pinia'
 import { storeToRefs } from 'pinia'
 import { MenuOutlined, HomeOutlined } from '@ant-design/icons-vue'
 import { MenuOutlined, HomeOutlined } from '@ant-design/icons-vue'
 import useSystemStore from '@/stores/useSystemStore'
 import useSystemStore from '@/stores/useSystemStore'
-import type { ILocale } from '@/stores/useSystemStore'
+import type { ILangs } from '@/stores/useSystemStore'
 import useLinkList from './hooks/useLinkList'
 import useLinkList from './hooks/useLinkList'
+import { reactive } from 'vue'
 
 
+// 国际化
 const i18n = useI18n()
 const i18n = useI18n()
 const systemStore = useSystemStore()
 const systemStore = useSystemStore()
-const { localeInfo, localeList } = storeToRefs(systemStore)
+const { localeInfo, localeList, locale } = storeToRefs(systemStore)
+
+// 文本链接
+const { textLinkList, textLinkList2, route, router, currentPath, currentId, handleTextLinkClick } =
+  useLinkList()
 
 
-const { textLinkList, textLinkList2, handleTextLinkClick } = useLinkList()
+// 下载连接
+const downloadLinks = reactive({
+  apkLinks: '',
+  googleLink: 'https://play.google.com/store/apps/details?id=uni.UNI0358C1D',
+  appleLink: 'https://apps.apple.com/app/vavabuy/id6748591418',
+})
 
 
-const onLocaleChange = function (info: ILocale) {
+// 语言切换回调
+const onLocaleChange = function (info: ILangs) {
   systemStore.toggleLocale(info)
   systemStore.toggleLocale(info)
-  i18n.locale.value = info.value
+  i18n.locale.value = info.code
+  const historyLength = window.history.length
+  if (historyLength > 1) {
+    window.history.go(-historyLength + 2)
+  }
+  router.replace('/')
+  setTimeout(() => {
+    window.location.reload()
+  })
+}
+
+// 跳转首页回调
+const goHome = function () {
+  router.push({
+    path: '/',
+  })
+}
+
+// 登录跳转
+const goLogin = function () {
+  window.open('https://vavabuy.net/#/pages/login/login', '__blank')
+}
+
+// App 下载
+function handleDownload(key) {
+  if (!key) return
+  let link = downloadLinks[key]
+  if (!link) return
+  window.open(link)
 }
 }
 </script>
 </script>
 
 
@@ -153,6 +294,7 @@ const onLocaleChange = function (info: ILocale) {
   img {
   img {
     width: 150px;
     width: 150px;
     width: 9.375rem;
     width: 9.375rem;
+    cursor: pointer;
   }
   }
 }
 }
 
 
@@ -190,6 +332,74 @@ const onLocaleChange = function (info: ILocale) {
   cursor: pointer;
   cursor: pointer;
 }
 }
 
 
+.store-download-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 76px;
+  min-width: 4.75rem;
+  height: 30px;
+  height: 1.875rem;
+  padding: 0 8px;
+  padding: 0 0.5rem;
+  background: #000000;
+  border-radius: 6px;
+  border-radius: 0.375rem;
+  text-align: center;
+  color: #fff;
+  line-height: 30px;
+  line-height: 1.875rem;
+  cursor: pointer;
+
+  img {
+    width: 18px;
+    width: 1.125rem;
+    height: 18px;
+    height: 1.125rem;
+    margin-right: 4px;
+  }
+
+  .store-download-btn__icon {
+    width: 20px;
+    width: 1.25rem;
+    height: 20px;
+    height: 1.25rem;
+  }
+}
+
+.download-btn-768 {
+  display: none;
+}
+
+.download-btn-normal {
+  display: block;
+}
+
+.download-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: auto;
+  height: 30px;
+  height: 1.875rem;
+  margin-left: 16px;
+  margin-left: 1rem;
+  padding: 0 8px;
+  background: #fff;
+  border: 1px solid #555;
+  border-radius: 6px;
+  border-radius: 0.375rem;
+  text-align: center;
+  cursor: pointer;
+
+  img {
+    width: 20px;
+    width: 1.25rem;
+    margin-right: 8px;
+    margin-right: 0.5rem;
+  }
+}
+
 .text-link__list {
 .text-link__list {
   display: flex;
   display: flex;
   align-content: center;
   align-content: center;
@@ -233,6 +443,12 @@ const onLocaleChange = function (info: ILocale) {
 }
 }
 </style>
 </style>
 
 
+<style>
+:where(.css-dev-only-do-not-override-1p3hq3p).ant-popconfirm .ant-popconfirm-message-title {
+  margin-inline-start: 0;
+}
+</style>
+
 <style lang="less" scoped>
 <style lang="less" scoped>
 @import url(./css/header@1200.less);
 @import url(./css/header@1200.less);
 @import url(./css/header@1024.less);
 @import url(./css/header@1024.less);

+ 4 - 0
src/views/Layout/modules/Main/index.vue

@@ -11,4 +11,8 @@ import { RouterView } from 'vue-router'
 
 
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
+#main__container {
+  position: relative;
+  flex: 1;
+}
 </style>
 </style>

二進制
vava_buy_official.zip


+ 4 - 0
vite.config.ts

@@ -15,4 +15,8 @@ export default defineConfig({
       '@': fileURLToPath(new URL('./src', import.meta.url))
       '@': fileURLToPath(new URL('./src', import.meta.url))
     },
     },
   },
   },
+  server: {
+    port: 5000,
+    host: '0.0.0.0'
+  }
 })
 })