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 */}
|
||||
|
||||
Reference in New Issue
Block a user