feat: [bpm][ele] 新建流程模型 20%
This commit is contained in:
@@ -62,30 +62,30 @@ const routes: RouteRecordRaw[] = [
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// path: 'manager/model/create',
|
path: 'manager/model/create',
|
||||||
// component: () => import('#/views/bpm/model/form/index.vue'),
|
component: () => import('#/views/bpm/model/form/index.vue'),
|
||||||
// name: 'BpmModelCreate',
|
name: 'BpmModelCreate',
|
||||||
// meta: {
|
meta: {
|
||||||
// title: '创建流程',
|
title: '创建流程',
|
||||||
// activePath: '/bpm/manager/model',
|
activePath: '/bpm/manager/model',
|
||||||
// icon: 'carbon:flow-connection',
|
icon: 'carbon:flow-connection',
|
||||||
// hideInMenu: true,
|
hideInMenu: true,
|
||||||
// keepAlive: true,
|
keepAlive: true,
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// path: 'manager/model/:type/:id',
|
path: 'manager/model/:type/:id',
|
||||||
// component: () => import('#/views/bpm/model/form/index.vue'),
|
component: () => import('#/views/bpm/model/form/index.vue'),
|
||||||
// name: 'BpmModelUpdate',
|
name: 'BpmModelUpdate',
|
||||||
// meta: {
|
meta: {
|
||||||
// title: '修改流程',
|
title: '修改流程',
|
||||||
// activePath: '/bpm/manager/model',
|
activePath: '/bpm/manager/model',
|
||||||
// icon: 'carbon:flow-connection',
|
icon: 'carbon:flow-connection',
|
||||||
// hideInMenu: true,
|
hideInMenu: true,
|
||||||
// keepAlive: true,
|
keepAlive: true,
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
path: 'manager/definition',
|
path: 'manager/definition',
|
||||||
component: () => import('#/views/bpm/model/definition/index.vue'),
|
component: () => import('#/views/bpm/model/definition/index.vue'),
|
||||||
|
|||||||
498
apps/web-ele/src/views/bpm/model/form/index.vue
Normal file
498
apps/web-ele/src/views/bpm/model/form/index.vue
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { BpmCategoryApi } from '#/api/bpm/category';
|
||||||
|
import type { BpmProcessDefinitionApi } from '#/api/bpm/definition';
|
||||||
|
import type { BpmFormApi } from '#/api/bpm/form';
|
||||||
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
|
import type { SystemUserApi } from '#/api/system/user';
|
||||||
|
|
||||||
|
import { onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { confirm, Page } from '@vben/common-ui';
|
||||||
|
import {
|
||||||
|
BpmAutoApproveType,
|
||||||
|
BpmModelFormType,
|
||||||
|
BpmModelType,
|
||||||
|
} from '@vben/constants';
|
||||||
|
import { useTabs } from '@vben/hooks';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { useUserStore } from '@vben/stores';
|
||||||
|
|
||||||
|
import { ElButton, ElCard, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { getCategorySimpleList } from '#/api/bpm/category';
|
||||||
|
import { getProcessDefinition } from '#/api/bpm/definition';
|
||||||
|
import { getFormSimpleList } from '#/api/bpm/form';
|
||||||
|
import {
|
||||||
|
createModel,
|
||||||
|
deployModel,
|
||||||
|
getModel,
|
||||||
|
updateModel,
|
||||||
|
} from '#/api/bpm/model';
|
||||||
|
import { getSimpleDeptList } from '#/api/system/dept';
|
||||||
|
import { getSimpleUserList } from '#/api/system/user';
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
defineOptions({ name: 'BpmModelCreate' });
|
||||||
|
|
||||||
|
type BpmProcessDefinitionType = Omit<
|
||||||
|
BpmProcessDefinitionApi.ProcessDefinition,
|
||||||
|
'modelId' | 'modelType'
|
||||||
|
> & {
|
||||||
|
id?: string;
|
||||||
|
type?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const tabs = useTabs();
|
||||||
|
|
||||||
|
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 actionType = route.params.type as string; // 操作类型:create、copy、update
|
||||||
|
const currentStep = ref(-1); // 步骤控制。-1 用于,一开始全部不展示等当前页面数据初始化完成
|
||||||
|
const steps = [
|
||||||
|
{ title: '基本信息', validator: validateBasic },
|
||||||
|
{ title: '表单设计', validator: validateForm },
|
||||||
|
{ title: '流程设计', validator: validateProcess },
|
||||||
|
{ title: '更多设置', validator: validateExtra },
|
||||||
|
];
|
||||||
|
|
||||||
|
const formData: any = ref({
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
key: '',
|
||||||
|
category: undefined,
|
||||||
|
icon: undefined,
|
||||||
|
description: '',
|
||||||
|
type: BpmModelType.SIMPLE,
|
||||||
|
formType: BpmModelFormType.NORMAL,
|
||||||
|
formId: '',
|
||||||
|
formCustomCreatePath: '',
|
||||||
|
formCustomViewPath: '',
|
||||||
|
visible: true,
|
||||||
|
startUserType: undefined,
|
||||||
|
startUserIds: [],
|
||||||
|
startDeptIds: [],
|
||||||
|
managerUserIds: [],
|
||||||
|
allowCancelRunningProcess: true,
|
||||||
|
processIdRule: {
|
||||||
|
enable: false,
|
||||||
|
prefix: '',
|
||||||
|
infix: '',
|
||||||
|
postfix: '',
|
||||||
|
length: 5,
|
||||||
|
},
|
||||||
|
autoApprovalType: BpmAutoApproveType.NONE,
|
||||||
|
titleSetting: {
|
||||||
|
enable: false,
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
summarySetting: {
|
||||||
|
enable: false,
|
||||||
|
summary: [],
|
||||||
|
},
|
||||||
|
allowWithdrawTask: false,
|
||||||
|
}); // 表单数据
|
||||||
|
const processData = ref<any>(); // 流程数据
|
||||||
|
|
||||||
|
const formList = ref<BpmFormApi.Form[]>([]);
|
||||||
|
const categoryList = ref<BpmCategoryApi.Category[]>([]);
|
||||||
|
const userList = ref<SystemUserApi.User[]>([]);
|
||||||
|
const deptList = ref<SystemDeptApi.Dept[]>([]);
|
||||||
|
|
||||||
|
provide('processData', processData);
|
||||||
|
provide('modelData', formData);
|
||||||
|
|
||||||
|
/** 步骤校验函数 */
|
||||||
|
async function validateBasic() {
|
||||||
|
await basicInfoRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表单设计校验 */
|
||||||
|
async function validateForm() {
|
||||||
|
await formDesignRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 流程设计校验 */
|
||||||
|
async function validateProcess() {
|
||||||
|
await processDesignRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更多设置校验 */
|
||||||
|
async function validateExtra() {
|
||||||
|
await extraSettingRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化数据 */
|
||||||
|
async function initData() {
|
||||||
|
if (actionType === 'definition') {
|
||||||
|
// 情况一:流程定义场景(恢复)
|
||||||
|
const definitionId = route.params.id as string;
|
||||||
|
const data = await getProcessDefinition(definitionId);
|
||||||
|
const processDefinition: BpmProcessDefinitionType = data;
|
||||||
|
// 将 definition => model
|
||||||
|
processDefinition.type = data.modelType;
|
||||||
|
processDefinition.id = data.modelId;
|
||||||
|
if (data.simpleModel) {
|
||||||
|
processDefinition.simpleModel = JSON.parse(data.simpleModel);
|
||||||
|
}
|
||||||
|
formData.value = processDefinition;
|
||||||
|
|
||||||
|
// 设置 startUserType
|
||||||
|
if (formData.value.startUserIds?.length > 0) {
|
||||||
|
formData.value.startUserType = 1;
|
||||||
|
} else if (formData.value.startDeptIds?.length > 0) {
|
||||||
|
formData.value.startUserType = 2;
|
||||||
|
} else {
|
||||||
|
formData.value.startUserType = 0;
|
||||||
|
}
|
||||||
|
} else if (['copy', 'update'].includes(actionType)) {
|
||||||
|
// 情况二:修改场景/复制场景
|
||||||
|
const modelId = route.params.id as string;
|
||||||
|
formData.value = await getModel(modelId);
|
||||||
|
|
||||||
|
// 设置 startUserType
|
||||||
|
if (formData.value.startUserIds?.length > 0) {
|
||||||
|
formData.value.startUserType = 1;
|
||||||
|
} else if (formData.value.startDeptIds?.length > 0) {
|
||||||
|
formData.value.startUserType = 2;
|
||||||
|
} else {
|
||||||
|
formData.value.startUserType = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊:复制场景
|
||||||
|
if (route.params.type === 'copy') {
|
||||||
|
delete formData.value.id;
|
||||||
|
if (formData.value.bpmnXml) {
|
||||||
|
formData.value.bpmnXml = formData.value.bpmnXml.replaceAll(
|
||||||
|
formData.value.name,
|
||||||
|
`${formData.value.name}副本`,
|
||||||
|
);
|
||||||
|
formData.value.bpmnXml = formData.value.bpmnXml.replaceAll(
|
||||||
|
formData.value.key,
|
||||||
|
`${formData.value.key}_copy`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
formData.value.name += '副本';
|
||||||
|
formData.value.key += '_copy';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 情况三:新增场景
|
||||||
|
formData.value.startUserType = 0; // 全体
|
||||||
|
formData.value.managerUserIds.push(userStore.userInfo?.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取表单列表
|
||||||
|
formList.value = await getFormSimpleList();
|
||||||
|
categoryList.value = await getCategorySimpleList();
|
||||||
|
// 获取用户列表
|
||||||
|
userList.value = await getSimpleUserList();
|
||||||
|
// 获取部门列表
|
||||||
|
deptList.value = await getSimpleDeptList();
|
||||||
|
|
||||||
|
// 最终,设置 currentStep 切换到第一步
|
||||||
|
currentStep.value = 0;
|
||||||
|
// 以前未配置更多设置的流程
|
||||||
|
extraSettingRef.value?.initData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据类型切换流程数据 */
|
||||||
|
watch(
|
||||||
|
async () => formData.value.type,
|
||||||
|
() => {
|
||||||
|
if (formData.value.type === BpmModelType.BPMN) {
|
||||||
|
processData.value = formData.value.bpmnXml;
|
||||||
|
} else if (formData.value.type === BpmModelType.SIMPLE) {
|
||||||
|
processData.value = formData.value.simpleModel;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 校验所有步骤数据是否完整 */
|
||||||
|
async function validateAllSteps() {
|
||||||
|
// 基本信息校验
|
||||||
|
try {
|
||||||
|
await validateBasic();
|
||||||
|
} catch {
|
||||||
|
currentStep.value = 0;
|
||||||
|
ElMessage.warning('请完善基本信息');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单设计校验
|
||||||
|
try {
|
||||||
|
await validateForm();
|
||||||
|
} catch {
|
||||||
|
currentStep.value = 1;
|
||||||
|
ElMessage.warning('请完善自定义表单信息');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 流程设计校验
|
||||||
|
try {
|
||||||
|
await validateProcess();
|
||||||
|
} catch {
|
||||||
|
currentStep.value = 2;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更多设置校验
|
||||||
|
try {
|
||||||
|
await validateExtra();
|
||||||
|
} catch {
|
||||||
|
currentStep.value = 3;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存操作 */
|
||||||
|
async function handleSave() {
|
||||||
|
try {
|
||||||
|
// 保存前校验所有步骤的数据
|
||||||
|
const result = await validateAllSteps();
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新表单数据
|
||||||
|
const modelData = {
|
||||||
|
...formData.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (actionType) {
|
||||||
|
case 'copy': {
|
||||||
|
// 情况三:复制场景
|
||||||
|
formData.value.id = await createModel(modelData);
|
||||||
|
// 提示成功
|
||||||
|
ElMessage.success('复制成功,可点击【发布】按钮,进行发布模型');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'definition': {
|
||||||
|
// 情况一:流程定义场景(恢复)
|
||||||
|
await updateModel(modelData);
|
||||||
|
// 提示成功
|
||||||
|
ElMessage.success('恢复成功,可点击【发布】按钮,进行发布模型');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'update': {
|
||||||
|
// 情况二:修改场景
|
||||||
|
await updateModel(modelData);
|
||||||
|
// 提示成功
|
||||||
|
ElMessage.success('修改成功,可点击【发布】按钮,进行发布模型');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// 情况四:新增场景
|
||||||
|
formData.value.id = await createModel(modelData);
|
||||||
|
// 提示成功
|
||||||
|
ElMessage.success('新建成功,可点击【发布】按钮,进行发布模型');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回列表页(排除更新的情况)
|
||||||
|
if (actionType !== 'update') {
|
||||||
|
await router.push({ name: 'BpmModel' });
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('保存失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 发布操作 */
|
||||||
|
async function handleDeploy() {
|
||||||
|
try {
|
||||||
|
// 1.1 修改场景下直接发布,新增场景下需要先确认
|
||||||
|
if (!formData.value.id) {
|
||||||
|
await confirm('是否确认发布该流程?');
|
||||||
|
}
|
||||||
|
// 1.2 校验所有步骤
|
||||||
|
await validateAllSteps();
|
||||||
|
|
||||||
|
// 2.1 更新表单数据
|
||||||
|
const modelData = {
|
||||||
|
...formData.value,
|
||||||
|
};
|
||||||
|
// 2.2 先保存所有数据
|
||||||
|
if (formData.value.id) {
|
||||||
|
await updateModel(modelData);
|
||||||
|
} else {
|
||||||
|
const result = await createModel(modelData);
|
||||||
|
formData.value.id = result.id;
|
||||||
|
}
|
||||||
|
// 2.3 发布
|
||||||
|
await deployModel(formData.value.id);
|
||||||
|
|
||||||
|
// 3. 路由并提示
|
||||||
|
ElMessage.success('发布成功');
|
||||||
|
await router.push({ name: 'BpmModel' });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('发布失败:', error);
|
||||||
|
ElMessage.warning(error.message || '发布失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 步骤切换处理 */
|
||||||
|
async function handleStepClick(index: number) {
|
||||||
|
try {
|
||||||
|
if (index !== 0) {
|
||||||
|
await validateBasic();
|
||||||
|
}
|
||||||
|
if (index !== 1) {
|
||||||
|
await validateForm();
|
||||||
|
}
|
||||||
|
if (index !== 2) {
|
||||||
|
await validateProcess();
|
||||||
|
}
|
||||||
|
if (index !== 3) {
|
||||||
|
await validateExtra();
|
||||||
|
}
|
||||||
|
// 切换步骤
|
||||||
|
currentStep.value = index;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('步骤切换失败:', error);
|
||||||
|
if (currentStep.value !== 2) {
|
||||||
|
ElMessage.warning('请先完善当前步骤必填信息');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 返回列表页 */
|
||||||
|
function handleBack() {
|
||||||
|
tabs.closeCurrentTab();
|
||||||
|
router.push({ name: 'BpmModel' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(async () => {
|
||||||
|
await initData();
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 添加组件卸载前的清理 */
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
// 清理所有的引用
|
||||||
|
basicInfoRef.value = undefined;
|
||||||
|
formDesignRef.value = undefined;
|
||||||
|
processDesignRef.value = undefined;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<div class="mx-auto">
|
||||||
|
<!-- 头部导航栏 -->
|
||||||
|
<div
|
||||||
|
class="bg-card absolute inset-x-0 top-0 z-10 flex h-12 items-center border-b px-5"
|
||||||
|
>
|
||||||
|
<!-- 左侧标题 -->
|
||||||
|
<div class="flex w-48 items-center overflow-hidden">
|
||||||
|
<IconifyIcon
|
||||||
|
icon="lucide:arrow-left"
|
||||||
|
class="size-5 flex-shrink-0 cursor-pointer"
|
||||||
|
@click="handleBack"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ml-2.5 truncate text-base"
|
||||||
|
:title="formData.name || '创建流程'"
|
||||||
|
>
|
||||||
|
{{ formData.name || '创建流程' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤条 -->
|
||||||
|
<div class="flex h-full flex-1 items-center justify-center">
|
||||||
|
<div class="flex h-full w-auto items-center justify-center">
|
||||||
|
<div
|
||||||
|
v-for="(step, index) in steps"
|
||||||
|
:key="index"
|
||||||
|
class="relative mx-6 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
|
||||||
|
class="mr-2 flex h-7 w-7 items-center justify-center rounded-full border-2 border-solid text-base"
|
||||||
|
: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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧按钮 -->
|
||||||
|
<div class="flex w-48 items-center justify-end gap-2">
|
||||||
|
<ElButton
|
||||||
|
v-if="actionType === 'update'"
|
||||||
|
type="primary"
|
||||||
|
@click="handleDeploy"
|
||||||
|
>
|
||||||
|
发 布
|
||||||
|
</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleSave">
|
||||||
|
<span v-if="actionType === 'definition'">恢 复</span>
|
||||||
|
<span v-else>保 存</span>
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 主体内容 -->
|
||||||
|
<ElCard body-style="padding: 10px" class="mb-4">
|
||||||
|
<div class="mt-12">
|
||||||
|
<!-- 第一步:基本信息 -->
|
||||||
|
<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"
|
||||||
|
v-model="formData"
|
||||||
|
ref="processDesignRef"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 第四步:更多设置 -->
|
||||||
|
<div v-if="currentStep === 3" class="mx-auto w-4/6">
|
||||||
|
<ExtraSetting v-model="formData" ref="extraSettingRef" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
442
apps/web-ele/src/views/bpm/model/form/modules/basic-info.vue
Normal file
442
apps/web-ele/src/views/bpm/model/form/modules/basic-info.vue
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormItemRule } from 'element-plus';
|
||||||
|
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
|
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 { DICT_TYPE } from '@vben/constants';
|
||||||
|
import { getDictOptions } from '@vben/hooks';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ElAvatar,
|
||||||
|
ElButton,
|
||||||
|
ElForm,
|
||||||
|
ElFormItem,
|
||||||
|
ElInput,
|
||||||
|
ElOption,
|
||||||
|
ElRadio,
|
||||||
|
ElRadioGroup,
|
||||||
|
ElSelect,
|
||||||
|
ElTooltip,
|
||||||
|
} from 'element-plus';
|
||||||
|
|
||||||
|
// import { DeptSelectModal, UserSelectModal } from '#/components/select-modal';
|
||||||
|
import { ImageUpload } from '#/components/upload';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
categoryList: {
|
||||||
|
type: Array as PropType<BpmCategoryApi.Category[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
userList: {
|
||||||
|
type: Array as PropType<SystemUserApi.User[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
deptList: {
|
||||||
|
type: Array as PropType<SystemDeptApi.Dept[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// const [UserSelectModalComp, userSelectModalApi] = useVbenModal({
|
||||||
|
// connectedComponent: UserSelectModal,
|
||||||
|
// destroyOnClose: true,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const [DeptSelectModalComp, deptSelectModalApi] = useVbenModal({
|
||||||
|
// connectedComponent: DeptSelectModal,
|
||||||
|
// destroyOnClose: true,
|
||||||
|
// });
|
||||||
|
|
||||||
|
const formRef = ref(); // 表单引用
|
||||||
|
const modelData = defineModel<any>(); // 创建本地数据副本
|
||||||
|
|
||||||
|
const selectedStartUsers = ref<SystemUserApi.User[]>([]); // 选中的发起人
|
||||||
|
const selectedStartDepts = ref<SystemDeptApi.Dept[]>([]); // 选中的发起部门
|
||||||
|
|
||||||
|
const selectedManagerUsers = ref<SystemUserApi.User[]>([]); // 选中的流程管理员
|
||||||
|
const currentSelectType = ref<'manager' | 'start'>('start');
|
||||||
|
const selectedUsers = ref<number[]>(); // 选中的用户
|
||||||
|
const rules: Record<string, FormItemRule[]> = {
|
||||||
|
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||||
|
key: [
|
||||||
|
{ required: true, message: '流程标识不能为空', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (_rule: any, value: string, callback: any) => {
|
||||||
|
if (!value) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^[a-z_][-\w.$]*$/i.test(value)) {
|
||||||
|
callback(
|
||||||
|
new Error(
|
||||||
|
'只能包含字母、数字、下划线、连字符和点号,且必须以字母或下划线开头',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
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' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 初始化选中的用户 */
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 打开发起人选择 */
|
||||||
|
function openStartUserSelect() {
|
||||||
|
currentSelectType.value = 'start';
|
||||||
|
selectedUsers.value = selectedStartUsers.value.map(
|
||||||
|
(user) => user.id,
|
||||||
|
) as number[];
|
||||||
|
// userSelectModalApi.setData({ userIds: selectedUsers.value }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开部门选择 */
|
||||||
|
function openStartDeptSelect() {
|
||||||
|
// deptSelectModalApi.setData({ selectedList: selectedStartDepts.value }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// /** 处理部门选择确认 */
|
||||||
|
// function handleDeptSelectConfirm(depts: SystemDeptApi.Dept[]) {
|
||||||
|
// modelData.value = {
|
||||||
|
// ...modelData.value,
|
||||||
|
// startDeptIds: depts.map((d) => d.id),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
/** 打开管理员选择 */
|
||||||
|
function openManagerUserSelect() {
|
||||||
|
currentSelectType.value = 'manager';
|
||||||
|
selectedUsers.value = selectedManagerUsers.value.map(
|
||||||
|
(user) => user.id,
|
||||||
|
) as number[];
|
||||||
|
// userSelectModalApi.setData({ userIds: selectedUsers.value }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// /** 处理用户选择确认 */
|
||||||
|
// function 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),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /** 用户选择弹窗关闭 */
|
||||||
|
// function handleUserSelectClosed() {
|
||||||
|
// selectedUsers.value = [];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /** 用户选择弹窗取消 */
|
||||||
|
// function handleUserSelectCancel() {
|
||||||
|
// selectedUsers.value = [];
|
||||||
|
// }
|
||||||
|
|
||||||
|
/** 处理发起人类型变化 */
|
||||||
|
function handleStartUserTypeChange(value: number) {
|
||||||
|
switch (value) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 移除发起人 */
|
||||||
|
function handleRemoveStartUser(user: SystemUserApi.User) {
|
||||||
|
modelData.value = {
|
||||||
|
...modelData.value,
|
||||||
|
startUserIds: modelData.value.startUserIds.filter(
|
||||||
|
(id: number) => id !== user.id,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 移除部门 */
|
||||||
|
function handleRemoveStartDept(dept: SystemDeptApi.Dept) {
|
||||||
|
modelData.value = {
|
||||||
|
...modelData.value,
|
||||||
|
startDeptIds: modelData.value.startDeptIds.filter(
|
||||||
|
(id: number) => id !== dept.id,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 移除管理员 */
|
||||||
|
function handleRemoveManagerUser(user: SystemUserApi.User) {
|
||||||
|
modelData.value = {
|
||||||
|
...modelData.value,
|
||||||
|
managerUserIds: modelData.value.managerUserIds.filter(
|
||||||
|
(id: number) => id !== user.id,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表单校验 */
|
||||||
|
async function validate() {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ validate });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ElForm ref="formRef" :model="modelData" :rules="rules" label-width="100px">
|
||||||
|
<ElFormItem label="流程标识" prop="key">
|
||||||
|
<div class="flex w-full items-center">
|
||||||
|
<ElInput
|
||||||
|
v-model="modelData.key"
|
||||||
|
:disabled="!!modelData.id"
|
||||||
|
placeholder="请输入流程标识,以字母或下划线开头"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<ElTooltip
|
||||||
|
:content="
|
||||||
|
modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'
|
||||||
|
"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<IconifyIcon icon="lucide:circle-help" class="ml-1 size-5" />
|
||||||
|
</ElTooltip>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="流程名称" prop="name">
|
||||||
|
<ElInput
|
||||||
|
v-model="modelData.name"
|
||||||
|
:disabled="!!modelData.id"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入流程名称"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="流程分类" prop="category">
|
||||||
|
<ElSelect
|
||||||
|
v-model="modelData.category"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择流程分类"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="category in categoryList"
|
||||||
|
:key="category.code"
|
||||||
|
:value="category.code"
|
||||||
|
:label="category.name"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="流程图标">
|
||||||
|
<ImageUpload v-model:value="modelData.icon" />
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="流程描述" prop="description">
|
||||||
|
<ElInput v-model="modelData.description" type="textarea" clearable />
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="流程类型" prop="type">
|
||||||
|
<ElRadioGroup v-model="modelData.type">
|
||||||
|
<ElRadio
|
||||||
|
v-for="dict in getDictOptions(DICT_TYPE.BPM_MODEL_TYPE, 'number')"
|
||||||
|
:key="dict.value as number"
|
||||||
|
:value="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</ElRadio>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="是否可见" prop="visible">
|
||||||
|
<ElRadioGroup v-model="modelData.visible">
|
||||||
|
<ElRadio
|
||||||
|
v-for="dict in getDictOptions(
|
||||||
|
DICT_TYPE.INFRA_BOOLEAN_STRING,
|
||||||
|
'boolean',
|
||||||
|
)"
|
||||||
|
:key="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</ElRadio>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="谁可以发起" prop="startUserType">
|
||||||
|
<ElSelect
|
||||||
|
v-model="modelData.startUserType"
|
||||||
|
placeholder="请选择谁可以发起"
|
||||||
|
@change="handleStartUserTypeChange"
|
||||||
|
>
|
||||||
|
<ElOption :value="0" label="全员" />
|
||||||
|
<ElOption :value="1" label="指定人员" />
|
||||||
|
<ElOption :value="2" label="指定部门" />
|
||||||
|
</ElSelect>
|
||||||
|
<div
|
||||||
|
v-if="modelData.startUserType === 1"
|
||||||
|
class="mt-2 flex flex-wrap gap-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="user in selectedStartUsers"
|
||||||
|
:key="user.id"
|
||||||
|
class="relative flex h-8 items-center rounded-lg bg-gray-100 pr-2 hover:bg-gray-200 dark:border dark:border-gray-500 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<ElAvatar
|
||||||
|
class="m-1 size-7"
|
||||||
|
v-if="user.avatar"
|
||||||
|
:src="user.avatar"
|
||||||
|
/>
|
||||||
|
<ElAvatar class="m-1 size-7" v-else>
|
||||||
|
{{ user.nickname?.substring(0, 1) }}
|
||||||
|
</ElAvatar>
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">
|
||||||
|
{{ user.nickname }}
|
||||||
|
</span>
|
||||||
|
<IconifyIcon
|
||||||
|
icon="lucide:x"
|
||||||
|
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500 dark:text-gray-200"
|
||||||
|
@click="handleRemoveStartUser(user)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ElButton link @click="openStartUserSelect" class="flex items-center">
|
||||||
|
<template #icon>
|
||||||
|
<IconifyIcon icon="lucide:user-plus" class="size-4" />
|
||||||
|
</template>
|
||||||
|
选择人员
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="modelData.startUserType === 2"
|
||||||
|
class="mt-2 flex flex-wrap gap-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="dept in selectedStartDepts"
|
||||||
|
:key="dept.id"
|
||||||
|
class="relative flex h-8 items-center rounded-lg bg-gray-100 pr-2 shadow-sm hover:bg-gray-200 dark:border dark:border-gray-500 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<IconifyIcon icon="lucide:building" class="size-6 px-1" />
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">
|
||||||
|
{{ dept.name }}
|
||||||
|
</span>
|
||||||
|
<IconifyIcon
|
||||||
|
icon="lucide:x"
|
||||||
|
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
|
||||||
|
@click="handleRemoveStartDept(dept)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ElButton link @click="openStartDeptSelect" class="flex items-center">
|
||||||
|
<template #icon>
|
||||||
|
<IconifyIcon icon="lucide:user-plus" class="size-4" />
|
||||||
|
</template>
|
||||||
|
选择部门
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="流程管理员" prop="managerUserIds">
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
<div
|
||||||
|
v-for="user in selectedManagerUsers"
|
||||||
|
:key="user.id"
|
||||||
|
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2 hover:bg-gray-200 dark:border dark:border-gray-500 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<ElAvatar
|
||||||
|
class="m-1 size-7"
|
||||||
|
v-if="user.avatar"
|
||||||
|
:src="user.avatar"
|
||||||
|
/>
|
||||||
|
<ElAvatar class="m-1 size-7" v-else>
|
||||||
|
{{ user.nickname?.substring(0, 1) }}
|
||||||
|
</ElAvatar>
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">
|
||||||
|
{{ user.nickname }}
|
||||||
|
</span>
|
||||||
|
<IconifyIcon
|
||||||
|
icon="lucide:x"
|
||||||
|
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
|
||||||
|
@click="handleRemoveManagerUser(user)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ElButton
|
||||||
|
link
|
||||||
|
@click="openManagerUserSelect"
|
||||||
|
class="flex items-center"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<IconifyIcon icon="lucide:user-plus" class="size-4" />
|
||||||
|
</template>
|
||||||
|
选择人员
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
|
||||||
|
<!-- 用户选择弹窗 -->
|
||||||
|
<!-- <UserSelectModalComp
|
||||||
|
class="w-3/5"
|
||||||
|
v-model:value="selectedUsers"
|
||||||
|
:multiple="true"
|
||||||
|
@confirm="handleUserSelectConfirm"
|
||||||
|
@closed="handleUserSelectClosed"
|
||||||
|
@cancel="handleUserSelectCancel"
|
||||||
|
/> -->
|
||||||
|
<!-- 部门选择对话框 -->
|
||||||
|
<!-- <DeptSelectModalComp
|
||||||
|
class="w-3/5"
|
||||||
|
:check-strictly="true"
|
||||||
|
@confirm="handleDeptSelectConfirm"
|
||||||
|
/> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
import { inject, onBeforeUnmount, provide, ref, shallowRef, watch } from 'vue';
|
||||||
|
|
||||||
|
import { ContentWrap } from '@vben/common-ui';
|
||||||
|
import { BpmModelFormType } from '@vben/constants';
|
||||||
|
|
||||||
|
import { getForm } from '#/api/bpm/form';
|
||||||
|
// import {
|
||||||
|
// MyProcessDesigner,
|
||||||
|
// MyProcessPenal,
|
||||||
|
// } from '#/views/bpm/components/bpmn-process-designer/package';
|
||||||
|
// // 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务)
|
||||||
|
// import CustomContentPadProvider from '#/views/bpm/components/bpmn-process-designer/package/designer/plugins/content-pad';
|
||||||
|
// // 自定义左侧菜单(修改 默认任务 为 用户任务)
|
||||||
|
// import CustomPaletteProvider from '#/views/bpm/components/bpmn-process-designer/package/designer/plugins/palette';
|
||||||
|
|
||||||
|
defineOptions({ name: 'BpmModelEditor' });
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
modelId?: string;
|
||||||
|
modelKey: string;
|
||||||
|
modelName: string;
|
||||||
|
value?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// const emit = defineEmits(['success', 'init-finished']);
|
||||||
|
|
||||||
|
const formFields = ref<string[]>([]); // 表单信息
|
||||||
|
const formType = ref(BpmModelFormType.NORMAL); // 表单类型,暂仅限流程表单 TODO @jason:是不是已经支持 业务表单 了?
|
||||||
|
provide('formFields', formFields);
|
||||||
|
provide('formType', formType);
|
||||||
|
|
||||||
|
// const xmlString = inject('processData') as Ref; // 注入流程数据
|
||||||
|
const modelData = inject('modelData') as Ref; // 注入模型数据
|
||||||
|
|
||||||
|
const modeler = shallowRef(); // BPMN Modeler
|
||||||
|
// const processDesigner = ref();
|
||||||
|
// const controlForm = ref({
|
||||||
|
// simulation: true,
|
||||||
|
// labelEditing: false,
|
||||||
|
// labelVisible: false,
|
||||||
|
// prefix: 'flowable',
|
||||||
|
// headerButtonSize: 'mini',
|
||||||
|
// // additionalModel: [CustomContentPadProvider, CustomPaletteProvider],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const model = ref<BpmModelApi.Model>(); // 流程模型的信息
|
||||||
|
|
||||||
|
/** 初始化 modeler */
|
||||||
|
// const initModeler = async (item: any) => {
|
||||||
|
// model.value = modelData.value;
|
||||||
|
// modeler.value = item;
|
||||||
|
// };
|
||||||
|
|
||||||
|
/** 添加/修改模型 */
|
||||||
|
// const save = async (bpmnXml: string) => {
|
||||||
|
// try {
|
||||||
|
// xmlString.value = bpmnXml;
|
||||||
|
// emit('success', bpmnXml);
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('保存失败:', error);
|
||||||
|
// ElMessage.error('保存失败');
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
/** 监听表单 ID 变化,加载表单数据 */
|
||||||
|
watch(
|
||||||
|
() => modelData.value.formId,
|
||||||
|
async (newFormId) => {
|
||||||
|
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
|
||||||
|
const data = await getForm(newFormId);
|
||||||
|
formFields.value = data.fields;
|
||||||
|
} else {
|
||||||
|
formFields.value = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 在组件卸载时清理 */
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
modeler.value = null;
|
||||||
|
// 清理全局实例
|
||||||
|
const w = window as any;
|
||||||
|
if (w.bpmnInstances) {
|
||||||
|
w.bpmnInstances = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 流程设计器,负责绘制流程等 -->
|
||||||
|
<!-- <MyProcessDesigner
|
||||||
|
key="designer"
|
||||||
|
v-model="xmlString"
|
||||||
|
:value="xmlString"
|
||||||
|
v-bind="controlForm"
|
||||||
|
keyboard
|
||||||
|
ref="processDesigner"
|
||||||
|
@init-finished="initModeler"
|
||||||
|
:additional-model="controlForm.additionalModel"
|
||||||
|
:model="model"
|
||||||
|
@save="save"
|
||||||
|
:process-id="modelKey"
|
||||||
|
:process-name="modelName"
|
||||||
|
/> -->
|
||||||
|
<!-- 流程属性器,负责编辑每个流程节点的属性 -->
|
||||||
|
<!-- <MyProcessPenal
|
||||||
|
v-if="modeler"
|
||||||
|
key="penal"
|
||||||
|
:bpmn-modeler="modeler"
|
||||||
|
:prefix="controlForm.prefix"
|
||||||
|
class="process-panel"
|
||||||
|
:model="model"
|
||||||
|
/> -->
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.process-panel__container) {
|
||||||
|
@apply absolute right-[20px] top-[70px];
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { MentionItem } from '../modules/tinymce-plugin';
|
||||||
|
|
||||||
|
import { computed, onBeforeUnmount, ref, shallowRef } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import Editor from '@tinymce/tinymce-vue';
|
||||||
|
import { ElAlert } from 'element-plus';
|
||||||
|
|
||||||
|
import { setupTinyPlugins } from './tinymce-plugin';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
formFields?: Array<{ field: string; title: string }>;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
formFields: () => [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
(e: 'confirm', value: string): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
/** TinyMCE 自托管:https://www.jianshu.com/p/59a9c3802443 */
|
||||||
|
const tinymceScriptSrc = `${import.meta.env.VITE_BASE}tinymce/tinymce.min.js`;
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modalApi.lock();
|
||||||
|
try {
|
||||||
|
const { template } = modalApi.getData<{
|
||||||
|
template: string;
|
||||||
|
}>();
|
||||||
|
if (template !== undefined) {
|
||||||
|
valueHtml.value = template;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onConfirm() {
|
||||||
|
emits('confirm', valueHtml.value);
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mentionList = computed<MentionItem[]>(() => {
|
||||||
|
const base: MentionItem[] = [
|
||||||
|
{ id: 'startUser', name: '发起人' },
|
||||||
|
{ id: 'startUserDept', name: '发起人部门' },
|
||||||
|
{ id: 'processName', name: '流程名称' },
|
||||||
|
{ id: 'processNum', name: '流程编号' },
|
||||||
|
{ id: 'startTime', name: '发起时间' },
|
||||||
|
{ id: 'endTime', name: '结束时间' },
|
||||||
|
{ id: 'processStatus', name: '流程状态' },
|
||||||
|
{ id: 'printUser', name: '打印人' },
|
||||||
|
{ id: 'printTime', name: '打印时间' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const extras: MentionItem[] = (props.formFields || []).map((it: any) => ({
|
||||||
|
id: it.field,
|
||||||
|
name: `[表单]${it.title}`,
|
||||||
|
}));
|
||||||
|
return [...base, ...extras];
|
||||||
|
}); // 提供给 @ 自动补全的字段(默认 + 表单字段)
|
||||||
|
|
||||||
|
const valueHtml = ref<string>('');
|
||||||
|
const editorRef = shallowRef<any>(); // 编辑器
|
||||||
|
|
||||||
|
const tinyInit = {
|
||||||
|
height: 400,
|
||||||
|
width: 'auto',
|
||||||
|
menubar: false,
|
||||||
|
plugins: 'link importcss table code preview autoresize lists ',
|
||||||
|
toolbar:
|
||||||
|
'undo redo | styles fontsize | bold italic underline | alignleft aligncenter alignright | link table | processrecord code preview',
|
||||||
|
language: 'zh_CN',
|
||||||
|
branding: false,
|
||||||
|
statusbar: true,
|
||||||
|
content_style:
|
||||||
|
'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }',
|
||||||
|
setup(editor: any) {
|
||||||
|
editorRef.value = editor;
|
||||||
|
// 在编辑器 setup 时注册自定义插件
|
||||||
|
setupTinyPlugins(editor, () => mentionList.value);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (editorRef.value) {
|
||||||
|
editorRef.value.destroy?.();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal class="w-3/4" title="自定义模板">
|
||||||
|
<div class="mb-3">
|
||||||
|
<ElAlert
|
||||||
|
message="输入 @ 可选择插入流程选项和表单选项"
|
||||||
|
type="info"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Editor
|
||||||
|
v-model="valueHtml"
|
||||||
|
:init="tinyInit"
|
||||||
|
:tinymce-script-src="tinymceScriptSrc"
|
||||||
|
license-key="gpl"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
618
apps/web-ele/src/views/bpm/model/form/modules/extra-setting.vue
Normal file
618
apps/web-ele/src/views/bpm/model/form/modules/extra-setting.vue
Normal file
@@ -0,0 +1,618 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, provide, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import {
|
||||||
|
BpmAutoApproveType,
|
||||||
|
BpmModelFormType,
|
||||||
|
ProcessVariableEnum,
|
||||||
|
} from '@vben/constants';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import {
|
||||||
|
ElButton,
|
||||||
|
ElCheckbox,
|
||||||
|
ElCol,
|
||||||
|
ElForm,
|
||||||
|
ElFormItem,
|
||||||
|
ElInput,
|
||||||
|
ElInputNumber,
|
||||||
|
ElMention,
|
||||||
|
ElOption,
|
||||||
|
ElRadio,
|
||||||
|
ElRadioGroup,
|
||||||
|
ElRow,
|
||||||
|
ElSelect,
|
||||||
|
ElSwitch,
|
||||||
|
ElText,
|
||||||
|
ElTooltip,
|
||||||
|
} from 'element-plus';
|
||||||
|
|
||||||
|
import { getForm } from '#/api/bpm/form';
|
||||||
|
// import {
|
||||||
|
// HttpRequestSetting,
|
||||||
|
// parseFormFields,
|
||||||
|
// } from '#/views/bpm/components/simple-process-design';
|
||||||
|
import { parseFormFields } from '#/components/form-create';
|
||||||
|
|
||||||
|
import PrintTemplate from './custom-print-template.vue';
|
||||||
|
|
||||||
|
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);
|
||||||
|
function handleProcessBeforeTriggerEnableChange(
|
||||||
|
val: boolean | number | string,
|
||||||
|
) {
|
||||||
|
modelData.value.processBeforeTriggerSetting = val
|
||||||
|
? {
|
||||||
|
url: '',
|
||||||
|
header: [],
|
||||||
|
body: [],
|
||||||
|
response: [],
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 是否开启流程后置通知 */
|
||||||
|
const processAfterTriggerEnable = ref(false);
|
||||||
|
function handleProcessAfterTriggerEnableChange(val: boolean | number | string) {
|
||||||
|
modelData.value.processAfterTriggerSetting = val
|
||||||
|
? {
|
||||||
|
url: '',
|
||||||
|
header: [],
|
||||||
|
body: [],
|
||||||
|
response: [],
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 是否开启任务前置通知 */
|
||||||
|
const taskBeforeTriggerEnable = ref(false);
|
||||||
|
function handleTaskBeforeTriggerEnableChange(val: boolean | number | string) {
|
||||||
|
modelData.value.taskBeforeTriggerSetting = val
|
||||||
|
? {
|
||||||
|
url: '',
|
||||||
|
header: [],
|
||||||
|
body: [],
|
||||||
|
response: [],
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 是否开启任务后置通知 */
|
||||||
|
const taskAfterTriggerEnable = ref(false);
|
||||||
|
function handleTaskAfterTriggerEnableChange(val: boolean | number | string) {
|
||||||
|
modelData.value.taskAfterTriggerSetting = val
|
||||||
|
? {
|
||||||
|
url: '',
|
||||||
|
header: [],
|
||||||
|
body: [],
|
||||||
|
response: [],
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表单字段 */
|
||||||
|
const formFields = ref<Array<{ field: string; title: string }>>([]);
|
||||||
|
const formFieldOptions4Title = computed(() => {
|
||||||
|
const cloneFormField = formFields.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 formFields.value.map((item) => {
|
||||||
|
return {
|
||||||
|
label: item.title,
|
||||||
|
value: item.field,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const unParsedFormFields = ref<string[]>([]); // 未解析的表单字段
|
||||||
|
provide('formFields', unParsedFormFields); // 暴露给子组件 HttpRequestSetting 使用
|
||||||
|
|
||||||
|
/** 兼容以前未配置更多设置的流程 */
|
||||||
|
function initData() {
|
||||||
|
if (!modelData.value.processIdRule) {
|
||||||
|
modelData.value.processIdRule = {
|
||||||
|
enable: false,
|
||||||
|
prefix: '',
|
||||||
|
infix: '',
|
||||||
|
postfix: '',
|
||||||
|
length: 5,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!modelData.value.printTemplateSetting) {
|
||||||
|
modelData.value.printTemplateSetting = {
|
||||||
|
enable: false,
|
||||||
|
template: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (modelData.value.allowWithdrawTask === undefined) {
|
||||||
|
modelData.value.allowWithdrawTask = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 监听表单 ID 变化,加载表单数据 */
|
||||||
|
watch(
|
||||||
|
() => modelData.value.formId,
|
||||||
|
async (newFormId) => {
|
||||||
|
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
|
||||||
|
const data = await getForm(newFormId);
|
||||||
|
const result: Array<{ field: string; title: string }> = [];
|
||||||
|
if (data.fields) {
|
||||||
|
unParsedFormFields.value = data.fields;
|
||||||
|
data.fields.forEach((fieldStr: string) => {
|
||||||
|
parseFormFields(JSON.parse(fieldStr), result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
formFields.value = result;
|
||||||
|
} else {
|
||||||
|
formFields.value = [];
|
||||||
|
unParsedFormFields.value = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
const formRef = ref(); // 表单引用
|
||||||
|
|
||||||
|
/** 表单校验 */
|
||||||
|
async function validate() {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 自定义打印模板模态框 */
|
||||||
|
const [PrintTemplateModal, printTemplateModalApi] = useVbenModal({
|
||||||
|
connectedComponent: PrintTemplate,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 弹出自定义打印模板弹窗 */
|
||||||
|
function openPrintTemplateModal() {
|
||||||
|
printTemplateModalApi
|
||||||
|
.setData({ template: modelData.value.printTemplateSetting.template })
|
||||||
|
.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 默认的打印模板, 目前自定义模板没有引入自定义样式。 看后续是否需要 */
|
||||||
|
const defaultTemplate = `<p style="text-align: center;font-size: 1.25rem;"><strong><span data-w-e-type="mention" data-value="流程名称" data-info="%7B%22id%22%3A%22processName%22%7D">@流程名称</span></strong></p>
|
||||||
|
<p style="text-align: right;">打印人员:<span data-w-e-type="mention" data-info="%7B%22id%22%3A%22printUser%22%7D">@打印人</span></p>
|
||||||
|
<p style="text-align: left;">流程编号:<span data-w-e-type="mention" data-value="流程编号" data-info="%7B%22id%22%3A%22processNum%22%7D">@流程编号</span></p>
|
||||||
|
<p> </p>
|
||||||
|
<table style="width: 100%; height: 72.2159px;">
|
||||||
|
<tbody>
|
||||||
|
<tr style="height: 36.108px;">
|
||||||
|
<td style="width: 21.7532%; border: 1px solid;" colspan="1" rowspan="1" width="auto">发起人</td>
|
||||||
|
<td style="width: 30.5551%; border: 1px solid;" colspan="1" rowspan="1" width="auto"><span data-w-e-type="mention" data-value="发起人" data-info="%7B%22id%22%3A%22startUser%22%7D">@发起人</span></td>
|
||||||
|
<td style="width: 21.7532%; border: 1px solid;" colspan="1" rowspan="1" width="auto">发起时间</td>
|
||||||
|
<td style="width: 26.0284%; border: 1px solid;" colspan="1" rowspan="1" width="auto"><span data-w-e-type="mention" data-value="发起时间" data-info="%7B%22id%22%3A%22startTime%22%7D">@发起时间</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="height: 36.108px;">
|
||||||
|
<td style="width: 21.7532%; border: 1px solid;" colspan="1" rowspan="1" width="auto">所属部门</td>
|
||||||
|
<td style="width: 30.5551%; border: 1px solid;" colspan="1" rowspan="1" width="auto"><span data-w-e-type="mention" data-w-e-is-void="" data-w-e-is-inline="" data-value="发起人部门" data-info="%7B%22id%22%3A%22startUserDept%22%7D">@发起人部门</span></td>
|
||||||
|
<td style="width: 21.7532%; border: 1px solid;" colspan="1" rowspan="1" width="auto">流程状态</td>
|
||||||
|
<td style="width: 26.0284%; border: 1px solid;" colspan="1" rowspan="1" width="auto"><span data-w-e-type="mention" data-value="流程状态" data-info="%7B%22id%22%3A%22processStatus%22%7D">@流程状态</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p> </p>
|
||||||
|
<div contenteditable="false" data-w-e-type="process-record" data-w-e-is-void="">
|
||||||
|
<table class="process-record-table" style="width: 100%; border-collapse: collapse; border: 1px solid;">
|
||||||
|
<tr>
|
||||||
|
<td style="width: 100%; border: 1px solid; text-align: center;" colspan="2">流程记录</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 25%; border: 1px solid;">节点</td>
|
||||||
|
<td style="width: 75%; border: 1px solid;">操作</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p> </p>`;
|
||||||
|
|
||||||
|
const printTemplateEnable = computed<boolean>({
|
||||||
|
get() {
|
||||||
|
return !!modelData.value?.printTemplateSetting?.enable;
|
||||||
|
},
|
||||||
|
set(val: boolean) {
|
||||||
|
if (!modelData.value.printTemplateSetting) {
|
||||||
|
modelData.value.printTemplateSetting = {
|
||||||
|
enable: false,
|
||||||
|
template: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
modelData.value.printTemplateSetting.enable = val;
|
||||||
|
},
|
||||||
|
}); // 自定义打印模板开关
|
||||||
|
|
||||||
|
function handlePrintTemplateEnableChange(checked: any) {
|
||||||
|
const val = !!checked;
|
||||||
|
if (val && !modelData.value.printTemplateSetting.template) {
|
||||||
|
modelData.value.printTemplateSetting.template = defaultTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmPrintTemplate(template: string) {
|
||||||
|
modelData.value.printTemplateSetting.template = template;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ initData, validate });
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ElForm
|
||||||
|
ref="formRef"
|
||||||
|
:model="modelData"
|
||||||
|
label-width="120px"
|
||||||
|
class="mt-5 px-5"
|
||||||
|
>
|
||||||
|
<ElFormItem class="mb-5" label="提交人权限">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<ElCheckbox v-model="modelData.allowCancelRunningProcess">
|
||||||
|
允许撤销审批中的申请
|
||||||
|
</ElCheckbox>
|
||||||
|
<div class="ml-6 text-sm">
|
||||||
|
<ElText type="warning">
|
||||||
|
第一个审批节点通过后,提交人仍可撤销申请
|
||||||
|
</ElText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem class="mb-5" label="审批人权限">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<ElCheckbox v-model="modelData.allowWithdrawTask">
|
||||||
|
允许审批人撤回任务
|
||||||
|
</ElCheckbox>
|
||||||
|
<div class="ml-6 text-sm">
|
||||||
|
<ElText type="info">审批人可撤回正在审批节点的前一节点</ElText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem v-if="modelData.processIdRule" class="mb-5" label="流程编码">
|
||||||
|
<ElRow :gutter="8">
|
||||||
|
<ElCol :span="1">
|
||||||
|
<ElCheckbox v-model="modelData.processIdRule.enable" />
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="5">
|
||||||
|
<ElInput
|
||||||
|
v-model="modelData.processIdRule.prefix"
|
||||||
|
placeholder="前缀"
|
||||||
|
:disabled="!modelData.processIdRule.enable"
|
||||||
|
/>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="6">
|
||||||
|
<ElSelect
|
||||||
|
v-model="modelData.processIdRule.infix"
|
||||||
|
clearable
|
||||||
|
placeholder="中缀"
|
||||||
|
:disabled="!modelData.processIdRule.enable"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="option in timeOptions"
|
||||||
|
:key="option.value"
|
||||||
|
:value="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="4">
|
||||||
|
<ElInput
|
||||||
|
v-model="modelData.processIdRule.postfix"
|
||||||
|
placeholder="后缀"
|
||||||
|
:disabled="!modelData.processIdRule.enable"
|
||||||
|
/>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="4">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="modelData.processIdRule.length"
|
||||||
|
:min="5"
|
||||||
|
:disabled="!modelData.processIdRule.enable"
|
||||||
|
/>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
<div class="ml-6 mt-2" v-if="modelData.processIdRule.enable">
|
||||||
|
<ElText type="success">编码示例:{{ numberExample }}</ElText>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem class="mb-5" label="自动去重">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="mb-2">
|
||||||
|
<ElText>同一审批人在流程中重复出现时:</ElText>
|
||||||
|
</div>
|
||||||
|
<ElRadioGroup v-model="modelData.autoApprovalType">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<ElRadio :value="0">不自动通过</ElRadio>
|
||||||
|
<ElRadio :value="1">
|
||||||
|
仅审批一次,后续重复的审批节点均自动通过
|
||||||
|
</ElRadio>
|
||||||
|
<ElRadio :value="2">仅针对连续审批的节点自动通过</ElRadio>
|
||||||
|
</div>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem v-if="modelData.titleSetting" class="mb-5" label="标题设置">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<ElRadioGroup v-model="modelData.titleSetting.enable">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<ElRadio :value="false">
|
||||||
|
系统默认
|
||||||
|
<ElText type="success"> 展示流程名称 </ElText>
|
||||||
|
</ElRadio>
|
||||||
|
<ElRadio :value="true">
|
||||||
|
<div class="inline-flex items-center">
|
||||||
|
自定义标题
|
||||||
|
<ElTooltip
|
||||||
|
content="输入字符 '{' 即可插入表单字段"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<IconifyIcon
|
||||||
|
icon="lucide:circle-help"
|
||||||
|
class="ml-1 size-4 text-gray-500"
|
||||||
|
/>
|
||||||
|
</ElTooltip>
|
||||||
|
</div>
|
||||||
|
</ElRadio>
|
||||||
|
</div>
|
||||||
|
</ElRadioGroup>
|
||||||
|
<ElMention
|
||||||
|
v-if="modelData.titleSetting.enable"
|
||||||
|
v-model="modelData.titleSetting.title"
|
||||||
|
class="w-full min-w-[400px]"
|
||||||
|
type="textarea"
|
||||||
|
prefix="{"
|
||||||
|
split="}"
|
||||||
|
:options="formFieldOptions4Title"
|
||||||
|
placeholder="请插入表单字段(输入 '{' 可以选择表单字段)或输入文本"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem
|
||||||
|
v-if="
|
||||||
|
modelData.summarySetting &&
|
||||||
|
modelData.formType === BpmModelFormType.NORMAL
|
||||||
|
"
|
||||||
|
class="mb-5"
|
||||||
|
label="摘要设置"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<ElRadioGroup v-model="modelData.summarySetting.enable">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<ElRadio :value="false">
|
||||||
|
系统默认
|
||||||
|
<ElText type="info">展示表单前 3 个字段</ElText>
|
||||||
|
</ElRadio>
|
||||||
|
<ElRadio :value="true"> 自定义摘要 </ElRadio>
|
||||||
|
</div>
|
||||||
|
</ElRadioGroup>
|
||||||
|
<ElSelect
|
||||||
|
v-if="modelData.summarySetting.enable"
|
||||||
|
v-model="modelData.summarySetting.summary"
|
||||||
|
multiple
|
||||||
|
placeholder="请选择要展示的表单字段"
|
||||||
|
class="w-full min-w-[400px]"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="option in formFieldOptions4Summary"
|
||||||
|
:key="option.value"
|
||||||
|
:value="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem class="mb-5" label="流程前置通知">
|
||||||
|
<ElRow class="mt-1">
|
||||||
|
<ElCol :span="24">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="processBeforeTriggerEnable"
|
||||||
|
@change="handleProcessBeforeTriggerEnableChange"
|
||||||
|
/>
|
||||||
|
<span class="ml-4">流程启动后通知</span>
|
||||||
|
</div>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
<ElRow v-if="processBeforeTriggerEnable">
|
||||||
|
<ElCol :span="24" class="mt-6">
|
||||||
|
<!-- <HttpRequestSetting
|
||||||
|
v-model:setting="modelData.processBeforeTriggerSetting"
|
||||||
|
:response-enable="true"
|
||||||
|
form-item-prefix="processBeforeTriggerSetting"
|
||||||
|
/> -->
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem class="mb-5" label="流程后置通知">
|
||||||
|
<ElRow class="mt-1">
|
||||||
|
<ElCol :span="24">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="processAfterTriggerEnable"
|
||||||
|
@change="handleProcessAfterTriggerEnableChange"
|
||||||
|
/>
|
||||||
|
<span class="ml-4">流程结束后通知</span>
|
||||||
|
</div>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
<ElRow v-if="processAfterTriggerEnable" class="mt-2">
|
||||||
|
<ElCol :span="24">
|
||||||
|
<HttpRequestSetting
|
||||||
|
v-model:setting="modelData.processAfterTriggerSetting"
|
||||||
|
:response-enable="true"
|
||||||
|
form-item-prefix="processAfterTriggerSetting"
|
||||||
|
/>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem class="mb-5" label="任务前置通知">
|
||||||
|
<ElRow class="mt-1">
|
||||||
|
<ElCol :span="24">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="taskBeforeTriggerEnable"
|
||||||
|
@change="handleTaskBeforeTriggerEnableChange"
|
||||||
|
/>
|
||||||
|
<span class="ml-4">任务执行时通知</span>
|
||||||
|
</div>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
<ElRow v-if="taskBeforeTriggerEnable" class="mt-2">
|
||||||
|
<ElCol :span="24">
|
||||||
|
<HttpRequestSetting
|
||||||
|
v-model:setting="modelData.taskBeforeTriggerSetting"
|
||||||
|
:response-enable="true"
|
||||||
|
form-item-prefix="taskBeforeTriggerSetting"
|
||||||
|
/>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem class="mb-5" label="任务后置通知">
|
||||||
|
<ElRow class="mt-1">
|
||||||
|
<ElCol :span="24">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="taskAfterTriggerEnable"
|
||||||
|
@change="handleTaskAfterTriggerEnableChange"
|
||||||
|
/>
|
||||||
|
<span class="ml-4">任务结束后通知</span>
|
||||||
|
</div>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
<ElRow v-if="taskAfterTriggerEnable" class="mt-2">
|
||||||
|
<ElCol :span="24">
|
||||||
|
<HttpRequestSetting
|
||||||
|
v-model:setting="modelData.taskAfterTriggerSetting"
|
||||||
|
:response-enable="true"
|
||||||
|
form-item-prefix="taskAfterTriggerSetting"
|
||||||
|
/>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem class="mb-5" label="自定义打印模板">
|
||||||
|
<div class="flex w-full flex-col">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="printTemplateEnable"
|
||||||
|
@change="handlePrintTemplateEnableChange"
|
||||||
|
/>
|
||||||
|
<ElButton
|
||||||
|
v-if="printTemplateEnable"
|
||||||
|
class="ml-2 flex items-center"
|
||||||
|
link
|
||||||
|
@click="openPrintTemplateModal"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<IconifyIcon icon="lucide:pencil" />
|
||||||
|
</template>
|
||||||
|
编辑模板
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<PrintTemplateModal
|
||||||
|
:form-fields="formFields"
|
||||||
|
@confirm="confirmPrintTemplate"
|
||||||
|
/>
|
||||||
|
</ElForm>
|
||||||
|
</template>
|
||||||
184
apps/web-ele/src/views/bpm/model/form/modules/form-design.vue
Normal file
184
apps/web-ele/src/views/bpm/model/form/modules/form-design.vue
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormItemRule } from 'element-plus';
|
||||||
|
|
||||||
|
import type { BpmFormApi } from '#/api/bpm/form';
|
||||||
|
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { BpmModelFormType, DICT_TYPE } from '@vben/constants';
|
||||||
|
import { getDictOptions } from '@vben/hooks';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import FormCreate from '@form-create/element-ui';
|
||||||
|
import {
|
||||||
|
ElForm,
|
||||||
|
ElFormItem,
|
||||||
|
ElInput,
|
||||||
|
ElOption,
|
||||||
|
ElRadio,
|
||||||
|
ElRadioGroup,
|
||||||
|
ElSelect,
|
||||||
|
ElTooltip,
|
||||||
|
} from 'element-plus';
|
||||||
|
|
||||||
|
import { getForm } from '#/api/bpm/form';
|
||||||
|
import { setConfAndFields2 } from '#/components/form-create';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
formList: {
|
||||||
|
type: Array<BpmFormApi.Form>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
|
||||||
|
const modelData = defineModel<any>(); // 创建本地数据副本
|
||||||
|
const formPreview = ref({
|
||||||
|
formData: {} as any,
|
||||||
|
rule: [],
|
||||||
|
option: {
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
formData: {},
|
||||||
|
},
|
||||||
|
}); // 表单预览数据
|
||||||
|
|
||||||
|
const rules: Record<string, FormItemRule[]> = {
|
||||||
|
formType: [{ required: true, message: '表单类型不能为空', trigger: 'blur' }],
|
||||||
|
formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
|
||||||
|
formCustomCreatePath: [
|
||||||
|
{ required: true, message: '表单提交路由不能为空', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
formCustomViewPath: [
|
||||||
|
{ required: true, message: '表单查看地址不能为空', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 监听表单 ID 变化,加载表单数据 */
|
||||||
|
watch(
|
||||||
|
() => modelData.value.formId,
|
||||||
|
async (newFormId) => {
|
||||||
|
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
|
||||||
|
const data = await getForm(newFormId);
|
||||||
|
setConfAndFields2(formPreview.value, data.conf, data.fields);
|
||||||
|
// 设置只读
|
||||||
|
formPreview.value.rule.forEach((item: any) => {
|
||||||
|
item.props = { ...item.props, disabled: true };
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
formPreview.value.rule = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 表单校验 */
|
||||||
|
async function validate() {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ validate });
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ElForm
|
||||||
|
ref="formRef"
|
||||||
|
:model="modelData"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="120px"
|
||||||
|
class="mt-5"
|
||||||
|
>
|
||||||
|
<ElFormItem label="表单类型" prop="formType" class="mb-5">
|
||||||
|
<ElRadioGroup v-model="modelData.formType">
|
||||||
|
<ElRadio
|
||||||
|
v-for="(dict, index) in getDictOptions(
|
||||||
|
DICT_TYPE.BPM_MODEL_FORM_TYPE,
|
||||||
|
'number',
|
||||||
|
)"
|
||||||
|
:key="index"
|
||||||
|
:value="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</ElRadio>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem
|
||||||
|
v-if="modelData.formType === BpmModelFormType.NORMAL"
|
||||||
|
label="流程表单"
|
||||||
|
prop="formId"
|
||||||
|
class="mb-5"
|
||||||
|
>
|
||||||
|
<ElSelect v-model="modelData.formId" clearable>
|
||||||
|
<ElOption
|
||||||
|
v-for="form in props.formList"
|
||||||
|
:key="form.id"
|
||||||
|
:value="form.id!"
|
||||||
|
:label="form.name"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem
|
||||||
|
v-if="modelData.formType === BpmModelFormType.CUSTOM"
|
||||||
|
label="表单提交路由"
|
||||||
|
prop="formCustomCreatePath"
|
||||||
|
class="mb-5"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ElInput
|
||||||
|
v-model="modelData.formCustomCreatePath"
|
||||||
|
placeholder="请输入表单提交路由"
|
||||||
|
/>
|
||||||
|
<ElTooltip
|
||||||
|
content="自定义表单的提交路径,使用 Vue 的路由地址, 例如说: bpm/oa/leave/create.vue"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<IconifyIcon
|
||||||
|
icon="lucide:circle-help"
|
||||||
|
class="ml-1 size-5 text-gray-900"
|
||||||
|
/>
|
||||||
|
</ElTooltip>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem
|
||||||
|
v-if="modelData.formType === BpmModelFormType.CUSTOM"
|
||||||
|
label="表单查看地址"
|
||||||
|
prop="formCustomViewPath"
|
||||||
|
class="mb-5"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ElInput
|
||||||
|
v-model="modelData.formCustomViewPath"
|
||||||
|
placeholder="请输入表单查看的组件地址"
|
||||||
|
/>
|
||||||
|
<ElTooltip
|
||||||
|
content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<IconifyIcon
|
||||||
|
icon="lucide:circle-help"
|
||||||
|
class="ml-1 size-5 text-gray-900"
|
||||||
|
/>
|
||||||
|
</ElTooltip>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<!-- 表单预览 -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
modelData.formType === BpmModelFormType.NORMAL &&
|
||||||
|
modelData.formId &&
|
||||||
|
formPreview.rule.length > 0
|
||||||
|
"
|
||||||
|
class="mb-5 mt-7 rounded-sm border border-solid border-gray-200 p-5"
|
||||||
|
>
|
||||||
|
<div class="mb-4 flex items-center">
|
||||||
|
<div class="mr-2 h-4 w-1 bg-blue-500"></div>
|
||||||
|
<span class="text-base font-bold">表单预览</span>
|
||||||
|
</div>
|
||||||
|
<FormCreate
|
||||||
|
v-model:api="formPreview.formData"
|
||||||
|
:rule="formPreview.rule"
|
||||||
|
:option="formPreview.option"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ElForm>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { BpmModelType } from '@vben/constants';
|
||||||
|
|
||||||
|
import BpmModelEditor from './bpm-model-editor.vue';
|
||||||
|
import SimpleModelDesign from './simple-model-design.vue';
|
||||||
|
|
||||||
|
const modelData = defineModel<any>(); // 创建本地数据副本
|
||||||
|
// const processData = inject('processData') as Ref;
|
||||||
|
|
||||||
|
const simpleDesign = ref();
|
||||||
|
|
||||||
|
/** 表单校验 */
|
||||||
|
async function validate() {
|
||||||
|
// 获取最新的流程数据
|
||||||
|
// if (!processData.value) {
|
||||||
|
// throw new Error('请设计流程');
|
||||||
|
// }
|
||||||
|
// if (modelData.value.type === BpmModelType.SIMPLE) {
|
||||||
|
// // 简易设计器校验
|
||||||
|
// const validateResult = await simpleDesign.value?.validateConfig();
|
||||||
|
// if (!validateResult) {
|
||||||
|
// throw new Error('请完善设计配置');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理设计器保存成功 */
|
||||||
|
async function handleDesignSuccess(data?: any) {
|
||||||
|
if (data) {
|
||||||
|
// 创建新的对象以触发响应式更新
|
||||||
|
const newModelData = {
|
||||||
|
...modelData.value,
|
||||||
|
bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
|
||||||
|
simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data,
|
||||||
|
};
|
||||||
|
// 使用 emit 更新父组件的数据
|
||||||
|
await nextTick();
|
||||||
|
// 更新表单的模型数据部分
|
||||||
|
modelData.value = newModelData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 是否显示设计器 */
|
||||||
|
const showDesigner = computed(() => {
|
||||||
|
return Boolean(modelData.value?.key && modelData.value?.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({ validate });
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="h-full">
|
||||||
|
<!-- BPMN设计器 -->
|
||||||
|
<template v-if="modelData.type === BpmModelType.BPMN">
|
||||||
|
<BpmModelEditor
|
||||||
|
v-if="showDesigner"
|
||||||
|
:model-id="modelData.id"
|
||||||
|
:model-key="modelData.key"
|
||||||
|
:model-name="modelData.name"
|
||||||
|
@success="handleDesignSuccess"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<!-- Simple设计器 -->
|
||||||
|
<template v-else>
|
||||||
|
<SimpleModelDesign
|
||||||
|
v-if="showDesigner"
|
||||||
|
:model-name="modelData.name"
|
||||||
|
:model-form-id="modelData.formId"
|
||||||
|
:model-form-type="modelData.formType"
|
||||||
|
:start-user-ids="modelData.startUserIds"
|
||||||
|
:start-dept-ids="modelData.startDeptIds"
|
||||||
|
@success="handleDesignSuccess"
|
||||||
|
ref="simpleDesign"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { ContentWrap } from '@vben/common-ui';
|
||||||
|
|
||||||
|
// import { SimpleProcessDesigner } from '#/views/bpm/components/simple-process-design';
|
||||||
|
|
||||||
|
defineOptions({ name: 'SimpleModelDesign' });
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
modelFormId?: number;
|
||||||
|
modelFormType?: number;
|
||||||
|
modelName?: string;
|
||||||
|
startDeptIds?: number[];
|
||||||
|
startUserIds?: number[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// const emit = defineEmits(['success']);
|
||||||
|
|
||||||
|
const designerRef = ref();
|
||||||
|
|
||||||
|
/** 保存成功回调 */
|
||||||
|
// function handleSuccess(data?: any) {
|
||||||
|
// if (data) {
|
||||||
|
// emit('success', data);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/** 设计器配置校验 */
|
||||||
|
async function validateConfig() {
|
||||||
|
return await designerRef.value.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ validateConfig });
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ContentWrap class="px-4 py-5">
|
||||||
|
<!-- <SimpleProcessDesigner
|
||||||
|
:model-form-id="modelFormId"
|
||||||
|
:model-name="modelName"
|
||||||
|
:model-form-type="modelFormType"
|
||||||
|
@success="handleSuccess"
|
||||||
|
:start-user-ids="startUserIds"
|
||||||
|
:start-dept-ids="startDeptIds"
|
||||||
|
ref="designerRef"
|
||||||
|
/> -->
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/** TinyMCE 自定义功能:
|
||||||
|
* - processrecord 按钮:插入流程记录占位元素
|
||||||
|
* - @ 自动补全:插入 mention 占位元素
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @ts-ignore TinyMCE 全局或通过打包器提供
|
||||||
|
import type { Editor } from 'tinymce';
|
||||||
|
|
||||||
|
export interface MentionItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 在编辑器 setup 回调中注册流程记录按钮和 @ 自动补全 */
|
||||||
|
export function setupTinyPlugins(
|
||||||
|
editor: Editor,
|
||||||
|
getMentionList: () => MentionItem[],
|
||||||
|
) {
|
||||||
|
// 按钮:流程记录
|
||||||
|
editor.ui.registry.addButton('processrecord', {
|
||||||
|
text: '流程记录',
|
||||||
|
tooltip: '插入流程记录占位',
|
||||||
|
onAction: () => {
|
||||||
|
// 流程记录占位显示, 仅用于显示。process-print.vue 组件中会替换掉
|
||||||
|
editor.insertContent(
|
||||||
|
[
|
||||||
|
'<div data-w-e-type="process-record" data-w-e-is-void contenteditable="false">',
|
||||||
|
'<table class="process-record-table" style="width: 100%; border-collapse: collapse; border: 1px solid;">',
|
||||||
|
'<tr><td style="width: 100%; border: 1px solid; text-align: center;" colspan="2">流程记录</td></tr>',
|
||||||
|
'<tr>',
|
||||||
|
'<td style="width: 25%; border: 1px solid;">节点</td>',
|
||||||
|
'<td style="width: 75%; border: 1px solid;">操作</td>',
|
||||||
|
'</tr>',
|
||||||
|
'</table>',
|
||||||
|
'</div>',
|
||||||
|
].join(''),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ 自动补全
|
||||||
|
editor.ui.registry.addAutocompleter('bpmMention', {
|
||||||
|
trigger: '@',
|
||||||
|
minChars: 0,
|
||||||
|
columns: 1,
|
||||||
|
fetch: (
|
||||||
|
pattern: string,
|
||||||
|
_maxResults: number,
|
||||||
|
_fetchOptions: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
const list = getMentionList();
|
||||||
|
const keyword = (pattern || '').toLowerCase().trim();
|
||||||
|
const data = list
|
||||||
|
.filter((i) => i.name.toLowerCase().includes(keyword))
|
||||||
|
.map((i) => ({
|
||||||
|
value: i.id,
|
||||||
|
text: i.name,
|
||||||
|
}));
|
||||||
|
return Promise.resolve(data);
|
||||||
|
},
|
||||||
|
onAction: (
|
||||||
|
autocompleteApi: any,
|
||||||
|
rng: Range,
|
||||||
|
value: string,
|
||||||
|
_meta: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
const list = getMentionList();
|
||||||
|
const item = list.find((i) => i.id === value);
|
||||||
|
const name = item ? item.name : value;
|
||||||
|
const info = encodeURIComponent(JSON.stringify({ id: value }));
|
||||||
|
editor.selection.setRng(rng);
|
||||||
|
editor.insertContent(
|
||||||
|
`<span data-w-e-type="mention" data-info="${info}">@${name}</span>`,
|
||||||
|
);
|
||||||
|
autocompleteApi.hide();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user