Просмотр исходного кода

新增搜索功能至Header组件,优化首页搜索逻辑,调整视频页面搜索处理,提升用户体验

wuyi 3 месяцев назад
Родитель
Сommit
1d77157970
3 измененных файлов с 135 добавлено и 91 удалено
  1. 81 9
      src/components/layout/Header.vue
  2. 12 1
      src/components/layout/MainLayout.vue
  3. 42 81
      src/views/Home.vue

+ 81 - 9
src/components/layout/Header.vue

@@ -1,26 +1,51 @@
 <script setup lang="ts">
-import { computed } from "vue";
-import { useRouter } from "vue-router";
+import { computed, ref } from "vue";
+import { useRouter, useRoute } from "vue-router";
 import { useUserStore } from "@/store/user";
 import { vipLevelToText, VipLevel } from "@/types/vip";
 
 const userStore = useUserStore();
 const router = useRouter();
+const route = useRoute();
 const isLoggedIn = computed(() => !!userStore.token);
 const isVip = computed(() => {
   const level = userStore.userInfo?.vipLevel;
   return level && level !== VipLevel.GUEST && level !== VipLevel.FREE;
 });
 
+// 判断是否在首页
+const isHomePage = computed(() => route.path === "/");
+
 defineProps<{
   title?: string;
 }>();
 
-const emit = defineEmits(["show-login", "switch-tab"]);
+const emit = defineEmits(["show-login", "switch-tab", "search"]);
 
 const goToHome = () => {
   router.push("/");
 };
+
+// 搜索相关
+const searchKeyword = ref("");
+
+const handleSearch = () => {
+  if (searchKeyword.value.trim()) {
+    emit("search", searchKeyword.value.trim());
+  }
+};
+
+const clearSearch = () => {
+  searchKeyword.value = "";
+  emit("search", "");
+};
+
+// 截断用户名显示
+const truncateUsername = (name: string, maxLength = 6) => {
+  if (!name) return "账号";
+  if (name.length <= maxLength) return name;
+  return name.substring(0, maxLength) + "...";
+};
 </script>
 
 <template>
@@ -37,19 +62,66 @@ const goToHome = () => {
         >
           {{ title || "探索" }}
         </h1>
-        <div class="flex items-center gap-4 text-sm">
-          <template v-if="isLoggedIn">
+
+        <!-- 搜索栏 - 只在首页显示 -->
+        <div v-if="isHomePage" class="flex-1 max-w-xs mx-2 sm:mx-4">
+          <div class="relative">
+            <input
+              v-model="searchKeyword"
+              type="text"
+              placeholder="搜索视频..."
+              class="w-full px-3 py-1.5 pr-16 rounded-lg bg-white/5 border border-white/10 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-emerald-500/50 focus:border-emerald-500/50 transition text-xs sm:text-sm"
+              @keyup.enter="handleSearch"
+            />
+            <!-- 清除按钮 -->
             <button
-              class="text-white/70 hover:text-brand transition"
-              @click="$emit('switch-tab', 'purchased')"
+              v-if="searchKeyword"
+              @click="clearSearch"
+              class="absolute right-8 top-1/2 transform -translate-y-1/2 text-white/50 hover:text-white/80 transition"
+            >
+              <svg
+                class="w-3 h-3 sm:w-4 sm:h-4"
+                fill="none"
+                stroke="currentColor"
+                viewBox="0 0 24 24"
+              >
+                <path
+                  stroke-linecap="round"
+                  stroke-linejoin="round"
+                  stroke-width="2"
+                  d="M6 18L18 6M6 6l12 12"
+                />
+              </svg>
+            </button>
+            <!-- 搜索按钮 -->
+            <button
+              @click="handleSearch"
+              class="absolute right-2 top-1/2 transform -translate-y-1/2 text-white/50 hover:text-white/80 transition"
             >
-              已购
+              <svg
+                class="w-3 h-3 sm:w-4 sm:h-4"
+                fill="none"
+                stroke="currentColor"
+                viewBox="0 0 24 24"
+              >
+                <path
+                  stroke-linecap="round"
+                  stroke-linejoin="round"
+                  stroke-width="2"
+                  d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
+                />
+              </svg>
             </button>
+          </div>
+        </div>
+
+        <div class="flex items-center gap-4 text-sm">
+          <template v-if="isLoggedIn">
             <button
               class="text-white/70 hover:text-brand transition"
               @click="$emit('switch-tab', 'account')"
             >
-              {{ userStore.userInfo?.name || "账号" }}
+              {{ truncateUsername(userStore.userInfo?.name || "") }}
               <span
                 v-if="isVip"
                 class="ml-2 px-2 py-0.5 rounded bg-yellow-400/90 text-xs text-yellow-900 font-semibold align-middle"

+ 12 - 1
src/components/layout/MainLayout.vue

@@ -2,7 +2,11 @@
   <div
     class="min-h-screen bg-surface text-slate-100 selection:bg-brand/20 selection:text-brand"
   >
-    <Header @show-login="showLoginDialog = true" @switch-tab="switchTab" />
+    <Header
+      @show-login="showLoginDialog = true"
+      @switch-tab="switchTab"
+      @search="handleSearch"
+    />
 
     <main class="max-w-screen-sm mx-auto w-full px-4 pb-28 pt-3">
       <router-view @show-login="showLoginDialog = true" />
@@ -107,6 +111,13 @@ async function handleLoginSuccess() {
   }
 }
 
