办学质量监测教学评价系统
du
16 小时以前 7bf1f40b58ee3c61664b5f16a84cbaac59f88735
流程图
已修改2个文件
已添加1个文件
511 ■■■■■ 文件已修改
ruoyi-ui/apps/web-antd/package.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/services/flowableService.js 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/apps/web-antd/src/views/system/process/index.vue 507 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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:",
ruoyi-ui/apps/web-antd/src/services/flowableService.js
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="请输入流程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: [
        {
          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
}
// 销毁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>