/** * 功能:为资源表单和详情页加载、缓存并搜索关联资源选项。 * 版本:v1.0.0 */ import { Message } from '@arco-design/web-vue'; import { onBeforeUnmount, reactive } from 'vue'; import { resourceApi } from '@/api/resource'; import type { ResourceField } from '@/api/resources'; import type { ResourceRow } from '@/api/resource-page-rules'; import { createRelationRequestVersionGuard } from './resource-relation-linkage-policy'; export type ResourceRelationLoadOptions = { filters?: Record; preserveIdentities?: string[]; }; export function useResourceRelations() { const options = reactive>({}); const loading = reactive>({}); const timers = new Map>(); const requestGuard = createRelationRequestVersionGuard(); /** 合并需要保留的当前选项,搜索和筛选时不让下拉框退回显示原始标识。 */ function mergePreserved( resource: string, rows: ResourceRow[], identities: string[] = [], ) { const merged = [...rows]; const seen = new Set(rows.map((row) => String(row.identity ?? ''))); for (const identity of identities.filter(Boolean)) { if (seen.has(identity)) continue; const current = (options[resource] ?? []).find( (row) => String(row.identity ?? '') === identity, ); if (current) { merged.push(current); seen.add(identity); } } return merged; } /** 加载一类关联资源,并丢弃已经过期的搜索或筛选响应。 */ async function load( resource: string, keyword = '', request: ResourceRelationLoadOptions = {}, ) { const version = requestGuard.begin(resource); loading[resource] = true; try { const filters: Record = { ...(request.filters ?? {}), ...(keyword ? { keyword } : {}), }; if (resource === '/staff_account') filters.role_code = 'delivery'; const rows = ( await resourceApi.list(resource, 1, 100, filters) ).list; if (!requestGuard.isLatest(resource, version)) return; options[resource] = mergePreserved( resource, rows, request.preserveIdentities, ); } catch (error) { if (!requestGuard.isLatest(resource, version)) return; Message.error(`关联数据加载失败:${(error as Error).message}`); } finally { if (requestGuard.isLatest(resource, version)) { loading[resource] = false; } } } /** 按唯一标识补载当前选项,解决编辑记录不在列表前 100 条时的回显。 */ async function ensure(resource: string, identity: string) { if (!identity) return undefined; const current = (options[resource] ?? []).find( (row) => String(row.identity ?? '') === identity, ); if (current) return current; try { const row = await resourceApi.detail(resource, identity); const resolved = (options[resource] ?? []).find( (item) => String(item.identity ?? '') === identity, ); if (resolved) return resolved; options[resource] = [...(options[resource] ?? []), row]; return row; } catch (error) { Message.error(`关联数据加载失败:${(error as Error).message}`); return undefined; } } /** 预加载当前页面实际使用的全部关系字段。 */ async function preload(fields: ResourceField[]) { const resources = new Set( fields .map((field) => field.relation) .filter((value): value is string => Boolean(value)), ); await Promise.all([...resources].map((resource) => load(resource))); } /** 对远程关系选项进行防抖搜索。 */ function search( resource: string | undefined, keyword: string, request: ResourceRelationLoadOptions = {}, ) { if (!resource) return; cancelSearch(resource); timers.set( resource, setTimeout(() => { timers.delete(resource); void load(resource, keyword.trim(), request); }, 250), ); } /** 取消关联资源尚未触发的防抖搜索。 */ function cancelSearch(resource: string) { const timer = timers.get(resource); if (timer) clearTimeout(timer); timers.delete(resource); } onBeforeUnmount(() => { for (const resource of timers.keys()) cancelSearch(resource); }); return { options, loading, load, ensure, preload, search, cancelSearch, }; } export type ResourceRelations = ReturnType;