"use client"; import { useState, useEffect, useRef, useCallback } from 'react'; import type { MouseEvent } from 'react'; import { Loader2, Download, Send } from 'lucide-react'; import { useRouter } from 'next/navigation'; import './style/create-to-video2.css'; import { getScriptEpisodeListNew, MovieProject } from "@/api/script_episode"; import { ChatInputBox } from '@/components/ChatInputBox/ChatInputBox'; import cover_image1 from '@/public/assets/cover_image3.jpg'; import cover_image2 from '@/public/assets/cover_image_shu.jpg'; import { motion } from 'framer-motion'; import { Tooltip, Button } from 'antd'; import { downloadVideo, downloadAllVideos, getFirstFrame } from '@/utils/tools'; import { showDownloadOptionsModal } from '@/components/pages/work-flow/download-options-modal'; import { post, get } from '@/api/request'; import { baseUrl } from '@/lib/env'; import ShareModal from '@/components/common/ShareModal'; import Masonry from 'react-masonry-css'; import debounce from 'lodash/debounce'; // ideaText已迁移到ChatInputBox组件中 export default function CreateToVideo2() { const router = useRouter(); const [episodeList, setEpisodeList] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [perPage] = useState(28); const [isLoading, setIsLoading] = useState(false); const [hasMore, setHasMore] = useState(true); const [isLoadingMore, setIsLoadingMore] = useState(false); const [isPreloading, setIsPreloading] = useState(false); const scrollContainerRef = useRef(null); const [userId, setUserId] = useState(0); const [isLoadingDownloadBtn, setIsLoadingDownloadBtn] = useState(false); const [isLoadingShareBtn, setIsLoadingShareBtn] = useState(false); const [shareModalVisible, setShareModalVisible] = useState(false); const [selectedProject, setSelectedProject] = useState(null); const masonryRef = useRef(null); interface PreloadedData { page: number; data: { code: number; data: { movie_projects: MovieProject[]; total_pages: number; }; }; } const preloadedDataRef = useRef(null); // 添加一个 ref 来跟踪当前正在加载的页码 const loadingPageRef = useRef(null); // 在客户端挂载后读取localStorage // 预加载下一页数据 const preloadNextPage = async (userId: number, page: number) => { if (isPreloading || !hasMore || page > totalPages) return; setIsPreloading(true); try { const response = await fetchEpisodeData(userId, page); if (response.code === 0) { preloadedDataRef.current = { page, data: response }; } } catch (error) { console.error('Failed to preload next page:', error); } finally { setIsPreloading(false); } }; // 监听滚动事件,实现无限加载和预加载 const handleScroll = useCallback(() => { if (!scrollContainerRef.current || !hasMore || isLoadingMore || isLoading) return; const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current; const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; // 在滚动到 30% 时预加载下一页 // if (scrollPercentage > 0.30 && !isPreloading && currentPage < totalPages) { // preloadNextPage(userId, currentPage + 1); // } // 在滚动到 70% 时加载下一页 if (scrollPercentage > 0.7) { const nextPage = currentPage + 1; if (nextPage <= totalPages) { getEpisodeList(userId, nextPage, true); } } }, [hasMore, isLoadingMore, isLoading, totalPages, userId, currentPage, isPreloading]); useEffect(() => { if (typeof window !== 'undefined') { const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}'); console.log('currentUser', currentUser); setUserId(currentUser.id); getEpisodeList(currentUser.id, 1, false); } }, []); // 添加滚动监听 useEffect(() => { const scrollContainer = scrollContainerRef.current; if (scrollContainer) { scrollContainer.addEventListener('scroll', handleScroll); return () => scrollContainer.removeEventListener('scroll', handleScroll); } }, [handleScroll]); // 获取剧集列表数据 const fetchEpisodeData = async (userId: number, page: number) => { const params = { user_id: String(userId), page, per_page: perPage }; return await getScriptEpisodeListNew(params); }; // 修改获取剧集列表函数 const getEpisodeList = async (userId: number, page: number = 1, loadMore: boolean = false) => { // 检查是否正在加载该页 if (loadingPageRef.current === page) return; if (isLoading || (isLoadingMore && !loadMore)) return; // 设置当前正在加载的页码 loadingPageRef.current = page; if (loadMore) { setIsLoadingMore(true); } else { setIsLoading(true); } try { let episodeListResponse; // 如果有预加载的数据且页码匹配,直接使用 if (preloadedDataRef.current && preloadedDataRef.current.page === page) { episodeListResponse = preloadedDataRef.current.data; preloadedDataRef.current = null; } else { episodeListResponse = await fetchEpisodeData(userId, page); } if (episodeListResponse.code === 0) { const { movie_projects, total_pages } = episodeListResponse.data; // 确保数据不重复 if (loadMore) { setEpisodeList(prev => { const newProjects = movie_projects.filter( project => !prev.some(p => p.project_id === project.project_id) ); return [...prev, ...newProjects]; }); } else { setEpisodeList(movie_projects); } setTotalPages(total_pages); setHasMore(page < total_pages); setCurrentPage(page); // 预加载下一页数据 // if (page < total_pages && !isPreloading) { // preloadNextPage(userId, page + 1); // } } } catch (error) { console.error('Failed to fetch episode list:', error); } finally { setIsLoading(false); setIsLoadingMore(false); // 清除当前加载页码 loadingPageRef.current = null; } }; // 视频上传和创建功能已迁移到ChatInputBox组件中 // 所有视频工具相关的函数和配置已迁移到ChatInputBox组件中 const StatusBadge = (status: string) => { return ( {/* 进行中 脉冲小圆点 */} {status === 'pending' && ( <> {/* 状态文字 */} )} {/* 失败 */} {status === 'failed' && ( <> FAILED )} ) } // 创建一个视频引用Map const videoRefs = useRef>(new Map()); const handleMouseEnter = (projectId: string) => { const videoElement = videoRefs.current.get(projectId); if (videoElement) { videoElement.play().catch(() => { console.log('Video autoplay prevented'); }); } }; const handleMouseLeave = (projectId: string) => { const videoElement = videoRefs.current.get(projectId); if (videoElement) { videoElement.pause(); videoElement.currentTime = 0; } }; const setVideoRef = (projectId: string, element: HTMLVideoElement | null) => { if (element) { videoRefs.current.set(projectId, element); } else { videoRefs.current.delete(projectId); } }; // 监听窗口大小变化,触发 Masonry 重排 useEffect(() => { const handleResize = debounce(() => { if (masonryRef.current?.recomputeCellPositions) { masonryRef.current.recomputeCellPositions(); } }, 200); window.addEventListener('resize', handleResize); return () => { handleResize.cancel(); window.removeEventListener('resize', handleResize); }; }, []); const renderProjectCard = (project: MovieProject): JSX.Element => { const handleDownloadClick = async (e: MouseEvent, project: MovieProject) => { console.log(project); e.stopPropagation(); showDownloadOptionsModal({ currentVideoIndex: 0, totalVideos: 1, isCurrentVideoFailed: false, isFinalStage: true, projectId: project.project_id, onDownloadCurrent: async (withWatermark: boolean) => { setIsLoadingDownloadBtn(true); try { const json: any = await post('/movie/download_video', { project_id: project.project_id, watermark: withWatermark }); const url = json?.data?.download_url as string | undefined; if (url) { await downloadVideo(url); } } finally { setIsLoadingDownloadBtn(false); } }, onDownloadAll: ()=>{} }); }; const handleShareClick = (e: MouseEvent, project: MovieProject) => { e.stopPropagation(); setSelectedProject(project); setShareModalVisible(true); }; const getPosterUrl = (project: MovieProject): string => { if (project.video_snapshot_url && project.video_snapshot_url.trim() !== '') { return project.video_snapshot_url; } //使用getFirstFrame生成 const videoUrl = project.final_video_url || project.final_simple_video_url || project.video_urls || ''; if (videoUrl && videoUrl.trim() !== '') { return getFirstFrame(videoUrl, 300); } return ''; }; // 根据 aspect_ratio 计算纵横比 const getAspectRatio = () => { switch (project.aspect_ratio) { case "VIDEO_ASPECT_RATIO_LANDSCAPE": return 16 / 9; // 横屏 16:9 case "VIDEO_ASPECT_RATIO_PORTRAIT": return 9 / 16; // 竖屏 9:16 default: return 16 / 9; // 默认横屏 } }; const aspectRatio = getAspectRatio(); return (
handleMouseEnter(project.project_id)} onMouseLeave={() => handleMouseLeave(project.project_id)} data-alt="project-card" > {/* 视频/图片区域(使用 aspect_ratio 预设高度) */}
router.push(`/movies/work-flow?episodeId=${project.project_id}`)} > {(project.final_video_url || project.final_simple_video_url || project.video_urls) ? (
); }; return (
{episodeList.length > 0 && ( /* 优化的剧集网格 */
{(() => { const masonryBreakpoints = { default: 5, 1024: 2, 640: 1 }; return ( {episodeList.map(renderProjectCard)} ); })()} {/* 加载更多指示器 */} {isLoadingMore && (
)}
)} {episodeList.length === 0 && isLoading && (
)}
{/* 视频工具组件 - 使用独立组件 */} {/* {!isLoading && } */} {/* 分享弹框 */} {selectedProject && ( { setShareModalVisible(false); setSelectedProject(null); }} project={selectedProject} /> )}
); }