wilhelm wong 3 hete
szülő
commit
aae63c9fd4

+ 1 - 14
index.html

@@ -93,20 +93,7 @@
       var _czc = _czc || [];
       (function () {
         var um = document.createElement("script");
-        um.src = "https://v1.cnzz.com/z.js?id=1281444456&async=1";
-        
-        // 添加加载成功回调(用于调试)
-        um.onload = function() {
-          console.log('[CNZZ统计] 脚本加载成功');
-          console.log('[CNZZ统计] 站点ID: 1281444456');
-          console.log('[CNZZ统计] _czc对象:', window._czc);
-        };
-        
-        // 添加加载失败回调(用于调试)
-        um.onerror = function() {
-          console.error('[CNZZ统计] 脚本加载失败,请检查网络连接');
-        };
-        
+        um.src = "https://s4.cnzz.com/z.js?id=1281451689&async=1";
         var s = document.getElementsByTagName("script")[0];
         s.parentNode.insertBefore(um, s);
       })();

+ 245 - 0
src/components/VideoFeedbackDialog.vue

@@ -0,0 +1,245 @@
+<template>
+  <div
+    v-if="modelValue"
+    class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+    @click.self="closeDialog"
+  >
+    <div
+      class="relative bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-sm mx-4"
+    >
+      <!-- 关闭按钮 -->
+      <button
+        @click="closeDialog"
+        class="absolute top-4 right-4 text-white/60 hover:text-white/80 transition-colors"
+      >
+        <svg
+          class="w-5 h-5"
+          fill="none"
+          stroke="currentColor"
+          viewBox="0 0 24 24"
+        >
+          <path
+            stroke-linecap="round"
+            stroke-linejoin="round"
+            stroke-width="2"
+            d="M6 18L18 6M6 6l12 12"
+          />
+        </svg>
+      </button>
+
+      <!-- 标题 -->
+      <h3 class="text-lg font-semibold text-white/90 mb-4 text-center">
+        视频反馈
+      </h3>
+
+      <!-- 反馈理由选择 -->
+      <div class="space-y-3 mb-4">
+        <label
+          class="flex items-center p-3 border border-white/20 rounded-lg cursor-pointer hover:bg-white/5 transition"
+          :class="selectedFeedbackReason === '视频无法播放' ? 'border-brand bg-brand/10' : ''"
+        >
+          <input
+            v-model="selectedFeedbackReason"
+            value="视频无法播放"
+            type="radio"
+            name="feedback-reason"
+            class="sr-only"
+          />
+          <div class="flex items-center flex-1">
+            <div
+              class="w-5 h-5 rounded-full border-2 mr-3 flex items-center justify-center"
+              :class="selectedFeedbackReason === '视频无法播放' ? 'border-brand' : 'border-white/40'"
+            >
+              <div
+                v-if="selectedFeedbackReason === '视频无法播放'"
+                class="w-3 h-3 rounded-full bg-brand"
+              ></div>
+            </div>
+            <span class="text-white/90 text-sm">视频无法播放</span>
+          </div>
+        </label>
+
+        <label
+          class="flex items-center p-3 border border-white/20 rounded-lg cursor-pointer hover:bg-white/5 transition"
+          :class="selectedFeedbackReason === '视频卡顿' ? 'border-brand bg-brand/10' : ''"
+        >
+          <input
+            v-model="selectedFeedbackReason"
+            value="视频卡顿"
+            type="radio"
+            name="feedback-reason"
+            class="sr-only"
+          />
+          <div class="flex items-center flex-1">
+            <div
+              class="w-5 h-5 rounded-full border-2 mr-3 flex items-center justify-center"
+              :class="selectedFeedbackReason === '视频卡顿' ? 'border-brand' : 'border-white/40'"
+            >
+              <div
+                v-if="selectedFeedbackReason === '视频卡顿'"
+                class="w-3 h-3 rounded-full bg-brand"
+              ></div>
+            </div>
+            <span class="text-white/90 text-sm">视频卡顿</span>
+          </div>
+        </label>
+
+        <label
+          class="flex items-center p-3 border border-white/20 rounded-lg cursor-pointer hover:bg-white/5 transition"
+          :class="selectedFeedbackReason === '其他' ? 'border-brand bg-brand/10' : ''"
+        >
+          <input
+            v-model="selectedFeedbackReason"
+            value="其他"
+            type="radio"
+            name="feedback-reason"
+            class="sr-only"
+          />
+          <div class="flex items-center flex-1">
+            <div
+              class="w-5 h-5 rounded-full border-2 mr-3 flex items-center justify-center"
+              :class="selectedFeedbackReason === '其他' ? 'border-brand' : 'border-white/40'"
+            >
+              <div
+                v-if="selectedFeedbackReason === '其他'"
+                class="w-3 h-3 rounded-full bg-brand"
+              ></div>
+            </div>
+            <span class="text-white/90 text-sm">其他</span>
+          </div>
+        </label>
+      </div>
+
+      <!-- 其他理由输入框 -->
+      <div v-if="selectedFeedbackReason === '其他'" class="mb-4">
+        <textarea
+          v-model="customFeedbackReason"
+          placeholder="请输入反馈理由..."
+          rows="3"
+          class="w-full px-3 py-2 bg-white/5 border border-white/20 rounded-lg text-white/90 placeholder-white/50 focus:outline-none focus:border-brand transition resize-none"
+        ></textarea>
+      </div>
+
+      <!-- 操作按钮 -->
+      <div class="flex gap-2 pt-3 border-t border-white/10">
+        <button
+          @click="closeDialog"
+          class="flex-1 px-3 py-2 border border-white/20 text-white/70 rounded-lg hover:bg-white/5 transition text-sm"
+        >
+          取消
+        </button>
+        <button
+          @click="handleSubmit"
+          :disabled="!selectedFeedbackReason || (selectedFeedbackReason === '其他' && !customFeedbackReason.trim()) || isSubmitting"
+          class="flex-1 px-3 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition disabled:opacity-50 disabled:cursor-not-allowed text-sm"
+        >
+          {{ isSubmitting ? "提交中..." : "提交反馈" }}
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from "vue";
+import { submitVideoFeedback } from "@/services/api";
+
+const props = defineProps<{
+  modelValue: boolean;
+  videoId: number | string; // 视频ID
+}>();
+
+const emit = defineEmits<{
+  (e: "update:modelValue", value: boolean): void;
+  (e: "success", message: string): void;
+  (e: "error", message: string): void;
+}>();
+
+// 反馈相关状态
+const selectedFeedbackReason = ref("");
+const customFeedbackReason = ref("");
+const isSubmitting = ref(false);
+
+// 关闭对话框
+const closeDialog = () => {
+  emit("update:modelValue", false);
+  // 重置表单
+  resetForm();
+};
+
+// 重置表单
+const resetForm = () => {
+  selectedFeedbackReason.value = "";
+  customFeedbackReason.value = "";
+  isSubmitting.value = false;
+};
+
+// 监听对话框关闭,重置表单
+watch(
+  () => props.modelValue,
+  (newVal) => {
+    if (!newVal) {
+      resetForm();
+    }
+  }
+);
+
+// 提交反馈
+const handleSubmit = async () => {
+  if (!selectedFeedbackReason.value) {
+    emit("error", "请选择反馈理由");
+    return;
+  }
+
+  if (
+    selectedFeedbackReason.value === "其他" &&
+    !customFeedbackReason.value.trim()
+  ) {
+    emit("error", "请输入反馈理由");
+    return;
+  }
+
+  if (!props.videoId) {
+    emit("error", "视频ID不存在");
+    return;
+  }
+
+  // 确定反馈理由
+  const reason =
+    selectedFeedbackReason.value === "其他"
+      ? customFeedbackReason.value.trim()
+      : selectedFeedbackReason.value;
+
+  isSubmitting.value = true;
+
+  try {
+    // 将视频ID转换为数字
+    const videoId = parseInt(String(props.videoId));
+    if (isNaN(videoId)) {
+      emit("error", "视频ID格式错误");
+      return;
+    }
+
+    // 获取当前页面URL
+    const currentUrl = window.location.href;
+
+    const response = await submitVideoFeedback(videoId, reason, currentUrl);
+
+    if (response.code === 1) {
+      emit("success", "反馈提交成功");
+      // 关闭对话框并重置表单
+      closeDialog();
+    } else {
+      emit("error", response.msg || "提交反馈失败");
+    }
+  } catch (error: any) {
+    console.error("提交反馈失败:", error);
+    const errorMessage =
+      error?.response?.data?.msg || error?.msg || "提交反馈失败,请重试";
+    emit("error", errorMessage);
+  } finally {
+    isSubmitting.value = false;
+  }
+};
+</script>
+

