/** * 功能描述:加载配送点资源关系候选,并处理合同驱动的父子联动与过期响应。 * 版本:v1.0.0。 */ import { Message } from '@arco-design/web-vue'; import { onBeforeUnmount, reactive } from 'vue'; import { resourceApi } from '@/api/resource'; import type { ResourceRow } from '@/api/resource-record-form'; import type { ResourceField } from '@/api/resources'; /** 创建关系候选状态;同一资源仅允许最新请求更新页面。 */ export function useResourceRelations(form: Record) { const options = reactive>({}); const loading = reactive>({}); const versions = new Map(); const timers = new Map>(); function begin(resource: string) { const version = (versions.get(resource) ?? 0) + 1; versions.set(resource, version); return version; } function isLatest(resource: string, version: number) { return versions.get(resource) === version; } /** 清空候选并使尚未返回的旧请求失效。 */ function clear(resource: string | undefined) { if (!resource) return; begin(resource); options[resource] = []; loading[resource] = false; } /** 返回字段当前必须携带的静态与父级筛选参数。 */ function filters(field: ResourceField) { const linkage = field.relationLinkage; const parent = linkage ? String(form[linkage.parentKey] ?? '').trim() : ''; return { ...(field.relation === '/staff_account' ? { role_code: 'delivery' } : {}), ...(field.relationFilters ?? {}), ...(linkage && parent ? { [linkage.filterKey]: parent } : {}), }; } /** 加载一个关系字段,搜索时仍保留当前已选候选的可读文案。 */ async function load(field: ResourceField, keyword = '') { const resource = field.relation; if (!resource) return; const linkage = field.relationLinkage; if ( linkage?.requiresParent && !String(form[linkage.parentKey] ?? '').trim() ) { clear(resource); return; } const version = begin(resource); loading[resource] = true; const selected: string[] = ( Array.isArray(form[field.key]) ? form[field.key] : [form[field.key]] ) .map((value: unknown) => String(value ?? '')) .filter(Boolean); try { const rows = ( await resourceApi.list(resource, 1, 100, { ...filters(field), ...(keyword ? { keyword } : {}), }) ).list; if (!isLatest(resource, version)) return; const validIdentities = new Set( rows.map((row) => String(row.identity ?? '')), ); if ( field.relationStrictFilter && !keyword && selected.some((identity) => !validIdentities.has(identity)) ) { form[field.key] = field.type === 'identity-list' ? [] : ''; Message.info( field.relationInvalidMessage ?? '所选关联数据已失效,已清空', ); } const merged = [...rows]; const identities = new Set(validIdentities); for (const identity of selected) { if (field.relationStrictFilter && !keyword) continue; const current = (options[resource] ?? []).find( (row) => String(row.identity ?? '') === identity, ); if (current && !identities.has(identity)) merged.push(current); } options[resource] = merged; if (!selected.length && field.relationAutoSelectKey) { const preferred = rows.find((row) => Boolean(row[field.relationAutoSelectKey as string]), ); if (preferred && isLatest(resource, version)) form[field.key] = String(preferred.identity ?? ''); } } catch (error) { if (isLatest(resource, version)) Message.error(`关联数据加载失败:${(error as Error).message}`); } finally { if (isLatest(resource, version)) loading[resource] = false; } } /** 先加载普通关系,再加载已有父值约束下的子关系。 */ async function preload(fields: ResourceField[]) { const unique = new Map(); for (const field of fields) { if (field.relation && !unique.has(field.relation)) unique.set(field.relation, field); } await Promise.all( [...unique.values()] .filter((field) => !field.relationLinkage) .map((field) => load(field)), ); await Promise.all( [...unique.values()] .filter((field) => field.relationLinkage) .map((field) => load(field)), ); } /** 父关系变化时原子清空所有子值,再按新父值加载候选。 */ async function change(field: ResourceField) { const fields = configuredFields; const children = fields.filter( (item) => item.relationLinkage?.parentKey === field.key, ); await Promise.all( children.map(async (child) => { const previous = Array.isArray(form[child.key]) ? form[child.key].length > 0 : Boolean(form[child.key]); form[child.key] = child.type === 'identity-list' ? [] : ''; clear(child.relation); if (previous) Message.info( child.relationLinkage?.parentChangeMessage ?? '上级数据已变更,请重新选择', ); await load(child); }), ); } let configuredFields: ResourceField[] = []; function configure(fields: ResourceField[]) { configuredFields = fields; } /** 搜索采用 250ms 防抖,并始终附带当前父级过滤。 */ function search(field: ResourceField, keyword: string) { const resource = field.relation; if (!resource) return; const timer = timers.get(resource); if (timer) clearTimeout(timer); timers.set( resource, setTimeout(() => { timers.delete(resource); void load(field, keyword.trim()); }, 250), ); } /** 返回必填候选为空时的业务提示。 */ function unavailableMessage(fields: ResourceField[]) { for (const field of fields) { if (!field.required || !field.relation || !field.relationEmptyText) continue; const linkage = field.relationLinkage; if ( linkage?.requiresParent && !String(form[linkage.parentKey] ?? '').trim() ) continue; if ( !loading[field.relation] && (options[field.relation] ?? []).length === 0 ) return field.relationEmptyText; } return ''; } function isLoading(fields: ResourceField[]) { return fields.some((field) => Boolean(field.relation && loading[field.relation]), ); } onBeforeUnmount(() => { for (const timer of timers.values()) clearTimeout(timer); }); return { options, loading, configure, preload, load, change, search, clear, unavailableMessage, isLoading, }; }