chore: preserve frontend concept lab
This commit is contained in:
@@ -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 (
|
||||
<header className="sticky top-0 z-20 flex h-14 items-center justify-between border-b border-border/80 bg-surface/95 px-4 shadow-sm backdrop-blur sm:px-6 lg:px-8">
|
||||
<header data-slot="app-topbar" className="sticky top-0 z-20 flex h-14 items-center justify-between border-b border-border/80 bg-background/95 px-4 backdrop-blur sm:px-6 lg:px-8">
|
||||
<div className="flex min-w-0 items-center gap-2 text-sm">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="-ml-2 lg:hidden"
|
||||
aria-label="打开导航"
|
||||
onClick={onMobileMenuClick}
|
||||
>
|
||||
<Menu data-icon="inline-start" />
|
||||
</Button>
|
||||
<SidebarTrigger className="-ml-1" aria-label="切换侧边栏" />
|
||||
<Separator orientation="vertical" className="mx-1 h-4" />
|
||||
<span className="hidden font-medium text-foreground sm:inline">企业服务治理中台</span>
|
||||
<ChevronRight className="hidden size-4 text-muted-foreground sm:block" />
|
||||
<span className="truncate text-muted-foreground">当前工作区</span>
|
||||
<span className="truncate text-muted-foreground">{pageTitle}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className="hidden sm:inline-flex">本地环境</Badge>
|
||||
<Separator orientation="vertical" className="h-5" />
|
||||
<Button variant="ghost" size="icon" aria-label="通知">
|
||||
<Bell data-icon="inline-start" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-1">
|
||||
<AppThemeToggle />
|
||||
<Button variant="ghost" className="hidden gap-2 sm:inline-flex">
|
||||
<UserRound data-icon="inline-start" />
|
||||
<span>{user?.display_name || user?.username || '当前用户'}</span>
|
||||
</Button>
|
||||
<Button variant="outline" className="gap-2" onClick={() => void logout()}>
|
||||
<LogOut data-icon="inline-start" />
|
||||
<span className="hidden sm:inline">退出</span>
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="gap-2 px-2 sm:px-3">
|
||||
<span className="flex size-7 items-center justify-center rounded-md bg-muted text-foreground">
|
||||
<UserRound />
|
||||
</span>
|
||||
<span className="hidden max-w-32 truncate sm:inline">{user?.display_name || user?.username || '当前用户'}</span>
|
||||
<ChevronDown data-icon="inline-end" className="hidden text-muted-foreground sm:block" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
<DropdownMenuLabel>
|
||||
<span className="grid gap-0.5">
|
||||
<span className="truncate text-sm text-foreground">{user?.display_name || user?.username || '当前用户'}</span>
|
||||
<span className="truncate text-xs font-normal text-muted-foreground">{user?.username || '未设置账号'}</span>
|
||||
</span>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem variant="destructive" onSelect={() => void logout()}>
|
||||
<LogOut data-icon="inline-start" />
|
||||
退出登录
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user