Make admin panel responsive
This commit is contained in:
parent
6db5d43891
commit
07a68ca204
@ -42,10 +42,18 @@ styles/
|
||||
```
|
||||
- 使用 `next export` 生成静态 HTML:
|
||||
```bash
|
||||
npm run build && npx next export
|
||||
npm run build && npx next export
|
||||
```
|
||||
|
||||
生成的 `out/` 目录即可作为纯静态网站部署。
|
||||
|
||||
## 响应式设计
|
||||
示例实现利用 Tailwind 的 `md:` 等断点完成移动端适配:
|
||||
|
||||
- 小屏幕默认隐藏侧边栏,在 Header 内提供菜单按钮控制显示。
|
||||
- 大屏幕侧边栏常驻,内容区域使用 `md:ml-64` 留出宽度。
|
||||
|
||||
布局组件 `layout.tsx` 中使用 React 状态切换侧边栏,可满足主流移动端浏览体验。
|
||||
|
||||
## 示例代码结构
|
||||
本仓库 `ui/nextjs` 目录提供了一套最简参考实现,可直接 `npm run build && npx next export` 编译到 `ui/dist` 供 Go 服务嵌入。
|
||||
|
||||
@ -1,14 +1,26 @@
|
||||
'use client'
|
||||
|
||||
import '../styles/globals.css'
|
||||
import { useState } from 'react'
|
||||
import Sidebar from '../components/Sidebar'
|
||||
import Header from '../components/Header'
|
||||
|
||||
export const dynamic = 'force-static'
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="flex min-h-screen">
|
||||
<Sidebar />
|
||||
<main className="flex-1 p-4">{children}</main>
|
||||
<body className="flex min-h-screen overflow-x-hidden">
|
||||
<Sidebar
|
||||
className={`fixed inset-y-0 left-0 z-20 transform transition-transform bg-gray-100 md:static md:translate-x-0 ${open ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}`}
|
||||
onNavigate={() => setOpen(false)}
|
||||
/>
|
||||
<div className="flex flex-col flex-1">
|
||||
<Header onMenu={() => setOpen(!open)} />
|
||||
<main className="flex-1 p-4 md:ml-64">{children}</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@ -1,7 +1,21 @@
|
||||
export default function Header() {
|
||||
export interface HeaderProps {
|
||||
onMenu?: () => void
|
||||
}
|
||||
|
||||
export default function Header({ onMenu }: HeaderProps) {
|
||||
return (
|
||||
<header className="p-4 border-b">
|
||||
<h1 className="text-lg font-bold">XControl Admin</h1>
|
||||
<header className="flex items-center justify-between p-4 border-b bg-white shadow md:ml-64">
|
||||
<button
|
||||
className="md:hidden p-2 text-gray-600"
|
||||
onClick={onMenu}
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
{/* simple hamburger icon */}
|
||||
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<h1 className="text-lg font-bold flex-1 text-center md:text-left">XControl Admin</h1>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
export interface SidebarProps {
|
||||
className?: string
|
||||
onNavigate?: () => void
|
||||
}
|
||||
|
||||
const menu = [
|
||||
{ href: '/', label: 'Home' },
|
||||
{ href: '/agent', label: 'Agent' },
|
||||
@ -8,11 +13,16 @@ const menu = [
|
||||
{ href: '/xray', label: 'XRay' },
|
||||
]
|
||||
|
||||
export default function Sidebar() {
|
||||
export default function Sidebar({ className = '', onNavigate }: SidebarProps) {
|
||||
return (
|
||||
<nav className="w-48 bg-gray-100 p-4 space-y-2">
|
||||
<nav className={`w-64 bg-gray-100 p-4 space-y-2 ${className}`}>
|
||||
{menu.map(m => (
|
||||
<Link key={m.href} href={m.href} className="block p-2 rounded hover:bg-gray-200">
|
||||
<Link
|
||||
key={m.href}
|
||||
href={m.href}
|
||||
className="block p-2 rounded hover:bg-gray-200"
|
||||
onClick={onNavigate}
|
||||
>
|
||||
{m.label}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html,
|
||||
body {
|
||||
@apply text-gray-800;
|
||||
@apply h-full text-gray-800;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user