Prechádzať zdrojové kódy

2025年6月3日:01)新增“文章详情”和“常见问题”静态页面;02)部分代码文件内容优化

yankun 2 mesiacov pred
rodič
commit
e2992c667b

+ 1 - 1
.env.development

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

+ 1 - 1
.env.production

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

+ 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;
   }
 }

+ 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)

+ 11 - 1
src/router/index.ts

@@ -16,7 +16,17 @@ const routes: Array<RouteRecordRaw> = [
         path: 'home',
         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")
+      },
     ]
   },
 ]

+ 18 - 3
src/utils/request.ts

@@ -1,10 +1,25 @@
 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,
+  baseURL: BASE_URL,
   timeout: 1000 * 60 * 5,
-})
+})
+
+
+
+// 请求拦截器
+
+
+
+
+// 响应拦截器
+
+
+
+
+
+export default instance

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

@@ -0,0 +1,254 @@
+<template>
+  <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">
+        {{ formatDate(article.publishTime) }}
+      </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>
+  </section>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+// 模拟文章数据
+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 formatDate = (dateString) => {
+  const options = { year: 'numeric', month: 'long', day: 'numeric' }
+  return new Date(dateString).toLocaleDateString(undefined, options)
+}
+</script>
+
+<style scoped lang="less">
+.article-container {
+  max-width: 1200px;
+  max-width: 75rem;
+  margin: 0 auto;
+  padding: 60px 15px;
+  padding: 3.75rem 0.9375rem;
+  font-family: 'Helvetica Neue', Arial, sans-serif;
+  color: #333;
+}
+
+.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 {
+  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) {}

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

@@ -0,0 +1,13 @@
+@media screen and (max-width: 768px) {
+  .question-banner {
+
+    h1 {
+      font-size: 32px;
+      font-size: 2rem;
+    }
+
+    p {
+      font-size: 1rem;
+    }
+  }
+}

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

