Browse Source

重构应用结构,新增主布局和视频播放布局,优化路由配置,更新环境变量,移除冗余代码,增强搜索功能和视频展示逻辑

wuyi 3 months ago
parent
commit
2bc7f1a4fd

+ 5 - 5
.env

@@ -1,7 +1,7 @@
 VITE_API_URL=http://localhost:3010/api
 
-VIDEO_API_URL=https://qa3-api.69mediatest.com/api
-VIDEO_PLAT_ID:30017
-VIDEO_CHANNEL_ID:30017001
-VIDEO_USER_ID:30005470
-VIDEO_TOKEN:0YzN4gDM4UzNxMSZzAjNjdTN1IGNhNmY4ADZlVWO5UjY1YDOmVjNilTNwMiNwEDO5QTNwYzI3ETZ3I2MxMGZhJmZ0MzY2IDZyUjNlVGOlVzNwIjZmJzI0IjO5UjOzEDI3ETL5ATL1IDMyMCN
+VITE_VIDEO_API_URL=https://qa3-api.69mediatest.com/api
+VITE_VIDEO_PLAT_ID=30017
+VITE_VIDEO_CHANNEL_ID=30017001
+VITE_VIDEO_USER_ID=30005470
+VITE_VIDEO_TOKEN=0YzN4gDM4UzNxMSZzAjNjdTN1IGNhNmY4ADZlVWO5UjY1YDOmVjNilTNwMiNwEDO5QTNwYzI3ETZ3I2MxMGZhJmZ0MzY2IDZyUjNlVGOlVzNwIjZmJzI0IjO5UjOzEDI3ETL5ATL1IDMyMCN

+ 5 - 5
.env.production

@@ -1,7 +1,7 @@
 VITE_API_URL=https://9g15.vip/api
 
-VIDEO_API_URL=https://qa3-api.69mediatest.com/api
-VIDEO_PLAT_ID:30017
-VIDEO_CHANNEL_ID:30017001
-VIDEO_USER_ID:30005470
-VIDEO_TOKEN:0YzN4gDM4UzNxMSZzAjNjdTN1IGNhNmY4ADZlVWO5UjY1YDOmVjNilTNwMiNwEDO5QTNwYzI3ETZ3I2MxMGZhJmZ0MzY2IDZyUjNlVGOlVzNwIjZmJzI0IjO5UjOzEDI3ETL5ATL1IDMyMCN
+VITE_VIDEO_API_URL=https://qa3-api.69mediatest.com/api
+VITE_VIDEO_PLAT_ID=30017
+VITE_VIDEO_CHANNEL_ID=30017001
+VITE_VIDEO_USER_ID=30005470
+VITE_VIDEO_TOKEN=0YzN4gDM4UzNxMSZzAjNjdTN1IGNhNmY4ADZlVWO5UjY1YDOmVjNilTNwMiNwEDO5QTNwYzI3ETZ3I2MxMGZhJmZ0MzY2IDZyUjNlVGOlVzNwIjZmJzI0IjO5UjOzEDI3ETL5ATL1IDMyMCN

+ 2 - 110
src/App.vue

@@ -1,115 +1,7 @@
 <script setup lang="ts">
-import { ref, onMounted, onBeforeUnmount, computed } from "vue";
-import Header from "@/components/layout/Header.vue";
-import Footer from "@/components/layout/Footer.vue";
-import Home from "@/views/Home.vue";
-import Purchased from "@/views/Purchased.vue";
-import Account from "@/views/Account.vue";
-import Favorite from "@/views/Favorite.vue";
-import LoginDialog from "@/components/LoginDialog.vue";
-import { useUserStore } from "@/store/user";
-
-type TabKey = "home" | "purchased" | "account" | "favorite";
-
-const active = ref<TabKey>("home");
-const showScrollTop = ref(false);
-const showLoginDialog = ref(false);
-const userStore = useUserStore();
-
-const isLoggedIn = computed(() => !!userStore.token);
-
-function handleScroll() {
-  showScrollTop.value = window.scrollY > 320;
-}
-
-function scrollToTop() {
-  window.scrollTo({ top: 0, behavior: "smooth" });
-}
-
-function switchTab(key: TabKey) {
-  if (key === "purchased" && !isLoggedIn.value) {
-    showLoginDialog.value = true;
-    return;
-  }
-  active.value = key;
-}
-
-function handleLoginSuccess() {
-  if (active.value === "account") {
-    userStore.sync();
-  }
-}
-
-onMounted(async () => {
-  window.addEventListener("scroll", handleScroll, { passive: true });
-
-  if (userStore.token) {
-    userStore.sync().catch(() => {
-      userStore.logout();
-      showLoginDialog.value = true;
-    });
-  } else if (!userStore.userManuallyLoggedOut) {
-    await createGuestAccount();
-  }
-});
-
-const createGuestAccount = async () => {
-  try {
-    const urlParams = new URLSearchParams(window.location.search);
-    const inviteCode = urlParams.get("code");
-
-    await userStore.createGuest(inviteCode || undefined);
-    console.log("游客账号创建成功", userStore.userInfo);
-  } catch (error) {
-    console.error("创建游客账号失败", error);
-  }
-};
-
-onBeforeUnmount(() => {
-  window.removeEventListener("scroll", handleScroll);
-});
+// App.vue 现在只负责路由渲染
 </script>
 
 <template>
-  <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" />
-
-    <main class="max-w-screen-sm mx-auto w-full px-4 pb-28 pt-3">
-      <Home v-if="active === 'home'" />
-      <Purchased v-else-if="active === 'purchased' && isLoggedIn" />
-      <Account
-        v-else-if="active === 'account'"
-        @show-login="showLoginDialog = true"
-      />
-      <Favorite v-else-if="active === 'favorite'" />
-    </main>
-
-    <button
-      v-show="showScrollTop"
-      @click="scrollToTop"
-      class="fixed right-4 bottom-28 z-40 h-11 w-11 rounded-full bg-brand text-slate-900 shadow-lg grid place-items-center active:scale-95 transition"
-      aria-label="回到顶部"
-    >
-      <svg
-        viewBox="0 0 24 24"
-        width="22"
-        height="22"
-        fill="none"
-        stroke="currentColor"
-        stroke-width="2"
-        class="text-slate-900"
-      >
-        <path d="m5 15 7-7 7 7" />
-      </svg>
-    </button>
-
-    <Footer :active="active" @switch-tab="switchTab" />
-
-    <LoginDialog
-      v-model="showLoginDialog"
-      @login-success="handleLoginSuccess"
-    />
-  </div>
+  <router-view />
 </template>

+ 1 - 1
src/components/LoginDialog.vue