+ 24 - 12
src/data/freeVideo.ts

@@ -24,18 +24,18 @@ export const freeVideos: FreeVideo[] = [
 //     m3u8: "https://api.xmqspco.com//v/YccfZkGyfDqhnkAQGP-tZA.m3u8?user_id=605500911&plat_id=60005",
 //     name: "常去保健室的我和保健科小花老师上课时放学后的中出SEX 小花暖   中文字幕 角色剧情 制服诱惑 ",
 //   },
-  {
-    id: "free-3",
-    cover: "https://cknz.t1mg0oa.com/2208/MDSQ00006700/cover",
-    m3u8: "https://api.xmqspco.com//v/uUIHFW3MfZvlwL7hTQWNng.m3u8?user_id=605500911&plat_id=60005",
-    name: "樱狸子-白丝水手服自拍#网红#福利姬#萝莉#白丝#超短裙#爆乳#樱狸子编号C1802109",
-  },
-  {
-    id: "free-4",
-    cover: "https://cknz.t1mg0oa.com/2208/MDSQ00006222/cover",
-    m3u8: "https://api.xmqspco.com//v/gS4t6fB-EU6vS4WGeLO60Q.m3u8?user_id=605500911&plat_id=60005",
-    name: "きょう肉肉引き裂き靴下撕黑丝OL秘书#黑丝#包臀裙#气质#性感#丝袜诱惑#蜜桃臀#肉感#肉肉编号24C8F8E6",
-  },
+//   {
+//     id: "free-3",
+//     cover: "https://cknz.t1mg0oa.com/2208/MDSQ00006700/cover",
+//     m3u8: "https://api.xmqspco.com//v/uUIHFW3MfZvlwL7hTQWNng.m3u8?user_id=605500911&plat_id=60005",
+//     name: "樱狸子-白丝水手服自拍#网红#福利姬#萝莉#白丝#超短裙#爆乳#樱狸子编号C1802109",
+//   },
+//   {
+//     id: "free-4",
+//     cover: "https://cknz.t1mg0oa.com/2208/MDSQ00006222/cover",
+//     m3u8: "https://api.xmqspco.com//v/gS4t6fB-EU6vS4WGeLO60Q.m3u8?user_id=605500911&plat_id=60005",
+//     name: "きょう肉肉引き裂き靴下撕黑丝OL秘书#黑丝#包臀裙#气质#性感#丝袜诱惑#蜜桃臀#肉感#肉肉编号24C8F8E6",
+//   },
 //   {
 //     id: "free-5",
 //     cover: "https://cknz.t1mg0oa.com/2208/K1CP3FVKKS5K/cover",
@@ -48,6 +48,18 @@ export const freeVideos: FreeVideo[] = [
 //     m3u8: "https://api.xmqspco.com//v/j2tCad8_KNrE083MnrgPGQ.m3u8?user_id=605500911&plat_id=60005",
 //     name: "え、ここでヤルの ダメッ すぐイっちゃうってばぁ 早漏ビンカン娘にいきなり即ズボッ大作戦 1日完全密着イクイク声我慢ドキュメント森千里   直接开啪 ",
 //   },
+  {
+    id: "free-35279",
+    cover: "http://154.89.195.242/api/video/image?id=35279",
+    m3u8: "https://9g15.vip/api/proxy/m3u8/20250226/MN2kL3pj/hls/index.m3u8",
+    name: "WH-台北娜娜-激情通话油亮肉丝被单男疯狂操逼",
+  },
+  {
+    id: "free-29648",
+    cover: "http://154.89.195.242/api/video/image?id=29648",
+    m3u8: "https://9g15.vip/api/proxy/m3u8/20250227/jGonSXUL/hls/index.m3u8",
+    name: "极品网红爆乳尤物辛尤里美腿蜜桃臀爆操表情诱惑",
+  },
 ];
 
 // 默认导出

+ 26 - 2
src/router/index.ts

@@ -8,6 +8,7 @@ import Purchased from "../views/Purchased.vue";
 import Account from "../views/Account.vue";
 import Favorite from "../views/Favorite.vue";
 import TestVideo from "../views/TestVideo.vue";
+import VideoIframeTest from "../views/VideoIframeTest.vue";
 import Redirect from "../views/Redirect.vue";
 import PaymentRedirect from "../views/PaymentRedirect.vue";
 import { useUserStore } from "@/store/user";
@@ -57,6 +58,14 @@ const routes: Array<RouteRecordRaw> = [
     name: "TestVideo",
     component: TestVideo,
   },
+  {
+    path: "/test-iframe",
+    name: "VideoIframeTest",
+    component: VideoIframeTest,
+    meta: {
+      skipRedirect: true, // 跳过重定向检查
+    },
+  },
   {
     path: "/redirect",
     name: "Redirect",
@@ -70,7 +79,21 @@ const routes: Array<RouteRecordRaw> = [
   {
     path: "/:pathMatch(.*)*",
     name: "NotFound",
-    redirect: "/",
+    beforeEnter: (to, from, next) => {
+      // 如果是 HTML 文件请求,不拦截(让服务器直接提供)
+      // 注意:实际上 Vue Router 不应该拦截静态 HTML 文件
+      // 这个检查主要是为了确保路由不会意外处理 HTML 文件
+      if (to.path.endsWith('.html')) {
+        // 对于 HTML 文件,应该由服务器直接提供,不应该进入路由
+        // 如果进入了这里,说明服务器配置可能有问题
+        console.warn('HTML 文件请求进入了路由:', to.path);
+        // 不处理,让服务器直接提供文件(实际上不应该到这里)
+        return;
+      }
+      // 其他情况重定向到首页
+      next({ path: '/' });
+    },
+    component: () => import('../views/Home.vue'), // 添加一个组件,避免类型错误
   },
 ];
 
@@ -212,7 +235,8 @@ router.beforeEach(async (to, from, next) => {
     sessionStorage.removeItem('_fromRedirect');
   }
   
-  if (checkIfFromSuperDomain() && to.name !== 'Redirect' && !isFromRedirect) {
+  // 跳过测试页面的重定向检查
+  if (checkIfFromSuperDomain() && to.name !== 'Redirect' && !isFromRedirect && !to.meta?.skipRedirect) {
     // 立即处理历史记录:替换当前历史记录,移除一级域名的记录
     // 这必须在跳转之前执行
     const currentUrl = window.location.href;

+ 16 - 2
src/services/api.ts

@@ -330,6 +330,20 @@ export const recordPageClick = async (pageType: "home" | "video"): Promise<any>
   return res.data;
 };
 
+// 提交视频反馈
+export const submitVideoFeedback = async (
+  videoId: number,
+  reason: string,
+  url?: string
+): Promise<any> => {
+  const res = await api.post("/video-feedback/submit", {
+    videoId,
+    reason,
+    url,
+  });
+  return res.data;
+};
+
 /**
  * ===================== 视频相关接口 =====================
  */
@@ -524,13 +538,13 @@ export const getVodListFromExternal = async (
  */
 
 // 测试版视频列表的随机关键词
-const TEST_VIDEO_KEYWORDS = ["露脸", "极品", "学生", "吃瓜","网红"];
+export const TEST_VIDEO_KEYWORDS = ["露脸", "极品", "学生", "吃瓜","网红"];
 
 /**
  * 获取随机关键词
  * @returns 随机选择的关键词
  */
-const getRandomKeyword = (): string => {
+export const getRandomKeyword = (): string => {
   const randomIndex = Math.floor(Math.random() * TEST_VIDEO_KEYWORDS.length);
   return TEST_VIDEO_KEYWORDS[randomIndex];
 };

+ 143 - 0
src/utils/preventBackToSuperDomain.ts

@@ -0,0 +1,143 @@
+// 检测是否为移动设备
+const isMobileDevice = (): boolean => {
+  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
+    navigator.userAgent
+  );
+};
+
+// 是否已初始化
+let isInitialized = false;
+// 是否已添加事件监听器
+let hasListeners = false;
+// 是否从一级域名跳转过来(通过 URL 参数判断)
+let isFromSuperDomain = false;
+
+// 检查 URL 参数,判断是否从一级域名跳转过来
+const checkIfFromSuperDomain = (): boolean => {
+  const urlParams = new URLSearchParams(window.location.search);
+  
+  // 从一级域名跳转过来会带的参数
+  // ref: 推荐人参数(可能加密)
+  // code: 邀请码
+  // memberCode: 会员码
+  // iv: ref 参数的加密向量(如果 ref 是加密的)
+  const superDomainParams = ['ref', 'code', 'memberCode', 'iv'];
+  
+  // 如果 URL 中存在这些参数中的任何一个,说明是从一级域名跳转过来的
+  for (const param of superDomainParams) {
+    if (urlParams.has(param)) {
+      return true;
+    }
+  }
+  
+  return false;
+};
+
+// 检查是否需要阻止返回(只有当从外部一级域名跳转过来时才阻止)
+const shouldPreventBack = (): boolean => {
+  return isFromSuperDomain;
+};
+
+// 防止返回一级域名的处理函数
+const handlePopState = (): void => {
+  // 如果不需要阻止返回,直接返回
+  if (!shouldPreventBack()) {
+    return;
+  }
+
+  // 当用户尝试返回时,立即再次添加当前页面到历史记录
+  const currentUrl = window.location.href;
+  
+  // 使用 setTimeout 确保在浏览器完成返回操作之前执行
+  // 这对于移动浏览器(特别是 iOS Safari)很重要
+  setTimeout(() => {
+    // 添加多个历史记录条目,确保移动浏览器无法返回到一级域名
+    window.history.pushState(null, '', currentUrl);
+    window.history.pushState(null, '', currentUrl);
+    window.history.pushState(null, '', currentUrl);
+  }, 0);
+};
+
+// 处理 iOS Safari 的 pageshow 事件(用于检测返回)
+const handlePageShow = (event: PageTransitionEvent): void => {
+  // 如果不需要阻止返回,直接返回
+  if (!shouldPreventBack()) {
+    return;
+  }
+
+  // 如果是从缓存中恢复(返回操作),阻止返回到一级域名
+  if (event.persisted) {
+    const currentUrl = window.location.href;
+    window.history.pushState(null, '', currentUrl);
+    window.history.pushState(null, '', currentUrl);
+  }
+};
+
+// hashchange 事件处理函数
+const handleHashChange = (): void => {
+  if (!shouldPreventBack()) {
+    return;
+  }
+  const currentUrl = window.location.href;
+  window.history.pushState(null, '', currentUrl);
+};
+
+// 立即处理历史记录(同步执行,不等待)
+const processHistoryImmediately = (): void => {
+  const currentUrl = window.location.href;
+  
+  // 立即替换当前历史记录,移除一级域名的记录
+  // 这必须在页面加载的最早时机执行,在 Vue 应用启动之前
+  window.history.replaceState(null, '', currentUrl);
+  
+  // 对于移动设备,添加多个历史记录条目以确保无法返回到一级域名
+  // 对于PC设备,添加一个即可
+  const pushCount = isMobileDevice() ? 5 : 1;
+  for (let i = 0; i < pushCount; i++) {
+    window.history.pushState(null, '', currentUrl);
+  }
+};
+
+// 初始化防止返回一级域名的功能
+export const initPreventBackToSuperDomain = async (): Promise<void> => {
+  // 避免重复初始化
+  if (isInitialized) {
+    return;
+  }
+
+  // 检查是否从一级域名跳转过来(通过 URL 参数判断)
+  // 如果同步初始化已经设置了,就使用已有的值
+  if (!isFromSuperDomain) {
+    isFromSuperDomain = checkIfFromSuperDomain();
+  }
+
+  // 防止返回一级域名:只有当从外部一级域名跳转过来时才阻止
+  if (shouldPreventBack()) {
+    // 历史记录已经在同步初始化时处理过了,这里只需要添加事件监听器
+    // 监听 popstate 事件,当用户尝试返回时阻止返回到一级域名
+    window.addEventListener('popstate', handlePopState);
+    
+    // 监听 pageshow 事件(主要用于 iOS Safari)
+    window.addEventListener('pageshow', handlePageShow);
+    
+    // 监听 hashchange 事件(某些移动浏览器使用)
+    window.addEventListener('hashchange', handleHashChange);
+    
+    // 标记已添加事件监听器
+    hasListeners = true;
+  }
+
+  isInitialized = true;
+};
+
+
+// 清理事件监听器
+export const cleanupPreventBackToSuperDomain = (): void => {
+  if (hasListeners) {
+    window.removeEventListener('popstate', handlePopState);
+    window.removeEventListener('pageshow', handlePageShow);
+    window.removeEventListener('hashchange', handleHashChange);
+    hasListeners = false;
+  }
+};
+

+ 55 - 16
src/views/Home.vue

@@ -49,7 +49,7 @@
     </div> -->
 
     <!-- 排序选择栏 -->
-    <div v-if="!isSearchMode && videoSource !== 3" class="flex flex-wrap gap-1.5">
+    <div v-if="!isSearchMode || videoSource === 3" class="flex flex-wrap gap-1.5">
       <button
         v-for="sortOption in sortOptions"
         :key="sortOption.value"
@@ -434,7 +434,8 @@ import {
   searchVideoByKeyword,
   getTestVideoList,
   convertTestVideoCoverUrl,
-  getVideoSourceById
+  getVideoSourceById,
+  getRandomKeyword
 } from "@/services/api";
 import {
   videoMenus as fixedVideoMenus,
@@ -642,7 +643,7 @@ const selectSort = async (sort: string) => {
     // 如果选择了特定菜单,按菜单查询
     await loadVideosByTag(selectedMenu.value, 1);
   } else {
-    // 如果没有选择菜单,查询所有视频
+    // 如果没有选择菜单,loadVideosByTag会自动使用随机关键词(测试版)或查询所有视频(视频源1)
     await loadVideosByTag("", 1);
   }
 };
@@ -655,7 +656,25 @@ const toggleTags = () => {
 // 显示免费视频
 const showFreeVideos = () => {
   loading.value = false;
-  videoList.value = freeVideos;
+  // 处理试看视频的图片URL,将外部地址转换为当前API地址
+  videoList.value = freeVideos.map((video) => {
+    // 如果ID格式为 free-35279,提取实际视频ID
+    const actualVideoId = video.id.startsWith("free-") 
+      ? video.id.replace("free-", "") 
+      : video.id;
+    
+    // 如果图片URL是外部地址,转换为当前API地址
+    let coverUrl = video.cover;
+    if (coverUrl.includes("/api/video/image?id=")) {
+      // 使用convertTestVideoCoverUrl转换URL
+      coverUrl = convertTestVideoCoverUrl(coverUrl, actualVideoId);
+    }
+    
+    return {
+      ...video,
+      cover: coverUrl,
+    };
+  });
   totalPages.value = 1;
   totalCount.value = freeVideos.length;
   currentPage.value = 1;
@@ -671,13 +690,19 @@ const showFreeVideos = () => {
 const handleSearch = async (keyword: string, page = 1) => {
   const trimmedKeyword = keyword.trim();
   
-  // 如果关键词为空,清除搜索模式,但仍然使用空字符串作为关键词(不使用随机关键词)
-  if (!trimmedKeyword) {
+  // 测试版视频源:如果关键词为空,使用随机关键词
+  let finalKeyword = trimmedKeyword;
+  if (!trimmedKeyword && videoSource.value === 3) {
+    finalKeyword = getRandomKeyword();
+  }
+  
+  // 如果最终关键词为空,清除搜索模式
+  if (!finalKeyword) {
     isSearchMode.value = false;
     currentSearchKeyword.value = "";
   } else {
     isSearchMode.value = true;
-    currentSearchKeyword.value = trimmedKeyword;
+    currentSearchKeyword.value = finalKeyword;
   }
 
   loading.value = true;
@@ -689,8 +714,8 @@ const handleSearch = async (keyword: string, page = 1) => {
   try {
     // 测试版视频源(视频源3)- 使用测试版接口搜索
     if (videoSource.value === 3) {
-      // 传递关键词(即使是空字符串),避免使用随机关键词
-      const response = await getTestVideoList(page, pageSize.value, trimmedKeyword);
+      // 使用最终关键词(如果为空则已使用随机关键词)
+      const response = await getTestVideoList(page, pageSize.value, finalKeyword);
       
       if (response.code === 1 && response.data?.list) {
         // 转换数据格式
@@ -841,11 +866,15 @@ const loadVideosByTag = async (tagHash: string, page = 1) => {
     // 测试版视频源(视频源3)- 使用测试版接口加载视频列表
     if (videoSource.value === 3) {
       // 如果 tagHash 存在,找到对应的标签名作为关键词
-      // 否则传递空字符串,避免使用随机关键词
-      let keyword = "";
+      // 否则使用随机关键词
+      let keyword: string | undefined = undefined;
       if (tagHash) {
         const tag = fixedVideoMenus.find((menu) => menu.hash === tagHash);
-        keyword = tag ? tag.name : "";
+        keyword = tag ? tag.name : undefined;
+      }
+      // 如果没有标签,使用随机关键词
+      if (!keyword) {
+        keyword = getRandomKeyword();
       }
       const response = await getTestVideoList(page, pageSize.value, keyword);
       
@@ -1030,12 +1059,22 @@ const initializeVideoMenus = async () => {
           `首次访问,随机选择标签: ${randomTag.name} (${randomTag.hash})`
         );
       } else {
-        // 如果没有初始标签数据,使用空字符串作为关键词(不使用随机关键词)
-        await handleSearch("", 1);
+        // 如果没有初始标签数据,使用随机关键词(测试版)或加载所有视频(视频源1)
+        if (videoSource.value === 3) {
+          const randomKeyword = getRandomKeyword();
+          await handleSearch(randomKeyword, 1);
+        } else {
+          await loadVideosByTag("", 1);
+        }
       }
     } else {
-      // 不是第一次访问,使用空字符串作为关键词(不使用随机关键词)
-      await handleSearch("", 1);
+      // 不是第一次访问,使用随机关键词(测试版)或加载所有视频(视频源1)
+      if (videoSource.value === 3) {
+        const randomKeyword = getRandomKeyword();
+        await handleSearch(randomKeyword, 1);
+      } else {
+        await loadVideosByTag("", 1);
+      }
     }
   }
 };

+ 33 - 32
src/views/PaymentRedirect.vue

@@ -121,17 +121,14 @@ const isAndroid = computed(() => platform.value.isAndroid);
 const isIOS = computed(() => platform.value.isIOS);
 
 // 获取真实域名并构建URL
-// 直接使用二级域名 yz1df.cc
 const buildRealDomainUrl = () => {
-  // 直接使用二级域名 yz1df.cc
-  const secondaryDomain = 'yz1df.cc';
-  const domainWithProtocol = `https://${secondaryDomain}`;
+  // 使用当前域名(动态获取)
+  const currentOrigin = window.location.origin;
   
-  // 构建支付中转页面的完整URL(使用二级域名)
-  // 例如:https://yz1df.cc/payment-redirect?url=支付链接
+  // 构建支付中转页面的完整URL(使用当前域名)
   const currentPath = route.path;
   const currentQuery = route.query;
-  const realUrl = new URL(`${domainWithProtocol}${currentPath}`);
+  const realUrl = new URL(`${currentOrigin}${currentPath}`);
   
   // 添加所有查询参数
   Object.keys(currentQuery).forEach(key => {
@@ -143,11 +140,35 @@ const buildRealDomainUrl = () => {
   realDomainUrl.value = realUrl.toString();
 };
 
+// 检测是否为移动设备
+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);
+};
+
 // 跳转到支付页面
+// 注意:此页面本身可能已经在预打开的窗口中,所以应该直接在当前窗口跳转
 const redirectToPayment = () => {
-  if (paymentUrl.value) {
-    window.location.href = paymentUrl.value;
-  }
+  if (!paymentUrl.value) return;
+  
+  // 直接在当前窗口跳转到支付页面(因为PaymentRedirect页面本身就在预打开的窗口中)
+  // 移动端和PC端都使用window.location.href直接跳转
+  window.location.href = paymentUrl.value;
 };
 
 // 在浏览器中打开(安卓)
@@ -185,28 +206,8 @@ const goBack = () => {
 };
 
 onMounted(async () => {
-  // 方案2:检测当前域名,如果不是真实域名,立即跳转到真实域名
-  const currentDomain = window.location.hostname;
-  const realDomain = 'yz1df.cc';
-  
-  if (currentDomain !== realDomain) {
-    // 当前不在真实域名下,立即跳转到真实域名
-    const realDomainUrl = `https://${realDomain}`;
-    const currentPath = route.path;
-    const currentQuery = route.query;
-    
-    // 构建真实域名的完整URL
-    const realUrl = new URL(`${realDomainUrl}${currentPath}`);
-    Object.keys(currentQuery).forEach(key => {
-      if (currentQuery[key]) {
-        realUrl.searchParams.set(key, String(currentQuery[key]));
-      }
-    });
-    
-    // 使用 replace 跳转,避免返回历史记录
-    window.location.replace(realUrl.toString());
-    return; // 立即返回,不执行后续代码
-  }
+  // 注意:PaymentRedirect页面应该已经在正确的域名下,不需要域名跳转
+  // 如果需要在特定域名下运行,可以通过环境变量或配置来设置
 
   // 从 URL 参数中获取支付链接
   const urlParam = route.query.url as string;

+ 476 - 0
src/views/VideoIframeTest.vue

@@ -0,0 +1,476 @@
+<template>
+  <div class="video-iframe-test-container">
+    <div class="test-header">
+      <h1>iframe 视频播放器测试</h1>
+      <p class="description">
+        测试使用 iframe 在 HTTPS 页面中播放 HTTP 视频
+      </p>
+    </div>
+
+    <div class="test-controls">
+      <div class="input-group">
+        <label>视频地址 (m3u8):</label>
+        <input
+          v-model="testVideoUrl"
+          type="text"
+          placeholder="输入 HTTP m3u8 视频地址"
+          class="url-input"
+        />
+        <button @click="loadVideo" class="load-btn">加载视频</button>
+      </div>
+      
+      <div class="info-box">
+        <h3>测试说明:</h3>
+        <ul>
+          <li>输入 HTTP 协议的 m3u8 视频地址</li>
+          <li>点击"加载视频"按钮</li>
+          <li>视频将在 iframe 中播放</li>
+          <li>如果成功,说明 iframe 方案可行</li>
+          <li>如果失败,会显示错误信息</li>
+        </ul>
+      </div>
+
+      <div class="status-box" v-if="statusMessage">
+        <div :class="['status-message', statusType]">
+          {{ statusMessage }}
+        </div>
+      </div>
+    </div>
+
+    <div class="video-wrapper">
+      <div class="video-container" v-if="currentVideoUrl">
+        <iframe
+          ref="videoIframe"
+          :src="iframeSrc"
+          frameborder="0"
+          allowfullscreen
+          allow="autoplay; fullscreen; picture-in-picture; encrypted-media"
+          class="video-iframe"
+          @load="onIframeLoad"
+        ></iframe>
+      </div>
+      <div v-else class="placeholder">
+        <p>请输入视频地址并点击"加载视频"</p>
+        <p class="example">
+          示例:http://cnd9103.jxcnjd.com/20250513/X7MFkhQH/hls/index.m3u8
+        </p>
+      </div>
+    </div>
+
+    <div class="log-box" v-if="logs.length > 0">
+      <h3>日志信息:</h3>
+      <div class="logs">
+        <div v-for="(log, index) in logs" :key="index" :class="['log-item', log.type]">
+          <span class="log-time">{{ log.time }}</span>
+          <span class="log-message">{{ log.message }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted, onUnmounted } from "vue";
+
+const testVideoUrl = ref("http://cnd9103.jxcnjd.com/20250513/X7MFkhQH/hls/index.m3u8");
+const currentVideoUrl = ref("");
+const statusMessage = ref("");
+const statusType = ref<"success" | "error" | "info">("info");
+const videoIframe = ref<HTMLIFrameElement>();
+const logs = ref<Array<{ time: string; message: string; type: string }>>([]);
+
+// 构建 iframe src
+const iframeSrc = computed(() => {
+  if (!currentVideoUrl.value) return "";
+  
+  // 使用独立的视频播放测试页面(HTTPS)
+  // 使用绝对路径,确保在正式服务器上也能正确访问
+  const playerPageUrl = `${window.location.origin}/video-player-test.html`;
+  const encodedUrl = encodeURIComponent(currentVideoUrl.value);
+  
+  const fullUrl = `${playerPageUrl}?url=${encodedUrl}`;
+  console.log('[VideoIframeTest] iframe src:', fullUrl);
+  return fullUrl;
+});
+
+// 添加日志
+const addLog = (message: string, type: string = "info") => {
+  const time = new Date().toLocaleTimeString();
+  logs.value.unshift({ time, message, type });
+  if (logs.value.length > 50) {
+    logs.value.pop();
+  }
+};
+
+// 加载视频
+const loadVideo = () => {
+  if (!testVideoUrl.value.trim()) {
+    statusMessage.value = "请输入视频地址";
+    statusType.value = "error";
+    return;
+  }
+
+  try {
+    // 验证 URL
+    const url = new URL(testVideoUrl.value.trim());
+    if (url.protocol !== "http:" && url.protocol !== "https:") {
+      statusMessage.value = "无效的 URL 协议";
+      statusType.value = "error";
+      return;
+    }
+
+    currentVideoUrl.value = testVideoUrl.value.trim();
+    statusMessage.value = "正在加载视频...";
+    statusType.value = "info";
+    addLog(`开始加载视频: ${currentVideoUrl.value}`, "info");
+    
+    // 显示 iframe 的完整 URL
+    const iframeUrl = iframeSrc.value;
+    addLog(`iframe URL: ${iframeUrl}`, "info");
+    addLog(`当前页面协议: ${window.location.protocol}`, "info");
+    addLog(`视频协议: ${url.protocol}`, "info");
+  } catch (error) {
+    statusMessage.value = "无效的 URL 格式";
+    statusType.value = "error";
+    addLog(`URL 格式错误: ${error}`, "error");
+  }
+};
+
+// iframe 加载完成
+const onIframeLoad = () => {
+  addLog("iframe 加载完成", "success");
+  statusMessage.value = "iframe 加载完成,等待视频加载...";
+  statusType.value = "info";
+  
+  // 检查 iframe 内容是否被重定向
+  try {
+    if (videoIframe.value?.contentWindow) {
+      const iframeUrl = videoIframe.value.contentWindow.location.href;
+      addLog(`iframe 当前 URL: ${iframeUrl}`, "info");
+      
+      // 如果 iframe 被重定向到首页,说明有问题
+      if (iframeUrl.includes('/') && !iframeUrl.includes('video-player-test.html')) {
+        addLog("⚠️ iframe 可能被重定向了!", "error");
+        statusMessage.value = "iframe 被重定向,可能是路由拦截或文件不存在";
+        statusType.value = "error";
+      }
+    }
+  } catch (e) {
+    // 跨域情况下无法访问 iframe 内容,这是正常的
+    addLog("无法访问 iframe 内容(跨域限制,这是正常的)", "info");
+  }
+};
+
+// 处理来自 iframe 的消息
+const handleMessage = (event: MessageEvent) => {
+  // 验证消息来源(可选)
+  // if (event.origin !== window.location.origin) return;
+  
+  const { type, currentTime, duration, error: errorMsg, code, url } = event.data;
+  
+  switch (type) {
+    case "video-play":
+      addLog("视频开始播放", "success");
+      statusMessage.value = "视频播放中";
+      statusType.value = "success";
+      break;
+      
+    case "video-pause":
+      addLog("视频暂停", "info");
+      break;
+      
+    case "video-timeupdate":
+      if (currentTime !== undefined && duration !== undefined) {
+        // 只在整数秒时记录
+        if (Math.floor(currentTime) % 10 === 0) {
+          addLog(`播放进度: ${Math.floor(currentTime)}s / ${Math.floor(duration)}s`, "info");
+        }
+      }
+      break;
+      
+    case "video-loaded":
+      addLog(`视频元数据加载完成,时长: ${Math.floor(duration)}秒`, "success");
+      statusMessage.value = `视频加载成功,时长: ${Math.floor(duration)}秒`;
+      statusType.value = "success";
+      break;
+      
+    case "html-loaded":
+      addLog(`HTML 文件已加载: ${event.data.url}`, "success");
+      break;
+      
+    case "video-info":
+      addLog(`视频信息: ${event.data.message}`, "info");
+      break;
+      
+    case "video-error-msg":
+      addLog(`视频错误消息: ${event.data.message}`, "error");
+      statusMessage.value = event.data.message;
+      statusType.value = "error";
+      break;
+      
+    case "video-error":
+      addLog(`视频播放错误: ${errorMsg}`, "error");
+      statusMessage.value = `播放失败: ${errorMsg}`;
+      statusType.value = "error";
+      
+      // 检查是否是混合内容错误
+      if (errorMsg && errorMsg.includes("混合内容")) {
+        addLog("⚠️ 这是混合内容错误,iframe 方案可能无法解决", "error");
+      }
+      break;
+      
+    case "video-fullscreenchange":
+      addLog(`全屏状态: ${event.data.isFullscreen ? "全屏" : "退出全屏"}`, "info");
+      break;
+  }
+};
+
+// 监听消息
+onMounted(() => {
+  window.addEventListener("message", handleMessage);
+  addLog("测试页面已加载", "info");
+  addLog(`当前页面协议: ${window.location.protocol}`, "info");
+});
+
+// 清理
+onUnmounted(() => {
+  window.removeEventListener("message", handleMessage);
+});
+</script>
+
+<style scoped>
+.video-iframe-test-container {
+  min-height: 100vh;
+  background: #0f172a;
+  color: #fff;
+  padding: 20px;
+  max-width: 1400px;
+  margin: 0 auto;
+}
+
+.test-header {
+  text-align: center;
+  margin-bottom: 30px;
+}
+
+.test-header h1 {
+  font-size: 28px;
+  margin-bottom: 10px;
+}
+
+.description {
+  color: #94a3b8;
+  font-size: 14px;
+}
+
+.test-controls {
+  margin-bottom: 30px;
+}
+
+.input-group {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  margin-bottom: 20px;
+}
+
+.input-group label {
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.url-input {
+  padding: 12px;
+  border: 1px solid #334155;
+  border-radius: 6px;
+  background: #1e293b;
+  color: #fff;
+  font-size: 14px;
+}
+
+.url-input:focus {
+  outline: none;
+  border-color: #10b981;
+}
+
+.load-btn {
+  padding: 12px 24px;
+  background: #10b981;
+  color: #fff;
+  border: none;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: background 0.2s;
+}
+
+.load-btn:hover {
+  background: #059669;
+}
+
+.info-box {
+  background: #1e293b;
+  border: 1px solid #334155;
+  border-radius: 6px;
+  padding: 15px;
+  margin-bottom: 20px;
+}
+
+.info-box h3 {
+  font-size: 16px;
+  margin-bottom: 10px;
+}
+
+.info-box ul {
+  list-style: none;
+  padding: 0;
+}
+
+.info-box li {
+  padding: 5px 0;
+  font-size: 14px;
+  color: #cbd5e1;
+}
+
+.info-box li::before {
+  content: "• ";
+  color: #10b981;
+  font-weight: bold;
+}
+
+.status-box {
+  margin-bottom: 20px;
+}
+
+.status-message {
+  padding: 12px 16px;
+  border-radius: 6px;
+  font-size: 14px;
+}
+
+.status-message.success {
+  background: #065f46;
+  color: #6ee7b7;
+  border: 1px solid #10b981;
+}
+
+.status-message.error {
+  background: #7f1d1d;
+  color: #fca5a5;
+  border: 1px solid #ef4444;
+}
+
+.status-message.info {
+  background: #1e3a8a;
+  color: #93c5fd;
+  border: 1px solid #3b82f6;
+}
+
+.video-wrapper {
+  margin-bottom: 30px;
+}
+
+.video-container {
+  position: relative;
+  width: 100%;
+  aspect-ratio: 16 / 9;
+  background: #000;
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.video-iframe {
+  width: 100%;
+  height: 100%;
+  border: none;
+  display: block;
+}
+
+.placeholder {
+  aspect-ratio: 16 / 9;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background: #1e293b;
+  border: 2px dashed #334155;
+  border-radius: 8px;
+  color: #94a3b8;
+  text-align: center;
+  padding: 40px;
+}
+
+.placeholder p {
+  margin: 10px 0;
+  font-size: 16px;
+}
+
+.example {
+  font-size: 12px !important;
+  color: #64748b;
+  word-break: break-all;
+}
+
+.log-box {
+  background: #1e293b;
+  border: 1px solid #334155;
+  border-radius: 6px;
+  padding: 15px;
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.log-box h3 {
+  font-size: 16px;
+  margin-bottom: 10px;
+}
+
+.logs {
+  display: flex;
+  flex-direction: column;
+  gap: 5px;
+}
+
+.log-item {
+  padding: 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-family: 'Courier New', monospace;
+}
+
+.log-item.info {
+  background: #1e3a8a;
+  color: #93c5fd;
+}
+
+.log-item.success {
+  background: #065f46;
+  color: #6ee7b7;
+}
+
+.log-item.error {
+  background: #7f1d1d;
+  color: #fca5a5;
+}
+
+.log-time {
+  color: #64748b;
+  margin-right: 10px;
+}
+
+.log-message {
+  word-break: break-all;
+}
+
+@media (max-width: 768px) {
+  .video-iframe-test-container {
+    padding: 15px;
+  }
+  
+  .test-header h1 {
+    font-size: 24px;
+  }
+}
+</style>
+

+ 272 - 39
src/views/VideoPlayer.vue

@@ -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);
     }
   }
 };