2025.11.27.17.50
This commit is contained in:
5
src/pages/admin/articles/create.tsx
Normal file
5
src/pages/admin/articles/create.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import ArticleEditor from '@/components/admin/ArticleEditor';
|
||||
|
||||
export default function CreateArticlePage() {
|
||||
return <ArticleEditor mode="create" />;
|
||||
}
|
||||
17
src/pages/admin/articles/edit/[id].tsx
Normal file
17
src/pages/admin/articles/edit/[id].tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import ArticleEditor from '@/components/admin/ArticleEditor';
|
||||
import AdminLayout from '@/components/admin/AdminLayout';
|
||||
|
||||
export default function EditArticlePage() {
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
|
||||
if (!id) return null;
|
||||
|
||||
return (
|
||||
<AdminLayout>
|
||||
<ArticleEditor mode="edit" articleId={id as string} />
|
||||
</AdminLayout>
|
||||
);
|
||||
}
|
||||
238
src/pages/admin/articles/index.tsx
Normal file
238
src/pages/admin/articles/index.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Link from 'next/link';
|
||||
import AdminLayout from '@/components/admin/AdminLayout';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Plus, Search, Edit, Trash2, Eye, Loader2 } from 'lucide-react';
|
||||
|
||||
interface Article {
|
||||
_id: string;
|
||||
文章标题: string;
|
||||
分类ID: { _id: string; 分类名称: string } | null;
|
||||
作者ID: { _id: string; username: string } | null;
|
||||
发布状态: 'draft' | 'published' | 'offline';
|
||||
统计数据: {
|
||||
阅读数: number;
|
||||
点赞数: number;
|
||||
};
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export default function ArticlesPage() {
|
||||
const router = useRouter();
|
||||
const [articles, setArticles] = useState<Article[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [deleteId, setDeleteId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchArticles();
|
||||
}, [page, searchTerm]);
|
||||
|
||||
const fetchArticles = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch(`/api/admin/articles?page=${page}&limit=10&search=${searchTerm}`);
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
setArticles(data.articles);
|
||||
setTotalPages(data.pagination.pages);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch articles', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!deleteId) return;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/articles/${deleteId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (res.ok) {
|
||||
fetchArticles();
|
||||
setDeleteId(null);
|
||||
} else {
|
||||
alert('删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to delete article', error);
|
||||
alert('删除出错');
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case 'published':
|
||||
return <Badge className="bg-green-500">已发布</Badge>;
|
||||
case 'draft':
|
||||
return <Badge variant="secondary">草稿</Badge>;
|
||||
case 'offline':
|
||||
return <Badge variant="destructive">已下架</Badge>;
|
||||
default:
|
||||
return <Badge variant="outline">{status}</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AdminLayout>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold tracking-tight">资源管理</h2>
|
||||
<p className="text-muted-foreground">
|
||||
管理网站的所有文章和资源内容。
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/admin/articles/create">
|
||||
<Button>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
发布新资源
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="relative flex-1 max-w-sm">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜索文章标题..."
|
||||
className="pl-8"
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
setPage(1); // 重置到第一页
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border bg-white">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>标题</TableHead>
|
||||
<TableHead>分类</TableHead>
|
||||
<TableHead>作者</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>数据 (阅/赞)</TableHead>
|
||||
<TableHead>发布时间</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="h-24 text-center">
|
||||
<Loader2 className="mx-auto h-6 w-6 animate-spin" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : articles.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="h-24 text-center">
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
articles.map((article) => (
|
||||
<TableRow key={article._id}>
|
||||
<TableCell className="font-medium max-w-[200px] truncate" title={article.文章标题}>
|
||||
{article.文章标题}
|
||||
</TableCell>
|
||||
<TableCell>{article.分类ID?.分类名称 || '-'}</TableCell>
|
||||
<TableCell>{article.作者ID?.username || 'Unknown'}</TableCell>
|
||||
<TableCell>{getStatusBadge(article.发布状态)}</TableCell>
|
||||
<TableCell>
|
||||
{article.统计数据.阅读数} / {article.统计数据.点赞数}
|
||||
</TableCell>
|
||||
<TableCell>{new Date(article.createdAt).toLocaleDateString()}</TableCell>
|
||||
<TableCell className="text-right space-x-2">
|
||||
<Link href={`/admin/articles/edit/${article._id}`}>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-red-500 hover:text-red-600 hover:bg-red-50"
|
||||
onClick={() => setDeleteId(article._id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
||||
disabled={page === 1}
|
||||
>
|
||||
上一页
|
||||
</Button>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
第 {page} 页 / 共 {totalPages} 页
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
||||
disabled={page === totalPages}
|
||||
>
|
||||
下一页
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog open={!!deleteId} onOpenChange={(open) => !open && setDeleteId(null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>确认删除</DialogTitle>
|
||||
<DialogDescription>
|
||||
您确定要删除这篇文章吗?此操作无法撤销。
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setDeleteId(null)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={handleDelete}>
|
||||
确认删除
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user