import { useEffect, useState, useCallback } from 'react'; import { useParams } from 'react-router-dom'; import { Table, Button, Space, Modal, Form, Input, InputNumber, DatePicker, Switch, Select, Tag, message, Popconfirm, Drawer, Descriptions, Segmented, Timeline, } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined, EyeOutlined, } from '@ant-design/icons'; import { listPluginData, createPluginData, updatePluginData, deletePluginData, type PluginDataListOptions, } from '../api/pluginData'; import EntitySelect from '../components/EntitySelect'; import { getPluginSchema, type PluginFieldSchema, type PluginEntitySchema, type PluginPageSchema, type PluginSectionSchema, } from '../api/plugins'; import { evaluateVisibleWhen } from '../utils/exprEvaluator'; const { Search } = Input; const { TextArea } = Input; interface PluginCRUDPageProps { /** 如果从 tabs/detail 页面内嵌使用,通过 props 传入配置 */ pluginIdOverride?: string; entityOverride?: string; filterField?: string; filterValue?: string; enableViews?: string[]; /** detail 页面内嵌时使用 compact 模式 */ compact?: boolean; } export default function PluginCRUDPage({ pluginIdOverride, entityOverride, filterField, filterValue, enableViews: enableViewsProp, compact, }: PluginCRUDPageProps = {}) { const routeParams = useParams<{ pluginId: string; entityName: string }>(); const pluginId = pluginIdOverride || routeParams.pluginId || ''; const entityName = entityOverride || routeParams.entityName || ''; const [records, setRecords] = useState[]>([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const [fields, setFields] = useState([]); const [displayName, setDisplayName] = useState(entityName || ''); const [modalOpen, setModalOpen] = useState(false); const [editRecord, setEditRecord] = useState | null>(null); const [form] = Form.useForm(); const [formValues, setFormValues] = useState>({}); // 筛选/搜索/排序 state const [searchText, setSearchText] = useState(''); const [filters, setFilters] = useState>({}); const [sortBy, setSortBy] = useState(); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); // 视图切换 const [viewMode, setViewMode] = useState('table'); // 详情 Drawer const [detailOpen, setDetailOpen] = useState(false); const [detailRecord, setDetailRecord] = useState | null>(null); const [detailSections, setDetailSections] = useState([]); const [allEntities, setAllEntities] = useState([]); const [allPages, setAllPages] = useState([]); // 从 fields 中提取 filterable 字段 const filterableFields = fields.filter((f) => f.filterable); // 查找是否有 detail 页面 const hasDetailPage = allPages.some( (p) => p.type === 'detail' && 'entity' in p && p.entity === entityName, ); // 可用视图 const enableViews = enableViewsProp || (() => { const page = allPages.find( (p) => p.type === 'crud' && 'entity' in p && p.entity === entityName, ); return (page as { enable_views?: string[] })?.enable_views || ['table']; })(); // 加载 schema useEffect(() => { if (!pluginId) return; const abortController = new AbortController(); async function loadSchema() { try { const schema = await getPluginSchema(pluginId!); if (abortController.signal.aborted) return; const entities: PluginEntitySchema[] = (schema as { entities?: PluginEntitySchema[] }).entities || []; setAllEntities(entities); const entity = entities.find((e) => e.name === entityName); if (entity) { setFields(entity.fields); setDisplayName(entity.display_name || entityName || ''); } const ui = (schema as { ui?: { pages: PluginPageSchema[] } }).ui; if (ui?.pages) { setAllPages(ui.pages); const detailPage = ui.pages.find( (p) => p.type === 'detail' && 'entity' in p && p.entity === entityName, ); if (detailPage && 'sections' in detailPage) { setDetailSections(detailPage.sections); } } } catch { message.warning('Schema 加载失败,部分功能不可用'); } } loadSchema(); return () => abortController.abort(); }, [pluginId, entityName]); const fetchData = useCallback( async (p = page, overrides?: { search?: string; sort_by?: string; sort_order?: 'asc' | 'desc' }) => { if (!pluginId || !entityName) return; setLoading(true); try { const options: PluginDataListOptions = {}; const mergedFilters = { ...filters }; if (filterField && filterValue) { mergedFilters[filterField] = filterValue; } if (Object.keys(mergedFilters).length > 0) { options.filter = mergedFilters; } const effectiveSearch = overrides?.search ?? searchText; if (effectiveSearch) options.search = effectiveSearch; const effectiveSortBy = overrides?.sort_by ?? sortBy; const effectiveSortOrder = overrides?.sort_order ?? sortOrder; if (effectiveSortBy) { options.sort_by = effectiveSortBy; options.sort_order = effectiveSortOrder; } const result = await listPluginData(pluginId, entityName, p, 20, options); setRecords( result.data.map((r) => ({ ...r.data, _id: r.id, _version: r.version })), ); setTotal(result.total); } catch { message.error('加载数据失败'); } setLoading(false); }, [pluginId, entityName, page, filters, searchText, sortBy, sortOrder, filterField, filterValue], ); useEffect(() => { fetchData(); }, [fetchData]); // 筛选变化 const handleFilterChange = (fieldName: string, value: string | undefined) => { const newFilters = { ...filters }; if (value) { newFilters[fieldName] = value; } else { delete newFilters[fieldName]; } setFilters(newFilters); setPage(1); // 直接触发重新查询 fetchData(1); }; const handleSubmit = async (values: Record) => { if (!pluginId || !entityName) return; const { _id, _version, ...data } = values as Record & { _id?: string; _version?: number; }; try { if (editRecord) { await updatePluginData( pluginId, entityName, editRecord._id as string, data, editRecord._version as number, ); message.success('更新成功'); } else { await createPluginData(pluginId, entityName, data); message.success('创建成功'); } setModalOpen(false); setEditRecord(null); fetchData(); } catch { message.error('操作失败'); } }; const handleDelete = async (record: Record) => { if (!pluginId || !entityName) return; try { await deletePluginData(pluginId, entityName, record._id as string); message.success('删除成功'); fetchData(); } catch { message.error('删除失败'); } }; // 动态生成列 const columns = [ ...fields.slice(0, 5).map((f) => ({ title: f.display_name || f.name, dataIndex: f.name, key: f.name, ellipsis: true, sorter: f.sortable ? true : undefined, render: (val: unknown) => { if (typeof val === 'boolean') return val ? : ; return String(val ?? '-'); }, })), { title: '操作', key: 'action', width: hasDetailPage ? 200 : 150, render: (_: unknown, record: Record) => ( {hasDetailPage && ( )} handleDelete(record)}> ), }, ]; // 动态生成表单字段 const renderFormField = (field: PluginFieldSchema) => { const widget = field.ui_widget || field.field_type; switch (widget) { case 'number': case 'integer': case 'float': case 'decimal': return ; case 'boolean': return ; case 'date': case 'datetime': return ; case 'select': return ( ); case 'textarea': return