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 */}
-