Files
jiang13-bbs/frontend/components/CommentSection.tsx
freefire 0c2b0d2d6a 首次提交:姜十三论坛
后端 Go+Gin:认证/CSRF/限流、板块、帖子、评论、点赞、通知、用户资料、置顶推荐;前端 Next.js 16:发帖/编辑/删除、搜索、分页、点赞、通知中心、设置;基础设施 docker-compose 与配置模板;添加 .gitignore 与专有许可证(保留所有权利)
2026-09-12 01:11:05 +08:00

122 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { Trash2 } from "lucide-react";
import { apiCreateComment, apiDeleteComment, apiMe, type Comment, type User } from "@/lib/api";
interface CommentSectionProps {
postId: string;
comments: Comment[];
}
export default function CommentSection({ postId, comments }: CommentSectionProps) {
const router = useRouter();
const [content, setContent] = useState("");
const [submitting, setSubmitting] = useState(false);
const [user, setUser] = useState<User | null>(null);
const [deletingId, setDeletingId] = useState<number | null>(null);
useEffect(() => {
apiMe()
.then((res) => setUser(res.user || null))
.catch(() => setUser(null));
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!content.trim()) return;
setSubmitting(true);
try {
const res = await apiCreateComment(postId, content);
if (res.comment) {
setContent("");
router.refresh();
} else if (res.error === "未登录") {
router.push("/login");
} else {
alert(res.error || "评论失败");
}
} finally {
setSubmitting(false);
}
};
const handleDelete = async (commentId: number) => {
if (!confirm("确定要删除这条评论吗?")) return;
setDeletingId(commentId);
try {
const res = await apiDeleteComment(postId, commentId);
if (res.message) {
router.refresh();
} else {
alert(res.error || "删除失败");
}
} finally {
setDeletingId(null);
}
};
const canDelete = (comment: Comment) =>
user && (user.id === comment.user.id || user.role === "admin");
return (
<section className="mt-8 pt-6 border-t" style={{ borderColor: "var(--border)" }}>
<h2 className="text-xl font-bold mb-4">评论 ({comments.length})</h2>
<form onSubmit={handleSubmit} className="mb-6">
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="写下你的评论..."
className="w-full p-3 rounded-lg border resize-none h-24"
style={{ borderColor: "var(--border)", background: "var(--bg)", color: "var(--text)" }}
/>
<button
type="submit"
disabled={submitting || !content.trim()}
className="mt-2 px-4 py-2 rounded-lg text-white disabled:opacity-50"
style={{ background: "var(--primary)" }}
>
{submitting ? "发送中..." : "发表评论"}
</button>
</form>
<div className="space-y-4">
{comments.map((comment) => (
<div key={comment.id} className="p-4 rounded-lg" style={{ background: "var(--bg-secondary)" }}>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<Link href={`/u/${comment.user.id}`} className="font-medium hover:underline" style={{ color: "var(--primary)" }}>
{comment.user.nickname}
</Link>
<span className="text-sm" style={{ color: "var(--text-secondary)" }}>
{new Date(comment.created_at).toLocaleString("zh-CN")}
</span>
</div>
{canDelete(comment) && (
<button
onClick={() => handleDelete(comment.id)}
disabled={deletingId === comment.id}
className="flex items-center gap-1 text-sm text-red-600 hover:opacity-80 disabled:opacity-50"
>
<Trash2 size={14} />
{deletingId === comment.id ? "删除中" : "删除"}
</button>
)}
</div>
<p style={{ color: "var(--text)" }}>{comment.content}</p>
</div>
))}
{comments.length === 0 && (
<p className="text-center py-8" style={{ color: "var(--text-secondary)" }}>
暂无评论,来抢沙发吧!
</p>
)}
</div>
</section>
);
}