Browse Source

添加分享逻辑,合并代码

wilhelm wong 2 tháng trước cách đây
mục cha
commit
6c3cc897e0

+ 1 - 1
dev-dist/sw.js

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

+ 24 - 52
src/components/LoginDialog.vue

@@ -39,7 +39,8 @@
       </div>
 
       <div class="space-y-4">
-        <div>
+        <!-- 登录时显示用户名字段 -->
+        <div v-if="!isRegister">
           <label for="username" class="block text-sm text-white/70 mb-1.5"
             >用户名</label
           >
@@ -52,6 +53,20 @@
           />
         </div>
 
+                <!-- 注册时显示邮箱字段 -->
+        <div v-if="isRegister">
+          <label for="email" class="block text-sm text-white/70 mb-1.5"
+            >邮箱</label
+          >
+          <input
+            type="email"
+            id="email"
+            v-model="email"
+            class="w-full px-4 py-2.5 rounded-lg bg-white/5 border border-white/10 text-white/90 focus:outline-none focus:ring-2 focus:ring-brand/50"
+            placeholder="请输入邮箱"
+          />
+        </div>
+        
         <div>
           <label for="password" class="block text-sm text-white/70 mb-1.5"
             >密码</label
@@ -65,47 +80,7 @@
           />
         </div>
 
-        <!-- 注册表单额外字段 -->
-        <div v-if="isRegister" class="space-y-4">
-          <div>
-            <label for="email" class="block text-sm text-white/70 mb-1.5"
-              >邮箱(选填)</label
-            >
-            <input
-              type="email"
-              id="email"
-              v-model="email"
-              class="w-full px-4 py-2.5 rounded-lg bg-white/5 border border-white/10 text-white/90 focus:outline-none focus:ring-2 focus:ring-brand/50"
-              placeholder="请输入邮箱"
-            />
-          </div>
 
-          <div>
-            <label for="phone" class="block text-sm text-white/70 mb-1.5"
-              >手机号(选填)</label
-            >
-            <input
-              type="tel"
-              id="phone"
-              v-model="phone"
-              class="w-full px-4 py-2.5 rounded-lg bg-white/5 border border-white/10 text-white/90 focus:outline-none focus:ring-2 focus:ring-brand/50"
-              placeholder="请输入手机号"
-            />
-          </div>
-
-          <div>
-            <label for="code" class="block text-sm text-white/70 mb-1.5"
-              >邀请码(选填)</label
-            >
-            <input
-              type="text"
-              id="code"
-              v-model="code"
-              class="w-full px-4 py-2.5 rounded-lg bg-white/5 border border-white/10 text-white/90 focus:outline-none focus:ring-2 focus:ring-brand/50"
-              placeholder="请输入邀请码"
-            />
-          </div>
-        </div>
 
         <div v-if="error" class="text-red-400 text-sm py-1">
           {{ error }}
