Prechádzať zdrojové kódy

修复滚动加载失效问题

- 移除 Drawer 的 loading prop,避免初次加载时 children(含 sentinel 节点)被 Spin 替换导致 observer 失效
- 改用 ref callback 模式,sentinel 挂载/卸载时自动重建 IntersectionObserver
- 用 ref 持有最新的 hasMore / loading / currentPage / getVideoList,避免闭包陈旧
- rootMargin 设 200px 提前触发;初次加载和加载更多统一显示「加载中...」

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
刘立冬 8 hodín pred
rodič
commit
b21339662d

+ 23 - 14
src/views/publishContent/weCom/components/videoSelectModal/index.tsx

@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState, useEffect, useRef, useCallback } from 'react';
 import {
 	Drawer,
 	Button,
@@ -49,10 +49,18 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 	const [videoListAll, setVideoListAll] = useState<WeVideoItem[]>([]);
 	const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
 	const [playingVideo, setPlayingVideo] = useState<WeVideoItem | null>(null);
-	const sentinelRef = useRef<HTMLDivElement>(null);
+	const observerRef = useRef<IntersectionObserver | null>(null);
 	const loadingMoreRef = useRef(false);
+	const hasMoreRef = useRef(true);
+	const loadingRef = useRef(false);
+	const currentPageRef = useRef(1);
+	const getVideoListRef = useRef<(pageNum: number, append: boolean) => Promise<void>>();
 	const MAX_SELECTION = 3;
 
+	useEffect(() => { hasMoreRef.current = hasMore; }, [hasMore]);
+	useEffect(() => { loadingRef.current = loading; }, [loading]);
+	useEffect(() => { currentPageRef.current = currentPage; }, [currentPage]);
+
 	const getVideoList = async (pageNum: number, append: boolean) => {
 		if (append) {
 			if (loadingMoreRef.current) return;
@@ -93,6 +101,8 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 		}
 	}
 
+	getVideoListRef.current = getVideoList;
+
 	useEffect(() => {
 		getVideoList(1, false);
 	}, []);
@@ -103,17 +113,17 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 		}
 	}, [visible, initialSelectedIds]);
 
-	useEffect(() => {
-		const sentinel = sentinelRef.current;
-		if (!sentinel) return;
-		const observer = new IntersectionObserver((entries) => {
-			if (entries[0].isIntersecting && hasMore && !loadingMoreRef.current && !loading) {
-				getVideoList(currentPage + 1, true);
+	const sentinelRef = useCallback((node: HTMLDivElement | null) => {
+		observerRef.current?.disconnect();
+		observerRef.current = null;
+		if (!node) return;
+		observerRef.current = new IntersectionObserver((entries) => {
+			if (entries[0].isIntersecting && hasMoreRef.current && !loadingMoreRef.current && !loadingRef.current) {
+				getVideoListRef.current?.(currentPageRef.current + 1, true);
 			}
-		}, { threshold: 0.1 });
-		observer.observe(sentinel);
-		return () => observer.disconnect();
-	}, [hasMore, loading, currentPage]);
+		}, { rootMargin: '200px' });
+		observerRef.current.observe(node);
+	}, []);
 
 	const handleSearch = () => {
 		setHasMore(true);
@@ -182,7 +192,6 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 				onClose={onClose}
 				width={900}
 				placement="right"
-				loading={loading}
 				destroyOnClose
 				styles={{ footer: { textAlign: 'right', padding: '10px 24px' } }}
 				footer={
@@ -281,7 +290,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ visible, onClose, o
 					})}
 				</div>
 				<div ref={sentinelRef} className="text-center py-4 text-gray-400 text-xs">
-					{loadingMore ? '加载中...' : !hasMore && videoList.length > 0 ? '— 没有更多了 —' : ''}
+					{loading ? '加载中...' : loadingMore ? '加载中...' : !hasMore && videoList.length > 0 ? '— 没有更多了 —' : ''}
 				</div>
 			</Drawer>
 

+ 23 - 15
src/views/publishContent/weGZH/components/videoSelectModal/index.tsx

@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState, useEffect, useRef, useCallback } from 'react';
 import {
 	Drawer,
 	Button,
@@ -45,10 +45,18 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 	const [videoListAll, setVideoListAll] = useState<VideoItem[]>([]);
 	const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
 	const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null);
-	const sentinelRef = useRef<HTMLDivElement>(null);
+	const observerRef = useRef<IntersectionObserver | null>(null);
 	const loadingMoreRef = useRef(false);
+	const hasMoreRef = useRef(true);
+	const loadingRef = useRef(false);
+	const currentPageRef = useRef(1);
+	const getVideoListRef = useRef<(pageNum: number, append: boolean) => Promise<void>>();
 	const MAX_SELECTION = 3;
 
+	useEffect(() => { hasMoreRef.current = hasMore; }, [hasMore]);
+	useEffect(() => { loadingRef.current = loading; }, [loading]);
+	useEffect(() => { currentPageRef.current = currentPage; }, [currentPage]);
+
 	const getVideoListType = (planType: GzhPlanType) => {
 		if (planType === GzhPlanType.自动回复) {
 			return VideoSearchPlanType.自动回复;
@@ -99,6 +107,8 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 		}
 	}
 
+	getVideoListRef.current = getVideoList;
+
 	useEffect(() => {
 		if (visible) {
 			setVideoList(selectedVideos);
@@ -114,18 +124,17 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 		}
 	}, [visible, initialSelectedIds]);
 
-	useEffect(() => {
-		if (!visible) return;
-		const sentinel = sentinelRef.current;
-		if (!sentinel) return;
-		const observer = new IntersectionObserver((entries) => {
-			if (entries[0].isIntersecting && hasMore && !loadingMoreRef.current && !loading) {
-				getVideoList(currentPage + 1, true);
+	const sentinelRef = useCallback((node: HTMLDivElement | null) => {
+		observerRef.current?.disconnect();
+		observerRef.current = null;
+		if (!node) return;
+		observerRef.current = new IntersectionObserver((entries) => {
+			if (entries[0].isIntersecting && hasMoreRef.current && !loadingMoreRef.current && !loadingRef.current) {
+				getVideoListRef.current?.(currentPageRef.current + 1, true);
 			}
-		}, { threshold: 0.1 });
-		observer.observe(sentinel);
-		return () => observer.disconnect();
-	}, [visible, hasMore, loading, currentPage]);
+		}, { rootMargin: '200px' });
+		observerRef.current.observe(node);
+	}, []);
 
 	const handleSearch = () => {
 		setHasMore(true);
@@ -170,7 +179,6 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 				onClose={onClose}
 				width={900}
 				placement="right"
-				loading={loading}
 				styles={{ footer: { textAlign: 'right', padding: '10px 24px' } }}
 				footer={
 					<div className="flex justify-between items-center">
@@ -252,7 +260,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
 					})}
 				</div>
 				<div ref={sentinelRef} className="text-center py-4 text-gray-400 text-xs">
-					{loadingMore ? '加载中...' : !hasMore && videoList.length > 0 ? '— 没有更多了 —' : ''}
+					{loading ? '加载中...' : loadingMore ? '加载中...' : !hasMore && videoList.length > 0 ? '— 没有更多了 —' : ''}
 				</div>
 			</Drawer>