!319 feat: [bpm][antd] todo 修改

Merge pull request !319 from Jason/dev
This commit is contained in:
芋道源码
2026-01-17 09:09:11 +00:00
committed by Gitee
16 changed files with 162 additions and 104 deletions

View File

@@ -16,7 +16,7 @@ import {
} from 'ant-design-vue'; } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import ProcessListenerSelectModal from '#/views/bpm/processListener/components/process-listener-select-modal.vue'; import { ProcessListenerSelectModal } from '#/views/bpm/processListener/components';
import { createListenerObject, updateElementExtensions } from '../../utils'; import { createListenerObject, updateElementExtensions } from '../../utils';
import ListenerFieldModal from './ListenerFieldModal.vue'; import ListenerFieldModal from './ListenerFieldModal.vue';

View File

@@ -16,7 +16,7 @@ import {
} from 'ant-design-vue'; } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import ProcessListenerSelectModal from '#/views/bpm/processListener/components/process-listener-select-modal.vue'; import { ProcessListenerSelectModal } from '#/views/bpm/processListener/components';
import { createListenerObject, updateElementExtensions } from '../../utils'; import { createListenerObject, updateElementExtensions } from '../../utils';
import ListenerFieldModal from './ListenerFieldModal.vue'; import ListenerFieldModal from './ListenerFieldModal.vue';

View File

