diff --git a/api/script_episode.ts b/api/script_episode.ts index cc074fc..490333b 100644 --- a/api/script_episode.ts +++ b/api/script_episode.ts @@ -69,13 +69,14 @@ interface ListMovieProjectsParams { per_page: number; } -interface MovieProject { +export interface MovieProject { project_id: string; name: string; status: string; step: string; final_video_url: string; final_simple_video_url: string; + video_urls: string; last_message: string; updated_at: string; created_at: string; diff --git a/components/pages/create-to-video2.tsx b/components/pages/create-to-video2.tsx index 6147b1c..e71f762 100644 --- a/components/pages/create-to-video2.tsx +++ b/components/pages/create-to-video2.tsx @@ -5,13 +5,14 @@ import { Loader2, Download } from 'lucide-react'; import { useRouter } from 'next/navigation'; import './style/create-to-video2.css'; -import { getScriptEpisodeListNew } from "@/api/script_episode"; +import { getScriptEpisodeListNew, MovieProject } from "@/api/script_episode"; import { ChatInputBox } from '@/components/ChatInputBox/ChatInputBox'; import cover_image1 from '@/public/assets/cover_image3.jpg'; import { motion } from 'framer-motion'; import { Tooltip, Button } from 'antd'; import { downloadVideo, getFirstFrame } from '@/utils/tools'; -import LazyLoad from "react-lazyload"; +import Masonry from 'react-masonry-css'; +import debounce from 'lodash/debounce'; @@ -19,35 +20,75 @@ import LazyLoad from "react-lazyload"; export default function CreateToVideo2() { const router = useRouter(); - const [episodeList, setEpisodeList] = useState([]); + const [episodeList, setEpisodeList] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); - const [perPage] = useState(12); + 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 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; - if (scrollHeight - scrollTop - clientHeight < 100) { - // 直接使用 currentPage,不再使用 setCurrentPage 的回调 + 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]); + }, [hasMore, isLoadingMore, isLoading, totalPages, userId, currentPage, isPreloading]); useEffect(() => { if (typeof window !== 'undefined') { @@ -67,6 +108,16 @@ export default function CreateToVideo2() { } }, [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) => { // 检查是否正在加载该页 @@ -83,13 +134,15 @@ export default function CreateToVideo2() { } try { - const params = { - user_id: String(userId), - page, - per_page: perPage - }; - - const episodeListResponse = await getScriptEpisodeListNew(params); + 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; @@ -109,6 +162,11 @@ export default function CreateToVideo2() { setTotalPages(total_pages); setHasMore(page < total_pages); setCurrentPage(page); + + // 预加载下一页数据 + // if (page < total_pages && !isPreloading) { + // preloadNextPage(userId, page + 1); + // } } } catch (error) { @@ -188,38 +246,72 @@ export default function CreateToVideo2() { } }; - const renderProjectCard = (project: any) => { + // 监听窗口大小变化,触发 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 => { + // 根据 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" > - {/* 视频/图片区域 */} -
router.push(`/movies/work-flow?episodeId=${project.project_id}`)}> + {/* 视频/图片区域(使用 aspect_ratio 预设高度) */} +
router.push(`/movies/work-flow?episodeId=${project.project_id}`)} + > {(project.final_video_url || project.final_simple_video_url || project.video_urls) ? (