完善配送订单创建联动
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* 功能描述:加载配送点资源关系候选,并处理合同驱动的父子联动与过期响应。
|
||||
* 版本: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<string, any>) {
|
||||
const options = reactive<Record<string, ResourceRow[]>>({});
|
||||
const loading = reactive<Record<string, boolean>>({});
|
||||
const versions = new Map<string, number>();
|
||||
const timers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
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<ResourceRow>(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<string, ResourceField>();
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user