@@ -168,8 +143,6 @@ const emit = defineEmits<{
 const username = ref("");
 const password = ref("");
 const email = ref("");
-const phone = ref("");
-const code = ref("");
 const error = ref("");
 const isLoading = ref(false);
 const isRegister = ref(false);
@@ -213,22 +186,23 @@ const handleLogin = async () => {
 };
 
 const handleRegister = async () => {
-  // 简单的表单验证
-  if (!username.value || !password.value) {
-    error.value = "用户名和密码不能为空";
+  // 注册表单验证
+  if (!email.value || !password.value) {
+    error.value = "邮箱和密码不能为空";
     return;
   }
 
+  // 使用邮箱作为用户名
+  const finalUsername = email.value;
+
   try {
     error.value = "";
     isLoading.value = true;
 
     await userStore.register(
-      username.value,
+      finalUsername,
       password.value,
-      email.value || undefined,
-      phone.value || undefined,
-      code.value || undefined
+      email.value
     );
 
     emit("login-success");
@@ -248,8 +222,6 @@ watch(
       username.value = "";
       password.value = "";
       email.value = "";
-      phone.value = "";
-      code.value = "";
       error.value = "";
       isLoading.value = false;
       isRegister.value = false;

+ 127 - 0
src/components/ShareDialog.vue

@@ -0,0 +1,127 @@
+<template>
+  <div
+    v-if="modelValue"
+    class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+    @click.self="closeDialog"
+  >
+    <div
+      class="bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-md mx-4"
+    >
+      <div class="text-center mb-4">
+        <h3 class="text-lg font-semibold text-white/90 mb-2">分享链接</h3>
+        <p class="text-sm text-white/70">将链接分享给朋友,每天最多获得三部免费观看</p>
+      </div>
+
+      <!-- 分享链接显示区域 -->
+      <div class="mb-4">
+        <label class="block text-sm text-white/70 mb-2">分享链接:</label>
+        <div class="flex items-center gap-2">
+          <input
+            :value="shareUrl"
+            readonly
+            class="flex-1 px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white/90 text-sm"
+          />
+          <button
+            @click="copyShareLink"
+            class="px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition text-sm font-medium"
+          >
+            复制
+          </button>
+        </div>
+      </div>
+
+      <!-- 分享提示 -->
+      <div class="mb-4 p-3 bg-green-500/10 rounded-lg border border-green-500/20">
+        <p class="text-xs text-green-400">
+          💡 提示:朋友通过此链接访问并观看视频,即可计入您的分享次数
+        </p>
+      </div>
+
+      <!-- 操作按钮 -->
+      <div class="flex justify-center">
+        <button
+          @click="closeDialog"
+          class="px-6 py-2 border border-white/20 text-white/70 rounded-lg hover:bg-white/5 transition"
+        >
+          关闭
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, watch } from 'vue';
+import { generateShareLink } from '@/services/shareApi';
+import { useUserStore } from '@/store/user';
+
+const props = defineProps<{
+  modelValue: boolean;
+  shareUrl?: string;
+  shareTitle?: string;
+  resourceId?: string;
+}>();
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', value: boolean): void;
+  (e: 'success', message: string): void;
+  (e: 'error', message: string): void;
+}>();
+
+const userStore = useUserStore();
+
+// 计算分享链接
+const shareUrl = computed(() => {
+  console.log('🔄 shareUrl 计算属性执行:', {
+    shareUrl: props.shareUrl,
+    resourceId: props.resourceId,
+    userId: userStore.userInfo?.id
+  });
+  
+  if (props.shareUrl) {
+    return props.shareUrl + "&inviter=" + userStore.userInfo?.id + "&resource=" + props.resourceId;
+  }
+  
+  // 如果有resourceId,生成带分享参数的链接
+  if (props.resourceId && userStore.userInfo?.id) {
+    const generatedUrl = generateShareLink(userStore.userInfo.id, props.resourceId);
+    console.log('✅ 生成分享链接:', generatedUrl);
+    return generatedUrl;
+  }
+  
+  console.log('⚠️ 使用默认URL:', window.location.href);
+  return window.location.href;
+});
+
+// 关闭弹窗
+const closeDialog = () => {
+  emit('update:modelValue', false);
+};
+
+// 复制分享链接
+const copyShareLink = async () => {
+  try {
+    await navigator.clipboard.writeText(shareUrl.value);
+    emit('success', '链接已复制到剪贴板!');
+  } catch (error) {
+    console.error('复制失败:', error);
+    emit('error', '复制失败,请手动复制链接');
+  }
+};
+
+// 移除分享行为记录逻辑
+// 分享者点击分享按钮时不需要调用recordShare接口
+// 只有被分享者点击分享链接时才调用recordShareClick接口
+
+// 监听弹窗显示状态(移除自动记录分享行为)
+watch(() => props.modelValue, (newVal) => {
+  if (newVal) {
+    // 弹窗打开时,确保用户信息已加载
+    console.log('ShareDialog 打开,用户信息:', userStore.userInfo);
+    console.log('resourceId:', props.resourceId);
+    console.log('生成的分享链接:', shareUrl.value);
+  }
+});
+
+
+</script>

+ 215 - 0
src/services/shareApi.ts

@@ -0,0 +1,215 @@
+/**
+ * 分享功能API服务
+ */
+
+import api from "./api";
+
+// 分享记录接口类型定义
+export interface ShareRecord {
+  id: number;
+  inviterId: number;
+  invitedUserId?: number;
+  resourceId: string;
+  resourceName?: string;
+  invitedUserIp?: string;
+  inviterName: string;
+  teamId?: number;
+  teamName?: string;
+  status: boolean;
+  delFlag: boolean;
+  createdAt: string;
+  updatedAt: string;
+}
+
+// 分享记录响应接口
+export interface ShareRecordResponse {
+  id: number;
+  resourceId: string;
+  inviterName: string;
+  teamId?: number;
+  teamName?: string;
+  createdAt: string;
+}
+
+// 分享点击响应接口
+export interface ShareClickResponse {
+  id: number;
+  inviterId: number;
+  resourceId: string;
+  inviterName: string;
+  teamId?: number;
+  teamName?: string;
+  createdAt: string;
+}
+
+// 分享统计接口
+export interface ShareStats {
+  totalShares: number;
+  successShares: number;
+  todayShares: number;
+  hasReward: boolean;
+}
+
+// 分享记录列表响应
+export interface ShareRecordsResponse {
+  content: ShareRecord[];
+  metadata: {
+    total: number;
+    page: number;
+    size: number;
+  };
+}
+
+// 邀请验证响应
+export interface InviteValidationResponse {
+  valid: boolean;
+}
+
+// 通过分享链接注册请求
+export interface ShareRegisterRequest {
+  name: string;
+  password: string;
+  inviterId: number;
+  resourceId: string;
+  email?: string;
+  phone?: string;
+}
+
+// 通过分享链接注册响应
+export interface ShareRegisterResponse {
+  user: {
+    id: number;
+    name: string;
+    role: string;
+  };
+  member: {
+    id: number;
+    vipLevel: string;
+    status: string;
+  };
+  inviterId: number;
+  token: string;
+}
+
+/**
+ * 记录用户分享单片
+ * 注意:此接口用于记录分享者主动分享的行为,通常在分享者点击分享按钮时调用
+ */
+export async function recordShare(resourceId: string): Promise<ShareRecordResponse> {
+  const res = await api.post('/user-share/record', { resourceId });
+  return res.data;
+}
+
+/**
+ * 记录分享链接点击
+ * 注意:此接口用于记录被分享者点击分享链接的行为,在被分享者访问分享链接时自动调用
+ */
+export async function recordShareClick(inviterId: number, resourceId: string, resourceName?: string): Promise<ShareClickResponse> {
+  console.log('📞 recordShareClick 调用参数:', { inviterId, resourceId, resourceName });
+  console.log('📞 请求路径:', '/user-share/click');
+  const params: { userId: number; resourceId: string; resourceName?: string } = { userId: inviterId, resourceId };
+  if (resourceName) {
+    params.resourceName = resourceName;
+  }
+  const res = await api.get('/user-share/click', { params });
+  console.log('✅ recordShareClick 响应:', res.data);
+  return res.data;
+}
+
+/**
+ * 获取分享统计
+ */
+export async function getShareStats(): Promise<ShareStats> {
+  const res = await api.get('/user-share/stats');
+  return res.data;
+}
+
+/**
+ * 获取分享记录列表
+ */
+export async function getShareRecords(params: {
+  page?: number;
+  size?: number;
+  status?: boolean;
+  startDate?: string;
+  endDate?: string;
+  resourceId?: string;
+  inviterId?: number;
+  teamId?: number;
+} = {}): Promise<ShareRecordsResponse> {
+  const res = await api.get('/user-share/records', { params });
+  return res.data;
+}
+
+/**
+ * 验证邀请者
+ */
+export async function validateInviter(inviterId: number): Promise<InviteValidationResponse> {
+  const res = await api.get('/user-share/validate', { params: { inviterId } });
+  return res.data;
+}
+
+/**
+ * 通过分享链接注册
+ */
+export async function registerByShare(data: ShareRegisterRequest): Promise<ShareRegisterResponse> {
+  const res = await api.post('/user-share/register', data);
+  return res.data;
+}
+
+/**
+ * 生成分享链接
+ */
+export function generateShareLink(userId: number, resourceId: string, baseUrl?: string): string {
+  // 使用当前页面的完整URL,包括路径
+  const currentUrl = baseUrl || window.location.href.split('?')[0]; // 移除现有参数
+  const url = new URL(currentUrl);
+  console.log('🔗 生成分享链接:', url + "?inviter=" + userId + "&resource=" + resourceId);
+  return  url + "?inviter=" + userId + "&resource=" + resourceId;
+}
+
+/**
+ * 从URL中解析分享参数
+ */
+export function parseShareParams(): { inviterId?: number; resourceId?: string; resourceName?: string } {
+  const urlParams = new URLSearchParams(window.location.search);
+  const inviterId = urlParams.get('inviter');
+  const resourceId = urlParams.get('resource');
+  const resourceName = urlParams.get('name');
+  
+  return {
+    inviterId: inviterId ? parseInt(inviterId, 10) : undefined,
+    resourceId: resourceId || undefined,
+    resourceName: resourceName || undefined
+  };
+}
+
+/**
+ * 检查是否为分享链接访问
+ */
+export function isShareLinkAccess(): boolean {
+  const { inviterId, resourceId } = parseShareParams();
+  return !!(inviterId && resourceId);
+}
+
+/**
+ * 处理分享链接访问
+ */
+export async function handleShareLinkAccess(): Promise<void> {
+  const { inviterId, resourceId, resourceName } = parseShareParams();
+  
+  if (!inviterId || !resourceId) {
+    console.log('🔗 无分享参数,跳过处理');
+    return;
+  }
+
+  try {
+    console.log('🔗 处理分享链接访问:', { inviterId, resourceId, resourceName });
+    // 记录分享点击
+    const result = await recordShareClick(inviterId, resourceId, resourceName);
+    console.log('✅ 分享点击已记录:', result);
+  } catch (error) {
+    console.error('❌ 记录分享点击失败:', error);
+    // 不抛出错误,避免影响页面正常加载
+  }
+}

+ 141 - 0
src/views/Account.vue

@@ -10,6 +10,7 @@ import {
   resetPassword,
 } from "@/services/api";
 import { vipLevelToText, VipLevel } from "@/types/vip";
+import { getShareRecords, ShareRecord } from "@/services/shareApi";
 
 const userStore = useUserStore();
 const priceStore = usePriceStore();
@@ -57,6 +58,17 @@ const isResetPasswordLoading = ref(false);
 const editUserError = ref("");
 const resetPasswordError = ref("");
 
+// 邀请记录相关状态
+const shareRecords = ref<ShareRecord[]>([]);
+const shareRecordsLoading = ref(false);
+const shareRecordsError = ref("");
+const shareRecordsPagination = ref({
+  page: 0,
+  size: 6,
+  total: 0,
+  totalPages: 0
+});
+
 // 使用动态价格配置
 const membershipPlans = computed(() => priceStore.getMembershipPlans);
 
@@ -349,6 +361,35 @@ const handleQueryOrder = async () => {
   }
 };
 
+// 加载邀请记录
+const loadShareRecords = async (page = 0) => {
+  if (!isLoginUser.value) return;
+  
+  shareRecordsLoading.value = true;
+  shareRecordsError.value = "";
+  
+  try {
+    const response = await getShareRecords({
+      page,
+      size: shareRecordsPagination.value.size,
+      status: true // 只获取成功的邀请记录
+    });
+    
+    shareRecords.value = response.content;
+    shareRecordsPagination.value = {
+      page: response.metadata.page,
+      size: response.metadata.size,
+      total: response.metadata.total,
+      totalPages: Math.ceil(response.metadata.total / response.metadata.size)
+    };
+  } catch (error: any) {
+    console.error("加载邀请记录失败:", error);
+    shareRecordsError.value = error.message || "加载邀请记录失败";
+  } finally {
+    shareRecordsLoading.value = false;
+  }
+};
+
 // 加载价格配置的函数
 const loadPriceConfig = async () => {
   if (isLoginUser.value && !priceStore.isPriceConfigLoaded) {
@@ -366,6 +407,7 @@ watch(
   (newValue) => {
     if (newValue) {
       loadPriceConfig();
+      loadShareRecords();
     }
   },
   { immediate: true }
@@ -455,6 +497,105 @@ onMounted(async () => {
       </button>
     </div>
 
+    <!-- 邀请记录展示区域 -->
+    <div
+      v-if="isLoginUser"
+      class="rounded-2xl bg-white/5 border border-white/10 p-4"
+    >
+      <div class="flex items-center justify-between mb-4">
+        <h3 class="text-lg font-semibold text-white/90">邀请记录</h3>
+        <button
+          @click="loadShareRecords(0)"
+          :disabled="shareRecordsLoading"
+          class="px-3 py-1.5 text-sm text-white/60 hover:text-white/80 transition disabled:opacity-50"
+        >
+          {{ shareRecordsLoading ? "加载中..." : "刷新" }}
+        </button>
+      </div>
+
+      <!-- 邀请记录列表 -->
+      <div v-if="shareRecordsLoading" class="text-center py-8">
+        <div class="w-8 h-8 mx-auto border-2 border-white/20 border-t-brand rounded-full animate-spin mb-2"></div>
+        <p class="text-white/60 text-sm">加载中...</p>
+      </div>
+
+      <div v-else-if="shareRecordsError" class="text-center py-8">
+        <div class="w-12 h-12 mx-auto bg-red-500/20 rounded-full flex items-center justify-center mb-3">
+          <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>
+        <p class="text-red-400 text-sm mb-3">{{ shareRecordsError }}</p>
+        <button
+          @click="loadShareRecords(0)"
+          class="px-4 py-2 bg-red-500/20 text-red-400 rounded-lg hover:bg-red-500/30 transition text-sm"
+        >
+          重试
+        </button>
+      </div>
+
+      <div v-else-if="shareRecords.length === 0" class="text-center py-8">
+        <div class="w-12 h-12 mx-auto bg-white/10 rounded-full flex items-center justify-center mb-3">
+          <svg class="w-6 h-6 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
+          </svg>
+        </div>
+        <p class="text-white/60 text-sm">暂无邀请记录</p>
+      </div>
+
+      <div v-else class="space-y-3">
+        <div
+          v-for="record in shareRecords"
+          :key="record.id"
+          class="bg-white/5 rounded-lg p-3 border border-white/10"
+        >
+          <div class="flex items-center justify-between mb-2">
+            <div class="flex items-center gap-2">
+              <span class="text-sm font-medium text-white/90">{{ record.inviterName }}</span>
+              <span class="px-2 py-0.5 rounded text-xs font-medium bg-green-500/20 text-green-400">
+                成功
+              </span>
+            </div>
+            <span class="text-xs text-white/50">
+              {{ new Date(record.createdAt).toLocaleDateString() }}
+            </span>
+          </div>
+          <div class="text-xs text-white/60">
+            <p v-if="record.resourceName">资源名称: {{ record.resourceName }}</p>
+            <p v-else>资源ID: {{ record.resourceId }}</p>
+            <p v-if="record.invitedUserId">被邀请用户ID: {{ record.invitedUserId }}</p>
+            <p v-if="record.teamName">团队: {{ record.teamName }}</p>
+          </div>
+        </div>
+
+        <!-- 分页控件 -->
+        <div v-if="shareRecordsPagination.totalPages > 1" class="flex items-center justify-between pt-4 border-t border-white/10">
+          <div class="text-xs text-white/60">
+            共 {{ shareRecordsPagination.total }} 条记录
+          </div>
+          <div class="flex items-center gap-2">
+            <button
+              @click="loadShareRecords(shareRecordsPagination.page - 1)"
+              :disabled="shareRecordsPagination.page === 0 || shareRecordsLoading"
+              class="px-3 py-1.5 text-sm border border-white/20 text-white/70 rounded hover:bg-white/5 transition disabled:opacity-50 disabled:cursor-not-allowed"
+            >
+              上一页
+            </button>
+            <span class="text-sm text-white/60">
+              {{ shareRecordsPagination.page + 1 }} / {{ shareRecordsPagination.totalPages }}
+            </span>
+            <button
+              @click="loadShareRecords(shareRecordsPagination.page + 1)"
+              :disabled="shareRecordsPagination.page >= shareRecordsPagination.totalPages - 1 || shareRecordsLoading"
+              class="px-3 py-1.5 text-sm border border-white/20 text-white/70 rounded hover:bg-white/5 transition disabled:opacity-50 disabled:cursor-not-allowed"
+            >
+              下一页
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+
     <div
       v-if="isLoginUser"
       class="rounded-2xl overflow-hidden border border-white/10 divide-y divide-white/10"

+ 79 - 1
src/views/VideoPlayer.vue

@@ -1,7 +1,7 @@
 <template>
   <section class="space-y-6">
     <div class="flex items-center justify-between">
-      <!-- 返回按钮 -->
+      <!-- 返回按钮和分享按钮 -->
       <div class="flex items-center gap-2 sm:gap-3">
         <button
           @click="goBack"
@@ -22,6 +22,29 @@
           </svg>
           <span class="text-xs sm:text-sm">返回</span>
         </button>
+        
+        <!-- 分享按钮 -->
+        <button
+          @click="handleShare"
+          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="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>
+          <span class="text-xs sm:text-sm">分享获得此片</span>
+        </button>
+        
       </div>
 
       <!-- 购买按钮区域 -->
@@ -145,6 +168,17 @@
           >
             单独购买本片
           </button>
+          
+          <!-- 分享按钮 -->
+          <button
+            @click="handleShare"
+            class="w-full bg-green-500 hover:bg-green-600 text-white py-3 px-4 rounded-lg font-semibold transition shadow-lg hover:shadow-xl flex items-center justify-center gap-2"
+          >
+            <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"></path>
+            </svg>
+            分享获取免费观看
+          </button>
 
           <!-- 未登录用户说明 -->
           <div
@@ -484,6 +518,17 @@
       </div>
     </div>
 
+    <!-- 分享链接弹窗 -->
+    <ShareDialog
+      v-model="showShareLinkDialog"
+      :share-url="shareUrl"
+      :share-title="`精彩视频推荐:${videoInfo.name || '这个视频'}`"
+      :resource-id="videoInfo.id"
+      @success="showSuccess"
+      @error="showError"
+    />
+    
+
     <!-- 视频信息区域 -->
     <div class="space-y-6">
       <!-- 视频标题和基本信息 -->
@@ -645,8 +690,10 @@ import {
   checkSinglePurchase,
 } from "@/services/api";
 import VideoJSPlayer from "@/components/VideoJSPlayer.vue";
+import ShareDialog from "@/components/ShareDialog.vue";
 import { useUserStore } from "@/store/user";
 import { usePriceStore } from "@/store/price";
+import { handleShareLinkAccess } from "@/services/shareApi";
 
 // 路由相关
 const route = useRoute();
@@ -697,6 +744,14 @@ const showLoginDialog = ref(false);
 // 使用动态价格配置
 const membershipPlans = computed(() => priceStore.getMembershipPlans);
 
+// 分享链接
+const shareUrl = computed(() => {
+  return window.location.href;
+});
+
+
+
+
 // 会员购买相关
 const selectedPlan = ref("");
 const selectedPayment = ref("alipay");
@@ -707,6 +762,8 @@ const isSinglePurchased = ref(false);
 
 // 单片购买支付等待相关
 const showSinglePaymentWaitingDialog = ref(false);
+// 分享链接弹窗
+const showShareLinkDialog = ref(false);
 const singleCurrentOrderNo = ref("");
 
 // 会员购买按钮点击
@@ -839,6 +896,24 @@ const handleSinglePurchase = async () => {
   }
 };
 
+// 处理分享
+const handleShare = () => {
+  // 显示分享链接弹窗
+  showShareLinkDialog.value = true;
+};
+
+
+
+// 处理分享链接访问
+const handleShareLinkVisit = async () => {
+  try {
+    await handleShareLinkAccess();
+  } catch (error) {
+    console.error('处理分享链接访问失败:', error);
+  }
+};
+
+
 // 查询会员订单状态
 const handleMembershipQueryOrder = async () => {
   if (!currentOrderNo.value) {
@@ -1460,6 +1535,9 @@ onMounted(async () => {
 
   // 单片是否购买
   await checkVideoPurchaseStatus();
+  
+  // 处理分享链接访问
+  await handleShareLinkVisit();
 });
 
 onUnmounted(() => {