feat: 优化评论功能,添加登录提示和国际化支持,重构相关组件
All checks were successful
Push to Helm Chart Repository / build (push) Successful in 10s

This commit is contained in:
2025-09-09 23:21:08 +08:00
parent cb3f602663
commit 4fb39110ad
7 changed files with 278 additions and 229 deletions

View File

@ -214,9 +214,33 @@ func (cr *CommentRepo) ListComments(currentUserID, targetID, commentID uint, tar
return items, nil
}
func (cr *CommentRepo) CountReplyComments(commentID uint) (int64, error) {
func (cr *CommentRepo) CountReplyComments(currentUserID, commentID uint) (int64, error) {
var count int64
if err := GetDB().Model(&model.Comment{}).Where("reply_id = ?", commentID).Count(&count).Error; err != nil {
var masterID uint
// 根据commentID查询所属对象的用户ID
comment, err := cr.GetComment(strconv.Itoa(int(commentID)))
if err != nil {
return 0, err
}
if comment.TargetType == constant.TargetTypePost {
post, err := Post.GetPostByID(strconv.Itoa(int(comment.TargetID)))
if err != nil {
return 0, err
}
masterID = post.UserID
} else {
// 如果不是文章类型,可以根据需要添加其他类型的处理逻辑
return 0, errs.New(http.StatusBadRequest, "unsupported target type for counting replies", nil)
}
query := GetDB().Model(&model.Comment{}).Where("reply_id = ?", commentID)
if currentUserID > 0 {
query = query.Where("(is_private = ? OR (is_private = ? AND (user_id = ? OR user_id = ?)))", false, true, currentUserID, masterID)
} else {
query = query.Where("is_private = ?", false)
}
if err := query.Count(&count).Error; err != nil {
return 0, err
}
return count, nil

View File

@ -138,7 +138,7 @@ func (cs *CommentService) GetCommentList(ctx context.Context, req *dto.GetCommen
commentDtos := make([]dto.CommentDto, 0)
for _, comment := range comments {
replyCount, _ := repo.Comment.CountReplyComments(comment.ID)
replyCount, _ := repo.Comment.CountReplyComments(currentUserID, comment.ID)
isLiked := false
if currentUserID != 0 {
isLiked, _ = repo.Like.IsLiked(currentUserID, comment.ID, constant.TargetTypeComment)

View File

@ -27,15 +27,22 @@ export function CommentInput(
}
) {
const t = useTranslations('Comment')
const handleToLogin = useToLogin()
const toUserProfile = useToUserProfile();
const commonT = useTranslations('Common')
const clickToLogin = useToLogin()
const clickToUserProfile = useToUserProfile();
const [isPrivate, setIsPrivate] = useState(initIsPrivate);
const [commentContent, setCommentContent] = useState(initContent);
const handleCommentSubmit = async () => {
if (!user) {
toast.error(<NeedLogin>{t("login_required")}</NeedLogin>);
// 通知
toast.error(t("login_required"), {
action: {
label: commonT("login"),
onClick: clickToLogin,
},
})
return;
}
if (!commentContent.trim()) {
@ -49,13 +56,13 @@ export function CommentInput(
return (
<div className="fade-in-up">
<div className="flex py-4 fade-in">
<div onClick={user ? () => toUserProfile(user.username) : handleToLogin} className="flex-shrink-0 w-10 h-10 fade-in">
<div onClick={user ? () => clickToUserProfile(user.username) : clickToLogin} className="flex-shrink-0 w-10 h-10 fade-in">
{user ? getGravatarByUser(user) : null}
{!user && <CircleUser className="w-full h-full fade-in" />}
</div>
<div className="flex-1 pl-2 fade-in-up">
<Textarea
placeholder={user?t("placeholder"):t("login_required")}
placeholder={user ? t("placeholder") : t("login_required", { loginButton: "登录" })}
className="w-full p-2 border border-gray-300 rounded-md fade-in-up"
value={commentContent}
onChange={(e) => setCommentContent(e.target.value)}

View File

@ -1,4 +1,4 @@
import { useToUserProfile } from "@/hooks/use-route";
import { useToLogin, useToUserProfile } from "@/hooks/use-route";
import { User } from "@/models/user";
import { useTranslations } from "next-intl";
import { useState } from "react";
@ -29,7 +29,9 @@ export function CommentItem(
}
) {
const t = useTranslations("Comment")
const toUserProfile = useToUserProfile();
const commonT = useTranslations('Common')
const clickToUserProfile = useToUserProfile();
const clickToLogin = useToLogin();
const { confirming, onClick, onBlur } = useDoubleConfirm();
const [likeCount, setLikeCount] = useState(comment.likeCount);
@ -43,6 +45,19 @@ export function CommentItem(
const [showEditInput, setShowEditInput] = useState(false);
const handleToggleLike = () => {
if (!user) {
toast.error(t("login_required"), {
action: <div className="flex justify-end">
<button
onClick={clickToLogin}
className="ml-0 text-left bg-red-400 text-white dark:text-black px-3 py-1 rounded font-semibold hover:bg-red-600 transition-colors"
>
{commonT("login")}
</button>
</div>,
});
return;
}
toggleLike(
{ targetType: TargetType.Comment, targetId: comment.id }
).then(res => {
@ -133,7 +148,7 @@ export function CommentItem(
}
{
parentComment &&
<>{t("reply")} <button onClick={() => toUserProfile(parentComment.user.nickname)} className="text-primary">{parentComment?.user.nickname}</button>: </>
<>{t("reply")} <button onClick={() => clickToUserProfile(parentComment.user.nickname)} className="text-primary">{parentComment?.user.nickname}</button>: </>
}
{comment.content}
</p>

View File

@ -19,7 +19,7 @@
"like": "点赞",
"like_failed": "点赞失败",
"like_success": "点赞成功",
"login_required": "请先登录后再评论。",
"login_required": "请先登录后再操作",
"placeholder": "写你的评论...",
"private": "私密评论",
"reply": "回复",
@ -28,6 +28,9 @@
"unlike_success": "已取消点赞",
"update": "更新"
},
"Common":{
"login": "登录"
},
"Login": {
"welcome": "欢迎回来",
"with_oidc": "使用第三方身份提供者",