@@ -43,7 +43,7 @@ import {
MULTI_LEVEL_DEPT, MULTI_LEVEL_DEPT,
} from '#/views/bpm/components/simple-process-design/consts'; } from '#/views/bpm/components/simple-process-design/consts';
import { useFormFieldsPermission } from '#/views/bpm/components/simple-process-design/helpers'; import { useFormFieldsPermission } from '#/views/bpm/components/simple-process-design/helpers';
import ProcessExpressionSelectModal from '#/views/bpm/processExpression/components/process-expression-select-modal.vue'; import { ProcessExpressionSelectModal } from '#/views/bpm/processExpression/components';
defineOptions({ name: 'UserTask' }); defineOptions({ name: 'UserTask' });
const props = defineProps({ const props = defineProps({

View File

@@ -29,6 +29,7 @@ import {
import { getForm } from '#/api/bpm/form'; import { getForm } from '#/api/bpm/form';
import { getModelList } from '#/api/bpm/model'; import { getModelList } from '#/api/bpm/model';
import { parseFormFields } from '#/components/form-create';
import { import {
CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE, CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE,
@@ -42,12 +43,7 @@ import {
TIME_UNIT_TYPES, TIME_UNIT_TYPES,
TimeUnitType, TimeUnitType,
} from '../../consts'; } from '../../consts';
import { import { useFormFields, useNodeName, useWatchNode } from '../../helpers';
parseFormFields,
useFormFields,
useNodeName,
useWatchNode,
} from '../../helpers';
import { convertTimeUnit } from './utils'; import { convertTimeUnit } from './utils';
defineOptions({ name: 'ChildProcessNodeConfig' }); defineOptions({ name: 'ChildProcessNodeConfig' });

View File

@@ -6,9 +6,9 @@ import type { ComponentPublicInstance, Ref } from 'vue';
import type { ButtonSetting, SimpleFlowNode } from '../../consts'; import type { ButtonSetting, SimpleFlowNode } from '../../consts';
import type { UserTaskFormType } from '../../helpers'; import type { UserTaskFormType } from '../../helpers';
import { computed, nextTick, onMounted, reactive, ref } from 'vue'; import { computed, nextTick, onMounted, reactive, ref, watchEffect } from 'vue';
import { useVbenDrawer } from '@vben/common-ui'; import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { import {
BpmModelFormType, BpmModelFormType,
BpmNodeTypeEnum, BpmNodeTypeEnum,
@@ -39,6 +39,8 @@ import {
TypographyText, TypographyText,
} from 'ant-design-vue'; } from 'ant-design-vue';
import { ProcessExpressionSelectModal } from '#/views/bpm/processExpression/components';
import { import {
APPROVE_METHODS, APPROVE_METHODS,
APPROVE_TYPE, APPROVE_TYPE,
@@ -112,10 +114,20 @@ const [Drawer, drawerApi] = useVbenDrawer({
}, },
}); });
const [ExpressionSelectModal, expressionSelectModalApi] = useVbenModal({
connectedComponent: ProcessExpressionSelectModal,
destroyOnClose: true,
showConfirmButton: false,
});
// 节点名称配置 // 节点名称配置
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } = const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
useNodeName(BpmNodeTypeEnum.USER_TASK_NODE); useNodeName(BpmNodeTypeEnum.USER_TASK_NODE);
watchEffect(() => {
void inputRef.value;
});
// 激活的 Tab 标签页 // 激活的 Tab 标签页
const activeTabName = ref('user'); const activeTabName = ref('user');
@@ -218,9 +230,18 @@ function changeCandidateStrategy() {
configForm.value.deptLevel = 1; configForm.value.deptLevel = 1;
configForm.value.formUser = ''; configForm.value.formUser = '';
configForm.value.formDept = ''; configForm.value.formDept = '';
configForm.value.expression = '';
configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_APPROVE; configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_APPROVE;
} }
function openExpressionSelect() {
expressionSelectModalApi.open();
}
function handleExpressionSelected(row: any) {
configForm.value.expression = row?.expression ?? '';
}
/** 审批方式改变 */ /** 审批方式改变 */
function approveMethodChanged() { function approveMethodChanged() {
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS; configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS;
@@ -843,7 +864,6 @@ onMounted(() => {
</SelectOption> </SelectOption>
</Select> </Select>
</FormItem> </FormItem>
<!-- TODO @jason后续要支持选择已经存好的表达式 -->
<FormItem <FormItem
v-if=" v-if="
configForm.candidateStrategy === CandidateStrategy.EXPRESSION configForm.candidateStrategy === CandidateStrategy.EXPRESSION
@@ -851,7 +871,15 @@ onMounted(() => {
label="流程表达式" label="流程表达式"
name="expression" name="expression"
> >
<Textarea v-model:value="configForm.expression" allow-clear /> <div class="flex gap-2">
<Textarea v-model:value="configForm.expression" :rows="2" />
<div class="flex flex-col gap-2">
<Button type="primary" @click="openExpressionSelect">
选择
</Button>
<Button @click="configForm.expression = ''">清空</Button>
</div>
</div>
</FormItem> </FormItem>
<!-- 多人审批/办理 方式 --> <!-- 多人审批/办理 方式 -->
<FormItem :label="`多人${nodeTypeName}方式`" name="approveMethod"> <FormItem :label="`多人${nodeTypeName}方式`" name="approveMethod">
@@ -1266,4 +1294,6 @@ onMounted(() => {
</TabPane> </TabPane>
</Tabs> </Tabs>
</Drawer> </Drawer>
<ExpressionSelectModal @select="handleExpressionSelected" />
</template> </template>

View File

@@ -20,6 +20,8 @@ import {
ProcessVariableEnum, ProcessVariableEnum,
} from '@vben/constants'; } from '@vben/constants';
import { parseFormFields } from '#/components/form-create';
import { import {
ApproveMethodType, ApproveMethodType,
AssignEmptyHandlerType, AssignEmptyHandlerType,
@@ -56,49 +58,6 @@ function parseFormCreateFields(formFields?: string[]) {
return result; return result;
} }
/**
* 解析表单组件的 field, title 等字段(递归,如果组件包含子组件)
*
* @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule
* @param fields 解析后表单组件字段
* @param parentTitle 如果是子表单,子表单的标题,默认为空
*/
export const parseFormFields = (
rule: Record<string, any>,
fields: Array<Record<string, any>> = [],
parentTitle: string = '',
) => {
const { type, field, $required, title: tempTitle, children } = rule;
if (field && tempTitle) {
let title = tempTitle;
if (parentTitle) {
title = `${parentTitle}.${tempTitle}`;
}
let required = false;
if ($required) {
required = true;
}
fields.push({
field,
title,
type,
required,
});
// TODO 子表单 需要处理子表单字段
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
// // 解析子表单的字段
// rule.props.rule.forEach((item) => {
// parseFields(item, fieldsPermission, title)
// })
// }
}
if (children && Array.isArray(children)) {
children.forEach((rule) => {
parseFormFields(rule, fields);
});
}
};
/** /**
* @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点 * @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点
*/ */

View File

@@ -7,5 +7,3 @@ export { default as SimpleProcessDesigner } from './components/simple-process-de
export { default as SimpleProcessViewer } from './components/simple-process-viewer.vue'; export { default as SimpleProcessViewer } from './components/simple-process-viewer.vue';
export type { SimpleFlowNode } from './consts'; export type { SimpleFlowNode } from './consts';
export { parseFormFields } from './helpers';

View File

@@ -32,7 +32,7 @@ defineProps<{
const emit = defineEmits(['success', 'init-finished']); const emit = defineEmits(['success', 'init-finished']);
const formFields = ref<string[]>([]); // 表单信息 const formFields = ref<string[]>([]); // 表单信息
const formType = ref(BpmModelFormType.NORMAL); // 表单类型,暂仅限流程表单 TODO @jason是不是已经支持 业务表单 了? const formType = ref(BpmModelFormType.NORMAL); // 表单类型
provide('formFields', formFields); provide('formFields', formFields);
provide('formType', formType); provide('formType', formType);
@@ -40,7 +40,6 @@ const xmlString = inject('processData') as Ref; // 注入流程数据
const modelData = inject('modelData') as Ref; // 注入模型数据 const modelData = inject('modelData') as Ref; // 注入模型数据
const modeler = shallowRef(); // BPMN Modeler const modeler = shallowRef(); // BPMN Modeler
const processDesigner = ref();
const controlForm = ref({ const controlForm = ref({
simulation: true, simulation: true,
labelEditing: false, labelEditing: false,
@@ -102,7 +101,6 @@ onBeforeUnmount(() => {
:value="xmlString" :value="xmlString"
v-bind="controlForm" v-bind="controlForm"
keyboard keyboard
ref="processDesigner"
@init-finished="initModeler" @init-finished="initModeler"
:additional-model="controlForm.additionalModel" :additional-model="controlForm.additionalModel"
:model="model" :model="model"

View File

@@ -464,7 +464,8 @@ function handleRenameSuccess() {
> >
<div class="flex h-12 items-center"> <div class="flex h-12 items-center">
<!-- 头部分类名 --> <!-- 头部分类名 -->
<!-- TODO @jason2拖动后直接请求排序不用有个保存排序模型分类和排序分类里的模型交互有点不同哈@芋艿 好像 yudao-ui-admin-vue3 交互也是这样的需要改吗? --> <!-- 2拖动后直接请求排序不用有个保存排序模型分类和排序分类里的模型交互有点不同哈
@芋艿 好像 yudao-ui-admin-vue3 交互也是这样的需要改吗? -->
<div class="flex items-center"> <div class="flex items-center">
<Tooltip v-if="isCategorySorting" title="拖动排序"> <Tooltip v-if="isCategorySorting" title="拖动排序">
<!-- drag-handle 标识可以拖动不能删掉 --> <!-- drag-handle 标识可以拖动不能删掉 -->

View File

@@ -0,0 +1 @@
export { default as ProcessExpressionSelectModal } from './select-modal.vue';

View File

@@ -1,11 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VxeGridPropTypes } from '#/adapter/vxe-table'; import type { VbenFormSchema } from '#/adapter/form';
import type {
VxeGridPropTypes,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression'; import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { CommonStatusEnum } from '@vben/constants'; import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getProcessExpressionPage } from '#/api/bpm/processExpression'; import { getProcessExpressionPage } from '#/api/bpm/processExpression';
@@ -16,35 +19,23 @@ const emit = defineEmits<{
select: [expression: BpmProcessExpressionApi.ProcessExpression]; select: [expression: BpmProcessExpressionApi.ProcessExpression];
}>(); }>();
// TODO @jason
//
const queryParams = ref({
status: CommonStatusEnum.ENABLE,
});
// VxeGrid // VxeGrid
const [Grid] = useVbenVxeGrid({ const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: { gridOptions: {
columns: [ columns: useGridColumns(),
{ field: 'name', title: '名字', minWidth: 160 },
{ field: 'expression', title: '表达式', minWidth: 260 },
{
field: 'action',
title: '操作',
width: 120,
slots: { default: 'action' },
},
],
showOverflow: true, showOverflow: true,
minHeight: 300, minHeight: 300,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
// //
query: async ({ page }) => { query: async ({ page }, formValues) => {
return await getProcessExpressionPage({ return await getProcessExpressionPage({
pageNo: page.currentPage, pageNo: page.currentPage,
pageSize: page.pageSize, pageSize: page.pageSize,
status: queryParams.value.status, ...formValues,
}); });
}, },
}, },
@@ -62,6 +53,7 @@ const [Grid] = useVbenVxeGrid({
// Modal // Modal
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
showConfirmButton: false, showConfirmButton: false,
contentClass: 'bg-background-deep p-3',
destroyOnClose: true, destroyOnClose: true,
}); });
@@ -70,6 +62,53 @@ function handleSelect(row: BpmProcessExpressionApi.ProcessExpression) {
emit('select', row); emit('select', row);
modalApi.close(); modalApi.close();
} }
/** 列表的搜索表单 */
function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'name',
label: '名字',
component: 'Input',
componentProps: {
placeholder: '请输入名字',
allowClear: true,
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
defaultValue: CommonStatusEnum.ENABLE,
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
disabled: true,
},
},
];
}
function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{ field: 'name', title: '名字', minWidth: 160 },
{ field: 'expression', title: '表达式', minWidth: 260 },
{
field: 'status',
title: '状态',
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
field: 'action',
title: '操作',
width: 120,
slots: { default: 'action' },
},
];
}
</script> </script>
<template> <template>

View File

@@ -169,7 +169,8 @@ async function initProcessInfo(row: any, formVariables?: any) {
path: row.formCustomCreatePath, path: row.formCustomCreatePath,
}); });
// 返回选择流程 // 返回选择流程
// TODO @jason这里为啥要有个 cancel 事件哈?目前看 vue3 + element-plus 貌似不需要呀; // 这里为啥要有个 cancel 事件哈?目前看 vue3 + element-plus 貌似不需要呀;
// @芋艿 不加貌似会有点问题。
emit('cancel'); emit('cancel');
} }
} }

View File

@@ -15,8 +15,7 @@ import {
cancelProcessInstanceByAdmin, cancelProcessInstanceByAdmin,
getProcessInstanceManagerPage, getProcessInstanceManagerPage,
} from '#/api/bpm/processInstance'; } from '#/api/bpm/processInstance';
// TODO @jason现在 ele 和 antd 使用的 parseFormFields 路径不同看看以哪个为主。ele 是 import { parseFormFields } from '#/components/form-create'; import { parseFormFields } from '#/components/form-create';
import { parseFormFields } from '#/views/bpm/components/simple-process-design';
import { useGridColumns, useGridFormSchema } from './data'; import { useGridColumns, useGridFormSchema } from './data';

View File

@@ -1,21 +1,32 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE } from '@vben/constants'; import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
/** 选择监听器弹窗的列表字段 */ /** 选择监听器弹窗的列表字段 */
export function useGridColumns(): VxeTableGridOptions['columns'] { export function useGridColumns(): VxeTableGridOptions['columns'] {
return [ return [
{ field: 'name', title: '名字', minWidth: 120 }, { field: 'name', title: '名字', minWidth: 160 },
{ {
field: 'type', field: 'type',
title: '类型', title: '类型',
minWidth: 200, minWidth: 120,
cellRender: { cellRender: {
name: 'CellDict', name: 'CellDict',
props: { type: DICT_TYPE.BPM_PROCESS_LISTENER_TYPE }, props: { type: DICT_TYPE.BPM_PROCESS_LISTENER_TYPE },
}, },
}, },
{ field: 'event', title: '事件', minWidth: 200 }, {
field: 'status',
title: '状态',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{ field: 'event', title: '事件', minWidth: 120 },
{ {
field: 'valueType', field: 'valueType',
title: '值类型', title: '值类型',
@@ -34,3 +45,29 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
}, },
]; ];
} }
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'name',
label: '名字',
component: 'Input',
componentProps: {
placeholder: '请输入名字',
allowClear: true,
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
defaultValue: CommonStatusEnum.ENABLE,
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
disabled: true,
},
},
];
}

View File

@@ -0,0 +1 @@
export { default as ProcessListenerSelectModal } from './select-modal.vue';

View File

@@ -5,12 +5,11 @@ import type { BpmProcessListenerApi } from '#/api/bpm/processListener';
import { ref } from 'vue'; import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { CommonStatusEnum } from '@vben/constants';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getProcessListenerPage } from '#/api/bpm/processListener'; import { getProcessListenerPage } from '#/api/bpm/processListener';
import { useGridColumns } from './data'; import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'ProcessListenerSelectModal' }); defineOptions({ name: 'ProcessListenerSelectModal' });
@@ -18,27 +17,25 @@ const emit = defineEmits<{
select: [listener: BpmProcessListenerApi.ProcessListener]; select: [listener: BpmProcessListenerApi.ProcessListener];
}>(); }>();
// TODO @jason const listenerType = ref('');
//
const queryParams = ref({
type: '',
status: CommonStatusEnum.ENABLE,
});
// VxeGrid // VxeGrid
const [Grid] = useVbenVxeGrid({ const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: { gridOptions: {
columns: useGridColumns(), columns: useGridColumns(),
showOverflow: true, showOverflow: true,
minHeight: 300, minHeight: 300,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
query: async ({ page }) => { query: async ({ page }, formValues) => {
return await getProcessListenerPage({ return await getProcessListenerPage({
pageNo: page.currentPage, pageNo: page.currentPage,
pageSize: page.pageSize, pageSize: page.pageSize,
type: queryParams.value.type, type: listenerType.value,
status: queryParams.value.status, ...formValues,
}); });
}, },
}, },
@@ -56,14 +53,15 @@ const [Grid] = useVbenVxeGrid({
// Modal // Modal
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
showConfirmButton: false, showConfirmButton: false,
contentClass: 'bg-background-deep p-3',
onOpenChange: async (isOpen: boolean) => { onOpenChange: async (isOpen: boolean) => {
if (!isOpen) { if (!isOpen) {
queryParams.value.type = ''; listenerType.value = '';
return; return;
} }
const data = modalApi.getData<{ type: string }>(); const data = modalApi.getData<{ type: string }>();
if (data?.type) { if (data?.type) {
queryParams.value.type = data.type; listenerType.value = data.type;
} }
}, },
destroyOnClose: true, destroyOnClose: true,