Эх сурвалжийг харах

2025年06月06日:01)完成 “常见问题” 和 “文章详情” 页开发;02)网页内容优化

yankun 1 сар өмнө
parent
commit
7de914e00b
35 өөрчлөгдсөн 805 нэмэгдсэн , 609 устгасан
  1. 1 1
      .env.development
  2. 1 1
      .env.production
  3. 9 0
      index.html
  4. 19 0
      src/App.vue
  5. 32 0
      src/apis/article.ts
  6. 0 9
      src/apis/system.ts
  7. 2 2
      src/locale/index.ts
  8. 19 5
      src/locale/lang/en.ts
  9. 16 5
      src/locale/lang/zh.ts
  10. BIN
      src/public/images/avatar_1.jpg
  11. 0 0
      src/public/images/avatar_2.png
  12. BIN
      src/public/images/avatar_3.jpg
  13. BIN
      src/public/images/avatar_4.jpg
  14. BIN
      src/public/images/icons/aodaliya_logo_icon.png
  15. BIN
      src/public/images/icons/xinxilan_logo_icon.png
  16. BIN
      src/public/images/icons/yidali_logo_icon.png
  17. BIN
      src/public/images/logo_icon.png
  18. 123 0
      src/stores/useFooterStore.ts
  19. 13 2
      src/stores/useSystemStore.ts
  20. 3 0
      src/utils/rem.ts
  21. 5 1
      src/utils/request.ts
  22. 112 0
      src/views/ArticleDetails/hooks/useData.ts
  23. 58 98
      src/views/ArticleDetails/index.vue
  24. 78 186
      src/views/FAQ/hooks/useData.ts
  25. 129 92
      src/views/FAQ/index.vue
  26. 3 6
      src/views/Home/modules/Banners/index.vue
  27. 25 9
      src/views/Home/modules/WrapFive/index.vue
  28. 20 2
      src/views/Home/modules/WrapTwo/index.vue
  29. 2 0
      src/views/Layout/index.vue
  30. 0 102
      src/views/Layout/modules/Footer/hooks/useLink.ts
  31. 112 82
      src/views/Layout/modules/Footer/index.vue
  32. 7 6
      src/views/Layout/modules/Header/hooks/useLinkList.ts
  33. 8 0
      src/views/Layout/modules/Header/index.vue
  34. 4 0
      src/views/Layout/modules/Main/index.vue
  35. 4 0
      vite.config.ts

+ 1 - 1
.env.development

@@ -1,5 +1,5 @@
 # 请求 url
 # 请求 url
-VITE_BASE_URL=
+VITE_BASE_URL=https://vvbuy.csvip.top/api
 
 
 # 标题
 # 标题
 VITE_APP_TITLE="VAVA BUY"
 VITE_APP_TITLE="VAVA BUY"

+ 1 - 1
.env.production

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

+ 9 - 0
index.html

@@ -9,5 +9,14 @@
   <body>
   <body>
     <div id="app"></div>
     <div id="app"></div>
     <script type="module" src="/src/main.ts"></script>
     <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 -->
   </body>
   </body>
 </html>
 </html>

+ 19 - 0
src/App.vue

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

+ 32 - 0
src/apis/article.ts

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

+ 0 - 9
src/apis/system.ts

@@ -1,9 +0,0 @@
-import request from '@/utils/request'
-
-
-
-export const getInfo = (params: Object) => request({
-  url: '/api/cate/getCateListProduct',
-  method: 'get',
-  params: params
-})

+ 2 - 2
src/locale/index.ts

@@ -5,7 +5,7 @@ import zh from './lang/zh'
 
 
 // 获取当前语言
 // 获取当前语言
 export function getCurrentLang(): string | null {
 export function getCurrentLang(): string | null {
-  return !!localStorage.getItem('locale') && localStorage.getItem('locale') !== 'null' && localStorage.getItem('locale') !== 'undefine' ? localStorage.getItem('locale') : 'zh-CN'
+  return !!localStorage.getItem('locale') && localStorage.getItem('locale') !== 'null' && localStorage.getItem('locale') !== 'undefine' ? localStorage.getItem('locale') : 'en'
 }
 }
 
 
 
 
