|
|
@@ -1,31 +1,11 @@
|
|
|
<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="relative">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
<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
|
|
|
class="w-5 h-5"
|
|
|
@@ -37,26 +17,64 @@
|
|
|
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"
|
|
|
+ d="M15 19l-7-7 7-7"
|
|
|
/>
|
|
|
</svg>
|
|
|
+ <span class="text-sm">返回</span>
|
|
|
</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 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>
|
|
|
|
|
|
<!-- 视频播放器区域 -->
|
|
|
@@ -91,7 +109,7 @@
|
|
|
|
|
|
<!-- 试看结束遮罩 -->
|
|
|
<div
|
|
|
- v-if="showPurchaseModal"
|
|
|
+ v-if="showTrialEndModal && !isSinglePurchased"
|
|
|
class="absolute inset-0 bg-black/80 flex items-center justify-center z-50"
|
|
|
>
|
|
|
<div
|
|
|
@@ -106,18 +124,18 @@
|
|
|
<div class="space-y-3">
|
|
|
<button
|
|
|
@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
|
|
|
@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
|
|
|
- @click="showPurchaseModal = false"
|
|
|
+ @click="showTrialEndModal = false"
|
|
|
class="w-full text-white/60 hover:text-white/80 py-2 text-sm transition"
|
|
|
>
|
|
|
取消
|
|
|
@@ -128,6 +146,311 @@
|
|
|
</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">
|
|
|
<!-- 视频标题和基本信息 -->
|
|
|
@@ -274,7 +597,14 @@
|
|
|
<script setup lang="ts">
|
|
|
import { ref, onMounted, onUnmounted, computed, watch } from "vue";
|
|
|
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 { useUserStore } from "@/store/user";
|
|
|
import { VipLevel, canWatchFullVideo, getTrialDuration } from "@/types/vip";
|
|
|
@@ -308,9 +638,54 @@ const relatedVideos = ref<any[]>([]);
|
|
|
|
|
|
// 状态管理
|
|
|
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 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 hex = "0123456789ABCDEF";
|
|
|
@@ -348,8 +723,8 @@ const onVideoProcessorRetry = () => {
|
|
|
};
|
|
|
|
|
|
const onVideoPlay = () => {
|
|
|
- // 如果是guest或free用户且还没有开始试看,则开始试看
|
|
|
- if (isGuestOrFree.value && !isTrialMode.value) {
|
|
|
+ // 如果是guest或free用户且还没有开始试看,且未购买当前视频,则开始试看
|
|
|
+ if (isGuestOrFree.value && !isTrialMode.value && !isSinglePurchased.value) {
|
|
|
startTrial();
|
|
|
}
|
|
|
};
|
|
|
@@ -367,7 +742,7 @@ const onVideoTimeUpdate = () => {
|
|
|
if (videoProcessorRef.value) {
|
|
|
videoProcessorRef.value.stopHlsLoading();
|
|
|
}
|
|
|
- showPurchaseModal.value = true;
|
|
|
+ showTrialEndModal.value = true;
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
@@ -400,10 +775,6 @@ const formatNumber = (num: string | number): string => {
|
|
|
return n.toString();
|
|
|
};
|
|
|
|
|
|
-// 图片加载错误处理已移至 VideoProcessor 组件
|
|
|
-
|
|
|
-// 视频播放器事件处理已移至 VideoProcessor 组件
|
|
|
-
|
|
|
// 返回上一页
|
|
|
const goBack = () => {
|
|
|
router.back();
|
|
|
@@ -459,29 +830,176 @@ const startTrial = () => {
|
|
|
|
|
|
// 购买会员
|
|
|
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 vipLevel = userStore.getVipLevel();
|
|
|
|
|
|
+ // 获取视频ID(无论什么用户类型都使用相同的获取方式)
|
|
|
+ const videoId = route.params.id || route.query.id;
|
|
|
+
|
|
|
// 如果是guest或free用户,从query参数获取视频信息
|
|
|
if (vipLevel === VipLevel.GUEST || vipLevel === VipLevel.FREE) {
|
|
|
const { cover, m3u8, name, duration, view, like } = route.query;
|
|
|
|
|
|
if (cover && m3u8) {
|
|
|
videoInfo.value = {
|
|
|
- id: "trial",
|
|
|
+ id: videoId || "unknown",
|
|
|
name: name || "试看视频",
|
|
|
cover: cover as string,
|
|
|
m3u8: m3u8 as string,
|
|
|
@@ -494,13 +1012,12 @@ const loadVideoInfo = async () => {
|
|
|
|
|
|
// 设置页面标题
|
|
|
document.title = `${videoInfo.value.name} - 试看`;
|
|
|
+
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 其他情况,从路由参数获取ID
|
|
|
- const videoId = route.params.id || route.query.id;
|
|
|
-
|
|
|
+ // 其他情况,通过API获取视频详情
|
|
|
if (videoId) {
|
|
|
try {
|
|
|
// 通过API获取视频详情
|
|
|
@@ -620,19 +1137,39 @@ watch(
|
|
|
() => route.params.id,
|
|
|
async (newId, oldId) => {
|
|
|
if (newId && newId !== oldId) {
|
|
|
+ // 重置购买状态
|
|
|
+ isSinglePurchased.value = false;
|
|
|
await loadVideoInfo();
|
|
|
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 }
|
|
|
);
|
|
|
|
|
|
onMounted(async () => {
|
|
|
- // 如果路由参数监听没有触发,则在这里加载
|
|
|
- if (!route.params.id) {
|
|
|
- await loadVideoInfo();
|
|
|
- await loadRelatedVideos();
|
|
|
- }
|
|
|
+ // 路由监听器已经在处理所有情况,这里不需要额外逻辑
|
|
|
});
|
|
|
|
|
|
onUnmounted(() => {
|