diff --git a/components/common/ShareModal.tsx b/components/common/ShareModal.tsx index a866f97..be428d5 100644 --- a/components/common/ShareModal.tsx +++ b/components/common/ShareModal.tsx @@ -1,7 +1,7 @@ 'use client'; -import React, { useState } from 'react'; -import { Modal, Button, Spin, QRCode } from 'antd'; +import React, { useState, useEffect } from 'react'; +import { Modal, Button, Spin, QRCode, message } from 'antd'; import { Youtube, Instagram, @@ -11,6 +11,7 @@ import { Loader2 } from 'lucide-react'; import { shareApiUrl } from '@/lib/env'; +import { VideoShareForm } from './VideoShareForm'; interface ShareModalProps { /** 是否显示弹框 */ @@ -88,6 +89,42 @@ const sharePlatforms: SharePlatform[] = [ export default function ShareModal({ visible, onClose, project }: ShareModalProps) { const [loadingPlatform, setLoadingPlatform] = useState(null); + const [showShareForm, setShowShareForm] = useState(false); + const [authError, setAuthError] = useState(null); + const [selectedPlatform, setSelectedPlatform] = useState(''); + + /** + * 监听 URL 参数变化,处理授权回调 + */ + useEffect(() => { + if (!visible) return; + + const urlParams = new URLSearchParams(window.location.search); + const platform = urlParams.get('platform'); + const status = urlParams.get('status'); + const error = urlParams.get('error'); + + if (platform && status) { + // 统一转换成小写 + const normalizedPlatform = platform.toLowerCase(); + setSelectedPlatform(normalizedPlatform); + + if (status === 'success') { + setAuthError(null); + setShowShareForm(true); + message.success(`${platform} 授权成功!`); + } else if (status === 'error') { + setAuthError(error || '授权失败,请重试'); + setShowShareForm(true); + message.error(`${platform} 授权失败`); + } + + // 清理 URL 参数 + const newUrl = window.location.pathname; + window.history.replaceState({}, '', newUrl); + } + }, [visible]); + /** * 获取用户ID */ @@ -99,11 +136,11 @@ export default function ShareModal({ visible, onClose, project }: ShareModalProp /** * 通用平台授权检查 */ - const checkPlatformAuth = async (apiPath: string, platformName: string) => { + const checkPlatformAuth = async (apiPath: string, platformName: string, platformId: string) => { const userId = getUserId(); if (!userId) { - console.error('用户ID不存在,请先登录'); + message.error('用户ID不存在,请先登录'); return; } @@ -111,25 +148,29 @@ export default function ShareModal({ visible, onClose, project }: ShareModalProp method: 'GET', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token') || ''}` + 'Authorization': `Bearer ${localStorage.getItem('token') || ''}`, + 'ngrok-skip-browser-warning': 'true' } }); const data = await response.json(); if (data.successful && data.code === 0) { - const { has_valid_token, auth_url, access_token } = data.data; + const { has_valid_token, auth_url } = data.data; if (!has_valid_token) { // 需要用户授权,跳转到授权页面 window.open(auth_url, '_blank'); + message.info('请在新窗口完成授权'); } else { - // 用户已授权,直接使用access_token进行第三方登录 - console.log(`${platformName}用户已授权,access_token:`, access_token); - // TODO: 实现视频上传逻辑 + // 用户已授权,直接显示上传表单 + setSelectedPlatform(platformId); + setAuthError(null); + setShowShareForm(true); + message.success(`${platformName} 已授权,可以直接上传视频`); } } else { - console.error(`获取${platformName}授权信息失败:`, data.message); + message.error(data.message || `获取${platformName}授权信息失败`); } }; @@ -146,14 +187,15 @@ export default function ShareModal({ visible, onClose, project }: ShareModalProp if (platformId === 'copy') { await handleCopyLink(); } else if (platformId === 'instagram') { - alert('Instagram分享功能正在开发中,敬请期待!'); + message.info('Instagram分享功能正在开发中,敬请期待!'); } else if (platform.apiPath) { - await checkPlatformAuth(platform.apiPath, platform.name); + await checkPlatformAuth(platform.apiPath, platform.name, platformId); } else { console.log(`Share to ${platformId} not implemented yet`); } } catch (error) { console.error(`Share to ${platformId} failed:`, error); + message.error(`操作失败:${error instanceof Error ? error.message : '未知错误'}`); } finally { setLoadingPlatform(null); } @@ -168,13 +210,13 @@ export default function ShareModal({ visible, onClose, project }: ShareModalProp const videoUrl = project.final_video_url || project.final_simple_video_url; if (videoUrl) { await navigator.clipboard.writeText(videoUrl); - console.log('视频链接已复制到剪贴板'); - // TODO: 显示复制成功提示 + message.success('视频链接已复制到剪贴板'); } else { - console.error('视频链接不存在'); + message.error('视频链接不存在'); } } catch (error) { console.error('复制链接失败:', error); + message.error('复制链接失败,请手动复制'); } }; @@ -183,18 +225,44 @@ export default function ShareModal({ visible, onClose, project }: ShareModalProp return project.final_video_url || project.final_simple_video_url || ''; }; + /** + * 处理视频分享提交成功 + */ + const handleVideoShare = (videoUrl: string, videoName: string) => { + // 上传成功后的处理 + setShowShareForm(false); + setSelectedPlatform(''); + setAuthError(null); + + // 延迟关闭主对话框,让用户看到成功提示 + setTimeout(() => { + onClose(); + }, 1000); + }; + + /** + * 关闭分享表单 + */ + const handleCancelShareForm = () => { + setShowShareForm(false); + setAuthError(null); + setSelectedPlatform(''); + }; + return ( - Share Video + + {showShareForm ? `分享到 ${selectedPlatform}` : 'Share Video'} + } open={visible} onCancel={onClose} footer={null} - width={720} + width={showShareForm ? 500 : 720} className="share-modal" styles={{ content: { @@ -212,7 +280,19 @@ export default function ShareModal({ visible, onClose, project }: ShareModalProp } }} > -
+ {showShareForm ? ( + /* 显示视频分享表单 */ + + ) : ( + /* 显示平台选择界面 */ +
{/* 左侧:二维码区域 */}
@@ -265,7 +345,7 @@ export default function ShareModal({ visible, onClose, project }: ShareModalProp
- + )} ); } diff --git a/components/common/VideoShareForm.tsx b/components/common/VideoShareForm.tsx index d1b7a22..eb0f4af 100644 --- a/components/common/VideoShareForm.tsx +++ b/components/common/VideoShareForm.tsx @@ -1,37 +1,154 @@ import React, { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; +import { AlertCircle, Loader2 } from 'lucide-react'; +import { shareApiUrl } from '@/lib/env'; +import { message } from 'antd'; -export const VideoShareForm: React.FC<{ onCancel: () => void; onShare: (videoUrl: string, videoName: string) => void }> = ({ onCancel, onShare }) => { - const [videoUrl, setVideoUrl] = useState(''); - const [videoName, setVideoName] = useState(''); +interface VideoShareFormProps { + onCancel: () => void; + onShare: (videoUrl: string, videoName: string) => void; + error?: string | null; + platform?: string; + videoUrl?: string; + videoName?: string; +} - const handleShare = () => { - onShare(videoUrl, videoName); +// 平台上传接口映射(统一使用小写 key) +const PLATFORM_UPLOAD_API: Record = { + 'youtube': '/api/video-share/youtube/upload', + 'tiktok': '/api/video-share/tiktok/upload', + 'reddit': '/api/video-share/reddit/upload', + 'twitter': '/api/video-share/x/upload', + // Instagram 开发中,暂不支持 + // 'instagram': '/api/video-share/instagram/upload', +}; + +export const VideoShareForm: React.FC = ({ + onCancel, + onShare, + error = null, + platform = '', + videoUrl: initialVideoUrl = '', + videoName: initialVideoName = '' +}) => { + const [videoUrl, setVideoUrl] = useState(initialVideoUrl); + const [videoName, setVideoName] = useState(initialVideoName); + const [uploading, setUploading] = useState(false); + + /** + * 获取用户ID + */ + const getUserId = () => { + const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}'); + return currentUser.id || currentUser.userId; + }; + + /** + * 上传视频到指定平台 + */ + const uploadVideoToPlatform = async (platformName: string, contentUrl: string, title: string) => { + const userId = getUserId(); + + if (!userId) { + message.error('用户ID不存在,请先登录'); + return false; + } + + // 统一转换成小写后查找 API 路径 + const normalizedPlatform = platformName.toLowerCase().replace(/\s+/g, '').replace(/[()]/g, ''); + const apiPath = PLATFORM_UPLOAD_API[normalizedPlatform]; + if (!apiPath) { + message.error(`平台 ${platformName} 暂不支持或正在开发中`); + return false; + } + + try { + const response = await fetch(`${shareApiUrl}${apiPath}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('token') || ''}`, + 'ngrok-skip-browser-warning': 'true' + }, + body: JSON.stringify({ + user_id: userId, + content_url: contentUrl, + title: title + }) + }); + + const data = await response.json(); + + if (data.successful && data.code === 0) { + message.success(`视频已成功上传到 ${platformName}!`); + return true; + } else { + message.error(data.message || `上传到 ${platformName} 失败`); + return false; + } + } catch (error) { + console.error(`上传到 ${platformName} 失败:`, error); + message.error(`上传失败:${error instanceof Error ? error.message : '网络错误'}`); + return false; + } + }; + + const handleShare = async () => { + if (!videoUrl || !videoName) { + message.warning('请填写完整的视频信息'); + return; + } + + setUploading(true); + try { + const success = await uploadVideoToPlatform(platform, videoUrl, videoName); + if (success) { + onShare(videoUrl, videoName); + } + } finally { + setUploading(false); + } }; return ( -
-

转发视频

+
+

+ 转发视频{platform && ` - ${platform}`} +

+ + {/* 错误信息显示 */} + {error && ( +
+ +
+

授权失败

+

{error}

+
+
+ )} +
- + setVideoUrl(e.target.value)} placeholder="请输入视频链接" - className="bg-white/50 dark:bg-slate-800/50 border-slate-300 dark:border-slate-700 text-slate-900 dark:text-slate-100" + className="bg-white/10 border-white/20 text-white placeholder:text-white/50" + disabled={!!error} />
- + setVideoName(e.target.value)} placeholder="请输入视频名称" - className="bg-white/50 dark:bg-slate-800/50 border-slate-300 dark:border-slate-700 text-slate-900 dark:text-slate-100" + className="bg-white/10 border-white/20 text-white placeholder:text-white/50" + disabled={!!error} />
@@ -40,17 +157,38 @@ export const VideoShareForm: React.FC<{ onCancel: () => void; onShare: (videoUrl variant="outline" onClick={onCancel} data-alt="cancel-button" - className="border-slate-400 dark:border-slate-600 text-slate-800 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800" + className="border-white/30 text-white hover:bg-white/10" + disabled={uploading} > - 取消 - - + {!error && ( + + )}
); diff --git a/components/pages/create-to-video2.tsx b/components/pages/create-to-video2.tsx index 65ff45c..b31d347 100644 --- a/components/pages/create-to-video2.tsx +++ b/components/pages/create-to-video2.tsx @@ -375,7 +375,7 @@ export default function CreateToVideo2() { {(project.final_video_url || project.final_simple_video_url) && (
{/* 转发按钮 */} - {/* + - */} + {/* 下载按钮 */}