封装 Header 和 Navbar 组件,调整用户界面
This commit is contained in:
42
src/app/admin/_client/header.tsx
Normal file
42
src/app/admin/_client/header.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
'use client'
|
||||
import {PanelLeftCloseIcon, PanelLeftOpenIcon} from 'lucide-react'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import Profile from './profile'
|
||||
import {useLayoutStore} from '@/components/providers/StoreProvider'
|
||||
import {merge} from '@/lib/utils'
|
||||
|
||||
export type HeaderProps = {}
|
||||
|
||||
export default function Header(props: HeaderProps) {
|
||||
|
||||
const navbar = useLayoutStore(store => store.navbar)
|
||||
const toggleNavbar = useLayoutStore(store => store.toggleNavbar)
|
||||
|
||||
return (
|
||||
<header className={merge(
|
||||
`flex-none h-16`,
|
||||
`flex items-stretch`,
|
||||
)}>
|
||||
{/* left */}
|
||||
<div className={`flex-auto flex items-center gap-2`}>
|
||||
<Button
|
||||
theme="ghost"
|
||||
className="w-9 h-9"
|
||||
onClick={toggleNavbar}>
|
||||
{navbar
|
||||
? <PanelLeftCloseIcon/>
|
||||
: <PanelLeftOpenIcon/>
|
||||
}
|
||||
</Button>
|
||||
<span>
|
||||
欢迎来到,蓝狐代理
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* right */}
|
||||
<div className={`flex-none flex items-center justify-end pr-4`}>
|
||||
<Profile/>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
108
src/app/admin/_client/navbar.tsx
Normal file
108
src/app/admin/_client/navbar.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
'use client'
|
||||
import {ReactNode} from 'react'
|
||||
import {merge} from '@/lib/utils'
|
||||
import {useLayoutStore} from '@/components/providers/StoreProvider'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import logo from '@/assets/logo.webp'
|
||||
import logoMini from '../_assets/logo-mini.webp'
|
||||
|
||||
export type NavbarProps = {}
|
||||
|
||||
export default function Navbar(props: NavbarProps) {
|
||||
|
||||
const navbar = useLayoutStore(store => store.navbar)
|
||||
|
||||
return (
|
||||
<nav data-expand={navbar} className={merge(
|
||||
`transition-[flex-basis] duration-200 ease-in-out`,
|
||||
`flex flex-col overflow-hidden group`,
|
||||
`flex-none ${navbar ? `expand basis-52` : `noexpand basis-16`}`,
|
||||
)}>
|
||||
{/* logo */}
|
||||
<Logo mini={!navbar}/>
|
||||
|
||||
{/* routes */}
|
||||
<section className={merge(
|
||||
`transition-[padding] duration-200 ease-in-out`,
|
||||
`flex-auto overflow-auto ${navbar ? `px-4` : `px-3`}`,
|
||||
)}>
|
||||
<NavItem href={'/admin'} icon={`🏠`} label={`账户总览`} expand={navbar}/>
|
||||
<NavTitle label={`个人信息`}/>
|
||||
<NavItem href={`/admin/profile`} icon={`📝`} label={`个人中心`} expand={navbar}/>
|
||||
<NavItem href={`/admin/identify`} icon={`🆔`} label={`实名认证`} expand={navbar}/>
|
||||
<NavItem href={`/admin/whitelist`} icon={`🔒`} label={`白名单`} expand={navbar}/>
|
||||
<NavItem href={`/admin/bills`} icon={`💰`} label={`我的账单`} expand={navbar}/>
|
||||
<NavTitle label={`套餐管理`}/>
|
||||
<NavItem href={`/admin/purchase`} icon={`🛒`} label={`购买套餐`} expand={navbar}/>
|
||||
<NavItem href={`/admin/resources`} icon={`📦`} label={`套餐管理`} expand={navbar}/>
|
||||
<NavTitle label={`IP 管理`}/>
|
||||
<NavItem href={`/admin/extract`} icon={`📤`} label={`提取 IP`} expand={navbar}/>
|
||||
<NavItem href={`/admin`} icon={`👁️`} label={`IP 管理`} expand={navbar}/>
|
||||
<NavItem href={`/admin`} icon={`📜`} label={`提取记录`} expand={navbar}/>
|
||||
<NavItem href={`/admin`} icon={`🗂️`} label={`使用记录`} expand={navbar}/>
|
||||
</section>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
function Logo(props: {
|
||||
mini?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div className={`flex-none h-[64px] flex items-center justify-center`}>
|
||||
<Link href={'/'} className="block">
|
||||
{props.mini
|
||||
? <Image src={logoMini} alt={`logo`} className="h-9 object-contain"/>
|
||||
: <Image src={logo} alt={`logo`} className="h-9 object-contain"/>
|
||||
}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NavTitle(props: {
|
||||
label: string
|
||||
}) {
|
||||
return (
|
||||
<p className={merge(
|
||||
`transition-[height] duration-200 ease-in-out`,
|
||||
`text-sm text-gray-500 whitespace-nowrap flex items-center relative`,
|
||||
`group-data-[expand=true]:h-9`,
|
||||
`group-data-[expand=false]:h-4`,
|
||||
)}>
|
||||
<span className={merge(
|
||||
`transition-[opacity] duration-150 ease-in-out absolute mx-4`,
|
||||
`group-data-[expand=true]:delay-[50ms] group-data-[expand=true]:opacity-100 group-data-[expand=false]:opacity-0`,
|
||||
)}>{props.label}</span>
|
||||
<div className={merge(
|
||||
`transition-[opacity] duration-150 ease-in-out absolute w-full border-b`,
|
||||
`group-data-[expand=false]:delay-[50ms] group-data-[expand=false]:opacity-100 group-data-[expand=true]:opacity-0`,
|
||||
)}></div>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
function NavItem(props: {
|
||||
href: string
|
||||
icon?: ReactNode
|
||||
label: string
|
||||
expand: boolean
|
||||
}) {
|
||||
return (
|
||||
<Link className={merge(
|
||||
`transition-[padding] duration-200 ease-in-out`,
|
||||
`flex items-center rounded-md gap-2 whitespace-nowrap`,
|
||||
`hover:bg-gray-100`,
|
||||
`group-data-[expand=true]:px-4`,
|
||||
)} href={props.href}>
|
||||
<span className={`flex-none w-10 h-10 flex items-center justify-center`}>{props.icon}</span>
|
||||
<span className={merge(
|
||||
`flex-auto`,
|
||||
`transition-[width,opacity] duration-200 ease-in-out`,
|
||||
`group-data-[expand=true]:w-auto group-data-[expand=true]:opacity-100`,
|
||||
`group-data-[expand=false]:w-0 group-data-[expand=false]:opacity-0`,
|
||||
)}>{props.label}</span>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
35
src/app/admin/_client/profile.tsx
Normal file
35
src/app/admin/_client/profile.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
'use client'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import {logout} from '@/actions/auth/auth'
|
||||
import {useProfileStore} from '@/components/providers/StoreProvider'
|
||||
import {useRouter} from 'next/navigation'
|
||||
import {toast} from 'sonner'
|
||||
|
||||
export type ProfileProps = {}
|
||||
|
||||
export default function Profile(props: ProfileProps) {
|
||||
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
||||
const router = useRouter()
|
||||
const doLogout = async () => {
|
||||
try {
|
||||
const resp = await logout()
|
||||
if (resp.success) {
|
||||
await refreshProfile()
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
toast.error('退出登录失败', {
|
||||
description: (e as Error).message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button theme={`error`} onClick={doLogout}>
|
||||
退出登录
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user