From 6237cddc874c6d9bff4d8725559eeebef1e18e72 Mon Sep 17 00:00:00 2001 From: Snowykami Date: Sun, 7 Sep 2025 23:56:25 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=B7=BB=E5=8A=A0=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E5=8A=9F=E8=83=BD=EF=BC=8C=E9=87=8D=E6=9E=84=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E8=BE=93=E5=85=A5=E5=92=8C=E5=88=97=E8=A1=A8=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E6=94=AF=E6=8C=81=E5=A4=9A=E7=A7=8D=E7=9B=AE?= =?UTF-8?q?=E6=A0=87=E7=B1=BB=E5=9E=8B=EF=BC=8C=E6=9B=B4=E6=96=B0=E5=9B=BD?= =?UTF-8?q?=E9=99=85=E5=8C=96=E6=96=87=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 ++++++ internal/service/comment.go | 10 ++- web/src/api/comment.ts | 3 +- web/src/components/blog-post/blog-post.tsx | 5 +- web/src/components/comment/comment-input.tsx | 62 ++++++++++++++--- web/src/components/comment/index.tsx | 69 +++++++++++++------ .../components/common/image-placeholder.tsx | 7 ++ web/src/components/common/need-login.tsx | 12 ++++ web/src/components/login/login-form.tsx | 34 ++++----- web/src/components/login/to-login.tsx | 0 web/src/components/ui/skeleton.tsx | 13 ++++ web/src/hooks/use-to-login.ts | 13 ++++ web/src/locales/zh-CN.json | 24 +++++++ web/src/models/comment.ts | 7 +- web/src/models/types.ts | 9 +++ web/src/models/user.ts | 2 +- 16 files changed, 235 insertions(+), 57 deletions(-) create mode 100644 web/src/components/common/image-placeholder.tsx create mode 100644 web/src/components/common/need-login.tsx create mode 100644 web/src/components/login/to-login.tsx create mode 100644 web/src/components/ui/skeleton.tsx create mode 100644 web/src/hooks/use-to-login.ts create mode 100644 web/src/models/types.ts diff --git a/README.md b/README.md index ac5a9c6..c9bb942 100644 --- a/README.md +++ b/README.md @@ -110,5 +110,27 @@ pnpm start 可以通过环境变量或者.env.production文件配置后端API端点 +## 开发 + +### 后端 + +```bash +# 启动后端服务器 +go run ./cmd/server +``` + +### 前端 + +```bash +# 进入前端目录 +cd web + +# 安装依赖 +pnpm install + +# 启动前端开发服务器 +pnpm dev +``` + ## 环境变量配置 后端所有环境变量及其示例在[`.env.example`](./.env.example)文件中 \ No newline at end of file diff --git a/internal/service/comment.go b/internal/service/comment.go index 09d95b0..e8c3e61 100644 --- a/internal/service/comment.go +++ b/internal/service/comment.go @@ -2,9 +2,10 @@ package service import ( "context" - "github.com/snowykami/neo-blog/pkg/constant" "strconv" + "github.com/snowykami/neo-blog/pkg/constant" + "github.com/snowykami/neo-blog/internal/ctxutils" "github.com/snowykami/neo-blog/internal/dto" "github.com/snowykami/neo-blog/internal/model" @@ -124,9 +125,12 @@ func (cs *CommentService) GetComment(ctx context.Context, commentID string) (*dt } func (cs *CommentService) GetCommentList(ctx context.Context, req *dto.GetCommentListReq) ([]dto.CommentDto, error) { - currentUser, _ := ctxutils.GetCurrentUser(ctx) + currentUserID := uint(0) + if currentUser, ok := ctxutils.GetCurrentUser(ctx); ok { + currentUserID = currentUser.ID + } - comments, err := repo.Comment.ListComments(currentUser.ID, req.TargetID, req.TargetType, req.Page, req.Size, req.OrderBy, req.Desc, req.Depth) + comments, err := repo.Comment.ListComments(currentUserID, req.TargetID, req.TargetType, req.Page, req.Size, req.OrderBy, req.Desc, req.Depth) if err != nil { return nil, errs.New(errs.ErrInternalServer.Code, "failed to list comments", err) } diff --git a/web/src/api/comment.ts b/web/src/api/comment.ts index 17a3f14..a0e9a87 100644 --- a/web/src/api/comment.ts +++ b/web/src/api/comment.ts @@ -3,6 +3,7 @@ import { CreateCommentRequest, UpdateCommentRequest, Comment } from '@/models/co import type { PaginationParams } from '@/models/common' import { OrderBy } from '@/models/common' import type { BaseResponse } from '@/models/resp' +import { TargetType } from '@/models/types' export async function createComment( @@ -24,7 +25,7 @@ export async function deleteComment(id: number): Promise { } export interface ListCommentsParams { - targetType: 'post' | 'page' + targetType: TargetType targetId: number depth?: number orderBy?: OrderBy diff --git a/web/src/components/blog-post/blog-post.tsx b/web/src/components/blog-post/blog-post.tsx index be5ca65..05be701 100644 --- a/web/src/components/blog-post/blog-post.tsx +++ b/web/src/components/blog-post/blog-post.tsx @@ -5,6 +5,7 @@ import { RenderMarkdown } from "@/components/common/markdown"; import { isMobileByUA } from "@/utils/server/device"; import { calculateReadingTime } from "@/utils/common/post"; import CommentSection from "@/components/comment"; +import { TargetType } from '../../models/types'; function PostMeta({ post }: { post: Post }) { return ( @@ -94,7 +95,7 @@ async function PostHeader({ post }: { post: Post }) { } async function PostContent({ post }: { post: Post }) { - const markdownClass = + const markdownClass = "prose prose-lg max-w-none dark:prose-invert " + // h1-h6 "[&_h1]:scroll-m-20 [&_h1]:text-4xl [&_h1]:font-extrabold [&_h1]:tracking-tight [&_h1]:text-balance [&_h1]:mt-10 [&_h1]:mb-6 " + @@ -138,7 +139,7 @@ async function BlogPost({ post }: { post: Post }) { {/* */} - + ); } diff --git a/web/src/components/comment/comment-input.tsx b/web/src/components/comment/comment-input.tsx index 2d9217c..8b657a1 100644 --- a/web/src/components/comment/comment-input.tsx +++ b/web/src/components/comment/comment-input.tsx @@ -1,37 +1,77 @@ "use client"; import { Textarea } from "@/components/ui/textarea" import { getGravatarByUser } from "@/components/common/gravatar" - +import { toast } from "sonner"; import { useState, useEffect } from "react"; import type { User } from "@/models/user"; import { getLoginUser } from "@/api/user"; +import { createComment } from "@/api/comment"; -export function CommentInput() { - const [user, setUser] = useState(null); // 假设 User 是你的用户模型 - useEffect(()=>{ +import { CircleUser } from "lucide-react"; +import { useTranslations } from "next-intl"; +import { TargetType } from "@/models/types"; +import { useToLogin } from "@/hooks/use-to-login"; +import NeedLogin from "../common/need-login"; + +export function CommentInput( + { targetId, targetType, onCommentSubmitted }: { targetId: number, targetType: TargetType, onCommentSubmitted: () => void } +) { + const t = useTranslations('Comment') + const toLogin = useToLogin() + const [user, setUser] = useState(null); + const [commentContent, setCommentContent] = useState(""); + + useEffect(() => { getLoginUser() .then(response => { setUser(response.data); }) - .catch(error => { - console.error("获取用户信息失败:", error); - }); }, []); + + const handleCommentSubmit = async () => { + if (!user) { + toast.error({t("login_required")}); + return; + } + if (!commentContent.trim()) { + toast.error(t("content_required")); + return; + } + await createComment({ + targetType: targetType, + targetId: targetId, + content: commentContent, + }).then(response => { + setCommentContent(""); + toast.success(t("comment_success")); + onCommentSubmitted(); + }).catch(error => { + toast.error(t("comment_failed") + ": " + + error?.response?.data?.message || error?.message + ); + }); + }; return (
{/* Avatar */} -
+
{user && getGravatarByUser(user)} + {!user && }
{/* Input Area */}
-