136 lines
3.6 KiB
TypeScript
136 lines
3.6 KiB
TypeScript
"use client"
|
||
|
||
import { Loader2 } from "lucide-react"
|
||
import dynamic from "next/dynamic"
|
||
import Link from "next/link"
|
||
import { useRouter } from "next/navigation"
|
||
import { useCallback, useEffect, useState } from "react"
|
||
import { toast } from "sonner"
|
||
import { createArticle, getArticle, updateArticle } from "@/actions/article"
|
||
import type { ArticleGroup } from "@/models/article-group"
|
||
|
||
const Editor = dynamic(() => import("@/components/editor/richTextEditor"), {
|
||
ssr: false,
|
||
loading: () => (
|
||
<div className="flex items-center justify-center h-64 text-muted-foreground">
|
||
编辑器加载中...
|
||
</div>
|
||
),
|
||
})
|
||
|
||
interface ArticleEditorProps {
|
||
articleId: string
|
||
groups: ArticleGroup[]
|
||
}
|
||
|
||
export default function ArticleEditor({
|
||
articleId,
|
||
groups,
|
||
}: ArticleEditorProps) {
|
||
const router = useRouter()
|
||
const isNew = articleId === "new"
|
||
const [loading, setLoading] = useState(!isNew)
|
||
const [title, setTitle] = useState("")
|
||
const [initialContent, setInitialContent] = useState("")
|
||
const [groupId, setGroupId] = useState<string>(
|
||
groups.length > 0 ? String(groups[0].id) : "",
|
||
)
|
||
|
||
useEffect(() => {
|
||
if (!isNew) {
|
||
const id = Number(articleId)
|
||
if (Number.isNaN(id)) {
|
||
toast.error("无效的文章 ID")
|
||
router.push("/articles")
|
||
return
|
||
}
|
||
getArticle(id).then(resp => {
|
||
if (resp.success && resp.data) {
|
||
setTitle(resp.data.title || "")
|
||
setInitialContent(resp.data.content || "")
|
||
if (resp.data.group_id) {
|
||
setGroupId(String(resp.data.group_id))
|
||
}
|
||
} else {
|
||
toast.error(
|
||
resp.success ? "文章数据为空" : resp.message || "文章不存在",
|
||
)
|
||
router.push("/articles")
|
||
}
|
||
setLoading(false)
|
||
})
|
||
}
|
||
}, [articleId, isNew, router])
|
||
|
||
const handleSave = useCallback(
|
||
async (data: { title: string; content: string }) => {
|
||
if (!groupId) {
|
||
toast.error("请选择文章分组")
|
||
return
|
||
}
|
||
if (isNew) {
|
||
const resp = await createArticle({
|
||
title: data.title,
|
||
content: data.content,
|
||
group_id: Number(groupId),
|
||
status: 1,
|
||
})
|
||
if (resp.success) {
|
||
toast.success("文章创建成功")
|
||
router.push("/articles")
|
||
} else {
|
||
toast.error(resp.message || "创建失败")
|
||
}
|
||
} else {
|
||
const id = Number(articleId)
|
||
const resp = await updateArticle({
|
||
id,
|
||
title: data.title,
|
||
content: data.content,
|
||
group_id: Number(groupId),
|
||
status: 1,
|
||
})
|
||
if (resp.success) {
|
||
toast.success("文章保存成功")
|
||
router.push("/articles")
|
||
} else {
|
||
toast.error(resp.message || "保存失败")
|
||
}
|
||
}
|
||
},
|
||
[isNew, articleId, groupId, router],
|
||
)
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="flex items-center justify-center h-full">
|
||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (groups.length === 0) {
|
||
return (
|
||
<div className="flex flex-col items-center justify-center h-full gap-4 text-muted-foreground">
|
||
<p>还没有文章分组,请先创建分组</p>
|
||
<Link href="/article-groups">
|
||
<span className="text-primary hover:underline">前往创建分组</span>
|
||
</Link>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="flex-1 h-full min-h-0">
|
||
<Editor
|
||
content={initialContent}
|
||
title={title}
|
||
groups={groups}
|
||
groupId={groupId}
|
||
onGroupChange={setGroupId}
|
||
onSave={handleSave}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|