<template>
|
<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 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'
|
|
// 表格列定义
|
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
|
}
|
|
// 销毁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>
|