feat: 增强评论功能,添加评论未更改提示,优化评论输入组件的占位符

This commit is contained in:
2025-09-10 11:59:42 +08:00
parent b380e971cc
commit 09c024ccbb
4 changed files with 39 additions and 14 deletions

View File

@ -49,6 +49,10 @@ export function CommentInput(
toast.error(t("content_required")); toast.error(t("content_required"));
return; return;
} }
if (initContent === commentContent.trim() && initIsPrivate === isPrivate) {
toast.warning(t("comment_unchanged"));
return;
}
onCommentSubmitted({ commentContent, isPrivate }); onCommentSubmitted({ commentContent, isPrivate });
setCommentContent(""); setCommentContent("");
}; };
@ -62,7 +66,7 @@ export function CommentInput(
</div> </div>
<div className="flex-1 pl-2 fade-in-up"> <div className="flex-1 pl-2 fade-in-up">
<Textarea <Textarea
placeholder={user ? t("placeholder") : t("login_required", { loginButton: "登录" })} placeholder={user ? (isPrivate ? t("private_placeholder") : t("placeholder")) : t("login_required", { loginButton: "登录" })}
className="w-full p-2 border border-gray-300 rounded-md fade-in-up" className="w-full p-2 border border-gray-300 rounded-md fade-in-up"
value={commentContent} value={commentContent}
onChange={(e) => setCommentContent(e.target.value)} onChange={(e) => setCommentContent(e.target.value)}
@ -77,7 +81,6 @@ export function CommentInput(
/> />
<Label>{t("private")}</Label> <Label>{t("private")}</Label>
</div> </div>
<button onClick={handleCommentSubmit} className="px-2 py-1 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors fade-in-up"> <button onClick={handleCommentSubmit} className="px-2 py-1 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors fade-in-up">
{isUpdate ? t("update") : t("submit")} {isUpdate ? t("update") : t("submit")}
</button> </button>

View File

@ -21,11 +21,15 @@ export function CommentItem(
comment, comment,
parentComment, parentComment,
onCommentDelete, onCommentDelete,
activeInput,
setActiveInputId
}: { }: {
user: User | null, user: User | null,
comment: Comment, comment: Comment,
parentComment: Comment | null, parentComment: Comment | null,
onCommentDelete: ({ commentId }: { commentId: number }) => void, onCommentDelete: ({ commentId }: { commentId: number }) => void,
activeInput: { id: number; type: 'reply' | 'edit' } | null,
setActiveInputId: (input: { id: number; type: 'reply' | 'edit' } | null) => void,
} }
) { ) {
const t = useTranslations("Comment") const t = useTranslations("Comment")
@ -40,10 +44,8 @@ export function CommentItem(
const [isPrivate, setIsPrivate] = useState(comment.isPrivate); const [isPrivate, setIsPrivate] = useState(comment.isPrivate);
const [replyCount, setReplyCount] = useState(comment.replyCount); const [replyCount, setReplyCount] = useState(comment.replyCount);
const [showReplies, setShowReplies] = useState(false); const [showReplies, setShowReplies] = useState(false);
const [showReplyInput, setShowReplyInput] = useState(false);
const [replies, setReplies] = useState<Comment[]>([]); const [replies, setReplies] = useState<Comment[]>([]);
const [repliesLoaded, setRepliesLoaded] = useState(false); const [repliesLoaded, setRepliesLoaded] = useState(false);
const [showEditInput, setShowEditInput] = useState(false);
const handleToggleLike = () => { const handleToggleLike = () => {
if (!canClickLike) { if (!canClickLike) {
@ -112,7 +114,7 @@ export function CommentItem(
toast.success(t("edit_success")); toast.success(t("edit_success"));
comment.content = newContent; comment.content = newContent;
setIsPrivate(isPrivate); setIsPrivate(isPrivate);
setShowEditInput(false); setActiveInputId(null);
}).catch(error => { }).catch(error => {
toast.error(t("edit_failed") + ": " + error.message); toast.error(t("edit_failed") + ": " + error.message);
}); });
@ -129,7 +131,7 @@ export function CommentItem(
toast.success(t("comment_success")); toast.success(t("comment_success"));
reloadReplies(); reloadReplies();
setShowReplies(true); setShowReplies(true);
setShowReplyInput(false); setActiveInputId(null);
setReplyCount(replyCount + 1); setReplyCount(replyCount + 1);
}).catch(error => { }).catch(error => {
toast.error(t("comment_failed") + ": " + toast.error(t("comment_failed") + ": " +
@ -181,10 +183,16 @@ export function CommentItem(
{/* 回复按钮 */} {/* 回复按钮 */}
<button <button
title={t("reply")} title={t("reply")}
onClick={() => { setShowReplyInput(!showReplyInput); setShowEditInput(false); }} onClick={() => {
if (activeInput?.type === 'reply' && activeInput.id === comment.id) {
setActiveInputId(null);
} else {
setActiveInputId({ id: comment.id, type: 'reply' });
}
}}
className={`flex items-center justify-center px-2 py-1 h-5 className={`flex items-center justify-center px-2 py-1 h-5
text-primary-foreground dark:text-white text-xs text-primary-foreground dark:text-white text-xs
rounded ${showReplyInput ? "bg-slate-600" : "bg-slate-400"} hover:bg-slate-600 dark:hover:bg-slate-500 fade-in-up`}> rounded ${activeInput?.type === 'reply' && activeInput.id === comment.id ? "bg-slate-600" : "bg-slate-400"} hover:bg-slate-600 dark:hover:bg-slate-500 fade-in-up`}>
<Reply className="w-3 h-3" /> <Reply className="w-3 h-3" />
</button> </button>
{/* 编辑和删除按钮 仅自己的评论可见 */} {/* 编辑和删除按钮 仅自己的评论可见 */}
@ -192,11 +200,18 @@ export function CommentItem(
<> <>
<button <button
title={t("edit")} title={t("edit")}
onClick={() => {
if (activeInput?.type === 'edit' && activeInput.id === comment.id) {
setActiveInputId(null);
} else {
setActiveInputId({ id: comment.id, type: 'edit' });
}
}}
className={` className={`
flex items-center justify-center px-2 py-1 h-5 flex items-center justify-center px-2 py-1 h-5
text-primary-foreground dark:text-white text-xs text-primary-foreground dark:text-white text-xs
rounded ${showEditInput ? "bg-slate-600" : "bg-slate-400"} hover:bg-slate-600 dark:hover:bg-slate-500 fade-in-up`} rounded ${activeInput?.type === 'edit' && activeInput.id === comment.id ? "bg-slate-600" : "bg-slate-400"} hover:bg-slate-600 dark:hover:bg-slate-500 fade-in-up`}
onClick={() => { setShowEditInput(!showEditInput); setShowReplyInput(false); }}
> >
<Pencil className="w-3 h-3" /> <Pencil className="w-3 h-3" />
</button> </button>
@ -225,12 +240,12 @@ export function CommentItem(
} }
</div> </div>
{/* 这俩输入框一次只能显示一个 */} {/* 这俩输入框一次只能显示一个 */}
{showReplyInput && !showEditInput && <CommentInput {activeInput && activeInput.type === 'reply' && activeInput.id === comment.id && <CommentInput
user={user} user={user}
onCommentSubmitted={onReply} onCommentSubmitted={onReply}
initIsPrivate={isPrivate} initIsPrivate={isPrivate}
/>} />}
{showEditInput && !showReplyInput && <CommentInput {activeInput && activeInput.type === 'edit' && activeInput.id === comment.id && <CommentInput
user={user} user={user}
initContent={comment.content} initContent={comment.content}
initIsPrivate={isPrivate} initIsPrivate={isPrivate}
@ -249,6 +264,8 @@ export function CommentItem(
comment={reply} comment={reply}
parentComment={comment} parentComment={comment}
onCommentDelete={onReplyDelete} onCommentDelete={onReplyDelete}
activeInput={activeInput}
setActiveInputId={setActiveInputId}
/> />
))} ))}
</div> </div>

View File

@ -33,6 +33,7 @@ export function CommentSection(
const [currentUser, setCurrentUser] = useState<User | null>(null); const [currentUser, setCurrentUser] = useState<User | null>(null);
const [comments, setComments] = useState<Comment[]>([]); const [comments, setComments] = useState<Comment[]>([]);
const [refreshCommentsKey, setRefreshCommentsKey] = useState(0); const [refreshCommentsKey, setRefreshCommentsKey] = useState(0);
const [activeInput, setActiveInput] = useState<{ id: number; type: 'reply' | 'edit' } | null>(null);
// 获取当前登录用户 // 获取当前登录用户
useEffect(() => { useEffect(() => {
@ -98,6 +99,8 @@ export function CommentSection(
comment={comment} comment={comment}
parentComment={null} parentComment={null}
onCommentDelete={onCommentDelete} onCommentDelete={onCommentDelete}
activeInput={activeInput}
setActiveInputId={setActiveInput}
/> />
</div> </div>
))} ))}

View File

@ -7,6 +7,7 @@
"comment": "评论", "comment": "评论",
"comment_failed": "评论失败", "comment_failed": "评论失败",
"comment_success": "评论成功!", "comment_success": "评论成功!",
"comment_unchanged": "评论内容未更改。",
"confirm_delete": "确定吗?", "confirm_delete": "确定吗?",
"content_required": "评论内容不能为空。", "content_required": "评论内容不能为空。",
"delete": "删除", "delete": "删除",
@ -20,8 +21,9 @@
"like_failed": "点赞失败", "like_failed": "点赞失败",
"like_success": "点赞成功", "like_success": "点赞成功",
"login_required": "请先登录后再操作", "login_required": "请先登录后再操作",
"placeholder": "写你的评论...", "placeholder": "写你的评论...",
"private": "私密评论", "private": "私密评论",
"private_placeholder": "悄悄地说一句...",
"reply": "回复", "reply": "回复",
"submit": "提交", "submit": "提交",
"unlike": "取消点赞", "unlike": "取消点赞",