完善配送订单创建联动

This commit is contained in:
czl231
2026-08-23 11:30:52 +08:00
parent c07c9ea6f2
commit 59a01192c0
15 changed files with 935 additions and 163 deletions

View File

@@ -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,
};
}