当前位置:首页>python>Python PyQt5 流程审批拖拽工具

Python PyQt5 流程审批拖拽工具

  • 2026-09-02 20:00:08
Python PyQt5 流程审批拖拽工具

做了一个开箱即用的流程审批拖拽工具,包含核心功能:

  • 左侧:审批节点库(发起人、审批人、抄送人、结束节点)
  • 中间:画布自由拖拽节点
  • 右键菜单:删除节点、清空画布
  • 顶部:保存流程(保存为JSON)、加载流程
  • 节点连线:自动连接前后节点,可视化审批流

直接复制代码就能运行,无需额外修改!

完整代码

import sysimport jsonfrom PyQt5.QtWidgets import *from PyQt5.QtGui import *from PyQt5.QtCore import *# 审批节点类型NODE_TYPES = {"start": {"name": "发起人", "color": "#4CAF50"},"approve": {"name": "审批人", "color": "#2196F3"},"copy": {"name": "抄送人", "color": "#FF9800"},"end": {"name": "结束", "color": "#F44336"}}# 可拖拽的节点列表项classNodeListWidget(QListWidget):def__init__(self, parent=None):        super().__init__(parent)        self.setDragEnabled(True)  # 允许拖拽        self.setFixedWidth(120)        self.add_nodes()defadd_nodes(self):# 添加所有审批节点for node_type, info in NODE_TYPES.items():            item = QListWidgetItem(info["name"])            item.setData(Qt.UserRole, node_type)# 设置节点样式            item.setBackground(QColor(info["color"]))            item.setForeground(QColor("white"))            item.setTextAlignment(Qt.AlignCenter)            self.addItem(item)defstartDrag(self, supportedActions):        drag = QDrag(self)        mimedata = QMimeData()# 获取选中的节点类型        current_item = self.currentItem()        node_type = current_item.data(Qt.UserRole)        mimedata.setText(node_type)        drag.setMimeData(mimedata)# 拖拽预览样式        pixmap = QPixmap(100, 40)        pixmap.fill(QColor(NODE_TYPES[node_type]["color"]))        painter = QPainter(pixmap)        painter.setPen(QColor("white"))        painter.drawText(pixmap.rect(), Qt.AlignCenter, current_item.text())        painter.end()        drag.setPixmap(pixmap)        drag.setHotSpot(QPoint(50, 20))        drag.exec_(Qt.CopyAction)# 画布上的节点控件classCanvasNode(QWidget):def__init__(self, node_type, parent=None):        super().__init__(parent)        self.node_type = node_type        self.info = NODE_TYPES[node_type]        self.setFixedSize(100, 40)        self.setCursor(Qt.OpenHandCursor)        self.setToolTip(f"{self.info['name']}\n右键可删除")defpaintEvent(self, event):        painter = QPainter(self)        painter.setRenderHint(QPainter.Antialiasing)# 绘制节点背景        painter.setBrush(QColor(self.info["color"]))        painter.setPen(Qt.NoPen)        painter.drawRoundedRect(self.rect(), 8, 8)# 绘制文字        painter.setPen(QColor("white"))        painter.setFont(QFont("Arial", 10, QFont.Bold))        painter.drawText(self.rect(), Qt.AlignCenter, self.info["name"])# 流程画布(支持拖拽、连线、右键菜单)classFlowCanvas(QWidget):    node_moved = pyqtSignal()  # 节点移动信号def__init__(self, parent=None):        super().__init__(parent)        self.setAcceptDrops(True)        self.nodes = []  # 所有节点        self.lines = []  # 所有连线        self.dragging_node = None        self.drag_offset = QPoint()        self.setStyleSheet("background-color: #f5f5f5;")defdragEnterEvent(self, event):if event.mimeData().hasText():            event.acceptProposedAction()defdropEvent(self, event):# 创建新节点        node_type = event.mimeData().text()        node = CanvasNode(node_type, self)        pos = event.pos() - QPoint(50, 20)        node.move(pos)        node.show()        self.nodes.append(node)        self.node_moved.emit()        event.acceptProposedAction()defmousePressEvent(self, event):# 右键菜单if event.button() == Qt.RightButton:            self.show_context_menu(event.pos())return# 左键拖动节点for node in self.nodes:if node.geometry().contains(event.pos()):                self.dragging_node = node                self.drag_offset = event.pos() - node.pos()                self.setCursor(Qt.ClosedHandCursor)breakdefmouseMoveEvent(self, event):if self.dragging_node:            self.dragging_node.move(event.pos() - self.drag_offset)            self.node_moved.emit()defmouseReleaseEvent(self, event):if self.dragging_node:            self.setCursor(Qt.OpenHandCursor)            self.dragging_node = NonedefpaintEvent(self, event):        super().paintEvent(event)        painter = QPainter(self)        painter.setRenderHint(QPainter.Antialiasing)        painter.setPen(QPen(QColor("#666"), 2))# 绘制节点连线(按顺序连接)for i in range(len(self.nodes)-1):            n1 = self.nodes[i]            n2 = self.nodes[i+1]            p1 = n1.pos() + QPoint(n1.width()//2, n1.height()//2)            p2 = n2.pos() + QPoint(n2.width()//2, n2.height()//2)            painter.drawLine(p1, p2)defshow_context_menu(self, pos):        menu = QMenu(self)# 删除选中节点        delete_action = menu.addAction("删除节点")# 清空画布        clear_action = menu.addAction("清空画布")        action = menu.exec_(self.mapToGlobal(pos))if action == delete_action:for node in self.nodes:if node.geometry().contains(pos):                    node.deleteLater()                    self.nodes.remove(node)                    self.node_moved.emit()                    self.update()returnelif action == clear_action:for node in self.nodes:                node.deleteLater()            self.nodes.clear()            self.update()# 主窗口classMainWindow(QMainWindow):def__init__(self):        super().__init__()        self.setWindowTitle("流程审批拖拽工具")        self.setGeometry(100, 100, 1000, 700)        self.init_ui()definit_ui(self):# 主布局        main_widget = QWidget()        self.setCentralWidget(main_widget)        layout = QHBoxLayout(main_widget)        layout.setContentsMargins(5,5,5,5)        layout.setSpacing(5)# 左侧节点库        self.node_list = NodeListWidget()        layout.addWidget(self.node_list)# 中间画布        self.canvas = FlowCanvas()        layout.addWidget(self.canvas)        self.canvas.node_moved.connect(self.canvas.update)# 顶部工具栏        self.init_toolbar()definit_toolbar(self):        toolbar = QToolBar()        self.addToolBar(toolbar)# 保存按钮        save_btn = QAction("保存流程", self)        save_btn.triggered.connect(self.save_flow)        toolbar.addAction(save_btn)# 加载按钮        load_btn = QAction("加载流程", self)        load_btn.triggered.connect(self.load_flow)        toolbar.addAction(load_btn)        toolbar.addSeparator()# 说明        toolbar.addWidget(QLabel("操作:左键拖拽 | 右键删除/清空"))defsave_flow(self):# 保存为JSON文件ifnot self.canvas.nodes:            QMessageBox.warning(self, "提示", "画布上没有流程节点!")return        path, _ = QFileDialog.getSaveFileName(self, "保存流程", "", "JSON Files (*.json)")ifnot path:return        data = {"nodes": [                {"type": node.node_type,"x": node.x(),"y": node.y()                } for node in self.canvas.nodes            ]        }with open(path, "w", encoding="utf-8") as f:            json.dump(data, f, ensure_ascii=False, indent=2)        QMessageBox.information(self, "成功", "流程已保存!")defload_flow(self):# 加载JSON流程        path, _ = QFileDialog.getOpenFileName(self, "加载流程", "", "JSON Files (*.json)")ifnot path:returntry:with open(path, "r", encoding="utf-8") as f:                data = json.load(f)# 清空画布for node in self.canvas.nodes:                node.deleteLater()            self.canvas.nodes.clear()            self.canvas.update()# 重建节点for item in data["nodes"]:                node = CanvasNode(item["type"], self.canvas)                node.move(item["x"], item["y"])                node.show()                self.canvas.nodes.append(node)            self.canvas.update()            QMessageBox.information(self, "成功", "流程加载完成!")except Exception as e:            QMessageBox.critical(self, "错误", f"加载失败:{str(e)}")if __name__ == "__main__":    app = QApplication(sys.argv)    window = MainWindow()    window.show()    sys.exit(app.exec_())

运行方法

  1. 先安装依赖:
pip install pyqt5
  1. 直接运行代码,窗口自动打开

功能说明

1. 拖拽使用

  • 从左侧列表选中节点,按住左键拖到中间画布松手即可
  • 在画布上按住节点左键可以自由移动位置
  • 节点会自动连线,按添加顺序形成审批流

2. 右键菜单

  • 在节点上右键 → 删除该节点
  • 在空白处右键 → 清空整个画布

3. 保存/加载

  • 点击顶部「保存流程」→ 导出为 JSON文件
  • 点击「加载流程」→ 导入之前保存的流程,自动恢复节点位置和类型

4. 节点类型

  • 🟢 发起人(开始)
  • 🔵 审批人
  • 🟠 抄送人
  • 🔴 结束

扩展建议(你可以轻松修改)

  1. 给节点添加编辑名称/审批人姓名功能
  2. 添加条件分支(网关节点)
  3. 导出为图片/PNG
  4. 连接数据库保存流程
  5. 添加节点选中高亮、撤销/重做

总结

这个工具已经实现了你要的拖拽、画布、保存、右键操作全部核心功能,代码结构清晰,直接可用,也方便你后续二次开发。

最新文章

随机文章