Browse Source

回滚到最后一次提交前

wilhelm wong 2 months ago
parent
commit
ed7ceb01b6
3 changed files with 142 additions and 8 deletions
  1. 1 1
      dev-dist/sw.js
  2. 95 4
      src/views/Home.vue
  3. 46 3
      src/views/VideoPlayer.vue

+ 1 - 1
dev-dist/sw.js

@@ -79,7 +79,7 @@ define(['./workbox-f2cb1a81'], (function (workbox) { 'use strict';
    */
   workbox.precacheAndRoute([{
     "url": "index.html",
-    "revision": "0.6qs42s98bgo"
+    "revision": "0.80ucr7e7akg"
   }], {});
   workbox.cleanupOutdatedCaches();
   workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

+ 95 - 4
src/views/Home.vue

@@ -34,7 +34,10 @@
         v-for="sortOption in sortOptions"
         :key="sortOption.value"
         class="sort-chip"
-        :class="{ 'sort-chip-active': selectedSort === sortOption.value }"
+        :class="{
+          'sort-chip-active': selectedSort === sortOption.value,
+          'sort-chip-free': sortOption.value === 'free',
+        }"
         @click="selectSort(sortOption.value)"
       >
         {{ sortOption.label }}
@@ -57,6 +60,7 @@
             cover-class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
           />
           <div
+            v-if="selectedSort !== 'free'"
             class="absolute bottom-2 right-2 bg-black/70 text-white text-xs px-2 py-1 rounded"
           >
             {{ formatDuration(video.duration) }}
@@ -68,7 +72,10 @@
           <h3 class="text-sm font-medium text-white/90 line-clamp-2 mb-1">
             {{ video.name }}
           </h3>
-          <p class="text-xs text-white/50 truncate">
+          <p
+            v-if="selectedSort !== 'free'"
+            class="text-xs text-white/50 truncate"
+          >
             {{ formatNumber(video.view) }} 次观看 ·
             {{ formatNumber(video.like) }} 点赞
           </p>
@@ -107,7 +114,7 @@
     </div>
 
     <!-- 分页组件 -->
-    <div v-if="totalPages > 1" class="py-6">
+    <div v-if="totalPages > 1 && selectedSort !== 'free'" class="py-6">
       <!-- 桌面端:完整分页 -->
       <div class="hidden sm:block">
         <!-- 上一页、页码、下一页 -->
@@ -360,6 +367,9 @@
         </div>
       </div>
     </div>
+
+    <!-- 登录弹窗 -->
+    <LoginDialog v-model="showLoginDialog" @login-success="onLoginSuccess" />
   </section>
 </template>
 
@@ -368,7 +378,9 @@ import { ref, onMounted, onBeforeUnmount, watch, nextTick } from "vue";
 import { useRouter, useRoute } from "vue-router";
 import { searchVideoByTags, searchVideoByKeyword } from "@/services/api";
 import { videoMenus as fixedVideoMenus } from "@/data/videoMenus";
+import { freeVideos } from "@/data/freeVideo";
 import VideoJSPlayer from "@/components/VideoJSPlayer.vue";
+import LoginDialog from "@/components/LoginDialog.vue";
 import { useUserStore } from "@/store/user";
 import { VipLevel } from "@/types/vip";
 
@@ -415,6 +427,7 @@ const sortOptions = ref([
   { value: "time", label: "最新" },
   { value: "view", label: "热门" },
   { value: "like", label: "点赞" },
+  { value: "free", label: "试看" },
 ]);
 const selectedSort = ref<string>("time");
 
@@ -425,6 +438,16 @@ const showAllTags = ref(false);
 const isSearchMode = ref(false);
 const currentSearchKeyword = ref("");
 
+// 登录弹窗
+const showLoginDialog = ref(false);
+
+// 处理登录成功
+const onLoginSuccess = async () => {
+  // 登录成功后刷新用户信息
+  await userStore.sync();
+  console.log("登录成功,用户信息已更新");
+};
+
 // 格式化时长
 const formatDuration = (duration: string | number): string => {
   const seconds = parseInt(String(duration));
@@ -506,6 +529,13 @@ const selectMenu = async (hash: string) => {
 // 选择排序方式
 const selectSort = async (sort: string) => {
   selectedSort.value = sort;
+
+  // 如果是免费视频,直接显示免费视频列表
+  if (sort === "free") {
+    showFreeVideos();
+    return;
+  }
+
   // 重新查询当前显示的内容
   if (isSearchMode.value) {
     // 如果是搜索模式,重新执行搜索
@@ -524,6 +554,18 @@ const toggleTags = () => {
   showAllTags.value = !showAllTags.value;
 };
 
+// 显示免费视频
+const showFreeVideos = () => {
+  loading.value = false;
+  videoList.value = freeVideos;
+  totalPages.value = 1;
+  totalCount.value = freeVideos.length;
+  currentPage.value = 1;
+  // 清除搜索模式
+  isSearchMode.value = false;
+  currentSearchKeyword.value = "";
+};
+
 // 搜索功能
 const handleSearch = async (keyword: string, page = 1) => {
   if (!keyword.trim()) {
@@ -578,7 +620,26 @@ const playVideo = (video: any) => {
 
   const vipLevel = userStore.getVipLevel();
 
-  if (vipLevel === VipLevel.GUEST || vipLevel === VipLevel.FREE) {
+  // 如果是试看视频,检查登录状态
+  if (selectedSort.value === "free" || video.id?.startsWith("free-")) {
+    // 检查用户是否已登录
+    if (!userStore.token) {
+      // 未登录,显示登录弹窗
+      showLoginDialog.value = true;
+      return;
+    }
+    // 已登录,跳转到试看视频播放页
+    router.push({
+      name: "VideoPlayer",
+      params: { id: video.id },
+      query: {
+        cover: video.cover,
+        m3u8: video.m3u8,
+        name: video.name,
+        isFree: "true",
+      },
+    });
+  } else if (vipLevel === VipLevel.GUEST || vipLevel === VipLevel.FREE) {
     // guest和free用户通过URL参数传递cover和m3u8,同时包含视频ID
     router.push({
       name: "VideoPlayer",
@@ -760,4 +821,34 @@ onBeforeUnmount(() => {
 .sort-chip-active {
   @apply bg-emerald-500/20 text-emerald-400 border-emerald-400/30;
 }
+
+.sort-chip-free {
+  @apply relative overflow-hidden;
+  box-shadow: 0 0 10px rgba(16, 185, 129, 0.3);
+}
+
+.sort-chip-free::before {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: -100%;
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(
+    90deg,
+    transparent,
+    rgba(255, 255, 255, 0.4),
+    transparent
+  );
+  animation: sparkle 1.5s ease-in-out infinite;
+}
+
+@keyframes sparkle {
+  0% {
+    left: -100%;
+  }
+  100% {
+    left: 100%;
+  }
+}
 </style>

+ 46 - 3
src/views/VideoPlayer.vue

@@ -46,6 +46,34 @@
       </div>
     </div>
 
+    <!-- 试看视频标识 -->
+    <div v-if="isFreeVideo" class="mb-4">
+      <div
+        class="inline-flex items-center gap-2 px-3 py-1.5 bg-emerald-500/20 border border-emerald-400/30 rounded-full"
+      >
+        <svg
+          class="w-4 h-4 text-emerald-400"
+          fill="none"
+          stroke="currentColor"
+          viewBox="0 0 24 24"
+        >
+          <path
+            stroke-linecap="round"
+            stroke-linejoin="round"
+            stroke-width="2"
+            d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
+          />
+          <path
+            stroke-linecap="round"
+            stroke-linejoin="round"
+            stroke-width="2"
+            d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
+          />
+        </svg>
+        <span class="text-sm font-medium text-emerald-400">试看视频</span>
+      </div>
+    </div>
+
     <!-- 视频播放器区域 -->
     <div class="relative rounded-2xl overflow-hidden bg-black">
       <div class="aspect-video video-container">
@@ -461,7 +489,10 @@
         <h1 class="text-xl font-semibold text-white leading-tight">
           {{ videoInfo.name || "视频标题" }}
         </h1>
-        <div class="flex items-center gap-4 text-sm text-white/60">
+        <div
+          v-if="!isFreeVideo"
+          class="flex items-center gap-4 text-sm text-white/60"
+        >
           <div class="flex items-center gap-1">
             <svg
               class="w-4 h-4"
@@ -766,6 +797,13 @@ const openPaymentPage = (url: string) => {
 // 计算属性
 const currentVipLevel = computed(() => userStore.getVipLevel());
 
+// 判断是否为试看视频
+const isFreeVideo = computed(() => {
+  return (
+    route.query.isFree === "true" || videoInfo.value.id?.startsWith("free-")
+  );
+});
+
 // 参考Account.vue的用户类型判断
 const isGuest = computed(() => {
   return userStore.userInfo?.vipLevel === "guest";
@@ -1202,14 +1240,14 @@ const loadVideoInfo = async () => {
     userInfo.vipLevel === "guest" ||
     userInfo.vipLevel === "free"
   ) {
-    const { cover, name, duration, view, like } = route.query;
+    const { cover, name, duration, view, like, m3u8, isFree } = route.query;
 
     if (cover && name) {
       videoInfo.value = {
         id: videoId || "unknown",
         name: name as string,
         cover: cover as string,
-        m3u8: "", // guest和free用户不提供 m3u8 地址
+        m3u8: (m3u8 as string) || "", // 如果是免费视频,提供 m3u8 地址
         duration: parseInt(duration as string) || 0,
         view: parseInt(view as string) || 0,
         like: parseInt(like as string) || 0,
@@ -1217,6 +1255,11 @@ const loadVideoInfo = async () => {
         taginfo: [],
       };
 
+      // 如果是免费视频,标记为已购买状态,这样可以直接播放
+      if (isFree === "true") {
+        isSinglePurchased.value = true;
+      }
+
       // 设置页面标题
       document.title = `${videoInfo.value.name} - 视频`;