实现评论本地更新

This commit is contained in:
2025-06-07 13:49:33 +08:00
parent 96316e3d51
commit 2627c85ec5
5 changed files with 35 additions and 24 deletions

View File

@@ -3,26 +3,28 @@
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { BlogApi } from "@/lib/api"; import { BlogApi } from "@/lib/api";
import { BlogComment } from "@/lib/types/blogComment";
import { Send } from "lucide-react"; import { Send } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
export function BlogCommentTool({ blogId }: { blogId: string }) { export function BlogCommentTool({ blogId, onInsertComment }: { blogId: string, onInsertComment: (b: BlogComment) => void }) {
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
const submit = async () => { const submit = async () => {
if (comment.trim().length === 0) return;
const res = await BlogApi.createComment(blogId, comment); const res = await BlogApi.createComment(blogId, comment);
if (res) { if (res) {
toast.success('发布成功'); toast.success('发布成功');
setComment(''); setComment('');
// 提交界面刷新 onInsertComment(res);
} }
} }
return ( return (
<div className="my-3 flex items-end gap-2"> <div className="my-3 flex items-end gap-2">
<Textarea placeholder="评论" onChange={v => setComment(v.target.value)} value={comment} /> <Textarea placeholder="评论" onChange={v => setComment(v.target.value)} value={comment} />
<Button variant='outline' size='icon' onClick={() => submit()}> <Button variant='outline' size='icon' onClick={() => submit()} disabled={comment.trim().length === 0}>
<Send /> <Send />
</Button> </Button>
</div> </div>

View File

@@ -1,23 +1,34 @@
import useSWR from "swr"; import useSWR from "swr";
import { BlogCommentTool } from "./BlogCommentTool"; import { BlogCommentTool } from "./BlogCommentTool";
import { BlogApi } from "@/lib/api"; import { BlogApi } from "@/lib/api";
import { BlogComment } from "@/lib/types/blogComment";
export function BlogComments({ blogId }: { blogId: string }) { export function BlogComments({ blogId }: { blogId: string }) {
const { data, isLoading, error } = useSWR( const { data, isLoading, error, mutate } = useSWR(
`/api/blog/${blogId}/comments`, `/api/blog/${blogId}/comments`,
() => BlogApi.getComments(blogId), () => BlogApi.getComments(blogId),
) )
const insertComment = async (newOne: BlogComment) => {
await mutate(
(comments) => {
if (!comments) return [newOne];
return [newOne, ...comments]
},
{ revalidate: false }
)
}
return ( return (
data && <div className="" > data && <div className="" >
<h1 className="px-2 border-l-4 border-zinc-300"> {data.length}</h1> <h1 className="px-2 border-l-4 border-zinc-300"> {data.length}</h1>
<BlogCommentTool blogId={blogId} /> <BlogCommentTool blogId={blogId} onInsertComment={insertComment} />
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{ {
data.filter(d => !d.parentId) data.filter(d => !d.parentId)
.map(d => ( .map(d => (
<div key={d.id}> <div key={d.id}>
<h1 className="text-zinc-500">{d.user ? d.user : '匿名'}</h1> <h1 className="text-zinc-500">{d.user ? d.user.nickname : '匿名'}</h1>
<div>{d.content}</div> <div>{d.content}</div>
<div className="text-xs text-zinc-500 flex gap-2"> <div className="text-xs text-zinc-500 flex gap-2">
<p>{new Date(d.createdAt).toLocaleString()}</p> <p>{new Date(d.createdAt).toLocaleString()}</p>
@@ -30,7 +41,7 @@ export function BlogComments({ blogId }: { blogId: string }) {
{ {
data.filter(c => c.parentId === d.id).map(c => ( data.filter(c => c.parentId === d.id).map(c => (
<div key={c.id}> <div key={c.id}>
<h1 className="text-zinc-500">{c.user ? c.user : '匿名'}</h1> <h1 className="text-zinc-500">{c.user ? c.user.nickname : '匿名'}</h1>
<div>{c.content}</div> <div>{c.content}</div>
<p className="text-xs text-zinc-500 flex gap-2"> <p className="text-xs text-zinc-500 flex gap-2">
<p>{new Date().toLocaleString()}</p> <p>{new Date().toLocaleString()}</p>

View File

@@ -1,14 +1,8 @@
import { BlogComment } from "@/lib/types/blogComment";
import fetcher from "../fetcher"; import fetcher from "../fetcher";
export async function createComment(blogId: string, content: string) { export async function createComment(blogId: string, content: string) {
return fetcher<{ return fetcher<BlogComment>(`/api/blog/${blogId}/comment`, {
blogId: string;
content: string;
createdAt: string
deletedAt: null; // 原则上能看到就是null
id: string;
parentId: string | null;
}>(`/api/blog/${blogId}/comment`, {
method: 'POST', method: 'POST',
body: JSON.stringify({ content }), body: JSON.stringify({ content }),
}); });

View File

@@ -1,13 +1,6 @@
import { BlogComment } from "@/lib/types/blogComment";
import fetcher from "../fetcher"; import fetcher from "../fetcher";
export async function getComments(blogId: string) { export async function getComments(blogId: string) {
return fetcher<{ return fetcher<BlogComment[]>(`/api/blog/${blogId}/comments`, { method: 'GET' });
blogId: string;
content: string;
createdAt: string;
deletedAt: string | null;// 原则上能看到就是null
id: string;
parentId: string | null; // 如果是回复则有parentId
user: null;// TODO需要完善
}[]>(`/api/blog/${blogId}/comments`, { method: 'GET' });
} }

View File

@@ -0,0 +1,11 @@
import { User } from "./user";
export interface BlogComment {
id: string;
blogId: string;
content: string;
createdAt: string;
deletedAt: string | null;
parentId: string | null;
user: User | null;
}