Files
hive-manage-v3/frontend/src/components/layout/AppLayout.tsx
T

139 lines
3.9 KiB
TypeScript

import { useState, type ReactNode } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { useTheme } from '../../hooks/useTheme'
import Sidebar from './Sidebar'
import Header from './Header'
import MobileNav from './MobileNav'
import TabBar from './TabBar'
function useMediaQuery(query: string) {
const [matches, setMatches] = useState(() => {
if (typeof window !== 'undefined') {
return window.matchMedia(query).matches
}
return false
})
if (typeof window !== 'undefined') {
const mql = window.matchMedia(query)
mql.addEventListener('change', (e) => setMatches(e.matches))
}
return matches
}
export interface AppLayoutProps {
children: ReactNode
breadcrumb?: string
onlineCount?: number
runningCount?: number
onSearch?: (query: string) => any
}
export default function AppLayout({
children,
breadcrumb,
onlineCount = 0,
runningCount = 0,
onSearch,
}: AppLayoutProps) {
const { theme, toggle } = useTheme()
const navigate = useNavigate()
const location = useLocation()
const isMobile = useMediaQuery('(max-width: 767px)')
const isTablet = useMediaQuery('(min-width: 768px) and (max-width: 1023px)')
const [mobileNavOpen, setMobileNavOpen] = useState(false)
const handleNavigate = (path: string) => {
navigate(path)
}
const sidebarWidth = isTablet ? 180 : 220
if (isMobile) {
return (
<div className="min-h-screen flex flex-col" style={{ background: 'var(--bg-root)' }}>
{/* Mobile header */}
<header
className="flex items-center justify-between px-4 shrink-0"
style={{
height: 'var(--header-h)',
background: 'var(--bg-nav)',
borderBottom: '1px solid var(--border)',
}}
>
<div className="flex items-center gap-2">
<button
onClick={() => setMobileNavOpen(true)}
className="cursor-pointer bg-transparent border-none text-[20px] p-1"
style={{ color: 'var(--text)' }}
>
☰
</button>
<span style={{ color: 'var(--honey)', fontWeight: 700, fontSize: 14 }}>知微蜂巢</span>
</div>
<button
onClick={toggle}
className="cursor-pointer bg-transparent border-none text-[18px] leading-none p-1"
title={theme === 'dark' ? '切换浅色模式' : '切换深色模式'}
>
{theme === 'dark' ? '☀️' : '🌙'}
</button>
</header>
{/* Mobile content */}
<main className="flex-1 overflow-y-auto p-3 pb-[64px]">{children}</main>
{/* Mobile tab bar */}
<TabBar activePath={location.pathname} />
{/* Mobile nav drawer */}
<MobileNav
open={mobileNavOpen}
onClose={() => setMobileNavOpen(false)}
activePath={location.pathname}
onNavigate={handleNavigate}
/>
</div>
)
}
return (
<div
className="min-h-screen"
style={{
display: 'grid',
gridTemplateColumns: `${sidebarWidth}px 1fr`,
gridTemplateRows: 'var(--header-h) 1fr',
gridTemplateAreas: '"sidebar header" "sidebar main"',
background: 'var(--bg-root)',
}}
>
{/* Sidebar */}
<div style={{ gridArea: 'sidebar' }}>
<Sidebar activePath={location.pathname} onNavigate={handleNavigate} />
</div>
{/* Header */}
<div style={{ gridArea: 'header' }}>
<Header
breadcrumb={breadcrumb}
onlineCount={onlineCount}
runningCount={runningCount}
onSearch={onSearch}
theme={theme}
onToggleTheme={toggle}
/>
</div>
{/* Main */}
<main
className="overflow-y-auto p-5"
style={{ gridArea: 'main', background: 'var(--bg-root)' }}
>
{children}
</main>
</div>
)
}