From 4f65156a9579f8e13f0b010b126c60f6baeacea1 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Mon, 25 Aug 2025 09:42:18 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20[BPM=20=E5=B7=A5=E4=BD=9C=E6=B5=81]?= =?UTF-8?q?=20=E6=B5=81=E7=A8=8B=E8=A1=A8=E5=8D=95=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E8=B0=83=E6=95=B4=E4=B8=BA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=BA=93=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/utils/formCreate.ts | 7 ++++--- .../views/bpm/processInstance/create/modules/form.vue | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/web-antd/src/utils/formCreate.ts b/apps/web-antd/src/utils/formCreate.ts index a653a44bb..070b394ad 100644 --- a/apps/web-antd/src/utils/formCreate.ts +++ b/apps/web-antd/src/utils/formCreate.ts @@ -4,6 +4,7 @@ // TODO @芋艿:后续这些 form-create 的优化;另外需要使用 form-create-helper 会好点 import { isRef } from 'vue'; +import formCreate from '@form-create/ant-design-vue'; // 编码表单 Conf export const encodeConf = (designerRef: any) => { return JSON.stringify(designerRef.value.getOption()); @@ -23,7 +24,7 @@ export const encodeFields = (designerRef: any) => { export const decodeFields = (fields: string[]) => { const rule: object[] = []; fields.forEach((item) => { - rule.push(JSON.parse(item)); + rule.push(formCreate.parseJson(item)); }); return rule; }; @@ -34,7 +35,7 @@ export const setConfAndFields = ( conf: string, fields: string | string[], ) => { - designerRef.value.setOption(JSON.parse(conf)); + designerRef.value.setOption(formCreate.parseJson(conf)); // 处理 fields 参数类型,确保传入 decodeFields 的是 string[] 类型 const fieldsArray = Array.isArray(fields) ? fields : [fields]; designerRef.value.setRule(decodeFields(fieldsArray)); @@ -50,7 +51,7 @@ export const setConfAndFields2 = ( if (isRef(detailPreview)) { detailPreview = detailPreview.value; } - detailPreview.option = JSON.parse(conf); + detailPreview.option = formCreate.parseJson(conf); detailPreview.rule = decodeFields(fields); if (value) { detailPreview.value = value; diff --git a/apps/web-antd/src/views/bpm/processInstance/create/modules/form.vue b/apps/web-antd/src/views/bpm/processInstance/create/modules/form.vue index b91202d1c..29474006e 100644 --- a/apps/web-antd/src/views/bpm/processInstance/create/modules/form.vue +++ b/apps/web-antd/src/views/bpm/processInstance/create/modules/form.vue @@ -7,6 +7,7 @@ import { computed, nextTick, ref, watch } from 'vue'; import { useTabs } from '@vben/hooks'; import { IconifyIcon } from '@vben/icons'; +import formCreate from '@form-create/ant-design-vue'; import { Button, Card, Col, message, Row, Space, Tabs } from 'ant-design-vue'; import { getProcessDefinition } from '#/api/bpm/definition'; @@ -126,11 +127,11 @@ async function initProcessInfo(row: any, formVariables?: any) { // 注意:需要从 formVariables 中,移除不在 row.formFields 的值。 // 原因是:后端返回的 formVariables 里面,会有一些非表单的信息。例如说,某个流程节点的审批人。 // 这样,就可能导致一个流程被审批不通过后,重新发起时,会直接后端报错!!! - const allowedFields = new Set( - decodeFields(row.formFields).map((fieldObj: any) => fieldObj.field), - ); + const formApi = formCreate.create(decodeFields(row.formFields)); + const allowedFields = formApi.fields(); + console.error('allowedFields===>', allowedFields); for (const key in formVariables) { - if (!allowedFields.has(key)) { + if (!allowedFields.includes(key)) { delete formVariables[key]; } } From e43bb989b353806da0b2c6681e275c5b54fbfea3 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Mon, 25 Aug 2025 10:17:19 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E5=8E=BB=E6=8E=89=20console=20?= =?UTF-8?q?=E6=89=93=E5=8D=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/bpm/processInstance/create/modules/form.vue | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/web-antd/src/views/bpm/processInstance/create/modules/form.vue b/apps/web-antd/src/views/bpm/processInstance/create/modules/form.vue index b818ebf63..43a331aca 100644 --- a/apps/web-antd/src/views/bpm/processInstance/create/modules/form.vue +++ b/apps/web-antd/src/views/bpm/processInstance/create/modules/form.vue @@ -52,7 +52,7 @@ const props = defineProps({ const emit = defineEmits(['cancel']); // 增加表单就绪状态变量 表单就绪后再渲染form-create -const isFormReady = ref(false) +const isFormReady = ref(false); const { closeCurrentTab } = useTabs(); @@ -132,7 +132,6 @@ async function initProcessInfo(row: any, formVariables?: any) { // 这样,就可能导致一个流程被审批不通过后,重新发起时,会直接后端报错!!! const formApi = formCreate.create(decodeFields(row.formFields)); const allowedFields = formApi.fields(); - console.error('allowedFields===>', allowedFields); for (const key in formVariables) { if (!allowedFields.includes(key)) { delete formVariables[key]; @@ -141,7 +140,7 @@ async function initProcessInfo(row: any, formVariables?: any) { setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables); // 设置表单就绪状态 - isFormReady.value = true + isFormReady.value = true; await nextTick(); fApi.value?.btn.show(false); // 隐藏提交按钮 From e01fc275e1464e99a4fd48708867fbb767a71e66 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 26 Oct 2025 09:10:52 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20[antd]->[bpm]=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E8=AF=84=E5=AE=A1=E4=BF=AE=E6=94=B9=2020%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/category-draggable-model.vue | 136 ++++++++++++------ .../bpm/processInstance/detail/index.vue | 44 +++--- .../detail/modules/operation-button.vue | 3 +- 3 files changed, 107 insertions(+), 76 deletions(-) diff --git a/apps/web-antd/src/views/bpm/model/modules/category-draggable-model.vue b/apps/web-antd/src/views/bpm/model/modules/category-draggable-model.vue index db06edeee..202ecaf8a 100644 --- a/apps/web-antd/src/views/bpm/model/modules/category-draggable-model.vue +++ b/apps/web-antd/src/views/bpm/model/modules/category-draggable-model.vue @@ -154,13 +154,17 @@ function handleModelSort() { /** 处理模型的排序提交 */ async function handleModelSortSubmit() { - // TODO @jason:loading 加一下,体验好点。 + // 确保数据已经正确同步 + if (!modelList.value || modelList.value.length === 0) { + message.error('排序数据异常,请重试'); + return; + } + + const hideLoading = message.loading({ + content: '正在保存排序...', + duration: 0, + }); try { - // 确保数据已经正确同步 - if (!modelList.value || modelList.value.length === 0) { - message.error('排序数据异常,请重试'); - return; - } // 保存排序 const ids = modelList.value.map((item) => item.id); await updateModelSortBatch(ids); @@ -170,6 +174,8 @@ async function handleModelSortSubmit() { emit('success'); } catch (error) { console.error('排序保存失败', error); + } finally { + hideLoading(); } } @@ -207,18 +213,29 @@ async function handleDeleteCategory() { return; } - // TODO @jason:改成 await;然后增加一个 loading; - confirm({ + await confirm({ + beforeClose: async ({ isConfirm }) => { + if (!isConfirm) return; + // 发起删除 + const hideLoading = message.loading({ + content: `正在删除分类: "${props.categoryInfo.name}"...`, + duration: 0, + }); + try { + await deleteCategory(props.categoryInfo.id); + } finally { + hideLoading(); + } + return true; + }, content: `确定要删除[${props.categoryInfo.name}]吗?`, - }).then(async () => { - // 发起删除 - await deleteCategory(props.categoryInfo.id); - message.success( - $t('ui.actionMessage.deleteSuccess', [props.categoryInfo.name]), - ); - // 刷新列表 - emit('success'); + icon: 'question', }); + message.success( + $t('ui.actionMessage.deleteSuccess', [props.categoryInfo.name]), + ); + // 刷新列表 + emit('success'); } /** 处理表单详情点击 */ @@ -249,21 +266,27 @@ async function modelOperation(type: string, id: number) { /** 发布流程 */ async function handleDeploy(row: any) { - // TODO @jason:改成 await;然后增加一个 loading; - confirm({ + await confirm({ beforeClose: async ({ isConfirm }) => { if (!isConfirm) return; // 发起部署 - await deployModel(row.id); + const hideLoading = message.loading({ + content: `正在发布流程: "${row.name}"...`, + duration: 0, + }); + try { + await deployModel(row.id); + } finally { + hideLoading(); + } return true; }, content: `确认要发布[${row.name}]流程吗?`, icon: 'question', - }).then(async () => { - message.success(`发布[${row.name}]流程成功`); - // 刷新列表 - emit('success'); }); + message.success(`发布[${row.name}]流程成功`); + // 刷新列表 + emit('success'); } /** '更多'操作按钮 */ @@ -300,63 +323,82 @@ function handleModelCommand(command: string, row: any) { } /** 更新状态操作 */ -function handleChangeState(row: any) { +async function handleChangeState(row: any) { const state = row.processDefinition.suspensionState; const newState = state === 1 ? 2 : 1; const statusState = state === 1 ? '停用' : '启用'; - // TODO @jason:改成 await;然后增加一个 loading; - confirm({ + await confirm({ beforeClose: async ({ isConfirm }) => { if (!isConfirm) return; // 发起更新状态 - await updateModelState(row.id, newState); + const hideLoading = message.loading({ + content: `正在${statusState}流程: "${row.name}"...`, + duration: 0, + }); + try { + await updateModelState(row.id, newState); + } finally { + hideLoading(); + } return true; }, content: `确认要${statusState}流程: "${row.name}" 吗?`, icon: 'question', - }).then(async () => { - message.success(`${statusState} 流程: "${row.name}" 成功`); - // 刷新列表 - emit('success'); }); + message.success(`${statusState} 流程: "${row.name}" 成功`); + // 刷新列表 + emit('success'); } /** 清理流程操作 */ -function handleClean(row: any) { - // TODO @jason:改成 await;然后增加一个 loading; - confirm({ +async function handleClean(row: any) { + await confirm({ beforeClose: async ({ isConfirm }) => { if (!isConfirm) return; // 发起清理操作 - await cleanModel(row.id); + const hideLoading = message.loading({ + content: `正在清理流程: "${row.name}"...`, + duration: 0, + }); + try { + await cleanModel(row.id); + } finally { + hideLoading(); + } return true; }, content: `确认要清理流程: "${row.name}" 吗?`, icon: 'question', - }).then(async () => { - message.success(`清理流程: "${row.name}" 成功`); - // 刷新列表 - emit('success'); }); + message.success(`清理流程: "${row.name}" 成功`); + // 刷新列表 + emit('success'); } /** 删除流程操作 */ -function handleDelete(row: any) { - // TODO @jason:改成 await;然后增加一个 loading; - confirm({ +async function handleDelete(row: any) { + await confirm({ beforeClose: async ({ isConfirm }) => { if (!isConfirm) return; // 发起删除操作 - await deleteModel(row.id); + const hideLoading = message.loading({ + content: $t('ui.actionMessage.deleting', [row.name]), + duration: 0, + }); + try { + await deleteModel(row.id); + } finally { + hideLoading(); + } return true; }, content: `确认要删除流程: "${row.name}" 吗?`, icon: 'question', - }).then(async () => { - message.success(`删除流程: "${row.name}" 成功`); - // 刷新列表 - emit('success'); }); + + message.success(`删除流程: "${row.name}" 成功`); + // 刷新列表 + emit('success'); } /** 跳转到指定流程定义列表 */ diff --git a/apps/web-antd/src/views/bpm/processInstance/detail/index.vue b/apps/web-antd/src/views/bpm/processInstance/detail/index.vue index 6f60bf4c6..578f9aaaf 100644 --- a/apps/web-antd/src/views/bpm/processInstance/detail/index.vue +++ b/apps/web-antd/src/views/bpm/processInstance/detail/index.vue @@ -6,6 +6,7 @@ import { nextTick, onMounted, ref, shallowRef, watch } from 'vue'; import { Page } from '@vben/common-ui'; import { + BpmFieldPermissionType, BpmModelFormType, BpmModelType, BpmTaskStatusEnum, @@ -44,13 +45,6 @@ const props = defineProps<{ taskId?: string; // 任务编号 }>(); -// TODO @jason:是不是使用全局的 FieldPermissionType?export enum FieldPermissionType { -enum FieldPermissionType { - NONE = '3', // 隐藏 - READ = '1', // 只读 - WRITE = '2', // 编辑 -} - const processInstanceLoading = ref(false); // 流程实例的加载中 const processInstance = ref(); // 流程实例 const processDefinition = ref({}); // 流程定义 @@ -131,18 +125,16 @@ async function getApprovalDetail() { processInstance.value.formVariables, ); } - // TODO @jason:这里 await 来搞? - nextTick().then(() => { - fApi.value?.btn.show(false); - fApi.value?.resetBtn.show(false); - fApi.value?.disabled(true); - // 设置表单字段权限 - if (formFieldsPermission) { - Object.keys(data.formFieldsPermission).forEach((item) => { - setFieldPermission(item, formFieldsPermission[item]); - }); - } - }); + await nextTick(); + fApi.value?.btn.show(false); + fApi.value?.resetBtn.show(false); + fApi.value?.disabled(true); + // 设置表单字段权限 + if (formFieldsPermission) { + Object.keys(data.formFieldsPermission).forEach((item) => { + setFieldPermission(item, formFieldsPermission[item]); + }); + } } else { // 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue BusinessFormComponent.value = registerComponent( @@ -178,15 +170,15 @@ async function getProcessModelView() { /** 设置表单权限 */ function setFieldPermission(field: string, permission: string) { - if (permission === FieldPermissionType.READ) { + if (permission === BpmFieldPermissionType.READ) { fApi.value?.disabled(true, field); } - if (permission === FieldPermissionType.WRITE) { + if (permission === BpmFieldPermissionType.WRITE) { fApi.value?.disabled(false, field); // 加入可以编辑的字段 writableFields.push(field); } - if (permission === FieldPermissionType.NONE) { + if (permission === BpmFieldPermissionType.NONE) { fApi.value?.hidden(true, field); } } @@ -203,13 +195,11 @@ function setFieldPermission(field: string, permission: string) { /** 监听 Tab 切换,当切换到 "record" 标签时刷新任务列表 */ watch( () => activeTab.value, - (newVal) => { + async (newVal) => { if (newVal === 'record') { // 如果切换到流转记录标签,刷新任务列表 - // TODO @jason:await nextTick 要不? - nextTick(() => { - taskListRef.value?.refresh(); - }); + await nextTick(); + taskListRef.value?.refresh(); } }, ); diff --git a/apps/web-antd/src/views/bpm/processInstance/detail/modules/operation-button.vue b/apps/web-antd/src/views/bpm/processInstance/detail/modules/operation-button.vue index 14179827e..b6ab093fd 100644 --- a/apps/web-antd/src/views/bpm/processInstance/detail/modules/operation-button.vue +++ b/apps/web-antd/src/views/bpm/processInstance/detail/modules/operation-button.vue @@ -548,9 +548,8 @@ async function handleCancel() { /** 处理再次提交 */ async function handleReCreate() { // 跳转发起流程界面 - // TODO @jason:这个要优化成 push 到 name 么?这样后续 path 可以按需调整; await router.push({ - path: '/bpm/task/create', + name: 'BpmProcessInstanceCreate', query: { processInstanceId: props.processInstance?.id }, }); } From 66dbf16a77fadcc71dcb1c5ffacb0cf8935f9871 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 26 Oct 2025 23:19:01 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20[antd]=20[bpm]=20=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=8E=92=E5=BA=8F=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/views/bpm/model/index.vue | 74 +++++++++---------- .../modules/category-draggable-model.vue | 26 ++++--- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/apps/web-antd/src/views/bpm/model/index.vue b/apps/web-antd/src/views/bpm/model/index.vue index 08568e1ec..91c504f9e 100644 --- a/apps/web-antd/src/views/bpm/model/index.vue +++ b/apps/web-antd/src/views/bpm/model/index.vue @@ -150,44 +150,44 @@ async function handleCategorySortSubmit() { v-spinning="modelListSpinning" > + + + + +
@@ -199,7 +199,7 @@ async function handleCategorySortSubmit() { 保存排序
- +
diff --git a/apps/web-antd/src/views/bpm/model/modules/category-draggable-model.vue b/apps/web-antd/src/views/bpm/model/modules/category-draggable-model.vue index 202ecaf8a..e285077d2 100644 --- a/apps/web-antd/src/views/bpm/model/modules/category-draggable-model.vue +++ b/apps/web-antd/src/views/bpm/model/modules/category-draggable-model.vue @@ -12,7 +12,6 @@ import { IconifyIcon } from '@vben/icons'; import { useUserStore } from '@vben/stores'; import { cloneDeep, formatDateTime, isEqual } from '@vben/utils'; -// TODO @jason:某个模型下的排序功能,无法使用。操作流程:模型分类 A 的右侧【排序】,然后,无法拖拽使用; import { useDebounceFn } from '@vueuse/core'; import { useSortable } from '@vueuse/integrations/useSortable'; import { @@ -65,7 +64,7 @@ const userId = useUserStore().userInfo?.id; const isModelSorting = ref(false); const originalData = ref([]); const modelList = ref([]); -// TODO @jason:可以全部展开么? +// TODO @jason:可以全部展开么? @芋艿 上次讨论。好像是因为性能问题才只展开第一个分类 const isExpand = ref(props.isFirst); // 根据是否为第一个分类, 来设置初始展开状态 const sortableInstance = ref(null); // 排序引用,以便后续启用或禁用排序 @@ -464,12 +463,14 @@ function handleRenameSuccess() { >
- +
- + +
{{ categoryInfo.name }} @@ -559,15 +560,16 @@ function handleRenameSuccess() { :class="`category-${categoryInfo.id}`" >