feat(admin): 补全 admin 模块核心功能与产品体验优化
修复 v4 报告中的 13 个产品体验问题:新增用户管理列表页和系统设置页,重组导航菜单并补充缺失入口,增加角色切换机制,Dashboard 增加快捷操作和 recharts 趋势图表,考勤增加统计概览,排课增加课表网格视图,统一 Toast 操作反馈,同步更新架构文档
This commit is contained in:
@@ -9,8 +9,9 @@ import { requirePermission, getAuthContext } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { getSearchParam, type SearchParams } from "@/shared/lib/utils"
|
||||
import { getAdminClasses } from "@/modules/classes/data-access"
|
||||
import { getAttendanceRecords } from "@/modules/attendance/data-access"
|
||||
import { getAttendanceRecords, getAttendanceStats } from "@/modules/attendance/data-access"
|
||||
import { AttendanceFilters } from "@/modules/attendance/components/attendance-filters"
|
||||
import { AttendanceStatsCards } from "@/modules/attendance/components/attendance-stats-cards"
|
||||
import { AttendanceRecordList } from "@/modules/attendance/components/attendance-record-list"
|
||||
import type { AttendanceStatus } from "@/modules/attendance/types"
|
||||
|
||||
@@ -50,6 +51,13 @@ export default async function AdminAttendancePage({
|
||||
date: date && date.length > 0 ? date : undefined,
|
||||
})
|
||||
|
||||
const stats = await getAttendanceStats({
|
||||
scope: ctx.dataScope,
|
||||
currentUserId: ctx.userId,
|
||||
classId: classId && classId !== "all" ? classId : undefined,
|
||||
date: date && date.length > 0 ? date : undefined,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="h-full flex-1 flex-col space-y-8 p-8 md:flex">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
@@ -65,6 +73,8 @@ export default async function AdminAttendancePage({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<AttendanceStatsCards stats={stats} />
|
||||
|
||||
<AttendanceFilters classes={classOptions} />
|
||||
|
||||
{result.items.length === 0 && !classId && !status && !date ? (
|
||||
|
||||
@@ -3,15 +3,19 @@ import { PlusCircle, ClipboardList } from "lucide-react"
|
||||
import type { Metadata } from "next"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import { EmptyState } from "@/shared/components/ui/empty-state"
|
||||
import { getSearchParam, type SearchParams } from "@/shared/lib/utils"
|
||||
import {
|
||||
getAdminClassesForScheduling,
|
||||
getScheduleChanges,
|
||||
getScheduleEntriesForAdmin,
|
||||
} from "@/modules/scheduling/data-access"
|
||||
import { ScheduleChangeList } from "@/modules/scheduling/components/schedule-change-list"
|
||||
import { ScheduleConflictsView } from "@/modules/scheduling/components/schedule-conflicts-view"
|
||||
import { ScheduleGridView } from "@/modules/scheduling/components/schedule-grid-view"
|
||||
import type { ScheduleChangeStatus } from "@/modules/scheduling/types"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -29,15 +33,17 @@ export default async function AdminSchedulingChangesPage({
|
||||
}: {
|
||||
searchParams: Promise<SearchParams>
|
||||
}): Promise<JSX.Element> {
|
||||
await requirePermission(Permissions.SCHEDULE_ADJUST)
|
||||
const sp = await searchParams
|
||||
const statusParam = getSearchParam(sp, "status")
|
||||
const status = isValidStatus(statusParam) ? statusParam : undefined
|
||||
const classIdParam = getSearchParam(sp, "classId")
|
||||
const classId = classIdParam && classIdParam !== "all" ? classIdParam : undefined
|
||||
|
||||
const [classes, items] = await Promise.all([
|
||||
const [classes, items, scheduleEntries] = await Promise.all([
|
||||
getAdminClassesForScheduling(),
|
||||
getScheduleChanges({ status, classId }),
|
||||
getScheduleEntriesForAdmin(),
|
||||
])
|
||||
const classOptions = classes.map((c) => ({ id: c.id, name: c.name, grade: c.grade }))
|
||||
|
||||
@@ -87,6 +93,14 @@ export default async function AdminSchedulingChangesPage({
|
||||
<ScheduleConflictsView classes={classOptions} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">课表网格</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
按班级查看当前课表分布。
|
||||
</p>
|
||||
<ScheduleGridView entries={scheduleEntries} classes={classOptions} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
22
src/app/(dashboard)/admin/settings/page.tsx
Normal file
22
src/app/(dashboard)/admin/settings/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { Metadata } from "next"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { AdminSettingsView } from "@/modules/settings/components/admin-settings-view"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "系统设置 - Next_Edu",
|
||||
description: "管理系统基础信息与运行参数",
|
||||
}
|
||||
|
||||
export const dynamic = "force-dynamic"
|
||||
|
||||
export default async function AdminSettingsPage(): Promise<JSX.Element> {
|
||||
await requirePermission(Permissions.SETTINGS_ADMIN)
|
||||
return (
|
||||
<div className="flex h-full flex-col p-8">
|
||||
<AdminSettingsView />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
48
src/app/(dashboard)/admin/users/page.tsx
Normal file
48
src/app/(dashboard)/admin/users/page.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { Metadata } from "next"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { getSearchParam, type SearchParams } from "@/shared/lib/utils"
|
||||
import { getAdminUsers, getAdminUserRoles } from "@/modules/users/data-access"
|
||||
import { AdminUsersView } from "@/modules/users/components/admin-users-view"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "用户管理 - Next_Edu",
|
||||
description: "管理系统所有用户",
|
||||
}
|
||||
|
||||
export const dynamic = "force-dynamic"
|
||||
|
||||
export default async function AdminUsersPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<SearchParams>
|
||||
}): Promise<JSX.Element> {
|
||||
await requirePermission(Permissions.USER_MANAGE)
|
||||
|
||||
const sp = await searchParams
|
||||
const page = Number(getSearchParam(sp, "page") ?? "1") || 1
|
||||
const search = getSearchParam(sp, "search") ?? ""
|
||||
const role = getSearchParam(sp, "role") ?? ""
|
||||
|
||||
const [result, roleOptions] = await Promise.all([
|
||||
getAdminUsers({ page, search: search || undefined, role: role || undefined }),
|
||||
getAdminUserRoles(),
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col space-y-6 p-8">
|
||||
<AdminUsersView
|
||||
users={result.items}
|
||||
roleOptions={roleOptions}
|
||||
page={result.page}
|
||||
pageSize={result.pageSize}
|
||||
total={result.total}
|
||||
totalPages={result.totalPages}
|
||||
search={search}
|
||||
roleFilter={role}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user