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 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, 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 (
{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 && ( )} ) })}
) }