Explorar el Código

新增会员和单片购买功能,优化用户支付流程,添加购买记录查询和状态检查接口,提升用户体验

wuyi hace 3 meses
padre
commit
7e8b16c4e3
Se han modificado 5 ficheros con 1096 adiciones y 116 borrados
  1. 40 3
      src/services/api.ts
  2. 2 3
      src/types/vip.ts
  3. 0 14
      src/views/Account.vue
  4. 450 29
      src/views/Purchased.vue
  5. 604 67
      src/views/VideoPlayer.vue

+ 40 - 3
src/services/api.ts

@@ -129,17 +129,54 @@ export const upgradeGuest = async (
   return res.data;
   return res.data;
 };
 };
 
 
+// 会员购买
 export const purchaseMember = async (
 export const purchaseMember = async (
   userId: number,
   userId: number,
   type: string
   type: string
 ): Promise<any> => {
 ): Promise<any> => {
-  const res = await api.post("/payment/create", { userId, type });
+  const res = await api.post("/payment/vip/create", { userId, type });
   return res.data;
   return res.data;
 };
 };
 
 
-export const userQueryOrder = async (orderNo?: string): Promise<any> => {
+// 单片购买
+export const purchaseSingle = async (
+  userId: number,
+  resourceId: string
+): Promise<any> => {
+  const res = await api.post("/payment/single/create", { userId, resourceId });
+  return res.data;
+};
+
+// 用户支付订单查询
+export const userQueryOrder = async (
+  orderNo?: string,
+  resourceId?: string
+): Promise<any> => {
+  const params = {
+    orderNo: orderNo || undefined,
+    resourceId: resourceId || undefined,
+  };
   const res = await api.get(`/payment/user/query`, {
   const res = await api.get(`/payment/user/query`, {
-    params: { orderNo: orderNo || undefined },
+    params,
+  });
+  return res.data;
+};
+
+// 查询用户是否已购买特定单片资源
+export const checkSinglePurchase = async (resourceId: string): Promise<any> => {
+  const res = await api.get("/payment/single/query", {
+    params: { resourceId },
+  });
+  return res.data;
+};
+
+// 获取用户的单片机购买记录列表(分页)
+export const getSinglePurchaseList = async (
+  page = 0,
+  size = 20
+): Promise<any> => {
+  const res = await api.get("/payment/single/list", {
+    params: { page, size },
   });
   });
   return res.data;
   return res.data;
 };
 };

+ 2 - 3
src/types/vip.ts

@@ -10,14 +10,13 @@ export enum VipLevel {
   LIFETIME = "lifetime",
   LIFETIME = "lifetime",
 }
 }
 
 
-// 检查用户是否有权限观看完整视频
 export const canWatchFullVideo = (vipLevel: VipLevel): boolean => {
 export const canWatchFullVideo = (vipLevel: VipLevel): boolean => {
   return vipLevel !== VipLevel.GUEST && vipLevel !== VipLevel.FREE;
   return vipLevel !== VipLevel.GUEST && vipLevel !== VipLevel.FREE;
 };
 };
 
 
