修复工作人员资质关联与角色过滤
按安装、配送、运维菜单显式过滤工作人员,锁定资质所有者并使用姓名和角色回显。 扩展工作人员状态与多角色查询,补充权限校验、回归测试、操作日志和项目文档。
This commit is contained in:
@@ -72,7 +72,7 @@ const ownershipKeys = [
|
||||
|
||||
/** 业务动作字段不启用记录页联动,只沿用普通关联搜索。 */
|
||||
function searchRelation(field: ResourceField, keyword: string) {
|
||||
relations.search(field.relation, keyword);
|
||||
relations.searchField(field, keyword);
|
||||
}
|
||||
|
||||
watch(
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
<span class="detail-label">{{ entry.label }}</span>
|
||||
<pre v-if="entry.objectValue" class="json-value">{{ entry.value }}</pre>
|
||||
<IdentityText v-else-if="entry.key === 'identity'" :value="String(entry.value)" />
|
||||
<div v-else-if="entry.identityValue" class="detail-relation-value">
|
||||
<span class="detail-value">{{ entry.value }}</span>
|
||||
<IdentityText :value="entry.identityValue" />
|
||||
</div>
|
||||
<span v-else class="detail-value">{{ entry.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,6 +75,7 @@ type DetailEntry = {
|
||||
value: string;
|
||||
objectValue: boolean;
|
||||
wide: boolean;
|
||||
identityValue: string;
|
||||
};
|
||||
|
||||
const entries = computed<DetailEntry[]>(() => {
|
||||
@@ -115,6 +120,8 @@ const entries = computed<DetailEntry[]>(() => {
|
||||
label: resourceFieldLabel(props.definition, actualKey),
|
||||
value: display,
|
||||
objectValue,
|
||||
identityValue:
|
||||
field?.staffRelation?.showIdentityCopy && value ? String(value) : '',
|
||||
wide:
|
||||
objectValue ||
|
||||
/(address|terms|content|body|remark|reason|params|args)$/.test(key),
|
||||
@@ -189,6 +196,10 @@ const collections = computed(() =>
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.detail-relation-value {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
.json-value {
|
||||
max-height: 360px;
|
||||
margin: 0;
|
||||
|
||||
@@ -67,26 +67,34 @@
|
||||
{{ option.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-else-if="field.type === 'identity' || field.type === 'identity-list'"
|
||||
v-model="model[field.key]"
|
||||
:disabled="disabledSet.has(field.key)"
|
||||
:multiple="field.type === 'identity-list'"
|
||||
:placeholder="field.placeholder"
|
||||
:loading="relationLoading[field.relation ?? '']"
|
||||
allow-clear
|
||||
allow-search
|
||||
@change="(value: unknown) => emit('change-relation', field, value)"
|
||||
@search="(value: string) => emit('search-relation', field, value)"
|
||||
>
|
||||
<a-option
|
||||
v-for="option in relationOptions[field.relation ?? ''] ?? []"
|
||||
:key="String(option.identity)"
|
||||
:value="String(option.identity)"
|
||||
<div v-else-if="field.type === 'identity' || field.type === 'identity-list'" class="relation-control">
|
||||
<a-select
|
||||
v-model="model[field.key]"
|
||||
:disabled="disabledSet.has(field.key)"
|
||||
:multiple="field.type === 'identity-list'"
|
||||
:placeholder="field.placeholder"
|
||||
:loading="relationLoading[field.relation ?? '']"
|
||||
allow-clear
|
||||
allow-search
|
||||
@change="(value: unknown) => emit('change-relation', field, value)"
|
||||
@search="(value: string) => emit('search-relation', field, value)"
|
||||
>
|
||||
{{ optionLabel(option) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-option
|
||||
v-for="option in relationOptions[field.relation ?? ''] ?? []"
|
||||
:key="String(option.identity)"
|
||||
:value="String(option.identity)"
|
||||
>
|
||||
{{ relationOptionLabel(field, option) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<div
|
||||
v-if="disabledSet.has(field.key) && field.staffRelation?.showIdentityCopy && model[field.key]"
|
||||
class="relation-identity"
|
||||
>
|
||||
<span>人员标识</span>
|
||||
<IdentityText :value="String(model[field.key])" />
|
||||
</div>
|
||||
</div>
|
||||
<a-input
|
||||
v-else
|
||||
v-model="model[field.key]"
|
||||
@@ -99,10 +107,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { optionLabel } from '@/api/resource-display';
|
||||
import { relationOptionLabel } from '@/api/resource-display';
|
||||
import type { ResourceField } from '@/api/resources';
|
||||
import type { ResourceRow } from '@/api/resource-page-rules';
|
||||
import type { PlatformRole } from '@/api/platform';
|
||||
import IdentityText from '@/components/IdentityText.vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -175,6 +184,17 @@ function selectOptions(field: ResourceField) {
|
||||
:deep(.arco-picker) {
|
||||
width: 100%;
|
||||
}
|
||||
.relation-control {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
.relation-identity {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
color: var(--color-text-3);
|
||||
font-size: 12px;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.field-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -131,7 +131,7 @@ import { IconEdit, IconLeft } from '@arco-design/web-vue/es/icon';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ApiError } from '@/api/http';
|
||||
import { platformApi, type PlatformRole } from '@/api/platform';
|
||||
import type { PlatformRole } from '@/api/platform';
|
||||
import { resourceApi } from '@/api/resource';
|
||||
import { buildResourcePayload } from '@/api/resource-form';
|
||||
import {
|
||||
@@ -169,9 +169,10 @@ import ResourceDetailContent from './ResourceDetailContent.vue';
|
||||
import ResourceFieldForm from './ResourceFieldForm.vue';
|
||||
import ResourceWalletSummary from './ResourceWalletSummary.vue';
|
||||
import { createResourceRecordNavigation } from './resource-record-navigation';
|
||||
import { loadResourceRecordRelations } from './load-resource-record-relations';
|
||||
import { useResourceAvatar } from './use-resource-avatar';
|
||||
import { useResourceRelationLinkage } from './use-resource-relation-linkage';
|
||||
import { useResourceRelations } from './use-resource-relations';
|
||||
import { useStaffCredentialOwnerGuard } from './use-staff-credential-owner-guard';
|
||||
import { useUnsavedRecord } from './use-unsaved-record';
|
||||
|
||||
const route = useRoute();
|
||||
@@ -199,7 +200,22 @@ const roleOptions = ref<PlatformRole[]>([]);
|
||||
const fieldOptions = reactive<
|
||||
Record<string, Array<{ label: string; value: string | number }>>
|
||||
>({});
|
||||
const relations = useResourceRelations();
|
||||
const staffOwnerGuard = useStaffCredentialOwnerGuard({
|
||||
definitionName: () => definition.value.name,
|
||||
mode: () => mode.value,
|
||||
record: () => record.value,
|
||||
ownerIdentity: () =>
|
||||
String(
|
||||
mode.value === 'create'
|
||||
? (route.query.owner_identity ?? '')
|
||||
: (record.value.staff_account_identity ?? ''),
|
||||
),
|
||||
explicitStaffType: () => route.query.staff_type,
|
||||
routeStaffType: () => route.query.staff_type ?? route.meta.staffType,
|
||||
returnPath: () => returnPath.value,
|
||||
});
|
||||
const resolvedStaffType = staffOwnerGuard.resolvedStaffType;
|
||||
const relations = staffOwnerGuard.relations;
|
||||
const relationLinkage = useResourceRelationLinkage(form, relations);
|
||||
const actionVisible = ref(false);
|
||||
const activeAction = ref<DetailAction>();
|
||||
@@ -212,8 +228,7 @@ const clearAvatar = avatar.clear;
|
||||
const accountSummary = computed(() => usesAccountSummary(definition.value));
|
||||
const accountSummaryVisible = computed(
|
||||
() =>
|
||||
accountSummary.value &&
|
||||
(mode.value !== 'create' || hasAvatarField.value),
|
||||
accountSummary.value && (mode.value !== 'create' || hasAvatarField.value),
|
||||
);
|
||||
const hasAvatarField = computed(() =>
|
||||
definition.value.fields.some((field) => field.key === 'avatar'),
|
||||
@@ -246,12 +261,16 @@ const editableKeySet = computed(
|
||||
const readonlyKeys = computed(() =>
|
||||
mode.value === 'create'
|
||||
? formFields.value
|
||||
.filter((field) => field.readonlyOnCreate)
|
||||
.filter(
|
||||
(field) =>
|
||||
field.readonlyOnCreate ||
|
||||
(field.staffRelation?.lockPrefilled && Boolean(form[field.key])),
|
||||
)
|
||||
.map((field) => field.key)
|
||||
: mode.value === 'edit'
|
||||
? formFields.value
|
||||
.filter((field) => !editableKeySet.value.has(field.key))
|
||||
.map((field) => field.key)
|
||||
.filter((field) => !editableKeySet.value.has(field.key))
|
||||
.map((field) => field.key)
|
||||
: [],
|
||||
);
|
||||
const requiredKeys = computed(() =>
|
||||
@@ -320,6 +339,7 @@ async function initialize() {
|
||||
}
|
||||
loading.value = true;
|
||||
errorMessage.value = '';
|
||||
staffOwnerGuard.reset();
|
||||
try {
|
||||
if (mode.value !== 'create')
|
||||
detail.value = await resourceApi.detail<ResourceRow>(
|
||||
@@ -336,10 +356,24 @@ async function initialize() {
|
||||
mode.value === 'create' ? String(route.query.relation_key ?? '') : '',
|
||||
ownerIdentity:
|
||||
mode.value === 'create' ? String(route.query.owner_identity ?? '') : '',
|
||||
staffType:
|
||||
mode.value === 'create' ? String(route.meta.staffType ?? '') : '',
|
||||
staffType: mode.value === 'create' ? resolvedStaffType.value : '',
|
||||
});
|
||||
const ownerGuardResult = await staffOwnerGuard.prepare();
|
||||
if (!ownerGuardResult.ok) {
|
||||
errorStatus.value = ownerGuardResult.status;
|
||||
errorMessage.value = ownerGuardResult.message;
|
||||
return;
|
||||
}
|
||||
await loadResourceRecordRelations({
|
||||
detailMode: mode.value === 'detail',
|
||||
definition: definition.value,
|
||||
formFields: formFields.value,
|
||||
record: record.value,
|
||||
relations,
|
||||
relationLinkage,
|
||||
fieldOptions,
|
||||
roleOptions,
|
||||
});
|
||||
await loadRelationsAndRoles();
|
||||
// 钱包和头像属于附加信息,读取失败时不能阻断主详情或基础表单。
|
||||
await Promise.allSettled([loadWallet(), loadAvatar()]);
|
||||
unsaved.markInitialized();
|
||||
@@ -359,31 +393,6 @@ async function initialize() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRelationsAndRoles() {
|
||||
if (mode.value === 'detail') {
|
||||
await relations.preload(definition.value.fields);
|
||||
} else {
|
||||
await relationLinkage.preload(formFields.value);
|
||||
}
|
||||
if (
|
||||
definition.value.fields.some((field) => field.key === 'platform_role_code')
|
||||
) {
|
||||
try {
|
||||
const roles = (await platformApi.listRole()).list;
|
||||
fieldOptions.platform_role_code = roles.map((role) => ({
|
||||
label: role.name,
|
||||
value: role.role_code,
|
||||
}));
|
||||
roleOptions.value = roles.filter(
|
||||
(role) => !role.is_system && role.status === 1,
|
||||
);
|
||||
} catch (error) {
|
||||
fieldOptions.platform_role_code = [];
|
||||
Message.warning(`平台角色加载失败:${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadWallet() {
|
||||
if (
|
||||
!definition.value.walletOwnerType ||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 功能:统一加载资源记录页的关系选项和平台角色选项。
|
||||
* 版本:v1.0.0
|
||||
*/
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { platformApi, type PlatformRole } from '@/api/platform';
|
||||
import type { ResourceField, ResourceUiDefinition } from '@/api/resources';
|
||||
import type { ResourceRow } from '@/api/resource-page-rules';
|
||||
import type { ResourceRelations } from './use-resource-relations';
|
||||
|
||||
type LoaderOptions = {
|
||||
detailMode: boolean;
|
||||
definition: ResourceUiDefinition;
|
||||
formFields: ResourceField[];
|
||||
record: ResourceRow;
|
||||
relations: ResourceRelations;
|
||||
relationLinkage: { preload: (fields: ResourceField[]) => Promise<void> };
|
||||
fieldOptions: Record<
|
||||
string,
|
||||
Array<{ label: string; value: string | number }>
|
||||
>;
|
||||
roleOptions: Ref<PlatformRole[]>;
|
||||
};
|
||||
|
||||
/** 加载当前页面实际使用的关系和动态平台角色。 */
|
||||
export async function loadResourceRecordRelations(options: LoaderOptions) {
|
||||
if (options.detailMode) {
|
||||
await options.relations.preload(options.definition.fields);
|
||||
await options.relations.ensureValues(
|
||||
options.definition.fields,
|
||||
options.record,
|
||||
);
|
||||
} else {
|
||||
await options.relationLinkage.preload(options.formFields);
|
||||
}
|
||||
if (
|
||||
!options.definition.fields.some(
|
||||
(field) => field.key === 'platform_role_code',
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const roles = (await platformApi.listRole()).list;
|
||||
options.fieldOptions.platform_role_code = roles.map((role) => ({
|
||||
label: role.name,
|
||||
value: role.role_code,
|
||||
}));
|
||||
options.roleOptions.value = roles.filter(
|
||||
(role) => !role.is_system && role.status === 1,
|
||||
);
|
||||
} catch (error) {
|
||||
options.fieldOptions.platform_role_code = [];
|
||||
Message.warning(`平台角色加载失败:${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
@@ -57,10 +57,11 @@ export function useResourceRelationLinkage(
|
||||
}
|
||||
|
||||
async function reloadChildOptions() {
|
||||
const resource = active?.childField.relation;
|
||||
const childField = active?.childField;
|
||||
const resource = childField?.relation;
|
||||
if (!resource) return;
|
||||
relations.cancelSearch(resource);
|
||||
await relations.load(resource, '', childRequest());
|
||||
await relations.loadField(childField, '', childRequest());
|
||||
}
|
||||
|
||||
/** 预加载普通关联项,并单独按父级条件加载启用联动的子选项。 */
|
||||
@@ -69,6 +70,7 @@ export function useResourceRelationLinkage(
|
||||
configure(fields);
|
||||
if (!active?.childField.relationLinkage || !active.childField.relation) {
|
||||
await relations.preload(fields);
|
||||
await relations.ensureValues(fields, form);
|
||||
initialized = true;
|
||||
return;
|
||||
}
|
||||
@@ -108,11 +110,7 @@ export function useResourceRelationLinkage(
|
||||
return;
|
||||
}
|
||||
if (
|
||||
shouldClearLinkedOption(
|
||||
parentIdentity,
|
||||
option,
|
||||
linkage.optionParentKey,
|
||||
)
|
||||
shouldClearLinkedOption(parentIdentity, option, linkage.optionParentKey)
|
||||
) {
|
||||
form[active.childField.key] = '';
|
||||
Message.info('所属气站已变更,请重新选择配送点');
|
||||
@@ -169,10 +167,10 @@ export function useResourceRelationLinkage(
|
||||
function search(field: ResourceField, keyword: string) {
|
||||
if (!field.relation) return;
|
||||
if (field.key === active?.childField.key) {
|
||||
relations.search(field.relation, keyword, childRequest());
|
||||
relations.searchField(field, keyword, childRequest());
|
||||
return;
|
||||
}
|
||||
relations.search(field.relation, keyword);
|
||||
relations.searchField(field, keyword);
|
||||
}
|
||||
|
||||
/** 返回保存前的父子关系错误,空字符串代表当前组合可提交。 */
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 功能:校验工作人员资质页面的所有者、来源角色和访问状态。
|
||||
* 版本:v1.0.0
|
||||
*/
|
||||
import { ref } from 'vue';
|
||||
import type { LocationQueryValue } from 'vue-router';
|
||||
import { ApiError } from '@/api/http';
|
||||
import { resourceApi } from '@/api/resource';
|
||||
import {
|
||||
credentialOwnerValidationMessage,
|
||||
normalizeStaffRole,
|
||||
resolveCredentialStaffRole,
|
||||
type StaffRoleCode,
|
||||
} from '@/api/resource-staff-relation';
|
||||
import type { RecordPageMode, ResourceRow } from '@/api/resource-page-rules';
|
||||
import { useResourceRelations } from './use-resource-relations';
|
||||
|
||||
type GuardOptions = {
|
||||
definitionName: () => string;
|
||||
mode: () => RecordPageMode;
|
||||
record: () => ResourceRow;
|
||||
ownerIdentity: () => string;
|
||||
explicitStaffType: () => LocationQueryValue | LocationQueryValue[];
|
||||
routeStaffType: () => unknown;
|
||||
returnPath: () => string;
|
||||
};
|
||||
|
||||
type GuardResult =
|
||||
| { ok: true }
|
||||
| { ok: false; status: '403' | '404'; message: string };
|
||||
|
||||
/** 创建资质所有者守卫,并向关系加载器提供已验证的角色上下文。 */
|
||||
export function useStaffCredentialOwnerGuard(options: GuardOptions) {
|
||||
const resolvedStaffType = ref<StaffRoleCode | ''>(
|
||||
normalizeStaffRole(options.routeStaffType()),
|
||||
);
|
||||
const relations = useResourceRelations(() => ({
|
||||
staffType: resolvedStaffType.value,
|
||||
}));
|
||||
|
||||
/** 每次页面重新初始化时重置路由角色,避免复用上一次记录的上下文。 */
|
||||
function reset() {
|
||||
resolvedStaffType.value = normalizeStaffRole(options.routeStaffType());
|
||||
}
|
||||
|
||||
async function prepare(): Promise<GuardResult> {
|
||||
if (options.definitionName() !== 'staff_credential') return { ok: true };
|
||||
const ownerIdentity = options.ownerIdentity();
|
||||
if (options.mode() === 'create') {
|
||||
const resolved = resolveCredentialStaffRole(
|
||||
options.explicitStaffType(),
|
||||
options.returnPath(),
|
||||
);
|
||||
if (resolved.error) {
|
||||
return { ok: false, status: '403', message: resolved.error };
|
||||
}
|
||||
resolvedStaffType.value = resolved.role;
|
||||
}
|
||||
if (!ownerIdentity) {
|
||||
return {
|
||||
ok: false,
|
||||
status: '403',
|
||||
message: '缺少工作人员唯一标识,请从工作人员页面进入',
|
||||
};
|
||||
}
|
||||
let owner: ResourceRow;
|
||||
try {
|
||||
owner = await resourceApi.detail<ResourceRow>(
|
||||
'/staff_account',
|
||||
ownerIdentity,
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
status:
|
||||
error instanceof ApiError && error.status === 404 ? '404' : '403',
|
||||
message: `无法确认资质所属工作人员:${(error as Error).message}`,
|
||||
};
|
||||
}
|
||||
if (options.mode() !== 'create') {
|
||||
resolvedStaffType.value = normalizeStaffRole(owner.role_code);
|
||||
}
|
||||
const validationMessage = credentialOwnerValidationMessage(
|
||||
ownerIdentity,
|
||||
resolvedStaffType.value as StaffRoleCode,
|
||||
owner,
|
||||
);
|
||||
if (validationMessage) {
|
||||
return { ok: false, status: '403', message: validationMessage };
|
||||
}
|
||||
relations.options['/staff_account'] = [owner];
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
return { resolvedStaffType, relations, reset, prepare };
|
||||
}
|
||||
Reference in New Issue
Block a user