feat(admin): enhance admin dashboard, user management, and content editor UX

- Dashboard: add stats API with content status distribution, recent notifications,
  and recent content updates; display active users, pending reviews, unread counts
- User management: full CRUD with role assignment, search, pagination, delete dialog
- Notification center: list with unread filter, mark as read, mark all as read,
  pagination, and auto-refresh unread count
- Content editor: form validation (required fields, slug format, blur-triggered
  errors), auto-save with 3s debounce and status indicator, publish confirmation
  dialog, unsaved changes warning on leave
- Admin layout: add navigation links for user management, roles, and notifications
- admin-api: make request() method public for custom API calls
- gitignore: add reports/mutation/ to exclude mutation test output
This commit is contained in:
2026-07-31 23:05:24 +08:00
parent a995f40eae
commit c480772aec
9 changed files with 1942 additions and 120 deletions
+82
View File
@@ -0,0 +1,82 @@
import { NextRequest } from 'next/server';
import { prisma } from '@/lib/db';
import { authenticateRequest } from '@/lib/auth';
import { success, unauthorized, internalError } from '@/lib/api-response';
// GET /api/admin/stats - 获取仪表盘统计数据
export async function GET(request: NextRequest) {
const user = authenticateRequest(request);
if (!user) return unauthorized();
try {
const [modelCount, itemCount, zoneCount, userCount, pendingReviewCount, unreadCount] = await Promise.all([
prisma.contentModel.count(),
prisma.contentItem.count(),
prisma.contentZone.count(),
prisma.user.count({ where: { status: 1 } }),
prisma.contentItem.count({ where: { status: 'review' } }),
prisma.notification.count({ where: { userId: user.userId, read: false } }),
]);
// 获取每个内容模型的条目数
const modelItemCounts = await prisma.contentItem.groupBy({
by: ['modelCode'],
_count: { modelCode: true },
});
// 获取不同状态的条目数
const statusCounts = await prisma.contentItem.groupBy({
by: ['status'],
_count: { status: true },
});
// 获取最近 5 条通知
const recentNotifications = await prisma.notification.findMany({
where: { userId: user.userId },
orderBy: { createdAt: 'desc' },
take: 5,
select: {
id: true,
type: true,
title: true,
read: true,
createdAt: true,
},
});
// 获取最近 5 条更新的内容
const recentItems = await prisma.contentItem.findMany({
orderBy: { updatedAt: 'desc' },
take: 5,
select: {
id: true,
title: true,
modelCode: true,
status: true,
updatedAt: true,
},
});
return success({
models: modelCount,
items: itemCount,
zones: zoneCount,
activeUsers: userCount,
pendingReviews: pendingReviewCount,
unreadNotifications: unreadCount,
modelItemCounts: modelItemCounts.map((m) => ({
modelCode: m.modelCode,
count: m._count.modelCode,
})),
statusCounts: statusCounts.map((s) => ({
status: s.status,
count: s._count.status,
})),
recentNotifications,
recentItems,
});
} catch (error) {
console.error('Get stats error:', error);
return internalError();
}
}