Files
hms/apps/web/src/pages/PluginCRUDPage/usePluginData.ts
iven 41af241238
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
refactor(web): 前端工程化 — 组件拆分 + 名称缓存统一
- useHealthStore 新增 batchResolvePatientNames/batchResolveDoctorNames
  批量解析方法(去重 → 过滤已缓存 → 5 并发批次加载)
- PointsOrderList 移除局部 nameCache,改用 useHealthStore 全局缓存
- PluginCRUDPage (871L) 拆分为 usePluginData + DetailDrawer +
  ImportModal + PluginCRUDPageInner,原文件改为 re-export
- PluginGraphPage (765L) 拆分为 useGraphData + useGraphCanvas hooks
- StatisticsDashboard (580L) 拆分为 useStatsData + HealthDataCenter
2026-04-27 20:56:27 +08:00

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,
};
}