优化图文页
This commit is contained in:
@@ -12,13 +12,23 @@ interface CommentPanelProps {
|
||||
mounted: boolean;
|
||||
}
|
||||
|
||||
export function CommentPanel({ open, onClose, author, createdAt, awemeId, mounted }: CommentPanelProps) {
|
||||
export function CommentPanel({
|
||||
open,
|
||||
onClose,
|
||||
author,
|
||||
createdAt,
|
||||
awemeId,
|
||||
mounted,
|
||||
}: CommentPanelProps) {
|
||||
const [comments, setComments] = useState<Comment[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
// ranked 排序的稳定参数(由后端返回,前端透传保证会话内稳定)
|
||||
const [rankParams, setRankParams] = useState<null | { seed: string; snapshot: string }>(null);
|
||||
const [rankParams, setRankParams] = useState<null | {
|
||||
seed: string;
|
||||
snapshot: string;
|
||||
}>(null);
|
||||
// 两套滚动容器与哨兵,分别对应横屏与竖屏面板
|
||||
const scrollRefLandscape = useRef<HTMLDivElement>(null);
|
||||
const sentinelRefLandscape = useRef<HTMLDivElement>(null);
|
||||
@@ -27,67 +37,81 @@ export function CommentPanel({ open, onClose, author, createdAt, awemeId, mounte
|
||||
const loadingRef = useRef(false);
|
||||
|
||||
// 加载评论
|
||||
const loadComments = useCallback(async (reset = false) => {
|
||||
if (loadingRef.current || (!reset && !hasMore)) return;
|
||||
|
||||
loadingRef.current = true;
|
||||
setLoading(true);
|
||||
const loadComments = useCallback(
|
||||
async (reset = false) => {
|
||||
if (loadingRef.current || (!reset && !hasMore)) return;
|
||||
|
||||
try {
|
||||
const skip = reset ? 0 : comments.length;
|
||||
const query = new URLSearchParams({
|
||||
skip: String(skip),
|
||||
take: String(20),
|
||||
mode: 'ranked',
|
||||
});
|
||||
if (rankParams) {
|
||||
query.set('seed', rankParams.seed);
|
||||
query.set('snapshot', rankParams.snapshot);
|
||||
}
|
||||
const response = await fetch(`/api/comments/${awemeId}?${query.toString()}`);
|
||||
const data = await response.json();
|
||||
loadingRef.current = true;
|
||||
setLoading(true);
|
||||
|
||||
// 统一做一次基于 cid 的去重,避免分页偶发重复
|
||||
if (reset) {
|
||||
setComments(() => {
|
||||
const seen = new Set<string>();
|
||||
return (data.comments as Comment[]).filter((c) => {
|
||||
if (seen.has(c.cid)) return false;
|
||||
seen.add(c.cid);
|
||||
return true;
|
||||
try {
|
||||
const skip = reset ? 0 : comments.length;
|
||||
const query = new URLSearchParams({
|
||||
skip: String(skip),
|
||||
take: String(20),
|
||||
mode: "ranked",
|
||||
});
|
||||
if (rankParams) {
|
||||
query.set("seed", rankParams.seed);
|
||||
query.set("snapshot", rankParams.snapshot);
|
||||
}
|
||||
const response = await fetch(
|
||||
`/api/comments/${awemeId}?${query.toString()}`,
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
// 统一做一次基于 cid 的去重,避免分页偶发重复
|
||||
if (reset) {
|
||||
setComments(() => {
|
||||
const seen = new Set<string>();
|
||||
return (data.comments as Comment[]).filter((c) => {
|
||||
if (seen.has(c.cid)) return false;
|
||||
seen.add(c.cid);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setComments((prev) => {
|
||||
const merged = [...prev, ...(data.comments as Comment[])];
|
||||
const seen = new Set<string>();
|
||||
// 保留首次出现的项,既保证顺序也避免重复
|
||||
return merged.filter((c) => {
|
||||
if (seen.has(c.cid)) return false;
|
||||
seen.add(c.cid);
|
||||
return true;
|
||||
} else {
|
||||
setComments((prev) => {
|
||||
const merged = [...prev, ...(data.comments as Comment[])];
|
||||
const seen = new Set<string>();
|
||||
// 保留首次出现的项,既保证顺序也避免重复
|
||||
return merged.filter((c) => {
|
||||
if (seen.has(c.cid)) return false;
|
||||
seen.add(c.cid);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setTotal(data.total);
|
||||
setHasMore(data.hasMore);
|
||||
if (data.mode === "ranked" && data.seed && data.snapshot) {
|
||||
// 初始化或重置时更新稳定参数
|
||||
setRankParams((prev) => {
|
||||
if (reset)
|
||||
return {
|
||||
seed: String(data.seed),
|
||||
snapshot: String(data.snapshot),
|
||||
};
|
||||
return (
|
||||
prev ?? {
|
||||
seed: String(data.seed),
|
||||
snapshot: String(data.snapshot),
|
||||
}
|
||||
);
|
||||
});
|
||||
} else if (reset) {
|
||||
setRankParams(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载评论失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
loadingRef.current = false;
|
||||
}
|
||||
|
||||
setTotal(data.total);
|
||||
setHasMore(data.hasMore);
|
||||
if (data.mode === 'ranked' && data.seed && data.snapshot) {
|
||||
// 初始化或重置时更新稳定参数
|
||||
setRankParams((prev) => {
|
||||
if (reset) return { seed: String(data.seed), snapshot: String(data.snapshot) };
|
||||
return prev ?? { seed: String(data.seed), snapshot: String(data.snapshot) };
|
||||
});
|
||||
} else if (reset) {
|
||||
setRankParams(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载评论失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
loadingRef.current = false;
|
||||
}
|
||||
}, [awemeId, comments.length, hasMore]);
|
||||
},
|
||||
[awemeId, comments.length, hasMore],
|
||||
);
|
||||
|
||||
// 面板打开时加载初始评论
|
||||
useEffect(() => {
|
||||
@@ -102,7 +126,10 @@ export function CommentPanel({ open, onClose, author, createdAt, awemeId, mounte
|
||||
|
||||
const observers: IntersectionObserver[] = [];
|
||||
|
||||
const setup = (rootEl: HTMLDivElement | null, targetEl: HTMLDivElement | null) => {
|
||||
const setup = (
|
||||
rootEl: HTMLDivElement | null,
|
||||
targetEl: HTMLDivElement | null,
|
||||
) => {
|
||||
if (!rootEl || !targetEl) return;
|
||||
const io = new IntersectionObserver(
|
||||
(entries) => {
|
||||
@@ -113,9 +140,9 @@ export function CommentPanel({ open, onClose, author, createdAt, awemeId, mounte
|
||||
},
|
||||
{
|
||||
root: rootEl,
|
||||
rootMargin: '0px 0px 200px 0px', // 距底部 200px 触发
|
||||
rootMargin: "0px 0px 200px 0px", // 距底部 200px 触发
|
||||
threshold: 0,
|
||||
}
|
||||
},
|
||||
);
|
||||
io.observe(targetEl);
|
||||
observers.push(io);
|
||||
@@ -180,19 +207,28 @@ export function CommentPanel({ open, onClose, author, createdAt, awemeId, mounte
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref={scrollRefLandscape} className="p-3 overflow-auto comment-scroll">
|
||||
<CommentList author={author} createdAt={createdAt} comments={comments} />
|
||||
|
||||
<div
|
||||
ref={scrollRefLandscape}
|
||||
className="p-3 overflow-auto comment-scroll"
|
||||
>
|
||||
<CommentList
|
||||
author={author}
|
||||
createdAt={createdAt}
|
||||
comments={comments}
|
||||
/>
|
||||
|
||||
{/* 底部加载触发区 */}
|
||||
{hasMore && (
|
||||
<div ref={sentinelRefLandscape} className="h-1 w-full" />
|
||||
)}
|
||||
|
||||
{hasMore && <div ref={sentinelRefLandscape} className="h-1 w-full" />}
|
||||
|
||||
{loading && (
|
||||
<div className="py-4 text-center text-white/60 text-sm">加载中...</div>
|
||||
<div className="py-4 text-center text-white/60 text-sm">
|
||||
加载中...
|
||||
</div>
|
||||
)}
|
||||
{!hasMore && comments.length > 0 && (
|
||||
<div className="py-4 text-center text-white/40 text-sm">没有更多评论了</div>
|
||||
<div className="py-4 text-center text-white/40 text-sm">
|
||||
没有更多评论了
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
@@ -221,19 +257,28 @@ export function CommentPanel({ open, onClose, author, createdAt, awemeId, mounte
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ref={scrollRefPortrait} className="p-3 overflow-auto comment-scroll">
|
||||
<CommentList author={author} createdAt={createdAt} comments={comments} />
|
||||
|
||||
<div
|
||||
ref={scrollRefPortrait}
|
||||
className="p-3 overflow-auto comment-scroll"
|
||||
>
|
||||
<CommentList
|
||||
author={author}
|
||||
createdAt={createdAt}
|
||||
comments={comments}
|
||||
/>
|
||||
|
||||
{/* 底部加载触发区 */}
|
||||
{hasMore && (
|
||||
<div ref={sentinelRefPortrait} className="h-1 w-full" />
|
||||
)}
|
||||
|
||||
{hasMore && <div ref={sentinelRefPortrait} className="h-1 w-full" />}
|
||||
|
||||
{loading && (
|
||||
<div className="py-4 text-center text-white/60 text-sm">加载中...</div>
|
||||
<div className="py-4 text-center text-white/60 text-sm">
|
||||
加载中...
|
||||
</div>
|
||||
)}
|
||||
{!hasMore && comments.length > 0 && (
|
||||
<div className="py-4 text-center text-white/40 text-sm">没有更多评论了</div>
|
||||
<div className="py-4 text-center text-white/40 text-sm">
|
||||
没有更多评论了
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
Reference in New Issue
Block a user