-// 获取试看时长(秒)
+// 试看时长
 export const getTrialDuration = (): number => {
 export const getTrialDuration = (): number => {
-  return 5 * 60; // 5分钟
+  return 5 * 60;
 };
 };
 
 
 export const vipLevelToText = (level: VipLevel | string): string => {
 export const vipLevelToText = (level: VipLevel | string): string => {

+ 0 - 14
src/views/Account.vue

@@ -268,17 +268,6 @@ onMounted(() => {
       </button>
       </button>
     </div>
     </div>
 
 
-    <div v-if="isLoggedIn" class="grid grid-cols-2 gap-3">
-      <button class="stat">
-        <strong class="text-lg">12</strong>
-        <span>收藏</span>
-      </button>
-      <button class="stat">
-        <strong class="text-lg">6</strong>
-        <span>已购</span>
-      </button>
-    </div>
-
     <div
     <div
       v-if="isLoggedIn"
       v-if="isLoggedIn"
       class="rounded-2xl overflow-hidden border border-white/10 divide-y divide-white/10"
       class="rounded-2xl overflow-hidden border border-white/10 divide-y divide-white/10"
@@ -617,9 +606,6 @@ onMounted(() => {
   </section>
   </section>
 </template>
 </template>
 <style scoped>
 <style scoped>
-.stat {
-  @apply rounded-xl bg-white/5 border border-white/10 p-3 text-center text-white/70 hover:text-white transition;
-}
 .row {
 .row {
   @apply w-full flex items-center justify-between px-4 py-3 bg-white/5 text-white/80 hover:bg-white/10;
   @apply w-full flex items-center justify-between px-4 py-3 bg-white/5 text-white/80 hover:bg-white/10;
 }
 }

+ 450 - 29
src/views/Purchased.vue

@@ -1,46 +1,467 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref } from 'vue'
-
-const purchasedItems = ref(
-  Array.from({ length: 6 }).map((_, i) => ({
-    id: i + 1,
-    title: `购买条目 ${i + 1}`,
-    meta: '高清 · 永久观看',
-    progress: Math.round(20 + (i * 13) % 70),
-  }))
-)
+import { ref, onMounted, onUnmounted } from "vue";
+import { useRouter } from "vue-router";
+import { getSinglePurchaseList, getVideoDetail } from "@/services/api";
+
+interface PurchaseRecord {
+  id: number;
+  userId: number;
+  resourceId: string;
+  status: boolean;
+  createdAt: string;
+}
+
+interface PurchaseItem {
+  id: number;
+  resourceId: string;
+  title: string;
+  meta: string;
+  progress: number;
+  cover: string;
+  duration: number;
+  createdAt: string;
+}
+
+const router = useRouter();
+
+const purchasedItems = ref<PurchaseItem[]>([]);
+const isLoading = ref(true);
+const currentPage = ref(0);
+const hasMore = ref(true);
+const coverUrls = ref<Record<string, string>>({}); // 存储解密后的封面URLs
+
+// 生成设备标识
+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();
+
+// 解密封面图片的函数(从VideoProcessor组件复制)
+const decryptCover = async (url: string): Promise<string> => {
+  if (!url.includes("cover")) {
+    return url;
+  }
+
+  try {
+    const response = await fetch(url, { mode: "cors" });
+    if (!response.ok) {
+      throw new Error(`HTTP error! status: ${response.status}`);
+    }
+
+    const reader = response.body!.getReader();
+    const key = new Uint8Array(8);
+    const buffer: Uint8Array[] = [];
+    let len = 0;
+    let offset = 0;
+
+    for (;;) {
+      const read = await reader.read();
+      if (read.done) break;
+      if (!read.value) continue;
+
+      if (len < 8) {
+        let i = 0;
+        while (i < read.value.length) {
+          key[len++] = read.value[i++];
+          if (len > 7) break;
+        }
+        if (len < 8) continue;
+        read.value = read.value.slice(i);
+      }
+
+      // 复制数据以避免修改原始数据
+      const decryptedValue = new Uint8Array(read.value.length);
+      for (let j = 0; j < read.value.length; j++) {
+        decryptedValue[j] = read.value[j] ^ key[(offset + j) % 8];
+      }
+
+      buffer.push(decryptedValue);
+      offset += read.value.length;
+    }
+
+    // 合并所有解密后的数据
+    const totalLength = buffer.reduce((sum, chunk) => sum + chunk.length, 0);
+    const finalBuffer = new Uint8Array(totalLength);
+    let pos = 0;
+    for (const chunk of buffer) {
+      finalBuffer.set(chunk, pos);
+      pos += chunk.length;
+    }
+
+    // 创建Blob并生成URL
+    const blob = new Blob([finalBuffer], { type: "image/jpeg" });
+    return URL.createObjectURL(blob);
+  } catch (error) {
+    console.error("解密封面失败:", error);
+    return url; // 解密失败时返回原始URL
+  }
+};
+
+// 加载购买记录
+const loadPurchasedItems = async () => {
+  try {
+    isLoading.value = true;
+
+    const response = await getSinglePurchaseList(currentPage.value, 20);
+
+    if (response.content && Array.isArray(response.content)) {
+      const purchaseRecords: PurchaseRecord[] = response.content;
+
+      // 并发获取每个视频的详情信息
+      const videoDetails = await Promise.all(
+        purchaseRecords.map(async (record) => {
+          // 处理所有后端返回的记录
+
+          try {
+            const videoResponse = await getVideoDetail(
+              device,
+              record.resourceId
+            );
+            if (videoResponse.status === 0 && videoResponse.data) {
+              const data = videoResponse.data;
+
+              // 解密封面图片
+              let coverUrl = "";
+              const originalCover =
+                data.cover || data.pic || data.thumbnail || "";
+              if (originalCover) {
+                try {
+                  coverUrl = await decryptCover(originalCover);
+                  coverUrls.value[record.id] = coverUrl;
+                } catch (error) {
+                  console.error(`解密封面失败 (${record.resourceId}):`, error);
+                  coverUrl = originalCover;
+                }
+              }
+
+              return {
+                id: record.id,
+                resourceId: record.resourceId,
+                title: data.name || `视频 ${record.resourceId}`,
+                meta: "高清 · 永久观看",
+                progress: Math.round(20 + ((record.id * 13) % 70)),
+                cover: coverUrl,
+                duration: data.duration || 0,
+                createdAt: record.createdAt,
+              } as PurchaseItem;
+            } else {
+              // 如果API返回失败,也返回默认信息
+              return {
+                id: record.id,
+                resourceId: record.resourceId,
+                title: `视频 ${record.resourceId}`,
+                meta: "高清 · 永久观看",
+                progress: Math.round(20 + ((record.id * 13) % 70)),
+                cover: "",
+                duration: 0,
+                createdAt: record.createdAt,
+              } as PurchaseItem;
+            }
+          } catch (error) {
+            console.error(`获取视频 ${record.resourceId} 详情失败:`, error);
+
+            // 如果获取详情失败,返回默认信息
+            return {
+              id: record.id,
+              resourceId: record.resourceId,
+              title: `购买条目 ${record.id}`,
+              meta: "高清 · 永久观看",
+              progress: Math.round(20 + ((record.id * 13) % 70)),
+              cover: "",
+              duration: 0,
+              createdAt: record.createdAt,
+            } as PurchaseItem;
+          }
+        })
+      );
+
+      // 添加视频详情到列表
+      if (currentPage.value === 0) {
+        purchasedItems.value = videoDetails;
+      } else {
+        purchasedItems.value.push(...videoDetails);
+      }
+
+      // 检查是否还有更多数据
+      hasMore.value =
+        response.metadata &&
+        purchasedItems.value.length < response.metadata.total;
+    }
+  } catch (error) {
+    console.error("加载购买记录失败:", error);
+    // 如果API失败,显示默认数据
+    purchasedItems.value = Array.from({ length: 6 }).map((_, i) => ({
+      id: i + 1,
+      resourceId: `${i + 1}`, // 默认资源ID
+      title: `购买条目 ${i + 1}`,
+      meta: "高清 · 永久观看",
+      progress: Math.round(20 + ((i * 13) % 70)),
+      cover: "",
+      duration: 0,
+      createdAt: new Date().toISOString(), // 默认当前时间
+    }));
+  } finally {
+    isLoading.value = false;
+  }
+};
+
+// 格式化时长
+const formatDuration = (duration: number): string => {
+  const minutes = Math.floor(duration / 60);
+  const seconds = duration % 60;
+  return `${minutes}:${seconds.toString().padStart(2, "0")}`;
+};
+
+// 格式化日期
+const formatDate = (dateString: string): string => {
+  try {
+    const date = new Date(dateString);
+    const now = new Date();
+    const diffTime = Math.abs(now.getTime() - date.getTime());
+    const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
+
+    if (diffDays === 0) {
+      return "今天";
+    } else if (diffDays === 1) {
+      return "昨天";
+    } else if (diffDays < 7) {
+      return `${diffDays}天前`;
+    } else if (diffDays < 30) {
+      const weeks = Math.floor(diffDays / 7);
+      return `${weeks}周前`;
+    } else if (diffDays < 365) {
+      const months = Math.floor(diffDays / 30);
+      return `${months}个月前`;
+    } else {
+      return date.toLocaleDateString("zh-CN", {
+        year: "numeric",
+        month: "short",
+        day: "numeric",
+      });
+    }
+  } catch (error) {
+    console.error("日期格式化失败:", error);
+    return "未知";
+  }
+};
+
+// 加载更多
+const loadMore = () => {
+  if (!hasMore.value || isLoading.value) return;
+
+  currentPage.value++;
+  loadPurchasedItems();
+};
+
+// 点击播放视频
+const playVideo = (item: PurchaseItem) => {
+  // 跳转到视频播放页面
+  router.push({
+    name: "VideoPlayer",
+    params: { id: item.resourceId },
+  });
+};
+
+onMounted(() => {
+  loadPurchasedItems();
+});
+
+// 清理URL对象,防止内存泄漏
+onUnmounted(() => {
+  Object.values(coverUrls.value).forEach((url) => {
+    if (url.startsWith("blob:")) {
+      URL.revokeObjectURL(url);
+    }
+  });
+  coverUrls.value = {};
+});
 </script>
 </script>
 
 
 <template>
 <template>
   <section class="space-y-4">
   <section class="space-y-4">
     <h2 class="sr-only">已购买</h2>
     <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
+      class="rounded-xl bg-white/5 border border-white/10 p-3 text-sm text-white/70"
+    >
+      <div v-if="isLoading" class="flex items-center gap-2">
+        <div
+          class="w-4 h-4 border-2 border-white/20 border-t-white/80 rounded-full animate-spin"
+        ></div>
+        <span>加载中...</span>
+      </div>
+      <div v-else>共 {{ purchasedItems.length }} 个已购条目</div>
+    </div>
+
+    <!-- 加载状态 -->
+    <div
+      v-if="isLoading && purchasedItems.length === 0"
+      class="grid grid-cols-2 gap-3"
+    >
+      <article
+        v-for="i in 6"
+        :key="i"
+        class="rounded-xl overflow-hidden bg-white/5 border border-white/10 animate-pulse"
+      >
+        <div class="aspect-[16/9] bg-slate-700/60" />
+        <div class="p-3">
+          <div class="h-4 bg-slate-700/60 rounded mb-1" />
+          <div class="h-3 bg-slate-700/40 rounded w-16" />
+        </div>
+      </article>
+    </div>
+
+    <!-- 购买记录列表 -->
+    <div v-else class="grid grid-cols-2 gap-3">
+      <article
+        v-for="item in purchasedItems"
+        :key="item.id"
+        @click="playVideo(item)"
+        class="group rounded-xl overflow-hidden bg-white/5 border border-white/10 cursor-pointer hover:bg-white/10 transition-all duration-300"
+      >
+        <!-- 封面图片 -->
+        <div class="aspect-[16/9] relative">
+          <img
+            v-if="item.cover"
+            :src="item.cover"
+            :alt="item.title"
+            class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
+            @error="event => {
+              const target = event.target as HTMLImageElement;
+              if (target) target.style.display = 'none';
+            }"
+          />
+          <!-- 无封面时显示占位符 -->
+          <div
+            v-else
+            class="w-full h-full bg-gradient-to-br from-slate-700/60 to-slate-800/60 flex flex-col items-center justify-center"
+          >
+            <svg
+              class="w-12 h-12 text-white/40 mb-2"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="1.5"
+                d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
+              />
+            </svg>
+            <span class="text-xs text-white/40">暂无预览</span>
           </div>
           </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
+            class="absolute bottom-2 right-2 bg-black/70 text-white text-xs px-2 py-1 rounded backdrop-blur-sm"
+          >
+            {{ formatDuration(item.duration) }}
+          </div>
+        </div>
+
+        <!-- 视频信息 -->
+        <div class="p-3">
+          <h3
+            class="text-sm font-medium text-white/90 line-clamp-2 mb-1 group-hover:text-white transition-colors"
+          >
+            {{ item.title }}
+          </h3>
+
+          <div class="flex items-center justify-between text-xs text-white/50">
+            <!-- 购买日期 -->
+            <span>{{ formatDate(item.createdAt) }}</span>
+
+            <!-- 已购买状态 -->
+            <div class="flex items-center gap-1">
+              <svg
+                class="w-3 h-3"
+                fill="none"
+                stroke="currentColor"
+                viewBox="0 0 24 24"
+              >
+                <path
+                  stroke-linecap="round"
+                  stroke-linejoin="round"
+                  stroke-width="2"
+                  d="M5 13l4 4L19 7"
+                />
+              </svg>
+              <span>已购买</span>
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
       </article>
       </article>
     </div>
     </div>
+
+    <!-- 加载更多按钮或到底提示 -->
+    <div class="flex justify-center pt-6">
+      <div v-if="hasMore">
+        <button
+          @click="loadMore"
+          :disabled="isLoading"
+          class="px-6 py-3 rounded-lg bg-white/10 border border-white/20 text-white/80 hover:bg-white/20 hover:text-white transition-all disabled:opacity-50 disabled:cursor-not-allowed"
+        >
+          {{ isLoading ? "加载中..." : "加载更多" }}
+        </button>
+      </div>
+      <div
+        v-else-if="purchasedItems.length > 0"
+        class="text-center text-white/40 text-sm"
+      >
+        <div class="flex items-center justify-center gap-2">
+          <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="M5 13l4 4L19 7"
+            />
+          </svg>
+          <span>已经到底啦</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- 空状态 -->
+    <div
+      v-if="!isLoading && purchasedItems.length === 0"
+      class="text-center py-12"
+    >
+      <div class="text-white/40 text-sm">
+        <svg
+          class="w-16 h-16 mx-auto mb-4"
+          fill="none"
+          stroke="currentColor"
+          viewBox="0 0 24 24"
+        >
+          <path
+            stroke-linecap="round"
+            stroke-linejoin="round"
+            stroke-width="1.5"
+            d="M15 13l-3 3m0 0l-3-3m3 3V8m0 13a9 9 0 110-18 9 9 0 010 18z"
+          />
+        </svg>
+        <p>暂无购买记录</p>
+      </div>
+    </div>
   </section>
   </section>
 </template>
 </template>
 
 
 <style scoped>
 <style scoped>
-.btn-subtle{ @apply px-2.5 py-1 rounded-md bg-white/5 border border-white/10 text-white/80 hover:bg-white/10; }
+.btn-subtle {
+  @apply px-2.5 py-1 rounded-md bg-white/5 border border-white/10 text-white/80 hover:bg-white/10;
+}
 </style>
 </style>

+ 604 - 67
src/views/VideoPlayer.vue

@@ -1,31 +1,11 @@
 <template>
 <template>
   <section class="space-y-6">
   <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="relative">
+    <div class="flex items-center justify-between">
+      <div class="flex items-center gap-3">
         <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"
+          @click="goBack"
+          class="flex items-center gap-2 text-white/80 hover:text-white transition"
         >
         >
           <svg
           <svg
             class="w-5 h-5"
             class="w-5 h-5"
@@ -37,26 +17,64 @@
               stroke-linecap="round"
               stroke-linecap="round"
               stroke-linejoin="round"
               stroke-linejoin="round"
               stroke-width="2"
               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"
+              d="M15 19l-7-7 7-7"
             />
             />
           </svg>
           </svg>
+          <span class="text-sm">返回</span>
         </button>
         </button>
 
 
-        <!-- 分享提示弹窗 -->
-        <div
-          v-if="showShareModal"
-          class="absolute top-0 left-full ml-2 z-50 bg-emerald-500 text-white px-3 py-1.5 rounded-lg shadow-lg flex items-center gap-1.5 whitespace-nowrap"
-        >
-          <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
-            <path
-              fill-rule="evenodd"
-              d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
-              clip-rule="evenodd"
-            />
-          </svg>
-          <span class="text-xs font-medium">已复制</span>
+        <div class="relative">
+          <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
+            v-if="showShareModal"
+            class="absolute top-0 left-full ml-2 z-50 bg-emerald-500 text-white px-3 py-1.5 rounded-lg shadow-lg flex items-center gap-1.5 whitespace-nowrap"
+          >
+            <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
+              <path
+                fill-rule="evenodd"
+                d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1-0 011.414 0z"
+                clip-rule="evenodd"
+              />
+            </svg>
+            <span class="text-xs font-medium">已复制</span>
+          </div>
         </div>
         </div>
       </div>
       </div>
+
+      <!-- 游客和免费用户购买按钮 -->
+      <div v-if="isGuestOrFree && !isSinglePurchased" class="flex gap-1.5">
+        <button
+          @click="showMembershipPurchaseModal = true"
+          class="px-2 py-1 rounded-md bg-brand text-slate-900 text-xs font-medium hover:bg-brand/90 transition"
+        >
+          购买会员
+        </button>
+        <button
+          @click="showSinglePurchaseModal = true"
+          class="px-2 py-1 rounded-md bg-blue-500 text-white text-xs font-medium hover:bg-blue-600 transition"
+        >
+          单片购买
+        </button>
+      </div>
     </div>
     </div>
 
 
     <!-- 视频播放器区域 -->
     <!-- 视频播放器区域 -->
@@ -91,7 +109,7 @@
 
 
         <!-- 试看结束遮罩 -->
         <!-- 试看结束遮罩 -->
         <div
         <div
-          v-if="showPurchaseModal"
+          v-if="showTrialEndModal && !isSinglePurchased"
           class="absolute inset-0 bg-black/80 flex items-center justify-center z-50"
           class="absolute inset-0 bg-black/80 flex items-center justify-center z-50"
         >
         >
           <div
           <div
@@ -106,18 +124,18 @@
             <div class="space-y-3">
             <div class="space-y-3">
               <button
               <button
                 @click="purchaseMembership"
                 @click="purchaseMembership"
-                class="w-full bg-emerald-500 hover:bg-emerald-600 text-white py-3 px-4 rounded-lg font-medium transition"
+                class="w-full bg-brand hover:bg-brand/90 text-slate-900 py-3 px-4 rounded-lg font-medium transition"
               >
               >
                 购买会员
                 购买会员
               </button>
               </button>
               <button
               <button
                 @click="purchaseVideo"
                 @click="purchaseVideo"
-                class="w-full bg-white/10 hover:bg-white/20 text-white py-3 px-4 rounded-lg font-medium transition"
+                class="w-full bg-blue-500 hover:bg-blue-600 text-white py-3 px-4 rounded-lg font-medium transition"
               >
               >
                 单独购买本片
                 单独购买本片
               </button>
               </button>
               <button
               <button
-                @click="showPurchaseModal = false"
+                @click="showTrialEndModal = false"
                 class="w-full text-white/60 hover:text-white/80 py-2 text-sm transition"
                 class="w-full text-white/60 hover:text-white/80 py-2 text-sm transition"
               >
               >
                 取消
                 取消
@@ -128,6 +146,311 @@
       </div>
       </div>
     </div>
     </div>
 
 
+    <!-- 单片购买确认弹窗 -->
+    <div
+      v-if="showSinglePurchaseModal"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+      @click.self="showSinglePurchaseModal = false"
+    >
+      <div
+        class="bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-sm mx-4 text-center"
+      >
+        <h3 class="text-lg font-semibold text-white/90 mb-2">确认购买</h3>
+        <p class="text-sm text-white/70 mb-6">
+          确定要购买当前视频吗?<br />
+          购买后即可观看完整内容
+        </p>
+
+        <div class="space-y-3">
+          <button
+            @click="purchaseVideo"
+            class="w-full bg-brand hover:bg-brand/90 text-slate-900 py-3 px-4 rounded-lg font-medium transition"
+          >
+            确认购买
+          </button>
+          <button
+            @click="showSinglePurchaseModal = false"
+            class="w-full text-white/60 hover:text-white/80 py-2 text-sm transition"
+          >
+            取消
+          </button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 会员购买弹窗 -->
+    <div
+      v-if="showMembershipPurchaseModal"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+      @click.self="showMembershipPurchaseModal = false"
+    >
+      <div
+        class="bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-lg mx-4 max-h-[90vh] overflow-y-auto"
+      >
+        <h3 class="text-xl font-semibold text-white/90 mb-6 text-center">
+          开通会员
+        </h3>
+
+        <!-- 会员套餐选择 -->
+        <div class="space-y-2 mb-4">
+          <h4 class="text-sm font-medium text-white/70 mb-2">选择套餐</h4>
+          <div class="grid grid-cols-1 gap-2">
+            <label
+              v-for="plan in membershipPlans"
+              :key="plan.key"
+              class="relative cursor-pointer"
+            >
+              <input
+                v-model="selectedPlan"
+                :value="plan.key"
+                type="radio"
+                name="membership-plan"
+                class="sr-only"
+              />
+              <div
+                class="border-2 rounded-lg p-2 transition-all"
+                :class="
+                  selectedPlan === plan.key
+                    ? 'border-brand bg-brand/10'
+                    : 'border-white/20 hover:border-white/30'
+                "
+              >
+                <div class="flex justify-between items-center">
+                  <div>
+                    <div class="font-medium text-white/90 text-sm">
+                      {{ plan.label }}
+                    </div>
+                    <div class="text-xs text-white/60">{{ plan.duration }}</div>
+                  </div>
+                  <div class="text-right">
+                    <div class="font-semibold text-white/90 text-sm">
+                      {{ plan.price }}
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </label>
+          </div>
+        </div>
+
+        <!-- 支付方式选择 -->
+        <div class="space-y-2 mb-4">
+          <h4 class="text-sm font-medium text-white/70 mb-2">支付方式</h4>
+          <div class="space-y-2">
+            <label
+              class="flex items-center p-2 border border-white/20 rounded-lg cursor-pointer hover:bg-white/5"
+            >
+              <input
+                v-model="selectedPayment"
+                value="alipay"
+                type="radio"
+                name="payment-method"
+                class="sr-only"
+              />
+              <div class="flex items-center">
+                <div
+                  class="w-8 h-8 bg-blue-500 rounded flex items-center justify-center mr-3"
+                >
+                  <span class="text-white text-sm font-bold">支</span>
+                </div>
+                <span class="text-white/90">支付宝</span>
+              </div>
+            </label>
+          </div>
+        </div>
+
+        <!-- 操作按钮 -->
+        <div class="flex gap-3 pt-3 border-t border-white/10">
+          <button
+            type="button"
+            @click="showMembershipPurchaseModal = false"
+            class="flex-1 px-4 py-2 border border-white/20 text-white/70 rounded-lg hover:bg-white/5 transition"
+          >
+            取消
+          </button>
+          <button
+            @click="handleMembershipPurchase"
+            :disabled="!selectedPlan || isPaymentLoading"
+            class="flex-1 px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition disabled:opacity-50 disabled:cursor-not-allowed"
+          >
+            {{ isPaymentLoading ? "处理中..." : "立即购买" }}
+          </button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 支付等待弹窗 -->
+    <div
+      v-if="showPaymentWaitingDialog"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+    >
+      <div
+        class="bg-surface border border-white/10 rounded-2xl p-8 w-full max-w-sm mx-4 text-center"
+      >
+        <!-- 加载动画 -->
+        <div class="mb-6">
+          <div
+            class="w-16 h-16 mx-auto border-4 border-white/20 border-t-brand rounded-full animate-spin"
+          ></div>
+        </div>
+
+        <!-- 等待文字 -->
+        <h3 class="text-lg font-semibold text-white/90 mb-2">等待支付...</h3>
+        <p class="text-sm text-white/60 mb-6">
+          请在支付页面完成支付<br />
+          支付完成后会自动跳转
+        </p>
+
+        <!-- 操作按钮 -->
+        <div class="flex gap-3">
+          <button
+            @click="showPaymentWaitingDialog = false"
+            class="flex-1 px-4 py-2 border border-white/20 text-white/70 rounded-lg hover:bg-white/5 transition"
+          >
+            取消支付
+          </button>
+          <button
+            @click="handleQueryOrder"
+            class="flex-1 px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition"
+          >
+            已完成支付
+          </button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 单片购买支付等待弹窗 -->
+    <div
+      v-if="showSinglePaymentWaitingDialog"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+    >
+      <div
+        class="bg-surface border border-white/10 rounded-2xl p-8 w-full max-w-sm mx-4 text-center"
+      >
+        <!-- 加载动画 -->
+        <div class="mb-6">
+          <div
+            class="w-16 h-16 mx-auto border-4 border-white/20 border-t-brand rounded-full animate-spin"
+          ></div>
+        </div>
+
+        <!-- 等待文字 -->
+        <h3 class="text-lg font-semibold text-white/90 mb-2">等待支付...</h3>
+        <p class="text-sm text-white/60 mb-6">
+          请在支付页面完成支付<br />
+          支付完成后会自动跳转
+        </p>
+
+        <!-- 操作按钮 -->
+        <div class="flex gap-3">
+          <button
+            @click="showSinglePaymentWaitingDialog = false"
+            class="flex-1 px-4 py-2 border border-white/20 text-white/70 rounded-lg hover:bg-white/5 transition"
+          >
+            取消支付
+          </button>
+          <button
+            @click="handleSingleQueryOrder"
+            class="flex-1 px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition"
+          >
+            已完成支付
+          </button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 错误提示弹窗 -->
+    <div
+      v-if="showErrorDialog"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+      @click.self="showErrorDialog = false"
+    >
+      <div
+        class="bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-sm mx-4 text-center"
+      >
+        <!-- 错误图标 -->
+        <div class="mb-4">
+          <div
+            class="w-12 h-12 mx-auto bg-red-500/20 rounded-full flex items-center justify-center"
+          >
+            <svg
+              class="w-6 h-6 text-red-400"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
+              />
+            </svg>
+          </div>
+        </div>
+
+        <!-- 错误信息 -->
+        <h3 class="text-lg font-semibold text-white/90 mb-2">操作失败</h3>
+        <p class="text-sm text-white/70 mb-6">
+          {{ errorMessage }}
+        </p>
+
+        <!-- 确认按钮 -->
+        <button
+          @click="showErrorDialog = false"
+          class="w-full px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition"
+        >
+          确定
+        </button>
+      </div>
+    </div>
+
+    <!-- 成功提示弹窗 -->
+    <div
+      v-if="showSuccessDialog"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+      @click.self="showSuccessDialog = false"
+    >
+      <div
+        class="bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-sm mx-4 text-center"
+      >
+        <!-- 成功图标 -->
+        <div class="mb-4">
+          <div
+            class="w-12 h-12 mx-auto bg-green-500/20 rounded-full flex items-center justify-center"
+          >
+            <svg
+              class="w-6 h-6 text-green-400"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M5 13l4 4L19 7"
+              />
+            </svg>
+          </div>
+        </div>
+
+        <!-- 成功信息 -->
+        <h3 class="text-lg font-semibold text-white/90 mb-2">操作成功</h3>
+        <p class="text-sm text-white/70 mb-6">
+          {{ successMessage }}
+        </p>
+
+        <!-- 确认按钮 -->
+        <button
+          @click="showSuccessDialog = false"
+          class="w-full px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition"
+        >
+          确定
+        </button>
+      </div>
+    </div>
+
     <!-- 视频信息区域 -->
     <!-- 视频信息区域 -->
     <div class="space-y-6">
     <div class="space-y-6">
       <!-- 视频标题和基本信息 -->
       <!-- 视频标题和基本信息 -->
@@ -274,7 +597,14 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { ref, onMounted, onUnmounted, computed, watch } from "vue";
 import { ref, onMounted, onUnmounted, computed, watch } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { useRoute, useRouter } from "vue-router";
-import { searchVideoByTags, getVideoDetail } from "@/services/api";
+import {
+  searchVideoByTags,
+  getVideoDetail,
+  purchaseSingle,
+  purchaseMember,
+  userQueryOrder,
+  checkSinglePurchase,
+} from "@/services/api";
 import VideoProcessor from "@/components/VideoProcessor.vue";
 import VideoProcessor from "@/components/VideoProcessor.vue";
 import { useUserStore } from "@/store/user";
 import { useUserStore } from "@/store/user";
 import { VipLevel, canWatchFullVideo, getTrialDuration } from "@/types/vip";
 import { VipLevel, canWatchFullVideo, getTrialDuration } from "@/types/vip";
@@ -308,9 +638,54 @@ const relatedVideos = ref<any[]>([]);
 
 
 // 状态管理
 // 状态管理
 const showShareModal = ref(false);
 const showShareModal = ref(false);
-const showPurchaseModal = ref(false);
+const showTrialEndModal = ref(false);
+const showMembershipPurchaseModal = ref(false);
+const showSinglePurchaseModal = ref(false);
 const isTrialMode = ref(false);
 const isTrialMode = ref(false);
 
 
+// 单片购买状态
+const isSinglePurchased = ref(false);
+
+// 会员购买相关
+const membershipPlans = [
+  { key: "hourly", label: "一小时", price: "1", duration: "1小时" },
+  { key: "daily", label: "一天", price: "10", duration: "1天" },
+  { key: "weekly", label: "一周", price: "60", duration: "7天" },
+  { key: "monthly", label: "一个月", price: "200", duration: "30天" },
+  { key: "quarterly", label: "三个月", price: "500", duration: "90天" },
+  { key: "yearly", label: "一年", price: "1000", duration: "365天" },
+  { key: "lifetime", label: "终身", price: "1500", duration: "永久" },
+];
+
+const selectedPlan = ref("");
+const selectedPayment = ref("alipay");
+const isPaymentLoading = ref(false);
+
+// 支付等待相关
+const showPaymentWaitingDialog = ref(false);
+const currentOrderNo = ref("");
+
+// 提示弹窗相关
+const showSuccessDialog = ref(false);
+const showErrorDialog = ref(false);
+const successMessage = ref("");
+const errorMessage = ref("");
+
+// 单片购买支付等待相关
+const showSinglePaymentWaitingDialog = ref(false);
+const singleCurrentOrderNo = ref("");
+
+// 显示成功/错误提示的函数
+const showSuccess = (message: string) => {
+  successMessage.value = message;
+  showSuccessDialog.value = true;
+};
+
+const showError = (message: string) => {
+  errorMessage.value = message;
+  showErrorDialog.value = true;
+};
+
 // 生成设备标识
 // 生成设备标识
 const generateMacAddress = (): string => {
 const generateMacAddress = (): string => {
   const hex = "0123456789ABCDEF";
   const hex = "0123456789ABCDEF";
@@ -348,8 +723,8 @@ const onVideoProcessorRetry = () => {
 };
 };
 
 
 const onVideoPlay = () => {
 const onVideoPlay = () => {
-  // 如果是guest或free用户且还没有开始试看,则开始试看
-  if (isGuestOrFree.value && !isTrialMode.value) {
+  // 如果是guest或free用户且还没有开始试看,且未购买当前视频,则开始试看
+  if (isGuestOrFree.value && !isTrialMode.value && !isSinglePurchased.value) {
     startTrial();
     startTrial();
   }
   }
 };
 };
@@ -367,7 +742,7 @@ const onVideoTimeUpdate = () => {
       if (videoProcessorRef.value) {
       if (videoProcessorRef.value) {
         videoProcessorRef.value.stopHlsLoading();
         videoProcessorRef.value.stopHlsLoading();
       }
       }
-      showPurchaseModal.value = true;
+      showTrialEndModal.value = true;
     }
     }
   }
   }
 };
 };
