Files
platforms/frontend/platform_admin/src/views/resource/use-resource-relations.ts

148 lines
4.5 KiB
TypeScript
Raw Normal View History

/**
*
* v1.0.0
*/
import { Message } from '@arco-design/web-vue';
2026-08-11 14:28:13 +08:00
import { onBeforeUnmount, reactive } from 'vue';
import { resourceApi } from '@/api/resource';
import type { ResourceField } from '@/api/resources';
import type { ResourceRow } from '@/api/resource-page-rules';
2026-08-11 14:28:13 +08:00
import { createRelationRequestVersionGuard } from './resource-relation-linkage-policy';
export type ResourceRelationLoadOptions = {
filters?: Record<string, string>;
preserveIdentities?: string[];
};
export function useResourceRelations() {
const options = reactive<Record<string, ResourceRow[]>>({});
const loading = reactive<Record<string, boolean>>({});
const timers = new Map<string, ReturnType<typeof setTimeout>>();
2026-08-11 14:28:13 +08:00
const requestGuard = createRelationRequestVersionGuard();
2026-08-11 14:28:13 +08:00
/** 合并需要保留的当前选项,搜索和筛选时不让下拉框退回显示原始标识。 */
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 {
2026-08-11 14:28:13 +08:00
const filters: Record<string, string> = {
...(request.filters ?? {}),
...(keyword ? { keyword } : {}),
};
if (resource === '/staff_account') filters.role_code = 'delivery';
2026-08-11 14:28:13 +08:00
const rows = (
await resourceApi.list<ResourceRow>(resource, 1, 100, filters)
).list;
2026-08-11 14:28:13 +08:00
if (!requestGuard.isLatest(resource, version)) return;
options[resource] = mergePreserved(
resource,
rows,
request.preserveIdentities,
);
} catch (error) {
2026-08-11 14:28:13 +08:00
if (!requestGuard.isLatest(resource, version)) return;
Message.error(`关联数据加载失败:${(error as Error).message}`);
} finally {
2026-08-11 14:28:13 +08:00
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<ResourceRow>(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)));
}
/** 对远程关系选项进行防抖搜索。 */
2026-08-11 14:28:13 +08:00
function search(
resource: string | undefined,
keyword: string,
request: ResourceRelationLoadOptions = {},
) {
if (!resource) return;
2026-08-11 14:28:13 +08:00
cancelSearch(resource);
timers.set(
resource,
2026-08-11 14:28:13 +08:00
setTimeout(() => {
timers.delete(resource);
void load(resource, keyword.trim(), request);
}, 250),
);
}
2026-08-11 14:28:13 +08:00
/** 取消关联资源尚未触发的防抖搜索。 */
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,
};
}
2026-08-11 14:28:13 +08:00
export type ResourceRelations = ReturnType<typeof useResourceRelations>;