refactor: standardize admin page shell
This commit is contained in:
25
frontend/src/components/app/AppPage.tsx
Normal file
25
frontend/src/components/app/AppPage.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user