| | |
| | | <template> |
| | | <div style="padding: 20px"> |
| | | <a-card style="width: 100%" title="流程定义列表"> |
| | | <a-table :dataSource="dataSource" :columns="columns" /> |
| | | <div class="process-definition-container"> |
| | | <a-card title="流程定义列表" :bordered="false"> |
| | | <div class="table-actions"> |
| | | <a-button type="primary" @click="showCreateModal">新增流程图</a-button> |
| | | </div> |
| | | <a-table |
| | | :columns="columns" |
| | | :data-source="definitions" |
| | | :row-key="record => record.id" |
| | | :pagination="pagination" |
| | | :loading="loading" |
| | | @change="handleTableChange" |
| | | > |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="column.key === 'action'"> |
| | | <a-space> |
| | | <a-button type="link" @click="showDiagram(record)">查看</a-button> |
| | | <a-button type="link" @click="editDiagram(record)">修改</a-button> |
| | | <a-button type="link" danger @click="deleteDefinition(record)">删除</a-button> |
| | | </a-space> |
| | | </template> |
| | | </template> |
| | | </a-table> |
| | | </a-card> |
| | | |
| | | <!-- 流程图查看模态框 --> |
| | | <a-modal |
| | | v-model:visible="diagramVisible" |
| | | title="流程图查看" |
| | | width="80%" |
| | | :footer="null" |
| | | @cancel="handleDiagramCancel" |
| | | > |
| | | <div style="text-align: center"> |
| | | <img |
| | | v-if="currentDiagramUrl" |
| | | :src="currentDiagramUrl" |
| | | alt="流程图" |
| | | style="max-width: 100%" |
| | | /> |
| | | <a-skeleton v-else active /> |
| | | </div> |
| | | </a-modal> |
| | | |
| | | <!-- 流程图编辑模态框 --> |
| | | <a-modal |
| | | v-model:visible="editorVisible" |
| | | :title="editorTitle" |
| | | width="90%" |
| | | :maskClosable="false" |
| | | :okText="'保存'" |
| | | :cancelText="'取消'" |
| | | :confirmLoading="editorSaving" |
| | | @ok="handleEditorOk" |
| | | @cancel="handleEditorCancel" |
| | | :destroyOnClose="true" |
| | | :afterClose="handleEditorAfterClose" |
| | | :style="{ top: '20px' }" |
| | | :bodyStyle="{ |
| | | padding: '0', |
| | | height: 'calc(100vh - 100px)', |
| | | overflow: 'hidden', |
| | | display: 'flex', |
| | | flexDirection: 'column' |
| | | }" |
| | | > |
| | | <div class="editor-container"> |
| | | <div v-if="isCreateMode" class="create-form"> |
| | | <a-form layout="vertical"> |
| | | <a-form-item label="流程名称" required> |
| | | <a-input v-model:value="newProcess.name" placeholder="请输入流程名称" /> |
| | | </a-form-item> |
| | | <a-form-item label="流程Key" required> |
| | | <a-input v-model:value="newProcess.key" placeholder="请输入流程Key" /> |
| | | </a-form-item> |
| | | <a-form-item label="流程描述"> |
| | | <a-textarea v-model:value="newProcess.description" placeholder="请输入流程描述" /> |
| | | </a-form-item> |
| | | </a-form> |
| | | </div> |
| | | <div class="bpmn-editor" ref="bpmnEditor"></div> |
| | | </div> |
| | | </a-modal> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | setup() { |
| | | return { |
| | | dataSource: [ |
| | | { |
| | | key: '1', |
| | | name: '胡彦斌', |
| | | age: 32, |
| | | address: '西湖区湖底公园1号', |
| | | }, |
| | | { |
| | | key: '2', |
| | | name: '胡彦祖', |
| | | age: 42, |
| | | address: '西湖区湖底公园1号', |
| | | }, |
| | | ], |
| | | <script setup> |
| | | import { ref, onMounted, nextTick } from 'vue' |
| | | import { message, Modal } from 'ant-design-vue' |
| | | import BpmnModeler from 'bpmn-js/lib/Modeler' |
| | | import 'bpmn-js/dist/assets/diagram-js.css' |
| | | import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css' |
| | | import 'bpmn-js/dist/assets/bpmn-js.css' |
| | | |
| | | columns: [ |
| | | // 表格列定义 |
| | | const columns = [ |
| | | { |
| | | title: '姓名', |
| | | title: 'ID', |
| | | dataIndex: 'id', |
| | | key: 'id', |
| | | ellipsis: true |
| | | }, |
| | | { |
| | | title: '名称', |
| | | dataIndex: 'name', |
| | | key: 'name', |
| | | align: 'center', |
| | | key: 'name' |
| | | }, |
| | | { |
| | | title: '年龄', |
| | | dataIndex: 'age', |
| | | key: 'age', |
| | | align: 'center', |
| | | title: 'Key', |
| | | dataIndex: 'key', |
| | | key: 'key' |
| | | }, |
| | | { |
| | | title: '住址', |
| | | dataIndex: 'address', |
| | | key: 'address', |
| | | align: 'center', |
| | | }, |
| | | ], |
| | | }; |
| | | }, |
| | | }; |
| | | title: '操作', |
| | | key: 'action', |
| | | } |
| | | ] |
| | | |
| | | // 数据状态 |
| | | const definitions = ref([]) |
| | | const loading = ref(false) |
| | | const pagination = ref({ |
| | | current: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | showSizeChanger: true, |
| | | pageSizeOptions: ['10', '20', '50'] |
| | | }) |
| | | |
| | | // 流程图查看相关状态 |
| | | const diagramVisible = ref(false) |
| | | const currentDiagramUrl = ref('') |
| | | const currentProcessDefinition = ref(null) |
| | | |
| | | // 流程图编辑相关状态 |
| | | const editorVisible = ref(false) |
| | | const editorSaving = ref(false) |
| | | const editorTitle = ref('流程图编辑') |
| | | const bpmnModeler = ref(null) |
| | | const bpmnEditor = ref(null) |
| | | const isCreateMode = ref(false) |
| | | const newProcess = ref({ |
| | | name: '', |
| | | key: '', |
| | | description: '' |
| | | }) |
| | | |
| | | // 获取流程定义列表 |
| | | const fetchProcessDefinitions = async (params = {}) => { |
| | | loading.value = true |
| | | try { |
| | | // 模拟数据 |
| | | const mockData = [ |
| | | { id: '1', name: '请假流程', key: 'leaveProcess'}, |
| | | { id: '2', name: '报销流程', key: 'expenseProcess'}, |
| | | { id: '3', name: '采购流程', key: 'purchaseProcess' } |
| | | ] |
| | | // 模拟分页 |
| | | const start = (params.page - 1) * params.size |
| | | const end = start + params.size |
| | | definitions.value = mockData.slice(start, end) |
| | | pagination.value.total = mockData.length |
| | | } catch (error) { |
| | | message.error('加载流程定义失败:'+ error.message) |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // 表格分页/排序变化处理 |
| | | const handleTableChange = (pag, filters, sorter) => { |
| | | const params = { |
| | | page: pag.current, |
| | | size: pag.pageSize |
| | | } |
| | | |
| | | if (sorter.field) { |
| | | params.sort = sorter.field |
| | | params.order = sorter.order === 'ascend' ? 'asc' : 'desc' |
| | | } |
| | | |
| | | fetchProcessDefinitions(params) |
| | | } |
| | | |
| | | // 显示流程图 |
| | | const showDiagram = (record) => { |
| | | currentProcessDefinition.value = record |
| | | currentDiagramUrl.value = `https://via.placeholder.com/800x600?text=流程图+${record.id}` |
| | | diagramVisible.value = true |
| | | } |
| | | |
| | | // 销毁BPMN编辑器 |
| | | const destroyBpmnEditor = () => { |
| | | if (bpmnModeler.value) { |
| | | bpmnModeler.value.destroy() |
| | | bpmnModeler.value = null |
| | | } |
| | | } |
| | | |
| | | // 初始化BPMN编辑器 |
| | | const initBpmnEditor = async (xml) => { |
| | | await nextTick() |
| | | |
| | | // 先销毁旧的编辑器 |
| | | destroyBpmnEditor() |
| | | |
| | | try { |
| | | // 创建新的编辑器实例 |
| | | bpmnModeler.value = new BpmnModeler({ |
| | | container: bpmnEditor.value, |
| | | }) |
| | | |
| | | // 加载XML或默认流程图 |
| | | const diagram = xml || ` |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <bpmn:definitions |
| | | xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" |
| | | xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" |
| | | xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" |
| | | xmlns:di="http://www.omg.org/spec/DD/20100524/DI" |
| | | id="Definitions_1" |
| | | targetNamespace="http://bpmn.io/schema/bpmn"> |
| | | <bpmn:process id="Process_1" isExecutable="false"> |
| | | <bpmn:startEvent id="StartEvent_1" /> |
| | | </bpmn:process> |
| | | <bpmndi:BPMNDiagram id="BPMNDiagram_1"> |
| | | <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1"> |
| | | <bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1"> |
| | | <dc:Bounds x="173" y="102" width="36" height="36" /> |
| | | </bpmndi:BPMNShape> |
| | | </bpmndi:BPMNPlane> |
| | | </bpmndi:BPMNDiagram> |
| | | </bpmn:definitions> |
| | | ` |
| | | |
| | | await bpmnModeler.value.importXML(diagram) |
| | | } catch (err) { |
| | | console.error('Error rendering diagram', err) |
| | | message.error('初始化流程图编辑器失败: ' + err.message) |
| | | } |
| | | } |
| | | |
| | | // 显示创建新流程模态框 |
| | | const showCreateModal = () => { |
| | | isCreateMode.value = true |
| | | editorTitle.value = '创建新流程图' |
| | | editorVisible.value = true |
| | | |
| | | // 重置表单 |
| | | newProcess.value = { |
| | | name: '', |
| | | key: '', |
| | | description: '' |
| | | } |
| | | |
| | | // 初始化编辑器 |
| | | initBpmnEditor() |
| | | } |
| | | |
| | | // 编辑流程图 |
| | | const editDiagram = async (record) => { |
| | | isCreateMode.value = false |
| | | currentProcessDefinition.value = record |
| | | editorTitle.value = `编辑流程 - ${record.name}` |
| | | editorVisible.value = true |
| | | |
| | | // 模拟加载流程定义XML |
| | | const mockXml = ` |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <bpmn:definitions |
| | | xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" |
| | | xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" |
| | | xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" |
| | | xmlns:di="http://www.omg.org/spec/DD/20100524/DI" |
| | | id="Definitions_1" |
| | | targetNamespace="http://bpmn.io/schema/bpmn"> |
| | | <bpmn:process id="${record.id}" name="${record.name}" isExecutable="true"> |
| | | <bpmn:startEvent id="StartEvent_1" name="开始" /> |
| | | <bpmn:userTask id="UserTask_1" name="提交申请" /> |
| | | <bpmn:sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="UserTask_1" /> |
| | | <bpmn:endEvent id="EndEvent_1" name="结束" /> |
| | | <bpmn:sequenceFlow id="Flow_2" sourceRef="UserTask_1" targetRef="EndEvent_1" /> |
| | | </bpmn:process> |
| | | <bpmndi:BPMNDiagram id="BPMNDiagram_1"> |
| | | <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${record.id}"> |
| | | <bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1"> |
| | | <dc:Bounds x="173" y="102" width="36" height="36" /> |
| | | </bpmndi:BPMNShape> |
| | | <bpmndi:BPMNShape id="UserTask_1_di" bpmnElement="UserTask_1"> |
| | | <dc:Bounds x="280" y="80" width="100" height="80" /> |
| | | </bpmndi:BPMNShape> |
| | | <bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1"> |
| | | <dc:Bounds x="450" y="102" width="36" height="36" /> |
| | | </bpmndi:BPMNShape> |
| | | <bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1"> |
| | | <di:waypoint x="209" y="120" /> |
| | | <di:waypoint x="280" y="120" /> |
| | | </bpmndi:BPMNEdge> |
| | | <bpmndi:BPMNEdge id="Flow_2_di" bpmnElement="Flow_2"> |
| | | <di:waypoint x="380" y="120" /> |
| | | <di:waypoint x="450" y="120" /> |
| | | </bpmndi:BPMNEdge> |
| | | </bpmndi:BPMNPlane> |
| | | </bpmndi:BPMNDiagram> |
| | | </bpmn:definitions> |
| | | ` |
| | | |
| | | // 初始化编辑器并加载XML |
| | | initBpmnEditor(mockXml) |
| | | } |
| | | |
| | | // 保存流程图修改 |
| | | const handleEditorOk = async () => { |
| | | editorSaving.value = true |
| | | |
| | | try { |
| | | if (isCreateMode.value) { |
| | | // 验证表单 |
| | | if (!newProcess.value.name || !newProcess.value.key) { |
| | | message.error('请填写流程名称和Key') |
| | | return |
| | | } |
| | | |
| | | // 获取XML |
| | | const { xml } = await bpmnModeler.value.saveXML({ format: true }) |
| | | console.log('新流程XML:', xml) |
| | | |
| | | // 模拟创建新流程 |
| | | await new Promise(resolve => setTimeout(resolve, 1000)) |
| | | |
| | | // 添加到列表 |
| | | const newId = Math.max(...definitions.value.map(d => parseInt(d.id))) + 1 |
| | | definitions.value.unshift({ |
| | | id: newId.toString(), |
| | | name: newProcess.value.name, |
| | | key: newProcess.value.key, |
| | | version: 1 |
| | | }) |
| | | |
| | | message.success('新流程创建成功') |
| | | } else { |
| | | // 获取修改后的XML |
| | | const { xml } = await bpmnModeler.value.saveXML({ format: true }) |
| | | console.log('修改后的XML:', xml) |
| | | |
| | | // 模拟保存 |
| | | await new Promise(resolve => setTimeout(resolve, 1000)) |
| | | |
| | | message.success('流程图保存成功') |
| | | } |
| | | |
| | | editorVisible.value = false |
| | | } catch (error) { |
| | | console.error('Error saving BPMN diagram', error) |
| | | message.error('操作失败: ' + error.message) |
| | | } finally { |
| | | editorSaving.value = false |
| | | } |
| | | } |
| | | |
| | | // 删除流程定义 |
| | | const deleteDefinition = (record) => { |
| | | Modal.confirm({ |
| | | title: '确认删除流程?', |
| | | content: `确定要删除流程 "${record.name}" 吗?此操作不可恢复。`, |
| | | okText: '确认', |
| | | okType: 'danger', |
| | | cancelText: '取消', |
| | | onOk() { |
| | | // 模拟删除 |
| | | definitions.value = definitions.value.filter(item => item.id !== record.id) |
| | | message.success('流程删除成功') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 模态框关闭后清理 |
| | | const handleEditorAfterClose = () => { |
| | | destroyBpmnEditor() |
| | | } |
| | | |
| | | const handleEditorCancel = () => { |
| | | editorVisible.value = false |
| | | } |
| | | |
| | | const handleDiagramCancel = () => { |
| | | diagramVisible.value = false |
| | | } |
| | | |
| | | // 初始化加载数据 |
| | | onMounted(() => { |
| | | const params = { |
| | | page: 1, |
| | | size: 10 |
| | | } |
| | | fetchProcessDefinitions(params) |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .process-definition-container { |
| | | padding: 20px; |
| | | background: #fff; |
| | | } |
| | | |
| | | .table-actions { |
| | | margin-bottom: 16px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .editor-container { |
| | | height: 600px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | .bpmn-editor { |
| | | flex: 1; |
| | | border: 1px solid #d9d9d9; |
| | | border-radius: 2px; |
| | | margin-top: 16px; |
| | | min-height: 500px; /* 确保编辑器有足够高度 */ |
| | | } |
| | | |
| | | .create-form { |
| | | padding: 16px; |
| | | background: #fafafa; |
| | | border-radius: 2px; |
| | | border: 1px solid #d9d9d9; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | /* 隐藏BPMN水印 */ |
| | | .bpmn-editor :deep(.bjs-powered-by) { |
| | | display: none !important; |
| | | } |
| | | .editor-container { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .bpmn-editor { |
| | | flex: 1; |
| | | min-height: 0; /* 重要:允许flex容器收缩 */ |
| | | border: 1px solid #d9d9d9; |
| | | border-radius: 2px; |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .create-form { |
| | | padding: 16px; |
| | | background: #fafafa; |
| | | border-radius: 2px; |
| | | border: 1px solid #d9d9d9; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | /* 隐藏BPMN水印 */ |
| | | .bpmn-editor :deep(.bjs-powered-by) { |
| | | display: none !important; |
| | | } |
| | | |
| | | /* 确保BPMN工具栏可见 */ |
| | | .bpmn-editor :deep(.djs-palette) { |
| | | top: 20px; |
| | | left: 20px; |
| | | } |
| | | </style> |