@@ -85,7 +85,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, defineProps, defineEmits, watch } from "vue";
+import { ref, watch } from "vue";
 import { useUserStore } from "@/store/user";
 
 const props = defineProps<{

+ 1 - 32
src/components/layout/Header.vue

@@ -1,15 +1,10 @@
 <script setup lang="ts">
-import { ref, computed } from "vue";
+import { computed } from "vue";
 import { useUserStore } from "@/store/user";
 
-const query = ref("");
 const userStore = useUserStore();
 const isLoggedIn = computed(() => !!userStore.token);
 
-function onSearch(e: Event) {
-  e.preventDefault();
-}
-
 defineProps<{
   title?: string;
 }>();
@@ -52,32 +47,6 @@ const emit = defineEmits(["show-login", "switch-tab"]);
           </button>
         </div>
       </div>
-      <form class="pb-3" @submit="onSearch">
-        <label class="relative block">
-          <span class="absolute left-3 top-1/2 -translate-y-1/2 text-white/50">
-            <svg
-              width="20"
-              height="20"
-              viewBox="0 0 24 24"
-              fill="none"
-              stroke="currentColor"
-              stroke-width="2"
-              stroke-linecap="round"
-              stroke-linejoin="round"
-            >
-              <circle cx="11" cy="11" r="8" />
-              <path d="m21 21-4.3-4.3" />
-            </svg>
-          </span>
-          <input
-            v-model="query"
-            type="search"
-            enterkeyhint="search"
-            placeholder="搜索国产、日韩"
-            class="w-full rounded-xl bg-white/5 border border-white/10 pl-10 pr-4 py-2.5 text-[15px] placeholder:text-white/40 text-white outline-none focus:ring-2 focus:ring-brand/60 focus:border-brand/60"
-          />
-        </label>
-      </form>
     </div>
   </header>
 </template>

+ 105 - 0
src/components/layout/MainLayout.vue

@@ -0,0 +1,105 @@
+<template>
+  <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" />
+
+    <main class="max-w-screen-sm mx-auto w-full px-4 pb-28 pt-3">
+      <router-view />
+    </main>
+
+    <button
+      v-show="showScrollTop"
+      @click="scrollToTop"
+      class="fixed right-4 bottom-28 z-40 h-11 w-11 rounded-full bg-brand text-slate-900 shadow-lg grid place-items-center active:scale-95 transition"
+      aria-label="回到顶部"
+    >
+      <svg
+        viewBox="0 0 24 24"
+        width="22"
+        height="22"
+        fill="none"
+        stroke="currentColor"
+        stroke-width="2"
+        class="text-slate-900"
+      >
+        <path d="m5 15 7-7 7 7" />
+      </svg>
+    </button>
+
+    <Footer :active="active" @switch-tab="switchTab" />
+
+    <LoginDialog
+      v-model="showLoginDialog"
+      @login-success="handleLoginSuccess"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, onBeforeUnmount, computed } from "vue";
+import Header from "@/components/layout/Header.vue";
+import Footer from "@/components/layout/Footer.vue";
+import LoginDialog from "@/components/LoginDialog.vue";
+import { useUserStore } from "@/store/user";
+
+type TabKey = "home" | "purchased" | "account" | "favorite";
+
+const active = ref<TabKey>("home");
+const showScrollTop = ref(false);
+const showLoginDialog = ref(false);
+const userStore = useUserStore();
+
+const isLoggedIn = computed(() => !!userStore.token);
+
+function handleScroll() {
+  showScrollTop.value = window.scrollY > 320;
+}
+
+function scrollToTop() {
+  window.scrollTo({ top: 0, behavior: "smooth" });
+}
+
+function switchTab(key: TabKey) {
+  if (key === "purchased" && !isLoggedIn.value) {
+    showLoginDialog.value = true;
+    return;
+  }
+  active.value = key;
+}
+
+function handleLoginSuccess() {
+  if (active.value === "account") {
+    userStore.sync();
+  }
+}
+
+onMounted(async () => {
+  window.addEventListener("scroll", handleScroll, { passive: true });
+
+  if (userStore.token) {
+    userStore.sync().catch(() => {
+      userStore.logout();
+      showLoginDialog.value = true;
+    });
+  } else if (!userStore.userManuallyLoggedOut) {
+    await createGuestAccount();
+  }
+});
+
+const createGuestAccount = async () => {
+  try {
+    const urlParams = new URLSearchParams(window.location.search);
+    const inviteCode = urlParams.get("code");
+
+    await userStore.createGuest(inviteCode || undefined);
+    console.log("游客账号创建成功", userStore.userInfo);
+  } catch (error) {
+    console.error("创建游客账号失败", error);
+  }
+};
+
+onBeforeUnmount(() => {
+  window.removeEventListener("scroll", handleScroll);
+});
+</script>

+ 9 - 0
src/components/layout/VideoLayout.vue

@@ -0,0 +1,9 @@
+<template>
+  <div class="min-h-screen bg-black">
+    <router-view />
+  </div>
+</template>
+
+<script setup lang="ts">
+// 简单的全屏布局,用于视频播放页面
+</script>

+ 50 - 2
src/router/index.ts

@@ -1,13 +1,57 @@
 import { createRouter, createWebHistory } from "vue-router";
 import type { RouteRecordRaw } from "vue-router";
+import MainLayout from "../components/layout/MainLayout.vue";
+import VideoLayout from "../components/layout/VideoLayout.vue";
 import Home from "../views/Home.vue";
+import VideoPlayer from "../views/VideoPlayer.vue";
+import Purchased from "../views/Purchased.vue";
+import Account from "../views/Account.vue";
+import Favorite from "../views/Favorite.vue";
 import { useUserStore } from "@/store/user";
 
 const routes: Array<RouteRecordRaw> = [
   {
     path: "/",
-    name: "Home",
-    component: Home,
+    component: MainLayout,
+    children: [
+      {
+        path: "",
+        name: "Home",
+        component: Home,
+      },
+      {
+        path: "purchased",
+        name: "Purchased",
+        component: Purchased,
+      },
+      {
+        path: "account",
+        name: "Account",
+        component: Account,
+      },
+      {
+        path: "favorite",
+        name: "Favorite",
+        component: Favorite,
+      },
+    ],
+  },
+  {
+    path: "/video/:id",
+    component: MainLayout,
+    children: [
+      {
+        path: "",
+        name: "VideoPlayer",
+        component: VideoPlayer,
+        props: true,
+      },
+    ],
+  },
+  {
+    path: "/:pathMatch(.*)*",
+    name: "NotFound",
+    redirect: "/",
   },
 ];
 
@@ -17,13 +61,17 @@ const router = createRouter({
 });
 
 router.beforeEach((to, _, next) => {
+  console.log("Router navigation to:", to.path, to.name);
+
   const userStore = useUserStore();
 
   if (to.meta.requiresAuth && !userStore.token) {
+    console.log("Redirecting to Home due to auth requirement");
     next({ name: "Home" });
     return;
   }
 
+  console.log("Navigation allowed, proceeding to:", to.name);
   next();
 });
 

+ 38 - 22
src/services/api.ts

@@ -155,39 +155,24 @@ export const searchVideoByKeyword = async (
     ...(resource_type && { resource_type }),
     ...(lang && { lang }),
   });
-  return videoRequest(`/api/media/${plat_id}/keyword`, formData);
+  return videoRequest(`/media/${plat_id}/keyword`, formData);
 };
 
-// 标签分类 查询接口
+// 视频查询接口(支持标签分类和类型查询)
 export const searchVideoByTags = async (
   device: string,
-  tag: string,
   page_count = 1,
   page_size = 20,
+  tag?: string,
   resource_type?: "long" | "short",
   sort?: "view" | "like" | "time"
 ): Promise<any> => {
   const formData = createVideoFormData(device, page_count, page_size, {
-    tag,
+    ...(tag && { tag }),
     ...(resource_type && { resource_type }),
     ...(sort && { sort }),
   });
-  return videoRequest(`/api/media/${plat_id}/search`, formData);
-};
-
-// 长/短视频类型 查询接口
-export const searchVideoByType = async (
-  device: string,
-  resource_type: "long" | "short",
-  sort?: "view" | "like" | "time",
-  page_count = 1,
-  page_size = 20
-): Promise<any> => {
-  const formData = createVideoFormData(device, page_count, page_size, {
-    resource_type,
-    ...(sort && { sort }),
-  });
-  return videoRequest(`/api/media/${plat_id}/search`, formData);
+  return videoRequest(`/media/${plat_id}/search`, formData);
 };
 
 // 视频顶部标签 查询接口
@@ -198,7 +183,7 @@ export const getVideoMenu = async (
   page_size = 20
 ): Promise<any> => {
   const formData = createVideoFormData(device, page_count, page_size, { type });
-  return videoRequest(`/api/media/${plat_id}/menu`, formData);
+  return videoRequest(`/media/${plat_id}/menu`, formData);
 };
 
 // 点播集 查询接口
@@ -209,7 +194,38 @@ export const getVodList = async (
   page_size = 20
 ): Promise<any> => {
   const formData = createVideoFormData(device, page_count, page_size, { hash });
-  return videoRequest(`/api/media/${plat_id}/menu/vods`, formData);
+  return videoRequest(`/media/${plat_id}/menu/vods`, formData);
+};
+
+/**
+ * ===================== 视频播放相关接口 =====================
+ */
+
+// 记录视频播放统计(模拟接口,实际项目中需要后端支持)
+export const recordVideoPlay = async (
+  videoId: string,
+  action: "play" | "pause" | "ended" | "timeupdate",
+  currentTime?: number,
+  duration?: number
+): Promise<any> => {
+  // 这里只是模拟,实际项目中需要调用后端API
+  console.log("记录视频播放统计:", {
+    videoId,
+    action,
+    currentTime,
+    duration,
+  });
+
+  // 模拟API调用
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve({
+        status: 0,
+        msg: "success",
+        data: { recorded: true },
+      });
+    }, 100);
+  });
 };
 
 export default api;

