import { App, Button, Dropdown, Table } from "antd"; import type { ButtonProps, MenuProps, TableProps } from "antd"; import { saveAs } from "file-saver"; import { CheckSquare2, ChevronDown, Download, SearchX, X } from "lucide-react"; import { useState } from "react"; import type { ReactNode } from "react"; import { ListToolbar } from "@/components/layout/workspace-page"; import { cn } from "@/lib/utils"; export const configuredSecretText = "已配置 · 留空不改"; export type AdminStatusTone = "neutral" | "success" | "warning" | "error" | "info"; export function AdminStatusBadge({ label, tone = "neutral", title }: { label: string; tone?: AdminStatusTone; title?: string }) { return {label}; } export function AdminStatTile({ label, value, detail, trend }: { label: string; value: string | number; detail?: string; trend?: { value: string; tone?: AdminStatusTone } }) { return (
{label}
{value}
{trend || detail ? (
{trend ? : null} {trend && detail ? · : null} {detail ? {detail} : null}
) : null}
); } export function AdminDataTable({ toolbar, toolbarActive, toolbarFilters, toolbarActiveFilters, onReset, trailing, batchActions, footer, table, empty, skeletonColumns = 6, skeletonRows = 8, className, }: { toolbar?: ReactNode; toolbarActive?: boolean; toolbarFilters?: ReactNode; toolbarActiveFilters?: ReactNode; onReset?: () => void; trailing?: ReactNode; batchActions?: ReactNode; footer?: ReactNode; table: TableProps; empty?: ReactNode; skeletonColumns?: number; skeletonRows?: number; className?: string; }) { const dataSource = table.dataSource as readonly RecordType[] | undefined; const showSkeleton = Boolean(table.loading) && !dataSource?.length; return (
{toolbar ? {toolbar} : null} {batchActions}
{showSkeleton ? : } {footer ?
{footer}
: null} ); } function isStatusConfig(value: ReactNode | { label: string; color?: string }): value is { label: string; color?: string } { if (!value || typeof value !== "object") return false; return typeof (value as { label?: unknown }).label === "string"; } export function AdminExportButton({ exportFile, fileName, label = "导出", successMessage, errorMessage = "导出失败", size, ...buttonProps }: Omit & { exportFile: () => Blob | Promise; fileName: string | (() => string); label?: string; successMessage?: string; errorMessage?: string; }) { const { message } = App.useApp(); const [exporting, setExporting] = useState(false); const runExport = async () => { setExporting(true); try { const blob = await exportFile(); saveAs(blob, typeof fileName === "function" ? fileName() : fileName); if (successMessage) message.success(successMessage); } catch (error) { message.error(error instanceof Error ? error.message : errorMessage); } finally { setExporting(false); } }; return ; } export function AdminTableEmpty({ filtered = false, title, description, action, }: { filtered?: boolean; title?: string; description?: string; action?: ReactNode; }) { return (
{title || (filtered ? "没有符合筛选条件的数据" : "暂无数据")}
{description ?

{description}

: null} {action ?
{action}
: null}
); } export function AdminFilterChip({ label, onRemove }: { label: ReactNode; onRemove: () => void }) { return ( ); } export function AdminTableSkeleton({ rows = 8, columns = 6 }: { rows?: number; columns?: number }) { return (
{Array.from({ length: columns }).map((_, index) => )}
{Array.from({ length: Math.max(8, rows) }).map((_, rowIndex) => (
{Array.from({ length: columns }).map((_, columnIndex) => ( ))}
))}
); } export function AdminBatchBar({ count, onClear, children }: { count: number; onClear: () => void; children: ReactNode }) { if (count <= 0) return null; return (
已选择 {count} 项
{children}
); } export type AdminRowAction = { key: string; label: ReactNode; icon?: ReactNode; danger?: boolean; disabled?: boolean; onClick: () => void | Promise; confirm?: { title: string; description: string; okText: string; }; }; export function AdminRowActions({ primary, actions, visibleActionCount, }: { primary?: { label: ReactNode; icon?: ReactNode; onClick: () => void | Promise; disabled?: boolean }; actions: AdminRowAction[]; visibleActionCount?: number; }) { const { modal } = App.useApp(); // 少量操作直接露出,只有真正过多时才收进菜单,避免“更多”成为默认操作列。 const resolvedVisibleActionCount = visibleActionCount ?? (actions.length <= 3 ? actions.length : 2); const visibleActions = actions.slice(0, Math.max(0, resolvedVisibleActionCount)); const menuActions = actions.slice(Math.max(0, resolvedVisibleActionCount)); const items: MenuProps["items"] = menuActions.map((action) => ({ key: action.key, label: action.label, icon: action.icon, danger: action.danger, disabled: action.disabled, })); const runAction = (action: AdminRowAction) => { if (!action.confirm) { void action.onClick(); return; } modal.confirm({ title: action.confirm.title, content: action.confirm.description, okText: action.confirm.okText, cancelText: "取消", okButtonProps: { danger: action.danger }, onOk: action.onClick, }); }; const renderActionButton = (action: AdminRowAction) => ( ); return (
{primary ? ( ) : null} {visibleActions.map(renderActionButton)} {menuActions.length ? ( { const action = actions.find((item) => item.key === key); if (action) runAction(action); }, }} > ) : null}
); } export function SettingsSectionCard({ icon, title, description, status, children, footer, className, contentClassName, layout = "split", }: { icon?: ReactNode; title: string; description?: string; status?: { label: string; color?: string } | ReactNode; children: ReactNode; footer?: ReactNode; className?: string; contentClassName?: string; layout?: "split" | "stacked"; }) { const isStacked = layout === "stacked"; return (
{icon ? {icon} : null}

{title}

{description ?

{description}

: null}
{status ?
{isStatusConfig(status) ? : status}
: null}
{children} {footer ?
{footer}
: null}
); }