refactor: standardize admin page shell

This commit is contained in:
hailin
2026-07-02 15:39:03 +08:00
parent 09df56c21b
commit 2a63f97f35
13 changed files with 65 additions and 52 deletions

View File

@@ -0,0 +1,25 @@
import type * as React from 'react'
import { cn } from '@/lib/utils'
type AppPageProps = {
children: React.ReactNode
className?: string
contentClassName?: string
maxWidth?: '6xl' | '7xl'
}
const maxWidthClass: Record<NonNullable<AppPageProps['maxWidth']>, string> = {
'6xl': 'max-w-6xl',
'7xl': 'max-w-7xl'
}
export function AppPage({ children, className, contentClassName, maxWidth = '7xl' }: AppPageProps) {
return (
<main className={cn('min-h-screen bg-background px-4 py-4 text-foreground sm:px-6 lg:py-6', className)}>
<div className={cn('mx-auto flex flex-col gap-4', maxWidthClass[maxWidth], contentClassName)}>
{children}
</div>
</main>
)
}

View File

@@ -2,6 +2,7 @@ import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataT
import { AppDetailLayout } from '@/components/app/AppDetailLayout'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppFilterBar } from '@/components/app/AppFilterBar'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { AppPriorityBadge } from '@/components/app/AppPriorityBadge'
import { AppSection } from '@/components/app/AppSection'
@@ -33,8 +34,7 @@ const previewColumns: AppDataTableColumn<PreviewRow>[] = [
export function DesignSystemPreview() {
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto flex max-w-6xl flex-col gap-6">
<AppPage maxWidth="6xl" contentClassName="gap-6">
<AppPageHeader
title="设计系统预览"
actions={<Button>创建工单</Button>}
@@ -102,7 +102,6 @@ export function DesignSystemPreview() {
<p className="text-sm text-muted-foreground">页面级布局只复用 App 组件,业务字段由页面自行传入。</p>
</AppSection>
</AppDetailLayout>
</div>
</main>
</AppPage>
)
}

View File

@@ -2,6 +2,7 @@ import * as React from 'react'
import { ArrowLeft, Save } from 'lucide-react'
import { AppField } from '@/components/app/AppField'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { AppSection } from '@/components/app/AppSection'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
@@ -77,8 +78,7 @@ export function SolutionCreatePage() {
}
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto grid max-w-6xl gap-5">
<AppPage maxWidth="6xl" contentClassName="grid gap-5">
<AppPageHeader
title="新建解决方案"
actions={
@@ -202,7 +202,6 @@ export function SolutionCreatePage() {
</AppSection>
</div>
</form>
</div>
</main>
</AppPage>
)
}

View File

@@ -4,6 +4,7 @@ import { Plus } from 'lucide-react'
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppFilterBar } from '@/components/app/AppFilterBar'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { Badge } from '@/components/ui/badge'
import { Card, CardContent } from '@/components/ui/card'
@@ -73,8 +74,7 @@ export function SolutionListPage() {
: solutions
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto flex max-w-7xl flex-col gap-4">
<AppPage>
<AppPageHeader
title="解决方案库"
actions={
@@ -137,7 +137,6 @@ export function SolutionListPage() {
footer={<p className="text-sm text-muted-foreground">当前显示 {visibleSolutions.length} 条方案</p>}
/>
) : null}
</div>
</main>
</AppPage>
)
}

View File

@@ -3,6 +3,7 @@ import * as React from 'react'
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppFilterBar } from '@/components/app/AppFilterBar'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
@@ -65,8 +66,7 @@ export function AuditLogPage() {
]
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto flex max-w-7xl flex-col gap-4">
<AppPage>
<AppPageHeader title="操作审计" />
<AppFilterBar>
@@ -83,7 +83,6 @@ export function AuditLogPage() {
footer={<p className="text-sm text-muted-foreground">当前显示 {filteredLogs.length} 条记录</p>}
/>
) : null}
</div>
</main>
</AppPage>
)
}

View File

@@ -2,6 +2,7 @@ import * as React from 'react'
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
@@ -78,8 +79,7 @@ export function SystemMenusPage() {
]
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto flex max-w-7xl flex-col gap-4">
<AppPage>
<AppPageHeader title="菜单权限" />
<div className="rounded-lg border border-border bg-surface p-3">
@@ -96,7 +96,6 @@ export function SystemMenusPage() {
footer={<p className="text-sm text-muted-foreground">当前显示 {filteredMenus.length} 个菜单项</p>}
/>
) : null}
</div>
</main>
</AppPage>
)
}

View File

@@ -3,6 +3,7 @@ import * as React from 'react'
import { AppField } from '@/components/app/AppField'
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
@@ -71,8 +72,7 @@ export function SystemRolesPage() {
]
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto flex max-w-7xl flex-col gap-4">
<AppPage>
<AppPageHeader title="角色权限" />
<form className="grid gap-3 rounded-lg border border-border bg-surface p-3 md:grid-cols-[minmax(0,360px)_auto] md:items-end" onSubmit={handleCreate}>
@@ -98,8 +98,7 @@ export function SystemRolesPage() {
onClose={() => setEditingRole(null)}
onSaved={load}
/>
</div>
</main>
</AppPage>
)
}

