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. BIN
      src/public/images/avatar/avatar_1.jpg
  18. BIN
      src/public/images/avatar/avatar_10.jpg
  19. BIN
      src/public/images/avatar/avatar_11.jpg
  20. BIN
      src/public/images/avatar/avatar_2.jpg
  21. BIN
      src/public/images/avatar/avatar_3.jpg
  22. BIN
      src/public/images/avatar/avatar_4.jpg
  23. BIN
      src/public/images/avatar/avatar_5.jpg
  24. BIN
      src/public/images/avatar/avatar_6.jpg
  25. BIN
      src/public/images/avatar/avatar_7.jpg
  26. BIN
      src/public/images/avatar/avatar_8.jpg
  27. BIN
      src/public/images/avatar/avatar_9.jpg
  28. 0 0
      src/public/images/avatar_2.png
  29. BIN
      src/public/images/download_qrcode.png
  30. BIN
      src/public/images/icons/aodaliya_logo_icon.png
  31. BIN
      src/public/images/icons/app_store_icon.png
  32. BIN
      src/public/images/icons/download_icon.png
  33. BIN
      src/public/images/icons/menu_icon.png
  34. BIN
      src/public/images/icons/play_store_icon.png
  35. BIN
      src/public/images/icons/xinxilan_logo_icon.png
  36. BIN
      src/public/images/icons/yidali_logo_icon.png
  37. BIN
      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. BIN
      vava_buy_official.zip
  65. 4 0
      vite.config.ts

+ 2 - 1
.env.development

@@ -1,5 +1,6 @@
 # 请求 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"

+ 1 - 1
.env.production

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

+ 69 - 11
index.html

@@ -1,13 +1,71 @@
 <!DOCTYPE html>
 <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",
   "scripts": {
     "dev": "vite",
-    "build": "run-p type-check \"build-only {@}\" --",
+    "build": "vite build",
     "preview": "vite preview",
     "build-only": "vite build",
     "type-check": "vue-tsc --build",
@@ -41,4 +41,4 @@
     "vite-plugin-vue-devtools": "^7.7.2",
     "vue-tsc": "^2.2.8"
   }
-}
+}

+ 21 - 0
src/App.vue

@@ -1,5 +1,26 @@
 <script setup lang="ts">
+import { onMounted, onBeforeMount } from 'vue'
 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>
 
 <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 {
   position: relative;
 }
+
 .text_hover_underline:hover {
-  color: var(--color-active)!important;
+  color: var(--color-active) !important;
   cursor: pointer;
 }
+
 .text_hover_underline::after {
   content: "";
   position: absolute;
@@ -18,9 +20,19 @@
   border-radius: 2px;
   transition: all .25s;
 }
+
 .text_hover_underline:hover::after {
   width: 100%;
 }
+
+.text_hover_underline--active {
+  color: var(--color-active);
+}
+
+.text_hover_underline--active.text_hover_underline::after {
+  width: 100%;
+}
+
 /* -------------------- 文本 hover 下划线 end -------------------- */
 
 

+ 13 - 2
src/assets/base.css

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

+ 40 - 3
src/assets/variable.css

