From 52bb354a1bba6fe7ea3cb9f988c4b79cfda4715c Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 1 Jul 2026 11:40:32 +0800 Subject: [PATCH] feat: add enterprise app ui layer --- frontend/src/app/layout/AppShell.tsx | 10 +-- frontend/src/components/app/AppDataTable.tsx | 75 +++++++++++++++++++ .../src/components/app/AppDetailLayout.tsx | 15 ++++ frontend/src/components/app/AppEmptyState.tsx | 6 +- frontend/src/components/app/AppFilterBar.tsx | 15 ++++ frontend/src/components/app/AppPageHeader.tsx | 12 ++- frontend/src/components/app/AppSection.tsx | 25 +++++++ frontend/src/components/app/AppSidebar.tsx | 63 ++++++++++++++++ .../src/components/app/AppStatusBadge.tsx | 2 +- frontend/src/components/app/AppTopbar.tsx | 28 +++++++ 10 files changed, 240 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/app/AppDataTable.tsx create mode 100644 frontend/src/components/app/AppDetailLayout.tsx create mode 100644 frontend/src/components/app/AppFilterBar.tsx create mode 100644 frontend/src/components/app/AppSection.tsx create mode 100644 frontend/src/components/app/AppSidebar.tsx create mode 100644 frontend/src/components/app/AppTopbar.tsx diff --git a/frontend/src/app/layout/AppShell.tsx b/frontend/src/app/layout/AppShell.tsx index ca55491..b06357e 100644 --- a/frontend/src/app/layout/AppShell.tsx +++ b/frontend/src/app/layout/AppShell.tsx @@ -1,7 +1,7 @@ import type * as React from 'react' -import { Sidebar } from './Sidebar' -import { Topbar } from './Topbar' +import { AppSidebar } from '@/components/app/AppSidebar' +import { AppTopbar } from '@/components/app/AppTopbar' type AppShellProps = { children: React.ReactNode @@ -10,9 +10,9 @@ type AppShellProps = { export function AppShell({ children }: AppShellProps) { return (
- -
- + +
+ {children}
diff --git a/frontend/src/components/app/AppDataTable.tsx b/frontend/src/components/app/AppDataTable.tsx new file mode 100644 index 0000000..9d28a9f --- /dev/null +++ b/frontend/src/components/app/AppDataTable.tsx @@ -0,0 +1,75 @@ +import type * as React from 'react' + +import { AppEmptyState } from '@/components/app/AppEmptyState' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { cn } from '@/lib/utils' + +export type AppDataTableColumn = { + key: string + header: React.ReactNode + cell: (row: Row) => React.ReactNode + className?: string +} + +type AppDataTableProps = { + columns: AppDataTableColumn[] + rows: Row[] + getRowKey: (row: Row) => React.Key + emptyTitle: string + emptyDescription?: string + toolbar?: React.ReactNode + footer?: React.ReactNode + onRowClick?: (row: Row) => void +} + +export function AppDataTable({ + columns, + rows, + getRowKey, + emptyTitle, + emptyDescription, + toolbar, + footer, + onRowClick +}: AppDataTableProps) { + return ( +
+ {toolbar ?
{toolbar}
: null} + {rows.length === 0 ? ( +
+ +
+ ) : ( +
+ + + + {columns.map((column) => ( + + {column.header} + + ))} + + + + {rows.map((row) => ( + onRowClick?.(row)} + > + {columns.map((column) => ( + + {column.cell(row)} + + ))} + + ))} + +
+
+ )} + {footer ?
{footer}
: null} +
+ ) +} diff --git a/frontend/src/components/app/AppDetailLayout.tsx b/frontend/src/components/app/AppDetailLayout.tsx new file mode 100644 index 0000000..e3ce59b --- /dev/null +++ b/frontend/src/components/app/AppDetailLayout.tsx @@ -0,0 +1,15 @@ +import type * as React from 'react' + +type AppDetailLayoutProps = { + children: React.ReactNode + side: React.ReactNode +} + +export function AppDetailLayout({ children, side }: AppDetailLayoutProps) { + return ( +
+
{children}
+ +
+ ) +} diff --git a/frontend/src/components/app/AppEmptyState.tsx b/frontend/src/components/app/AppEmptyState.tsx index c7933ce..480c44d 100644 --- a/frontend/src/components/app/AppEmptyState.tsx +++ b/frontend/src/components/app/AppEmptyState.tsx @@ -1,13 +1,17 @@ +import type * as React from 'react' + type AppEmptyStateProps = { title: string description?: string + action?: React.ReactNode } -export function AppEmptyState({ title, description }: AppEmptyStateProps) { +export function AppEmptyState({ title, description, action }: AppEmptyStateProps) { return (

{title}

{description ?

{description}

: null} + {action ?
{action}
: null}
) } diff --git a/frontend/src/components/app/AppFilterBar.tsx b/frontend/src/components/app/AppFilterBar.tsx new file mode 100644 index 0000000..697fea7 --- /dev/null +++ b/frontend/src/components/app/AppFilterBar.tsx @@ -0,0 +1,15 @@ +import type * as React from 'react' + +type AppFilterBarProps = { + children: React.ReactNode + actions?: React.ReactNode +} + +export function AppFilterBar({ children, actions }: AppFilterBarProps) { + return ( +
+
{children}
+ {actions ?
{actions}
: null} +
+ ) +} diff --git a/frontend/src/components/app/AppPageHeader.tsx b/frontend/src/components/app/AppPageHeader.tsx index 539a760..2eee90f 100644 --- a/frontend/src/components/app/AppPageHeader.tsx +++ b/frontend/src/components/app/AppPageHeader.tsx @@ -3,17 +3,21 @@ import type * as React from 'react' type AppPageHeaderProps = { title: string description?: string + meta?: React.ReactNode actions?: React.ReactNode } -export function AppPageHeader({ title, description, actions }: AppPageHeaderProps) { +export function AppPageHeader({ title, description, meta, actions }: AppPageHeaderProps) { return ( -
+
-

{title}

+
+

{title}

+ {meta} +
{description ?

{description}

: null}
- {actions ?
{actions}
: null} + {actions ?
{actions}
: null}
) } diff --git a/frontend/src/components/app/AppSection.tsx b/frontend/src/components/app/AppSection.tsx new file mode 100644 index 0000000..057ce47 --- /dev/null +++ b/frontend/src/components/app/AppSection.tsx @@ -0,0 +1,25 @@ +import type * as React from 'react' + +import { Card, CardContent, CardHeader } from '@/components/ui/card' + +type AppSectionProps = { + title: string + description?: string + action?: React.ReactNode + children: React.ReactNode +} + +export function AppSection({ title, description, action, children }: AppSectionProps) { + return ( + + +
+

{title}

+ {description ?

{description}

: null} +
+ {action} +
+ {children} +
+ ) +} diff --git a/frontend/src/components/app/AppSidebar.tsx b/frontend/src/components/app/AppSidebar.tsx new file mode 100644 index 0000000..81940c8 --- /dev/null +++ b/frontend/src/components/app/AppSidebar.tsx @@ -0,0 +1,63 @@ +import { Bot, BriefcaseBusiness, Database, Home, Library, Settings, ShieldCheck, Ticket } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const navGroups = [ + { + label: '工作台', + items: [{ href: '#/', label: '运营总览', icon: Home }] + }, + { + label: '业务处理', + items: [ + { href: '#/tickets', label: '工单中心', icon: Ticket }, + { href: '#/tickets/new', label: '创建工单', icon: BriefcaseBusiness }, + { href: '#/solutions', label: '解决方案库', icon: Library } + ] + }, + { + label: '平台能力', + items: [ + { href: '#/knowledge', label: '知识库', icon: Database }, + { href: '#/agents', label: 'Agent 配置', icon: Bot }, + { href: '#/system', label: '系统治理', icon: ShieldCheck }, + { href: '#/design-system', label: '设计系统', icon: Settings } + ] + } +] + +export function AppSidebar() { + return ( + + ) +} diff --git a/frontend/src/components/app/AppStatusBadge.tsx b/frontend/src/components/app/AppStatusBadge.tsx index 8c49c32..d11492a 100644 --- a/frontend/src/components/app/AppStatusBadge.tsx +++ b/frontend/src/components/app/AppStatusBadge.tsx @@ -10,6 +10,6 @@ const STATUS_LABELS: Record = { } export function AppStatusBadge({ status }: { status: string }) { - const variant = status === 'resolved' || status === 'closed' ? 'success' : 'info' + const variant = status === 'resolved' || status === 'closed' ? 'success' : status === 'pending_confirm' ? 'warning' : 'info' return {STATUS_LABELS[status] ?? status} } diff --git a/frontend/src/components/app/AppTopbar.tsx b/frontend/src/components/app/AppTopbar.tsx new file mode 100644 index 0000000..1e854f2 --- /dev/null +++ b/frontend/src/components/app/AppTopbar.tsx @@ -0,0 +1,28 @@ +import { Bell, ChevronRight, UserRound } from 'lucide-react' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Separator } from '@/components/ui/separator' + +export function AppTopbar() { + return ( +
+
+ AI 工单平台 + + 当前工作区 +
+
+ 本地环境 + + + +
+
+ ) +}