feat: 播放页按来源列表切换,新增作者/搜索/hashtag 侧栏
- 播放上下文:作品链接带上来源(搜索、标签、作者、hashtag),上一条/下一条 在该列表内移动;当前作品不在列表中时退回全部作品的发布时间顺序 - 顶部提示标签显示当前列表与位置,点击打开侧栏列表,✕ 退出 - 侧栏改为「评论 | TA的作品 | 搜索结果/#hashtag」标签页,作品封面网格可就地切换 - 作者模式:打开「TA的作品」进入,关闭侧栏不退出,点提示 ✕ 后回到原来的列表 - 从搜索结果进入或点 hashtag 时自动打开对应列表 - 描述中的 hashtag 可点击,按完整 hashtag 精确匹配(#丰川祥子 不含 #丰川祥子cos) - 新增 /api/playlist;搜索 SQL 抽到 lib/search-query.ts 与播放页共用,保证顺序一致 - 移动端:底部抽屉、点作品后收起,媒体区支持上下滑切换 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
import { X } from "lucide-react";
|
||||
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { Comment, User } from "../types.ts";
|
||||
import { CommentList } from "./CommentList";
|
||||
|
||||
interface CommentPanelProps {
|
||||
/** 侧栏打开且停在「评论」标签页 */
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
author: User;
|
||||
createdAt: string | Date;
|
||||
awemeId: string;
|
||||
mounted: boolean;
|
||||
}
|
||||
|
||||
interface CommentsApiResponse {
|
||||
@@ -21,16 +19,14 @@ interface CommentsApiResponse {
|
||||
snapshot?: string | number;
|
||||
}
|
||||
|
||||
// 侧栏「评论」标签页的内容(外壳与标签切换在 SidePanel 里)
|
||||
export const CommentPanel = memo(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);
|
||||
const [hasOpened, setHasOpened] = useState(open);
|
||||
@@ -93,7 +89,6 @@ export const CommentPanel = memo(function CommentPanel({
|
||||
return deduped;
|
||||
});
|
||||
|
||||
setTotal(typeof data.total === "number" ? data.total : 0);
|
||||
const nextHasMore = Boolean(data.hasMore);
|
||||
hasMoreRef.current = nextHasMore;
|
||||
setHasMore(nextHasMore);
|
||||
@@ -131,7 +126,6 @@ export const CommentPanel = memo(function CommentPanel({
|
||||
hasMoreRef.current = true;
|
||||
rankParamsRef.current = null;
|
||||
setComments([]);
|
||||
setTotal(0);
|
||||
setHasMore(true);
|
||||
setLoading(false);
|
||||
|
||||
@@ -174,57 +168,29 @@ export const CommentPanel = memo(function CommentPanel({
|
||||
}, [open, hasMore, loadComments]);
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={`
|
||||
z-30 flex flex-col bg-[rgba(22,22,22,0.92)] text-white overflow-hidden
|
||||
fixed inset-x-0 bottom-0 w-full h-[min(80vh,88dvh)] border-t border-white/10
|
||||
landscape:relative landscape:inset-auto landscape:h-full landscape:translate-y-0 landscape:border-t-0
|
||||
${mounted ? "transition-transform landscape:transition-[width] duration-200 ease-out" : ""}
|
||||
${
|
||||
open
|
||||
? "translate-y-0 pointer-events-auto landscape:w-[min(420px,36vw)] landscape:border-l landscape:border-white/10"
|
||||
: "translate-y-full pointer-events-none landscape:w-0 landscape:border-l-0"
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="flex items-center justify-between landscape:flex-row-reverse px-3 py-3 border-b border-white/10 shrink-0">
|
||||
<div className="text-white font-semibold">
|
||||
评论 {total > 0 ? `(${total})` : ""}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="w-8 h-8 inline-flex items-center justify-center rounded-full bg-white/15 text-white/90 border border-white/20 hover:bg-white/25 transition-colors cursor-pointer"
|
||||
onClick={onClose}
|
||||
aria-label="关闭评论"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<div ref={scrollRef} className="p-3 overflow-auto comment-scroll flex-1">
|
||||
{(open || hasOpened) && (
|
||||
<>
|
||||
<CommentList
|
||||
author={author}
|
||||
createdAt={createdAt}
|
||||
comments={comments}
|
||||
/>
|
||||
|
||||
<div ref={scrollRef} className="p-3 overflow-auto comment-scroll flex-1">
|
||||
{(open || hasOpened) && (
|
||||
<>
|
||||
<CommentList
|
||||
author={author}
|
||||
createdAt={createdAt}
|
||||
comments={comments}
|
||||
/>
|
||||
{hasMore && <div ref={sentinelRef} className="h-1 w-full" />}
|
||||
|
||||
{hasMore && <div ref={sentinelRef} className="h-1 w-full" />}
|
||||
|
||||
{loading && (
|
||||
<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>
|
||||
</aside>
|
||||
{loading && (
|
||||
<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>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user