feat: [BPM 工作流] 新增流程模型更多设置

This commit is contained in:
jason
2025-06-06 07:22:35 +08:00
parent 01cb73050b
commit 60e1b8271d
25 changed files with 767 additions and 302 deletions

View File

@@ -29,6 +29,7 @@ import { getSimpleUserList } from '#/api/system/user';
import { BpmAutoApproveType, BpmModelFormType, BpmModelType } from '#/utils';
import BasicInfo from './modules/basic-info.vue';
import ExtraSetting from './modules/extra-setting.vue';
import FormDesign from './modules/form-design.vue';
import ProcessDesign from './modules/process-design.vue';
@@ -55,6 +56,8 @@ const basicInfoRef = ref<InstanceType<typeof BasicInfo>>();
const formDesignRef = ref<InstanceType<typeof FormDesign>>();
// 流程设计组件引用
const processDesignRef = ref<InstanceType<typeof ProcessDesign>>();
// 更多设置组件引用
const extraSettingRef = ref<InstanceType<typeof ExtraSetting>>();
/** 步骤校验函数 */
const validateBasic = async () => {
@@ -71,13 +74,18 @@ const validateProcess = async () => {
await processDesignRef.value?.validate();
};
/** 更多设置校验 */
const validateExtra = async () => {
await extraSettingRef.value?.validate();
};
const currentStep = ref(-1); // 步骤控制。-1 用于,一开始全部不展示等当前页面数据初始化完成
const steps = [
{ title: '基本信息', validator: validateBasic },
{ title: '表单设计', validator: validateForm },
{ title: '流程设计', validator: validateProcess },
{ title: '更多设置', validator: null },
{ title: '更多设置', validator: validateExtra },
];
// 表单数据
@@ -190,8 +198,8 @@ const initData = async () => {
// 最终,设置 currentStep 切换到第一步
currentStep.value = 0;
// TODO 兼容,以前未配置更多设置的流程
// extraSettingsRef.value.initData()
// 以前未配置更多设置的流程
extraSettingRef.value?.initData();
};
/** 根据类型切换流程数据 */
@@ -237,7 +245,13 @@ const validateAllSteps = async () => {
return false;
}
// TODO 更多设置校验
// 更多设置校验
try {
await validateExtra();
} catch {
currentStep.value = 3;
return false;
}
return true;
};
@@ -345,6 +359,9 @@ const handleStepClick = async (index: number) => {
if (index !== 2) {
await validateProcess();
}
if (index !== 3) {
await validateExtra();
}
// 切换步骤
currentStep.value = index;
} catch (error) {
@@ -475,8 +492,10 @@ onBeforeUnmount(() => {
ref="processDesignRef"
/>
<!-- 第四步更多设置 TODO -->
<div v-if="currentStep === 3" class="mx-auto w-4/6"></div>
<!-- 第四步更多设置 -->
<div v-if="currentStep === 3" class="mx-auto w-4/6">
<ExtraSetting v-model="formData" ref="extraSettingRef" />
</div>
</div>
</Card>
</div>

View File

@@ -0,0 +1,498 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { CircleHelp } from '@vben/icons';
import {
Checkbox,
Col,
Form,
FormItem,
Input,
InputNumber,
Mentions,
Radio,
RadioGroup,
Row,
Select,
Switch,
Tooltip,
TypographyText,
} from 'ant-design-vue';
import dayjs from 'dayjs';
import * as FormApi from '#/api/bpm/form';
import {
HttpRequestSetting,
parseFormFields,
} from '#/components/simple-process-design';
import { ProcessVariableEnum } from '#/utils';
import { BpmAutoApproveType, BpmModelFormType } from '#/utils/constants';
const modelData = defineModel<any>();
/** 自定义 ID 流程编码 */
const timeOptions = ref([
{
value: '',
label: '无',
},
{
value: 'DAY',
label: '精确到日',
},
{
value: 'HOUR',
label: '精确到时',
},
{
value: 'MINUTE',
label: '精确到分',
},
{
value: 'SECOND',
label: '精确到秒',
},
]);
const numberExample = computed(() => {
if (modelData.value.processIdRule.enable) {
let infix = '';
switch (modelData.value.processIdRule.infix) {
case 'DAY': {
infix = dayjs().format('YYYYMMDD');
break;
}
case 'HOUR': {
infix = dayjs().format('YYYYMMDDHH');
break;
}
case 'MINUTE': {
infix = dayjs().format('YYYYMMDDHHmm');
break;
}
case 'SECOND': {
infix = dayjs().format('YYYYMMDDHHmmss');
break;
}
default: {
break;
}
}
return (
modelData.value.processIdRule.prefix +
infix +
modelData.value.processIdRule.postfix +
'1'.padStart(modelData.value.processIdRule.length - 1, '0')
);
} else {
return '';
}
});
/** 是否开启流程前置通知 */
const processBeforeTriggerEnable = ref(false);
const handleProcessBeforeTriggerEnableChange = (
val: boolean | number | string,
) => {
modelData.value.processBeforeTriggerSetting = val
? {
url: '',
header: [],
body: [],
response: [],
}
: null;
};
/** 是否开启流程后置通知 */
const processAfterTriggerEnable = ref(false);
const handleProcessAfterTriggerEnableChange = (
val: boolean | number | string,
) => {
modelData.value.processAfterTriggerSetting = val
? {
url: '',
header: [],
body: [],
response: [],
}
: null;
};
/** 是否开启任务前置通知 */
const taskBeforeTriggerEnable = ref(false);
const handleTaskBeforeTriggerEnableChange = (
val: boolean | number | string,
) => {
modelData.value.taskBeforeTriggerSetting = val
? {
url: '',
header: [],
body: [],
response: [],
}
: null;
};
/** 是否开启任务后置通知 */
const taskAfterTriggerEnable = ref(false);
const handleTaskAfterTriggerEnableChange = (val: boolean | number | string) => {
modelData.value.taskAfterTriggerSetting = val
? {
url: '',
header: [],
body: [],
response: [],
}
: null;
};
/** 表单选项 */
const formField = ref<Array<{ field: string; title: string }>>([]);
const formFieldOptions4Title = computed(() => {
const cloneFormField = formField.value.map((item) => {
return {
label: item.title,
value: item.field,
};
});
// 固定添加发起人 ID 字段
cloneFormField.unshift({
label: '流程名称',
value: ProcessVariableEnum.PROCESS_DEFINITION_NAME,
});
cloneFormField.unshift({
label: '发起时间',
value: ProcessVariableEnum.START_TIME,
});
cloneFormField.unshift({
label: '发起人',
value: ProcessVariableEnum.START_USER_ID,
});
return cloneFormField;
});
const formFieldOptions4Summary = computed(() => {
return formField.value.map((item) => {
return {
label: item.title,
value: item.field,
};
});
});
/** 兼容以前未配置更多设置的流程 */
const initData = () => {
if (!modelData.value.processIdRule) {
modelData.value.processIdRule = {
enable: false,
prefix: '',
infix: '',
postfix: '',
length: 5,
};
}
if (!modelData.value.autoApprovalType) {
modelData.value.autoApprovalType = BpmAutoApproveType.NONE;
}
if (!modelData.value.titleSetting) {
modelData.value.titleSetting = {
enable: false,
title: '',
};
}
if (!modelData.value.summarySetting) {
modelData.value.summarySetting = {
enable: false,
summary: [],
};
}
if (modelData.value.processBeforeTriggerSetting) {
processBeforeTriggerEnable.value = true;
}
if (modelData.value.processAfterTriggerSetting) {
processAfterTriggerEnable.value = true;
}
if (modelData.value.taskBeforeTriggerSetting) {
taskBeforeTriggerEnable.value = true;
}
if (modelData.value.taskAfterTriggerSetting) {
taskAfterTriggerEnable.value = true;
}
};
/** 监听表单 ID 变化,加载表单数据 */
watch(
() => modelData.value.formId,
async (newFormId) => {
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
const data = await FormApi.getFormDetail(newFormId);
const result: Array<{ field: string; title: string }> = [];
if (data.fields) {
data.fields.forEach((fieldStr: string) => {
parseFormFields(JSON.parse(fieldStr), result);
});
}
formField.value = result;
} else {
formField.value = [];
}
},
{ immediate: true },
);
// 表单引用
const formRef = ref();
/** 表单校验 */
const validate = async () => {
await formRef.value?.validate();
};
defineExpose({ initData, validate });
</script>
<template>
<Form
ref="formRef"
:model="modelData"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
class="mt-5 px-5"
>
<FormItem class="mb-5" label="提交人权限">
<div class="mt-1 flex flex-col">
<Checkbox v-model:checked="modelData.allowCancelRunningProcess">
允许撤销审批中的申请
</Checkbox>
<div class="ml-6">
<TypographyText type="warning">
第一个审批节点通过后提交人仍可撤销申请
</TypographyText>
</div>
</div>
</FormItem>
<FormItem v-if="modelData.processIdRule" class="mb-5" label="流程编码">
<Row :gutter="8" align="middle">
<Col :span="1">
<Checkbox v-model:checked="modelData.processIdRule.enable" />
</Col>
<Col :span="5">
<Input
v-model:value="modelData.processIdRule.prefix"
placeholder="前缀"
:disabled="!modelData.processIdRule.enable"
/>
</Col>
<Col :span="6">
<Select
v-model:value="modelData.processIdRule.infix"
allow-clear
placeholder="中缀"
:disabled="!modelData.processIdRule.enable"
:options="timeOptions"
/>
</Col>
<Col :span="4">
<Input
v-model:value="modelData.processIdRule.postfix"
placeholder="后缀"
:disabled="!modelData.processIdRule.enable"
/>
</Col>
<Col :span="4">
<InputNumber
v-model:value="modelData.processIdRule.length"
:min="5"
:disabled="!modelData.processIdRule.enable"
/>
</Col>
</Row>
<div class="ml-6 mt-2" v-if="modelData.processIdRule.enable">
<TypographyText type="success">
编码示例{{ numberExample }}
</TypographyText>
</div>
</FormItem>
<FormItem class="mb-5" label="自动去重">
<div class="mt-1">
<TypographyText class="mb-2 block">
同一审批人在流程中重复出现时
</TypographyText>
<RadioGroup v-model:value="modelData.autoApprovalType">
<Row :gutter="[0, 8]">
<Col :span="24">
<Radio :value="0">不自动通过</Radio>
</Col>
<Col :span="24">
<Radio :value="1">
仅审批一次后续重复的审批节点均自动通过
</Radio>
</Col>
<Col :span="24">
<Radio :value="2">仅针对连续审批的节点自动通过</Radio>
</Col>
</Row>
</RadioGroup>
</div>
</FormItem>
<FormItem v-if="modelData.titleSetting" class="mb-5" label="标题设置">
<div class="mt-1">
<RadioGroup v-model:value="modelData.titleSetting.enable">
<Row :gutter="[0, 8]">
<Col :span="24">
<Radio :value="false">
系统默认
<TypographyText type="success"> 展示流程名称 </TypographyText>
</Radio>
</Col>
<Col :span="24">
<Radio :value="true">
<div class="inline-flex items-center">
自定义标题
<Tooltip
title="输入字符 '{' 即可插入表单字段"
placement="top"
>
<CircleHelp class="ml-1 size-4 text-gray-500" />
</Tooltip>
</div>
</Radio>
</Col>
</Row>
</RadioGroup>
<div class="mt-2">
<Mentions
v-if="modelData.titleSetting.enable"
v-model:value="modelData.titleSetting.title"
style="width: 100%; max-width: 600px"
type="textarea"
prefix="{"
split="}"
:options="formFieldOptions4Title"
placeholder="请插入表单字段(输入 '{' 可以选择表单字段)或输入文本"
/>
</div>
</div>
</FormItem>
<FormItem
v-if="
modelData.summarySetting &&
modelData.formType === BpmModelFormType.NORMAL
"
class="mb-5"
label="摘要设置"
>
<div class="mt-1">
<RadioGroup v-model:value="modelData.summarySetting.enable">
<Row :gutter="[0, 8]">
<Col :span="24">
<Radio :value="false">
系统默认
<TypographyText type="secondary">
展示表单前 3 个字段
</TypographyText>
</Radio>
</Col>
<Col :span="24">
<Radio :value="true"> 自定义摘要 </Radio>
</Col>
</Row>
</RadioGroup>
<div class="mt-2">
<Select
v-if="modelData.summarySetting.enable"
v-model:value="modelData.summarySetting.summary"
mode="multiple"
placeholder="请选择要展示的表单字段"
:options="formFieldOptions4Summary"
/>
</div>
</div>
</FormItem>
<FormItem class="mb-5" label="流程前置通知">
<Row class="mt-1">
<Col :span="24">
<div class="flex items-center">
<Switch
v-model:checked="processBeforeTriggerEnable"
@change="handleProcessBeforeTriggerEnableChange"
/>
<span class="ml-4">流程启动后通知</span>
</div>
</Col>
</Row>
<Row v-if="processBeforeTriggerEnable">
<Col :span="24" class="mt-6">
<HttpRequestSetting
v-model:setting="modelData.processBeforeTriggerSetting"
:response-enable="true"
form-item-prefix="processBeforeTriggerSetting"
/>
</Col>
</Row>
</FormItem>
<FormItem class="mb-5" label="流程后置通知">
<Row class="mt-1">
<Col :span="24">
<div class="flex items-center">
<Switch
v-model:checked="processAfterTriggerEnable"
@change="handleProcessAfterTriggerEnableChange"
/>
<span class="ml-4">流程结束后通知</span>
</div>
</Col>
</Row>
<Row v-if="processAfterTriggerEnable" class="mt-2">
<Col :span="24">
<HttpRequestSetting
v-model:setting="modelData.processAfterTriggerSetting"
:response-enable="true"
form-item-prefix="processAfterTriggerSetting"
/>
</Col>
</Row>
</FormItem>
<FormItem class="mb-5" label="任务前置通知">
<Row class="mt-1">
<Col :span="24">
<div class="flex items-center">
<Switch
v-model:checked="taskBeforeTriggerEnable"
@change="handleTaskBeforeTriggerEnableChange"
/>
<span class="ml-4">任务执行时通知</span>
</div>
</Col>
</Row>
<Row v-if="taskBeforeTriggerEnable" class="mt-2">
<Col :span="24">
<HttpRequestSetting
v-model:setting="modelData.taskBeforeTriggerSetting"
:response-enable="true"
form-item-prefix="taskBeforeTriggerSetting"
/>
</Col>
</Row>
</FormItem>
<FormItem class="mb-5" label="任务后置通知">
<Row class="mt-1">
<Col :span="24">
<div class="flex items-center">
<Switch
v-model:checked="taskAfterTriggerEnable"
@change="handleTaskAfterTriggerEnableChange"
/>
<span class="ml-4">任务结束后通知</span>
</div>
</Col>
</Row>
<Row v-if="taskAfterTriggerEnable" class="mt-2">
<Col :span="24">
<HttpRequestSetting
v-model:setting="modelData.taskAfterTriggerSetting"
:response-enable="true"
form-item-prefix="taskAfterTriggerSetting"
/>
</Col>
</Row>
</FormItem>
</Form>
</template>