<script lang="ts" setup>
|
import type { FormInstance } from 'ant-design-vue';
|
import type { Dayjs } from 'dayjs';
|
|
import type { DmsAction, DmsPermissionResource, DmsPrincipalType, DmsResourceScope } from '#/api/x/dms/permission';
|
|
import { computed, reactive, ref } from 'vue';
|
|
import { useMessage } from '@jnpf/hooks';
|
import { BasicDrawer, useDrawerInner } from '@jnpf/ui/drawer';
|
|
import dayjs from 'dayjs';
|
|
import { createPermissionGrant } from '#/api/x/dms/permission';
|
|
import PermissionGroupSelect from './PermissionGroupSelect.vue';
|
import { actionsForResource, normalizePrincipalId, scopesForResource } from './permissionRules';
|
|
interface GrantFormState {
|
actions: DmsAction[];
|
grantReason: string;
|
includeChildOrgs: boolean;
|
maxUseCount?: number;
|
principalId?: string;
|
principalType: DmsPrincipalType;
|
resourceScope: DmsResourceScope;
|
validity?: [Dayjs, Dayjs];
|
}
|
|
const emit = defineEmits<{
|
reload: [];
|
}>();
|
|
const formRef = ref<FormInstance>();
|
const resource = ref<DmsPermissionResource>();
|
const sourceId = ref('');
|
const form = reactive<GrantFormState>(defaultForm());
|
const { createMessage } = useMessage();
|
const [registerDrawer, { changeOkLoading, closeDrawer }] = useDrawerInner(init);
|
|
const principalOptions: Array<{ label: string; value: DmsPrincipalType }> = [
|
{ label: '用户', value: 'USER' },
|
{ label: '组织', value: 'ORG' },
|
{ label: '岗位', value: 'POSITION' },
|
{ label: '角色', value: 'ROLE' },
|
{ label: '权限组', value: 'GROUP' },
|
];
|
const availableActions = computed(() => (resource.value ? actionsForResource(resource.value.resourceType) : []));
|
const availableScopes = computed(() => (resource.value ? scopesForResource(resource.value.resourceType) : []));
|
const includeChildOrgsEnabled = computed(() => form.principalType === 'ORG');
|
|
function defaultForm(): GrantFormState {
|
return {
|
actions: ['READ'],
|
grantReason: '',
|
includeChildOrgs: false,
|
principalType: 'USER',
|
resourceScope: 'SELF',
|
};
|
}
|
|
function newSourceId() {
|
const value = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
return `manual-${value}`;
|
}
|
|
function init(data: { resource: DmsPermissionResource }) {
|
resource.value = data.resource;
|
Object.assign(form, defaultForm());
|
sourceId.value = newSourceId();
|
formRef.value?.clearValidate();
|
}
|
|
function handlePrincipalTypeChange() {
|
form.principalId = undefined;
|
form.includeChildOrgs = false;
|
if (form.principalType !== 'USER') form.maxUseCount = undefined;
|
formRef.value?.clearValidate(['principalId']);
|
}
|
|
async function handleSubmit() {
|
if (!resource.value) return;
|
await formRef.value?.validate();
|
changeOkLoading(true);
|
try {
|
const validFrom = form.validity?.[0]?.toISOString();
|
const validTo = form.validity?.[1]?.toISOString();
|
const response = await createPermissionGrant({
|
sourceId: sourceId.value,
|
sourceType: 'MANUAL',
|
items: form.actions.map((actionCode) => ({
|
actionCode,
|
grantReason: form.grantReason.trim() || undefined,
|
includeChildOrgs: includeChildOrgsEnabled.value && form.includeChildOrgs,
|
maxUseCount: form.maxUseCount,
|
principalId: normalizePrincipalId(form.principalId || ''),
|
principalType: form.principalType,
|
resourceId: resource.value!.id,
|
resourceScope: form.resourceScope,
|
resourceType: resource.value!.resourceType,
|
validFrom,
|
validTo,
|
})),
|
});
|
createMessage.success(response.msg || `已创建 ${response.data.length} 条授权`);
|
closeDrawer();
|
emit('reload');
|
} finally {
|
changeOkLoading(false);
|
}
|
}
|
|
function disabledDate(current: Dayjs) {
|
return current.isBefore(dayjs().startOf('day'));
|
}
|
</script>
|
|
<template>
|
<BasicDrawer
|
v-bind="$attrs"
|
@register="registerDrawer"
|
title="新增文件权限"
|
width="min(620px, calc(100vw - 16px))"
|
:mask-closable="false"
|
destroy-on-close
|
show-footer
|
@ok="handleSubmit">
|
<div class="grant-drawer-body" v-if="resource">
|
<a-alert class="mb-[20px]" type="info" show-icon>
|
<template #message> 授权资源:{{ resource.name }}({{ resource.resourceType }}) </template>
|
</a-alert>
|
<a-form ref="formRef" :model="form" layout="vertical">
|
<a-form-item label="授权主体类型" name="principalType" required>
|
<a-radio-group v-model:value="form.principalType" button-style="solid" @change="handlePrincipalTypeChange">
|
<a-radio-button v-for="option in principalOptions" :key="option.value" :value="option.value">
|
{{ option.label }}
|
</a-radio-button>
|
</a-radio-group>
|
</a-form-item>
|
<a-form-item label="授权主体" name="principalId" :rules="[{ required: true, message: '请选择授权主体' }]">
|
<JnpfUserSelect v-if="form.principalType === 'USER'" v-model:value="form.principalId" />
|
<JnpfOrganizeSelect v-else-if="form.principalType === 'ORG'" v-model:value="form.principalId" />
|
<JnpfPosSelect v-else-if="form.principalType === 'POSITION'" v-model:value="form.principalId" />
|
<JnpfRoleSelect v-else-if="form.principalType === 'ROLE'" v-model:value="form.principalId" />
|
<PermissionGroupSelect v-else v-model:value="form.principalId" />
|
</a-form-item>
|
<a-form-item label="允许操作" name="actions" :rules="[{ required: true, type: 'array', min: 1, message: '至少选择一个操作' }]">
|
<a-checkbox-group v-model:value="form.actions" :options="availableActions" />
|
</a-form-item>
|
<a-form-item label="资源范围" name="resourceScope" required>
|
<a-select v-model:value="form.resourceScope" :options="availableScopes" />
|
</a-form-item>
|
<a-form-item v-if="includeChildOrgsEnabled" label="组织范围">
|
<a-checkbox v-model:checked="form.includeChildOrgs">包含子组织成员</a-checkbox>
|
</a-form-item>
|
<a-form-item label="有效期">
|
<a-range-picker v-model:value="form.validity" class="w-full" :disabled-date="disabledDate" format="YYYY-MM-DD HH:mm" show-time />
|
</a-form-item>
|
<a-form-item v-if="form.principalType === 'USER'" label="最多使用次数" extra="不填写表示不限制;每次受限操作只扣减一次。">
|
<a-input-number v-model:value="form.maxUseCount" class="w-full" :min="1" :precision="0" placeholder="不限次数" />
|
</a-form-item>
|
<a-form-item label="授权原因">
|
<a-textarea v-model:value="form.grantReason" :maxlength="1000" :rows="3" show-count />
|
</a-form-item>
|
</a-form>
|
</div>
|
</BasicDrawer>
|
</template>
|
|
<style lang="scss" scoped>
|
.grant-drawer-body {
|
padding: 20px 24px 28px;
|
}
|
|
:deep(.ant-radio-group) {
|
display: flex;
|
flex-wrap: wrap;
|
}
|
</style>
|