refactor: bpm

This commit is contained in:
xingyu4j
2025-06-06 20:45:45 +08:00
parent 7e8f2a1328
commit 2c3dd668e3
47 changed files with 1454 additions and 1898 deletions

View File

@@ -10,6 +10,7 @@ import type { SystemUserApi } from '#/api/system/user';
import { ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { CircleHelp, IconifyIcon, Plus, X } from '@vben/icons';
import {
@@ -41,6 +42,16 @@ const props = defineProps({
},
});
const [UserSelectModalComp, userSelectModalApi] = useVbenModal({
connectedComponent: UserSelectModal,
destroyOnClose: true,
});
const [DeptSelectModalComp, deptSelectModalApi] = useVbenModal({
connectedComponent: DeptSelectModal,
destroyOnClose: true,
});
// 表单引用
const formRef = ref();
@@ -52,8 +63,6 @@ const selectedStartDepts = ref<SystemDeptApi.Dept[]>([]);
// 选中的流程管理员
const selectedManagerUsers = ref<SystemUserApi.User[]>([]);
const userSelectFormRef = ref();
const deptSelectFormRef = ref();
const currentSelectType = ref<'manager' | 'start'>('start');
// 选中的用户
const selectedUsers = ref<number[]>();
@@ -98,37 +107,37 @@ watch(
);
/** 打开发起人选择 */
const openStartUserSelect = () => {
function openStartUserSelect() {
currentSelectType.value = 'start';
selectedUsers.value = selectedStartUsers.value.map(
(user) => user.id,
) as number[];
userSelectFormRef.value.open(selectedUsers.value);
};
userSelectModalApi.setData({ userIds: selectedUsers.value }).open();
}
/** 打开部门选择 */
const openStartDeptSelect = () => {
deptSelectFormRef.value.open(selectedStartDepts.value);
};
function openStartDeptSelect() {
deptSelectModalApi.setData({ selectedList: selectedStartDepts.value }).open();
}
/** 处理部门选择确认 */
const handleDeptSelectConfirm = (depts: SystemDeptApi.Dept[]) => {
function handleDeptSelectConfirm(depts: SystemDeptApi.Dept[]) {
modelData.value = {
...modelData.value,
startDeptIds: depts.map((d) => d.id),
};
};
}
/** 打开管理员选择 */
const openManagerUserSelect = () => {
function openManagerUserSelect() {
currentSelectType.value = 'manager';
selectedUsers.value = selectedManagerUsers.value.map(
(user) => user.id,
) as number[];
userSelectFormRef.value.open(selectedUsers.value);
};
userSelectModalApi.setData({ userIds: selectedUsers.value }).open();
}
/** 处理用户选择确认 */
const handleUserSelectConfirm = (userList: SystemUserApi.User[]) => {
function handleUserSelectConfirm(userList: SystemUserApi.User[]) {
modelData.value =
currentSelectType.value === 'start'
? {
@@ -139,20 +148,20 @@ const handleUserSelectConfirm = (userList: SystemUserApi.User[]) => {
...modelData.value,
managerUserIds: userList.map((u) => u.id),
};
};
}
/** 用户选择弹窗关闭 */
const handleUserSelectClosed = () => {
function handleUserSelectClosed() {
selectedUsers.value = [];
};
}
/** 用户选择弹窗取消 */
const handleUserSelectCancel = () => {
function handleUserSelectCancel() {
selectedUsers.value = [];
};
}
/** 处理发起人类型变化 */
const handleStartUserTypeChange = (value: SelectValue) => {
function handleStartUserTypeChange(value: SelectValue) {
const numValue = Number(value);
switch (numValue) {
case 0: {
@@ -181,270 +190,266 @@ const handleStartUserTypeChange = (value: SelectValue) => {
break;
}
}
};
}
/** 移除发起人 */
const handleRemoveStartUser = (user: SystemUserApi.User) => {
function handleRemoveStartUser(user: SystemUserApi.User) {
modelData.value = {
...modelData.value,
startUserIds: modelData.value.startUserIds.filter(
(id: number) => id !== user.id,
),
};
};
}
/** 移除部门 */
const handleRemoveStartDept = (dept: SystemDeptApi.Dept) => {
function handleRemoveStartDept(dept: SystemDeptApi.Dept) {
modelData.value = {
...modelData.value,
startDeptIds: modelData.value.startDeptIds.filter(
(id: number) => id !== dept.id,
),
};
};
}
/** 移除管理员 */
const handleRemoveManagerUser = (user: SystemUserApi.User) => {
function handleRemoveManagerUser(user: SystemUserApi.User) {
modelData.value = {
...modelData.value,
managerUserIds: modelData.value.managerUserIds.filter(
(id: number) => id !== user.id,
),
};
};
}
/** 表单校验 */
const validate = async () => {
async function validate() {
await formRef.value?.validate();
};
}
defineExpose({ validate });
</script>
<template>
<Form
ref="formRef"
:model="modelData"
:rules="rules"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
class="mt-5"
>
<Form.Item label="流程标识" name="key" class="mb-5">
<div class="flex items-center">
<div>
<Form
ref="formRef"
:model="modelData"
:rules="rules"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
class="mt-5"
>
<Form.Item label="流程标识" name="key" class="mb-5">
<div class="flex items-center">
<Input
class="w-full"
v-model:value="modelData.key"
:disabled="!!modelData.id"
placeholder="请输入流程标识,以字母或下划线开头"
/>
<Tooltip
:title="
modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'
"
placement="top"
>
<CircleHelp class="ml-1 size-5 text-gray-900" />
</Tooltip>
</div>
</Form.Item>
<Form.Item label="流程名称" name="name" class="mb-5">
<Input
class="w-full"
v-model:value="modelData.key"
v-model:value="modelData.name"
:disabled="!!modelData.id"
placeholder="请输入流程标识,以字母或下划线开头"
allow-clear
placeholder="请输入流程名称"
/>
<Tooltip
:title="
modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'
"
placement="top"
</Form.Item>
<Form.Item label="流程分类" name="category" class="mb-5">
<Select
class="w-full"
v-model:value="modelData.category"
allow-clear
placeholder="请选择流程分类"
>
<CircleHelp class="ml-1 size-5 text-gray-900" />
</Tooltip>
</div>
</Form.Item>
<Form.Item label="流程名称" name="name" class="mb-5">
<Input
v-model:value="modelData.name"
:disabled="!!modelData.id"
allow-clear
placeholder="请输入流程名称"
/>
</Form.Item>
<Form.Item label="流程分类" name="category" class="mb-5">
<Select
class="w-full"
v-model:value="modelData.category"
allow-clear
placeholder="请选择流程分类"
>
<Select.Option
v-for="category in categoryList"
:key="category.code"
:value="category.code"
<Select.Option
v-for="category in categoryList"
:key="category.code"
:value="category.code"
>
{{ category.name }}
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="流程图标" class="mb-5">
<ImageUpload v-model:value="modelData.icon" />
</Form.Item>
<Form.Item label="流程描述" name="description" class="mb-5">
<Input.TextArea v-model:value="modelData.description" allow-clear />
</Form.Item>
<Form.Item label="流程类型" name="type" class="mb-5">
<Radio.Group v-model:value="modelData.type">
<!-- TODO BPMN 流程类型需要整合暂时禁用 -->
<Radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
:key="dict.value"
:value="dict.value"
:disabled="dict.value === 10"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="是否可见" name="visible" class="mb-5">
<Radio.Group v-model:value="modelData.visible">
<Radio
v-for="(dict, index) in getBoolDictOptions(
DICT_TYPE.INFRA_BOOLEAN_STRING,
)"
:key="index"
:value="dict.value"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="谁可以发起" name="startUserType" class="mb-5">
<Select
v-model:value="modelData.startUserType"
placeholder="请选择谁可以发起"
@change="handleStartUserTypeChange"
>
{{ category.name }}
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="流程图标" class="mb-5">
<ImageUpload v-model:value="modelData.icon" />
</Form.Item>
<Form.Item label="流程描述" name="description" class="mb-5">
<Input.TextArea v-model:value="modelData.description" allow-clear />
</Form.Item>
<Form.Item label="流程类型" name="type" class="mb-5">
<Radio.Group v-model:value="modelData.type">
<!-- TODO BPMN 流程类型需要整合暂时禁用 -->
<Radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
:key="dict.value"
:value="dict.value"
:disabled="dict.value === 10"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="是否可见" name="visible" class="mb-5">
<Radio.Group v-model:value="modelData.visible">
<Radio
v-for="(dict, index) in getBoolDictOptions(
DICT_TYPE.INFRA_BOOLEAN_STRING,
)"
:key="index"
:value="dict.value"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="谁可以发起" name="startUserType" class="mb-5">
<Select
v-model:value="modelData.startUserType"
placeholder="请选择谁可以发起"
@change="handleStartUserTypeChange"
>
<Select.Option :value="0">全员</Select.Option>
<Select.Option :value="1">指定人员</Select.Option>
<Select.Option :value="2">指定部门</Select.Option>
</Select>
<div
v-if="modelData.startUserType === 1"
class="mt-2 flex flex-wrap gap-2"
>
<Select.Option :value="0">全员</Select.Option>
<Select.Option :value="1">指定人员</Select.Option>
<Select.Option :value="2">指定部门</Select.Option>
</Select>
<div
v-for="user in selectedStartUsers"
:key="user.id"
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2"
v-if="modelData.startUserType === 1"
class="mt-2 flex flex-wrap gap-2"
>
<Avatar
class="m-1"
:size="28"
v-if="user.avatar"
:src="user.avatar"
/>
<Avatar class="m-1" :size="28" v-else>
{{ user.nickname?.substring(0, 1) }}
</Avatar>
{{ user.nickname }}
<X
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
@click="handleRemoveStartUser(user)"
/>
<div
v-for="user in selectedStartUsers"
:key="user.id"
class="bg-destructive text-destructive-foreground hover:bg-destructive-hover relative flex h-9 items-center rounded-full pr-2"
>
<Avatar
class="m-1"
:size="28"
v-if="user.avatar"
:src="user.avatar"
/>
<Avatar class="m-1" :size="28" v-else>
{{ user.nickname?.substring(0, 1) }}
</Avatar>
{{ user.nickname }}
<X
class="ml-2 size-4 cursor-pointer"
@click="handleRemoveStartUser(user)"
/>
</div>
<Button
type="link"
@click="openStartUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon
icon="mdi:account-plus-outline"
class="size-[18px]"
/>
</template>
选择人员
</Button>
</div>
<Button
type="link"
@click="openStartUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon icon="mdi:account-plus-outline" class="size-[18px]" />
</template>
选择人员
</Button>
</div>
<div
v-if="modelData.startUserType === 2"
class="mt-2 flex flex-wrap gap-2"
>
<div
v-for="dept in selectedStartDepts"
:key="dept.id"
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2"
v-if="modelData.startUserType === 2"
class="mt-2 flex flex-wrap gap-2"
>
<IconifyIcon icon="ep:office-building" class="size-6 px-1" />
{{ dept.name }}
<X
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
@click="handleRemoveStartDept(dept)"
/>
<div
v-for="dept in selectedStartDepts"
:key="dept.id"
class="bg-destructive text-destructive-foreground hover:bg-destructive-hover relative flex h-9 items-center rounded-full pr-2 shadow-sm"
>
<IconifyIcon icon="ep:office-building" class="size-6 px-1" />
{{ dept.name }}
<X
class="ml-2 size-4 cursor-pointer"
@click="handleRemoveStartDept(dept)"
/>
</div>
<Button
type="link"
@click="openStartDeptSelect"
class="flex items-center"
>
<template #icon>
<Plus class="size-[18px]" />
</template>
选择部门
</Button>
</div>
<Button
type="link"
@click="openStartDeptSelect"
class="flex items-center"
>
<template #icon>
<Plus class="size-[18px]" />
</template>
选择部门
</Button>
</div>
</Form.Item>
<Form.Item label="流程管理员" name="managerUserIds" class="mb-5">
<div class="flex flex-wrap gap-2">
<div
v-for="user in selectedManagerUsers"
:key="user.id"
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2"
>
<Avatar
class="m-1"
:size="28"
v-if="user.avatar"
:src="user.avatar"
/>
<Avatar class="m-1" :size="28" v-else>
{{ user.nickname?.substring(0, 1) }}
</Avatar>
{{ user.nickname }}
<X
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
@click="handleRemoveManagerUser(user)"
/>
</Form.Item>
<Form.Item label="流程管理员" name="managerUserIds" class="mb-5">
<div class="flex flex-wrap gap-2">
<div
v-for="user in selectedManagerUsers"
:key="user.id"
class="bg-destructive text-destructive-foreground hover:bg-destructive-hover relative flex h-9 items-center rounded-full pr-2"
>
<Avatar
class="m-1"
:size="28"
v-if="user.avatar"
:src="user.avatar"
/>
<Avatar class="m-1" :size="28" v-else>
{{ user.nickname?.substring(0, 1) }}
</Avatar>
{{ user.nickname }}
<X
class="ml-2 size-4 cursor-pointer"
@click="handleRemoveManagerUser(user)"
/>
</div>
<Button
type="link"
@click="openManagerUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon
icon="mdi:account-plus-outline"
class="size-[18px]"
/>
</template>
选择人员
</Button>
</div>
<Button
type="link"
@click="openManagerUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon icon="mdi:account-plus-outline" class="size-[18px]" />
</template>
选择人员
</Button>
</div>
</Form.Item>
</Form>
</Form.Item>
</Form>
<!-- 用户选择弹窗 -->
<UserSelectModal
ref="userSelectFormRef"
v-model:value="selectedUsers"
:multiple="true"
title="选择用户"
@confirm="handleUserSelectConfirm"
@closed="handleUserSelectClosed"
@cancel="handleUserSelectCancel"
/>
<!-- 部门选择对话框 -->
<DeptSelectModal
ref="deptSelectFormRef"
title="发起人部门选择"
:check-strictly="true"
@confirm="handleDeptSelectConfirm"
/>
<!-- 用户选择弹窗 -->
<UserSelectModalComp
v-model:value="selectedUsers"
:multiple="true"
title="选择用户"
@confirm="handleUserSelectConfirm"
@closed="handleUserSelectClosed"
@cancel="handleUserSelectCancel"
/>
<!-- 部门选择对话框 -->
<DeptSelectModalComp
title="发起人部门选择"
:check-strictly="true"
@confirm="handleDeptSelectConfirm"
/>
</div>
</template>
<style lang="scss" scoped>
.bg-gray-100 {
background-color: #f5f7fa;
transition: all 0.3s;
&:hover {
background-color: #e6e8eb;
}
}
.upload-img-placeholder {
cursor: pointer;
background-color: #fafafa;
transition: all 0.3s;
&:hover {

View File

@@ -91,9 +91,9 @@ const numberExample = computed(() => {
/** 是否开启流程前置通知 */
const processBeforeTriggerEnable = ref(false);
const handleProcessBeforeTriggerEnableChange = (
function handleProcessBeforeTriggerEnableChange(
val: boolean | number | string,
) => {
) {
modelData.value.processBeforeTriggerSetting = val
? {
url: '',
@@ -102,13 +102,11 @@ const handleProcessBeforeTriggerEnableChange = (
response: [],
}
: null;
};
}
/** 是否开启流程后置通知 */
const processAfterTriggerEnable = ref(false);
const handleProcessAfterTriggerEnableChange = (
val: boolean | number | string,
) => {
function handleProcessAfterTriggerEnableChange(val: boolean | number | string) {
modelData.value.processAfterTriggerSetting = val
? {
url: '',
@@ -117,13 +115,11 @@ const handleProcessAfterTriggerEnableChange = (
response: [],
}
: null;
};
}
/** 是否开启任务前置通知 */
const taskBeforeTriggerEnable = ref(false);
const handleTaskBeforeTriggerEnableChange = (
val: boolean | number | string,
) => {
function handleTaskBeforeTriggerEnableChange(val: boolean | number | string) {
modelData.value.taskBeforeTriggerSetting = val
? {
url: '',
@@ -132,11 +128,11 @@ const handleTaskBeforeTriggerEnableChange = (
response: [],
}
: null;
};
}
/** 是否开启任务后置通知 */
const taskAfterTriggerEnable = ref(false);
const handleTaskAfterTriggerEnableChange = (val: boolean | number | string) => {
function handleTaskAfterTriggerEnableChange(val: boolean | number | string) {
modelData.value.taskAfterTriggerSetting = val
? {
url: '',
@@ -145,7 +141,7 @@ const handleTaskAfterTriggerEnableChange = (val: boolean | number | string) => {
response: [],
}
: null;
};
}
/** 表单选项 */
const formField = ref<Array<{ field: string; title: string }>>([]);
@@ -181,7 +177,7 @@ const formFieldOptions4Summary = computed(() => {
});
/** 兼容以前未配置更多设置的流程 */
const initData = () => {
function initData() {
if (!modelData.value.processIdRule) {
modelData.value.processIdRule = {
enable: false,
@@ -218,7 +214,7 @@ const initData = () => {
if (modelData.value.taskAfterTriggerSetting) {
taskAfterTriggerEnable.value = true;
}
};
}
/** 监听表单 ID 变化,加载表单数据 */
watch(
@@ -242,9 +238,9 @@ watch(
// 表单引用
const formRef = ref();
/** 表单校验 */
const validate = async () => {
async function validate() {
await formRef.value?.validate();
};
}
defineExpose({ initData, validate });
</script>

View File

@@ -80,9 +80,9 @@ const rules: Record<string, Rule[]> = {
};
/** 表单校验 */
const validate = async () => {
async function validate() {
await formRef.value?.validate();
};
}
defineExpose({ validate });
</script>

View File

@@ -16,7 +16,7 @@ const processData = inject('processData') as Ref;
const simpleDesign = ref();
/** 表单校验 */
const validate = async () => {
async function validate() {
// 获取最新的流程数据
if (!processData.value) {
throw new Error('请设计流程');
@@ -29,9 +29,9 @@ const validate = async () => {
}
}
return true;
};
}
/** 处理设计器保存成功 */
const handleDesignSuccess = async (data?: any) => {
async function handleDesignSuccess(data?: any) {
if (data) {
// 创建新的对象以触发响应式更新
const newModelData = {
@@ -44,7 +44,7 @@ const handleDesignSuccess = async (data?: any) => {
// 更新表单的模型数据部分
modelData.value = newModelData;
}
};
}
/** 是否显示设计器 */
const showDesigner = computed(() => {

View File

@@ -18,15 +18,15 @@ const emit = defineEmits(['success']);
const designerRef = ref();
/** 保存成功回调 */
const handleSuccess = (data?: any) => {
function handleSuccess(data?: any) {
if (data) {
emit('success', data);
}
};
}
/** 设计器配置校验 */
const validateConfig = async () => {
async function validateConfig() {
return await designerRef.value.validate();
};
}
defineExpose({ validateConfig });
</script>
<template>