mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-27 03:26:29 +00:00
✨ feat: 添加评论功能,重构评论输入和列表组件,支持多种目标类型,更新国际化文本
All checks were successful
Push to Helm Chart Repository / build (push) Successful in 18s
All checks were successful
Push to Helm Chart Repository / build (push) Successful in 18s
This commit is contained in:
@ -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<User | null>(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<User | null>(null);
|
||||
const [commentContent, setCommentContent] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
getLoginUser()
|
||||
.then(response => {
|
||||
setUser(response.data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("获取用户信息失败:", error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleCommentSubmit = async () => {
|
||||
if (!user) {
|
||||
toast.error(<NeedLogin>{t("login_required")}</NeedLogin>);
|
||||
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 (
|
||||
<div>
|
||||
<div className="flex py-4">
|
||||
{/* Avatar */}
|
||||
<div>
|
||||
<div onClick={user ? undefined : toLogin} className="flex-shrink-0 w-10 h-10">
|
||||
{user && getGravatarByUser(user)}
|
||||
{!user && <CircleUser className="w-full h-full" />}
|
||||
</div>
|
||||
{/* Input Area */}
|
||||
<div className="flex-1 pl-2">
|
||||
<Textarea placeholder="写下你的评论..." className="w-full p-2 border border-gray-300 rounded-md" />
|
||||
<Textarea
|
||||
placeholder={t("placeholder")}
|
||||
className="w-full p-2 border border-gray-300 rounded-md"
|
||||
value={commentContent}
|
||||
onChange={(e) => setCommentContent(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<button className="px-2 py-1 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors">
|
||||
提交
|
||||
<button onClick={handleCommentSubmit} className="px-2 py-1 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors">
|
||||
{t("submit")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,22 +2,24 @@
|
||||
|
||||
import type { Comment } from "@/models/comment";
|
||||
import { CommentInput } from "@/components/comment/comment-input";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
import { listComments } from "@/api/comment";
|
||||
import { OrderBy } from "@/models/common";
|
||||
import { CommentItem } from "./comment-item";
|
||||
import { Separator } from "../ui/separator";
|
||||
import { TargetType } from "@/models/types";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
|
||||
interface CommentAreaProps {
|
||||
targetType: 'post' | 'page';
|
||||
targetType: TargetType;
|
||||
targetId: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default function CommentSection(props: CommentAreaProps) {
|
||||
const { targetType, targetId } = props;
|
||||
const [comments, setComments] = useState<Comment[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [newComment, setNewComment] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
@ -33,30 +35,57 @@ export default function CommentSection(props: CommentAreaProps) {
|
||||
.then(response => {
|
||||
setComments(response.data);
|
||||
})
|
||||
.catch(err => {
|
||||
setError("加载评论失败,请稍后再试。");
|
||||
console.error("Error loading comments:", err);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [targetType, targetId]);
|
||||
|
||||
const onCommentSubmitted = () => {
|
||||
// 重新加载评论列表
|
||||
listComments({
|
||||
targetType,
|
||||
targetId,
|
||||
depth: 0,
|
||||
orderBy: OrderBy.CreatedAt,
|
||||
desc: true,
|
||||
page: 1,
|
||||
size: 10
|
||||
})
|
||||
.then(response => {
|
||||
setComments(response.data);
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Separator className="my-16" />
|
||||
<div className="font-bold text-2xl">评论</div>
|
||||
<CommentInput />
|
||||
{loading && <p>加载中...</p>}
|
||||
{error && <p className="text-red-500">{error}</p>}
|
||||
<CommentInput targetType={targetType} targetId={targetId} onCommentSubmitted={onCommentSubmitted} />
|
||||
<div className="mt-4">
|
||||
{comments.map(comment => (
|
||||
<div key={comment.id}>
|
||||
<Separator className="my-2" />
|
||||
<CommentItem {...comment} />
|
||||
</div>
|
||||
))}
|
||||
<Suspense fallback={<CommentLoading />}>
|
||||
{comments.map(comment => (
|
||||
<div key={comment.id}>
|
||||
<Separator className="my-2" />
|
||||
<CommentItem {...comment} />
|
||||
</div>
|
||||
))}
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function CommentLoading() {
|
||||
return (
|
||||
<div className="space-y-6 py-8">
|
||||
{[...Array(3)].map((_, i) => (
|
||||
<div key={i} className="flex gap-3">
|
||||
<Skeleton className="w-10 h-10 rounded-full" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton className="h-4 w-1/4" />
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
<Skeleton className="h-4 w-2/3" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user