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

@@ -60,24 +60,24 @@ const processDesignRef = ref<InstanceType<typeof ProcessDesign>>();
const extraSettingRef = ref<InstanceType<typeof ExtraSetting>>();
/** 步骤校验函数 */
const validateBasic = async () => {
async function validateBasic() {
await basicInfoRef.value?.validate();
};
}
/** 表单设计校验 */
const validateForm = async () => {
async function validateForm() {
await formDesignRef.value?.validate();
};
}
/** 流程设计校验 */
const validateProcess = async () => {
async function validateProcess() {
await processDesignRef.value?.validate();
};
}
/** 更多设置校验 */
const validateExtra = async () => {
async function validateExtra() {
await extraSettingRef.value?.validate();
};
}
const currentStep = ref(-1); // 步骤控制。-1 用于,一开始全部不展示等当前页面数据初始化完成
@@ -139,7 +139,7 @@ const deptList = ref<SystemDeptApi.Dept[]>([]);
/** 初始化数据 */
const actionType = route.params.type as string;
const initData = async () => {
async function initData() {
if (actionType === 'definition') {
// 情况一:流程定义场景(恢复)
const definitionId = route.params.id as string;
@@ -200,7 +200,7 @@ const initData = async () => {
// 以前未配置更多设置的流程
extraSettingRef.value?.initData();
};
}
/** 根据类型切换流程数据 */
watch(
@@ -218,7 +218,7 @@ watch(
);
/** 校验所有步骤数据是否完整 */
const validateAllSteps = async () => {
async function validateAllSteps() {
// 基本信息校验
try {
await validateBasic();
@@ -254,10 +254,10 @@ const validateAllSteps = async () => {
}
return true;
};
}
/** 保存操作 */
const handleSave = async () => {
async function handleSave() {
try {
// 保存前校验所有步骤的数据
const result = await validateAllSteps();
@@ -311,10 +311,10 @@ const handleSave = async () => {
console.error('保存失败:', error);
// message.warning(error.msg || '请完善所有步骤的必填信息');
}
};
}
/** 发布操作 */
const handleDeploy = async () => {
async function handleDeploy() {
try {
// 修改场景下直接发布,新增场景下需要先确认
if (!formData.value.id) {
@@ -345,10 +345,10 @@ const handleDeploy = async () => {
console.error('发布失败:', error);
message.warning(error.message || '发布失败');
}
};
}
/** 步骤切换处理 */
const handleStepClick = async (index: number) => {
async function handleStepClick(index: number) {
try {
if (index !== 0) {
await validateBasic();
@@ -370,17 +370,17 @@ const handleStepClick = async (index: number) => {
message.warning('请先完善当前步骤必填信息');
}
}
};
}
const tabs = useTabs();
/** 返回列表页 */
const handleBack = () => {
function handleBack() {
// 关闭当前页签
tabs.closeCurrentTab();
// 跳转到列表页,使用路径, 目前后端的路由 name 'name'+ menuId
router.push({ path: '/bpm/manager/model' });
};
}
/** 初始化 */
onMounted(async () => {
@@ -398,11 +398,9 @@ onBeforeUnmount(() => {
<template>
<Page auto-content-height>
<div class="mx-auto">
<!-- 头部导航栏 -->
<div
class="absolute inset-x-0 top-0 z-10 flex h-12 items-center border-b bg-white px-5"
>
<!-- 主体内容 -->
<Card class="mb-4">
<template #title>
<!-- 左侧标题 -->
<div class="flex w-[200px] items-center overflow-hidden">
<ArrowLeft
@@ -416,88 +414,82 @@ onBeforeUnmount(() => {
{{ formData.name || '创建流程' }}
</span>
</div>
<!-- 步骤条 -->
<div class="flex h-full flex-1 items-center justify-center">
<div class="flex h-full w-[400px] items-center justify-between">
</template>
<template #extra>
<Button
v-if="actionType === 'update'"
type="primary"
@click="handleDeploy"
>
</Button>
<Button type="primary" @click="handleSave">
<span v-if="actionType === 'definition'"> </span>
<span v-else> </span>
</Button>
</template>
<!-- 步骤条 -->
<div class="flex h-full flex-1 items-center justify-center">
<div class="flex h-full w-[400px] items-center justify-between">
<div
v-for="(step, index) in steps"
:key="index"
class="relative mx-[15px] flex h-full cursor-pointer items-center"
:class="[
currentStep === index
? 'border-b-2 border-solid border-blue-500 text-blue-500'
: 'text-gray-500',
]"
@click="handleStepClick(index)"
>
<div
v-for="(step, index) in steps"
:key="index"
class="relative mx-[15px] flex h-full cursor-pointer items-center"
class="mr-2 flex h-7 w-7 items-center justify-center rounded-full border-2 border-solid text-[15px]"
:class="[
currentStep === index
? 'border-b-2 border-solid border-blue-500 text-blue-500'
: 'text-gray-500',
? 'border-blue-500 bg-blue-500 text-white'
: 'border-gray-300 bg-white text-gray-500',
]"
@click="handleStepClick(index)"
>
<div
class="mr-2 flex h-7 w-7 items-center justify-center rounded-full border-2 border-solid text-[15px]"
:class="[
currentStep === index
? 'border-blue-500 bg-blue-500 text-white'
: 'border-gray-300 bg-white text-gray-500',
]"
>
{{ index + 1 }}
</div>
<span class="whitespace-nowrap text-base font-bold">{{
step.title
}}</span>
{{ index + 1 }}
</div>
<span class="whitespace-nowrap text-base font-bold">{{
step.title
}}</span>
</div>
</div>
<!-- 右侧按钮 -->
<div class="flex w-[200px] items-center justify-end gap-2">
<Button
v-if="actionType === 'update'"
type="primary"
@click="handleDeploy"
>
</Button>
<Button type="primary" @click="handleSave">
<span v-if="actionType === 'definition'"> </span>
<span v-else> </span>
</Button>
</div>
</div>
<!-- 主体内容 -->
<Card :body-style="{ padding: '10px' }" class="mb-4">
<div class="mt-[50px]">
<!-- 第一步基本信息 -->
<div v-if="currentStep === 0" class="mx-auto w-4/6">
<BasicInfo
v-model="formData"
:category-list="categoryList"
:user-list="userList"
:dept-list="deptList"
ref="basicInfoRef"
/>
</div>
<!-- 第二步表单设计 -->
<div v-if="currentStep === 1" class="mx-auto w-4/6">
<FormDesign
v-model="formData"
:form-list="formList"
ref="formDesignRef"
/>
</div>
<!-- 第三步流程设计 -->
<ProcessDesign
v-if="currentStep === 2"
<div class="mt-[50px]">
<!-- 第一步基本信息 -->
<div v-if="currentStep === 0" class="mx-auto w-4/6">
<BasicInfo
v-model="formData"
ref="processDesignRef"
:category-list="categoryList"
:user-list="userList"
:dept-list="deptList"
ref="basicInfoRef"
/>
<!-- 第四步更多设置 -->
<div v-if="currentStep === 3" class="mx-auto w-4/6">
<ExtraSetting v-model="formData" ref="extraSettingRef" />
</div>
</div>
</Card>
</div>
<!-- 第二步表单设计 -->
<div v-if="currentStep === 1" class="mx-auto w-4/6">
<FormDesign
v-model="formData"
:form-list="formList"
ref="formDesignRef"
/>
</div>
<!-- 第三步流程设计 -->
<ProcessDesign
v-if="currentStep === 2"
v-model="formData"
ref="processDesignRef"
/>
<!-- 第四步更多设置 -->
<div v-if="currentStep === 3" class="mx-auto w-4/6">
<ExtraSetting v-model="formData" ref="extraSettingRef" />
</div>
</div>
</Card>
</Page>
</template>

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>

View File

@@ -4,21 +4,12 @@ import type { ModelCategoryInfo } from '#/api/bpm/model';
import { onActivated, reactive, ref, useTemplateRef, watch } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus, Search, Settings } from '@vben/icons';
import { IconifyIcon } from '@vben/icons';
import { cloneDeep } from '@vben/utils';
import { refAutoReset } from '@vueuse/core';
import { useSortable } from '@vueuse/integrations/useSortable';
import {
Button,
Card,
Divider,
Dropdown,
Form,
Input,
Menu,
message,
} from 'ant-design-vue';
import { Button, Card, Dropdown, Input, Menu, message } from 'ant-design-vue';
import {
getCategorySimpleList,
@@ -72,7 +63,7 @@ watch(
);
/** 加载数据 */
const getList = async () => {
async function getList() {
modelListSpinning.value = true;
try {
const modelList = await getModelList(queryParams.name);
@@ -89,27 +80,22 @@ const getList = async () => {
} finally {
modelListSpinning.value = false;
}
};
}
/** 初始化 */
onActivated(() => {
getList();
});
/** 查询方法 */
const handleQuery = () => {
getList();
};
/** 新增模型 */
const createModel = () => {
function createModel() {
router.push({
name: 'BpmModelCreate',
});
};
}
/** 处理下拉菜单命令 */
const handleCommand = (command: string) => {
function handleCommand(command: string) {
if (command === 'handleCategoryAdd') {
// 打开新建流程分类弹窗
categoryFormModalApi.open();
@@ -126,10 +112,10 @@ const handleCommand = (command: string) => {
});
}
}
};
}
/** 取消分类排序 */
const handleCategorySortCancel = () => {
function handleCategorySortCancel() {
// 恢复初始数据
categoryGroup.value = cloneDeep(originalData.value);
isCategorySorting.value = false;
@@ -137,10 +123,10 @@ const handleCategorySortCancel = () => {
if (sortableInstance.value) {
sortableInstance.value.option('disabled', true);
}
};
}
/** 提交分类排序 */
const handleCategorySortSubmit = async () => {
async function handleCategorySortSubmit() {
saveSortLoading.value = true;
try {
// 保存排序逻辑
@@ -157,76 +143,56 @@ const handleCategorySortSubmit = async () => {
if (sortableInstance.value) {
sortableInstance.value.option('disabled', true);
}
};
}
</script>
<template>
<Page auto-content-height>
<!-- TODO @jaosn没头像的图标展示文字头像哈 @芋艿 好像已经展示了文字头像是模型列表中吗? -->
<!-- 流程分类表单弹窗 -->
<CategoryFormModal @success="getList" />
<Card
:body-style="{ padding: '10px' }"
class="mb-4 h-[98%]"
title="流程模型"
v-spinning="modelListSpinning"
>
<template #extra>
<Input
v-model:value="queryParams.name"
placeholder="搜索流程"
allow-clear
@press-enter="getList"
class="!w-60"
/>
<Button type="primary" @click="createModel">
<IconifyIcon icon="lucide:plus" /> 新建模型
</Button>
<Dropdown placement="bottomRight" arrow>
<Button>
<template #icon>
<IconifyIcon icon="lucide:settings" />
</template>
</Button>
<template #overlay>
<Menu @click="(e) => handleCommand(e.key as string)">
<Menu.Item key="handleCategoryAdd">
<div class="flex items-center">
<IconifyIcon icon="lucide:plus" />
新建分类
</div>
</Menu.Item>
<Menu.Item key="handleCategorySort">
<div class="flex items-center">
<IconifyIcon icon="lucide:align-start-vertical" />
分类排序
</div>
</Menu.Item>
</Menu>
</template>
</Dropdown>
</template>
<div class="flex h-full items-center justify-between pl-5">
<span class="-mb-4 text-lg font-extrabold">流程模型</span>
<!-- 搜索工作栏 -->
<Form
v-if="!isCategorySorting"
class="-mb-4 mr-2.5 flex"
:model="queryParams"
layout="inline"
>
<Form.Item name="name" class="ml-auto">
<Input
v-model:value="queryParams.name"
placeholder="搜索流程"
allow-clear
@press-enter="handleQuery"
class="!w-60"
>
<template #prefix>
<Search class="mx-2.5" />
</template>
</Input>
</Form.Item>
<!-- 右上角新建模型更多操作 -->
<Form.Item>
<Button type="primary" @click="createModel">
<Plus class="size-5" /> 新建模型
</Button>
</Form.Item>
<Form.Item>
<Dropdown placement="bottomRight" arrow>
<Button>
<template #icon>
<Settings class="size-4" />
</template>
</Button>
<template #overlay>
<Menu @click="(e) => handleCommand(e.key as string)">
<Menu.Item key="handleCategoryAdd">
<div class="flex items-center">
<span
class="icon-[ant-design--plus-outlined] mr-1.5 text-[18px]"
></span>
新建分类
</div>
</Menu.Item>
<Menu.Item key="handleCategorySort">
<div class="flex items-center">
<span class="icon-[fa--sort-amount-desc] mr-1.5"></span>
分类排序
</div>
</Menu.Item>
</Menu>
</template>
</Dropdown>
</Form.Item>
</Form>
<div class="-mb-4 mr-6" v-else>
<div class="mb-4 mr-6" v-if="isCategorySorting">
<Button @click="handleCategorySortCancel" class="mr-3">
</Button>
@@ -240,7 +206,6 @@ const handleCategorySortSubmit = async () => {
</div>
</div>
<Divider />
<!-- 按照分类展示其所属的模型列表 -->
<div class="px-5" ref="categoryGroupRef">
<CategoryDraggableModel

View File

@@ -4,6 +4,7 @@ import type { BpmModelApi, ModelCategoryInfo } from '#/api/bpm/model';
import { computed, ref, watchEffect } from 'vue';
import { confirm, useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { cloneDeep, formatDateTime, isEqual } from '@vben/utils';
import { useDebounceFn } from '@vueuse/core';
@@ -36,6 +37,12 @@ const props = defineProps<{
const emit = defineEmits(['success']);
// 重命名分类对话框
const [CategoryRenameModal, categoryRenameModalApi] = useVbenModal({
connectedComponent: CategoryRenameForm,
destroyOnClose: true,
});
const isModelSorting = ref(false);
const originalData = ref<BpmModelApi.ModelVO[]>([]);
const modelList = ref<BpmModelApi.ModelVO[]>([]);
@@ -98,7 +105,7 @@ const columns = [
];
/** 处理模型的排序 */
const handleModelSort = () => {
function handleModelSort() {
// 保存初始数据
originalData.value = cloneDeep(props.categoryInfo.modelList);
// 展开数据
@@ -114,10 +121,10 @@ const handleModelSort = () => {
disabled: false, // 启用排序
});
}
};
}
/** 处理模型的排序提交 */
const handleModelSortSubmit = async () => {
async function handleModelSortSubmit() {
try {
// 保存排序
const ids = modelList.value.map((item) => item.id);
@@ -129,10 +136,10 @@ const handleModelSortSubmit = async () => {
} catch (error) {
console.error('排序保存失败', error);
}
};
}
/** 处理模型的排序取消 */
const handleModelSortCancel = () => {
function handleModelSortCancel() {
// 恢复初始数据
modelList.value = cloneDeep(originalData.value);
isModelSorting.value = false;
@@ -140,20 +147,20 @@ const handleModelSortCancel = () => {
if (sortableInstance.value) {
sortableInstance.value.option('disabled', true);
}
};
}
/** 处理下拉菜单命令 */
const handleCommand = (command: string) => {
function handleCommand(command: string) {
if (command === 'renameCategory') {
// 打开重命名分类对话框
categoryRenameModalApi.setData(props.categoryInfo).open();
} else if (command === 'deleteCategory') {
handleDeleteCategory();
}
};
}
/** 删除流程分类 */
const handleDeleteCategory = async () => {
async function handleDeleteCategory() {
if (props.categoryInfo.modelList.length > 0) {
message.warning('该分类下仍有流程定义,不允许删除');
return;
@@ -170,13 +177,13 @@ const handleDeleteCategory = async () => {
// 刷新列表
emit('success');
});
};
}
/** 处理表单详情点击 */
const handleFormDetail = (row: any) => {
function handleFormDetail(row: any) {
// TODO 待实现
console.warn('待实现', row);
};
}
/** 更新 modelList 模型列表 */
const updateModelList = useDebounceFn(() => {
@@ -205,17 +212,11 @@ watchEffect(() => {
});
/** 自定义表格行渲染 */
const customRow = (_record: any) => {
function customRow(_record: any) {
return {
class: isModelSorting.value ? 'cursor-move' : '',
};
};
// 重命名分类对话框
const [CategoryRenameModal, categoryRenameModalApi] = useVbenModal({
connectedComponent: CategoryRenameForm,
destroyOnClose: true,
});
}
// 处理重命名成功
const handleRenameSuccess = () => {
@@ -268,7 +269,7 @@ const handleRenameSuccess = () => {
@click.stop="handleModelSort"
>
<template #icon>
<span class="icon-[fa--sort-amount-desc]"></span>
<IconifyIcon icon="lucide:align-start-vertical" />
</template>
排序
</Button>
@@ -279,7 +280,7 @@ const handleRenameSuccess = () => {
class="flex items-center text-[14px]"
>
<template #icon>
<span class="icon-[ant-design--setting-outlined]"></span>
<IconifyIcon icon="lucide:settings" />
</template>
分类
</Button>