@@ -1,6 +1,11 @@
-:root {
+:root,
+html {
   --font-size: 16px;
   --font-size: 1rem;
+
+  --title-size: 30px;
+  --title-size: 1.875rem;
+
   --font-color: #333;
   --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) {
-  :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 en from './lang/en'
+import es from './lang/es'
 import zh from './lang/zh'
 
 
 // 获取当前语言
 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({
   legacy: false,
   locale: (getCurrentLang() as string),
-  fallbackLocale: 'zhCN',
+  fallbackLocale: 'en',
   messages: {
-    en: en,
-    zhCN: zh,
+    en,
+    es,
+    zh,
   },
 });
 

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

@@ -1,6 +1,5 @@
 export default {
   system: {
-    "中文": "English",
     "登录": "Log in",
     "立即免费注册": "Register immediately for free",
     "可以在数千家商店购物和支付": "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",
     "下单购买": "Place an order for purchase",
     "在商店购物,运送到您的新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",
     "VAVA BUY 优势对比": "VAVA BUY Advantage Comparison",
     "包裹运送到全球,而且还在不断增加...": "Packages are being delivered worldwide, and the number is constantly increasing...",
@@ -27,8 +26,19 @@ export default {
     "新西兰": "New Zealand",
     "澳大利亚": "Australia",
     "意大利": "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: {
     "常见问题": "FAQ",
@@ -36,7 +46,7 @@ export default {
     "如何运作": "How it works",
     "为什么选择 VAVA BUY": "Why Choose VAVA BUY",
     "优势": "Advantage",
-    "在 VAVA BUY 购物": "Shopping at VAVA BUY",
+    "开始在 VAVA BUY 购物": "Start shopping at VAVA BUY",
     "VAVA BUY 是如何工作的": "How VAVA BUY Works",
     "您所在国家/地区的运费和价格": "Shipping & Pricing for your Country",
     "报名": "Sign Up",
@@ -58,10 +68,69 @@ export default {
     "获取 VAVA BUY 应用程序": "Get the VAVA BUY app",
   },
   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",
+    "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻": "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",
     "我们使用 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 {
   system: {
-    "中文": "中文",
     "登录": "登录",
     "立即免费注册": "立即免费注册",
     "可以在数千家商店购物和支付": "可以在数千家商店购物和支付",
@@ -19,15 +18,23 @@ export default {
     "下单购买": "下单购买",
     "在商店购物,运送到您的新VAVA BUY地址": "在商店购物,运送到您的新VAVA BUY地址",
     "收货转运": "收货转运",
-    "合并包裹并节省高达80%的运费": "合并包裹并节省高达80%的运费",
+    "合并包裹并节省高达80%的运费": "合并包裹并节省高达 80% 的运费",
     "VAVA BUY 优势对比": "VAVA BUY 优势对比",
     "包裹运送到全球,而且还在不断增加...": "包裹运送到全球,而且还在不断增加...",
     "自 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: {
     "常见问题": "常见问题",
@@ -35,7 +42,7 @@ export default {
     "如何运作": "如何运作",
     "为什么选择 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 应用程序",
   },
   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 更好的了",
+    "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻": "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻",
     "非常易于使用,包装很精美,会收获很多惊喜": "非常易于使用,包装很精美,会收获很多惊喜",
-    "我们使用 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/request"
+
 import App from './App.vue'
 import router from './router'
 
 
-createApp(App)
-  .use(createPinia())
+const app = createApp(App)
+
+
+app.use(createPinia())
   .use(router)
   .use(Antd)
   .use(i18n)

BIN
src/public/images/avatar/avatar_1.jpg


BIN
src/public/images/avatar/avatar_10.jpg


BIN
src/public/images/avatar/avatar_11.jpg


BIN
src/public/images/avatar/avatar_2.jpg


BIN
src/public/images/avatar/avatar_3.jpg


BIN
src/public/images/avatar/avatar_4.jpg


BIN
src/public/images/avatar/avatar_5.jpg


BIN
src/public/images/avatar/avatar_6.jpg


BIN
src/public/images/avatar/avatar_7.jpg


BIN
src/public/images/avatar/avatar_8.jpg


BIN
src/public/images/avatar/avatar_9.jpg


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


BIN
src/public/images/download_qrcode.png


BIN
src/public/images/icons/aodaliya_logo_icon.png


BIN
src/public/images/icons/app_store_icon.png


BIN
src/public/images/icons/download_icon.png


BIN
src/public/images/icons/menu_icon.png


BIN
src/public/images/icons/play_store_icon.png


BIN
src/public/images/icons/xinxilan_logo_icon.png


BIN
src/public/images/icons/yidali_logo_icon.png


BIN
src/public/images/logo_icon.png


+ 14 - 3
src/router/index.ts

@@ -10,13 +10,24 @@ const routes: Array<RouteRecordRaw> = [
     path: '/',
     name: 'layout',
     component: Layout,
-    redirect: '/home',
+    // redirect: '/home',
     children: [
       {
-        path: 'home',
+        // path: 'home',
+        path: '',
         name: '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 { getCurrentLang } from '@/locale/index'
-
+import { getLocaleListApi } from '@/apis/system'
 
 
 export interface ISystemState {
   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 => ({
     locale: getCurrentLang() as string,
     localeList: [
-      { name: '简体中文', value: 'zhCN' },
-      { name: 'English', value: 'en' },
-    ]
+      { title: '中文(简体)', code: 'zh' },
+      { title: 'English', code: 'en' },
+    ],
+    screenWidth: 1920
   }),
 
   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: {
-    toggleLocale(localeInfo: ILocale) {
+    toggleLocale(localeInfo: ILangs) {
       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
 

+ 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,
-})
+})
+
+
+ 
+// 请求拦截器
+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>
-  <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>
 
 

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

@@ -6,14 +6,10 @@
     <!-- content -->
     <div class="banners__content">
       <div class="banners__content-title">
-        <span>{{ $t('paragraph["国际运转·一站解决"]') }}</span>
+        <span>{{ $t('paragraph["bannerTitle"]') }}</span>
       </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>
       </div>
     </div>
@@ -22,6 +18,10 @@
 
 
 <script lang="ts" setup>
+// 注册跳转
+const goRegister = function () {
+  window.open('https://vavabuy.net/#/pages/login/register', '__blank')
+}
 </script>
 
 
@@ -67,6 +67,7 @@
     font-size: 18px;
     font-size: 1.125rem;
     color: rgba(255, 255, 255, 0.8);
+    line-height: 2;
     transition: all 0.3s;
   }
 

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

@@ -10,16 +10,26 @@
         <div class="swiper-wrapper">
           <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-content">{{ $t(`paragraph["${item.content}"]`) }}</div>
+              <div class="swiper-slide__card-content">{{ $t(`comments["${item.content}"]`) }}</div>
             </div>
             <div class="swiper-slide__card-body">
               <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 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>
@@ -40,9 +50,14 @@ import Swiper from 'swiper'
 onMounted(() => {
   new Swiper('.mySwiper', {
     speed: 600,
-    // loop: true,
+    loop: true,
     slidesPerView: 'auto',
-    // centeredSlides: true,
+    centeredSlides: true,
+    autoplay: {
+      delay: 1500,
+      disableOnInteraction: false,
+      stopOnLastSlide: false,
+    },
     navigation: {
       nextEl: '.swiper-button-next',
       prevEl: '.swiper-button-prev',
@@ -52,23 +67,72 @@ onMounted(() => {
 
 const dataList = [
   {
-    name: '囧二',
+    name: '马库斯',
+    address: '奥地利',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '商品易于使用,质量很棒。物流给力,包装精美,客服态度热情!',
+  },
+  {
+    name: '劳伦斯',
     address: '新西兰',
-    avatar: [],
+    avatar: ['../../../../public/images/icons/xinxilan_logo_icon.png'],
     content: '如果你想买其他东西,没有比 Vava buy 更好的了',
+    locale: '',
   },
   {
-    name: '囧二',
+    name: '皮埃尔',
+    address: '法国',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '飞快送达,包装妥帖,商品完美。会再来光顾的!',
+  },
+  {
+    name: '约翰',
     address: '澳大利亚',
-    avatar: [],
-    content: '如果你想买其他东西,没有比 Vava buy 更好的了',
+    avatar: ['../../../../public/images/icons/aodaliya_logo_icon.png'],
+    content: '太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻',
   },
   {
-    name: '囧二',
+    name: '安娜',
     address: '意大利',
-    avatar: [],
+    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: '清关过程很顺利,物流速度很快。包装处理得很棒,连说明书都套了保护袋!',
+  },
+  {
+    name: '卢卡',
+    address: '意大利',
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
+    content: '物流全程透明,从出库到派送都能看到。包装很结实,拆开后商品崭新如初!',
+  },
 ]
 </script>
 
@@ -101,8 +165,8 @@ const dataList = [
   .wrap__five-box {
     display: inline-block;
     width: auto;
-    max-width: 1400px;
-    max-width: 87.5rem;
+    max-width: 1024px;
+    max-width: 64rem;
   }
 
   .mySwiper {
@@ -172,10 +236,20 @@ const dataList = [
     }
 
     .swiper-slide__card-avatar img {
+      width: 56px;
+      width: 3.5rem;
+      height: 56px;
+      height: 3.5rem;
+      border-radius: 50%;
       position: relative;
       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) {
       position: absolute;
       left: 30px;

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

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

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

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

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

@@ -4,7 +4,9 @@
     <div class="mask-bg"></div>
     <div class="wrap__four-body">
       <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 class="wrap__four-desc trans-300">
         <span>{{ $t('system["包裹运送到全球,而且还在不断增加..."]') }}</span>
@@ -19,6 +21,67 @@
 
 
 <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>
 
 
@@ -59,11 +122,14 @@
     .wrap__four-total {
       margin-bottom: 30px;
       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 {

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

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

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

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

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

@@ -1,13 +1,21 @@
 @media screen and (max-width: 512px) {
-  
+
   .footer-friendlink {
 
-    .footer-wrap {
-      
-    }
+    .footer-wrap {}
 
     .footer-friendlink__box {
       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>
-  <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 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 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 class="footer__container footer-footnote">
+    <!-- <div class="footer__container footer-footnote">
     <div class="footer__box width-1200">
       <span>{{
         $t(
@@ -43,49 +83,60 @@
       }}</span>
     </div>
   </div> -->
+  </section>
 </template>
 
 
 <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>
 
 
@@ -106,7 +157,9 @@ const linkList3 = [
   }
 
   .footer-friendlink__box {
-    width: auto;
+    // width: 25%;
+    max-width: 360px;
+    max-width: 22.5rem;
     padding: 6px 10px 10px;
     padding: 0.375rem 0.625rem 0.625rem;
   }
@@ -118,13 +171,27 @@ const linkList3 = [
   .footer-friendlink__box .footer-friendlink__logo {
     width: 150px;
     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 {
-    margin-bottom: 4px;
-    margin-bottom: 0.25rem;
+    margin-bottom: 6px;
+    margin-bottom: 0.375rem;
     font-family: Poppins;
     font-size: 14px;
     font-size: 0.875rem;
@@ -132,10 +199,12 @@ const linkList3 = [
     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 {
@@ -148,7 +217,7 @@ const linkList3 = [
   }
 }
 
-.footer-right {
+.footer-all-right {
   background-color: #000;
 
   .footer__box {
@@ -176,4 +245,4 @@ const linkList3 = [
 @import url(./css/footer@1024.less);
 @import url(./css/footer@768.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;
     }
   }
+
+  .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 {
   text: string;
-  link: string;
+  id?: string | number;
+  path?: string;
+  url?: string;
+  textColor?: string;
 }
 
 
 function useLinkList() {
   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>>([
-    { 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 {
     textLinkList,
     textLinkList2,
+    route,
+    router,
+    currentPath,
+    currentId,
     handleTextLinkClick
   }
 }

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

@@ -1,83 +1,184 @@
 <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>
-
-  <!-- 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>
-            <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>
             <template #overlay>
               <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>
                 </a-menu-item>
               </a-menu>
             </template>
           </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>
+  </section>
 </template>
 
 
@@ -88,18 +189,58 @@ import { useI18n } from 'vue-i18n'
 import { storeToRefs } from 'pinia'
 import { MenuOutlined, HomeOutlined } from '@ant-design/icons-vue'
 import useSystemStore from '@/stores/useSystemStore'
-import type { ILocale } from '@/stores/useSystemStore'
+import type { ILangs } from '@/stores/useSystemStore'
 import useLinkList from './hooks/useLinkList'
+import { reactive } from 'vue'
 
+// 国际化
 const i18n = useI18n()
 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)
-  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>
 
@@ -153,6 +294,7 @@ const onLocaleChange = function (info: ILocale) {
   img {
     width: 150px;
     width: 9.375rem;
+    cursor: pointer;
   }
 }
 
@@ -190,6 +332,74 @@ const onLocaleChange = function (info: ILocale) {
   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 {
   display: flex;
   align-content: center;
@@ -233,6 +443,12 @@ const onLocaleChange = function (info: ILocale) {
 }
 </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>
 @import url(./css/header@1200.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>
+#main__container {
+  position: relative;
+  flex: 1;
+}
 </style>

BIN
vava_buy_official.zip


+ 4 - 0
vite.config.ts

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