<script lang="ts" setup>
|
import type { FileAction, FilePermissionDesignerContext, FilePermissionPolicy, FilePermissionRule } from '../../helper/filePermissions';
|
|
import { computed, ref } from 'vue';
|
|
import { buildUUID } from '@jnpf/utils';
|
|
import { Empty as AEmpty, Modal as AModal } from 'ant-design-vue';
|
import { cloneDeep } from 'lodash-es';
|
|
import { fileActions, filePermissionDesignerContext, isEditableFilePermissionPolicy, validateDesignerFilePermissions } from '../../helper/filePermissions';
|
|
const props = defineProps<{ bpmn: any; flowType: number; modelValue?: FilePermissionPolicy | null }>();
|
const emit = defineEmits<{ 'update:modelValue': [value: FilePermissionPolicy] }>();
|
const open = ref(false);
|
const policy = ref<FilePermissionPolicy>({ version: 1, rules: [] });
|
const errors = ref<string[]>([]);
|
const malformed = ref(false);
|
const context = ref<FilePermissionDesignerContext>({ attachments: [], nodes: [], fieldsByForm: {}, nodeIds: new Set() });
|
const count = computed(() => (Array.isArray(props.modelValue?.rules) ? props.modelValue.rules.length : 0));
|
const actionNames: Record<FileAction, string> = {
|
VIEW: '查看',
|
DOWNLOAD: '下载',
|
EDIT: '在线编辑',
|
REVIEW: '审阅(审批模式)',
|
ADD: '上传新增',
|
REPLACE: '替换附件',
|
REMOVE: '移除附件',
|
};
|
const effects = [
|
{ value: 'INHERIT', label: '沿用原权限' },
|
{ value: 'ALLOW', label: '允许' },
|
{ value: 'DENY', label: '禁止' },
|
];
|
const subjects = [
|
{ value: 'CREATOR', label: '流程发起人' },
|
{ value: 'CURRENT_HANDLERS', label: '当前办理人' },
|
{ value: 'NON_CURRENT_HANDLERS', label: '非当前办理人' },
|
{ value: 'PARTICIPANTS', label: '本流程参与人' },
|
{ value: 'NODE_PARTICIPANTS', label: '指定节点参与人' },
|
];
|
const triggers = [
|
{ value: 'ACTIVE', label: '节点办理期间' },
|
{ value: 'ENTERED', label: '进入节点后持续生效' },
|
{ value: 'COMPLETED', label: '流程办结后' },
|
{ value: 'CANCELLED', label: '流程撤销后' },
|
{ value: 'TERMINATED', label: '流程终止后' },
|
];
|
const returns = [
|
{ value: 'KEEP', label: '退回后仍保留' },
|
{ value: 'RELEASE', label: '退回到触发节点前解除' },
|
];
|
function source() {
|
return {
|
data: props.bpmn.get('jnpfData').data,
|
ids: new Set<string>(
|
props.bpmn
|
.get('elementRegistry')
|
.getAll()
|
.map((node: any) => node.id),
|
),
|
};
|
}
|
function show() {
|
const { data, ids } = source();
|
context.value = filePermissionDesignerContext(data, ids);
|
malformed.value = props.modelValue != null && !isEditableFilePermissionPolicy(props.modelValue);
|
policy.value = malformed.value ? { version: 1, rules: [] } : cloneDeep(props.modelValue ?? { version: 1, rules: [] });
|
errors.value = malformed.value ? ['现有文件权限配置结构无法编辑,请先核对配置;关闭窗口会保留原配置。'] : [];
|
open.value = true;
|
}
|
function addRule() {
|
policy.value.rules.push({
|
id: buildUUID(),
|
formId: '',
|
field: '',
|
subject: 'CREATOR',
|
trigger: 'ACTIVE',
|
actions: Object.fromEntries(fileActions.map((action) => [action, 'INHERIT'])),
|
});
|
}
|
function targetValue(rule: FilePermissionRule) {
|
return rule.formId && rule.field ? JSON.stringify([rule.formId, rule.field]) : undefined;
|
}
|
function selectTarget(rule: FilePermissionRule, value: string) {
|
const target = context.value.attachments.find((item) => item.value === value);
|
if (!target) return;
|
rule.formId = target.formId;
|
rule.field = target.field;
|
}
|
function changeTrigger(rule: FilePermissionRule) {
|
if (!['ACTIVE', 'ENTERED'].includes(rule.trigger)) delete rule.triggerNodeId;
|
if (rule.trigger !== 'ENTERED') delete rule.onReturn;
|
}
|
function submit() {
|
if (malformed.value) return;
|
const { data, ids } = source();
|
context.value = filePermissionDesignerContext(data, ids);
|
const candidate = { ...data, global: { ...data.global, filePermissionPolicy: policy.value } };
|
errors.value = validateDesignerFilePermissions(candidate, ids, props.flowType);
|
if (errors.value.length) return;
|
// 无配置的老流程仅打开/确认窗口,不凭空新增策略。
|
if (props.modelValue != null || policy.value.rules.length) emit('update:modelValue', cloneDeep(policy.value));
|
open.value = false;
|
}
|
</script>
|
|
<template>
|
<a-button class="w-full" :disabled="![0, 1].includes(flowType)" @click="show">文件权限设置{{ count ? `(${count} 条)` : '' }}</a-button>
|
<div v-if="![0, 1].includes(flowType)" class="mt-[8px] text-gray-500">当前流程类型暂不支持文件权限规则</div>
|
<AModal v-model:open="open" title="流程文件权限" :width="880" :ok-button-props="{ disabled: malformed }" :mask-closable="false" @ok="submit">
|
<a-alert type="info" show-icon message="规则只影响使用该版本新建的本流程实例。未配置动作沿用原权限;禁止优先,允许不能突破表单和控件原有权限。" />
|
<div class="my-[12px] text-gray-500">按附件字段设置作用人员与生效条件。子表规则适用于该字段各行;已有流程实例不随配置修改。</div>
|
<div class="mb-[12px] text-gray-500">表单首次保存按原有上传权限保存初始附件;保存成功后,后续附件操作按本流程实例的规则控制。</div>
|
<a-alert v-for="(error, index) in errors" :key="index" class="mb-[8px]" type="error" show-icon :message="error" />
|
<template v-if="!malformed">
|
<AEmpty v-if="!policy.rules.length" description="尚未配置文件权限规则,沿用原权限" />
|
<a-card v-for="(rule, index) in policy.rules" :key="rule.id" :title="`规则 ${index + 1}`" size="small" class="mb-[12px]">
|
<template #extra><a-button type="link" danger @click="policy.rules.splice(index, 1)">删除规则</a-button></template>
|
<a-form layout="vertical">
|
<a-form-item label="附件字段" required>
|
<a-select
|
:value="targetValue(rule)"
|
:options="context.attachments"
|
placeholder="请选择本流程的附件字段"
|
@change="(value) => selectTarget(rule, value)" />
|
</a-form-item>
|
<div class="grid grid-cols-2 gap-x-[16px]">
|
<a-form-item label="作用人员" required>
|
<a-select v-model:value="rule.subject" :options="subjects" @change="rule.subject !== 'NODE_PARTICIPANTS' && delete rule.subjectNodeId" />
|
</a-form-item>
|
<a-form-item v-if="rule.subject === 'NODE_PARTICIPANTS'" label="参与人所属节点" required>
|
<a-select v-model:value="rule.subjectNodeId" :options="context.nodes" placeholder="请选择节点" />
|
</a-form-item>
|
<a-form-item label="生效条件" required>
|
<a-select v-model:value="rule.trigger" :options="triggers" @change="changeTrigger(rule)" />
|
</a-form-item>
|
<a-form-item v-if="['ACTIVE', 'ENTERED'].includes(rule.trigger)" label="触发节点" required>
|
<a-select v-model:value="rule.triggerNodeId" :options="context.nodes" placeholder="请选择节点" />
|
</a-form-item>
|
<a-form-item v-if="rule.trigger === 'ENTERED'" label="退回后的处理" required>
|
<a-select v-model:value="rule.onReturn" :options="returns" placeholder="请选择退回后的处理" />
|
</a-form-item>
|
</div>
|
<div class="grid grid-cols-3 gap-x-[16px]">
|
<a-form-item v-for="action in fileActions" :key="action" :label="actionNames[action]">
|
<a-select :value="rule.actions[action] ?? 'INHERIT'" :options="effects" @change="(value) => (rule.actions[action] = value)" />
|
</a-form-item>
|
</div>
|
</a-form>
|
</a-card>
|
<a-button type="dashed" block :disabled="!context.attachments.length" @click="addRule">添加文件权限规则</a-button>
|
<div v-if="!context.attachments.length" class="mt-[8px] text-gray-500">请先在节点中选择包含文件上传控件的表单。</div>
|
</template>
|
</AModal>
|
</template>
|