wilhelm wong 2 сар өмнө
parent
commit
d28d7d1da3

+ 94 - 0
src/components/Banner.vue

@@ -0,0 +1,94 @@
+<template>
+  <div v-if="banners.length > 0" class="banner-container">
+    <a
+      v-for="banner in banners"
+      :key="banner.id"
+      :href="banner.link"
+      @click="(event) => handleClick(event, banner)"
+      class="banner-item block w-full rounded-2xl overflow-hidden bg-white/5 border border-white/10 hover:bg-white/10 transition cursor-pointer"
+    >
+      <img
+        :src="banner.image"
+        :alt="banner.title"
+        class="w-full h-auto object-cover"
+      />
+    </a>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { getBannersByPosition, recordBannerClick } from "@/services/api";
+
+interface Banner {
+  id: number;
+  image: string;
+  title: string;
+  link: string;
+  position: "top" | "middle" | "bottom";
+  enabled: boolean;
+  clickCount: number;
+  todayClickCount: number;
+}
+
+const props = defineProps<{
+  position: "top" | "middle" | "bottom";
+}>();
+
+const banners = ref<Banner[]>([]);
+
+// 加载广告
+const loadBanners = async () => {
+  try {
+    const data = await getBannersByPosition(props.position);
+    // 接口已经只返回启用的广告,直接使用
+    banners.value = Array.isArray(data) ? data : [];
+  } catch (error) {
+    console.error(`获取${props.position}位置广告失败:`, error);
+    banners.value = [];
+  }
+};
+
+// 处理广告点击
+const handleClick = async (event: Event, banner: Banner) => {
+  // 阻止默认跳转,先记录点击
+  event.preventDefault();
+  
+  try {
+    // 异步记录点击,不等待完成,避免阻塞跳转
+    recordBannerClick(banner.id).catch((error) => {
+      console.error("记录广告点击失败:", error);
+    });
+    
+    // 记录请求发送后立即跳转
+    if (banner.link) {
+      window.open(banner.link, "_blank", "noopener,noreferrer");
+    }
+  } catch (error) {
+    console.error("处理广告点击失败:", error);
+    // 即使出错也允许跳转
+    if (banner.link) {
+      window.open(banner.link, "_blank", "noopener,noreferrer");
+    }
+  }
+};
+
+onMounted(() => {
+  loadBanners();
+});
+</script>
+
+<style scoped>
+.banner-container {
+  @apply w-full;
+}
+
+.banner-item {
+  @apply mb-3;
+}
+
+.banner-item:last-child {
+  @apply mb-0;
+}
+</style>
+

+ 10 - 1
src/components/LoginDialog.vue

@@ -195,6 +195,9 @@ const handleRegister = async () => {
   // 使用手机号作为用户名
   const finalUsername = phone.value;
 
+  // 从localStorage读取memberCode作为code参数
+  const memberCode = localStorage.getItem("memberCode");
+
   try {
     error.value = "";
     isLoading.value = true;
@@ -203,9 +206,15 @@ const handleRegister = async () => {
       finalUsername,
       password.value,
       undefined, // email
-      phone.value // phone
+      phone.value, // phone
+      memberCode || undefined // code参数,从memberCode获取
     );
 
+    // 注册成功后清除memberCode,避免重复使用
+    if (memberCode) {
+      localStorage.removeItem("memberCode");
+    }
+
     emit("login-success");
     closeDialog();
   } catch (err: any) {

+ 10 - 1
src/components/layout/MainLayout.vue

@@ -141,9 +141,18 @@ const createGuestAccount = async () => {
   try {
     const urlParams = new URLSearchParams(window.location.search);
     const inviteCode = urlParams.get("code");
+    const memberCode = urlParams.get("memberCode");
     const refParam = urlParams.get("ref");
     const ivParam = urlParams.get("iv");
 
+    // 优先使用URL中的code参数,如果没有则使用memberCode
+    // 如果memberCode存在,也保存到localStorage供注册时使用
+    const codeToUse = inviteCode || memberCode || localStorage.getItem("memberCode");
+    if (memberCode) {
+      localStorage.setItem("memberCode", memberCode);
+      console.log("保存memberCode到localStorage:", memberCode);
+    }
+
     // 如果存在 ref 参数,尝试解密
     let decryptedRef: string | null = null;
     if (refParam) {
@@ -158,7 +167,7 @@ const createGuestAccount = async () => {
     }
 
     await userStore.createGuest(
-      inviteCode || undefined,
+      codeToUse || undefined,
       decryptedRef || undefined
     );
     console.log("游客账号创建成功", userStore.userInfo);

+ 18 - 0
src/services/api.ts

@@ -241,6 +241,24 @@ export const checkBanStatus = async (): Promise<{ ip: string; isBanned: boolean
   return res.data;
 };
 
+/**
+ * ===================== 广告栏相关接口 =====================
+ */
+
+// 根据位置获取广告栏列表(公开接口)
+export const getBannersByPosition = async (
+  position: "top" | "middle" | "bottom"
+): Promise<any[]> => {
+  const res = await api.get(`/banners/position/${position}`);
+  return res.data;
+};
+
+// 记录广告栏点击
+export const recordBannerClick = async (bannerId: number): Promise<any> => {
+  const res = await api.post(`/banners/${bannerId}/click`);
+  return res.data;
+};
+
 /**
  * ===================== 视频相关接口 =====================
  */

+ 19 - 7
src/views/Home.vue

@@ -1,5 +1,8 @@
 <template>
   <section class="space-y-6">
+    <!-- 顶部广告栏 -->
+    <Banner position="top" />
+
     <!-- 动态标签栏 -->
     <div v-if="!isSearchMode" class="relative">
       <div
@@ -44,14 +47,16 @@
       </button>
     </div>
 
+    <!-- 中部广告栏 -->
+    <Banner position="middle" />
+
     <!-- 视频列表 -->
     <div class="grid grid-cols-2 gap-3">
-      <article
-        v-for="video in videoList"
-        :key="video.id"
-        @click="playVideo(video)"
-        class="group rounded-2xl overflow-hidden bg-white/5 border border-white/10 cursor-pointer hover:bg-white/10 transition"
-      >
+      <template v-for="(video, index) in videoList" :key="video.id">
+        <article
+          @click="playVideo(video)"
+          class="group rounded-2xl overflow-hidden bg-white/5 border border-white/10 cursor-pointer hover:bg-white/10 transition"
+        >
         <!-- 横屏封面 -->
         <div class="aspect-[16/9] relative">
           <VideoJSPlayer
@@ -100,7 +105,13 @@
             </span>
           </div>
         </div>
-      </article>
+        </article>
+        
+        <!-- 底部广告栏:在视频列表第一行后(第2个视频后)插入 -->
+        <div v-if="index === 1" class="col-span-2">
+          <Banner position="bottom" />
+        </div>
+      </template>
     </div>
 
     <!-- 加载状态 -->
@@ -384,6 +395,7 @@ import {
 } from "@/data/videoMenus";
 import { freeVideos } from "@/data/freeVideo";
 import VideoJSPlayer from "@/components/VideoJSPlayer.vue";
+import Banner from "@/components/Banner.vue";
 import { useUserStore } from "@/store/user";
 import { VipLevel } from "@/types/vip";