Files
frontend/apps/web-antd/src/components/simple-process-design/components/nodes-config/user-task-node-config.vue
2025-06-15 15:53:12 +08:00

1246 lines
40 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import type { Rule } from 'ant-design-vue/es/form';
import type { Ref } from 'vue';
import type { ButtonSetting, SimpleFlowNode } from '../../consts';
import type { UserTaskFormType } from '../../helpers';
import { computed, onMounted, reactive, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { cloneDeep } from '@vben/utils';
import {
Button,
Col,
Divider,
Form,
FormItem,
Input,
InputNumber,
Radio,
RadioButton,
RadioGroup,
Row,
Select,
SelectOption,
Switch,
TabPane,
Tabs,
Textarea,
TreeSelect,
TypographyText,
} from 'ant-design-vue';
import {
BpmModelFormType,
BpmNodeTypeEnum,
ProcessVariableEnum,
} from '#/utils';
import {
APPROVE_METHODS,
APPROVE_TYPE,
ApproveMethodType,
ApproveType,
ASSIGN_EMPTY_HANDLER_TYPES,
ASSIGN_START_USER_HANDLER_TYPES,
AssignEmptyHandlerType,
CANDIDATE_STRATEGY,
CandidateStrategy,
DEFAULT_BUTTON_SETTING,
FieldPermissionType,
MULTI_LEVEL_DEPT,
OPERATION_BUTTON_NAME,
REJECT_HANDLER_TYPES,
RejectHandlerType,
TIME_UNIT_TYPES,
TIMEOUT_HANDLER_TYPES,
TimeoutHandlerType,
TimeUnitType,
TRANSACTOR_DEFAULT_BUTTON_SETTING,
} from '../../consts';
import {
useFormFieldsPermission,
useNodeForm,
useNodeName,
useWatchNode,
} from '../../helpers';
import UserTaskListener from './modules/user-task-listener.vue';
import { convertTimeUnit, getApproveTypeText } from './utils';
defineOptions({ name: 'UserTaskNodeConfig' });
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true,
},
});
const emits = defineEmits<{
findReturnTaskNodes: [nodeList: SimpleFlowNode[]];
}>();
const deptLevelLabel = computed(() => {
let label = '部门负责人来源';
if (
configForm.value.candidateStrategy ===
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
) {
label = `${label}(指定部门向上)`;
} else if (
configForm.value.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
) {
label = `${label}(表单内部门向上)`;
} else {
label = `${label}(发起人部门向上)`;
}
return label;
});
// 监控节点的变化
const currentNode = useWatchNode(props);
// 抽屉配置
const [Drawer, drawerApi] = useVbenDrawer({
header: true,
closable: true,
title: '',
onConfirm() {
saveConfig();
},
});
// 节点名称配置
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
useNodeName(BpmNodeTypeEnum.USER_TASK_NODE);
// 激活的 Tab 标签页
const activeTabName = ref('user');
// 表单字段权限设置
const {
formType,
fieldsPermissionConfig,
formFieldOptions,
getNodeConfigFormFields,
} = useFormFieldsPermission(FieldPermissionType.READ);
// 表单内用户字段选项, 必须是必填和用户选择器
const userFieldOnFormOptions = computed(() => {
return formFieldOptions.filter((item) => item.type === 'UserSelect');
});
// 表单内部门字段选项, 必须是必填和部门选择器
const deptFieldOnFormOptions = computed(() => {
return formFieldOptions.filter((item) => item.type === 'DeptSelect');
});
// 操作按钮设置
const {
buttonsSetting,
btnDisplayNameEdit,
changeBtnDisplayName,
btnDisplayNameBlurEvent,
} = useButtonsSetting();
const approveType = ref(ApproveType.USER);
// 审批人表单设置
const formRef = ref(); // 表单 Ref
// 表单校验规则
const formRules: Record<string, Rule[]> = reactive({
candidateStrategy: [
{ required: true, message: '审批人设置不能为空', trigger: 'change' },
],
userIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
roleIds: [{ required: true, message: '角色不能为空', trigger: 'change' }],
deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
userGroups: [
{ required: true, message: '用户组不能为空', trigger: 'change' },
],
formUser: [
{ required: true, message: '表单内用户字段不能为空', trigger: 'change' },
],
formDept: [
{ required: true, message: '表单内部门字段不能为空', trigger: 'change' },
],
postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
expression: [
{ required: true, message: '流程表达式不能为空', trigger: 'blur' },
],
approveMethod: [
{ required: true, message: '多人审批方式不能为空', trigger: 'change' },
],
approveRatio: [
{ required: true, message: '通过比例不能为空', trigger: 'blur' },
],
returnNodeId: [
{ required: true, message: '驳回节点不能为空', trigger: 'change' },
],
timeoutHandlerEnable: [{ required: true }],
timeoutHandlerType: [{ required: true }],
timeDuration: [
{ required: true, message: '超时时间不能为空', trigger: 'blur' },
],
maxRemindCount: [
{ required: true, message: '提醒次数不能为空', trigger: 'blur' },
],
assignEmptyHandlerType: [{ required: true }],
assignEmptyHandlerUserIds: [
{ required: true, message: '用户不能为空', trigger: 'change' },
],
assignStartUserHandlerType: [{ required: true }],
});
const {
configForm: tempConfigForm,
roleOptions,
postOptions,
userOptions,
userGroupOptions,
deptTreeOptions,
handleCandidateParam,
parseCandidateParam,
getShowText,
} = useNodeForm(currentNode.value.type);
const configForm = tempConfigForm as Ref<UserTaskFormType>;
// 改变审批人设置策略
function changeCandidateStrategy() {
configForm.value.userIds = [];
configForm.value.deptIds = [];
configForm.value.roleIds = [];
configForm.value.postIds = [];
configForm.value.userGroups = [];
configForm.value.deptLevel = 1;
configForm.value.formUser = '';
configForm.value.formDept = '';
configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_APPROVE;
}
/** 审批方式改变 */
function approveMethodChanged() {
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS;
if (configForm.value.approveMethod === ApproveMethodType.APPROVE_BY_RATIO) {
configForm.value.approveRatio = 100;
}
formRef.value.clearValidate('approveRatio');
}
// 审批拒绝 可退回的节点
const returnTaskList = ref<SimpleFlowNode[]>([]);
// 审批人超时未处理设置
const {
timeoutHandlerChange,
cTimeoutType,
timeoutHandlerTypeChanged,
timeUnit,
timeUnitChange,
isoTimeDuration,
cTimeoutMaxRemindCount,
} = useTimeoutHandler();
const userTaskListenerRef = ref();
/** 节点类型名称 */
const nodeTypeName = computed(() => {
return currentNode.value.type === BpmNodeTypeEnum.TRANSACTOR_NODE
? '办理'
: '审批';
});
/** 校验节点配置 */
async function validateConfig() {
if (!formRef.value) return false;
if (!userTaskListenerRef.value) return false;
// 先进行表单验证,记录验证结果
const userFormValid = await formRef.value.validate().catch(() => false);
const listenerValid = await userTaskListenerRef.value.validate().catch(() => {
return false;
});
// 如果监听器有错误切换到监听器Tab
if (!listenerValid) {
activeTabName.value = 'listener';
return false;
}
// 如果审批人表单有错误切换到审批人Tab
if (!userFormValid) {
activeTabName.value = 'user';
return false;
}
const showText = getShowText();
if (!showText) return false;
return true;
}
/** 保存配置 */
async function saveConfig() {
// 如果不是人工审批,不执行校验,直接返回
if (approveType.value !== ApproveType.USER) {
currentNode.value.name = nodeName.value!;
currentNode.value.approveType = approveType.value;
currentNode.value.showText = getApproveTypeText(approveType.value);
drawerApi.close();
return true;
}
// 执行校验
if (!(await validateConfig())) {
return false;
}
// 设置审批节点名称
currentNode.value.name = nodeName.value!;
// 设置审批类型
currentNode.value.approveType = approveType.value;
// 设置审批人设置策略
currentNode.value.candidateStrategy = configForm.value.candidateStrategy;
// 处理 candidateParam 参数
currentNode.value.candidateParam = handleCandidateParam();
// 设置审批方式
currentNode.value.approveMethod = configForm.value.approveMethod;
if (configForm.value.approveMethod === ApproveMethodType.APPROVE_BY_RATIO) {
currentNode.value.approveRatio = configForm.value.approveRatio;
}
// 设置拒绝处理
currentNode.value.rejectHandler = {
type: configForm.value.rejectHandlerType!,
returnNodeId: configForm.value.returnNodeId,
};
// 设置超时处理
currentNode.value.timeoutHandler = {
enable: configForm.value.timeoutHandlerEnable!,
type: cTimeoutType.value,
timeDuration: isoTimeDuration.value,
maxRemindCount: cTimeoutMaxRemindCount.value,
};
// 设置审批人为空时
currentNode.value.assignEmptyHandler = {
type: configForm.value.assignEmptyHandlerType!,
userIds:
configForm.value.assignEmptyHandlerType ===
AssignEmptyHandlerType.ASSIGN_USER
? configForm.value.assignEmptyHandlerUserIds
: undefined,
};
// 设置审批人与发起人相同时
currentNode.value.assignStartUserHandlerType =
configForm.value.assignStartUserHandlerType;
// 设置表单权限
currentNode.value.fieldsPermission = fieldsPermissionConfig.value;
// 设置按钮权限
currentNode.value.buttonsSetting = buttonsSetting.value;
// 创建任务监听器
currentNode.value.taskCreateListener = {
enable: configForm.value.taskCreateListenerEnable ?? false,
path: configForm.value.taskCreateListenerPath,
header: configForm.value.taskCreateListener?.header,
body: configForm.value.taskCreateListener?.body,
};
// 指派任务监听器
currentNode.value.taskAssignListener = {
enable: configForm.value.taskAssignListenerEnable ?? false,
path: configForm.value.taskAssignListenerPath,
header: configForm.value.taskAssignListener?.header,
body: configForm.value.taskAssignListener?.body,
};
// 完成任务监听器
currentNode.value.taskCompleteListener = {
enable: configForm.value.taskCompleteListenerEnable ?? false,
path: configForm.value.taskCompleteListenerPath,
header: configForm.value.taskCompleteListener?.header,
body: configForm.value.taskCompleteListener?.body,
};
// 签名
currentNode.value.signEnable = configForm.value.signEnable;
// 审批意见
currentNode.value.reasonRequire = configForm.value.reasonRequire;
currentNode.value.showText = getShowText();
drawerApi.close();
return true;
}
/** 显示审批节点配置, 由父组件传过来 */
function showUserTaskNodeConfig(node: SimpleFlowNode) {
nodeName.value = node.name;
// 1 审批类型
approveType.value =
node?.approveType === undefined ? ApproveType.USER : node.approveType;
// 如果审批类型不是人工审批返回
if (approveType.value !== ApproveType.USER) {
drawerApi.open();
return;
}
// 2.1 审批人设置
configForm.value.candidateStrategy = node.candidateStrategy!;
// 解析候选人参数
parseCandidateParam(node.candidateStrategy!, node?.candidateParam);
// 2.2 设置审批方式
configForm.value.approveMethod = node.approveMethod!;
if (node.approveMethod === ApproveMethodType.APPROVE_BY_RATIO) {
configForm.value.approveRatio = node.approveRatio!;
}
// 2.3 设置审批拒绝处理
configForm.value.rejectHandlerType = node.rejectHandler?.type;
configForm.value.returnNodeId = node.rejectHandler?.returnNodeId;
const matchNodeList: SimpleFlowNode[] = [];
emits('findReturnTaskNodes', matchNodeList);
returnTaskList.value = matchNodeList;
// 2.4 设置审批超时处理
configForm.value.timeoutHandlerEnable = node.timeoutHandler?.enable;
if (node.timeoutHandler?.enable && node.timeoutHandler?.timeDuration) {
const strTimeDuration = node.timeoutHandler.timeDuration;
const parseTime = strTimeDuration.slice(2, -1);
const parseTimeUnit = strTimeDuration.slice(-1);
configForm.value.timeDuration = Number.parseInt(parseTime);
timeUnit.value = convertTimeUnit(parseTimeUnit);
}
configForm.value.timeoutHandlerType = node.timeoutHandler?.type;
configForm.value.maxRemindCount = node.timeoutHandler?.maxRemindCount;
// 2.5 设置审批人为空时
configForm.value.assignEmptyHandlerType = node.assignEmptyHandler?.type;
configForm.value.assignEmptyHandlerUserIds = node.assignEmptyHandler?.userIds;
// 2.6 设置用户任务的审批人与发起人相同时
configForm.value.assignStartUserHandlerType = node.assignStartUserHandlerType;
// 3. 操作按钮设置
buttonsSetting.value =
cloneDeep(node.buttonsSetting) ||
(node.type === BpmNodeTypeEnum.TRANSACTOR_NODE
? TRANSACTOR_DEFAULT_BUTTON_SETTING
: DEFAULT_BUTTON_SETTING);
// 4. 表单字段权限配置
getNodeConfigFormFields(node.fieldsPermission);
// 5. 监听器
// 5.1 创建任务
configForm.value.taskCreateListenerEnable = node.taskCreateListener?.enable;
configForm.value.taskCreateListenerPath = node.taskCreateListener?.path;
configForm.value.taskCreateListener = {
header: node.taskCreateListener?.header ?? [],
body: node.taskCreateListener?.body ?? [],
};
// 5.2 指派任务
configForm.value.taskAssignListenerEnable = node.taskAssignListener?.enable;
configForm.value.taskAssignListenerPath = node.taskAssignListener?.path;
configForm.value.taskAssignListener = {
header: node.taskAssignListener?.header ?? [],
body: node.taskAssignListener?.body ?? [],
};
// 5.3 完成任务
configForm.value.taskCompleteListenerEnable =
node.taskCompleteListener?.enable;
configForm.value.taskCompleteListenerPath = node.taskCompleteListener?.path;
configForm.value.taskCompleteListener = {
header: node.taskCompleteListener?.header ?? [],
body: node.taskCompleteListener?.body ?? [],
};
// 6. 签名
configForm.value.signEnable = node?.signEnable ?? false;
// 7. 审批意见
configForm.value.reasonRequire = node?.reasonRequire ?? false;
drawerApi.open();
}
defineExpose({ showUserTaskNodeConfig }); // 暴露方法给父组件
/** 操作按钮设置 */
function useButtonsSetting() {
const buttonsSetting = ref<ButtonSetting[]>();
// 操作按钮显示名称可编辑
const btnDisplayNameEdit = ref<boolean[]>([]);
const changeBtnDisplayName = (index: number) => {
btnDisplayNameEdit.value[index] = true;
};
const btnDisplayNameBlurEvent = (index: number) => {
btnDisplayNameEdit.value[index] = false;
const buttonItem = buttonsSetting.value![index];
if (buttonItem)
buttonItem.displayName =
buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)!;
};
return {
buttonsSetting,
btnDisplayNameEdit,
changeBtnDisplayName,
btnDisplayNameBlurEvent,
};
}
/** 审批人超时未处理配置 */
function useTimeoutHandler() {
// 时间单位
const timeUnit = ref(TimeUnitType.HOUR);
// 超时开关改变
const timeoutHandlerChange = () => {
if (configForm.value.timeoutHandlerEnable) {
timeUnit.value = 2;
configForm.value.timeDuration = 6;
configForm.value.timeoutHandlerType = 1;
configForm.value.maxRemindCount = 1;
}
};
// 超时执行的动作
const cTimeoutType = computed(() => {
if (!configForm.value.timeoutHandlerEnable) {
return undefined;
}
return configForm.value.timeoutHandlerType;
});
// 超时处理动作改变
const timeoutHandlerTypeChanged = () => {
if (configForm.value.timeoutHandlerType === TimeoutHandlerType.REMINDER) {
configForm.value.maxRemindCount = 1; // 超时提醒次数默认为1
}
};
// 时间单位改变
const timeUnitChange = () => {
// 分钟,默认是 60 分钟
if (timeUnit.value === TimeUnitType.MINUTE) {
configForm.value.timeDuration = 60;
}
// 小时,默认是 6 个小时
if (timeUnit.value === TimeUnitType.HOUR) {
configForm.value.timeDuration = 6;
}
// 天, 默认 1天
if (timeUnit.value === TimeUnitType.DAY) {
configForm.value.timeDuration = 1;
}
};
// 超时时间的 ISO 表示
const isoTimeDuration = computed(() => {
if (!configForm.value.timeoutHandlerEnable) {
return undefined;
}
let strTimeDuration = 'PT';
if (timeUnit.value === TimeUnitType.MINUTE) {
strTimeDuration += `${configForm.value.timeDuration}M`;
}
if (timeUnit.value === TimeUnitType.HOUR) {
strTimeDuration += `${configForm.value.timeDuration}H`;
}
if (timeUnit.value === TimeUnitType.DAY) {
strTimeDuration += `${configForm.value.timeDuration}D`;
}
return strTimeDuration;
});
// 超时最大提醒次数
const cTimeoutMaxRemindCount = computed(() => {
if (!configForm.value.timeoutHandlerEnable) {
return undefined;
}
if (configForm.value.timeoutHandlerType !== TimeoutHandlerType.REMINDER) {
return undefined;
}
return configForm.value.maxRemindCount;
});
return {
timeoutHandlerChange,
cTimeoutType,
timeoutHandlerTypeChanged,
timeUnit,
timeUnitChange,
isoTimeDuration,
cTimeoutMaxRemindCount,
};
}
/** 批量更新权限 */
function updatePermission(type: string) {
fieldsPermissionConfig.value.forEach((field) => {
if (type === 'READ') {
field.permission = FieldPermissionType.READ;
} else if (type === 'WRITE') {
field.permission = FieldPermissionType.WRITE;
} else {
field.permission = FieldPermissionType.NONE;
}
});
}
// 在组件初始化时记录初始位置
onMounted(() => {
// 固定添加发起人ID字段
formFieldOptions.unshift({
field: ProcessVariableEnum.START_USER_ID,
title: '发起人',
type: 'UserSelect',
required: true,
});
});
</script>
<template>
<Drawer class="w-[580px]">
<template #title>
<div class="config-header">
<Input
v-if="showInput"
ref="inputRef"
type="text"
class="config-editable-input"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="nodeName"
:placeholder="nodeName"
/>
<div v-else class="node-name">
{{ nodeName }}
<IconifyIcon class="ml-1" icon="lucide:edit-3" @click="clickIcon()" />
</div>
</div>
</template>
<div
v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE"
class="mb-3 flex items-center"
>
<span class="mr-3 text-[16px]">审批类型 :</span>
<RadioGroup v-model:value="approveType">
<RadioButton
v-for="(item, index) in APPROVE_TYPE"
:key="index"
:value="item.value"
:label="item.value"
>
{{ item.label }}
</RadioButton>
</RadioGroup>
</div>
<Tabs
v-model:active-key="activeTabName"
v-if="approveType === ApproveType.USER"
>
<TabPane :tab="`${nodeTypeName}人`" key="user">
<div>
<Form
ref="formRef"
:model="configForm"
:label-wrap="true"
:label-col="{ span: 24 }"
:wrapper-col="{ span: 24 }"
:rules="formRules"
>
<!-- 审批/办理 人设置 -->
<FormItem :label="`${nodeTypeName}人设置`" name="candidateStrategy">
<RadioGroup
v-model:value="configForm.candidateStrategy"
@change="changeCandidateStrategy"
>
<Row :gutter="[0, 8]">
<Col
v-for="(dict, index) in CANDIDATE_STRATEGY"
:key="index"
:span="8"
>
<Radio :value="dict.value" :label="dict.value">
{{ dict.label }}
</Radio>
</Col>
</Row>
</RadioGroup>
</FormItem>
<FormItem
v-if="configForm.candidateStrategy === CandidateStrategy.ROLE"
label="指定角色"
name="roleIds"
>
<Select
v-model:value="configForm.roleIds"
clearable
mode="multiple"
>
<SelectOption
v-for="item in roleOptions"
:key="item.id"
:label="item.name"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem>
<FormItem
v-if="
configForm.candidateStrategy ===
CandidateStrategy.DEPT_MEMBER ||
configForm.candidateStrategy ===
CandidateStrategy.DEPT_LEADER ||
configForm.candidateStrategy ===
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
"
label="指定部门"
name="deptIds"
>
<TreeSelect
v-model:value="configForm.deptIds"
:tree-data="deptTreeOptions"
:field-names="{
label: 'name',
value: 'id',
children: 'children',
}"
empty-text="加载中,请稍后"
multiple
:check-strictly="true"
allow-clear
tree-checkable
/>
</FormItem>
<FormItem
v-if="configForm.candidateStrategy === CandidateStrategy.POST"
label="指定岗位"
name="postIds"
>
<Select
v-model:value="configForm.postIds"
clearable
mode="multiple"
>
<SelectOption
v-for="item in postOptions"
:key="item.id"
:label="item.name"
:value="item.id!"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem>
<FormItem
v-if="configForm.candidateStrategy === CandidateStrategy.USER"
label="指定用户"
name="userIds"
>
<Select
v-model:value="configForm.userIds"
clearable
mode="multiple"
>
<SelectOption
v-for="item in userOptions"
:key="item.id"
:label="item.nickname"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem>
<FormItem
v-if="
configForm.candidateStrategy === CandidateStrategy.USER_GROUP
"
label="指定用户组"
name="userGroups"
>
<Select
v-model:value="configForm.userGroups"
clearable
mode="multiple"
>
<SelectOption
v-for="item in userGroupOptions"
:key="item.id"
:label="item.name"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem>
<FormItem
v-if="
configForm.candidateStrategy === CandidateStrategy.FORM_USER
"
label="表单内用户字段"
name="formUser"
>
<Select v-model:value="configForm.formUser" clearable>
<SelectOption
v-for="(item, idx) in userFieldOnFormOptions"
:key="idx"
:label="item.title"
:value="item.field"
:disabled="!item.required"
>
{{ item.title }}
</SelectOption>
</Select>
</FormItem>
<FormItem
v-if="
configForm.candidateStrategy ===
CandidateStrategy.FORM_DEPT_LEADER
"
label="表单内部门字段"
name="formDept"
>
<Select v-model:value="configForm.formDept" clearable>
<SelectOption
v-for="(item, idx) in deptFieldOnFormOptions"
:key="idx"
:label="item.title"
:value="item.field"
:disabled="!item.required"
>
{{ item.title }}
</SelectOption>
</Select>
</FormItem>
<FormItem
v-if="
configForm.candidateStrategy ===
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
configForm.candidateStrategy ===
CandidateStrategy.START_USER_DEPT_LEADER ||
configForm.candidateStrategy ===
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
configForm.candidateStrategy ===
CandidateStrategy.FORM_DEPT_LEADER
"
:label="deptLevelLabel!"
name="deptLevel"
>
<Select v-model:value="configForm.deptLevel" clearable>
<SelectOption
v-for="(item, index) in MULTI_LEVEL_DEPT"
:key="index"
:label="item.label"
:value="item.value"
>
{{ item.label }}
</SelectOption>
</Select>
</FormItem>
<!-- TODO @jason后续要支持选择已经存好的表达式 -->
<FormItem
v-if="
configForm.candidateStrategy === CandidateStrategy.EXPRESSION
"
label="流程表达式"
name="expression"
>
<Textarea v-model:value="configForm.expression" clearable />
</FormItem>
<!-- 多人审批/办理 方式 -->
<FormItem :label="`多人${nodeTypeName}方式`" name="approveMethod">
<RadioGroup
v-model:value="configForm.approveMethod"
@change="approveMethodChanged"
>
<Row :gutter="[0, 8]">
<Col
:span="24"
v-for="(item, index) in APPROVE_METHODS"
:key="index"
>
<div class="flex items-center">
<Radio :value="item.value" :label="item.value">
{{ item.label }}
</Radio>
<InputNumber
v-if="
item.value === ApproveMethodType.APPROVE_BY_RATIO &&
configForm.approveMethod ===
ApproveMethodType.APPROVE_BY_RATIO
"
v-model:value="configForm.approveRatio"
:min="10"
:max="100"
:step="10"
size="small"
/>
</div>
</Col>
</Row>
</RadioGroup>
</FormItem>
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">
<Divider content-position="left">审批人拒绝时</Divider>
<FormItem name="rejectHandlerType">
<RadioGroup
v-model:value="configForm.rejectHandlerType"
class="w-full"
>
<Row :gutter="24">
<Col
:span="8"
v-for="(item, index) in REJECT_HANDLER_TYPES"
:key="index"
>
<Radio :value="item.value" :label="item.label">
{{ item.label }}
</Radio>
</Col>
</Row>
</RadioGroup>
</FormItem>
<FormItem
v-if="
configForm.rejectHandlerType ===
RejectHandlerType.RETURN_USER_TASK
"
label="驳回节点"
name="returnNodeId"
>
<Select v-model:value="configForm.returnNodeId" clearable>
<SelectOption
v-for="item in returnTaskList"
:key="item.id"
:label="item.name"
:value="item.id"
>
{{ item.name }}
</SelectOption>
</Select>
</FormItem>
</div>
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">
<Divider content-position="left">审批人超时未处理时</Divider>
<FormItem
label="启用开关"
name="timeoutHandlerEnable"
label-align="left"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 4 }"
>
<Switch
v-model:checked="configForm.timeoutHandlerEnable"
checked-children="开"
un-checked-children="关"
@change="timeoutHandlerChange"
/>
</FormItem>
<FormItem
label="执行动作"
name="timeoutHandlerType"
v-if="configForm.timeoutHandlerEnable"
label-align="left"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<RadioGroup
v-model:value="configForm.timeoutHandlerType"
@change="timeoutHandlerTypeChanged"
>
<RadioButton
v-for="item in TIMEOUT_HANDLER_TYPES"
:key="item.value"
:value="item.value"
:label="item.label"
>
{{ item.label }}
</RadioButton>
</RadioGroup>
</FormItem>
<FormItem
label="超时时间设置"
v-if="configForm.timeoutHandlerEnable"
label-align="left"
class="h-[32px]"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<Row>
<Col>
<TypographyText class="mr-2 mt-2 inline-flex text-sm">
当超过
</TypographyText>
</Col>
<Col>
<FormItem name="timeDuration">
<InputNumber
class="mr-2 mt-0.5"
v-model:value="configForm.timeDuration"
:min="1"
controls-position="right"
/>
</FormItem>
</Col>
<Col>
<Select
v-model:value="timeUnit"
class="mr-2"
:style="{ width: '100px' }"
@change="timeUnitChange"
>
<SelectOption
v-for="item in TIME_UNIT_TYPES"
:key="item.value"
:label="item.label"
:value="item.value"
>
{{ item.label }}
</SelectOption>
</Select>
<TypographyText class="mr-2 mt-2 inline-flex text-sm">
未处理
</TypographyText>
</Col>
</Row>
</FormItem>
<FormItem
label="最大提醒次数"
name="maxRemindCount"
v-if="
configForm.timeoutHandlerEnable &&
configForm.timeoutHandlerType === 1
"
label-align="left"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<InputNumber
v-model:value="configForm.maxRemindCount"
:min="1"
:max="10"
/>
</FormItem>
</div>
<Divider content-position="left">
{{ nodeTypeName }}人为空时
</Divider>
<FormItem name="assignEmptyHandlerType">
<RadioGroup v-model:value="configForm.assignEmptyHandlerType">
<Row :gutter="[0, 16]">
<Col
:span="24"
v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES"
:key="index"
>
<Radio :value="item.value" :label="item.label">
{{ item.label }}
</Radio>
</Col>
</Row>
</RadioGroup>
</FormItem>
<FormItem
v-if="
configForm.assignEmptyHandlerType ===
AssignEmptyHandlerType.ASSIGN_USER
"
label="指定用户"
name="assignEmptyHandlerUserIds"
>
<Select
v-model:value="configForm.assignEmptyHandlerUserIds"
clearable
mode="multiple"
>
<SelectOption
v-for="item in userOptions"
:key="item.id"
:label="item.nickname"
:value="item.id"
>
{{ item.nickname }}
</SelectOption>
</Select>
</FormItem>
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">
<Divider content-position="left">
审批人与提交人为同一人时
</Divider>
<FormItem name="assignStartUserHandlerType">
<RadioGroup
v-model:value="configForm.assignStartUserHandlerType"
>
<Row :gutter="[0, 16]">
<Col
:span="24"
v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES"
:key="index"
>
<Radio :value="item.value" :label="item.label">
{{ item.label }}
</Radio>
</Col>
</Row>
</RadioGroup>
</FormItem>
</div>
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">
<Divider content-position="left">是否需要签名</Divider>
<FormItem name="signEnable">
<Switch
v-model:checked="configForm.signEnable"
checked-children="是"
un-checked-children="否"
/>
</FormItem>
</div>
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">
<Divider content-position="left">审批意见</Divider>
<FormItem name="reasonRequire">
<Switch
v-model:checked="configForm.reasonRequire"
checked-children="必填"
un-checked-children="非必填"
/>
</FormItem>
</div>
</Form>
</div>
</TabPane>
<TabPane
tab="操作按钮设置"
v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE"
key="buttons"
>
<div class="p-1">
<div class="mb-4 text-[16px] font-bold">操作按钮</div>
<!-- 表头 -->
<Row class="border border-gray-200 px-4 py-3">
<Col :span="8" class="font-bold">操作按钮</Col>
<Col :span="12" class="font-bold">显示名称</Col>
<Col :span="4" class="flex items-center justify-center font-bold">
启用
</Col>
</Row>
<!-- 表格内容 -->
<div v-for="(item, index) in buttonsSetting" :key="index">
<Row class="border border-t-0 border-gray-200 px-4 py-2">
<Col :span="8" class="flex items-center truncate">
{{ OPERATION_BUTTON_NAME.get(item.id) }}
</Col>
<Col :span="12" class="flex items-center">
<!-- TODO v-mountedFocus 自动聚集需要迁移 -->
<Input
v-if="btnDisplayNameEdit[index]"
type="text"
class="input-edit max-w-[130px]"
@blur="btnDisplayNameBlurEvent(index)"
v-model:value="item.displayName"
:placeholder="item.displayName"
/>
<Button v-else text @click="changeBtnDisplayName(index)">
<div class="flex items-center">
{{ item.displayName }}
<IconifyIcon icon="lucide:edit" class="ml-2" />
</div>
</Button>
</Col>
<Col :span="4" class="flex items-center justify-center">
<Switch v-model:checked="item.enable" />
</Col>
</Row>
</div>
</div>
</TabPane>
<TabPane
tab="表单字段权限"
key="fields"
v-if="formType === BpmModelFormType.NORMAL"
>
<div class="p-1">
<div class="mb-4 text-[16px] font-bold">字段权限</div>
<!-- 表头 -->
<Row class="border border-gray-200 px-4 py-3">
<Col :span="8" class="font-bold">字段名称</Col>
<Col :span="16">
<Row>
<Col :span="8" class="flex items-center justify-center">
<span
class="cursor-pointer font-bold"
@click="updatePermission('READ')"
>
只读
</span>
</Col>
<Col :span="8" class="flex items-center justify-center">
<span
class="cursor-pointer font-bold"
@click="updatePermission('WRITE')"
>
可编辑
</span>
</Col>
<Col :span="8" class="flex items-center justify-center">
<span
class="cursor-pointer font-bold"
@click="updatePermission('NONE')"
>
隐藏
</span>
</Col>
</Row>
</Col>
</Row>
<!-- 表格内容 -->
<div v-for="(item, index) in fieldsPermissionConfig" :key="index">
<Row class="border border-t-0 border-gray-200 px-4 py-2">
<Col :span="8" class="flex items-center truncate">
{{ item.title }}
</Col>
<Col :span="16">
<RadioGroup v-model:value="item.permission" class="w-full">
<Row>
<Col :span="8" class="flex items-center justify-center">
<Radio
:value="FieldPermissionType.READ"
size="large"
:label="FieldPermissionType.READ"
/>
</Col>
<Col :span="8" class="flex items-center justify-center">
<Radio
:value="FieldPermissionType.WRITE"
size="large"
:label="FieldPermissionType.WRITE"
/>
</Col>
<Col :span="8" class="flex items-center justify-center">
<Radio
:value="FieldPermissionType.NONE"
size="large"
:label="FieldPermissionType.NONE"
/>
</Col>
</Row>
</RadioGroup>
</Col>
</Row>
</div>
</div>
</TabPane>
<TabPane tab="监听器" key="listener" :force-render="true">
<UserTaskListener
ref="userTaskListenerRef"
v-model="configForm"
:form-field-options="formFieldOptions"
/>
</TabPane>
</Tabs>
</Drawer>
</template>
<style lang="scss" scoped>
.input-edit {
&:focus {
outline: 0;
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
}
}
</style>