feat(admin): 补全 admin 模块核心功能与产品体验优化
修复 v4 报告中的 13 个产品体验问题:新增用户管理列表页和系统设置页,重组导航菜单并补充缺失入口,增加角色切换机制,Dashboard 增加快捷操作和 recharts 趋势图表,考勤增加统计概览,排课增加课表网格视图,统一 Toast 操作反馈,同步更新架构文档
This commit is contained in:
@@ -11,6 +11,13 @@ import {
|
||||
CollapsibleTrigger,
|
||||
} from "@/shared/components/ui/collapsible"
|
||||
import { ScrollArea } from "@/shared/components/ui/scroll-area"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/shared/components/ui/select"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -19,6 +26,7 @@ import {
|
||||
} from "@/shared/components/ui/tooltip"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
import { usePermission } from "@/shared/hooks"
|
||||
import { UnreadMessageBadge } from "@/modules/messaging/components/unread-message-badge"
|
||||
import { useSidebar } from "./sidebar-provider"
|
||||
import { NAV_CONFIG, Role } from "../config/navigation"
|
||||
|
||||
@@ -27,21 +35,28 @@ interface AppSidebarProps {
|
||||
}
|
||||
|
||||
export function AppSidebar({ mode }: AppSidebarProps) {
|
||||
const { expanded, toggleSidebar, isMobile } = useSidebar()
|
||||
const { expanded, toggleSidebar, isMobile, currentRole, setCurrentRole } = useSidebar()
|
||||
const pathname = usePathname()
|
||||
const { permissions, hasRole } = usePermission()
|
||||
const { permissions, roles, hasRole } = usePermission()
|
||||
|
||||
// Determine which role's nav config to use based on session roles
|
||||
let currentRole: Role = "teacher"
|
||||
if (hasRole("admin")) {
|
||||
currentRole = "admin"
|
||||
} else if (hasRole("student")) {
|
||||
currentRole = "student"
|
||||
} else if (hasRole("parent")) {
|
||||
currentRole = "parent"
|
||||
// 自动检测当前角色(优先级 admin > student > parent > teacher)
|
||||
function detectAutoRole(): Role {
|
||||
if (hasRole("admin")) return "admin"
|
||||
if (hasRole("student")) return "student"
|
||||
if (hasRole("parent")) return "parent"
|
||||
return "teacher"
|
||||
}
|
||||
|
||||
const allNavItems = NAV_CONFIG[currentRole] ?? NAV_CONFIG.teacher ?? []
|
||||
// 用户在 NAV_CONFIG 中实际可用的角色(过滤掉未配置的角色)
|
||||
const availableRoles = roles.filter((r) => NAV_CONFIG[r] !== undefined)
|
||||
|
||||
// 如果 context 中有 currentRole 且用户拥有该角色,使用 currentRole;否则自动检测
|
||||
const effectiveRole: Role =
|
||||
currentRole !== null && availableRoles.includes(currentRole)
|
||||
? currentRole
|
||||
: detectAutoRole()
|
||||
|
||||
const allNavItems = NAV_CONFIG[effectiveRole] ?? NAV_CONFIG.teacher ?? []
|
||||
|
||||
// Filter nav items by permission
|
||||
const navItems = allNavItems.filter((item) => {
|
||||
@@ -154,6 +169,7 @@ export function AppSidebar({ mode }: AppSidebarProps) {
|
||||
>
|
||||
<item.icon className="size-4" />
|
||||
<span>{item.title}</span>
|
||||
{item.href === "/messages" ? <UnreadMessageBadge /> : null}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
@@ -163,12 +179,26 @@ export function AppSidebar({ mode }: AppSidebarProps) {
|
||||
|
||||
{/* Sidebar Footer */}
|
||||
<div className="p-4">
|
||||
{availableRoles.length > 1 && (expanded || isMobile) && (
|
||||
<div className="px-2 pb-2">
|
||||
<Select value={effectiveRole} onValueChange={(v) => setCurrentRole(v as Role)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="切换角色" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableRoles.map((r) => (
|
||||
<SelectItem key={r} value={r}>{r}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<button
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
className="hover:bg-sidebar-accent text-sidebar-foreground flex w-full items-center justify-center rounded-md border p-2 text-sm transition-colors"
|
||||
>
|
||||
{expanded ? "Collapse" : <ChevronRight className="size-4" />}
|
||||
{expanded ? "收起" : <ChevronRight className="size-4" />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -9,12 +9,15 @@ import {
|
||||
SheetTitle,
|
||||
} from "@/shared/components/ui/sheet"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
import type { Role } from "@/shared/types/permissions"
|
||||
|
||||
type SidebarContextType = {
|
||||
expanded: boolean
|
||||
setExpanded: (expanded: boolean) => void
|
||||
isMobile: boolean
|
||||
toggleSidebar: () => void
|
||||
currentRole: Role | null
|
||||
setCurrentRole: (role: Role | null) => void
|
||||
}
|
||||
|
||||
const SidebarContext = React.createContext<SidebarContextType | undefined>(
|
||||
@@ -38,6 +41,8 @@ export function SidebarProvider({ children, sidebar }: SidebarProviderProps) {
|
||||
const [expanded, setExpanded] = React.useState(true)
|
||||
const [isMobile, setIsMobile] = React.useState(false)
|
||||
const [openMobile, setOpenMobile] = React.useState(false)
|
||||
// null 表示自动检测(按现有优先级 admin > student > parent > teacher)
|
||||
const [currentRole, setCurrentRole] = React.useState<Role | null>(null)
|
||||
|
||||
React.useEffect(() => {
|
||||
const checkMobile = () => {
|
||||
@@ -62,7 +67,7 @@ export function SidebarProvider({ children, sidebar }: SidebarProviderProps) {
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider
|
||||
value={{ expanded, setExpanded, isMobile, toggleSidebar }}
|
||||
value={{ expanded, setExpanded, isMobile, toggleSidebar, currentRole, setCurrentRole }}
|
||||
>
|
||||
<div className="flex h-screen overflow-hidden w-full flex-col md:flex-row bg-background">
|
||||
{/* Mobile Trigger & Sheet */}
|
||||
|
||||
@@ -18,7 +18,9 @@ import {
|
||||
CalendarCheck,
|
||||
CalendarClock,
|
||||
Stethoscope,
|
||||
BookMarked
|
||||
BookMarked,
|
||||
BookCopy,
|
||||
Files,
|
||||
} from "lucide-react"
|
||||
import type { LucideIcon } from "lucide-react"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
@@ -54,10 +56,28 @@ export const NAV_CONFIG: Partial<Record<Role, NavItem[]>> = {
|
||||
{ title: "Departments", href: "/admin/school/departments" },
|
||||
{ title: "Classes", href: "/admin/school/classes" },
|
||||
{ title: "Academic Year", href: "/admin/school/academic-year" },
|
||||
{ title: "Course Plans", href: "/admin/course-plans", permission: Permissions.COURSE_PLAN_MANAGE },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Users",
|
||||
icon: Users,
|
||||
href: "/admin/users",
|
||||
permission: Permissions.USER_MANAGE,
|
||||
items: [
|
||||
{ title: "User List", href: "/admin/users" },
|
||||
{ title: "Import Users", href: "/admin/users/import", permission: Permissions.USER_MANAGE },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Teaching",
|
||||
icon: BookCopy,
|
||||
href: "/admin/course-plans",
|
||||
permission: Permissions.COURSE_PLAN_MANAGE,
|
||||
items: [
|
||||
{ title: "Course Plans", href: "/admin/course-plans", permission: Permissions.COURSE_PLAN_MANAGE },
|
||||
{ title: "Electives", href: "/admin/elective", permission: Permissions.ELECTIVE_MANAGE },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Scheduling",
|
||||
icon: CalendarClock,
|
||||
@@ -69,6 +89,24 @@ export const NAV_CONFIG: Partial<Record<Role, NavItem[]>> = {
|
||||
{ title: "Change Requests", href: "/admin/scheduling/changes", permission: Permissions.SCHEDULE_ADJUST },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Attendance",
|
||||
icon: CalendarCheck,
|
||||
href: "/admin/attendance",
|
||||
permission: Permissions.ATTENDANCE_READ,
|
||||
},
|
||||
{
|
||||
title: "Announcements",
|
||||
icon: Megaphone,
|
||||
href: "/admin/announcements",
|
||||
permission: Permissions.ANNOUNCEMENT_MANAGE,
|
||||
},
|
||||
{
|
||||
title: "文件管理",
|
||||
icon: Files,
|
||||
href: "/admin/files",
|
||||
permission: Permissions.FILE_READ,
|
||||
},
|
||||
{
|
||||
title: "Audit Logs",
|
||||
icon: ScrollText,
|
||||
@@ -80,18 +118,6 @@ export const NAV_CONFIG: Partial<Record<Role, NavItem[]>> = {
|
||||
{ title: "Data Changes", href: "/admin/audit-logs/data-changes" },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Announcements",
|
||||
icon: Megaphone,
|
||||
href: "/admin/announcements",
|
||||
permission: Permissions.ANNOUNCEMENT_MANAGE,
|
||||
},
|
||||
{
|
||||
title: "Electives",
|
||||
icon: BookMarked,
|
||||
href: "/admin/elective",
|
||||
permission: Permissions.ELECTIVE_MANAGE,
|
||||
},
|
||||
{
|
||||
title: "Messages",
|
||||
icon: Mail,
|
||||
@@ -101,130 +127,130 @@ export const NAV_CONFIG: Partial<Record<Role, NavItem[]>> = {
|
||||
{
|
||||
title: "Settings",
|
||||
icon: Settings,
|
||||
href: "/settings",
|
||||
href: "/admin/settings",
|
||||
permission: Permissions.SETTINGS_ADMIN,
|
||||
},
|
||||
],
|
||||
teacher: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
title: "仪表盘",
|
||||
icon: LayoutDashboard,
|
||||
href: "/teacher/dashboard",
|
||||
},
|
||||
{
|
||||
title: "Textbooks",
|
||||
title: "教材",
|
||||
icon: Library,
|
||||
href: "/teacher/textbooks",
|
||||
permission: Permissions.TEXTBOOK_READ,
|
||||
},
|
||||
{
|
||||
title: "Exams",
|
||||
title: "考试",
|
||||
icon: FileQuestion,
|
||||
href: "/teacher/exams",
|
||||
permission: Permissions.EXAM_CREATE,
|
||||
items: [
|
||||
{ title: "All Exams", href: "/teacher/exams/all" },
|
||||
{ title: "Create Exam", href: "/teacher/exams/create", permission: Permissions.EXAM_CREATE },
|
||||
{ title: "全部考试", href: "/teacher/exams/all" },
|
||||
{ title: "创建考试", href: "/teacher/exams/create", permission: Permissions.EXAM_CREATE },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Homework",
|
||||
title: "作业",
|
||||
icon: PenTool,
|
||||
href: "/teacher/homework",
|
||||
permission: Permissions.HOMEWORK_CREATE,
|
||||
items: [
|
||||
{ title: "Assignments", href: "/teacher/homework/assignments" },
|
||||
{ title: "Submissions", href: "/teacher/homework/submissions" },
|
||||
{ title: "作业列表", href: "/teacher/homework/assignments" },
|
||||
{ title: "提交记录", href: "/teacher/homework/submissions" },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Grades",
|
||||
title: "成绩",
|
||||
icon: GraduationCap,
|
||||
href: "/teacher/grades",
|
||||
permission: Permissions.GRADE_RECORD_MANAGE,
|
||||
items: [
|
||||
{ title: "All Grades", href: "/teacher/grades" },
|
||||
{ title: "Batch Entry", href: "/teacher/grades/entry", permission: Permissions.GRADE_RECORD_MANAGE },
|
||||
{ title: "Statistics", href: "/teacher/grades/stats", permission: Permissions.GRADE_RECORD_READ },
|
||||
{ title: "Analytics", href: "/teacher/grades/analytics", permission: Permissions.GRADE_RECORD_READ },
|
||||
{ title: "全部成绩", href: "/teacher/grades" },
|
||||
{ title: "批量录入", href: "/teacher/grades/entry", permission: Permissions.GRADE_RECORD_MANAGE },
|
||||
{ title: "成绩统计", href: "/teacher/grades/stats", permission: Permissions.GRADE_RECORD_READ },
|
||||
{ title: "成绩分析", href: "/teacher/grades/analytics", permission: Permissions.GRADE_RECORD_READ },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Question Bank",
|
||||
title: "题库",
|
||||
icon: ClipboardList,
|
||||
href: "/teacher/questions",
|
||||
permission: Permissions.QUESTION_READ,
|
||||
},
|
||||
{
|
||||
title: "Class Management",
|
||||
title: "班级管理",
|
||||
icon: Users,
|
||||
href: "/teacher/classes",
|
||||
permission: Permissions.CLASS_READ,
|
||||
items: [
|
||||
{ title: "My Classes", href: "/teacher/classes/my" },
|
||||
{ title: "Students", href: "/teacher/classes/students" },
|
||||
{ title: "Schedule", href: "/teacher/classes/schedule", permission: Permissions.CLASS_SCHEDULE },
|
||||
{ title: "我的班级", href: "/teacher/classes/my" },
|
||||
{ title: "学生", href: "/teacher/classes/students" },
|
||||
{ title: "课表", href: "/teacher/classes/schedule", permission: Permissions.CLASS_SCHEDULE },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Course Plans",
|
||||
title: "课程计划",
|
||||
icon: CalendarRange,
|
||||
href: "/teacher/course-plans",
|
||||
permission: Permissions.COURSE_PLAN_READ,
|
||||
},
|
||||
{
|
||||
title: "Lesson Plans",
|
||||
title: "我的备课",
|
||||
icon: PenTool,
|
||||
href: "/teacher/lesson-plans",
|
||||
permission: Permissions.LESSON_PLAN_READ,
|
||||
},
|
||||
{
|
||||
title: "Attendance",
|
||||
title: "考勤",
|
||||
icon: CalendarCheck,
|
||||
href: "/teacher/attendance",
|
||||
permission: Permissions.ATTENDANCE_MANAGE,
|
||||
items: [
|
||||
{ title: "Records", href: "/teacher/attendance" },
|
||||
{ title: "Take Attendance", href: "/teacher/attendance/sheet", permission: Permissions.ATTENDANCE_MANAGE },
|
||||
{ title: "Statistics", href: "/teacher/attendance/stats", permission: Permissions.ATTENDANCE_READ },
|
||||
{ title: "考勤记录", href: "/teacher/attendance" },
|
||||
{ title: "录入考勤", href: "/teacher/attendance/sheet", permission: Permissions.ATTENDANCE_MANAGE },
|
||||
{ title: "考勤统计", href: "/teacher/attendance/stats", permission: Permissions.ATTENDANCE_READ },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Schedule Changes",
|
||||
title: "调课申请",
|
||||
icon: CalendarClock,
|
||||
href: "/teacher/schedule-changes",
|
||||
permission: Permissions.SCHEDULE_ADJUST,
|
||||
},
|
||||
{
|
||||
title: "Diagnostic",
|
||||
title: "学情诊断",
|
||||
icon: Stethoscope,
|
||||
href: "/teacher/diagnostic",
|
||||
permission: Permissions.DIAGNOSTIC_READ,
|
||||
},
|
||||
{
|
||||
title: "Electives",
|
||||
title: "选修课",
|
||||
icon: BookMarked,
|
||||
href: "/teacher/elective",
|
||||
permission: Permissions.ELECTIVE_MANAGE,
|
||||
},
|
||||
{
|
||||
title: "Management",
|
||||
title: "年级管理",
|
||||
icon: Briefcase,
|
||||
href: "/management",
|
||||
permission: Permissions.GRADE_MANAGE,
|
||||
items: [
|
||||
{ title: "Grade Classes", href: "/management/grade/classes" },
|
||||
{ title: "Grade Insights", href: "/management/grade/insights" },
|
||||
{ title: "年级班级", href: "/management/grade/classes" },
|
||||
{ title: "年级洞察", href: "/management/grade/insights" },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Announcements",
|
||||
title: "公告",
|
||||
icon: Megaphone,
|
||||
href: "/announcements",
|
||||
permission: Permissions.ANNOUNCEMENT_READ,
|
||||
},
|
||||
{
|
||||
title: "Messages",
|
||||
title: "消息",
|
||||
icon: Mail,
|
||||
href: "/messages",
|
||||
permission: Permissions.MESSAGE_READ,
|
||||
@@ -308,6 +334,11 @@ export const NAV_CONFIG: Partial<Record<Role, NavItem[]>> = {
|
||||
href: "/parent/attendance",
|
||||
permission: Permissions.ATTENDANCE_READ,
|
||||
},
|
||||
{
|
||||
title: "Leave Request",
|
||||
icon: CalendarRange,
|
||||
href: "/parent/leave",
|
||||
},
|
||||
{
|
||||
title: "Announcements",
|
||||
icon: Megaphone,
|
||||
|
||||
Reference in New Issue
Block a user