- useHealthStore 新增 batchResolvePatientNames/batchResolveDoctorNames 批量解析方法(去重 → 过滤已缓存 → 5 并发批次加载) - PointsOrderList 移除局部 nameCache,改用 useHealthStore 全局缓存 - PluginCRUDPage (871L) 拆分为 usePluginData + DetailDrawer + ImportModal + PluginCRUDPageInner,原文件改为 re-export - PluginGraphPage (765L) 拆分为 useGraphData + useGraphCanvas hooks - StatisticsDashboard (580L) 拆分为 useStatsData + HealthDataCenter
207 lines
7.2 KiB
TypeScript
207 lines
7.2 KiB
TypeScript
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<string, unknown>[];
|
|
total: number;
|
|
page: number;
|
|
loading: boolean;
|
|
fields: PluginFieldSchema[];
|
|
displayName: string;
|
|
filters: Record<string, string>;
|
|
searchText: string;
|
|
sortBy: string | undefined;
|
|
sortOrder: 'asc' | 'desc';
|
|
resolvedLabels: Record<string, Record<string, string | null>>;
|
|
labelMeta: Record<string, { plugin_installed: boolean }>;
|
|
entityDef: PluginEntitySchema | null;
|
|
allEntities: PluginEntitySchema[];
|
|
allPages: PluginPageSchema[];
|
|
detailSections: PluginSectionSchema[];
|
|
hasDetailPage: boolean;
|
|
filterableFields: PluginFieldSchema[];
|
|
}
|
|
|
|
export interface PluginDataActions {
|
|
setRecords: React.Dispatch<React.SetStateAction<Record<string, unknown>[]>>;
|
|
setPage: React.Dispatch<React.SetStateAction<number>>;
|
|
setFilters: React.Dispatch<React.SetStateAction<Record<string, string>>>;
|
|
setSearchText: React.Dispatch<React.SetStateAction<string>>;
|
|
setSortBy: React.Dispatch<React.SetStateAction<string | undefined>>;
|
|
setSortOrder: React.Dispatch<React.SetStateAction<'asc' | 'desc'>>;
|
|
fetchData: (p?: number, overrides?: {
|
|
search?: string;
|
|
sort_by?: string;
|
|
sort_order?: 'asc' | 'desc';
|
|
}) => Promise<void>;
|
|
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<Record<string, unknown>[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [page, setPage] = useState(1);
|
|
const [loading, setLoading] = useState(false);
|
|
const [fields, setFields] = useState<PluginFieldSchema[]>([]);
|
|
const [displayName, setDisplayName] = useState(entityName || '');
|
|
const [filters, setFilters] = useState<Record<string, string>>({});
|
|
const [searchText, setSearchText] = useState('');
|
|
const [sortBy, setSortBy] = useState<string | undefined>();
|
|
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
|
|
|
const [resolvedLabels, setResolvedLabels] = useState<Record<string, Record<string, string | null>>>({});
|
|
const [labelMeta, setLabelMeta] = useState<Record<string, { plugin_installed: boolean }>>({});
|
|
|
|
const [entityDef, setEntityDef] = useState<PluginEntitySchema | null>(null);
|
|
const [allEntities, setAllEntities] = useState<PluginEntitySchema[]>([]);
|
|
const [allPages, setAllPages] = useState<PluginPageSchema[]>([]);
|
|
const [detailSections, setDetailSections] = useState<PluginSectionSchema[]>([]);
|
|
|
|
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<string, string[]> = {};
|
|
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<string, { plugin_installed: boolean }>);
|
|
})
|
|
.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,
|
|
};
|
|
}
|