import { useState, useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { Table, Button, Space, Modal, Form, Input, InputNumber, DatePicker, Switch, Select, Tag, message, Popconfirm, Segmented, Timeline, Dropdown, } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined, EyeOutlined, DownloadOutlined, UploadOutlined, } from '@ant-design/icons'; import { createPluginData, updatePluginData, deletePluginData, batchPluginData, exportPluginData, exportPluginDataAsBlob, } from '../../api/pluginData'; import EntitySelect from '../../components/EntitySelect'; import type { PluginFieldSchema } from '../../api/plugins'; import { evaluateVisibleWhen } from '../../utils/exprEvaluator'; import { usePluginData } from './usePluginData'; import DetailDrawer from './DetailDrawer'; import ImportModal from './ImportModal'; const { Search } = Input; const { TextArea } = Input; interface PluginCRUDPageProps { pluginIdOverride?: string; entityOverride?: string; filterField?: string; filterValue?: string; enableViews?: string[]; compact?: boolean; } export default function PluginCRUDPageInner({ 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, total, page, loading, fields, displayName, sortBy, sortOrder, resolvedLabels, labelMeta, entityDef, allEntities, detailSections, hasDetailPage, filterableFields, setPage, setSortBy, setSortOrder, fetchData, } = usePluginData(pluginId, entityName, filterField, filterValue); const [modalOpen, setModalOpen] = useState(false); const [editRecord, setEditRecord] = useState | null>(null); const [form] = Form.useForm(); const [formValues, setFormValues] = useState>({}); const [viewMode, setViewMode] = useState('table'); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [detailOpen, setDetailOpen] = useState(false); const [detailRecord, setDetailRecord] = useState | null>(null); const [importModalOpen, setImportModalOpen] = useState(false); const [exporting, setExporting] = useState(false); const enableViews = enableViewsProp || (() => { return ['table']; })(); 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 handleBatchDelete = async () => { if (!pluginId || !entityName || selectedRowKeys.length === 0) return; try { await batchPluginData(pluginId, entityName, { action: 'delete', ids: selectedRowKeys, }); message.success(`已删除 ${selectedRowKeys.length} 条记录`); setSelectedRowKeys([]); fetchData(); } catch { message.error('批量删除失败'); } }; const columns = useMemo(() => [ ...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 ? : ; if (f.ref_entity) { const uuid = String(val ?? ''); if (!uuid || uuid === '-') return '-'; const label = resolvedLabels[f.name]?.[uuid]; const installed = labelMeta[f.name]?.plugin_installed !== false; if (!installed) return {f.ref_fallback_label || '外部引用'}; if (label === null) return 无效引用; if (label) return {label}; } return String(val ?? '-'); }, })), { title: '操作', key: 'action', width: hasDetailPage ? 200 : 150, render: (_: unknown, record: Record) => ( {hasDetailPage && ( )} handleDelete(record)}> ), }, ], [fields, resolvedLabels, labelMeta, hasDetailPage, handleDelete]); 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