|
|
@@ -47,25 +47,51 @@
|
|
|
|
|
|
</div>
|
|
|
|
|
|
- <!-- 购买按钮区域 -->
|
|
|
- <div
|
|
|
- v-if="showPurchaseButtonArea"
|
|
|
- class="flex items-center gap-1 sm:gap-1.5"
|
|
|
- >
|
|
|
+ <!-- 购买按钮区域和反馈按钮 -->
|
|
|
+ <div class="flex items-center gap-1 sm:gap-1.5">
|
|
|
+ <!-- 反馈按钮(仅对付费用户显示) -->
|
|
|
<button
|
|
|
- v-if="!isVipUser"
|
|
|
- @click="handleMembershipClick"
|
|
|
- class="px-1.5 sm:px-2 py-0.5 sm:py-1 rounded-md bg-brand text-slate-900 text-2xs sm:text-xs font-medium hover:bg-brand/90 transition whitespace-nowrap min-w-fit"
|
|
|
+ v-if="isVipUser && videoInfo.id"
|
|
|
+ @click="showFeedbackDialog = true"
|
|
|
+ class="flex items-center gap-1 sm:gap-2 text-white/80 hover:text-white transition whitespace-nowrap"
|
|
|
+ title="反馈问题"
|
|
|
>
|
|
|
- 开通会员
|
|
|
+ <svg
|
|
|
+ class="w-4 h-4 sm:w-5 sm:h-5 flex-shrink-0"
|
|
|
+ fill="none"
|
|
|
+ stroke="currentColor"
|
|
|
+ viewBox="0 0 24 24"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ stroke-linecap="round"
|
|
|
+ stroke-linejoin="round"
|
|
|
+ stroke-width="2"
|
|
|
+ d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ <span class="text-xs sm:text-sm">反馈</span>
|
|
|
</button>
|
|
|
- <button
|
|
|
- v-if="!isVipUser"
|
|
|
- @click="handleSinglePurchaseClick"
|
|
|
- class="px-2 sm:px-3 py-1 sm:py-2 rounded-lg bg-blue-500 text-white text-2xs sm:text-sm font-semibold hover:bg-blue-600 transition shadow-lg hover:shadow-xl flash-animation glow-animation hover:animate-none whitespace-nowrap min-w-fit"
|
|
|
+
|
|
|
+ <!-- 购买按钮 -->
|
|
|
+ <div
|
|
|
+ v-if="showPurchaseButtonArea"
|
|
|
+ class="flex items-center gap-1 sm:gap-1.5"
|
|
|
>
|
|
|
- 单片购买
|
|
|
- </button>
|
|
|
+ <button
|
|
|
+ v-if="!isVipUser"
|
|
|
+ @click="handleMembershipClick"
|
|
|
+ class="px-1.5 sm:px-2 py-0.5 sm:py-1 rounded-md bg-brand text-slate-900 text-2xs sm:text-xs font-medium hover:bg-brand/90 transition whitespace-nowrap min-w-fit"
|
|
|
+ >
|
|
|
+ 开通会员
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ v-if="!isVipUser"
|
|
|
+ @click="handleSinglePurchaseClick"
|
|
|
+ class="px-2 sm:px-3 py-1 sm:py-2 rounded-lg bg-blue-500 text-white text-2xs sm:text-sm font-semibold hover:bg-blue-600 transition shadow-lg hover:shadow-xl flash-animation glow-animation hover:animate-none whitespace-nowrap min-w-fit"
|
|
|
+ >
|
|
|
+ 单片购买
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -840,6 +866,14 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- 视频反馈对话框 -->
|
|
|
+ <VideoFeedbackDialog
|
|
|
+ v-model="showFeedbackDialog"
|
|
|
+ :video-id="videoInfo.id"
|
|
|
+ @success="handleFeedbackSuccess"
|
|
|
+ @error="handleFeedbackError"
|
|
|
+ />
|
|
|
+
|
|
|
<!-- 视频信息区域 -->
|
|
|
<div class="space-y-6">
|
|
|
<!-- 视频标题和基本信息 -->
|
|
|
@@ -1097,6 +1131,7 @@ import {
|
|
|
} from "@/services/api";
|
|
|
import VideoJSPlayer from "@/components/VideoJSPlayer.vue";
|
|
|
import ShareDialog from "@/components/ShareDialog.vue";
|
|
|
+import VideoFeedbackDialog from "@/components/VideoFeedbackDialog.vue";
|
|
|
import { useUserStore } from "@/store/user";
|
|
|
import { usePriceStore } from "@/store/price";
|
|
|
import { handleShareLinkAccess } from "@/services/shareApi";
|
|
|
@@ -1197,6 +1232,9 @@ const showWechatCopyLinkDialog = ref(false);
|
|
|
const wechatCopyLinkUrl = ref("");
|
|
|
const isWechatEnv = computed(() => isWechat());
|
|
|
|
|
|
+// 视频反馈相关
|
|
|
+const showFeedbackDialog = ref(false);
|
|
|
+
|
|
|
// 用户信息是否已同步完成(用于避免初次刷新时过早进行购买判断)
|
|
|
const isUserSynced = ref(false);
|
|
|
|
|
|
@@ -1376,6 +1414,15 @@ const handleMembershipPurchase = async () => {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ // 在用户点击时立即预打开窗口(必须在同步代码中,iOS Safari要求)
|
|
|
+ // 必须在任何异步操作之前调用
|
|
|
+ // 非微信环境:打开空白页,后续直接跳转到支付链接
|
|
|
+ // 微信环境:打开支付中转页面
|
|
|
+ const loadingUrl = isWechat()
|
|
|
+ ? `${window.location.origin}/payment-redirect?url=about:blank`
|
|
|
+ : 'about:blank';
|
|
|
+ preOpenPaymentWindow(loadingUrl);
|
|
|
+
|
|
|
isPaymentLoading.value = true;
|
|
|
|
|
|
// 未登录用户先创建游客账户
|
|
|
@@ -1392,12 +1439,32 @@ const handleMembershipPurchase = async () => {
|
|
|
landingDomain || undefined
|
|
|
);
|
|
|
if (!guestResponse) {
|
|
|
+ // 如果失败,关闭预打开的窗口
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ preOpenedWindow = null;
|
|
|
+ }
|
|
|
showError("付费时创建账户失败,请重试");
|
|
|
+ isPaymentLoading.value = false;
|
|
|
return;
|
|
|
}
|
|
|
} catch (error) {
|
|
|
+ // 如果失败,关闭预打开的窗口
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ preOpenedWindow = null;
|
|
|
+ }
|
|
|
console.error("付费时创建账户失败:", error);
|
|
|
showError("付费时创建账户失败,请重试");
|
|
|
+ isPaymentLoading.value = false;
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
@@ -1416,8 +1483,10 @@ const handleMembershipPurchase = async () => {
|
|
|
// 关闭购买弹窗
|
|
|
showMembershipPurchaseModal.value = false;
|
|
|
|
|
|
- // 打开支付页面
|
|
|
- openPaymentPage(response.code_url);
|
|
|
+ // 打开支付页面(尝试使用预打开的窗口)
|
|
|
+ // 非微信环境:直接使用预打开窗口打开支付链接
|
|
|
+ // 微信环境:跳转到支付中转页面
|
|
|
+ openPaymentPage(response.code_url, true);
|
|
|
|
|
|
// 显示支付等待弹窗
|
|
|
showPaymentWaitingDialog.value = true;
|
|
|
@@ -1428,9 +1497,27 @@ const handleMembershipPurchase = async () => {
|
|
|
// 重置选择
|
|
|
selectedPlan.value = "";
|
|
|
} else {
|
|
|
+ // 如果失败,关闭预打开的窗口
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ preOpenedWindow = null;
|
|
|
+ }
|
|
|
showError(`支付失败: ${response.msg || "未知错误"}`);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
+ // 如果失败,关闭预打开的窗口
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ preOpenedWindow = null;
|
|
|
+ }
|
|
|
console.error("购买会员失败", error);
|
|
|
showError("购买失败,请重试");
|
|
|
} finally {
|
|
|
@@ -1440,8 +1527,26 @@ const handleMembershipPurchase = async () => {
|
|
|
|
|
|
// 处理单片购买
|
|
|
const handleSinglePurchase = async () => {
|
|
|
+ // 在用户点击时立即预打开窗口(必须在同步代码中,iOS Safari要求)
|
|
|
+ // 必须在任何异步操作之前调用
|
|
|
+ // 非微信环境:打开空白页,后续直接跳转到支付链接
|
|
|
+ // 微信环境:打开支付中转页面
|
|
|
+ const loadingUrl = isWechat()
|
|
|
+ ? `${window.location.origin}/payment-redirect?url=about:blank`
|
|
|
+ : 'about:blank';
|
|
|
+ preOpenPaymentWindow(loadingUrl);
|
|
|
+
|
|
|
try {
|
|
|
if (!videoInfo.value.id || videoInfo.value.id === "unknown") {
|
|
|
+ // 如果失败,关闭预打开的窗口
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ preOpenedWindow = null;
|
|
|
+ }
|
|
|
showError("未找到视频资源");
|
|
|
return;
|
|
|
}
|
|
|
@@ -1460,10 +1565,28 @@ const handleSinglePurchase = async () => {
|
|
|
landingDomain || undefined
|
|
|
);
|
|
|
if (!guestResponse) {
|
|
|
+ // 如果失败,关闭预打开的窗口
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ preOpenedWindow = null;
|
|
|
+ }
|
|
|
showError("付费时创建账户失败,请重试");
|
|
|
return;
|
|
|
}
|
|
|
} catch (error) {
|
|
|
+ // 如果失败,关闭预打开的窗口
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ preOpenedWindow = null;
|
|
|
+ }
|
|
|
console.error("付费时创建账户失败:", error);
|
|
|
showError("付费时创建账户失败,请重试");
|
|
|
return;
|
|
|
@@ -1483,8 +1606,10 @@ const handleSinglePurchase = async () => {
|
|
|
// 关闭弹窗
|
|
|
showSinglePurchaseModal.value = false;
|
|
|
|
|
|
- // 打开支付页面
|
|
|
- openPaymentPage(response.code_url);
|
|
|
+ // 打开支付页面(尝试使用预打开的窗口)
|
|
|
+ // 非微信环境:直接使用预打开窗口打开支付链接
|
|
|
+ // 微信环境:跳转到支付中转页面
|
|
|
+ openPaymentPage(response.code_url, true);
|
|
|
|
|
|
// 显示支付等待弹窗
|
|
|
showSinglePaymentWaitingDialog.value = true;
|
|
|
@@ -1492,10 +1617,28 @@ const handleSinglePurchase = async () => {
|
|
|
// 启动定时查询
|
|
|
startSingleQueryTimer();
|
|
|
} else {
|
|
|
+ // 如果失败,关闭预打开的窗口
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ preOpenedWindow = null;
|
|
|
+ }
|
|
|
console.error("单片购买失败:", response.msg);
|
|
|
showError(`购买失败: ${response.msg || "未知错误"}`);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
+ // 如果失败,关闭预打开的窗口
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ preOpenedWindow = null;
|
|
|
+ }
|
|
|
console.error("单片购买异常:", error);
|
|
|
showError("购买失败,请重试");
|
|
|
}
|
|
|
@@ -1507,6 +1650,16 @@ const handleShare = () => {
|
|
|
showShareLinkDialog.value = true;
|
|
|
};
|
|
|
|
|
|
+// 处理反馈成功
|
|
|
+const handleFeedbackSuccess = (message: string) => {
|
|
|
+ showSuccess(message);
|
|
|
+};
|
|
|
+
|
|
|
+// 处理反馈错误
|
|
|
+const handleFeedbackError = (message: string) => {
|
|
|
+ showError(message);
|
|
|
+};
|
|
|
+
|
|
|
|
|
|
|
|
|
// 处理分享链接访问
|
|
|
@@ -1803,34 +1956,114 @@ const currentPaymentUrl = ref(""); // 保存当前支付链接
|
|
|
// 记录已处理过的订单号,防止重复显示成功弹窗
|
|
|
const processedOrderNos = ref<Set<string>>(new Set());
|
|
|
|
|
|
+// 检测是否为移动设备
|
|
|
+const isMobile = () => {
|
|
|
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
|
|
+ navigator.userAgent
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// 使用<a>标签方式打开新窗口(适用于移动端和降级方案)
|
|
|
+const openLinkInNewTab = (url: string) => {
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = url;
|
|
|
+ link.target = '_blank';
|
|
|
+ link.rel = 'noopener noreferrer';
|
|
|
+ link.style.display = 'none';
|
|
|
+ document.body.appendChild(link);
|
|
|
+ link.click();
|
|
|
+ // 延迟移除,确保点击事件完成
|
|
|
+ setTimeout(() => {
|
|
|
+ document.body.removeChild(link);
|
|
|
+ }, 100);
|
|
|
+};
|
|
|
+
|
|
|
+// 预先打开一个窗口(用于iOS Safari,必须在用户交互事件中同步调用)
|
|
|
+let preOpenedWindow: Window | null = null;
|
|
|
+
|
|
|
+// 预先打开支付窗口(必须在用户点击事件中同步调用)
|
|
|
+const preOpenPaymentWindow = (redirectUrl?: string) => {
|
|
|
+ // 先关闭之前可能存在的窗口
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建一个空白页面或加载页面
|
|
|
+ const loadingUrl = redirectUrl || 'about:blank';
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 在用户交互事件中立即打开窗口
|
|
|
+ preOpenedWindow = window.open(loadingUrl, '_blank');
|
|
|
+ if (preOpenedWindow) {
|
|
|
+ preOpenedWindow.focus();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("预打开窗口失败:", error);
|
|
|
+ preOpenedWindow = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return preOpenedWindow;
|
|
|
+};
|
|
|
+
|
|
|
+// 将URL设置到预先打开的窗口
|
|
|
+const setUrlToPreOpenedWindow = (url: string) => {
|
|
|
+ if (preOpenedWindow && !preOpenedWindow.closed) {
|
|
|
+ try {
|
|
|
+ preOpenedWindow.location.href = url;
|
|
|
+ preOpenedWindow.focus();
|
|
|
+ return true;
|
|
|
+ } catch (error) {
|
|
|
+ console.error("设置URL到预打开窗口失败:", error);
|
|
|
+ // 如果失败,尝试关闭并重新打开
|
|
|
+ try {
|
|
|
+ preOpenedWindow.close();
|
|
|
+ } catch (e) {
|
|
|
+ // 忽略关闭失败
|
|
|
+ }
|
|
|
+ preOpenedWindow = null;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+};
|
|
|
+
|
|
|
// 跳转支付页面
|
|
|
-const openPaymentPage = (url: string) => {
|
|
|
- // 检测微信环境,如果在微信中,跳转到支付中转页面(使用真实域名)
|
|
|
- if (isWechat()) {
|
|
|
- // 直接跳转到真实域名的支付中转页面,避免在防封域名下跳转
|
|
|
- const realDomain = 'https://yz1df.cc';
|
|
|
- const redirectUrl = `${realDomain}/payment-redirect?url=${encodeURIComponent(url)}`;
|
|
|
- // 使用 window.location.href 强制跳转到真实域名
|
|
|
- window.location.href = redirectUrl;
|
|
|
+const openPaymentPage = (url: string, usePreOpenedWindow: boolean = false) => {
|
|
|
+ // 检测微信环境,如果在微信中,跳转到支付中转页面(使用当前域名)
|
|
|
+ const isWechatEnv = isWechat();
|
|
|
+ const finalUrl = isWechatEnv
|
|
|
+ ? `${window.location.origin}/payment-redirect?url=${encodeURIComponent(url)}`
|
|
|
+ : url;
|
|
|
+
|
|
|
+ // 如果使用预打开的窗口,尝试设置URL
|
|
|
+ if (usePreOpenedWindow) {
|
|
|
+ // 非微信环境:直接跳转到支付链接
|
|
|
+ // 微信环境:跳转到支付中转页面
|
|
|
+ if (setUrlToPreOpenedWindow(finalUrl)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果预打开窗口失败或未使用预打开窗口,使用常规方式打开
|
|
|
+ // 移动端使用<a>标签方式,PC端优先使用window.open
|
|
|
+ if (isMobile()) {
|
|
|
+ openLinkInNewTab(finalUrl);
|
|
|
} else {
|
|
|
- // 非微信环境,尝试打开新窗口
|
|
|
try {
|
|
|
- // 尝试打开新窗口
|
|
|
- const paymentWindow = window.open(url, "_blank");
|
|
|
-
|
|
|
- // 检查窗口是否被阻止
|
|
|
+ const paymentWindow = window.open(finalUrl, "_blank");
|
|
|
if (!paymentWindow || paymentWindow.closed || typeof paymentWindow.closed === "undefined") {
|
|
|
- // 如果弹窗被阻止,使用当前窗口跳转
|
|
|
- console.warn("弹窗被阻止,使用当前窗口跳转");
|
|
|
- window.location.href = url;
|
|
|
+ // 如果弹窗被阻止,使用<a>标签方式(降级方案)
|
|
|
+ openLinkInNewTab(finalUrl);
|
|
|
} else {
|
|
|
- // 成功打开新窗口,聚焦到新窗口
|
|
|
paymentWindow.focus();
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- // 如果出错,使用当前窗口跳转
|
|
|
- console.error("打开支付页面失败,使用当前窗口跳转:", error);
|
|
|
- window.location.href = url;
|
|
|
+ // 如果出错,使用<a>标签方式(降级方案)
|
|
|
+ openLinkInNewTab(finalUrl);
|
|
|
}
|
|
|
}
|
|
|
};
|