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
This commit is contained in:
206
apps/web/src/pages/PluginCRUDPage/usePluginData.ts
Normal file
206
apps/web/src/pages/PluginCRUDPage/usePluginData.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user