feat: 添加评论动画样式和登录组件

This commit is contained in:
2025-09-10 09:59:22 +08:00
parent 4fb39110ad
commit 052d72c8c0
2 changed files with 133 additions and 126 deletions

View File

@ -1,177 +1,177 @@
package service package service
import ( import (
"context" "context"
"strconv" "strconv"
"github.com/snowykami/neo-blog/pkg/constant" "github.com/snowykami/neo-blog/pkg/constant"
"github.com/snowykami/neo-blog/internal/ctxutils" "github.com/snowykami/neo-blog/internal/ctxutils"
"github.com/snowykami/neo-blog/internal/dto" "github.com/snowykami/neo-blog/internal/dto"
"github.com/snowykami/neo-blog/internal/model" "github.com/snowykami/neo-blog/internal/model"
"github.com/snowykami/neo-blog/internal/repo" "github.com/snowykami/neo-blog/internal/repo"
"github.com/snowykami/neo-blog/pkg/errs" "github.com/snowykami/neo-blog/pkg/errs"
) )
type CommentService struct{} type CommentService struct{}
func NewCommentService() *CommentService { func NewCommentService() *CommentService {
return &CommentService{} return &CommentService{}
} }
func (cs *CommentService) CreateComment(ctx context.Context, req *dto.CreateCommentReq) error { func (cs *CommentService) CreateComment(ctx context.Context, req *dto.CreateCommentReq) error {
currentUser, ok := ctxutils.GetCurrentUser(ctx) currentUser, ok := ctxutils.GetCurrentUser(ctx)
if !ok { if !ok {
return errs.ErrUnauthorized return errs.ErrUnauthorized
} }
if ok, err := cs.checkTargetExists(req.TargetID, req.TargetType); !ok { if ok, err := cs.checkTargetExists(req.TargetID, req.TargetType); !ok {
if err != nil { if err != nil {
return errs.New(errs.ErrBadRequest.Code, "target not found", err) return errs.New(errs.ErrBadRequest.Code, "target not found", err)
} }
return errs.ErrBadRequest return errs.ErrBadRequest
} }
comment := &model.Comment{ comment := &model.Comment{
Content: req.Content, Content: req.Content,
ReplyID: req.ReplyID, ReplyID: req.ReplyID,
TargetID: req.TargetID, TargetID: req.TargetID,
TargetType: req.TargetType, TargetType: req.TargetType,
UserID: currentUser.ID, UserID: currentUser.ID,
IsPrivate: req.IsPrivate, IsPrivate: req.IsPrivate,
} }
err := repo.Comment.CreateComment(comment) err := repo.Comment.CreateComment(comment)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func (cs *CommentService) UpdateComment(ctx context.Context, req *dto.UpdateCommentReq) error { func (cs *CommentService) UpdateComment(ctx context.Context, req *dto.UpdateCommentReq) error {
currentUser, ok := ctxutils.GetCurrentUser(ctx) currentUser, ok := ctxutils.GetCurrentUser(ctx)
if !ok { if !ok {
return errs.ErrUnauthorized return errs.ErrUnauthorized
} }
comment, err := repo.Comment.GetComment(strconv.Itoa(int(req.CommentID))) comment, err := repo.Comment.GetComment(strconv.Itoa(int(req.CommentID)))
if err != nil { if err != nil {
return err return err
} }
if currentUser.ID != comment.UserID { if currentUser.ID != comment.UserID {
return errs.ErrForbidden return errs.ErrForbidden
} }
comment.Content = req.Content comment.Content = req.Content
comment.IsPrivate = req.IsPrivate comment.IsPrivate = req.IsPrivate
err = repo.Comment.UpdateComment(comment) err = repo.Comment.UpdateComment(comment)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func (cs *CommentService) DeleteComment(ctx context.Context, commentID string) error { func (cs *CommentService) DeleteComment(ctx context.Context, commentID string) error {
currentUser, ok := ctxutils.GetCurrentUser(ctx) currentUser, ok := ctxutils.GetCurrentUser(ctx)
if !ok { if !ok {
return errs.ErrUnauthorized return errs.ErrUnauthorized
} }
if commentID == "" { if commentID == "" {
return errs.ErrBadRequest return errs.ErrBadRequest
} }
comment, err := repo.Comment.GetComment(commentID) comment, err := repo.Comment.GetComment(commentID)
if err != nil { if err != nil {
return errs.New(errs.ErrNotFound.Code, "comment not found", err) return errs.New(errs.ErrNotFound.Code, "comment not found", err)
} }
if comment.UserID != currentUser.ID { if comment.UserID != currentUser.ID {
return errs.ErrForbidden return errs.ErrForbidden
} }
if err := repo.Comment.DeleteComment(commentID); err != nil { if err := repo.Comment.DeleteComment(commentID); err != nil {
return err return err
} }
return nil return nil
} }
func (cs *CommentService) GetComment(ctx context.Context, commentID string) (*dto.CommentDto, error) { func (cs *CommentService) GetComment(ctx context.Context, commentID string) (*dto.CommentDto, error) {
comment, err := repo.Comment.GetComment(commentID) comment, err := repo.Comment.GetComment(commentID)
if err != nil { if err != nil {
return nil, errs.New(errs.ErrNotFound.Code, "comment not found", err) return nil, errs.New(errs.ErrNotFound.Code, "comment not found", err)
} }
commentDto := dto.CommentDto{ commentDto := dto.CommentDto{
ID: comment.ID, ID: comment.ID,
TargetID: comment.TargetID, TargetID: comment.TargetID,
TargetType: comment.TargetType, TargetType: comment.TargetType,
Content: comment.Content, Content: comment.Content,
ReplyID: comment.ReplyID, ReplyID: comment.ReplyID,
Depth: comment.Depth, Depth: comment.Depth,
CreatedAt: comment.CreatedAt.String(), CreatedAt: comment.CreatedAt.String(),
UpdatedAt: comment.UpdatedAt.String(), UpdatedAt: comment.UpdatedAt.String(),
User: comment.User.ToDto(), User: comment.User.ToDto(),
} }
return &commentDto, err return &commentDto, err
} }
func (cs *CommentService) GetCommentList(ctx context.Context, req *dto.GetCommentListReq) ([]dto.CommentDto, error) { func (cs *CommentService) GetCommentList(ctx context.Context, req *dto.GetCommentListReq) ([]dto.CommentDto, error) {
currentUserID := uint(0) currentUserID := uint(0)
if currentUser, ok := ctxutils.GetCurrentUser(ctx); ok { if currentUser, ok := ctxutils.GetCurrentUser(ctx); ok {
currentUserID = currentUser.ID currentUserID = currentUser.ID
} }
comments, err := repo.Comment.ListComments(currentUserID, req.TargetID, req.CommentID, req.TargetType, req.Page, req.Size, req.OrderBy, req.Desc, req.Depth) comments, err := repo.Comment.ListComments(currentUserID, req.TargetID, req.CommentID, req.TargetType, req.Page, req.Size, req.OrderBy, req.Desc, req.Depth)
if err != nil { if err != nil {
return nil, errs.New(errs.ErrInternalServer.Code, "failed to list comments", err) return nil, errs.New(errs.ErrInternalServer.Code, "failed to list comments", err)
} }
commentDtos := make([]dto.CommentDto, 0) commentDtos := make([]dto.CommentDto, 0)
for _, comment := range comments { for _, comment := range comments {
replyCount, _ := repo.Comment.CountReplyComments(currentUserID, comment.ID) replyCount, _ := repo.Comment.CountReplyComments(currentUserID, comment.ID)
isLiked := false isLiked := false
if currentUserID != 0 { if currentUserID != 0 {
isLiked, _ = repo.Like.IsLiked(currentUserID, comment.ID, constant.TargetTypeComment) isLiked, _ = repo.Like.IsLiked(currentUserID, comment.ID, constant.TargetTypeComment)
} }
commentDto := dto.CommentDto{ commentDto := dto.CommentDto{
ID: comment.ID, ID: comment.ID,
Content: comment.Content, Content: comment.Content,
TargetID: comment.TargetID, TargetID: comment.TargetID,
TargetType: comment.TargetType, TargetType: comment.TargetType,
ReplyID: comment.ReplyID, ReplyID: comment.ReplyID,
CreatedAt: comment.CreatedAt.String(), CreatedAt: comment.CreatedAt.String(),
UpdatedAt: comment.UpdatedAt.String(), UpdatedAt: comment.UpdatedAt.String(),
Depth: comment.Depth, Depth: comment.Depth,
User: comment.User.ToDto(), User: comment.User.ToDto(),
ReplyCount: replyCount, ReplyCount: replyCount,
LikeCount: comment.LikeCount, LikeCount: comment.LikeCount,
IsLiked: isLiked, IsLiked: isLiked,
IsPrivate: comment.IsPrivate, IsPrivate: comment.IsPrivate,
} }
commentDtos = append(commentDtos, commentDto) commentDtos = append(commentDtos, commentDto)
} }
return commentDtos, nil return commentDtos, nil
} }
func (cs *CommentService) checkTargetExists(targetID uint, targetType string) (bool, error) { func (cs *CommentService) checkTargetExists(targetID uint, targetType string) (bool, error) {
switch targetType { switch targetType {
case constant.TargetTypePost: case constant.TargetTypePost:
if _, err := repo.Post.GetPostByID(strconv.Itoa(int(targetID))); err != nil { if _, err := repo.Post.GetPostByID(strconv.Itoa(int(targetID))); err != nil {
return false, errs.New(errs.ErrNotFound.Code, "post not found", err) return false, errs.New(errs.ErrNotFound.Code, "post not found", err)
} }
default: default:
return false, errs.New(errs.ErrBadRequest.Code, "invalid target type", nil) return false, errs.New(errs.ErrBadRequest.Code, "invalid target type", nil)
} }
return true, nil return true, nil
} }

View File

@ -36,6 +36,7 @@ export function CommentItem(
const [likeCount, setLikeCount] = useState(comment.likeCount); const [likeCount, setLikeCount] = useState(comment.likeCount);
const [liked, setLiked] = useState(comment.isLiked); const [liked, setLiked] = useState(comment.isLiked);
const [canClickLike, setCanClickLike] = useState(true);
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);
@ -45,6 +46,10 @@ export function CommentItem(
const [showEditInput, setShowEditInput] = useState(false); const [showEditInput, setShowEditInput] = useState(false);
const handleToggleLike = () => { const handleToggleLike = () => {
if (!canClickLike) {
return;
}
setCanClickLike(false);
if (!user) { if (!user) {
toast.error(t("login_required"), { toast.error(t("login_required"), {
action: <div className="flex justify-end"> action: <div className="flex justify-end">
@ -58,6 +63,7 @@ export function CommentItem(
}); });
return; return;
} }
setLiked(!liked) // 提前转换状态,让用户觉得响应很快
toggleLike( toggleLike(
{ targetType: TargetType.Comment, targetId: comment.id } { targetType: TargetType.Comment, targetId: comment.id }
).then(res => { ).then(res => {
@ -67,6 +73,7 @@ export function CommentItem(
}).catch(error => { }).catch(error => {
toast.error(t("like_failed") + ": " + error.message); toast.error(t("like_failed") + ": " + error.message);
}); });
setCanClickLike(true);
} }
const reloadReplies = () => { const reloadReplies = () => {