From 7bf1f40b58ee3c61664b5f16a84cbaac59f88735 Mon Sep 17 00:00:00 2001 From: du <13220750630.163.com> Date: 星期一, 23 六月 2025 15:00:28 +0800 Subject: [PATCH] 流程图 --- ruoyi-ui/apps/web-antd/src/views/system/process/index.vue | 507 +++++++++++++++++++++++++++++++++++++++++++++++++++----- ruoyi-ui/apps/web-antd/src/services/flowableService.js | 0 ruoyi-ui/apps/web-antd/package.json | 4 3 files changed, 466 insertions(+), 45 deletions(-) diff --git a/ruoyi-ui/apps/web-antd/package.json b/ruoyi-ui/apps/web-antd/package.json index 2138a2a..7336cf0 100644 --- a/ruoyi-ui/apps/web-antd/package.json +++ b/ruoyi-ui/apps/web-antd/package.json @@ -46,14 +46,18 @@ "@vben/utils": "workspace:*", "@vueuse/core": "catalog:", "ant-design-vue": "catalog:", + "axios": "^1.10.0", + "bpmn-js": "^18.6.2", "cropperjs": "^1.6.2", "crypto-js": "^4.2.0", "dayjs": "catalog:", + "diagram-js": "^15.3.0", "echarts": "^5.5.1", "element-plus": "^2.10.2", "jsencrypt": "^3.3.2", "lodash-es": "^4.17.21", "pinia": "catalog:", + "qs": "^6.13.1", "tinymce": "^7.3.0", "unplugin-vue-components": "^0.27.3", "vue": "catalog:", diff --git a/ruoyi-ui/apps/web-antd/src/services/flowableService.js b/ruoyi-ui/apps/web-antd/src/services/flowableService.js new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/services/flowableService.js diff --git a/ruoyi-ui/apps/web-antd/src/views/system/process/index.vue b/ruoyi-ui/apps/web-antd/src/views/system/process/index.vue index 279ebf2..fd598fd 100644 --- a/ruoyi-ui/apps/web-antd/src/views/system/process/index.vue +++ b/ruoyi-ui/apps/web-antd/src/views/system/process/index.vue @@ -1,55 +1,472 @@ <template> -<div style="padding: 20px"> - <a-card style="width: 100%" title="娴佺▼瀹氫箟鍒楄〃"> - <a-table :dataSource="dataSource" :columns="columns" /> - </a-card> -</div> + <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="璇疯緭鍏ユ祦绋婯ey" /> + </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: [ - { - title: '濮撳悕', - dataIndex: 'name', - key: 'name', - align: 'center', - }, - { - title: '骞撮緞', - dataIndex: 'age', - key: 'age', - align: 'center', - }, - { - title: '浣忓潃', - dataIndex: 'address', - key: 'address', - align: 'center', - }, - ], - }; +// 琛ㄦ牸鍒楀畾涔� +const columns = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + ellipsis: true }, -}; + { + title: '鍚嶇О', + dataIndex: 'name', + key: 'name' + }, + { + title: 'Key', + dataIndex: 'key', + key: 'key' + }, + { + 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 +} + +// 閿�姣丅PMN缂栬緫鍣� +const destroyBpmnEditor = () => { + if (bpmnModeler.value) { + bpmnModeler.value.destroy() + bpmnModeler.value = null + } +} + +// 鍒濆鍖朆PMN缂栬緫鍣� +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> + ` + + // 鍒濆鍖栫紪杈戝櫒骞跺姞杞絏ML + 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('鏂版祦绋媂ML:', 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; /* 閲嶈锛氬厑璁竑lex瀹瑰櫒鏀剁缉 */ + 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> -- Gitblit v1.9.3