import { useEffect, useState, useCallback } from 'react'; import { message } from 'antd'; import { listPluginData, resolveRefLabels, type PluginDataListOptions, } from '../../api/pluginData'; import { getPluginSchema, type PluginFieldSchema, type PluginEntitySchema, type PluginPageSchema, type PluginSectionSchema, } from '../../api/plugins'; export interface PluginDataState { records: Record[]; total: number; page: number; loading: boolean; fields: PluginFieldSchema[]; displayName: string; filters: Record; searchText: string; sortBy: string | undefined; sortOrder: 'asc' | 'desc'; resolvedLabels: Record>; labelMeta: Record; entityDef: PluginEntitySchema | null; allEntities: PluginEntitySchema[]; allPages: PluginPageSchema[]; detailSections: PluginSectionSchema[]; hasDetailPage: boolean; filterableFields: PluginFieldSchema[]; } export interface PluginDataActions { setRecords: React.Dispatch[]>>; setPage: React.Dispatch>; setFilters: React.Dispatch>>; setSearchText: React.Dispatch>; setSortBy: React.Dispatch>; setSortOrder: React.Dispatch>; fetchData: (p?: number, overrides?: { search?: string; sort_by?: string; sort_order?: 'asc' | 'desc'; }) => Promise; handleFilterChange: (fieldName: string, value: string | undefined) => void; } export type PluginDataHook = PluginDataState & PluginDataActions; export function usePluginData( pluginId: string, entityName: string, filterField?: string, filterValue?: string, ): PluginDataHook { 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 [filters, setFilters] = useState>({}); const [searchText, setSearchText] = useState(''); const [sortBy, setSortBy] = useState(); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); const [resolvedLabels, setResolvedLabels] = useState>>({}); const [labelMeta, setLabelMeta] = useState>({}); const [entityDef, setEntityDef] = useState(null); const [allEntities, setAllEntities] = useState([]); const [allPages, setAllPages] = useState([]); const [detailSections, setDetailSections] = useState([]); const filterableFields = fields.filter((f) => f.filterable); const hasDetailPage = allPages.some( (p) => p.type === 'detail' && 'entity' in p && p.entity === entityName, ); // 加载 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 || ''); setEntityDef(entity); } 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]); // 数据加载后解析跨插件引用标签 useEffect(() => { if (!pluginId || !entityName || !records.length || !fields.length) return; const refFields = fields.filter((f) => f.ref_entity); if (!refFields.length) return; const fieldUuids: Record = {}; for (const f of refFields) { const uuids = [...new Set( records.map((r) => r[f.name]).filter(Boolean).map(String), )]; if (uuids.length) fieldUuids[f.name] = uuids; } if (!Object.keys(fieldUuids).length) return; resolveRefLabels(pluginId, entityName, fieldUuids) .then((result) => { setResolvedLabels(result.labels); setLabelMeta(result.meta as Record); }) .catch(() => {}); }, [records, fields, pluginId, entityName]); 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); }; return { records, total, page, loading, fields, displayName, filters, searchText, sortBy, sortOrder, resolvedLabels, labelMeta, entityDef, allEntities, allPages, detailSections, hasDetailPage, filterableFields, setRecords, setPage, setFilters, setSearchText, setSortBy, setSortOrder, fetchData, handleFilterChange, }; }