+ 0 - 539
src/views/Example.vue

@@ -1,539 +0,0 @@
-<script setup lang="ts">
-import { ref, onMounted, onBeforeUnmount } from "vue";
-
-type TabKey = "home" | "purchased" | "account" | "favorite";
-
-const tabs: { key: TabKey; label: string }[] = [
-  { key: "home", label: "首页" },
-  { key: "purchased", label: "已购买" },
-  { key: "account", label: "账号" },
-  { key: "favorite", label: "收藏本站" },
-];
-
-const active = ref<TabKey>("home");
-const query = ref("");
-const showScrollTop = ref(false);
-const showFavSheet = ref(false);
-
-const purchasedItems = ref(
-  Array.from({ length: 6 }).map((_, i) => ({
-    id: i + 1,
-    title: `购买条目 ${i + 1}`,
-    meta: "高清 · 永久观看",
-    progress: Math.round(20 + ((i * 13) % 70)),
-  }))
-);
-
-function onSearch(e: Event) {
-  e.preventDefault();
-}
-
-function switchTab(key: TabKey) {
-  active.value = key;
-  window.scrollTo({ top: 0, behavior: "smooth" });
-}
-
-function handleScroll() {
-  showScrollTop.value = window.scrollY > 320;
-}
-
-function copyLink() {
-  const url = window.location.href;
-  navigator.clipboard?.writeText(url);
-}
-
-function scrollToTop() {
-  window.scrollTo({ top: 0, behavior: "smooth" });
-}
-
-onMounted(() => {
-  window.addEventListener("scroll", handleScroll, { passive: true });
-});
-
-onBeforeUnmount(() => {
-  window.removeEventListener("scroll", handleScroll);
-});
-</script>
-
-<template>
-  <div
-    class="min-h-screen bg-surface text-slate-100 selection:bg-brand/20 selection:text-brand"
-  >
-    <header
-      class="sticky top-0 z-40 backdrop-blur supports-[backdrop-filter]:bg-surface/70 bg-surface/90 border-b border-white/5"
-    >
-      <div
-        class="max-w-screen-sm mx-auto w-full px-4 pt-[env(safe-area-inset-top)]"
-      >
-        <div class="flex items-center justify-between py-2">
-          <h1 class="text-base font-semibold tracking-tight text-white/90">
-            探索
-          </h1>
-          <div class="flex items-center gap-4 text-sm">
-            <button
-              class="text-white/70 hover:text-brand transition"
-              @click="switchTab('purchased')"
-            >
-              已购
-            </button>
-            <button
-              class="text-white/70 hover:text-brand transition"
-              @click="switchTab('account')"
-            >
-              账号
-            </button>
-          </div>
-        </div>
-        <form class="pb-3" @submit="onSearch">
-          <label class="relative block">
-            <span
-              class="absolute left-3 top-1/2 -translate-y-1/2 text-white/50"
-            >
-              <svg
-                width="20"
-                height="20"
-                viewBox="0 0 24 24"
-                fill="none"
-                stroke="currentColor"
-                stroke-width="2"
-                stroke-linecap="round"
-                stroke-linejoin="round"
-              >
-                <circle cx="11" cy="11" r="8" />
-                <path d="m21 21-4.3-4.3" />
-              </svg>
-            </span>
-            <input
-              v-model="query"
-              type="search"
-              enterkeyhint="search"
-              placeholder="搜索国产、日韩"
-              class="w-full rounded-xl bg-white/5 border border-white/10 pl-10 pr-4 py-2.5 text-[15px] placeholder:text-white/40 text-white outline-none focus:ring-2 focus:ring-brand/60 focus:border-brand/60"
-            />
-          </label>
-        </form>
-      </div>
-    </header>
-
-    <main class="max-w-screen-sm mx-auto w-full px-4 pb-28 pt-3">
-      <!-- 首页 -->
-      <section v-if="active === 'home'" class="space-y-6">
-        <div
-          class="rounded-2xl bg-gradient-to-br from-brand to-emerald-500 text-slate-900 p-4 shadow-lg"
-        >
-          <div class="flex items-center gap-3">
-            <div
-              class="h-10 w-10 rounded-xl bg-white/80 grid place-items-center text-emerald-700 shadow"
-            >
-              <svg
-                viewBox="0 0 24 24"
-                width="22"
-                height="22"
-                fill="none"
-                stroke="currentColor"
-                stroke-width="2"
-                class="text-emerald-700"
-              >
-                <path d="m3 12 7-7 4 4 7-7" />
-                <path d="M5 10v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8" />
-              </svg>
-            </div>
-            <div>
-              <p class="text-sm/5 text-emerald-950/80">为你推荐</p>
-              <h2 class="text-base font-semibold">今日热门与精选内容</h2>
-            </div>
-          </div>
-        </div>
-
-        <div class="flex flex-wrap gap-2">
-          <button class="chip">热门</button>
-          <button class="chip">国产</button>
-          <button class="chip">日韩</button>
-          <button class="chip">最新</button>
-          <button class="chip">高分</button>
-        </div>
-
-        <div class="grid grid-cols-2 gap-3">
-          <article
-            v-for="i in 8"
-            :key="i"
-            class="group rounded-2xl overflow-hidden bg-white/5 border border-white/10"
-          >
-            <div
-              class="aspect-[9/12] bg-gradient-to-br from-slate-700/60 to-slate-800/60 group-hover:from-slate-600/60 transition"
-            />
-            <div class="p-3">
-              <h3 class="text-[15px] font-medium text-white/90 truncate">
-                精选条目 {{ i }}
-              </h3>
-              <p class="text-xs text-white/50 mt-0.5 truncate">
-                高清 · 热门 · 推荐
-              </p>
-            </div>
-          </article>
-        </div>
-      </section>
-
-      <!-- 已购买 -->
-      <section v-else-if="active === 'purchased'" class="space-y-4">
-        <h2 class="sr-only">已购买</h2>
-        <div
-          class="rounded-xl bg-white/5 border border-white/10 p-3 text-sm text-white/70"
-        >
-          共 {{ purchasedItems.length }} 个已购条目
-        </div>
-        <div class="space-y-3">
-          <article
-            v-for="item in purchasedItems"
-            :key="item.id"
-            class="rounded-2xl bg-white/5 border border-white/10 overflow-hidden"
-          >
-            <div class="flex gap-3 p-3">
-              <div
-                class="w-24 shrink-0 rounded-xl overflow-hidden border border-white/10"
-              >
-                <div
-                  class="aspect-[9/12] bg-gradient-to-br from-slate-700/60 to-slate-800/60"
-                />
-              </div>
-              <div class="flex-1 min-w-0">
-                <h3 class="text-[15px] font-medium text-white/90 truncate">
-                  {{ item.title }}
-                </h3>
-                <p class="text-xs text-white/50 mt-0.5">{{ item.meta }}</p>
-                <div
-                  class="mt-2 h-2 w-full rounded-full bg-white/10 overflow-hidden"
-                >
-                  <div
-                    class="h-full bg-brand"
-                    :style="{ width: item.progress + '%' }"
-                  />
-                </div>
-                <div
-                  class="mt-2 flex items-center justify-between text-xs text-white/50"
-                >
-                  <span>观看进度 {{ item.progress }}%</span>
-                  <div class="flex gap-2">
-                    <button class="btn-subtle">继续观看</button>
-                    <button class="btn-subtle">下载</button>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </article>
-        </div>
-      </section>
-
-      <!-- 账号 -->
-      <section v-else-if="active === 'account'" class="space-y-5">
-        <div
-          class="rounded-2xl bg-white/5 border border-white/10 p-4 flex items-center gap-3"
-        >
-          <div
-            class="h-12 w-12 rounded-full bg-gradient-to-br from-emerald-400 to-emerald-600 grid place-items-center text-slate-900 font-semibold"
-          >
-            J
-          </div>
-          <div class="flex-1 min-w-0">
-            <p class="text-sm text-white/60">欢迎回来</p>
-            <h2 class="text-base font-semibold text-white/90 truncate">詹诚</h2>
-          </div>
-          <button
-            class="px-3 py-1.5 rounded-lg bg-brand text-slate-900 text-sm font-medium"
-          >
-            开通会员
-          </button>
-        </div>
-
-        <div class="grid grid-cols-3 gap-3">
-          <button class="stat">
-            <strong class="text-lg">6</strong>
-            <span>已购</span>
-          </button>
-          <button class="stat">
-            <strong class="text-lg">12</strong>
-            <span>收藏</span>
-          </button>
-          <button class="stat">
-            <strong class="text-lg">34</strong>
-            <span>历史</span>
-          </button>
-        </div>
-
-        <div
-          class="rounded-2xl overflow-hidden border border-white/10 divide-y divide-white/10"
-        >
-          <button class="row">
-            <span>订单记录</span>
-            <svg
-              viewBox="0 0 24 24"
-              width="18"
-              height="18"
-              fill="none"
-              stroke="currentColor"
-              stroke-width="2"
-            >
-              <path d="m9 18 6-6-6-6" />
-            </svg>
-          </button>
-          <button class="row">
-            <span>安全设置</span>
-            <svg
-              viewBox="0 0 24 24"
-              width="18"
-              height="18"
-              fill="none"
-              stroke="currentColor"
-              stroke-width="2"
-            >
-              <circle cx="12" cy="12" r="3" />
-              <path
-                d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V22a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H2a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06A2 2 0 1 1 6.04 4.4l.06.06c.46.46 1.12.6 1.71.33a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09c0 .66.39 1.26 1 1.51.59.27 1.25.13 1.71-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06c-.46.46-.6 1.12-.33 1.71.27.59.87 1 1.53 1H22a2 2 0 1 1 0 4h-.09c-.66 0-1.26.39-1.51 1Z"
-              />
-            </svg>
-          </button>
-          <button class="row">
-            <span>帮助与反馈</span>
-            <svg
-              viewBox="0 0 24 24"
-              width="18"
-              height="18"
-              fill="none"
-              stroke="currentColor"
-              stroke-width="2"
-            >
-              <path d="M9 18h6" />
-              <path d="M10 14a2 2 0 1 1 4 0v1" />
-              <path d="M12 6v2" />
-            </svg>
-          </button>
-          <button class="row text-red-300/90">
-            <span>退出登录</span>
-            <svg
-              viewBox="0 0 24 24"
-              width="18"
-              height="18"
-              fill="none"
-              stroke="currentColor"
-              stroke-width="2"
-            >
-              <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
-              <path d="m10 17 5-5-5-5" />
-              <path d="M15 12H3" />
-            </svg>
-          </button>
-        </div>
-      </section>
-
-      <!-- 收藏本站 -->
-      <section v-else class="space-y-4">
-        <div class="rounded-2xl bg-white/5 border border-white/10 p-4">
-          <h2 class="text-base font-semibold text-white/90">收藏本站</h2>
-          <p class="text-sm text-white/60 mt-1">
-            在浏览器中添加到收藏或主屏幕,访问更便捷。
-          </p>
-          <div class="mt-3 grid grid-cols-2 gap-3 text-sm">
-            <div class="rounded-xl bg-white/5 p-3 border border-white/10">
-              <p class="font-medium text-white/80">iOS</p>
-              <p class="text-white/60 mt-1">
-                Safari 底部“分享” → “添加到主��幕”。
-              </p>
-            </div>
-            <div class="rounded-xl bg-white/5 p-3 border border-white/10">
-              <p class="font-medium text-white/80">Android</p>
-              <p class="text-white/60 mt-1">Chrome 菜单 → “添加到主屏幕”。</p>
-            </div>
-          </div>
-          <div class="mt-3 flex gap-2">
-            <button
-              class="px-3 py-2 rounded-lg bg-brand text-slate-900 text-sm font-medium"
-              @click="showFavSheet = true"
-            >
-              查看步骤
-            </button>
-            <button
-              class="px-3 py-2 rounded-lg bg-white/5 border border-white/10 text-white/80 text-sm"
-              @click="copyLink"
-            >
-              复制当前链接
-            </button>
-          </div>
-        </div>
-      </section>
-    </main>
-
-    <button
-      v-show="showScrollTop"
-      @click="scrollToTop"
-      class="fixed right-4 bottom-28 z-40 h-11 w-11 rounded-full bg-brand text-slate-900 shadow-lg grid place-items-center active:scale-95 transition"
-      aria-label="回到顶部"
-    >
-      <svg
-        viewBox="0 0 24 24"
-        width="22"
-        height="22"
-        fill="none"
-        stroke="currentColor"
-        stroke-width="2"
-        class="text-slate-900"
-      >
-        <path d="m5 15 7-7 7 7" />
-      </svg>
-    </button>
-
-    <nav
-      class="fixed bottom-0 inset-x-0 z-40 border-t border-white/10 bg-surface/95 backdrop-blur"
-    >
-      <div
-        class="max-w-screen-sm mx-auto w-full px-2 pb-[max(env(safe-area-inset-bottom),0.25rem)]"
-      >
-        <ul class="grid grid-cols-4 items-end">
-          <li>
-            <button
-              @click="switchTab('home')"
-              class="tab-item"
-              :class="{ 'tab-active': active === 'home' }"
-            >
-              <svg
-                viewBox="0 0 24 24"
-                width="22"
-                height="22"
-                fill="none"
-                stroke="currentColor"
-                stroke-width="2"
-              >
-                <path d="M3 10.5 12 3l9 7.5" />
-                <path d="M5 10v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V10" />
-              </svg>
-              <span>首页</span>
-            </button>
-          </li>
-          <li>
-            <button
-              @click="switchTab('purchased')"
-              class="tab-item"
-              :class="{ 'tab-active': active === 'purchased' }"
-            >
-              <svg
-                viewBox="0 0 24 24"
-                width="22"
-                height="22"
-                fill="none"
-                stroke="currentColor"
-                stroke-width="2"
-              >
-                <path d="m6 6 1.5 12a2 2 0 0 0 2 2h5a2 2 0 0 0 2-2L18 6" />
-                <path d="M6 6h12" />
-                <path d="M9 6a3 3 0 1 1 6 0" />
-              </svg>
-              <span>已购买</span>
-            </button>
-          </li>
-          <li>
-            <button
-              @click="switchTab('account')"
-              class="tab-item"
-              :class="{ 'tab-active': active === 'account' }"
-            >
-              <svg
-                viewBox="0 0 24 24"
-                width="22"
-                height="22"
-                fill="none"
-                stroke="currentColor"
-                stroke-width="2"
-              >
-                <path d="M20 21a8 8 0 1 0-16 0" />
-                <circle cx="12" cy="7" r="4" />
-              </svg>
-              <span>账号</span>
-            </button>
-          </li>
-          <li>
-            <button
-              @click="switchTab('favorite')"
-              class="tab-item"
-              :class="{ 'tab-active': active === 'favorite' }"
-            >
-              <svg
-                viewBox="0 0 24 24"
-                width="22"
-                height="22"
-                fill="none"
-                stroke="currentColor"
-                stroke-width="2"
-              >
-                <path
-                  d="M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
-                />
-              </svg>
-              <span>收藏本站</span>
-            </button>
-          </li>
-        </ul>
-      </div>
-    </nav>
-
-    <!-- 收藏步骤弹层 -->
-    <div
-      v-if="showFavSheet"
-      class="fixed inset-0 z-50 bg-black/60 flex items-end md:items-center justify-center"
-    >
-      <div
-        class="w-full max-w-screen-sm bg-surface rounded-t-2xl md:rounded-2xl border border-white/10 p-4"
-      >
-        <div class="flex items-center justify-between">
-          <h3 class="text-base font-semibold">将本站添加到主屏幕</h3>
-          <button
-            class="text-white/60 hover:text-white"
-            @click="showFavSheet = false"
-          >
-            关闭
-          </button>
-        </div>
-        <ol
-          class="mt-3 space-y-2 text-sm text-white/80 list-decimal list-inside"
-        >
-          <li>iOS:在 Safari 点击底部 “分享” 按钮。</li>
-          <li>选择 “添加到主屏幕”,确认名称后添加。</li>
-          <li>Android:在 Chrome 右上角菜单选择 “添加到主屏幕”。</li>
-        </ol>
-        <div class="mt-3 flex gap-2">
-          <button
-            class="px-3 py-2 rounded-lg bg-brand text-slate-900 text-sm font-medium"
-            @click="copyLink"
-          >
-            复制当前链接
-          </button>
-          <button
-            class="px-3 py-2 rounded-lg bg-white/5 border border-white/10 text-white/80 text-sm"
-            @click="showFavSheet = false"
-          >
-            我知道了
-          </button>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style scoped>
-.tab-item {
-  @apply w-full py-2.5 flex flex-col items-center justify-center gap-1 text-[12px] text-white/60 hover:text-white/80 transition;
-}
-.tab-active {
-  @apply text-brand;
-}
-.chip {
-  @apply px-3 py-1.5 rounded-full bg-white/5 border border-white/10 text-sm text-white/70 hover:bg-white/10 hover:text-white transition;
-}
-.btn-subtle {
-  @apply px-2.5 py-1 rounded-md bg-white/5 border border-white/10 text-white/80 hover:bg-white/10;
-}
-.stat {
-  @apply rounded-xl bg-white/5 border border-white/10 p-3 text-center text-white/70 hover:text-white transition;
-}
-.row {
-  @apply w-full flex items-center justify-between px-4 py-3 bg-white/5 text-white/80 hover:bg-white/10;
-}
-</style>