@@ -13,7 +13,7 @@ export function getCurrentLang(): string | null {
 const i18n = createI18n({
 const i18n = createI18n({
   legacy: false,
   legacy: false,
   locale: (getCurrentLang() as string),
   locale: (getCurrentLang() as string),
-  fallbackLocale: 'zh-CN',
+  fallbackLocale: 'en',
   messages: {
   messages: {
     en: en,
     en: en,
     "zh-CN": zh,
     "zh-CN": zh,

+ 19 - 5
src/locale/lang/en.ts

@@ -18,7 +18,7 @@ export default {
     "加入VAVA BUY 以获取您的收货地址": "Join VAVA BUY to obtain your delivery address",
     "加入VAVA BUY 以获取您的收货地址": "Join VAVA BUY to obtain your delivery address",
     "下单购买": "Place an order for purchase",
     "下单购买": "Place an order for purchase",
     "在商店购物,运送到您的新VAVA BUY地址": "Make a purchase at the store and have it delivered to your new VAVA BUY address",
     "在商店购物,运送到您的新VAVA BUY地址": "Make a purchase at the store and have it delivered to your new VAVA BUY address",
-    "收货转运": "Delivery transfer",
+    "收货转运": "Delivery and transfer",
     "合并包裹并节省高达80%的运费": "Combine packages and save up to 80% on shipping costs",
     "合并包裹并节省高达80%的运费": "Combine packages and save up to 80% on shipping costs",
     "VAVA BUY 优势对比": "VAVA BUY Advantage Comparison",
     "VAVA BUY 优势对比": "VAVA BUY Advantage Comparison",
     "包裹运送到全球,而且还在不断增加...": "Packages are being delivered worldwide, and the number is constantly increasing...",
     "包裹运送到全球,而且还在不断增加...": "Packages are being delivered worldwide, and the number is constantly increasing...",
@@ -27,8 +27,17 @@ export default {
     "新西兰": "New Zealand",
     "新西兰": "New Zealand",
     "澳大利亚": "Australia",
     "澳大利亚": "Australia",
     "意大利": "Italy",
     "意大利": "Italy",
-    "囧二": "Guang Er",
-    updateTimeTxt: 'Updated on {updateTime}'
+    "Lawrence": "Lawrence",
+    "Anna": "Anna",
+    "John": "John",
+    updateTimeTxt: 'Updated on {updateTime}',
+    companyName: 'Guangzhou Wawamai Trading Co., Ltd.',
+    addressLabel: 'Address',
+    companyAddress: 'Room D390, 401-466, No. 401-1, Tianyuan Road, Tianhe District, Guangzhou City',
+    hongkongAddressLabel: 'Address (Hong Kong)',
+    hongkongCompanyAddress: 'UNIT 60 3/F YAU LEE CENTER NO.45 HOI YUEN ROAD Kwun Tong Hong Kong',
+    emailLabel: 'Email',
+    hongkongEmailLabel: 'Email (Hong Kong)',
 
 
   },
   },
   textLink: {
   textLink: {
@@ -59,9 +68,14 @@ export default {
     "获取 VAVA BUY 应用程序": "Get the VAVA BUY app",
     "获取 VAVA BUY 应用程序": "Get the VAVA BUY app",
   },
   },
   paragraph: {
   paragraph: {
-    "国际运转·一站解决": "International operation · One-stop solution",
-    "连接全球优质商品,一键下单无忧,实时查看订单进程,享受便捷购物体验。": "Connect to high-quality products from all over the world. Place orders with one click and enjoy worry-free shopping. View the order progress in real time and experience the convenient shopping experience.",
+    "bannerTitle": "International operation · One-stop solution",
+    "bannerDesc": `
+      <div>Access high-quality products from around the world.</div>
+      <div>Place one-click orders and enjoy worry-free shopping.</div>
+      <div>Track your order in real time and enjoy a seamless shopping experience.</div>
+    `,
     "如果你想买其他东西,没有比 Vava buy 更好的了": "If you want to buy anything else, there's no better option than Vava Buy",
     "如果你想买其他东西,没有比 Vava buy 更好的了": "If you want to buy anything else, there's no better option than Vava Buy",
+    "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻": "Great! Everything went smoothly. The speed of receiving orders and shipping them quickly was impressive",
     "非常易于使用,包装很精美,会收获很多惊喜": "Very easy to use, with exquisite packaging, and you will receive many surprises",
     "非常易于使用,包装很精美,会收获很多惊喜": "Very easy to use, with exquisite packaging, and you will receive many surprises",
     "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。": "We use cookies to provide a better online experience. By visiting and using VAVA BUY.com, you are agreeing to our use of cookies. Please read our terms and conditions, usage terms, and privacy policy.",
     "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。": "We use cookies to provide a better online experience. By visiting and using VAVA BUY.com, you are agreeing to our use of cookies. Please read our terms and conditions, usage terms, and privacy policy.",
   },
   },

+ 16 - 5
src/locale/lang/zh.ts

@@ -19,7 +19,7 @@ export default {
     "下单购买": "下单购买",
     "下单购买": "下单购买",
     "在商店购物,运送到您的新VAVA BUY地址": "在商店购物,运送到您的新VAVA BUY地址",
     "在商店购物,运送到您的新VAVA BUY地址": "在商店购物,运送到您的新VAVA BUY地址",
     "收货转运": "收货转运",
     "收货转运": "收货转运",
-    "合并包裹并节省高达80%的运费": "合并包裹并节省高达80%的运费",
+    "合并包裹并节省高达80%的运费": "合并包裹并节省高达 80% 的运费",
     "VAVA BUY 优势对比": "VAVA BUY 优势对比",
     "VAVA BUY 优势对比": "VAVA BUY 优势对比",
     "包裹运送到全球,而且还在不断增加...": "包裹运送到全球,而且还在不断增加...",
     "包裹运送到全球,而且还在不断增加...": "包裹运送到全球,而且还在不断增加...",
     "自 Vava buy.com 成立以来": "自 Vava buy.com 成立以来",
     "自 Vava buy.com 成立以来": "自 Vava buy.com 成立以来",
@@ -27,8 +27,18 @@ export default {
     "新西兰": "新西兰",
     "新西兰": "新西兰",
     "澳大利亚": "澳大利亚",
     "澳大利亚": "澳大利亚",
     "意大利": "意大利",
     "意大利": "意大利",
-    "囧二": "囧二",
-    updateTimeTxt: '更新于 {updateTime}'
+    "Lawrence": "劳伦斯",
+    "Anna": "安娜",
+    "John": "约翰",
+    updateTimeTxt: '更新于 {updateTime}',
+    companyName: '广州哇哇买商贸有限公司',
+    addressLabel: '地址',
+    companyAddress: '广州市天河区天源路401号之一401-466房D390',
+    hongkongAddressLabel: '地址(香港)',
+    hongkongCompanyAddress: '香港观塘海园道45号友利中心3楼60室',
+    emailLabel: '邮箱',
+    hongkongEmailLabel: '邮箱(香港)',
+
   },
   },
   textLink: {
   textLink: {
     "常见问题": "常见问题",
     "常见问题": "常见问题",
@@ -58,9 +68,10 @@ export default {
     "获取 VAVA BUY 应用程序": "获取 VAVA BUY 应用程序",
     "获取 VAVA BUY 应用程序": "获取 VAVA BUY 应用程序",
   },
   },
   paragraph: {
   paragraph: {
-    "国际运转·一站解决": "国际运转·一站解决",
-    "连接全球优质商品,一键下单无忧,实时查看订单进程,享受便捷购物体验。": "连接全球优质商品,一键下单无忧,实时查看订单进程,享受便捷购物体验。",
+    "bannerTitle": "国际运转·一站解决",
+    "bannerDesc": "连接全球优质商品,一键下单无忧,实时查看订单进程,享受便捷购物体验。",
     "如果你想买其他东西,没有比 Vava buy 更好的了": "如果你想买其他东西,没有比 Vava buy 更好的了",
     "如果你想买其他东西,没有比 Vava buy 更好的了": "如果你想买其他东西,没有比 Vava buy 更好的了",
+    "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻": "太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻",
     "非常易于使用,包装很精美,会收获很多惊喜": "非常易于使用,包装很精美,会收获很多惊喜",
     "非常易于使用,包装很精美,会收获很多惊喜": "非常易于使用,包装很精美,会收获很多惊喜",
     "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。": "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。",
     "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。": "我们使用 cookie 来提供更好的在线体验。 访问和使用 VAVA BUY.com,即表示您同意我们使用 Cookie。 通过阅读我们的条款和条件、使用条款、 和隐私政策。",
   },
   },

BIN
src/public/images/avatar_1.jpg


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


BIN
src/public/images/avatar_3.jpg


BIN
src/public/images/avatar_4.jpg


BIN
src/public/images/icons/aodaliya_logo_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


+ 123 - 0
src/stores/useFooterStore.ts

@@ -0,0 +1,123 @@
+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 : ''
+
+      switch(lang) {
+        case 'zh-CN':
+          lang = 'zh'; break;
+        default:
+          break;
+      }
+  
+      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

+ 13 - 2
src/stores/useSystemStore.ts

@@ -6,6 +6,7 @@ import { getCurrentLang } from '@/locale/index'
 export interface ISystemState {
 export interface ISystemState {
   locale: string;
   locale: string;
   localeList: ILocale[];
   localeList: ILocale[];
+  screenWidth: number;
 }
 }
 
 
 
 
@@ -23,13 +24,14 @@ const useSystemStore = defineStore('system', {
     localeList: [
     localeList: [
       { name: '简体中文', value: 'zh-CN' },
       { name: '简体中文', value: 'zh-CN' },
       { name: 'English', value: 'en' },
       { name: 'English', value: 'en' },
-    ]
+    ],
+    screenWidth: 1920
   }),
   }),
 
 
   getters: {
   getters: {
     localeInfo: function (): ILocale {
     localeInfo: function (): ILocale {
       const result = this.localeList.find((item: ILocale) => (item.value == this.locale))
       const result = this.localeList.find((item: ILocale) => (item.value == this.locale))
-      return result ? result : this.localeList[0]
+      return result ? result : this.localeList[1]
     }
     }
   },
   },
 
 
@@ -39,6 +41,15 @@ const useSystemStore = defineStore('system', {
         this.locale = localeInfo.value
         this.locale = localeInfo.value
         localStorage.setItem('locale', localeInfo.value)
         localStorage.setItem('locale', localeInfo.value)
       }
       }
+    },
+
+    updateScreenWidth(width: number) {
+      if (width < 325) {
+        this.screenWidth = 325
+      }
+      else {
+        this.screenWidth = width
+      }
     }
     }
   }
   }
 })
 })

+ 3 - 0
src/utils/rem.ts

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

+ 5 - 1
src/utils/request.ts

