chore: preserve frontend concept lab

This commit is contained in:
hailin
2026-07-14 13:39:36 +08:00
parent f46369883e
commit 13b0768dfd
33 changed files with 1987 additions and 192 deletions

View File

@@ -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>
)