+ 665 - 14
src/views/Home.vue

@@ -1,6 +1,62 @@
 <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"
       class="rounded-2xl bg-gradient-to-br from-brand to-emerald-500 text-slate-900 p-4 shadow-lg"
     >
       <div class="flex items-center gap-3">
@@ -27,37 +83,632 @@
       </div>
     </div>
 
-    <div class="flex flex-wrap gap-2">
-      <button class="chip">热门</button>
-      <button class="chip">国产</button>
-      <button class="chip">日韩</button>
-      <button class="chip">最新</button>
-      <button class="chip">高分</button>
+    <!-- 动态标签栏 -->
+    <div v-if="!isSearchMode" class="relative">
+      <div
+        class="flex flex-wrap gap-2 transition-all duration-300"
+        :class="{ 'max-h-20 overflow-hidden': !showAllTags }"
+      >
+        <button
+          v-for="menu in videoMenus"
+          :key="menu.hash"
+          class="chip"
+          :class="{ 'chip-active': selectedMenu === menu.hash }"
+          @click="selectMenu(menu.hash)"
+        >
+          {{ menu.name }}
+        </button>
+      </div>
+
+      <!-- 展开/收起按钮 -->
+      <div v-if="videoMenus.length > 6" class="mt-2 text-center">
+        <button
+          @click="toggleTags"
+          class="text-xs text-white/60 hover:text-white/80 transition-colors"
+        >
+          {{ showAllTags ? "收起" : `展开更多 (${videoMenus.length - 6})` }}
+        </button>
+      </div>
     </div>
 
