From 40cbfb5f891dbc0d4e2ea332919d85ad1c4c670a Mon Sep 17 00:00:00 2001 From: gnomeshgh <18153063927@163.com> Date: Fri, 3 Jul 2026 18:52:10 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(bpm)=EF=BC=9A=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E6=A8=A1=E5=9E=8B=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/api/bpm/model/index.ts | 11 ++ apps/web-antd/src/views/bpm/model/index.vue | 142 +++++++++++++++++++- 2 files changed, 151 insertions(+), 2 deletions(-) diff --git a/apps/web-antd/src/api/bpm/model/index.ts b/apps/web-antd/src/api/bpm/model/index.ts index e0c033a44..90a815be5 100644 --- a/apps/web-antd/src/api/bpm/model/index.ts +++ b/apps/web-antd/src/api/bpm/model/index.ts @@ -96,6 +96,17 @@ export async function createModel(data: BpmModelApi.Model) { return requestClient.post('/bpm/model/create', data); } +/** 导入流程模型 */ +export async function importModel(file: File, key?: string, name?: string) { + const formData = new FormData(); + formData.append('file', file); + if (key) formData.append('key', key); + if (name) formData.append('name', name); + return requestClient.post('/bpm/model/import', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); +} + /** 删除流程模型 */ export async function deleteModel(id: number) { return requestClient.delete(`/bpm/model/delete?id=${id}`); diff --git a/apps/web-antd/src/views/bpm/model/index.vue b/apps/web-antd/src/views/bpm/model/index.vue index 91c504f9e..6bea668d9 100644 --- a/apps/web-antd/src/views/bpm/model/index.vue +++ b/apps/web-antd/src/views/bpm/model/index.vue @@ -8,13 +8,23 @@ import { IconifyIcon } from '@vben/icons'; import { cloneDeep } from '@vben/utils'; import { useSortable } from '@vueuse/integrations/useSortable'; -import { Button, Card, Dropdown, Input, Menu, message } from 'ant-design-vue'; +import { + Alert, + Button, + Card, + Dropdown, + Form, + Input, + Menu, + message, + Upload, +} from 'ant-design-vue'; import { getCategorySimpleList, updateCategorySortBatch, } from '#/api/bpm/category'; -import { getModelList } from '#/api/bpm/model'; +import { getModelList, importModel as importModelApi } from '#/api/bpm/model'; import { router } from '#/router'; import CategoryForm from '../category/modules/form.vue'; @@ -35,6 +45,70 @@ const sortable = useTemplateRef('categoryGroupRef'); // 可以排 const sortableInstance = ref(null); // 排序引用,以便后续启用或禁用排序 const isCategorySorting = ref(false); // 分类排序状态 +// ========== 导入弹窗相关 ========== +const importFile = ref(null); // 导入文件(用于最终提交) +const importFileList = ref([]); // 上传组件的文件列表 +const importFormRef = ref(); +const importForm = reactive({ + key: '', + name: '', +}); + +const [ImportModal, importModalApi] = useVbenModal({ + async onConfirm() { + if (!importFile.value) { + message.warning('请上传流程模型文件'); + return; + } + await importFormRef.value?.validate(); + importModalApi.lock(); + try { + await importModelApi(importFile.value, importForm.key, importForm.name); + message.success('导入成功'); + await importModalApi.close(); + await getList(); + } catch { + // 全局 request 拦截器已处理错误提示 + } finally { + importModalApi.unlock(); + } + }, + async onOpenChange(isOpen: boolean) { + if (!isOpen) { + importFile.value = null; + importFileList.value = []; + importForm.key = ''; + importForm.name = ''; + } + }, +}); + +/** 上传前校验 + 读取文件自动填充字段 */ +function beforeUpload(file: File) { + const isJson = + file.type === 'application/json' || file.name.endsWith('.json'); + if (!isJson) { + message.error('仅支持上传 JSON 格式的流程模型文件'); + return Upload.LIST_IGNORE; + } + importFile.value = file; + file.text().then((text) => { + try { + const json = JSON.parse(text); + importForm.key = json.key || ''; + importForm.name = json.name || ''; + } catch { + message.error('JSON 文件格式不正确'); + } + }); + return false; // 阻止默认上传,不显示上传动画 +} + +/** 点击导入按钮 */ +function handleImportClick() { + importModalApi.open(); +} + const queryParams = reactive({ name: '', }); // 查询参数 @@ -161,6 +235,9 @@ async function handleCategorySortSubmit() { +