@@ -0,0 +1,837 @@
+<template>
+  <div class="question-container">
+    <!-- 顶部横幅 -->
+    <div class="question-banner">
+      <h1>常见问题</h1>
+      <p>我们将在这里为您解答所有疑问</p>
+
+      <!-- 搜索框 -->
+      <div class="question__search-box">
+        <input
+          type="text"
+          v-model="searchQuery"
+          placeholder="请输入您的问题关键词..."
+          @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>
+          搜索
+        </button>
+      </div>
+    </div>
+
+    <!-- 主要内容区 -->
+    <div class="question-main__container width-1200">
+      <!-- 左侧导航 -->
+      <div class="question-sidebar">
+        <div
+          v-for="category in categories"
+          :key="category.id"
+          class="category-item"
+          :class="{ active: activeCategory === category.id }"
+          @click="changeCategory(category.id)"
+        >
+          {{ category.name }}
+          <span class="count">{{ category.count }}个问题</span>
+        </div>
+      </div>
+
+      <!-- 右侧内容 -->
+      <div class="question-content">
+        <template v-if="!currentQuestion">
+          <!-- 搜索结果提示 -->
+          <div v-if="searchResults.length > 0" class="search-results">
+            <h3>搜索结果 ({{ 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>{{ getCategoryName(activeCategory) }}</h2>
+
+            <div
+              v-for="question in filteredQuestions"
+              :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">热门</div>
+                <div v-if="question.isNew" class="question-tag new-tag">新</div>
+              </div>
+              <div class="question-preview">{{ question.preview }}</div>
+            </div>
+          </div>
+        </template>
+
+        <!-- 问题详情 -->
+        <div v-else class="question-detail">
+          <div class="detail-header">
+            <h2>{{ currentQuestion.title }}</h2>
+            <div class="meta">
+              <span>更新于 {{ formatDate(currentQuestion.updateTime) }}</span>
+              <span>阅读 {{ currentQuestion.views }} 次</span>
+            </div>
+          </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>
+              返回列表
+            </button>
+
+            <!-- <div class="feedback">
+              <span>这篇回答对您有帮助吗?</span>
+              <button class="feedback-btn yes">有帮助</button>
+              <button class="feedback-btn no">没帮助</button>
+            </div> -->
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 联系客服 -->
+    <div class="contact-section width-1200">
+      <h3>没有找到您需要的答案?</h3>
+      <p>我们的客服团队随时为您服务</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>电话支持</strong>
+            <p>400-888-9999</p>
+            <p>周一至周日 9:00-18:00</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>电子邮件</strong>
+            <p>support@example.com</p>
+            <p>我们会在24小时内回复</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="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"
+            />
+          </svg>
+          <div>
+            <strong>在线客服</strong>
+            <p>点击右下角"在线咨询"</p>
+            <p>工作时间实时沟通</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue'
+
+// 问题分类
+const categories = 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 },
+])
+
+// 模拟问题数据
+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,
+  },
+])
+
+// 当前选中的分类ID
+const activeCategory = ref(1)
+
+// 当前查看的问题详情
+const currentQuestion = ref(null)
+
+// 搜索查询词
+const searchQuery = ref('')
+
+// 搜索结果
+const searchResults = ref([])
+
+// 获取当前分类下的问题
+const filteredQuestions = computed(() => {
+  return questions.value.filter((q) => q.categoryId === activeCategory.value)
+})
+
+// 获取分类名称
+const getCategoryName = (id) => {
+  const category = categories.value.find((c) => c.id === id)
+  return category ? category.name : '所有问题'
+}
+
+// 切换分类
+const changeCategory = (categoryId) => {
+  activeCategory.value = categoryId
+  currentQuestion.value = null
+  searchResults.value = []
+}
+
+// 显示问题详情
+const showQuestionDetail = (question) => {
+  currentQuestion.value = question
+}
+
+// 返回问题列表
+const backToList = () => {
+  currentQuestion.value = null
+}
+
+// 搜索问题
+const searchQuestions = () => {
+  if (!searchQuery.value.trim()) {
+    searchResults.value = []
+    return
+  }
+
+  const query = searchQuery.value.toLowerCase()
+  searchResults.value = questions.value.filter((q) => {
+    return q.title.toLowerCase().includes(query) || q.preview.toLowerCase().includes(query)
+  })
+  currentQuestion.value = null
+}
+
+// 格式化日期
+const formatDate = (dateString) => {
+  const options = { year: 'numeric', month: 'long', day: 'numeric' }
+  return new Date(dateString).toLocaleDateString('zh-CN', options)
+}
+</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;
+}
+
+.width-1200 {
+  padding: 0 15px 60px;
+  padding: 0 0.9375rem 3.75rem;
+}
+
+.question-banner {
+  width: 100%;
+  background: linear-gradient(135deg, #1976d2, var(--color-active));
+  background: var(--color-active);
+  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 {
+    width: 250px;
+    width: 15.625rem;
+    width: 250px;
+    width: 15.625rem;
+    flex-shrink: 0;
+
+    .category-item {
+      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;
+    }
+
+    .count {
+      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: #1976d2;
+    }
+  }
+
+  .question-list {
+    h2 {
+      font-size: 24px;
+      font-size: 1.5rem;
+      margin-bottom: 20px;
+      margin-bottom: 1.25rem;
+      padding-bottom: 10px;
+      padding-bottom: 0.625rem;
+      border-bottom: 1px solid #eee;
+    }
+  }
+
+  .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 {
+    color: #666;
+    font-size: 0.9375rem;
+  }
+
+  .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: 24px;
+        font-size: 1.8rem;
+        margin-bottom: 10px;
+        margin-bottom: 0.625rem;
+      }
+
+      .meta {
+        display: flex;
+        gap: 15px;
+        gap: 0.9375rem;
+        color: #757575;
+        font-size: 15px;
+        font-size: 0.9375rem;
+      }
+    }
+
+    .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;
+      } */
+    }
+  }
+}
+
+.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: #1976d2;
+      margin-top: 3px;
+    }
+
+    strong {
+      display: block;
+      margin-bottom: 5px;
+    }
+
+    p {
+      margin: 3px 0;
+      font-size: 0.9375rem;
+      color: #666;
+    }
+  }
+}
+</style>
+
+<style>
+/* 富文本内容样式 */
+.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;
+}
+
+.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>
 
 

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