+    <!-- 排序选择栏 -->
+    <div v-if="!isSearchMode" class="flex flex-wrap gap-1.5">
+      <button
+        v-for="sortOption in sortOptions"
+        :key="sortOption.value"
+        class="sort-chip"
+        :class="{ 'sort-chip-active': selectedSort === sortOption.value }"
+        @click="selectSort(sortOption.value)"
+      >
+        {{ sortOption.label }}
+      </button>
+    </div>
+
+    <!-- 视频列表 -->
     <div class="grid grid-cols-2 gap-3">
       <article
-        v-for="i in 8"
-        :key="i"
-        class="group rounded-2xl overflow-hidden bg-white/5 border border-white/10"
+        v-for="video in videoList"
+        :key="video.id"
+        @click="playVideo(video)"
+        class="group rounded-2xl overflow-hidden bg-white/5 border border-white/10 cursor-pointer hover:bg-white/10 transition"
       >
-        <div
-          class="aspect-[9/12] bg-gradient-to-br from-slate-700/60 to-slate-800/60 group-hover:from-slate-600/60 transition"
-        />
+        <div class="aspect-[9/12] relative">
+          <img
+            :src="video.cover"
+            :alt="video.name"
+            class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
+            @error="handleImageError"
+          />
+          <div
+            class="absolute bottom-2 right-2 bg-black/70 text-white text-xs px-2 py-1 rounded"
+          >
+            {{ formatDuration(video.duration) }}
+          </div>
+        </div>
         <div class="p-3">
           <h3 class="text-[15px] font-medium text-white/90 truncate">
-            精选条目 {{ i }}
+            {{ video.name }}
           </h3>
           <p class="text-xs text-white/50 mt-0.5 truncate">
-            高清 · 热门 · 推荐
+            {{ video.view }} 次观看 · {{ video.like }} 点赞
           </p>
         </div>
       </article>
     </div>
