import { useState } from 'react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Spinner } from '@/components/ui/spinner'; import { ErrorInline } from '@/components/layout/error'; import { EmptyStateWithBorder } from '@/components/layout/empty'; import { LoadingStateWithBorder } from '@/components/layout/loading'; import { Layers, ListRestart, LucideIcon, Trash2 } from 'lucide-react'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'; interface ManagePageProps { title: string; icon?: LucideIcon; data: T[]; loading: boolean; error: Error | null; onReload: () => void; /** 获取初始编辑数据 */ getInitialEditData: (item: T) => Partial; onSave: (item: T, editData: Partial) => Promise; onDelete?: (item: T) => Promise; /** 渲染表格 (Config-Driven) */ columns: { header: string; cell: (item: T) => React.ReactNode; width?: string; align?: 'left' | 'center' | 'right'; className?: string; }[]; /** 渲染详情 */ renderDetail: (props: { selected: T | null; hovered: T | null; editData: Partial; onEditDataChange: (field: keyof T, value: T[keyof T]) => void; onSave: () => void; saving: boolean; }) => React.ReactNode; /** 空状态图标 */ emptyIcon?: LucideIcon; emptyDescription?: string; loadingDescription?: string; getId: (item: T) => string | number; headerExtra?: React.ReactNode; } export function ManagePage({ title, icon: Icon, data, loading, error, onReload, getInitialEditData, onSave, onDelete, columns, renderDetail, emptyIcon = Layers, emptyDescription = '暂无数据', loadingDescription = '加载中', getId, headerExtra, }: ManagePageProps) { /** 悬停状态 */ const [hoveredItem, setHoveredItem] = useState(null); const [selectedItem, setSelectedItem] = useState(null); const [editData, setEditData] = useState>({}); const [saving, setSaving] = useState(false); const [deletingItem, setDeletingItem] = useState(null); /** 悬停处理 */ const handleHover = (item: T | null) => { setHoveredItem(item); }; /** 选择处理 */ const handleSelect = (item: T) => { const itemId = getId(item); const selectedId = selectedItem ? getId(selectedItem) : null; if (itemId === selectedId) { setSelectedItem(null); setEditData({}); } else { setSelectedItem(item); setEditData(getInitialEditData(item)); } setHoveredItem(null); }; /** 编辑数据处理 */ const handleEditDataChange = (field: keyof T, value: T[keyof T]) => { setEditData((prev) => ({ ...prev, [field]: value, })); }; /** 保存处理 */ const handleSave = async () => { if (!selectedItem) return; try { setSaving(true); await onSave(selectedItem, editData); toast.success('保存成功'); } catch (error) { toast.error('保存失败', { description: error instanceof Error ? error.message : '未知错误', }); } finally { setSaving(false); } }; /** 删除处理 */ const handleDeleteClick = (item: T) => { setDeletingItem(item); }; const handleConfirmDelete = async () => { if (!deletingItem || !onDelete) return; try { await onDelete(deletingItem); toast.success('删除成功'); setDeletingItem(null); // 如果删除的是当前选中的项,清除选中状态 if (selectedItem && getId(selectedItem) === getId(deletingItem)) { setSelectedItem(null); setEditData({}); } } catch (error) { toast.error('删除失败', { description: error instanceof Error ? error.message : '未知错误', }); } }; /** 渲染内容 */ const renderContent = () => { if (loading && (!data || data.length === 0)) { return ( ); } if (error) { return (
); } if (!data || data.length === 0) { return ( ); } return ( ); }; return (
{Icon && }

{title}

{headerExtra}
配置列表
{renderContent()}
{renderDetail({ selected: selectedItem, hovered: hoveredItem, editData, onEditDataChange: handleEditDataChange, onSave: handleSave, saving, })}
!open && setDeletingItem(null)} > 确认删除? 此操作无法撤销。这将永久删除该配置。 取消 确认删除
); } /** 详情面板 props */ interface ManageDetailPanelProps { title?: string; isEmpty: boolean; emptyDescription?: string; onSave?: () => void; saving?: boolean; children: React.ReactNode; } /** 详情面板 */ export function ManageDetailPanel({ title = '配置信息', isEmpty, emptyDescription = '请选择配置查看详情', onSave, saving = false, children, }: ManageDetailPanelProps) { if (isEmpty) { return (
{title}
); } return (
{title}
{onSave && ( )}
{children}
); } /** 表格面板 */ export function ManageTable({ data, columns, selected, hovered, onSelect, onHover, onDelete, getId, }: { data: T[]; columns: { header: string; cell: (item: T) => React.ReactNode; width?: string; align?: 'left' | 'center' | 'right'; className?: string; }[]; selected: T | null; hovered: T | null; onSelect: (item: T) => void; onHover: (item: T | null) => void; onDelete?: (item: T) => void; getId: (item: T) => string | number; }) { return (
{columns.map((col, index) => ( {col.header} ))} {onDelete && ( 操作 )} {data.map((item) => { const id = getId(item); const isSelected = selected && getId(selected) === id; const isHovered = hovered && getId(hovered) === id; return ( onHover(item)} onMouseLeave={() => onHover(null)} onClick={() => onSelect(item)} > {columns.map((col, index) => ( {col.cell(item)} ))} {onDelete && ( )} ); })}
); }