@@ -400,10 +775,6 @@ const formatNumber = (num: string | number): string => {
   return n.toString();
   return n.toString();
 };
 };
 
 
-// 图片加载错误处理已移至 VideoProcessor 组件
-
-// 视频播放器事件处理已移至 VideoProcessor 组件
-
 // 返回上一页
 // 返回上一页
 const goBack = () => {
 const goBack = () => {
   router.back();
   router.back();
@@ -459,29 +830,176 @@ const startTrial = () => {
 
 
 // 购买会员
 // 购买会员
 const purchaseMembership = () => {
 const purchaseMembership = () => {
-  // TODO: 实现购买会员逻辑
-  console.log("购买会员");
-  showPurchaseModal.value = false;
+  // 关闭试看结束弹窗
+  showTrialEndModal.value = false;
+  // 打开会员购买弹窗
+  showMembershipPurchaseModal.value = true;
+};
+
+// 处理会员购买
+const handleMembershipPurchase = async () => {
+  if (!selectedPlan.value) {
+    showError("请选择会员套餐");
+    return;
+  }
+
+  isPaymentLoading.value = true;
+  try {
+    const response = await purchaseMember(
+      userStore.userInfo.id,
+      selectedPlan.value
+    );
+
+    if (response.code === 1) {
+      // 保存订单号
+      currentOrderNo.value = response.out_trade_no;
+
+      // 关闭购买弹窗
+      showMembershipPurchaseModal.value = false;
+
+      // 打开支付页面
+      window.open(response.code_url, "_blank");
+
+      // 显示支付等待弹窗
+      showPaymentWaitingDialog.value = true;
+
+      // 重置选择
+      selectedPlan.value = "";
+    } else {
+      showError(`支付失败: ${response.msg || "未知错误"}`);
+    }
+  } catch (error) {
+    console.error("购买会员失败", error);
+    showError("购买失败,请重试");
+  } finally {
+    isPaymentLoading.value = false;
+  }
+};
+
+// 查询订单状态
+const handleQueryOrder = async () => {
+  if (!currentOrderNo.value) {
+    showError("没有找到订单信息");
+    return;
+  }
+
+  try {
+    const response = await userQueryOrder(currentOrderNo.value);
+
+    if (response.status === 1) {
+      // 支付成功
+      await userStore.sync();
+      showPaymentWaitingDialog.value = false;
+      showSuccess("会员购买成功!");
+      currentOrderNo.value = "";
+    } else {
+      showError(response.msg || "会员购买失败");
+    }
+  } catch (error) {
+    console.error("查询订单状态失败:", error);
+    showError("会员购买失败,请重试");
+  }
+};
+
+// 查询单片购买订单状态
+const handleSingleQueryOrder = async () => {
+  if (!singleCurrentOrderNo.value) {
+    showError("没有找到订单信息");
+    return;
+  }
+
+  try {
+    // 使用视频信息的真实ID查询订单
+    const response = await userQueryOrder(
+      singleCurrentOrderNo.value,
+      videoInfo.value.id
+    );
+
+    if (response.status === 1) {
+      // 支付成功
+      await userStore.sync();
+      showSinglePaymentWaitingDialog.value = false;
+      showSuccess("视频购买成功!");
+      singleCurrentOrderNo.value = "";
+      // 重新检查购买状态(已完成支付时的检查)
+      await checkVideoPurchaseStatus();
+    } else {
+      showError(response.msg || "视频购买失败");
+    }
+  } catch (error) {
+    console.error("查询单片购买订单状态失败:", error);
+    showError("视频购买失败,请重试");
+  }
 };
 };
 
 
 // 单独购买本片
 // 单独购买本片
-const purchaseVideo = () => {
-  // TODO: 实现单独购买逻辑
-  console.log("单独购买本片");
-  showPurchaseModal.value = false;
+const purchaseVideo = async () => {
+  try {
+    // 使用视频信息的真实ID
+    if (!videoInfo.value.id || videoInfo.value.id === "unknown") {
+      showError("未找到视频资源ID");
+      return;
+    }
+
+    const response = await purchaseSingle(
+      userStore.userInfo.id,
+      videoInfo.value.id
+    );
+
+    if (response.code === 1) {
+      // 保存订单号
+      singleCurrentOrderNo.value = response.out_trade_no;
+
+      // 关闭弹窗
+      showTrialEndModal.value = false;
+      showSinglePurchaseModal.value = false;
+
+      // 打开支付页面
+      window.open(response.code_url, "_blank");
+
+      // 显示支付等待弹窗
+      showSinglePaymentWaitingDialog.value = true;
+
+      console.log("单片购买订单创建成功:", response.out_trade_no);
+    } else {
+      console.error("单片购买失败:", response.msg);
+      showError(`购买失败: ${response.msg || "未知错误"}`);
+    }
+  } catch (error) {
+    console.error("单片购买异常:", error);
+    showError("购买失败,请重试");
+  }
+};
+
+// 检查用户是否已购买当前视频
+const checkVideoPurchaseStatus = async () => {
+  if (!videoInfo.value.id || videoInfo.value.id === "unknown") {
+    return;
+  }
+
+  try {
+    const response = await checkSinglePurchase(videoInfo.value.id);
+    isSinglePurchased.value = response === true;
+  } catch (error) {
+    console.error("检查视频购买状态失败:", error);
+    isSinglePurchased.value = false;
+  }
 };
 };
 
 
 // 加载视频信息
 // 加载视频信息
 const loadVideoInfo = async () => {
 const loadVideoInfo = async () => {
   const vipLevel = userStore.getVipLevel();
   const vipLevel = userStore.getVipLevel();
 
 
+  // 获取视频ID(无论什么用户类型都使用相同的获取方式)
+  const videoId = route.params.id || route.query.id;
+
   // 如果是guest或free用户,从query参数获取视频信息
   // 如果是guest或free用户,从query参数获取视频信息
   if (vipLevel === VipLevel.GUEST || vipLevel === VipLevel.FREE) {
   if (vipLevel === VipLevel.GUEST || vipLevel === VipLevel.FREE) {
     const { cover, m3u8, name, duration, view, like } = route.query;
     const { cover, m3u8, name, duration, view, like } = route.query;
 
 
     if (cover && m3u8) {
     if (cover && m3u8) {
       videoInfo.value = {
       videoInfo.value = {
-        id: "trial",
+        id: videoId || "unknown",
         name: name || "试看视频",
         name: name || "试看视频",
         cover: cover as string,
         cover: cover as string,
         m3u8: m3u8 as string,
         m3u8: m3u8 as string,
@@ -494,13 +1012,12 @@ const loadVideoInfo = async () => {
 
 
       // 设置页面标题
       // 设置页面标题
       document.title = `${videoInfo.value.name} - 试看`;
       document.title = `${videoInfo.value.name} - 试看`;
+
       return;
       return;
     }
     }
   }
   }
 
 
-  // 其他情况,从路由参数获取ID
-  const videoId = route.params.id || route.query.id;
-
+  // 其他情况,通过API获取视频详情
   if (videoId) {
   if (videoId) {
     try {
     try {
       // 通过API获取视频详情
       // 通过API获取视频详情
@@ -620,19 +1137,39 @@ watch(
   () => route.params.id,
   () => route.params.id,
   async (newId, oldId) => {
   async (newId, oldId) => {
     if (newId && newId !== oldId) {
     if (newId && newId !== oldId) {
+      // 重置购买状态
+      isSinglePurchased.value = false;
       await loadVideoInfo();
       await loadVideoInfo();
       await loadRelatedVideos();
       await loadRelatedVideos();
+
+      // 检查购买状态(首页点击进入视频时的检查)
+      const vipLevel = userStore.getVipLevel();
+      if (
+        vipLevel !== VipLevel.GUEST &&
+        videoInfo.value.id &&
+        videoInfo.value.id !== "unknown"
+      ) {
+        await checkVideoPurchaseStatus();
+      }
+    } else if (oldId === undefined && newId) {
+      await loadVideoInfo();
+      await loadRelatedVideos();
+
+      const vipLevel = userStore.getVipLevel();
+      if (
+        vipLevel !== VipLevel.GUEST &&
+        videoInfo.value.id &&
+        videoInfo.value.id !== "unknown"
+      ) {
+        await checkVideoPurchaseStatus();
+      }
     }
     }
   },
   },
   { immediate: true }
   { immediate: true }
 );
 );
 
 
 onMounted(async () => {
 onMounted(async () => {
-  // 如果路由参数监听没有触发,则在这里加载
-  if (!route.params.id) {
-    await loadVideoInfo();
-    await loadRelatedVideos();
-  }
+  // 路由监听器已经在处理所有情况,这里不需要额外逻辑
 });
 });
 
 
 onUnmounted(() => {
 onUnmounted(() => {