feat: 流程模型新增: 基本信息

This commit is contained in:
jason
2025-05-16 14:05:52 +08:00
parent a7dcebc82a
commit c5e844460e
5 changed files with 945 additions and 6 deletions

View File

@@ -0,0 +1,432 @@
<script lang="ts" setup>
import type { Rule } from 'ant-design-vue/es/form';
import type { SelectValue } from 'ant-design-vue/es/select';
import type { BpmCategoryApi } from '#/api/bpm/category';
import type { SystemDeptApi } from '#/api/system/dept';
import type { SystemUserApi } from '#/api/system/user';
import { ref, watch } from 'vue';
import { IconifyIcon, Plus, ShieldQuestion, X } from '@vben/icons';
import {
Avatar,
Button,
Form,
Input,
Radio,
Select,
Tooltip,
} from 'ant-design-vue';
import { ImageUpload } from '#/components/upload';
import { UserSelectModal } from '#/components/user-select-modal';
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '#/utils';
const props = withDefaults(
defineProps<{
categoryList: BpmCategoryApi.CategoryVO[];
deptList: SystemDeptApi.Dept[];
userList: SystemUserApi.User[];
}>(),
{},
);
// 表单引用
const formRef = ref();
// 选中的发起人
const selectedStartUsers = ref<SystemUserApi.User[]>([]);
// 选中的发起部门
const selectedStartDepts = ref<SystemDeptApi.Dept[]>([]);
// 选中的流程管理员
const selectedManagerUsers = ref<SystemUserApi.User[]>([]);
const userSelectFormRef = ref();
const currentSelectType = ref<'manager' | 'start'>('start');
// 选中的用户
const selectedUsers = ref<number[]>();
const rules: Record<string, Rule[]> = {
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
type: [{ required: true, message: '流程类型不能为空', trigger: 'blur' }],
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
managerUserIds: [
{ required: true, message: '流程管理员不能为空', trigger: 'blur' },
],
};
// 创建本地数据副本
const modelData = defineModel<any>();
// 初始化选中的用户
watch(
() => modelData.value,
(newVal) => {
selectedStartUsers.value = newVal.startUserIds?.length
? (props.userList.filter((user: SystemUserApi.User) =>
newVal.startUserIds.includes(user.id),
) as SystemUserApi.User[])
: [];
selectedStartDepts.value = newVal.startDeptIds?.length
? (props.deptList.filter((dept: SystemDeptApi.Dept) =>
newVal.startDeptIds.includes(dept.id),
) as SystemDeptApi.Dept[])
: [];
selectedManagerUsers.value = newVal.managerUserIds?.length
? (props.userList.filter((user: SystemUserApi.User) =>
newVal.managerUserIds.includes(user.id),
) as SystemUserApi.User[])
: [];
},
{
immediate: true,
},
);
/** 打开发起人选择 */
const openStartUserSelect = () => {
currentSelectType.value = 'start';
selectedUsers.value = selectedStartUsers.value.map(
(user) => user.id,
) as number[];
userSelectFormRef.value.open(selectedUsers.value);
};
/** 打开部门选择 */
const openStartDeptSelect = () => {
// TODO 部门选择组件暂时还没有
console.warn('部门选择功能暂未实现');
};
/** 打开管理员选择 */
const openManagerUserSelect = () => {
currentSelectType.value = 'manager';
selectedUsers.value = selectedManagerUsers.value.map(
(user) => user.id,
) as number[];
userSelectFormRef.value.open(selectedUsers.value);
};
/** 处理用户选择确认 */
const handleUserSelectConfirm = (userList: SystemUserApi.User[]) => {
modelData.value =
currentSelectType.value === 'start'
? {
...modelData.value,
startUserIds: userList.map((u) => u.id),
}
: {
...modelData.value,
managerUserIds: userList.map((u) => u.id),
};
};
/** 用户选择弹窗关闭 */
const handleUserSelectClosed = () => {
selectedUsers.value = [];
};
/** 用户选择弹窗取消 */
const handleUserSelectCancel = () => {
selectedUsers.value = [];
};
/** 处理发起人类型变化 */
const handleStartUserTypeChange = (value: SelectValue) => {
const numValue = Number(value);
switch (numValue) {
case 0: {
modelData.value = {
...modelData.value,
startUserIds: [],
startDeptIds: [],
};
break;
}
case 1: {
modelData.value = {
...modelData.value,
startDeptIds: [],
};
break;
}
case 2: {
modelData.value = {
...modelData.value,
startUserIds: [],
};
break;
}
}
};
/** 移除发起人 */
const handleRemoveStartUser = (user: SystemUserApi.User) => {
modelData.value = {
...modelData.value,
startUserIds: modelData.value.startUserIds.filter(
(id: number) => id !== user.id,
),
};
};
/** 移除部门 */
const handleRemoveStartDept = (dept: SystemDeptApi.Dept) => {
modelData.value = {
...modelData.value,
startDeptIds: modelData.value.startDeptIds.filter(
(id: number) => id !== dept.id,
),
};
};
/** 移除管理员 */
const handleRemoveManagerUser = (user: SystemUserApi.User) => {
modelData.value = {
...modelData.value,
managerUserIds: modelData.value.managerUserIds.filter(
(id: number) => id !== user.id,
),
};
};
/** 表单校验 */
const validate = async () => {
await formRef.value?.validate();
};
defineExpose({
validate,
});
</script>
<template>
<Form
ref="formRef"
:model="modelData"
:rules="rules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
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"
>
<ShieldQuestion class="ml-1 text-gray-500" />
</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"
>
{{ 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">
<Radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
:key="dict.value"
:value="dict.value"
>
{{ 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"
>
<div
v-for="user in selectedStartUsers"
: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="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>
<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"
>
<IconifyIcon icon="mdi:building-outline" class="size-5" />
{{ dept.name }}
<X
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
@click="handleRemoveStartDept(dept)"
/>
</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)"
/>
</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>
<!-- 用户选择弹窗 -->
<UserSelectModal
ref="userSelectFormRef"
v-model:value="selectedUsers"
:multiple="true"
title="选择用户"
@confirm="handleUserSelectConfirm"
@closed="handleUserSelectClosed"
@cancel="handleUserSelectCancel"
/>
</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 {
border-color: #1890ff !important;
}
}
</style>