@@ -21,6 +21,9 @@ http.interceptors.request.use(
     if (token) {
     if (token) {
       config.headers.token = token
       config.headers.token = token
     }
     }
+    let lang = localStorage.getItem('locale')
+    lang = lang && lang == 'zh-CN' ? 'zh' : lang
+    config.headers.lang = lang ? lang : 'en'
     return config
     return config
   },
   },
   err => {
   err => {
@@ -31,7 +34,8 @@ http.interceptors.request.use(
 // 响应拦截器
 // 响应拦截器
 http.interceptors.response.use(
 http.interceptors.response.use(
   res => {
   res => {
-    if (res.data.code == 1) {
+    // console.log('res', res)
+    if (res.data.ret == 1) {
       return Promise.resolve(res.data)
       return Promise.resolve(res.data)
     }
     }
     else{
     else{

+ 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

+ 58 - 98
src/views/ArticleDetails/index.vue

@@ -1,103 +1,54 @@
 <template>
 <template>
   <section class="article-container">
   <section class="article-container">
-    <!-- 文章标题 -->
-    <h1 class="article-title">{{ article.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">
-        <span>{{
-          $t('system["updateTimeTxt"]', {
-            updateTime: formatDate(article.publishTime, locale),
-          })
-        }}</span>
-      </div>
-    </div>
-
-    <!-- 富文本内容区域 -->
-    <div class="article-content" v-html="article.content"></div>
-
-    <!-- 标签 -->
-    <div class="article-tags" v-if="article.tags && article.tags.length">
-      <div class="tag" v-for="(tag, i) in article.tags" :key="i">
-        <span>{{ tag }}</span>
-      </div>
+    <div class="loading-box" v-show="loading">
+      <a-spin :spinning="loading" size="large" />
     </div>
     </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>
   </section>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { storeToRefs } from 'pinia'
 import { storeToRefs } from 'pinia'
-import { ref } from 'vue'
 import useSystemStore from '@/stores/useSystemStore'
 import useSystemStore from '@/stores/useSystemStore'
+import useData from './hooks/useData'
+import type { IDataInfo } from './hooks/useData'
 
 
+// system store
 const systemStore = useSystemStore()
 const systemStore = useSystemStore()
 const { locale } = storeToRefs(systemStore)
 const { locale } = storeToRefs(systemStore)
 
 
-// 模拟文章数据
-const article = ref({
-  title: '《平凡的世界》节选:黄土高原上的青春与奋斗',
-  author: {
-    name: '路遥',
-    avatar: 'https://via.placeholder.com/40',
-  },
-  publishTime: '2023-05-15T10:30:00',
-  content: `
-  <h2>第一章</h2>
-  
-  <p>一九七五年二三月间,一个平平常常的日子,细蒙蒙的雨丝夹着一星半点的雪花,正纷纷淋淋地向大地飘洒着。时令已快到惊蛰,雪当然再不会存留,往往还没等落地,就已经消失得无踪无影了。黄土高原严寒而漫长的冬天看来就要过去,但那真正温暖的春天还远远地没有到来。</p>
-  
-  <p>在这样雨雪交加的日子里,如果没有什么紧要事,人们宁愿一整天足不出户。因此,县城的大街小巷倒也比平时少了许多嘈杂。街巷背阴的地方,冬天残留的积雪和冰溜子正在雨点的敲击下蚀化,石板街上到处都漫流着肮脏的污水。</p>
-  
-  <blockquote>
-    "生活包含着更广阔的意义,而不在于我们实际得到了什么;关键是我们的心灵是否充实。"
-  </blockquote>
-  
-  <h3>孙少平的出场</h3>
-  
-  <p>就在这时候,在空旷的院坝的北头,走过来一个瘦高个的青年人。他胳膊窝里夹着一只碗,缩着脖子在泥地里蹒跚而行。小伙子脸色黄瘦,而且两颊有点塌陷,显得鼻子像希腊人一样又高又直。脸上看来才刚刚褪掉少年的稚气——显然由于营养不良,还没有焕发出他这种年龄所特有的那种青春光彩。</p>
-  
-  <p>他撩开两条瘦长的腿,扑踏扑踏地踩着泥水走着。这也许就是那几个黑面馍的主人?看他那一身可怜的穿戴想必也只能吃这种伙食。瞧吧,他那身衣服尽管式样裁剪得勉强还算是学生装,但分明是自家织出的那种老土粗布,而且黑颜料染得很不均匀,给人一种肮肮脏脏的感觉。脚上的一双旧黄胶鞋已经没有了鞋带,凑合着系两根白线绳;一只鞋帮上甚至还缀补着一块蓝布补丁。裤子显然是前两年缝的,人长布缩,现在已经短窄得吊在了半腿把上;幸亏袜腰高,否则就要露肉了。</p>
-  
-  <img src="https://via.placeholder.com/600x400?text=黄土高原风光" alt="黄土高原风光" style="margin: 20px auto; display: block; border-radius: 8px;">
-  
-  <h2>青春的苦涩与尊严</h2>
-  
-  <p>他独个儿来到馍筐前,先怔了一下,然后便弯腰拾了两个高粱面馍。筐里还剩两个,不知他为什么没有拿。他直起身子来,眼睛不由地朝三只空荡荡的菜盆里瞥了一眼。他瞧见乙菜盆的底子上还有一点残汤剩水。房上的檐水滴答下来,盆底上的菜汤四处飞溅。他扭头瞧了瞧:雨雪迷蒙的大院坝里空无一人。他很快蹲下来,慌得如同偷窃一般,用勺子把盆底上混合着雨水的剩菜汤往自己的碗里舀。铁勺刮盆底的嘶啦声像炸弹的爆炸声一样令人惊心。血涌上了他黄瘦的脸。一滴很大的檐水落在盆底,溅了他一脸菜汤。他闭住眼,紧接着,就见两颗泪珠慢慢地从脸颊上滑落了下来——唉,我们姑且就认为这是他眼中溅进了辣子汤吧!</p>
-  
-  <p>他站起来,用手抹了一把脸,端着半碗剩菜汤,来到西南拐角处的开水房前,在水房后墙上伸出来的管子上给菜汤里搀了一些开水,然后把高粱面馍掰碎泡进去,就蹲在房檐下狼吞虎咽地吃起来。</p>
-  
-  <pre><code>这就是《平凡的世界》开篇展现的,
-一个普通农村青年的日常生活。
-没有华丽的辞藻,
-只有真实得令人心痛的细节描写。</code></pre>
-  
-  <h3>关于尊严的思考</h3>
-  
-  <p>突然间,他听见后面传来一阵低低的议论声:"瞧,这就是那个每天最后一个来拿饭的......"</p>
-  
-  <p>孙少平猛地转过身,眼睛紧张地扫视着刚才声音传来的方向。他发现是两个低年级的女生正看着他交头接耳——她们显然没有料到他会突然转身,立刻尴尬地低下头,装作若无其事地从他身边匆匆走过。</p>
-  
-  <p>孙少平站在那里,手里端着那碗被雨水稀释了的菜汤,感到一种难以言喻的屈辱。他知道,在这所县立高中,像他这样从农村来的学生并不多,而像他这样穷困的就更少了。大多数同学至少还能吃上玉米面馍,像他这样顿顿啃高粱面馍的,恐怕全校也就他一个。</p>
-  
-  <ul>
-    <li>贫穷不是耻辱,但确实带来了难以言说的痛苦</li>
-    <li>青春期的自尊心在这种环境下显得格外脆弱</li>
-    <li>物质匮乏却无法阻挡精神世界的成长</li>
-  </ul>
-  
-  <h2>结语</h2>
-  
-  <p>这就是路遥笔下的《平凡的世界》,它展现了中国社会变革时期普通人的生存状态和精神世界。孙少平的形象,代表了一代农村青年在城乡二元结构下的挣扎与奋斗。小说通过这些看似平凡的日常生活描写,展现了不平凡的人性光辉。</p>
-  
-  <p>正如路遥在书中所写:"<strong>生活不能等待别人来安排,要自己去争取和奋斗;而不论其结果是喜是悲,但可以慰藉的是,你总不枉在这世界上活了一场。</strong>"</p>
-`,
-  tags: ['路遥', '《平凡的世界》', '茅盾文学奖', '现实主义'],
-})
+const { dataInfo, articleType, loading } = useData()
 
 
 // 格式化日期
 // 格式化日期
 const formatDate = (dateString: string, lang: string) => {
 const formatDate = (dateString: string, lang: string) => {
@@ -108,8 +59,9 @@ const formatDate = (dateString: string, lang: string) => {
 
 
 <style scoped lang="less">
 <style scoped lang="less">
 .article-container {
 .article-container {
-  max-width: 1200px;
+  max-width: 800px;
   max-width: 75rem;
   max-width: 75rem;
+  max-width: 50rem;
   margin: 0 auto;
   margin: 0 auto;
   padding: 60px 15px;
   padding: 60px 15px;
   padding: 3.75rem 0.9375rem;
   padding: 3.75rem 0.9375rem;
@@ -117,6 +69,13 @@ const formatDate = (dateString: string, lang: string) => {
   color: #333;
   color: #333;
 }
 }
 
 
+.loading-box {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
 .article-title {
 .article-title {
   font-size: var(--title-size);
   font-size: var(--title-size);
   margin-bottom: 20px;
   margin-bottom: 20px;
@@ -196,7 +155,7 @@ const formatDate = (dateString: string, lang: string) => {
 
 
 <style>
 <style>
 /* 全局样式用于富文本内容 */
 /* 全局样式用于富文本内容 */
-.article-content >>> h2 {
+.article-content h2 {
   font-size: 1.8rem;
   font-size: 1.8rem;
   margin: 30px 0 15px;
   margin: 30px 0 15px;
   margin: 1.875rem 0 0.9375rem;
   margin: 1.875rem 0 0.9375rem;
@@ -206,42 +165,43 @@ const formatDate = (dateString: string, lang: string) => {
   padding-bottom: 0.625rem;
   padding-bottom: 0.625rem;
 }
 }
 
 
-.article-content >>> h3 {
+.article-content h3 {
   font-size: 1.4rem;
   font-size: 1.4rem;
   margin: 25px 0 12px;
   margin: 25px 0 12px;
   margin: 1.5625rem 0 0.75rem;
   margin: 1.5625rem 0 0.75rem;
   color: #333;
   color: #333;
 }
 }
 
 
-.article-content >>> p {
+.article-content p {
   margin: 15px 0;
   margin: 15px 0;
   margin: 0.9375rem 0;
   margin: 0.9375rem 0;
   font-size: 1rem;
   font-size: 1rem;
 }
 }
 
 
-.article-content >>> ul,
-.article-content >>> ol {
+.article-content ul,
+.article-content ol {
   margin: 15px 0;
   margin: 15px 0;
   margin: 0.9375rem 0;
   margin: 0.9375rem 0;
   padding-left: 30px;
   padding-left: 30px;
   padding-left: 1.875rem;
   padding-left: 1.875rem;
 }
 }
 
 
-.article-content >>> li {
+.article-content li {
   margin: 8px 0;
   margin: 8px 0;
   margin: 0.5rem 0;
   margin: 0.5rem 0;
 }
 }
 
 
-.article-content >>> a {
+.article-content a {
   color: var(--color-active);
   color: var(--color-active);
   text-decoration: none;
   text-decoration: none;
 }
 }
 
 
-.article-content >>> a:hover {
+.article-content a:hover {
   text-decoration: underline;
   text-decoration: underline;
 }
 }
 
 
-.article-content >>> img {
+.article-content img {
+  width: 100%;
   max-width: 100%;
   max-width: 100%;
   height: auto;
   height: auto;
   margin: 20px 0;
   margin: 20px 0;
@@ -250,7 +210,7 @@ const formatDate = (dateString: string, lang: string) => {
   border-radius: 0.25rem;
   border-radius: 0.25rem;
 }
 }
 
 
-.article-content >>> pre {
+.article-content pre {
   background-color: #f6f8fa;
   background-color: #f6f8fa;
   padding: 16px;
   padding: 16px;
   padding: 1rem;
   padding: 1rem;

+ 78 - 186
src/views/FAQ/hooks/useData.ts

@@ -1,197 +1,51 @@
 import { ref, computed } from 'vue'
 import { ref, computed } from 'vue'
+import { getFAQCateListApi } from '@/apis/article'
 
 
 
 
-function useData() {
+// 问题
+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>; 
+}
+
 
 
-  // 问题分类
-  const categoryList = ref([
-    { id: 1, name: '账号问题', count: 12 },
-    { id: 2, name: '支付问题', count: 8 },
-    { id: 3, name: '订单问题', count: 15 },
-    { id: 4, name: '产品使用', count: 23 },
-    { id: 5, name: '售后服务', count: 7 },
-    { id: 6, name: '其他问题', count: 5 },
-  ])
-  // 当前选中的分类 ID
-  const activeCategory = ref<string | number>(1)
-
-
-  // 模拟问题数据
-  const questions = ref([
-    {
-      id: 101,
-      categoryId: 1,
-      title: '如何注册账号?',
-      preview: '详细说明注册账号的步骤和注意事项...',
-      content: `
-      <h3>注册账号步骤</h3>
-      <ol>
-        <li>点击网站右上角的"注册"按钮</li>
-        <li>填写您的手机号码或电子邮箱</li>
-        <li>设置登录密码(6-20位字符,包含字母和数字)</li>
-        <li>输入收到的验证码</li>
-        <li>阅读并同意用户协议</li>
-        <li>点击"立即注册"完成注册</li>
-      </ol>
-      <p><strong>注意事项:</strong></p>
-      <ul>
-        <li>请使用常用手机号或邮箱,以便接收重要通知</li>
-        <li>密码请勿设置过于简单</li>
-        <li>如未收到验证码,请检查垃圾邮件或60秒后重新获取</li>
-      </ul>
-    `,
-      updateTime: '2023-05-15',
-      views: 1245,
-      isHot: true,
-      isNew: false,
-    },
-    {
-      id: 102,
-      categoryId: 1,
-      title: '忘记密码怎么办?',
-      preview: '密码找回的几种方法和步骤说明...',
-      content: `
-      <h3>找回密码方法</h3>
-      <p>如果您忘记了密码,可以通过以下方式找回:</p>
-      
-      <h4>方法一:通过手机找回</h4>
-      <ol>
-        <li>在登录页面点击"忘记密码"</li>
-        <li>选择"通过手机找回"</li>
-        <li>输入注册时使用的手机号码</li>
-        <li>获取并输入短信验证码</li>
-        <li>设置新密码并确认</li>
-      </ol>
-      
-      <h4>方法二:通过邮箱找回</h4>
-      <ol>
-        <li>在登录页面点击"忘记密码"</li>
-        <li>选择"通过邮箱找回"</li>
-        <li>输入注册时使用的邮箱地址</li>
-        <li>登录邮箱查收重置密码邮件</li>
-        <li>点击邮件中的链接设置新密码</li>
-      </ol>
-      
-      <p>如果以上方法都无法解决您的问题,请联系客服人员。</p>
-    `,
-      updateTime: '2023-06-02',
-      views: 876,
-      isHot: true,
-      isNew: false,
-    },
-    {
-      id: 201,
-      categoryId: 2,
-      title: '支持哪些支付方式?',
-      preview: '介绍平台目前支持的支付渠道...',
-      content: `
-      <h3>支持的支付方式</h3>
-      <p>我们目前支持以下支付方式:</p>
-      
-      <div class="payment-method">
-        <h4>1. 在线支付</h4>
-        <ul>
-          <li>微信支付</li>
-          <li>支付宝</li>
-          <li>银联在线支付</li>
-          <li>Apple Pay</li>
-        </ul>
-      </div>
-      
-      <div class="payment-method">
-        <h4>2. 银行卡支付</h4>
-        <p>支持以下银行的储蓄卡和信用卡:</p>
-        <ul>
-          <li>中国工商银行</li>
-          <li>中国建设银行</li>
-          <li>中国银行</li>
-          <li>招商银行</li>
-          <li>交通银行等主流银行</li>
-        </ul>
-      </div>
-      
-      <p><strong>注意:</strong>部分银行可能有支付限额,请以银行规定为准。</p>
-    `,
-      updateTime: '2023-04-28',
-      views: 1532,
-      isHot: false,
-      isNew: false,
-    },
-    {
-      id: 301,
-      categoryId: 3,
-      title: '如何查询订单状态?',
-      preview: '订单状态查询的几种途径和方法...',
-      content: `
-      <h3>查询订单状态</h3>
-      <p>您可以通过以下方式查询订单状态:</p>
-      
-      <h4>方法一:网站查询</h4>
-      <ol>
-        <li>登录您的账号</li>
-        <li>点击右上角"我的订单"</li>
-        <li>在订单列表中找到相应订单</li>
-        <li>点击"查看详情"查看订单状态</li>
-      </ol>
-      
-      <h4>方法二:手机APP查询</h4>
-      <ol>
-        <li>打开APP并登录</li>
-        <li>点击底部导航栏"我的"</li>
-        <li>选择"我的订单"</li>
-        <li>查看相应订单状态</li>
-      </ol>
-      
-      <h4>方法三:联系客服查询</h4>
-      <p>如果您无法通过以上方式查询,可以联系客服提供订单号查询。</p>
-      
-      <p><strong>常见订单状态说明:</strong></p>
-      <ul>
-        <li><span class="status waiting">待付款</span> - 订单已生成,等待支付</li>
-        <li><span class="status paid">已支付</span> - 付款成功,等待处理</li>
-        <li><span class="status shipped">已发货</span> - 商品已发出</li>
-        <li><span class="status completed">已完成</span> - 订单交易完成</li>
-        <li><span class="status cancelled">已取消</span> - 订单已取消</li>
-      </ul>
-    `,
-      updateTime: '2023-05-20',
-      views: 2104,
-      isHot: true,
-      isNew: true,
-    },
-  ])
-  // 当前查看的问题详情
-  const currentQuestion = ref(null)
+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('')
+  const searchQuery = ref<string>('')
   // 搜索结果
   // 搜索结果
-  const searchResults = ref<Array<typeof questions.value[0]>>([])
+  const searchResults = ref<Array<IFAQInfo>>([])
 
 
 
 
-  // 获取分类名称
-  const getCategoryName = (id: string | number) => {
-    const category = categoryList.value.find((c) => (c.id === id))
-    return category ? category.name : '所有问题'
-  }
-
   // 切换分类
   // 切换分类
-  const changeCategory = (categoryId: string | number) => {
-    activeCategory.value = categoryId
+  const changeCategory = (categoryInfo: ICateInfo) => {
+    activeCategoryInfo.value = categoryInfo
+    activeCategoryId.value = categoryInfo.id
     currentQuestion.value = null
     currentQuestion.value = null
     searchResults.value = []
     searchResults.value = []
   }
   }
 
 
-  // 获取当前分类下的问题
-  const questionList = computed(() => {
-    return questions.value.filter((q: any) => q.categoryId === activeCategory.value)
-  })
-
-  // 显示问题详情
-  const showQuestionDetail = (question: any) => {
-    currentQuestion.value = question
-  }
 
 
   // 搜索问题
   // 搜索问题
   const searchQuestions = () => {
   const searchQuestions = () => {
@@ -201,26 +55,64 @@ function useData() {
     }
     }
 
 
     const query = searchQuery.value.toLowerCase()
     const query = searchQuery.value.toLowerCase()
-    searchResults.value = questions.value.filter((q) => {
-      return q.title.toLowerCase().includes(query) || q.preview.toLowerCase().includes(query)
-    })
+    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
     currentQuestion.value = null
   }
   }
 
 
+
+  // 显示问题详情
+  const showQuestionDetail = (question: IFAQInfo) => {
+    currentQuestion.value = question
+  }
+
   // 返回问题列表
   // 返回问题列表
   const backToList = () => {
   const backToList = () => {
     currentQuestion.value = null
     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 {
   return {
     categoryList,
     categoryList,
-    activeCategory,
-    questionList,
+    isLoading,
+    activeCategoryId,
+    activeCategoryInfo,
     currentQuestion,
     currentQuestion,
-    questions,
     searchQuery,
     searchQuery,
     searchResults,
     searchResults,
-    getCategoryName,
     changeCategory,
     changeCategory,
     showQuestionDetail,
     showQuestionDetail,
     searchQuestions,
     searchQuestions,

+ 129 - 92
src/views/FAQ/index.vue

@@ -39,7 +39,7 @@
             <!-- 搜索结果列表 -->
             <!-- 搜索结果列表 -->
             <div v-if="searchResults.length > 0" class="search-results question-list">
             <div v-if="searchResults.length > 0" class="search-results question-list">
               <h2>
               <h2>
-                <span>{{
+                <span style="flex: 1; padding-right: 16px; padding-right: 1rem; line-height: 1.3">{{
                   $t('questionPage["searchResultTitle"]', { count: searchResults.length })
                   $t('questionPage["searchResultTitle"]', { count: searchResults.length })
                 }}</span>
                 }}</span>
                 <div class="category-menu-btn" @click="showCategoryDrawer">
                 <div class="category-menu-btn" @click="showCategoryDrawer">
@@ -56,37 +56,41 @@
                 <div class="question-title">
                 <div class="question-title">
                   {{ item.title }}
                   {{ item.title }}
                 </div>
                 </div>
-                <div class="question-preview">{{ item.preview }}</div>
+                <div class="question-preview" v-html="item.content"></div>
               </div>
               </div>
             </div>
             </div>
 
 
             <!-- 问题列表 -->
             <!-- 问题列表 -->
             <div v-else class="question-list">
             <div v-else class="question-list">
               <h2>
               <h2>
-                <span>{{ getCategoryName(activeCategory) }}</span>
+                <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">
                 <div class="category-menu-btn" @click="showCategoryDrawer">
                   <span>{{ $t('questionPage["questionCategoryMenuBtn"]') }}</span>
                   <span>{{ $t('questionPage["questionCategoryMenuBtn"]') }}</span>
                   <img src="@/public/images/icons/menu_icon.png" alt="" />
                   <img src="@/public/images/icons/menu_icon.png" alt="" />
                 </div>
                 </div>
               </h2>
               </h2>
 
 
-              <div
-                v-for="question in questionList"
-                :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"]') }}
+              <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>
+                  <div class="question-preview" v-html="question.content"></div>
                 </div>
                 </div>
-                <div class="question-preview">{{ question.preview }}</div>
-              </div>
+              </template>
             </div>
             </div>
           </template>
           </template>
 
 
@@ -118,21 +122,15 @@
 
 
             <div class="detail-header">
             <div class="detail-header">
               <h2>{{ currentQuestion.title }}</h2>
               <h2>{{ currentQuestion.title }}</h2>
-              <div class="meta">
-                <span>{{
-                  $t('system["updateTimeTxt"]', {
-                    updateTime: formatDate(currentQuestion.updateTime, locale),
-                  })
-                }}</span>
-                <span>{{
-                  $t('questionPage["questionReadCount"]', { count: currentQuestion.views })
-                }}</span>
-              </div>
             </div>
             </div>
 
 
             <div class="detail-content" v-html="currentQuestion.content"></div>
             <div class="detail-content" v-html="currentQuestion.content"></div>
           </div>
           </div>
         </div>
         </div>
+
+        <div class="loading-box" v-show="isLoading">
+          <a-spin :spinning="isLoading" size="large" />
+        </div>
       </div>
       </div>
 
 
       <!-- 分类导航侧边抽屉 -->
       <!-- 分类导航侧边抽屉 -->
@@ -151,12 +149,12 @@
             v-for="category in categoryList"
             v-for="category in categoryList"
             :key="category.id"
             :key="category.id"
             class="category-item"
             class="category-item"
-            :class="{ active: activeCategory === category.id }"
-            @click="changeCategory(category.id)"
+            :class="{ active: activeCategoryId === category.id }"
+            @click="changeCategory(category)"
           >
           >
-            {{ category.name }}
+            <label class="category-item__name">{{ category.name }}</label>
             <span class="count">{{
             <span class="count">{{
-              $t('questionPage["questionCategoryCountTxt"]', { count: category.count })
+              $t('questionPage["questionCategoryCountTxt"]', { count: category.lists.length })
             }}</span>
             }}</span>
           </div>
           </div>
         </div>
         </div>
@@ -167,22 +165,32 @@
     <div class="question-main__container question-main-normal width-1200">
     <div class="question-main__container question-main-normal width-1200">
       <!-- 左侧分类导航 -->
       <!-- 左侧分类导航 -->
       <div class="question-sidebar">
       <div class="question-sidebar">
-        <div
-          v-for="category in categoryList"
-          :key="category.id"
-          class="category-item"
-          :class="{ active: activeCategory === category.id }"
-          @click="changeCategory(category.id)"
-        >
-          {{ category.name }}
-          <span class="count">{{
-            $t('questionPage["questionCategoryCountTxt"]', { count: category.count })
-          }}</span>
-        </div>
+        <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>
 
 
       <!-- 右侧内容 -->
       <!-- 右侧内容 -->
       <div class="question-content">
       <div class="question-content">
+        <!-- 问题列表 -->
         <template v-if="!currentQuestion">
         <template v-if="!currentQuestion">
           <!-- 搜索结果列表 -->
           <!-- 搜索结果列表 -->
           <div v-if="searchResults.length > 0" class="search-results">
           <div v-if="searchResults.length > 0" class="search-results">
@@ -199,25 +207,27 @@
 
 
           <!-- 问题列表 -->
           <!-- 问题列表 -->
           <div v-else class="question-list">
           <div v-else class="question-list">
-            <h2>{{ getCategoryName(activeCategory) }}</h2>
+            <h2>{{ activeCategoryInfo ? activeCategoryInfo.name : '' }}</h2>
 
 
-            <div
-              v-for="question in questionList"
-              :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"]') }}
+            <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>
+                <div class="question-preview" v-html="question.content"></div>
               </div>
               </div>
-              <div class="question-preview">{{ question.preview }}</div>
-            </div>
+            </template>
           </div>
           </div>
         </template>
         </template>
 
 
@@ -225,16 +235,6 @@
         <div v-else class="question-detail">
         <div v-else class="question-detail">
           <div class="detail-header">
           <div class="detail-header">
             <h2>{{ currentQuestion.title }}</h2>
             <h2>{{ currentQuestion.title }}</h2>
-            <div class="meta">
-              <span>{{
-                $t('system["updateTimeTxt"]', {
-                  updateTime: formatDate(currentQuestion.updateTime, locale),
-                })
-              }}</span>
-              <span>{{
-                $t('questionPage["questionReadCount"]', { count: currentQuestion.views })
-              }}</span>
-            </div>
           </div>
           </div>
 
 
           <div class="detail-content" v-html="currentQuestion.content"></div>
           <div class="detail-content" v-html="currentQuestion.content"></div>
@@ -263,6 +263,10 @@
             </div> -->
             </div> -->
           </div>
           </div>
         </div>
         </div>
+
+        <div class="loading-box" v-show="isLoading">
+          <a-spin :spinning="isLoading" size="large" />
+        </div>
       </div>
       </div>
     </div>
     </div>
 
 
@@ -285,7 +289,7 @@
           </svg>
           </svg>
           <div>
           <div>
             <strong>{{ $t('questionPage["contactForPhoneTitle"]') }}</strong>
             <strong>{{ $t('questionPage["contactForPhoneTitle"]') }}</strong>
-            <p>400-888-9999</p>
+            <p>+852-30623063</p>
             <p>{{ $t('questionPage["contactForPhoneWorkTime"]') }}</p>
             <p>{{ $t('questionPage["contactForPhoneWorkTime"]') }}</p>
           </div>
           </div>
         </div>
         </div>
@@ -304,7 +308,7 @@
           </svg>
           </svg>
           <div>
           <div>
             <strong>{{ $t('questionPage["contactForEmailTitle"]') }}</strong>
             <strong>{{ $t('questionPage["contactForEmailTitle"]') }}</strong>
-            <p>support@example.com</p>
+            <p>vavabuy@163.com</p>
             <p>{{ $t('questionPage["contactForEmailWorkTime"]') }}</p>
             <p>{{ $t('questionPage["contactForEmailWorkTime"]') }}</p>
           </div>
           </div>
         </div>
         </div>
@@ -315,8 +319,8 @@
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
-import { storeToRefs } from 'pinia'
 import { ref } from 'vue'
 import { ref } from 'vue'
+import { storeToRefs } from 'pinia'
 import useSystemStore from '@/stores/useSystemStore'
 import useSystemStore from '@/stores/useSystemStore'
 import useData from './hooks/useData'
 import useData from './hooks/useData'
 
 
@@ -326,13 +330,12 @@ const { locale } = storeToRefs(systemStore)
 
 
 const {
 const {
   categoryList,
   categoryList,
-  activeCategory,
-  questionList,
+  isLoading,
+  activeCategoryId,
+  activeCategoryInfo,
   currentQuestion,
   currentQuestion,
-  questions,
   searchQuery,
   searchQuery,
   searchResults,
   searchResults,
-  getCategoryName,
   changeCategory,
   changeCategory,
   showQuestionDetail,
   showQuestionDetail,
   searchQuestions,
   searchQuestions,
@@ -367,6 +370,13 @@ const formatDate = (dateString: string, lang: string) => {
   margin: 0 auto 3.125rem;
   margin: 0 auto 3.125rem;
 }
 }
 
 
+.loading-box {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
 .width-1200 {
 .width-1200 {
   padding: 0 15px 60px;
   padding: 0 15px 60px;
   padding: 0 0.9375rem 3.75rem;
   padding: 0 0.9375rem 3.75rem;
@@ -439,13 +449,14 @@ const formatDate = (dateString: string, lang: string) => {
   gap: 1.875rem;
   gap: 1.875rem;
 
 
   .question-sidebar {
   .question-sidebar {
-    width: 250px;
-    width: 15.625rem;
-    width: 250px;
-    width: 15.625rem;
+    min-width: 250px;
+    min-width: 15.625rem;
+    max-width: 300px;
+    max-width: 18.75rem;
     flex-shrink: 0;
     flex-shrink: 0;
 
 
     .category-item {
     .category-item {
+      flex: 1;
       padding: 12px 15px;
       padding: 12px 15px;
       padding: 0.75rem 0.9375rem;
       padding: 0.75rem 0.9375rem;
       border-left: 4px solid transparent;
       border-left: 4px solid transparent;
@@ -466,10 +477,21 @@ const formatDate = (dateString: string, lang: string) => {
     .category-item.active {
     .category-item.active {
       border-left-color: var(--color-active);
       border-left-color: var(--color-active);
       background: #e3f2fd;
       background: #e3f2fd;
-      font-weight: bold;
+      // font-weight: bold;
+    }
+
+    .category-item__name {
+      flex: 1;
+      padding-right: 12px;
+      padding-right: 0.75rem;
+      /*  white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis; */
     }
     }
 
 
     .count {
     .count {
+      min-width: 64px;
+      min-width: 4rem;
       color: #757575;
       color: #757575;
       font-size: 0.9rem;
       font-size: 0.9rem;
     }
     }
@@ -533,8 +555,13 @@ const formatDate = (dateString: string, lang: string) => {
   }
   }
 
 
   .question-preview {
   .question-preview {
+    max-height: 24px;
+    max-height: 1.5rem;
     color: #666;
     color: #666;
     font-size: 0.9375rem;
     font-size: 0.9375rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
   }
   }
 
 
   .question-tag {
   .question-tag {
@@ -570,16 +597,6 @@ const formatDate = (dateString: string, lang: string) => {
         margin-bottom: 0.625rem;
         margin-bottom: 0.625rem;
         text-align: center;
         text-align: center;
       }
       }
-
-      .meta {
-        display: flex;
-        justify-content: center;
-        gap: 15px;
-        gap: 0.9375rem;
-        color: #757575;
-        font-size: 15px;
-        font-size: 0.9375rem;
-      }
     }
     }
 
 
     .detail-footer {
     .detail-footer {
@@ -640,9 +657,16 @@ const formatDate = (dateString: string, lang: string) => {
   }
   }
 }
 }
 
 
+.question-main-normal {
+  position: relative;
+  min-height: 370px;
+}
+
 .question-main-768 {
 .question-main-768 {
+  position: relative;
   display: none;
   display: none;
   padding-bottom: 0;
   padding-bottom: 0;
+  min-height: 320px;
 }
 }
 
 
 .question-main__container-768 {
 .question-main__container-768 {
@@ -667,6 +691,7 @@ const formatDate = (dateString: string, lang: string) => {
       display: flex;
       display: flex;
       justify-content: space-between;
       justify-content: space-between;
       align-items: center;
       align-items: center;
+      align-items: flex-start;
       margin-bottom: 8px;
       margin-bottom: 8px;
       margin-bottom: 0.5rem;
       margin-bottom: 0.5rem;
       color: var(--color-active);
       color: var(--color-active);
@@ -675,6 +700,8 @@ const formatDate = (dateString: string, lang: string) => {
     }
     }
 
 
     .category-menu-btn {
     .category-menu-btn {
+      min-width: 60px;
+      min-width: 3.75rem;
       display: flex;
       display: flex;
       align-items: center;
       align-items: center;
       color: #333;
       color: #333;
@@ -798,6 +825,11 @@ const formatDate = (dateString: string, lang: string) => {
 </style>
 </style>
 
 
 <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 h3,
 .detail-content h4 {
 .detail-content h4 {
@@ -832,6 +864,11 @@ const formatDate = (dateString: string, lang: string) => {
   margin: 0.5rem 0;
   margin: 0.5rem 0;
 }
 }
 
 
+.question-content img,
+.detail-content img {
+  width: 100%;
+}
+
 .payment-method {
 .payment-method {
   margin: 20px 0;
   margin: 20px 0;
   margin: 1.25rem 0;
   margin: 1.25rem 0;

+ 3 - 6
src/views/Home/modules/Banners/index.vue

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

+ 25 - 9
src/views/Home/modules/WrapFive/index.vue

@@ -14,8 +14,13 @@
             </div>
             </div>
             <div class="swiper-slide__card-body">
             <div class="swiper-slide__card-body">
               <div class="swiper-slide__card-avatar">
               <div class="swiper-slide__card-avatar">
-                <img src="@/public/images/avatar01.png" alt="" />
-                <img src="@/public/images/avatar02.png" alt="" />
+                <img src="@/public/images/avatar_1.jpg" alt="" v-if="i == 0" />
+                <img src="@/public/images/avatar_3.jpg" alt="" v-if="i == 1" />
+                <img src="@/public/images/avatar_4.jpg" alt="" v-if="i == 2" />
+                <img src="../../../../public/images/logo_icon.png" />
+                <!-- <img src="../../../../public/images/icons/xinxilan_logo_icon.png" v-if="i == 0" />
+                <img src="../../../../public/images/icons/aodaliya_logo_icon.png" v-if="i == 1" />
+                <img src="../../../../public/images/icons/yidali_logo_icon.png" v-if="i == 2" /> -->
               </div>
               </div>
               <div>
               <div>
                 <div class="swiper-slide__card-name">{{ $t(`system["${item.name}"]`) }}</div>
                 <div class="swiper-slide__card-name">{{ $t(`system["${item.name}"]`) }}</div>
@@ -52,21 +57,22 @@ onMounted(() => {
 
 
 const dataList = [
 const dataList = [
   {
   {
-    name: '囧二',
+    name: 'Lawrence',
     address: '新西兰',
     address: '新西兰',
-    avatar: [],
+    avatar: ['../../../../public/images/icons/xinxilan_logo_icon.png'],
     content: '如果你想买其他东西,没有比 Vava buy 更好的了',
     content: '如果你想买其他东西,没有比 Vava buy 更好的了',
+    locale: '',
   },
   },
   {
   {
-    name: '囧二',
+    name: 'John',
     address: '澳大利亚',
     address: '澳大利亚',
-    avatar: [],
-    content: '如果你想买其他东西,没有比 Vava buy 更好的了',
+    avatar: ['../../../../public/images/icons/aodaliya_logo_icon.png'],
+    content: '太棒了!一切都很顺利,接收订单并快速发货的速度让人印象深刻',
   },
   },
   {
   {
-    name: '囧二',
+    name: 'Anna',
     address: '意大利',
     address: '意大利',
-    avatar: [],
+    avatar: ['../../../../public/images/icons/yidali_logo_icon.png'],
     content: '非常易于使用,包装很精美,会收获很多惊喜',
     content: '非常易于使用,包装很精美,会收获很多惊喜',
   },
   },
 ]
 ]
@@ -172,10 +178,20 @@ const dataList = [
     }
     }
 
 
     .swiper-slide__card-avatar img {
     .swiper-slide__card-avatar img {
+      width: 56px;
+      width: 3.5rem;
+      height: 56px;
+      height: 3.5rem;
+      border-radius: 50%;
       position: relative;
       position: relative;
       z-index: 1;
       z-index: 1;
     }
     }
 
 
+    .swiper-slide__card-avatar img:nth-child(1) {
+      box-sizing: content-box;
+      border: 3px solid #fff;
+    }
+
     .swiper-slide__card-avatar img:nth-child(2) {
     .swiper-slide__card-avatar img:nth-child(2) {
       position: absolute;
       position: absolute;
       left: 30px;
       left: 30px;

+ 20 - 2
src/views/Home/modules/WrapTwo/index.vue

@@ -8,7 +8,12 @@
       </div>
       </div>
       <div class="wrap__two-content">
       <div class="wrap__two-content">
         <div class="wrap__two-steps">
         <div class="wrap__two-steps">
-          <div v-for="(step, i) in stepList" :key="i" class="wrap__two-step-item">
+          <div
+            v-for="(step, i) in stepList"
+            :key="i"
+            class="wrap__two-step-item"
+            :class="[locale == 'en' ? 'wrap__two-step-item__en' : '']"
+          >
             <img
             <img
               v-if="i == 0"
               v-if="i == 0"
               src="@/public/images/icons/address_icon.png"
               src="@/public/images/icons/address_icon.png"
@@ -43,12 +48,17 @@
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { reactive } from 'vue'
 import { reactive } from 'vue'
+import { storeToRefs } from 'pinia'
+import useSystemStore from '@/stores/useSystemStore'
 
 
 interface IStep {
 interface IStep {
   name: string
   name: string
   desc: string
   desc: string
 }
 }
 
 
+const systemStore = useSystemStore()
+const { locale } = storeToRefs(systemStore)
+
 const stepList = reactive<IStep[]>([
 const stepList = reactive<IStep[]>([
   {
   {
     name: '注册账户',
     name: '注册账户',
@@ -117,11 +127,19 @@ const goRegister = function () {
     flex-direction: column;
     flex-direction: column;
     align-items: center;
     align-items: center;
     padding: 0 15px;
     padding: 0 15px;
-    padding: 0 1.875rem;
+    padding: 0 1rem;
     color: #fff;
     color: #fff;
     text-align: center;
     text-align: center;
   }
   }
 
 
+  .wrap__two-step-item__en {
+    flex: 3;
+  }
+
+  .wrap__two-step-item__en:nth-child(2) {
+    flex: 4;
+  }
+
   .wrap__two-step-icon {
   .wrap__two-step-icon {
     height: 58px;
     height: 58px;
     height: 3.625rem;
     height: 3.625rem;

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

@@ -42,6 +42,8 @@ import Footer from './modules/Footer/index.vue'
 
 
   .layout__main {
   .layout__main {
     flex: 1;
     flex: 1;
+    display: flex;
+    flex-direction: column;
     width: 100%;
     width: 100%;
     height: auto;
     height: auto;
   }
   }

+ 0 - 102
src/views/Layout/modules/Footer/hooks/useLink.ts

@@ -1,102 +0,0 @@
-import { reactive, ref, watch } from 'vue'
-import { useRoute, useRouter } from 'vue-router'
-
-
-export interface ILink {
-  text: string;
-  path?: string;
-  id?: string | number;
-  url?: string;
-}
-
-
-function useLink() {
-
-
-  const linkList1 = reactive<ILink[]>([
-    { path: '/article/article_details', id: '123', text: 'VAVA BUY 是如何工作的' },
-    { path: '/article/article_details', id: '456', text: '为什么选择 VAVA BUY' },
-    { path: '/article/article_details', id: '3', text: '您所在国家/地区的运费和价格' },
-    { path: '/article/article_details', id: '4', text: '报名' },
-    { path: '/article/article_details', id: '5', text: '汇率' },
-    { path: '/article/article_details', id: '6', text: '不能寄送的物品' },
-    { path: '/faq', id: '', text: '常见问题' },
-    { path: '/article/article_details', id: '8', text: '日志' },
-    { path: '/article/article_details', id: '9', text: '您的隐私权利' },
-  ])
-
-  const linkList2 = reactive<ILink[]>([
-    { path: '/article/article_details', id: '10', text: '关于 VAVA BUY' },
-    { path: '/article/article_details', id: '11', text: 'VAVA BUY 评论' },
-    { path: '/article/article_details', id: '12', text: '新闻与动态' },
-    { path: '/article/article_details', id: '13', text: 'VAVA BUY.com 的招聘信息' },
-    { path: '/article/article_details', id: 'abcdefg', text: '与 VAVA BUY 合作' },
-    { path: '/article/article_details', id: '15', text: '联系我们' },
-    { path: '/article/article_details', id: '16', text: '网站地图' },
-    { path: '/article/article_details', id: '17', text: '奖学金' },
-    { path: '/article/article_details', id: '18', text: '顶级商店' },
-  ])
-
-  const linkList3 = reactive<ILink[]>([
-    { path: '/article/article_details', id: '19', text: 'Facebook' },
-    { path: '/article/article_details', id: '20', text: '社交平台' },
-    { path: '/article/article_details', id: '21', text: 'X(原 Twitter)' },
-    { url: 'https://www.baidu.com', text: '获取 VAVA BUY 应用程序' },
-  ])
-
-
-  const route = useRoute()
-  const router = useRouter()
-  const currentPath = ref<string>('')
-  const currentId = ref<string | number | null>(null)
-
-
-  // 获取最新路由信息(路径&id)
-  watch(
-    () => route.fullPath,
-    (newVal, oldVal) => {
-      console.log('route', route)
-      currentPath.value = route.path
-      currentId.value = !!route.query.id ? (route.query.id as (string | number)) : null
-    },
-    { immediate: true }
-  )
-
-
-  // 跳转回调
-  const handleTextLinkClick = function (linkInfo: ILink, id?: string | number) {
-
-    if (!linkInfo || (!linkInfo.path && !linkInfo.url)) return
-
-    if (linkInfo.url) {
-      window.open(linkInfo.url, '_blank')
-    }
-    else if (linkInfo.id) {
-      router.push({
-        path: linkInfo.path,
-        query: {
-          id: linkInfo.id ? linkInfo.id : ''
-        }
-      })
-    }
-    else {
-      router.push({ path: linkInfo.path })
-    }
-  }
-
-  return {
-    linkList1,
-    linkList2,
-    linkList3,
-    route,
-    router,
-    currentPath,
-    currentId,
-    handleTextLinkClick
-  }
-}
-
-
-
-
-export default useLink

+ 112 - 82
src/views/Layout/modules/Footer/index.vue

@@ -6,69 +6,53 @@
           <img src="@/public/images/logo.png" class="footer-friendlink__logo" />
           <img src="@/public/images/logo.png" class="footer-friendlink__logo" />
           <div class="footer-friendlink__baseinfo">
           <div class="footer-friendlink__baseinfo">
             <div
             <div
-              v-for="(item, i) in baseInfoList"
-              :key="i"
               class="footer-friendlink__baseinfo-item"
               class="footer-friendlink__baseinfo-item"
+              style="font-size: 18px; font-size: 1.125rem; margin-bottom: 10px"
             >
             >
-              <span class="footer-friendlink__baseinfo-label">{{ item.label }}</span>
-              <span class="footer-friendlink__baseinfo-content">{{ item.content }}</span>
+              <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">296635202@qq.com </span>
+            </div>
+            <div class="footer-friendlink__baseinfo-item">
+              <span class="footer-friendlink__baseinfo-label"
+                >{{ $t('system.addressLabel') }}:
+              </span>
+              <span class="footer-friendlink__baseinfo-content"
+                >{{ $t('system.companyAddress') }}
+              </span>
+            </div>
+            <div class="footer-friendlink__baseinfo-item">
+              <span class="footer-friendlink__baseinfo-label"
+                >{{ $t('system.hongkongAddressLabel') }}:
+              </span>
+              <span class="footer-friendlink__baseinfo-content"
+                >{{ $t('system.hongkongCompanyAddress') }}
+              </span>
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
-        <div class="footer-friendlink__box">
-          <div
-            v-for="(link, i) in linkList1"
-            :key="i"
-            class="footer-friendlink__item"
-            @click="handleTextLinkClick(link, link.id ? link.id : '')"
-          >
-            <span
-              class="text_hover_underline"
-              :class="[
-                (currentId && currentId == link.id && currentPath == link.path) ||
-                (!currentId && currentPath == link.path)
-                  ? 'text_hover_underline--active'
-                  : '',
-              ]"
-              >{{ $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"
-            @click="handleTextLinkClick(link, link.id ? link.id : '')"
-          >
-            <span
-              class="text_hover_underline"
-              :class="[
-                (currentId && currentId == link.id && currentPath == link.path) ||
-                (!currentId && currentPath == link.path)
-                  ? 'text_hover_underline--active'
-                  : '',
-              ]"
-              >{{ $t(`textLink["${link.text}"]`) }}</span
-            >
-          </div>
-        </div>
-        <div class="footer-friendlink__box">
+        <div class="footer-friendlink__box" v-for="(itemInfo, i) in [...linkList]" :key="i">
+          <div class="footer-friendlink__box-head">{{ itemInfo.name }}</div>
           <div
           <div
-            v-for="(link, i) in linkList3"
-            :key="i"
+            v-for="(info, index) in itemInfo.lists"
+            :key="index"
             class="footer-friendlink__item"
             class="footer-friendlink__item"
-            @click="handleTextLinkClick(link, link.id ? link.id : '')"
+            @click="updateActiveInfo(itemInfo, info, goArticleDetailPage)"
           >
           >
             <span
             <span
               class="text_hover_underline"
               class="text_hover_underline"
               :class="[
               :class="[
-                (currentId && currentId == link.id && currentPath == link.path) ||
-                (!currentId && currentPath == link.path)
-                  ? 'text_hover_underline--active'
-                  : '',
+                activeLinkId && activeLinkId == info.id ? 'text_hover_underline--active' : '',
               ]"
               ]"
-              >{{ $t(`textLink["${link.text}"]`) }}</span
+              >{{ info.title }}</span
             >
             >
           </div>
           </div>
         </div>
         </div>
@@ -95,27 +79,55 @@
 
 
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import useLink from './hooks/useLink'
-
-const {
-  linkList1,
-  linkList2,
-  linkList3,
-  route,
-  router,
-  currentPath,
-  currentId,
-  handleTextLinkClick,
-} = useLink()
-
-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' },
-]
+import { watch, computed } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { storeToRefs } from 'pinia'
+import useSystemStore from '@/stores/useSystemStore'
+import useFooterStore from '@/stores/useFooterStore'
+
+// system store
+const systemStore = useSystemStore()
+const { locale, screenWidth } = storeToRefs(systemStore)
+// footer store
+const footerStore = useFooterStore()
+const { linkList, activeLinkId, activeLinkInfo, activeLinkCateInfo } = storeToRefs(footerStore)
+const { fetchHelpDatas, updateActiveInfo } = footerStore
+// 路由对象
+const route = useRoute()
+const router = useRouter()
+
+const linkListCount = computed<number>(() => {
+  let count = 4
+  if (screenWidth.value >= 1200) {
+    count = 4
+  } else if (screenWidth.value > 824) {
+    count = 3
+  } else if (screenWidth.value > 524) {
+    count = 2
+  } else {
+    count = 1
+  }
+  return count
+})
+
+// 获取数据列表
+fetchHelpDatas(locale.value)
+
+// 跳转文章详情页
+const goArticleDetailPage = function () {
+  if (!activeLinkInfo.value && !activeLinkId.value) return
+  router.push({
+    path: '/article/article_details',
+    query: {
+      id: activeLinkId.value
+        ? activeLinkId.value
+        : activeLinkInfo.value
+        ? activeLinkInfo.value.id
+        : '',
+      cateId: activeLinkCateInfo.value ? activeLinkCateInfo.value.id : '',
+    },
+  })
+}
 </script>
 </script>
 
 
 
 
@@ -136,7 +148,9 @@ const baseInfoList = [
   }
   }
 
 
   .footer-friendlink__box {
   .footer-friendlink__box {
-    width: auto;
+    // width: 25%;
+    max-width: 360px;
+    max-width: 22.5rem;
     padding: 6px 10px 10px;
     padding: 6px 10px 10px;
     padding: 0.375rem 0.625rem 0.625rem;
     padding: 0.375rem 0.625rem 0.625rem;
   }
   }
@@ -148,13 +162,27 @@ const baseInfoList = [
   .footer-friendlink__box .footer-friendlink__logo {
   .footer-friendlink__box .footer-friendlink__logo {
     width: 150px;
     width: 150px;
     width: 9.375rem;
     width: 9.375rem;
-    margin-bottom: 36px;
-    margin-bottom: 2.25rem;
+    margin-bottom: 20px;
+    margin-bottom: 1.25rem;
+  }
+
+  .footer-friendlink__box .footer-friendlink__box-head {
+    margin-bottom: 24px;
+    margin-bottom: 1.5rem;
+    font-size: 18px;
+    font-size: 1.125rem;
+    font-weight: 500;
+    color: #222;
+  }
+
+  .footer-friendlink__baseinfo {
+    max-width: 360px;
+    max-width: 22.5rem;
   }
   }
 
 
   .footer-friendlink__baseinfo-item {
   .footer-friendlink__baseinfo-item {
-    margin-bottom: 4px;
-    margin-bottom: 0.25rem;
+    margin-bottom: 6px;
+    margin-bottom: 0.375rem;
     font-family: Poppins;
     font-family: Poppins;
     font-size: 14px;
     font-size: 14px;
     font-size: 0.875rem;
     font-size: 0.875rem;
@@ -162,10 +190,12 @@ const baseInfoList = [
     color: #3d3d3d;
     color: #3d3d3d;
   }
   }
 
 
-  .footer-friendlink__baseinfo-item:last-child {
-    margin-bottom: 0;
-    margin-top: 20px;
-    margin-top: 1.25rem;
+  .footer-friendlink__baseinfo-label {
+    color: #000;
+  }
+
+  .footer-friendlink__baseinfo-content {
+    color: #555;
   }
   }
 
 
   .footer-friendlink__item {
   .footer-friendlink__item {
@@ -206,4 +236,4 @@ const baseInfoList = [
 @import url(./css/footer@1024.less);
 @import url(./css/footer@1024.less);
 @import url(./css/footer@768.less);
 @import url(./css/footer@768.less);
 @import url(./css/footer@512.less);
 @import url(./css/footer@512.less);
-</style>
+</style>./hooks/useData

+ 7 - 6
src/views/Layout/modules/Header/hooks/useLinkList.ts

@@ -6,8 +6,8 @@ import { useRoute, useRouter } from 'vue-router'
 
 
 export interface ITextLink {
 export interface ITextLink {
   text: string;
   text: string;
-  path?: string;
   id?: string | number;
   id?: string | number;
+  path?: string;
   url?: string;
   url?: string;
 }
 }
 
 
@@ -18,7 +18,7 @@ function useLinkList() {
     {
     {
       text: '与 VAVA BUY 合作',
       text: '与 VAVA BUY 合作',
       path: '/article/article_details',
       path: '/article/article_details',
-      id: 'abcdefg',
+      id: 'connect',
     },
     },
   ])
   ])
 
 
@@ -26,17 +26,17 @@ function useLinkList() {
     {
     {
       text: '如何运作',
       text: '如何运作',
       path: '/article/article_details',
       path: '/article/article_details',
-      id: '123',
+      id: 'works',
     },
     },
     {
     {
       text: '为什么选择 VAVA BUY',
       text: '为什么选择 VAVA BUY',
       path: '/article/article_details',
       path: '/article/article_details',
-      id: '456',
+      id: 'about',
     },
     },
     {
     {
       text: '优势',
       text: '优势',
       path: '/article/article_details',
       path: '/article/article_details',
-      id: '789',
+      id: 'superiority',
     },
     },
     {
     {
       text: '在 VAVA BUY 购物',
       text: '在 VAVA BUY 购物',
@@ -73,7 +73,8 @@ function useLinkList() {
       router.push({
       router.push({
         path: linkInfo.path,
         path: linkInfo.path,
         query: {
         query: {
-          id: linkInfo.id ? linkInfo.id : ''
+          id: linkInfo.id ? linkInfo.id : '',
+          type: 'header'
         }
         }
       })
       })
     }
     }

+ 8 - 0
src/views/Layout/modules/Header/index.vue

@@ -160,6 +160,14 @@ const { textLinkList, textLinkList2, route, router, currentPath, currentId, hand
 const onLocaleChange = function (info: ILocale) {
 const onLocaleChange = function (info: ILocale) {
   systemStore.toggleLocale(info)
   systemStore.toggleLocale(info)
   i18n.locale.value = info.value
   i18n.locale.value = info.value
+  const historyLength = window.history.length
+  if (historyLength > 1) {
+    window.history.go(-historyLength + 2)
+  }
+  router.replace('/')
+  setTimeout(() => {
+    window.location.reload()
+  })
 }
 }
 
 
 // 跳转首页回调
 // 跳转首页回调

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

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

+ 4 - 0
vite.config.ts

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