Browse Source

新增用户信息修改和密码重置功能,更新相关弹窗和表单逻辑,优化用户体验。同时,调整用户状态计算属性以提升代码可读性和维护性。

wuyi 2 tháng trước cách đây
mục cha
commit
95f0a68110
3 tập tin đã thay đổi với 529 bổ sung68 xóa
  1. 20 0
      src/services/api.ts
  2. 332 40
      src/views/Account.vue
  3. 177 28
      src/views/VideoPlayer.vue

+ 20 - 0
src/services/api.ts

@@ -203,6 +203,26 @@ export const getPriceConfig = async (): Promise<any> => {
   return res.data;
 };
 
+// 更新用户信息(用户名和邮箱)
+export const updateProfile = async (
+  name: string,
+  email?: string
+): Promise<any> => {
+  const res = await api.put("/member/profile", {
+    name,
+    email: email || null,
+  });
+  return res.data;
+};
+
+// 重置密码
+export const resetPassword = async (password: string): Promise<any> => {
+  const res = await api.post("/member/reset-password", {
+    password,
+  });
+  return res.data;
+};
+
 /**
  * ===================== 视频相关接口 =====================
  */

+ 332 - 40
src/views/Account.vue

@@ -2,24 +2,26 @@
 import { useUserStore } from "@/store/user";
 import { usePriceStore } from "@/store/price";
 import { computed, ref, onMounted, watch } from "vue";
-import { upgradeGuest, purchaseMember, userQueryOrder } from "@/services/api";
+import {
+  upgradeGuest,
+  purchaseMember,
+  userQueryOrder,
+  updateProfile,
+  resetPassword,
+} from "@/services/api";
 import { vipLevelToText, VipLevel } from "@/types/vip";
 
 const userStore = useUserStore();
 const priceStore = usePriceStore();
