修复工作人员资质关联与角色过滤

按安装、配送、运维菜单显式过滤工作人员,锁定资质所有者并使用姓名和角色回显。

扩展工作人员状态与多角色查询,补充权限校验、回归测试、操作日志和项目文档。
This commit is contained in:
czl231
2026-08-11 19:15:53 +08:00
parent 4b1f045806
commit 74c5e34275
20 changed files with 976 additions and 100 deletions

View File

@@ -5,6 +5,10 @@
import { Message } from '@arco-design/web-vue';
import { onBeforeUnmount, reactive } from 'vue';
import { resourceApi } from '@/api/resource';
import {
staffRelationFilters,
type StaffRelationContext,
} from '@/api/resource-staff-relation';
import type { ResourceField } from '@/api/resources';
import type { ResourceRow } from '@/api/resource-page-rules';
import { createRelationRequestVersionGuard } from './resource-relation-linkage-policy';
@@ -14,7 +18,9 @@ export type ResourceRelationLoadOptions = {
preserveIdentities?: string[];
};
export function useResourceRelations() {
export function useResourceRelations(
context: () => StaffRelationContext = () => ({}),
) {
const options = reactive<Record<string, ResourceRow[]>>({});
const loading = reactive<Record<string, boolean>>({});
const timers = new Map<string, ReturnType<typeof setTimeout>>();
@@ -54,7 +60,6 @@ export function useResourceRelations() {
...(request.filters ?? {}),
...(keyword ? { keyword } : {}),
};
if (resource === '/staff_account') filters.role_code = 'delivery';
const rows = (
await resourceApi.list<ResourceRow>(resource, 1, 100, filters)
).list;
@@ -74,6 +79,22 @@ export function useResourceRelations() {
}
}
/** 按字段的显式关系策略加载选项,工作人员字段不得依赖全局默认值。 */
function loadField(
field: ResourceField,
keyword = '',
request: ResourceRelationLoadOptions = {},
) {
if (!field.relation) return Promise.resolve();
return load(field.relation, keyword, {
...request,
filters: {
...staffRelationFilters(field, context()),
...(request.filters ?? {}),
},
});
}
/** 按唯一标识补载当前选项,解决编辑记录不在列表前 100 条时的回显。 */
async function ensure(resource: string, identity: string) {
if (!identity) return undefined;
@@ -97,12 +118,29 @@ export function useResourceRelations() {
/** 预加载当前页面实际使用的全部关系字段。 */
async function preload(fields: ResourceField[]) {
const resources = new Set(
fields
.map((field) => field.relation)
.filter((value): value is string => Boolean(value)),
const fieldsByResource = new Map<string, ResourceField>();
for (const field of fields) {
if (field.relation && !fieldsByResource.has(field.relation)) {
fieldsByResource.set(field.relation, field);
}
}
await Promise.all(
[...fieldsByResource.values()].map((field) => loadField(field)),
);
await Promise.all([...resources].map((resource) => load(resource)));
}
/** 补载表单或详情中已经保存的关系值,避免分页和筛选导致回显裸标识。 */
async function ensureValues(fields: ResourceField[], values: ResourceRow) {
const requests = fields.flatMap((field) => {
if (!field.relation) return [];
const value = values[field.key];
const identities = Array.isArray(value) ? value : [value];
return identities
.map((identity) => String(identity ?? ''))
.filter(Boolean)
.map((identity) => ensure(field.relation as string, identity));
});
await Promise.all(requests);
}
/** 对远程关系选项进行防抖搜索。 */
@@ -122,6 +160,22 @@ export function useResourceRelations() {
);
}
/** 按字段的显式关系策略执行防抖搜索。 */
function searchField(
field: ResourceField,
keyword: string,
request: ResourceRelationLoadOptions = {},
) {
if (!field.relation) return;
search(field.relation, keyword, {
...request,
filters: {
...staffRelationFilters(field, context()),
...(request.filters ?? {}),
},
});
}
/** 取消关联资源尚未触发的防抖搜索。 */
function cancelSearch(resource: string) {
const timer = timers.get(resource);
@@ -137,9 +191,12 @@ export function useResourceRelations() {
options,
loading,
load,
loadField,
ensure,
ensureValues,
preload,
search,
searchField,
cancelSearch,
};
}