+function handleSearch(keyword: string) {
+  // 通过事件总线传递搜索事件
+  window.dispatchEvent(
+    new CustomEvent("header-search", { detail: { keyword } })
+  );
+}
+
 onMounted(async () => {
   window.addEventListener("scroll", handleScroll, { passive: true });
 

+ 42 - 81
src/views/Home.vue

@@ -1,59 +1,5 @@
 <template>
   <section class="space-y-6">
-    <!-- 搜索框 - 固定在Header下方 -->
-    <div
-      class="sticky top-16 z-30 bg-surface/90 backdrop-blur supports-[backdrop-filter]:bg-surface/70 border-b border-white/5 pb-3"
-    >
-      <div class="flex gap-3">
-        <div class="relative flex-1">
-          <input
-            v-model="searchKeyword"
-            type="text"
-            placeholder="搜索视频..."
-            class="w-full px-4 py-3 pr-12 rounded-xl bg-white/5 border border-white/10 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-emerald-500/50 focus:border-emerald-500/50 transition"
-            @keyup.enter="() => handleSearch()"
-          />
-          <button
-            v-if="searchKeyword"
-            @click="clearSearch"
-            class="absolute right-3 top-1/2 transform -translate-y-1/2 text-white/50 hover:text-white/80 transition"
-          >
-            <svg
-              class="w-5 h-5"
-              fill="none"
-              stroke="currentColor"
-              viewBox="0 0 24 24"
-            >
-              <path
-                stroke-linecap="round"
-                stroke-linejoin="round"
-                stroke-width="2"
-                d="M6 18L18 6M6 6l12 12"
-              />
-            </svg>
-          </button>
-        </div>
-        <button
-          @click="() => handleSearch()"
-          class="px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white hover:border-white/20 transition"
-        >
-          <svg
-            class="w-5 h-5"
-            fill="none"
-            stroke="currentColor"
-            viewBox="0 0 24 24"
-          >
-            <path
-              stroke-linecap="round"
-              stroke-linejoin="round"
-              stroke-width="2"
-              d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
-            />
-          </svg>
-        </button>
-      </div>
-    </div>
-
     <!-- 为你推荐卡片 -->
     <div
       v-if="!isSearchMode"
@@ -199,7 +145,7 @@
           <button
             @click="
               isSearchMode
-                ? handleSearch(currentPage - 1)
+                ? handleSearch(currentSearchKeyword, currentPage - 1)
                 : loadVideosByTag(selectedMenu, currentPage - 1)
             "
             :disabled="currentPage <= 1"
@@ -215,7 +161,7 @@
               v-if="currentPage > 3"
               @click="
                 isSearchMode
-                  ? handleSearch(1)
+                  ? handleSearch(currentSearchKeyword, 1)
                   : loadVideosByTag(selectedMenu, 1)
               "
               class="px-3 py-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition text-sm"
@@ -233,7 +179,7 @@
               v-if="currentPage > 1"
               @click="
                 isSearchMode
-                  ? handleSearch(currentPage - 1)
+                  ? handleSearch(currentSearchKeyword, currentPage - 1)
                   : loadVideosByTag(selectedMenu, currentPage - 1)
               "
               class="px-3 py-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition text-sm"
@@ -253,7 +199,7 @@
               v-if="currentPage < totalPages"
               @click="
                 isSearchMode
-                  ? handleSearch(currentPage + 1)
+                  ? handleSearch(currentSearchKeyword, currentPage + 1)
                   : loadVideosByTag(selectedMenu, currentPage + 1)
               "
               class="px-3 py-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition text-sm"
@@ -289,7 +235,7 @@
               v-if="currentPage < totalPages - 2"
               @click="
                 isSearchMode
-                  ? handleSearch(totalPages)
+                  ? handleSearch(currentSearchKeyword, totalPages)
                   : loadVideosByTag(selectedMenu, totalPages)
               "
               class="px-3 py-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition text-sm"
@@ -302,7 +248,7 @@
           <button
             @click="
               isSearchMode
-                ? handleSearch(currentPage + 1)
+                ? handleSearch(currentSearchKeyword, currentPage + 1)
                 : loadVideosByTag(selectedMenu, currentPage + 1)
             "
             :disabled="currentPage >= totalPages"
@@ -327,7 +273,7 @@
           <button
             @click="
               isSearchMode
-                ? handleSearch(currentPage - 1)
+                ? handleSearch(currentSearchKeyword, currentPage - 1)
                 : loadVideosByTag(selectedMenu, currentPage - 1)
             "
             :disabled="currentPage <= 1"
@@ -343,7 +289,7 @@
               v-if="currentPage > 2"
               @click="
                 isSearchMode
-                  ? handleSearch(1)
+                  ? handleSearch(currentSearchKeyword, 1)
                   : loadVideosByTag(selectedMenu, 1)
               "
               class="px-2 py-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition text-sm"
@@ -361,7 +307,7 @@
               v-if="currentPage > 1"
               @click="
                 isSearchMode
-                  ? handleSearch(currentPage - 1)
+                  ? handleSearch(currentSearchKeyword, currentPage - 1)
                   : loadVideosByTag(selectedMenu, currentPage - 1)
               "
               class="px-2 py-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition text-sm"
@@ -381,7 +327,7 @@
               v-if="currentPage < totalPages"
               @click="
                 isSearchMode
-                  ? handleSearch(currentPage + 1)
+                  ? handleSearch(currentSearchKeyword, currentPage + 1)
                   : loadVideosByTag(selectedMenu, currentPage + 1)
               "
               class="px-2 py-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition text-sm"
@@ -401,7 +347,7 @@
               v-if="currentPage < totalPages - 1"
               @click="
                 isSearchMode
-                  ? handleSearch(totalPages)
+                  ? handleSearch(currentSearchKeyword, totalPages)
                   : loadVideosByTag(selectedMenu, totalPages)
               "
               class="px-2 py-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition text-sm"
@@ -425,7 +371,7 @@
           <button
             @click="
               isSearchMode
-                ? handleSearch(currentPage + 1)
+                ? handleSearch(currentSearchKeyword, currentPage + 1)
                 : loadVideosByTag(selectedMenu, currentPage + 1)
             "
             :disabled="currentPage >= totalPages"
@@ -447,7 +393,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from "vue";
+import { ref, onMounted, onBeforeUnmount } from "vue";
 import { useRouter } from "vue-router";
 import { searchVideoByTags, searchVideoByKeyword } from "@/services/api";
 import { videoMenus as fixedVideoMenus } from "@/data/videoMenus";
@@ -502,8 +448,8 @@ const selectedSort = ref<string>("time");
 const showAllTags = ref(false);
 
 // 搜索相关
-const searchKeyword = ref("");
 const isSearchMode = ref(false);
+const currentSearchKeyword = ref("");
 
 // 格式化时长
 const formatDuration = (duration: string | number): string => {
@@ -564,7 +510,7 @@ const jumpToPageHandler = () => {
   try {
     if (isSearchMode.value) {
       console.log("搜索模式,执行搜索跳转");
-      handleSearch(page);
+      handleSearch(currentSearchKeyword.value, page);
     } else {
       console.log("标签模式,执行标签跳转");
       loadVideosByTag(selectedMenu.value || "", page);
@@ -589,7 +535,7 @@ const selectSort = async (sort: string) => {
   // 重新查询当前显示的内容
   if (isSearchMode.value) {
     // 如果是搜索模式,重新执行搜索
-    await handleSearch(1);
+    await handleSearch(currentSearchKeyword.value, 1);
   } else if (selectedMenu.value) {
     // 如果选择了特定菜单,按菜单查询
     await loadVideosByTag(selectedMenu.value, 1);
@@ -619,17 +565,24 @@ const toggleTags = () => {
 };
 
 // 搜索功能
-const handleSearch = async (page = 1) => {
-  if (!searchKeyword.value.trim()) return;
+const handleSearch = async (keyword: string, page = 1) => {
+  if (!keyword.trim()) {
+    // 如果关键词为空,清除搜索模式
+    isSearchMode.value = false;
+    currentSearchKeyword.value = "";
+    loadVideosByTag("");
+    return;
+  }
 
   isSearchMode.value = true;
+  currentSearchKeyword.value = keyword.trim();
   loading.value = true;
   currentPage.value = page;
 
   try {
     const response = await searchVideoByKeyword(
       device,
-      searchKeyword.value.trim(),
+      keyword.trim(),
       page,
       pageSize.value,
       "long",
@@ -658,14 +611,6 @@ const handleSearch = async (page = 1) => {
   }
 };
 
-// 清除搜索
-const clearSearch = () => {
-  searchKeyword.value = "";
-  isSearchMode.value = false;
-  // 恢复默认显示(加载所有视频)
-  loadVideosByTag("");
-};
-
 // 播放视频
 const playVideo = (video: any) => {
   const vipLevel = userStore.getVipLevel();
@@ -738,8 +683,24 @@ const initializeVideoMenus = async () => {
   await loadVideosByTag("");
 };
 
+// 监听Header搜索事件
+const handleHeaderSearch = (event: CustomEvent) => {
+  const { keyword } = event.detail;
+  handleSearch(keyword);
+};
+
 onMounted(() => {
   initializeVideoMenus();
+  // 监听Header搜索事件
+  window.addEventListener("header-search", handleHeaderSearch as EventListener);
+});
+
+onBeforeUnmount(() => {
+  // 清理事件监听器
+  window.removeEventListener(
+    "header-search",
+    handleHeaderSearch as EventListener
+  );
 });
 </script>