-const isLoggedIn = computed(() => !!userStore.token);
-const isGuest = computed(() => {
-  return userStore.userInfo?.vipLevel === "guest";
-});
-const isVip = computed(() => {
-  const level = userStore.userInfo?.vipLevel;
-  return level && level !== VipLevel.GUEST && level !== VipLevel.FREE;
-});
+
+const isLoginUser = computed(() => userStore.isLoginUser);
+const isVipUser = computed(() => userStore.isVipUser);
+const isGuestUser = computed(() => userStore.isGuestUser);
 
 // 是否显示VIP到期时间
 const shouldShowExpireTime = computed(() => {
   return (
-    isVip.value &&
+    isVipUser.value &&
     userStore.userInfo?.vipLevel !== VipLevel.LIFETIME &&
     userStore.userInfo?.vipExpireTime
   );
@@ -31,6 +33,8 @@ const showMembershipDialog = ref(false);
 const showPaymentWaitingDialog = ref(false);
 const showErrorDialog = ref(false);
 const showSuccessDialog = ref(false);
+const showEditUserDialog = ref(false);
+const showResetPasswordDialog = ref(false);
 const errorMessage = ref("");
 const successMessage = ref("");
 const currentOrderNo = ref("");
@@ -38,8 +42,20 @@ const upgradeForm = ref({
   password: null,
   email: null,
 });
+const editUserForm = ref({
+  name: "",
+  email: "",
+});
+const resetPasswordForm = ref({
+  password: "",
+  confirmPassword: "",
+});
 const isLoading = ref(false);
 const isPaymentLoading = ref(false);
+const isEditUserLoading = ref(false);
+const isResetPasswordLoading = ref(false);
+const editUserError = ref("");
+const resetPasswordError = ref("");
 
 // 使用动态价格配置
 const membershipPlans = computed(() => priceStore.getMembershipPlans);
@@ -55,6 +71,120 @@ const showLoginDialog = () => {
   emit("show-login");
 };
 
+// 显示会员购买弹窗
+const showMembership = async () => {
+  // 确保价格配置已加载
+  if (!priceStore.isPriceConfigLoaded) {
+    try {
+      await priceStore.fetchPriceConfig();
+    } catch (error) {
+      console.error("价格配置加载失败:", error);
+      showError("价格配置加载失败,请重试");
+      return;
+    }
+  }
+  showMembershipDialog.value = true;
+};
+
+// 显示修改用户信息弹窗
+const showEditUser = () => {
+  // 清除错误信息
+  editUserError.value = "";
+  // 初始化表单数据
+  editUserForm.value = {
+    name: userStore.userInfo?.name || "",
+    email: userStore.userInfo?.email || "",
+  };
+  showEditUserDialog.value = true;
+};
+
+// 显示重置密码弹窗
+const showResetPassword = () => {
+  // 清除错误信息
+  resetPasswordError.value = "";
+  // 重置表单数据
+  resetPasswordForm.value = {
+    password: "",
+    confirmPassword: "",
+  };
+  showResetPasswordDialog.value = true;
+};
+
+// 处理重置密码
+const handleResetPassword = async () => {
+  // 清除之前的错误信息
+  resetPasswordError.value = "";
+
+  if (!resetPasswordForm.value.password.trim()) {
+    resetPasswordError.value = "请输入新密码";
+    return;
+  }
+
+  if (resetPasswordForm.value.password.length < 6) {
+    resetPasswordError.value = "密码长度至少6位";
+    return;
+  }
+
+  if (
+    resetPasswordForm.value.password !== resetPasswordForm.value.confirmPassword
+  ) {
+    resetPasswordError.value = "两次输入的密码不一致";
+    return;
+  }
+
+  isResetPasswordLoading.value = true;
+  try {
+    const response = await resetPassword(resetPasswordForm.value.password);
+
+    if (response.message) {
+      showResetPasswordDialog.value = false;
+      showSuccess(response.message);
+    }
+  } catch (error: any) {
+    console.error("重置密码失败", error);
+    resetPasswordError.value = error.message || "重置密码失败,请重试";
+  } finally {
+    isResetPasswordLoading.value = false;
+  }
+};
+
+// 处理修改用户信息
+const handleEditUser = async () => {
+  // 清除之前的错误信息
+  editUserError.value = "";
+
+  if (!editUserForm.value.name.trim()) {
+    editUserError.value = "请输入用户名";
+    return;
+  }
+
+  isEditUserLoading.value = true;
+  try {
+    const response = await updateProfile(
+      editUserForm.value.name,
+      editUserForm.value.email || undefined
+    );
+
+    if (response.message) {
+      // 更新本地用户信息
+      const updatedUserInfo = {
+        ...userStore.userInfo,
+        name: editUserForm.value.name,
+        email: editUserForm.value.email,
+      };
+      userStore.setUserInfo(updatedUserInfo);
+
+      showEditUserDialog.value = false;
+      showSuccess(response.message);
+    }
+  } catch (error: any) {
+    console.error("修改用户信息失败", error);
+    editUserError.value = error.message || "修改失败,请重试";
+  } finally {
+    isEditUserLoading.value = false;
+  }
+};
+
 // 格式化VIP到期时间
 const formatVipExpireTime = (expireTime: string | null | undefined): string => {
   if (!expireTime) return "";
@@ -83,21 +213,6 @@ const showSuccess = (message: string) => {
   showSuccessDialog.value = true;
 };
 
-// Safari兼容性处理:打开支付页面
-const openPaymentPage = (url: string) => {
-  // 检测是否为Safari浏览器
-  const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
-
-  if (isSafari) {
-    // Safari浏览器
-    setTimeout(() => window.open(url, "_blank"));
-    // window.location.href = url;
-  } else {
-    // 其他浏览器:正常使用window.open
-    window.open(url, "_blank");
-  }
-};
-
 const handleMembershipPurchase = async () => {
   if (!selectedPlan.value) {
     alert("请选择会员套餐");
@@ -236,7 +351,7 @@ const handleQueryOrder = async () => {
 
 // 加载价格配置的函数
 const loadPriceConfig = async () => {
-  if (isLoggedIn.value && !priceStore.isPriceConfigLoaded) {
+  if (isLoginUser.value && !priceStore.isPriceConfigLoaded) {
     try {
       await priceStore.fetchPriceConfig();
     } catch (error) {
@@ -247,7 +362,7 @@ const loadPriceConfig = async () => {
 
 // 监听登录状态变化
 watch(
-  isLoggedIn,
+  () => isLoginUser,
   (newValue) => {
     if (newValue) {
       loadPriceConfig();
@@ -269,7 +384,7 @@ onMounted(async () => {
       <!-- 个人信息区域 -->
       <div class="flex items-center gap-3 mb-4">
         <div
-          v-if="isLoggedIn"
+          v-if="isLoginUser"
           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"
         >
           {{ userStore.userInfo?.name?.[0] || "用" }}
@@ -292,14 +407,14 @@ onMounted(async () => {
         </div>
         <div class="flex-1 min-w-0">
           <p class="text-sm text-white/60">
-            {{ isLoggedIn ? "欢迎回来" : "未登录" }}
+            {{ isLoginUser ? "欢迎回来" : "未登录" }}
           </p>
           <h2 class="text-base font-semibold text-white/90 truncate">
             {{
-              isLoggedIn ? userStore.userInfo?.name || "用户" : "点击登录账号"
+              isLoginUser ? userStore.userInfo?.name || "用户" : "点击登录账号"
             }}
             <span
-              v-if="isVip"
+              v-if="isVipUser"
               class="ml-2 px-2 py-0.5 rounded bg-yellow-400/90 text-xs text-yellow-900 font-semibold align-middle"
               style="vertical-align: middle"
             >
@@ -316,21 +431,19 @@ onMounted(async () => {
       </div>
 
       <!-- 按钮区域 -->
-      <div v-if="isLoggedIn" class="flex gap-2">
+      <div v-if="isLoginUser" class="flex gap-2">
         <button
-          v-if="isGuest"
+          v-if="isGuestUser"
           @click="showUpgradeDialog = true"
           class="px-3 py-1.5 rounded-lg bg-blue-500 text-white text-sm font-medium hover:bg-blue-600 transition"
         >
           成为正式用户
         </button>
         <button
-          @click="showMembershipDialog = true"
+          @click="showMembership"
           class="px-3 py-1.5 rounded-lg bg-brand text-slate-900 text-sm font-medium"
-          :disabled="isGuest"
-          :class="{ 'opacity-50 cursor-not-allowed': isGuest }"
         >
-          {{ isVip ? "续订会员" : "开通会员" }}
+          {{ isVipUser ? "续订会员" : "开通会员" }}
         </button>
       </div>
       <button
@@ -343,9 +456,48 @@ onMounted(async () => {
     </div>
 
     <div
-      v-if="isLoggedIn"
+      v-if="isLoginUser"
       class="rounded-2xl overflow-hidden border border-white/10 divide-y divide-white/10"
     >
+      <button
+        v-if="!isGuestUser"
+        @click="showEditUser"
+        class="row text-white/80"
+      >
+        <span>修改用户信息</span>
+        <svg
+          viewBox="0 0 24 24"
+          width="18"
+          height="18"
+          fill="none"
+          stroke="currentColor"
+          stroke-width="2"
+        >
+          <path
+            d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
+          />
+          <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
+        </svg>
+      </button>
+      <button
+        v-if="!isGuestUser"
+        @click="showResetPassword"
+        class="row text-white/80"
+      >
+        <span>重置密码</span>
+        <svg
+          viewBox="0 0 24 24"
+          width="18"
+          height="18"
+          fill="none"
+          stroke="currentColor"
+          stroke-width="2"
+        >
+          <path
+            d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"
+          />
+        </svg>
+      </button>
       <button @click="handleLogout" class="row text-red-300/90">
         <span>退出登录</span>
         <svg
@@ -571,10 +723,150 @@ onMounted(async () => {
       </div>
     </div>
 
+    <!-- 修改用户信息弹窗 -->
+    <div
+      v-if="showEditUserDialog"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+      @click.self="showEditUserDialog = false"
+    >
+      <div
+        class="bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-md mx-4"
+      >
+        <h3 class="text-xl font-semibold text-white/90 mb-4">修改用户信息</h3>
+
+        <form @submit.prevent="handleEditUser" class="space-y-4">
+          <div>
+            <label class="block text-sm font-medium text-white/70 mb-1">
+              用户名 <span class="text-red-400">*</span>
+            </label>
+            <input
+              v-model="editUserForm.name"
+              type="text"
+              required
+              class="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/40 focus:ring-2 focus:ring-brand focus:border-transparent"
+              placeholder="请输入用户名"
+            />
+          </div>
+
+          <div>
+            <label class="block text-sm font-medium text-white/70 mb-1">
+              邮箱
+            </label>
+            <input
+              v-model="editUserForm.email"
+              type="email"
+              class="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/40 focus:ring-2 focus:ring-brand focus:border-transparent"
+              placeholder="请输入邮箱"
+            />
+          </div>
+
+          <!-- 错误信息显示 -->
+          <div
+            v-if="editUserError"
+            class="text-red-400 text-sm text-center py-2"
+          >
+            {{ editUserError }}
+          </div>
+
+          <div class="flex gap-3 pt-4">
+            <button
+              type="button"
+              @click="showEditUserDialog = false"
+              class="flex-1 px-4 py-2 border border-white/20 text-white/70 rounded-lg hover:bg-white/5 transition"
+            >
+              取消
+            </button>
+            <button
+              type="submit"
+              :disabled="isEditUserLoading"
+              class="flex-1 px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition disabled:opacity-50"
+            >
+              {{ isEditUserLoading ? "处理中..." : "确认修改" }}
+            </button>
+          </div>
+        </form>
+      </div>
+    </div>
+
+    <!-- 重置密码弹窗 -->
+    <div
+      v-if="showResetPasswordDialog"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+      @click.self="showResetPasswordDialog = false"
+    >
+      <div
+        class="bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-md mx-4"
+      >
+        <h3 class="text-xl font-semibold text-white/90 mb-4">重置密码</h3>
+
+        <div
+          class="bg-yellow-500/10 border border-yellow-500/20 rounded-lg p-4 mb-6"
+        >
+          <p class="text-sm text-yellow-200">
+            重置密码后,您需要使用新密码重新登录
+          </p>
+        </div>
+
+        <form @submit.prevent="handleResetPassword" class="space-y-4">
+          <div>
+            <label class="block text-sm font-medium text-white/70 mb-1">
+              新密码 <span class="text-red-400">*</span>
+            </label>
+            <input
+              v-model="resetPasswordForm.password"
+              type="password"
+              required
+              class="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/40 focus:ring-2 focus:ring-brand focus:border-transparent"
+              placeholder="请输入新密码"
+            />
+            <p class="mt-1 text-xs text-white/50">至少6位字符</p>
+          </div>
+
+          <div>
+            <label class="block text-sm font-medium text-white/70 mb-1">
+              确认密码 <span class="text-red-400">*</span>
+            </label>
+            <input
+              v-model="resetPasswordForm.confirmPassword"
+              type="password"
+              required
+              class="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/40 focus:ring-2 focus:ring-brand focus:border-transparent"
+              placeholder="请再次输入新密码"
+            />
+          </div>
+
+          <!-- 错误信息显示 -->
+          <div
+            v-if="resetPasswordError"
+            class="text-red-400 text-sm text-center py-2"
+          >
+            {{ resetPasswordError }}
+          </div>
+
+          <div class="flex gap-3 pt-4">
+            <button
+              type="button"
+              @click="showResetPasswordDialog = false"
+              class="flex-1 px-4 py-2 border border-white/20 text-white/70 rounded-lg hover:bg-white/5 transition"
+            >
+              取消
+            </button>
+            <button
+              type="submit"
+              :disabled="isResetPasswordLoading"
+              class="flex-1 px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition disabled:opacity-50"
+            >
+              {{ isResetPasswordLoading ? "处理中..." : "确认重置" }}
+            </button>
+          </div>
+        </form>
+      </div>
+    </div>
+
     <!-- 错误提示弹窗 -->
     <div
       v-if="showErrorDialog"
-      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-60"
       @click.self="showErrorDialog = false"
     >
       <div
@@ -620,7 +912,7 @@ onMounted(async () => {
     <!-- 成功提示弹窗 -->
     <div
       v-if="showSuccessDialog"
-      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-60"
       @click.self="showSuccessDialog = false"
     >
       <div

+ 177 - 28
src/views/VideoPlayer.vue

@@ -292,7 +292,7 @@
         <!-- 操作按钮 -->
         <div class="flex gap-3">
           <button
-            @click="showPaymentWaitingDialog = false"
+            @click="cancelMembershipPayment"
             class="flex-1 px-4 py-2 border border-white/20 text-white/70 rounded-lg hover:bg-white/5 transition"
           >
             取消支付
@@ -374,7 +374,7 @@
         <!-- 操作按钮 -->
         <div class="flex gap-3">
           <button
-            @click="showSinglePaymentWaitingDialog = false"
+            @click="cancelSinglePayment"
             class="flex-1 px-4 py-2 border border-white/20 text-white/70 rounded-lg hover:bg-white/5 transition"
           >
             取消支付
@@ -765,6 +765,9 @@ const handleMembershipPurchase = async () => {
       // 显示支付等待弹窗
       showPaymentWaitingDialog.value = true;
 
+      // 启动定时查询
+      startMembershipQueryTimer();
+
       // 重置选择
       selectedPlan.value = "";
     } else {
@@ -818,6 +821,9 @@ const handleSinglePurchase = async () => {
 
       // 显示支付等待弹窗
       showSinglePaymentWaitingDialog.value = true;
+
+      // 启动定时查询
+      startSingleQueryTimer();
     } else {
       console.error("单片购买失败:", response.msg);
       showError(`购买失败: ${response.msg || "未知错误"}`);
@@ -839,7 +845,8 @@ const handleMembershipQueryOrder = async () => {
     const response = await userQueryOrder(currentOrderNo.value);
 
     if (response.status === 1) {
-      // 支付成功
+      // 支付成功,停止定时查询
+      stopMembershipQueryTimer();
       await userStore.sync();
       showPaymentWaitingDialog.value = false;
       showSuccess("会员购买成功!");
@@ -867,7 +874,8 @@ const handleSingleQueryOrder = async () => {
     );
 
     if (response.status === 1) {
-      // 支付成功
+      // 支付成功,停止定时查询
+      stopSingleQueryTimer();
       await userStore.sync();
       showSinglePaymentWaitingDialog.value = false;
       showSuccess("视频购买成功!");
@@ -883,6 +891,122 @@ const handleSingleQueryOrder = async () => {
   }
 };
 
+// 定时支付查询相关
+let membershipQueryTimer: NodeJS.Timeout | null = null;
+let singleQueryTimer: NodeJS.Timeout | null = null;
+const maxQueryCount = 50; // 最大查询次数
+const queryInterval = 5000; // 查询间隔5秒
+
+// 会员购买定时查询
+const startMembershipQueryTimer = () => {
+  let queryCount = 0;
+
+  const query = async () => {
+    if (queryCount >= maxQueryCount) {
+      stopMembershipQueryTimer();
+      return;
+    }
+
+    queryCount++;
+
+    try {
+      const response = await userQueryOrder(currentOrderNo.value);
+
+      if (response.status === 1) {
+        // 支付成功
+        stopMembershipQueryTimer();
+        await userStore.sync();
+        showPaymentWaitingDialog.value = false;
+        showSuccess("会员购买成功!");
+        currentOrderNo.value = "";
+        await checkVideoPurchaseStatus();
+      }
+    } catch (error) {
+      console.error("定时查询会员订单状态失败:", error);
+    }
+
+    // 继续下一次查询
+    if (queryCount < maxQueryCount) {
+      membershipQueryTimer = setTimeout(query, queryInterval);
+    }
+  };
+
+  // 开始第一次查询
+  membershipQueryTimer = setTimeout(query, queryInterval);
+};
+
+// 停止会员购买定时查询
+const stopMembershipQueryTimer = () => {
+  if (membershipQueryTimer) {
+    clearTimeout(membershipQueryTimer);
+    membershipQueryTimer = null;
+  }
+};
+
+// 单片购买定时查询
+const startSingleQueryTimer = () => {
+  let queryCount = 0;
+
+  const query = async () => {
+    if (queryCount >= maxQueryCount) {
+      stopSingleQueryTimer();
+      return;
+    }
+
+    queryCount++;
+
+    try {
+      const response = await userQueryOrder(
+        singleCurrentOrderNo.value,
+        videoInfo.value.id
+      );
+
+      if (response.status === 1) {
+        // 支付成功
+        stopSingleQueryTimer();
+        await userStore.sync();
+        showSinglePaymentWaitingDialog.value = false;
+        showSuccess("视频购买成功!");
+        singleCurrentOrderNo.value = "";
+        // 重新检查购买状态
+        await checkVideoPurchaseStatus();
+      }
+    } catch (error) {
+      console.error("定时查询单片购买订单状态失败:", error);
+    }
+
+    // 继续下一次查询
+    if (queryCount < maxQueryCount) {
+      singleQueryTimer = setTimeout(query, queryInterval);
+    }
+  };
+
+  // 开始第一次查询
+  singleQueryTimer = setTimeout(query, queryInterval);
+};
+
+// 停止单片购买定时查询
+const stopSingleQueryTimer = () => {
+  if (singleQueryTimer) {
+    clearTimeout(singleQueryTimer);
+    singleQueryTimer = null;
+  }
+};
+
+// 取消会员支付
+const cancelMembershipPayment = () => {
+  stopMembershipQueryTimer();
+  showPaymentWaitingDialog.value = false;
+  currentOrderNo.value = "";
+};
+
+// 取消单片支付
+const cancelSinglePayment = () => {
+  stopSingleQueryTimer();
+  showSinglePaymentWaitingDialog.value = false;
+  singleCurrentOrderNo.value = "";
+};
+
 // 支付等待相关
 const showPaymentWaitingDialog = ref(false);
 const currentOrderNo = ref("");
@@ -1103,18 +1227,9 @@ const checkVideoPurchaseStatus = async () => {
   }
 
   try {
-    const response = await checkSinglePurchase(videoInfo.value.id);
-    console.log("checkVideoPurchaseStatus", response);
-    const wasPurchased = response.status === 1 || response === true;
-    isSinglePurchased.value = wasPurchased;
+    await loadVideoInfo();
 
-    // 如果用户已购买但还没有视频源,则调用详情接口获取视频源
-    if (
-      wasPurchased &&
-      (!videoInfo.value.m3u8 || videoInfo.value.m3u8 === "")
-    ) {
-      await loadVideoInfo();
-    }
+    console.log("showPurchasePrompt:", showPurchasePrompt.value);
   } catch (error) {
     console.error("检查视频购买状态失败:", error);
     isSinglePurchased.value = false;
@@ -1140,20 +1255,50 @@ const loadVideoInfo = async () => {
       taginfo: [],
     };
   } else if (!isVipUser) {
-    // 未登录或guest或free用户,从query参数获取视频信息
+    // 未登录或guest或free用户
     console.log("isNotVipUser");
-    const { name, cover, m3u8, duration, view, like } = route.query;
-    videoInfo.value = {
-      id: videoId,
-      name: name as string,
-      cover: cover as string,
-      m3u8: "",
-      duration: parseInt(duration as string) || 0,
-      view: parseInt(view as string) || 0,
-      like: parseInt(like as string) || 0,
-      time: 0,
-      taginfo: [],
-    };
+    const response = await checkSinglePurchase(videoId as string);
+    console.log("checkVideoPurchaseStatus", response);
+    const wasPurchased = response.status === 1 || response === true;
+    isSinglePurchased.value = wasPurchased;
+    if (response) {
+      // 关闭弹窗
+      showSinglePurchaseModal.value = false;
+      showMembershipPurchaseModal.value = false;
+      showPurchasePrompt.value = false;
+      const response = await getVideoDetail(device, String(videoId));
+      if (response.status === 0 && response.data) {
+        const data = response.data;
+        videoInfo.value = {
+          id: videoId,
+          name: data.name || "",
+          cover: data.cover || "",
+          m3u8: data.m3u8 || "",
+          duration: data.duration || 0,
+          view: data.view || 0,
+          like: data.like || 0,
+          time: data.time || 0,
+          taginfo: data.tags
+            ? data.tags
+                .split(",")
+                .map((tag: string) => ({ name: tag.trim(), hash: tag.trim() }))
+            : [],
+        };
+      }
+    } else {
+      const { name, cover, m3u8, duration, view, like } = route.query;
+      videoInfo.value = {
+        id: videoId,
+        name: name as string,
+        cover: cover as string,
+        m3u8: "",
+        duration: parseInt(duration as string) || 0,
+        view: parseInt(view as string) || 0,
+        like: parseInt(like as string) || 0,
+        time: 0,
+        taginfo: [],
+      };
+    }
   } else if (isVipUser) {
     // 付费用户,从详情接口获取视频信息
     const response = await getVideoDetail(device, String(videoId));
@@ -1290,6 +1435,10 @@ onUnmounted(() => {
   if (videoProcessorRef.value) {
     videoProcessorRef.value.stopVideo();
   }
+
+  // 清理定时器
+  stopMembershipQueryTimer();
+  stopSingleQueryTimer();
 });
 </script>