+
+    <!-- 加载状态 -->
+    <div v-if="loading" class="text-center py-8">
+      <div class="text-white/50">加载中...</div>
+    </div>
+
+    <!-- 空状态 -->
+    <div v-if="!loading && videoList.length === 0" class="text-center py-8">
+      <div class="text-white/50">暂无视频内容</div>
+    </div>
+
+    <!-- 分页组件 -->
+    <div v-if="totalPages > 1" class="py-6">
+      <!-- 桌面端:完整分页 -->
+      <div class="hidden sm:block">
+        <!-- 上一页、页码、下一页 -->
+        <div class="flex justify-between items-center gap-2 mb-3">
+          <!-- 上一页按钮 -->
+          <button
+            @click="
+              isSearchMode
+                ? handleSearch(currentPage - 1)
+                : loadVideosByTag(selectedMenu, currentPage - 1)
+            "
+            :disabled="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 disabled:opacity-50 disabled:cursor-not-allowed transition text-sm"
+          >
+            &lt;
+          </button>
+
+          <!-- 页码区域 -->
+          <div class="flex items-center gap-2">
+            <!-- 首页 -->
+            <button
+              v-if="currentPage > 3"
+              @click="
+                isSearchMode
+                  ? handleSearch(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"
+            >
+              1
+            </button>
+
+            <!-- 省略号 -->
+            <span v-if="currentPage > 4" class="text-white/50 text-sm"
+              >...</span
+            >
+
+            <!-- 当前页前后 -->
+            <button
+              v-if="currentPage > 1"
+              @click="
+                isSearchMode
+                  ? handleSearch(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"
+            >
+              {{ currentPage - 1 }}
+            </button>
+
+            <!-- 当前页 -->
+            <button
+              class="px-3 py-2 rounded-lg bg-emerald-500/20 text-emerald-400 border border-emerald-400/30 text-sm"
+            >
+              {{ currentPage }}
+            </button>
+
+            <!-- 当前页后 -->
+            <button
+              v-if="currentPage < totalPages"
+              @click="
+                isSearchMode
+                  ? handleSearch(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"
+            >
+              {{ currentPage + 1 }}
+            </button>
+
+            <!-- 省略号 -->
+            <span
+              v-if="currentPage < totalPages - 3"
+              class="text-white/50 text-sm"
+              >...</span
+            >
+
+            <!-- 跳转输入框(在省略号和末页之间) -->
+            <div
+              v-if="currentPage < totalPages - 3"
+              class="flex items-center gap-1"
+            >
+              <input
+                v-model="jumpToPage"
+                type="number"
+                :min="1"
+                :max="totalPages"
+                class="w-10 px-1 py-2 rounded-lg bg-white/5 border border-white/10 text-white text-sm text-center focus:outline-none focus:ring-2 focus:ring-emerald-500/50 focus:border-emerald-500/50"
+                @keyup.enter="jumpToPageHandler"
+                @blur="jumpToPageHandler"
+              />
+            </div>
+
+            <!-- 末页 -->
+            <button
+              v-if="currentPage < totalPages - 2"
+              @click="
+                isSearchMode
+                  ? handleSearch(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"
+            >
+              {{ totalPages }}
+            </button>
+          </div>
+
+          <!-- 下一页按钮 -->
+          <button
+            @click="
+              isSearchMode
+                ? handleSearch(currentPage + 1)
+                : loadVideosByTag(selectedMenu, currentPage + 1)
+            "
+            :disabled="currentPage >= 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 disabled:opacity-50 disabled:cursor-not-allowed transition text-sm"
+          >
+            &gt;
+          </button>
+        </div>
+
+        <!-- 页码信息 -->
+        <div class="text-center">
+          <div class="text-sm text-white/50">
+            第 {{ currentPage }} 页,共 {{ totalPages }} 页
+          </div>
+        </div>
+      </div>
+
+      <!-- 移动端:动态分页 -->
+      <div class="sm:hidden">
+        <div class="flex justify-between items-center gap-1 mb-3">
+          <!-- 上一页按钮 -->
+          <button
+            @click="
+              isSearchMode
+                ? handleSearch(currentPage - 1)
+                : loadVideosByTag(selectedMenu, currentPage - 1)
+            "
+            :disabled="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 disabled:opacity-50 disabled:cursor-not-allowed transition text-sm"
+          >
+            &lt;
+          </button>
+
+          <!-- 页码区域 -->
+          <div class="flex items-center gap-1 flex-1 justify-center">
+            <!-- 首页 -->
+            <button
+              v-if="currentPage > 2"
+              @click="
+                isSearchMode
+                  ? handleSearch(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"
+            >
+              1
+            </button>
+
+            <!-- 省略号 -->
+            <span v-if="currentPage > 3" class="text-white/50 text-sm"
+              >...</span
+            >
+
+            <!-- 当前页前 -->
+            <button
+              v-if="currentPage > 1"
+              @click="
+                isSearchMode
+                  ? handleSearch(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"
+            >
+              {{ currentPage - 1 }}
+            </button>
+
+            <!-- 当前页 -->
+            <button
+              class="px-2 py-2 rounded-lg bg-emerald-500/20 text-emerald-400 border border-emerald-400/30 text-sm"
+            >
+              {{ currentPage }}
+            </button>
+
+            <!-- 当前页后 -->
+            <button
+              v-if="currentPage < totalPages"
+              @click="
+                isSearchMode
+                  ? handleSearch(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"
+            >
+              {{ currentPage + 1 }}
+            </button>
+
+            <!-- 省略号 -->
+            <span
+              v-if="currentPage < totalPages - 2"
+              class="text-white/50 text-sm"
+              >...</span
+            >
+
+            <!-- 末页 -->
+            <button
+              v-if="currentPage < totalPages - 1"
+              @click="
+                isSearchMode
+                  ? handleSearch(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"
+            >
+              {{ totalPages }}
+            </button>
+
+            <!-- 跳转输入框 -->
+            <input
+              v-model="jumpToPage"
+              type="number"
+              :min="1"
+              :max="totalPages"
+              class="w-8 px-1 py-2 rounded-lg bg-white/5 border border-white/10 text-white text-xs text-center focus:outline-none focus:ring-1 focus:ring-emerald-500/50 focus:border-emerald-500/50"
+              @keyup.enter="jumpToPageHandler"
+              @blur="jumpToPageHandler"
+            />
+          </div>
+
+          <!-- 下一页按钮 -->
+          <button
+            @click="
+              isSearchMode
+                ? handleSearch(currentPage + 1)
+                : loadVideosByTag(selectedMenu, currentPage + 1)
+            "
+            :disabled="currentPage >= 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 disabled:opacity-50 disabled:cursor-not-allowed transition text-sm"
+          >
+            &gt;
+          </button>
+        </div>
+
+        <!-- 页码信息 -->
+        <div class="text-center">
+          <div class="text-sm text-white/50">
+            第 {{ currentPage }} 页,共 {{ totalPages }} 页
+          </div>
+        </div>
+      </div>
+    </div>
   </section>
 </template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import {
+  getVideoMenu,
+  searchVideoByTags,
+  searchVideoByKeyword,
+} from "@/services/api";
+
+// 路由
+const router = useRouter();
+
+// 生成MAC地址作为设备标识
+const generateMacAddress = (): string => {
+  const hex = "0123456789ABCDEF";
+  let mac = "";
+  for (let i = 0; i < 6; i++) {
+    if (i > 0) mac += ":";
+    mac += hex[Math.floor(Math.random() * 16)];
+    mac += hex[Math.floor(Math.random() * 16)];
+  }
+  return mac;
+};
+
+// 视频菜单数据
+const videoMenus = ref<any[]>([]);
+const selectedMenu = ref<string>("");
+const device = generateMacAddress();
+
+// 视频列表数据
+const videoList = ref<any[]>([]);
+const loading = ref(false);
+
+// 分页数据
+const currentPage = ref(1);
+const pageSize = ref(6);
+const totalPages = ref(0);
+const totalCount = ref(0);
+const jumpToPage = ref<number | string>("");
+
+// 排序选项
+const sortOptions = ref([
+  { value: "time", label: "最新" },
+  { value: "view", label: "热门" },
+  { value: "like", label: "点赞" },
+]);
+const selectedSort = ref<string>("time");
+
+// 标签展开状态
+const showAllTags = ref(false);
+
+// 搜索相关
+const searchKeyword = ref("");
+const isSearchMode = ref(false);
+
+// 格式化时长
+const formatDuration = (duration: string | number): string => {
+  const seconds = parseInt(String(duration));
+  const minutes = Math.floor(seconds / 60);
+  const remainingSeconds = seconds % 60;
+  return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
+};
+
+// 处理图片加载错误
+const handleImageError = (event: Event) => {
+  const img = event.target as HTMLImageElement;
+  img.src =
+    "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjMzMzIi8+CjxwYXRoIGQ9Ik0xMDAgNzVMMTI1IDEwMEgxMDBWMTI1SDc1TDEwMCAxMDBaIiBmaWxsPSIjNjY2Ii8+Cjwvc3ZnPgo=";
+};
+
+// 跳转到指定页面
+const jumpToPageHandler = () => {
+  const inputValue = String(jumpToPage.value).trim();
+
+  // 检查输入是否为空
+  if (!inputValue) {
+    console.log("输入框为空,不执行跳转");
+    return;
+  }
+
+  const page = parseInt(inputValue);
+
+  // 检查是否为有效数字
+  if (isNaN(page)) {
+    console.log("输入的不是有效数字:", inputValue);
+    jumpToPage.value = "";
+    return;
+  }
+
+  // 检查页码范围
+  if (page < 1 || page > totalPages.value) {
+    console.log(`页码超出范围: ${page}, 总页数: ${totalPages.value}`);
+    jumpToPage.value = "";
+    return;
+  }
+
+  // 检查是否与当前页相同
+  if (page === currentPage.value) {
+    console.log("跳转到当前页,无需操作");
+    jumpToPage.value = "";
+    return;
+  }
+
+  console.log(
+    `准备跳转到第 ${page} 页,当前页: ${currentPage.value}, 总页数: ${totalPages.value}`
+  );
+
+  try {
+    if (isSearchMode.value) {
+      console.log("搜索模式,执行搜索跳转");
+      handleSearch(page);
+    } else {
+      console.log("标签模式,执行标签跳转");
+      loadVideosByTag(selectedMenu.value || "", page);
+    }
+    jumpToPage.value = "";
+    console.log("跳转完成");
+  } catch (error) {
+    console.error("跳转失败:", error);
+    jumpToPage.value = "";
+  }
+};
+
+// 选择菜单并加载视频
+const selectMenu = async (hash: string) => {
+  selectedMenu.value = hash;
+  await loadVideosByTag(hash, 1);
+};
+
+// 选择排序方式
+const selectSort = async (sort: string) => {
+  selectedSort.value = sort;
+  // 重新查询当前显示的内容
+  if (isSearchMode.value) {
+    // 如果是搜索模式,重新执行搜索
+    await handleSearch(1);
+  } else if (selectedMenu.value) {
+    // 如果选择了特定菜单,按菜单查询
+    await loadVideosByTag(selectedMenu.value, 1);
+  } else {
+    // 如果没有选择菜单,查询所有视频
+    await loadVideosByTag("", 1);
+  }
+};
+
+// 切换标签展开状态
+const toggleTags = () => {
+  showAllTags.value = !showAllTags.value;
+};
+
+// 搜索功能
+const handleSearch = async (page = 1) => {
+  if (!searchKeyword.value.trim()) return;
+
+  isSearchMode.value = true;
+  loading.value = true;
+  currentPage.value = page;
+
+  try {
+    const response = await searchVideoByKeyword(
+      device,
+      searchKeyword.value.trim(),
+      page,
+      pageSize.value,
+      "long",
+      "zh_CN"
+    );
+
+    if (response.status === 0 && response.data?.list) {
+      videoList.value = response.data.list;
+      // 更新分页信息
+      if (response.data.pageInfo) {
+        totalPages.value = response.data.pageInfo.pageCount || 0;
+        totalCount.value = response.data.pageInfo.pageTotal || 0;
+      }
+    } else {
+      videoList.value = [];
+      totalPages.value = 0;
+      totalCount.value = 0;
+    }
+  } catch (error) {
+    console.error("搜索视频失败:", error);
+    videoList.value = [];
+    totalPages.value = 0;
+    totalCount.value = 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 清除搜索
+const clearSearch = () => {
+  searchKeyword.value = "";
+  isSearchMode.value = false;
+  // 恢复默认显示(加载所有视频)
+  loadVideosByTag("");
+};
+
+// 播放视频
+const playVideo = (video: any) => {
+  router.push({
+    name: "VideoPlayer",
+    params: { id: video.id },
+    query: {
+      name: video.name,
+      cover: video.cover,
+      m3u8: video.m3u8,
+      duration: video.duration,
+      view: video.view,
+      like: video.like,
+      time: video.time,
+      taginfo: JSON.stringify(video.taginfo || []),
+    },
+  });
+};
+
+// 根据标签加载视频
+const loadVideosByTag = async (tagHash: string, page = 1) => {
+  loading.value = true;
+  currentPage.value = page;
+
+  try {
+    const response = await searchVideoByTags(
+      device,
+      page,
+      pageSize.value,
+      tagHash || undefined,
+      "long",
+      selectedSort.value as "view" | "like" | "time"
+    );
+
+    if (response.status === 0 && response.data?.list) {
+      videoList.value = response.data.list;
+      // 更新分页信息
+      if (response.data.pageInfo) {
+        totalPages.value = response.data.pageInfo.pageCount || 0;
+        totalCount.value = response.data.pageInfo.pageTotal || 0;
+      }
+    } else {
+      videoList.value = [];
+      totalPages.value = 0;
+      totalCount.value = 0;
+    }
+  } catch (error) {
+    console.error("获取视频列表失败:", error);
+    videoList.value = [];
+    totalPages.value = 0;
+    totalCount.value = 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 获取视频菜单
+const fetchVideoMenus = async () => {
+  try {
+    const response = await getVideoMenu(device, "1", 1, 20); // 获取长视频菜单
+    if (response.status === 0 && response.data) {
+      videoMenus.value = response.data;
+      // 默认不选择任何菜单,加载所有视频
+      await loadVideosByTag("");
+    }
+  } catch (error) {
+    console.error("获取视频菜单失败:", error);
+  }
+};
+
+onMounted(() => {
+  fetchVideoMenus();
+});
+</script>
+
 <style scoped>
 .chip {
   @apply px-3 py-1.5 rounded-full bg-white/5 border border-white/10 text-sm text-white/70 hover:bg-white/10 hover:text-white transition;
 }
+
+.chip-active {
+  @apply bg-white/10 text-white border-white/20;
+}
+
+.sort-chip {
+  @apply px-2 py-1 rounded-full bg-white/5 border border-white/10 text-[10px] text-white/60 hover:bg-white/10 hover:text-white/80 transition;
+}
+
+.sort-chip-active {
+  @apply bg-emerald-500/20 text-emerald-400 border-emerald-400/30;
+}
 </style>

+ 512 - 0
src/views/VideoPlayer.vue

@@ -0,0 +1,512 @@
+<template>
+  <section class="space-y-6">
+    <!-- 返回按钮 -->
+    <div class="flex items-center gap-3">
+      <button
+        @click="goBack"
+        class="flex items-center gap-2 text-white/80 hover:text-white 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="M15 19l-7-7 7-7"
+          />
+        </svg>
+        <span class="text-sm">返回</span>
+      </button>
+
+      <div class="flex items-center gap-2">
+        <button
+          @click="toggleFavorite"
+          class="p-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition"
+        >
+          <svg
+            v-if="!isFavorite"
+            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="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
+            />
+          </svg>
+          <svg
+            v-else
+            class="w-5 h-5 fill-red-500 text-red-500"
+            viewBox="0 0 24 24"
+          >
+            <path
+              d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
+            />
+          </svg>
+        </button>
+
+        <button
+          @click="toggleShare"
+          class="p-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white 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="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z"
+            />
+          </svg>
+        </button>
+      </div>
+    </div>
+
+    <!-- 视频播放器区域 -->
+    <div class="relative rounded-2xl overflow-hidden bg-black">
+      <div class="aspect-video">
+        <video
+          ref="videoPlayer"
+          :src="videoInfo.m3u8"
+          :poster="videoInfo.cover"
+          class="w-full h-full object-contain"
+          controls
+          preload="metadata"
+          playsinline
+        >
+          您的浏览器不支持视频播放
+        </video>
+      </div>
+    </div>
+
+    <!-- 视频信息区域 -->
+    <div class="space-y-6">
+      <!-- 视频标题和基本信息 -->
+      <div class="space-y-3">
+        <h1 class="text-xl font-semibold text-white leading-tight">
+          {{ videoInfo.name || "视频标题" }}
+        </h1>
+        <div class="flex items-center gap-4 text-sm text-white/60">
+          <div class="flex items-center gap-1">
+            <svg
+              class="w-4 h-4"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
+              />
+            </svg>
+            <span>{{ formatDuration(videoInfo.duration) }}</span>
+          </div>
+          <div class="flex items-center gap-1">
+            <svg
+              class="w-4 h-4"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
+              />
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
+              />
+            </svg>
+            <span>{{ formatNumber(videoInfo.view) }} 次观看</span>
+          </div>
+          <div class="flex items-center gap-1">
+            <svg
+              class="w-4 h-4"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
+              />
+            </svg>
+            <span>{{ formatNumber(videoInfo.like) }} 点赞</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 标签信息 -->
+      <div
+        v-if="videoInfo.taginfo && videoInfo.taginfo.length > 0"
+        class="space-y-3"
+      >
+        <h3 class="text-sm font-medium text-white/80">标签</h3>
+        <div class="flex flex-wrap gap-2">
+          <span
+            v-for="tag in videoInfo.taginfo"
+            :key="tag.hash"
+            class="px-3 py-1.5 rounded-full bg-white/5 border border-white/10 text-xs text-white/70 hover:bg-white/10 hover:text-white transition"
+          >
+            {{ tag.name }}
+          </span>
+        </div>
+      </div>
+
+      <!-- 相关推荐 -->
+      <div v-if="relatedVideos.length > 0" class="space-y-4">
+        <h3 class="text-sm font-medium text-white/80">相关推荐</h3>
+        <div class="grid grid-cols-3 gap-2">
+          <article
+            v-for="video in relatedVideos"
+            :key="video.id"
+            @click="playVideo(video)"
+            class="group rounded-xl overflow-hidden bg-white/5 border border-white/10 cursor-pointer hover:bg-white/10 transition"
+          >
+            <div class="aspect-[9/12] relative">
+              <img
+                :src="video.cover"
+                :alt="video.name"
+                class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
+                @error="handleImageError"
+              />
+              <div
+                class="absolute bottom-1 right-1 bg-black/70 text-white text-xs px-1.5 py-0.5 rounded"
+              >
+                {{ formatDuration(video.duration) }}
+              </div>
+            </div>
+            <div class="p-2">
+              <h4
+                class="text-xs font-medium text-white/90 truncate leading-tight"
+              >
+                {{ video.name }}
+              </h4>
+              <p class="text-xs text-white/50 mt-0.5">
+                {{ formatNumber(video.view) }} 观看
+              </p>
+            </div>
+          </article>
+        </div>
+      </div>
+    </div>
+
+    <!-- 分享弹窗 -->
+    <div
+      v-if="showShareModal"
+      class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
+      @click="closeShareModal"
+    >
+      <div
+        class="bg-surface rounded-2xl p-6 max-w-sm w-full mx-4 border border-white/10"
+        @click.stop
+      >
+        <h3 class="text-lg font-semibold text-white mb-4">分享视频</h3>
+        <div class="space-y-3">
+          <button
+            @click="copyVideoLink"
+            class="w-full flex items-center gap-3 p-3 rounded-xl bg-white/5 border border-white/10 text-white/80 hover:bg-white/10 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="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
+              />
+            </svg>
+            <span>复制链接</span>
+          </button>
+          <button
+            @click="shareToSocial"
+            class="w-full flex items-center gap-3 p-3 rounded-xl bg-white/5 border border-white/10 text-white/80 hover:bg-white/10 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="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z"
+              />
+            </svg>
+            <span>分享到社交平台</span>
+          </button>
+        </div>
+        <button
+          @click="closeShareModal"
+          class="w-full mt-4 py-2 text-white/60 hover:text-white transition"
+        >
+          取消
+        </button>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { searchVideoByTags } from "@/services/api";
+
+// 路由相关
+const route = useRoute();
+const router = useRouter();
+
+// 视频播放器引用
+const videoPlayer = ref<HTMLVideoElement>();
+
+// 视频信息
+const videoInfo = ref<any>({
+  id: "",
+  name: "",
+  cover: "",
+  m3u8: "",
+  duration: 0,
+  view: 0,
+  like: 0,
+  time: 0,
+  taginfo: [],
+});
+
+// 相关视频
+const relatedVideos = ref<any[]>([]);
+
+// 状态管理
+const isFavorite = ref(false);
+const showShareModal = ref(false);
+
+// 生成设备标识
+const generateMacAddress = (): string => {
+  const hex = "0123456789ABCDEF";
+  let mac = "";
+  for (let i = 0; i < 6; i++) {
+    if (i > 0) mac += ":";
+    mac += hex[Math.floor(Math.random() * 16)];
+    mac += hex[Math.floor(Math.random() * 16)];
+  }
+  return mac;
+};
+
+const device = generateMacAddress();
+
+// 格式化时长
+const formatDuration = (duration: string | number): string => {
+  const seconds = parseInt(String(duration));
+  const minutes = Math.floor(seconds / 60);
+  const remainingSeconds = seconds % 60;
+  return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
+};
+
+// 格式化数字
+const formatNumber = (num: string | number): string => {
+  const n = parseInt(String(num));
+  if (n >= 10000) {
+    return `${(n / 10000).toFixed(1)}万`;
+  }
+  return n.toString();
+};
+
+// 处理图片加载错误
+const handleImageError = (event: Event) => {
+  const img = event.target as HTMLImageElement;
+  img.src =
+    "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjMzMzIi8+CjxwYXRoIGQ9Ik0xMDAgNzVMMTI1IDEwMEgxMDBWMTI1SDc1TDEwMCAxMDBaIiBmaWxsPSIjNjY2Ii8+Cjwvc3ZnPgo=";
+};
+
+// 返回上一页
+const goBack = () => {
+  router.back();
+};
+
+// 切换收藏状态
+const toggleFavorite = () => {
+  isFavorite.value = !isFavorite.value;
+  // TODO: 实现收藏功能
+};
+
+// 切换分享弹窗
+const toggleShare = () => {
+  showShareModal.value = true;
+};
+
+// 关闭分享弹窗
+const closeShareModal = () => {
+  showShareModal.value = false;
+};
+
+// 复制视频链接
+const copyVideoLink = async () => {
+  try {
+    const videoUrl = window.location.href;
+    await navigator.clipboard.writeText(videoUrl);
+    // TODO: 显示复制成功提示
+    closeShareModal();
+  } catch (error) {
+    console.error("复制链接失败:", error);
+  }
+};
+
+// 分享到社交平台
+const shareToSocial = () => {
+  // TODO: 实现社交平台分享
+  closeShareModal();
+};
+
+// 播放视频
+const playVideo = (video: any) => {
+  router.push({
+    name: "VideoPlayer",
+    params: { id: video.id },
+    query: {
+      name: video.name,
+      cover: video.cover,
+      m3u8: video.m3u8,
+      duration: video.duration,
+      view: video.view,
+      like: video.like,
+      time: video.time,
+      taginfo: JSON.stringify(video.taginfo || []),
+    },
+  });
+};
+
+// 加载视频信息
+const loadVideoInfo = () => {
+  // 优先从路由参数获取ID
+  const videoId = route.params.id || route.query.id;
+
+  // 从查询参数获取其他信息
+  const videoData = route.query;
+
+  if (videoId) {
+    videoInfo.value = {
+      id: videoId,
+      name: videoData.name || `视频 ${videoId}`,
+      cover: videoData.cover || "",
+      m3u8: videoData.m3u8 || "",
+      duration: videoData.duration || 0,
+      view: videoData.view || 0,
+      like: videoData.like || 0,
+      time: videoData.time || 0,
+      taginfo: videoData.taginfo ? JSON.parse(videoData.taginfo as string) : [],
+    };
+
+    // 设置页面标题
+    document.title = `${videoInfo.value.name} - 视频播放`;
+  } else {
+    console.error("未找到视频ID");
+  }
+};
+
+// 加载相关视频
+const loadRelatedVideos = async () => {
+  try {
+    // 获取当前视频的标签
+    const currentVideoTags = videoInfo.value.taginfo || [];
+
+    if (currentVideoTags.length > 0) {
+      // 随机选择一个标签
+      const randomTag =
+        currentVideoTags[Math.floor(Math.random() * currentVideoTags.length)];
+
+      // 生成1-10之间的随机页码
+      const randomPage = Math.floor(Math.random() * 10) + 1;
+
+      // 获取15个该标签下的最热视频
+      const response = await searchVideoByTags(
+        device,
+        randomPage,
+        15,
+        randomTag.hash,
+        "long",
+        "view"
+      );
+
+      if (response.status === 0 && response.data?.list) {
+        // 过滤掉当前视频
+        relatedVideos.value = response.data.list
+          .filter((video: any) => video.id !== videoInfo.value.id)
+          .slice(0, 15);
+      }
+    } else {
+      // 如果没有标签,则获取全局最热视频
+      const randomPage = Math.floor(Math.random() * 10) + 1;
+
+      const response = await searchVideoByTags(
+        device,
+        randomPage,
+        15,
+        undefined,
+        "long",
+        "view"
+      );
+
+      if (response.status === 0 && response.data?.list) {
+        relatedVideos.value = response.data.list
+          .filter((video: any) => video.id !== videoInfo.value.id)
+          .slice(0, 15);
+      }
+    }
+  } catch (error) {
+    console.error("加载相关视频失败:", error);
+  }
+};
+
+onMounted(() => {
+  loadVideoInfo();
+  loadRelatedVideos();
+});
+</script>
+
+<style scoped>
+/* 自定义视频播放器样式 */
+video::-webkit-media-controls-panel {
+  background-color: rgba(0, 0, 0, 0.5);
+}
+
+video::-webkit-media-controls-play-button,
+video::-webkit-media-controls-pause-button {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 50%;
+}
+
+video::-webkit-media-controls-timeline {
+  background-color: rgba(255, 255, 255, 0.3);
+  border-radius: 2px;
+}
+
+video::-webkit-media-controls-current-time-display,
+video::-webkit-media-controls-time-remaining-display {
+  color: white;
+  font-size: 12px;
+}
+</style>