@@ -0,0 +1,102 @@
+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

+ 87 - 57
src/views/Layout/modules/Footer/index.vue

@@ -1,40 +1,87 @@
 <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
+              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>
+            </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 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>
-      <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 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>
-      <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">
+          <div
+            v-for="(link, i) in linkList3"
+            :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>
     </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-right">
+      <div class="footer__box width-1200">
+        <span>©1997-2025 VAVA BUY.com All Rights Reserved</span>
+      </div>
     </div>
-  </div>
 
-  <!-- <div class="footer__container footer-footnote">
+    <!-- <div class="footer__container footer-footnote">
     <div class="footer__box width-1200">
       <span>{{
         $t(
@@ -43,10 +90,24 @@
       }}</span>
     </div>
   </div> -->
+  </section>
 </template>
 
 
 <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' },
@@ -55,37 +116,6 @@ const baseInfoList = [
   { 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 应用程序' },
-]
 </script>
 
 

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

@@ -1,35 +1,96 @@
 
-import { reactive } from 'vue'
+import { reactive, ref, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
 
 
 
 export interface ITextLink {
   text: string;
-  link: string;
+  path?: string;
+  id?: string | number;
+  url?: 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: 'abcdefg',
+    },
   ])
 
   const textLinkList2 = reactive<Array<ITextLink>>([
-    { text: '如何运作', link: '' },
-    { text: '为什么选择 VAVA BUY', link: '' },
-    { text: '优势', link: '' },
-    { text: '在 VAVA BUY 购物', link: '' },
+    {
+      text: '如何运作',
+      path: '/article/article_details',
+      id: '123',
+    },
+    {
+      text: '为什么选择 VAVA BUY',
+      path: '/article/article_details',
+      id: '456',
+    },
+    {
+      text: '优势',
+      path: '/article/article_details',
+      id: '789',
+    },
+    {
+      text: '在 VAVA BUY 购物',
+      url: 'https://www.baidu.com'
+    },
   ])
 
+  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) {
+      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 {
     textLinkList,
     textLinkList2,
+    route,
+    router,
+    currentPath,
+    currentId,
     handleTextLinkClick
   }
 }

+ 134 - 66
src/views/Layout/modules/Header/index.vue

@@ -1,83 +1,139 @@
 <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.name }}</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.name }}</span>
                   </div>
                 </a-menu-item>
               </a-menu>
             </template>
           </a-dropdown>
+          <div class="login-btn">
+            <span>{{ $t('system["登录"]') }}</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'
+                    : '',
+                ]"
+                >{{ $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>
 
 
@@ -91,16 +147,27 @@ import useSystemStore from '@/stores/useSystemStore'
 import type { ILocale } from '@/stores/useSystemStore'
 import useLinkList from './hooks/useLinkList'
 
+// 国际化
 const i18n = useI18n()
 const systemStore = useSystemStore()
 const { localeInfo, localeList } = storeToRefs(systemStore)
 
-const { textLinkList, textLinkList2, handleTextLinkClick } = useLinkList()
+// 文本链接
+const { textLinkList, textLinkList2, route, router, currentPath, currentId, handleTextLinkClick } =
+  useLinkList()
 
+// 语言切换回调
 const onLocaleChange = function (info: ILocale) {
   systemStore.toggleLocale(info)
   i18n.locale.value = info.value
 }
+
+// 跳转首页回调
+const goHome = function () {
+  router.push({
+    path: '/',
+  })
+}
 </script>
 
 
@@ -153,6 +220,7 @@ const onLocaleChange = function (info: ILocale) {
   img {
     width: 150px;
     width: 9.375rem;
+    cursor: pointer;
   }
 }