View File

@@ -3,6 +3,7 @@ import * as React from 'react'
import { AppField } from '@/components/app/AppField'
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
@@ -79,8 +80,7 @@ export function SystemUserGroupsPage() {
]
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto flex max-w-7xl flex-col gap-4">
<AppPage>
<AppPageHeader title="用户组管理" />
<form className="grid gap-3 rounded-lg border border-border bg-surface p-3 md:grid-cols-[1fr_1fr_2fr_auto] md:items-end" onSubmit={handleCreate}>
@@ -106,7 +106,6 @@ export function SystemUserGroupsPage() {
footer={<p className="text-sm text-muted-foreground">共 {groups.length} 个用户组</p>}
/>
) : null}
</div>
</main>
</AppPage>
)
}

View File

@@ -4,6 +4,7 @@ import { AppField } from '@/components/app/AppField'
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppFilterBar } from '@/components/app/AppFilterBar'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
@@ -138,8 +139,7 @@ export function SystemUsersPage() {
]
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto flex max-w-7xl flex-col gap-4">
<AppPage>
<AppPageHeader title="用户管理" />
<form className="grid gap-3 rounded-lg border border-border bg-surface p-3 md:grid-cols-[1fr_1fr_1fr_1fr_auto] md:items-end" onSubmit={handleCreateUser}>
@@ -180,8 +180,7 @@ export function SystemUsersPage() {
onClose={() => setEditingUser(null)}
onSaved={load}
/>
</div>
</main>
</AppPage>
)
}

View File

@@ -2,6 +2,7 @@ import * as React from 'react'
import { AlertCircle, Bot, CheckCircle2, Lightbulb, Send } from 'lucide-react'
import { AppField } from '@/components/app/AppField'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { AppSection } from '@/components/app/AppSection'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
@@ -47,8 +48,7 @@ export function TicketCreatePage() {
}
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto grid max-w-6xl gap-5">
<AppPage maxWidth="6xl" contentClassName="grid gap-5">
<AppPageHeader title="创建工单" />
<form className="grid gap-4 xl:grid-cols-[minmax(0,1fr)_340px]" onSubmit={handleSubmit}>
<div className="grid gap-4">
@@ -175,7 +175,6 @@ export function TicketCreatePage() {
</AppSection>
</div>
</form>
</div>
</main>
</AppPage>
)
}

View File

@@ -3,6 +3,7 @@ import { Bot, CheckCircle2, CircleDot, Send, XCircle } from 'lucide-react'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppDetailLayout } from '@/components/app/AppDetailLayout'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { AppPriorityBadge } from '@/components/app/AppPriorityBadge'
import { AppSection } from '@/components/app/AppSection'
@@ -87,23 +88,22 @@ export function TicketDetailPage({ ticketId }: { ticketId: number }) {
if (error) {
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<AppPage maxWidth="6xl">
<AppEmptyState title="无法加载工单" description={error} />
</main>
</AppPage>
)
}
if (!ticket) {
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<AppPage maxWidth="6xl">
<AppEmptyState title="正在加载工单" />
</main>
</AppPage>
)
}
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto flex max-w-6xl flex-col gap-4">
<AppPage maxWidth="6xl">
<AppPageHeader
title={ticket.title}
description={ticket.ticket_no}
@@ -164,7 +164,6 @@ export function TicketDetailPage({ ticketId }: { ticketId: number }) {
onOpenChange={setTransitionDialogOpen}
onSubmit={handleTransition}
/>
</div>
</main>
</AppPage>
)
}

View File

@@ -3,6 +3,7 @@ import { ArrowRight, Plus } from 'lucide-react'
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { AppPriorityBadge } from '@/components/app/AppPriorityBadge'
import { AppStatusBadge } from '@/components/app/AppStatusBadge'
@@ -194,8 +195,7 @@ export function TicketListPage() {
}
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto flex max-w-7xl flex-col gap-5">
<AppPage contentClassName="gap-5">
<AppPageHeader
title="工单中心"
actions={
@@ -285,7 +285,6 @@ export function TicketListPage() {
onRowClick={(ticket) => (window.location.hash = `/tickets/${ticket.id}`)}
/>
) : null}
</div>
</main>
</AppPage>
)
}

View File

@@ -3,6 +3,7 @@ import { ArrowRight, Plus } from 'lucide-react'
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppPage } from '@/components/app/AppPage'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { AppPriorityBadge } from '@/components/app/AppPriorityBadge'
import { AppSection } from '@/components/app/AppSection'
@@ -61,8 +62,7 @@ export function WorkbenchPage() {
const recentTickets = tickets.slice(0, 6)
return (
<main className="min-h-screen bg-background p-6 text-foreground">
<div className="mx-auto grid max-w-7xl gap-5">
<AppPage contentClassName="grid gap-5">
<AppPageHeader
title="今日工作台"
actions={
@@ -123,7 +123,6 @@ export function WorkbenchPage() {
</div>
</AppSection>
</div>
</div>
</main>
</AppPage>
)
}