-
{title}
- {description ?
{description} : null}
+
+
+
+
{title}
+ {description ?
{description}
: null}
{action}
-
-
{children}
-
+
+
+ {children}
+
)
}
diff --git a/frontend/src/components/app/AppSidebar.tsx b/frontend/src/components/app/AppSidebar.tsx
index 389968f..47f4a25 100644
--- a/frontend/src/components/app/AppSidebar.tsx
+++ b/frontend/src/components/app/AppSidebar.tsx
@@ -15,13 +15,28 @@ import {
Ticket,
UserCog,
Users,
- UsersRound
+ UsersRound,
+ Workflow
} from 'lucide-react'
+import { Badge } from '@/components/ui/badge'
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarRail,
+ SidebarSeparator,
+ useSidebar
+} from '@/components/ui/sidebar'
import { useAuth } from '@/features/auth/AuthProvider'
import type { AuthMenu } from '@/features/auth/types'
-import { cn } from '@/lib/utils'
-import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'
const iconMap = {
audit: ScrollText,
@@ -102,62 +117,41 @@ type SidebarNavContentProps = {
onNavigate?: () => void
}
-function SidebarBrand() {
- return (
-
- )
-}
-
function SidebarNavContent({ groups, currentHash, onNavigate }: SidebarNavContentProps) {
return (
-
+ >
)
}
-type AppSidebarProps = {
- mobileOpen?: boolean
- onMobileOpenChange?: (open: boolean) => void
-}
-
-export function AppSidebar({ mobileOpen = false, onMobileOpenChange }: AppSidebarProps) {
+export function AppSidebar() {
const { user } = useAuth()
+ const { setOpenMobile } = useSidebar()
const currentHash = useCurrentHash()
const menuGroups = (user?.menus ?? []).filter((menu) => menu.is_visible && menu.is_enabled)
const hasKnowledgeMenu = menuGroups.some(
@@ -166,24 +160,42 @@ export function AppSidebar({ mobileOpen = false, onMobileOpenChange }: AppSideba
const groups = hasKnowledgeMenu ? menuGroups : [...menuGroups, knowledgeNavGroup]
return (
- <>
-
-
-
-
- 移动端导航
-
-
- onMobileOpenChange?.(false)}
- />
-
-
- >
+
+
+
+
+
+
+
+
+
+
+ AI 工单平台
+ 企业服务治理中台
+
+
+
+
+
+
+
+
+ setOpenMobile(false)}
+ />
+
+
+
+
+
+
)
}
diff --git a/frontend/src/components/app/AppTopbar.tsx b/frontend/src/components/app/AppTopbar.tsx
index a90d54d..d4e0d99 100644
--- a/frontend/src/components/app/AppTopbar.tsx
+++ b/frontend/src/components/app/AppTopbar.tsx
@@ -1,49 +1,91 @@
-import { Bell, ChevronRight, LogOut, Menu, UserRound } from 'lucide-react'
+import * as React from 'react'
+import { ChevronDown, ChevronRight, LogOut, UserRound } from 'lucide-react'
import { AppThemeToggle } from '@/components/app/AppThemeToggle'
-import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger
+} from '@/components/ui/dropdown-menu'
import { Separator } from '@/components/ui/separator'
+import { SidebarTrigger } from '@/components/ui/sidebar'
import { useAuth } from '@/features/auth/AuthProvider'
-type AppTopbarProps = {
- onMobileMenuClick?: () => void
+const routeTitles: Array<{ pattern: RegExp; title: string }> = [
+ { pattern: /^\/$/, title: '工作台' },
+ { pattern: /^\/tickets$/, title: '工单中心' },
+ { pattern: /^\/tickets\/new$/, title: '创建工单' },
+ { pattern: /^\/tickets\/\d+$/, title: '工单详情' },
+ { pattern: /^\/solutions$/, title: '解决方案库' },
+ { pattern: /^\/solutions\/new/, title: '新建解决方案' },
+ { pattern: /^\/knowledge$/, title: '知识库' },
+ { pattern: /^\/knowledge\/search$/, title: '知识检索' },
+ { pattern: /^\/knowledge\/items\/new$/, title: '新建知识条目' },
+ { pattern: /^\/system\/users$/, title: '用户管理' },
+ { pattern: /^\/system\/user-groups$/, title: '用户组管理' },
+ { pattern: /^\/system\/roles$/, title: '角色管理' },
+ { pattern: /^\/system\/menus$/, title: '菜单权限' },
+ { pattern: /^\/audit$/, title: '审计日志' },
+ { pattern: /^\/design-system$/, title: '设计系统' }
+]
+
+export function getCurrentPageTitle(hash: string) {
+ const route = hash.replace(/^#/, '') || '/'
+ return routeTitles.find((item) => item.pattern.test(route))?.title ?? '当前工作区'
}
-export function AppTopbar({ onMobileMenuClick }: AppTopbarProps) {
+export function AppTopbar() {
const { logout, user } = useAuth()
+ const [pageTitle, setPageTitle] = React.useState(() => getCurrentPageTitle(window.location.hash))
+
+ React.useEffect(() => {
+ const handleHashChange = () => setPageTitle(getCurrentPageTitle(window.location.hash))
+ window.addEventListener('hashchange', handleHashChange)
+ return () => window.removeEventListener('hashchange', handleHashChange)
+ }, [])
return (
-