SPLITTER AND CONTAINER的用法

本文介绍在SAP开发中如何利用SPLITTER实现复杂的ALV功能,包括自定义屏幕、创建splitter容器及ALV控件、布局ALV等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SPLITTER AND CONTAINER的用法

 

SPLITTER的使用

在SAP开发过程中,我们有时候要实现一些复杂的ALV功能,这个时候FUNCTION ALV就显得不那么灵活方便

这个时候,我们可以采用OO去实现

OO实现的基本步骤

1、首先创建一个自定义屏幕,在屏幕上画一个自定义控件,这里的自定义控件是根据实际业务需要来定大小,并

起一个名字,比如G_CONTAINER,屏幕就画好了

2、开始在程序里具体去实现这个ALV

  一个ALV的组成,我们都比较熟悉,最基本的需要当然是要有fieldcat 和 layout了,这个只是针对基本的查询、

展示功能。然而我们通常要去实现一些复杂业务场景下的功能,这个时候就要添加events了。比如:user_command,

data_change等常用事件,有了这些基本的认识之后,我们下面来具体实现这个ALV

  一个业务功能的首界面通常都是,查询条件的展示,这个我们根据实际所需

  1)自定义好查询界面之后,点击执行,这个时候就调用一个ALV展示界面,如下图所示

这张图只是截取了ALV的展示部分,同时这张图是同时实现了两个ALV的展示,所以在我们起初画的自定义控件上,如何

更好的布局这两个ALV的展示,使其外观美丽大方就显得尤为重要,这里我们就用到了splitter

splitter汉语解释是,分隔器,用它来分隔一个屏幕上的多个ALV展示,让人看着舒服很多

  2)这个ALV屏幕的初始化展示,通常放在事务码的initialization事件当中,也就是说,每次输入完查询条件之后,执

行我们就看到这个屏幕的展示,接下来数据的具体展示就在这张ALV上进行功能操作

上图这个FORM 具体就是用来实现这个ALV的步骤

  3)先去创建一个splitter容器,传入我们起初屏幕上画的自定义控件参数,即就是说我们要在这个自定义控件里来进行整体分割

  

  这里我们来针对这个自定义控件,创建一个splitter分割容器

  4)

  这个form是针对已经创建的splitter容器,进行创建splitter控件,由上图我们可以看到,我们此时是将这个自定义

控件分割成两行一列,就是上下各展示一个ALV

  这样我们就将屏幕分割完成了,接下来开始布局ALV容器和控件

  5)

  这里我们可以看到,是在上面已经分割好的容器里,去开始创建一个ALV容器,在创建的时候,我们必须传递创建的位置坐标,

同时也看到,p_splitter里专门有创建ALV容器的方法,get_container()

  6)在创建好一个ALV容器之后,接下来创建ALV控件

  

  7) 为ALV添加事件响应,如果一个屏幕上有多个ALV展示,我们可以完全调用这同一个form

  

  这里的有一个g_event_receiver_1事件接收器,我们必须先事先定义

  8)这里是我们自定义的一个事件接收器类,里面声明三个方法,分别是,data_changed\user_command

  \toolbar事件

  

  9)下图是对这些方法的具体实现

  

  这里只是做测试,具体的实现没有去写

  同时,定义g_event_receiver_1这个对象,注意这里必须先声明,方可定义,这里是用面向对象的思想,就不过多赘述了

  10)下图是针对ALV上的菜单栏的添加,这里只是截取了一部分,在这里主要是进行隐藏,实际情况

  可以根据具体业务进行增减

  

ALV上的菜单栏做好后,下来开始设置layout\fieldcat这两个基本的组件了

  11)layout\fieldcat 设置如下图所示

  

  

  这里的gi_fcat_1-edit = 'X',是用来设置ALV的选择框

  12)接下来是最后的展示,将我们前面所有的准备做一个展示

  

  13)在起初的屏幕PBO里做最后的ALV刷新

  

  

  以上就是实现一个OO复杂的ALV的基本步骤,这里只是梳理的一个实现的思路,代码通过调试验证。

  当然方法还有更好的,欢迎一起深入研究

转载于:https://www.cnblogs.com/bailang-LBG/articles/4230602.html

import sys import json import os import copy from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QListWidget, QLabel, QDialog, QLineEdit, QTextEdit, QComboBox, QMessageBox, QTabWidget, QToolBar, QAction, QTreeWidget, QTreeWidgetItem, QSplitter, QStatusBar, QToolButton, QStackedWidget, QDateEdit, QListWidgetItem, QCheckBox, QFormLayout, QScrollArea, QSizePolicy) from PyQt5.QtCore import Qt, QSize, QDate from PyQt5.QtGui import QIcon, QFont, QColor, QPalette, QBrush, QLinearGradient class WorkflowManager(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("工作流程管理器") self.resize(1000, 650) self.workflows = [] self.current_workflow = None self.current_stage_index = 0 self.load_workflows() # 设置主窗口样式 self.setStyleSheet(""" QMainWindow { background-color: #f8f9fa; font-family: 'Segoe UI', 'Arial', sans-serif; } QLabel { font-size: 12px; } QPushButton { padding: 6px 12px; border-radius: 4px; font-weight: 500; } QListWidget { background-color: white; border: 1px solid #e3e6f0; border-radius: 4px; } """) # 创建主界面 self.create_main_ui() # 加载默认工作流程 if self.workflows: self.current_workflow = self.workflows[0] self.update_workflow_display() def filter_artifacts(self): """过滤成果物""" if not self.current_workflow or self.current_stage_index < 0: return search_text = self.artifacts_search.text().lower() current_stage = self.current_workflow["stages"][self.current_stage_index] for i in range(self.artifacts_list.count()): item = self.artifacts_list.item(i) widget = self.artifacts_list.itemWidget(item) # 获取复选框文本 checkbox = widget.findChild(QCheckBox) if checkbox: artifact_name = checkbox.text().lower() # 根据搜索文本显示/隐藏 item.setHidden(search_text not in artifact_name and search_text != "") def toggle_select_all(self): """切换全选/全不选状态""" if not self.current_workflow or self.current_stage_index < 0: return current_stage = self.current_workflow["stages"][self.current_stage_index] artifacts = current_stage.get("artifacts", []) if not artifacts: return # 检查当前是否全部已选 all_selected = all(a["completed"] for a in artifacts) new_state = not all_selected # 更新所有成果物状态 for i in range(self.artifacts_list.count()): item = self.artifacts_list.item(i) widget = self.artifacts_list.itemWidget(item) checkbox = widget.findChild(QCheckBox) if checkbox: checkbox.setChecked(new_state) # 更新数据 for artifact in artifacts: if artifact["name"] == checkbox.text(): artifact["completed"] = new_state # 更新按钮文本 self.select_all_btn.setText("全不选" if new_state else "全选") self.update_stage_status() def update_stage_status(self): """更新阶段状态 - 添加进度条显示""" # ... [原有代码] ... # 添加进度条显示 if total_artifacts > 0: progress_width = int((completed_artifacts / total_artifacts) * 100) self.progress_bar.setStyleSheet(f""" QWidget {{ background-color: #eaecf4; border-radius: 4px; }} QWidget::before {{ content: ""; display: block; background-color: {status_color}; width: {progress_width}%; height: 100%; border-radius: 4px; }} """) def create_main_ui(self): """创建主界面 - 修复了中央部件设置问题""" # 创建中央部件 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) main_layout.setContentsMargins(15, 15, 15, 15) # 创建工具栏 toolbar = QToolBar("工具栏") toolbar.setIconSize(QSize(24, 24)) self.addToolBar(Qt.TopToolBarArea, toolbar) # 添加工具栏动作 new_workflow_action = QAction(QIcon(), "新建工作流程", self) new_workflow_action.triggered.connect(self.new_workflow) toolbar.addAction(new_workflow_action) edit_workflow_action = QAction(QIcon(), "编辑工作流程", self) edit_workflow_action.triggered.connect(self.edit_workflow) toolbar.addAction(edit_workflow_action) delete_workflow_action = QAction(QIcon(), "删除工作流程", self) delete_workflow_action.triggered.connect(self.delete_workflow) toolbar.addAction(delete_workflow_action) toolbar.addSeparator() stats_action = QAction(QIcon(), "统计信息", self) stats_action.triggered.connect(self.show_stats) toolbar.addAction(stats_action) # 创建主分割器 main_splitter = QSplitter(Qt.Horizontal) main_layout.addWidget(main_splitter, 1) # 左侧面板(工作流程列表) left_panel = QWidget() left_layout = QVBoxLayout(left_panel) left_layout.setContentsMargins(0, 0, 0, 0) workflows_label = QLabel("工作流程列表") workflows_label.setStyleSheet("font-size: 14px; font-weight: 600; color: #4e73df; margin-bottom: 8px;") left_layout.addWidget(workflows_label) self.workflows_tree = QTreeWidget() self.workflows_tree.setHeaderHidden(True) self.workflows_tree.setStyleSheet(""" QTreeWidget { border: 1px solid #e3e6f0; border-radius: 4px; background-color: white; } """) self.workflows_tree.itemClicked.connect(self.select_workflow) left_layout.addWidget(self.workflows_tree) # 更新工作流程树 self.update_workflows_tree() main_splitter.addWidget(left_panel) # 右侧面板(工作流程详情) right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(0, 0, 0, 0) # 工作流程标题 self.flow_title = QLabel("无选中的工作流程") self.flow_title.setStyleSheet("font-size: 16px; font-weight: 600; color: #4e73df; margin-bottom: 15px;") right_layout.addWidget(self.flow_title) # 流程阶段显示区域 self.flow_stages = QHBoxLayout() self.flow_stages.setSpacing(15) self.flow_stages.setContentsMargins(0, 0, 0, 0) right_layout.addLayout(self.flow_stages) # 成果物区域 - 优化后的代码 artifacts_container = QWidget() artifacts_container.setStyleSheet("background-color: white; border-radius: 6px; padding: 15px;") artifacts_layout = QVBoxLayout(artifacts_container) # 添加搜索框 search_container = QHBoxLayout() search_container.setContentsMargins(0, 0, 0, 10) search_label = QLabel("搜索成果物:") search_label.setStyleSheet("font-size: 12px;") search_container.addWidget(search_label) self.artifacts_search = QLineEdit() self.artifacts_search.setPlaceholderText("输入关键词过滤...") self.artifacts_search.setStyleSheet("padding: 5px; border: 1px solid #d1d3e2; border-radius: 4px;") self.artifacts_search.textChanged.connect(self.filter_artifacts) search_container.addWidget(self.artifacts_search) artifacts_layout.addLayout(search_container) # 成果物标题 artifacts_title_layout = QHBoxLayout() self.artifacts_label = QLabel("成果物检查") self.artifacts_label.setStyleSheet("font-size: 14px; font-weight: 600; color: #4e73df;") artifacts_title_layout.addWidget(self.artifacts_label) # 添加全选/全不选按钮 self.select_all_btn = QPushButton("全选") self.select_all_btn.setStyleSheet(""" background-color: #4e73df; color: white; padding: 3px 8px; border-radius: 3px; font-size: 11px; """) self.select_all_btn.clicked.connect(self.toggle_select_all) artifacts_title_layout.addWidget(self.select_all_btn) artifacts_layout.addLayout(artifacts_title_layout) # 使用滚动区域包裹成果物列表 scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setStyleSheet("border: none;") scroll_area.setMinimumHeight(200) # 设置最小高度 scroll_content = QWidget() scroll_layout = QVBoxLayout(scroll_content) scroll_layout.setContentsMargins(0, 0, 0, 0) self.artifacts_list = QListWidget() self.artifacts_list.setStyleSheet(""" QListWidget { border: none; background-color: white; } QListWidget::item { border-bottom: 1px solid #eaecf4; } QListWidget::item:selected { background-color: transparent; } """) self.artifacts_list.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) scroll_layout.addWidget(self.artifacts_list) scroll_area.setWidget(scroll_content) artifacts_layout.addWidget(scroll_area, 1) # 设置弹性系数为1 # 状态信息 - 优化显示 status_container = QWidget() status_container.setStyleSheet("background-color: #f8f9fa; border-radius: 4px; padding: 8px;") status_layout = QVBoxLayout(status_container) self.status_info = QLabel("") self.status_info.setAlignment(Qt.AlignCenter) self.status_info.setStyleSheet("font-weight: 500;") status_layout.addWidget(self.status_info) # 添加进度条 self.progress_bar = QWidget() self.progress_bar.setFixedHeight(8) self.progress_bar.setStyleSheet("background-color: #eaecf4; border-radius: 4px;") status_layout.addWidget(self.progress_bar) artifacts_layout.addWidget(status_container) # 按钮区域 btn_layout = QHBoxLayout() btn_layout.setSpacing(10) self.prev_stage_btn = QPushButton("上一步") self.prev_stage_btn.setStyleSheet("background-color: #6c757d; color: white; padding: 8px;") self.prev_stage_btn.clicked.connect(self.prev_stage) btn_layout.addWidget(self.prev_stage_btn) self.next_stage_btn = QPushButton("下一步") self.next_stage_btn.setStyleSheet("background-color: #4e73df; color: white; padding: 8px;") self.next_stage_btn.clicked.connect(self.next_stage) btn_layout.addWidget(self.next_stage_btn) btn_layout.addStretch() right_layout.addLayout(btn_layout) main_splitter.addWidget(right_panel) main_splitter.setSizes([200, 600]) # 状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪", 5000) # 更新按钮状态 self.update_buttons_state() def update_workflows_tree(self): """更新工作流程树""" self.workflows_tree.clear() for workflow in self.workflows: item = QTreeWidgetItem(self.workflows_tree) item.setText(0, workflow["name"]) item.setData(0, Qt.UserRole, workflow) item.setToolTip(0, workflow["description"]) if self.workflows: self.workflows_tree.setCurrentItem(self.workflows_tree.topLevelItem(0)) def load_workflows(self): """加载工作流程并处理兼容性""" try: if os.path.exists("workflows.json"): with open("workflows.json", "r") as f: self.workflows = json.load(f) # 兼容旧数据结构 for workflow in self.workflows: for stage in workflow["stages"]: if stage.get("artifacts") and isinstance(stage["artifacts"][0], str): stage["artifacts"] = [{"name": name, "completed": False} for name in stage["artifacts"]] else: # 创建默认工作流程 default_workflow = { "name": "软件开发变更流程", "description": "标准的软件开发变更管理流程", "stages": [ { "name": "变更点分析", "description": "分析客户提供的变更需求", "color": "#4e73df", "artifacts": [{"name": "需求分析文档", "completed": False}], "completed": False }, { "name": "设计", "description": "设计系统架构和接口", "color": "#1cc88a", "artifacts": [ {"name": "系统设计文档", "completed": False}, {"name": "接口规范", "completed": False} ], "completed": False }, { "name": "实现", "description": "编写代码实现功能", "color": "#36b9cc", "artifacts": [{"name": "源代码", "completed": False}], "completed": False }, { "name": "测试", "description": "执行单元测试和集成测试", "color": "#f6c23e", "artifacts": [ {"name": "测试计划", "completed": False}, {"name": "测试报告", "completed": False} ], "completed": False }, { "name": "部署", "description": "部署应用到生产环境", "color": "#e74a3b", "artifacts": [{"name": "部署文档", "completed": False}], "completed": False } ], "tasks": [] } self.workflows = [default_workflow] self.save_workflows() except Exception as e: QMessageBox.critical(self, "加载错误", f"加载工作流程时出错: {str(e)}") self.workflows = [] def save_workflows(self): """保存工作流程""" try: with open("workflows.json", "w") as f: json.dump(self.workflows, f, indent=4) self.status_bar.showMessage("工作流程已保存", 2000) except Exception as e: QMessageBox.critical(self, "保存错误", f"保存工作流程时出错: {str(e)}") def update_workflow_display(self): """更新工作流程显示""" if not self.current_workflow: # 清空显示 self.flow_title.setText("无选中的工作流程") self.clear_flow_stages() self.artifacts_label.setText("成果物检查") self.artifacts_list.clear() self.status_info.setText("") self.update_buttons_state() return # 更新标题 self.flow_title.setText(f"{self.current_workflow['name']} - {self.current_workflow['description']}") # 清空现有阶段显示 self.clear_flow_stages() # 添加阶段 for i, stage in enumerate(self.current_workflow["stages"]): stage_widget = self.create_stage_widget(stage, i) self.flow_stages.addWidget(stage_widget) # 更新成果物显示 self.update_artifacts_display() # 更新按钮状态 self.update_buttons_state() def clear_flow_stages(self): """清空流程阶段显示""" while self.flow_stages.count(): item = self.flow_stages.takeAt(0) if item.widget(): item.widget().deleteLater() def create_stage_widget(self, stage, index): """创建单个阶段显示组件""" widget = QWidget() widget.setMinimumWidth(120) widget.setMaximumWidth(180) layout = QVBoxLayout(widget) layout.setContentsMargins(10, 10, 10, 10) # 设置背景色 bg_color = stage["color"] if index == self.current_stage_index: # 当前阶段使用更深的颜色 bg_color = self.darken_color(bg_color, 20) widget.setStyleSheet(f""" background-color: {bg_color}; border-radius: 8px; color: white; font-weight: 500; """) # 阶段名称 name_label = QLabel(stage["name"]) name_label.setStyleSheet("font-size: 14px; font-weight: 600;") name_label.setAlignment(Qt.AlignCenter) layout.addWidget(name_label) # 阶段描述 desc_label = QLabel(stage["description"]) desc_label.setStyleSheet("font-size: 11px;") desc_label.setWordWrap(True) desc_label.setAlignment(Qt.AlignCenter) layout.addWidget(desc_label) # 阶段状态 status = "已完成" if stage["completed"] else "进行中" if index == self.current_stage_index else "未开始" status_label = QLabel(status) status_label.setStyleSheet("font-size: 12px; font-weight: 500; margin-top: 8px;") status_label.setAlignment(Qt.AlignCenter) layout.addWidget(status_label) # 点击事件 widget.mousePressEvent = lambda event, idx=index: self.select_stage(idx) return widget def darken_color(self, hex_color, percent): """使颜色变暗指定百分比""" # 移除#号 hex_color = hex_color.lstrip('#') # 转换为RGB r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) # 计算变暗值 r = max(0, int(r * (1 - percent / 100))) g = max(0, int(g * (1 - percent / 100))) b = max(0, int(b * (1 - percent / 100))) # 返回新颜色 return f"#{r:02x}{g:02x}{b:02x}" def select_workflow(self, item): """选择工作流程""" if not item: return self.current_workflow = item.data(0, Qt.UserRole) self.current_stage_index = 0 self.update_workflow_display() self.update_artifacts_display() def select_stage(self, index): """选择阶段""" if not self.current_workflow or index < 0 or index >= len(self.current_workflow["stages"]): return self.current_stage_index = index self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"已选择阶段: {self.current_workflow['stages'][index]['name']}", 2000) def prev_stage(self): """返回上一阶段""" if not self.current_workflow or self.current_stage_index <= 0: return self.current_stage_index -= 1 self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"已返回阶段: {self.current_workflow['stages'][self.current_stage_index]['name']}", 2000) def next_stage(self): """进入下一阶段""" if not self.current_workflow or self.current_stage_index >= len(self.current_workflow["stages"]) - 1: return # 检查当前阶段是否完成 current_stage = self.current_workflow["stages"][self.current_stage_index] artifacts = current_stage.get("artifacts", []) total_artifacts = len(artifacts) completed_artifacts = sum(1 for a in artifacts if a["completed"]) if total_artifacts > 0 and completed_artifacts < total_artifacts: QMessageBox.warning(self, "无法继续", "请先完成当前阶段的所有成果物!") return self.current_stage_index += 1 self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"已进入阶段: {self.current_workflow['stages'][self.current_stage_index]['name']}", 2000) def update_buttons_state(self): """更新按钮状态""" if not self.current_workflow: self.prev_stage_btn.setEnabled(False) self.next_stage_btn.setEnabled(False) return # 上一阶段按钮 self.prev_stage_btn.setEnabled(self.current_stage_index > 0) # 下一阶段按钮 self.next_stage_btn.setEnabled(self.current_stage_index < len(self.current_workflow["stages"]) - 1) def new_workflow(self): """新建工作流程""" dialog = WorkflowDialog(self) if dialog.exec_() == QDialog.Accepted: new_workflow = dialog.get_workflow() if new_workflow: self.workflows.append(new_workflow) self.current_workflow = new_workflow self.current_stage_index = 0 self.save_workflows() self.update_workflows_tree() self.update_workflow_display() self.update_artifacts_display() def edit_workflow(self): """编辑工作流程""" if not self.current_workflow: QMessageBox.warning(self, "警告", "请先选择一个工作流程!") return dialog = WorkflowDialog(self, self.current_workflow) if dialog.exec_() == QDialog.Accepted: edited_workflow = dialog.get_workflow() if edited_workflow: index = self.workflows.index(self.current_workflow) self.workflows[index] = edited_workflow self.current_workflow = edited_workflow self.save_workflows() self.update_workflows_tree() self.update_workflow_display() self.update_artifacts_display() def delete_workflow(self): """删除当前工作流程""" if not self.current_workflow: QMessageBox.warning(self, "警告", "请先选择一个工作流程!") return reply = QMessageBox.question( self, '确认删除', f"确定要删除工作流程【{self.current_workflow['name']}】吗?\n此操作不可恢复!", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: try: # 从工作流程列表中移除当前工作流程 self.workflows.remove(self.current_workflow) # 更新当前选中的工作流程 if self.workflows: self.current_workflow = self.workflows[0] self.current_stage_index = 0 else: self.current_workflow = None self.current_stage_index = -1 # 保存更改并更新界面 self.save_workflows() self.update_workflows_tree() self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"工作流程已删除", 2000) except Exception as e: QMessageBox.critical(self, "删除错误", f"删除工作流程时出错: {str(e)}") def update_artifacts_display(self): """更新成果物显示并恢复状态""" self.artifacts_list.clear() if not self.current_workflow or self.current_stage_index < 0: self.artifacts_label.setText("成果物检查") self.status_info.setText("") return try: current_stage = self.current_workflow["stages"][self.current_stage_index] self.artifacts_label.setText(f"成果物检查 - {current_stage['name']}") for artifact in current_stage.get("artifacts", []): item = QListWidgetItem() widget = QWidget() layout = QHBoxLayout(widget) layout.setContentsMargins(10, 5, 10, 5) checkbox = QCheckBox(artifact["name"]) checkbox.setChecked(artifact["completed"]) checkbox.setStyleSheet("font-size: 19px;") checkbox.stateChanged.connect(lambda state, a=artifact: self.update_artifact_status(a, state)) layout.addWidget(checkbox) item.setSizeHint(widget.sizeHint()) self.artifacts_list.addItem(item) self.artifacts_list.setItemWidget(item, widget) # 更新状态信息 self.update_stage_status() except Exception as e: print(f"更新成果物显示时出错: {e}") def update_artifact_status(self, artifact, state): """更新成果物状态""" if not self.current_workflow: return try: artifact["completed"] = (state == Qt.Checked) self.update_stage_status() except Exception as e: print(f"更新成果物状态时出错: {e}") def update_stage_status(self): """更新阶段状态""" if not self.current_workflow or self.current_stage_index < 0: return try: current_stage = self.current_workflow["stages"][self.current_stage_index] artifacts = current_stage.get("artifacts", []) total_artifacts = len(artifacts) completed_artifacts = sum(1 for a in artifacts if a["completed"]) # 更新状态文本 if total_artifacts == 0: status_text = "当前阶段没有成果物" status_color = "#6c757d" elif completed_artifacts == 0: status_text = "状态: 待开始 (0% 完成)" status_color = "#e74a3b" current_stage["completed"] = False elif completed_artifacts < total_artifacts: percent = (completed_artifacts / total_artifacts) * 100 status_text = f"状态: 进行中 ({percent:.0f}% 完成)" status_color = "#f6c23e" current_stage["completed"] = False else: status_text = "状态: 已完成 (100% 完成)" status_color = "#1cc88a" current_stage["completed"] = True self.status_info.setText(status_text) self.status_info.setStyleSheet(f""" font-size: 12px; font-weight: 500; color: white; background-color: {status_color}; padding: 6px; border-radius: 4px; margin-top: 8px; """) self.update_buttons_state() except Exception as e: print(f"更新阶段状态时出错: {e}") def show_stats(self): """显示统计信息""" if not self.current_workflow: return # 计算统计数据 total_stages = len(self.current_workflow["stages"]) completed_stages = sum(1 for stage in self.current_workflow["stages"] if stage["completed"]) progress = (completed_stages / total_stages) * 100 if total_stages > 0 else 0 # 创建统计对话框 stats_dialog = QDialog(self) stats_dialog.setWindowTitle("流程统计") stats_dialog.resize(400, 300) layout = QVBoxLayout(stats_dialog) # 标题 title = QLabel(f"{self.current_workflow['name']} - 统计信息") title.setStyleSheet("font-size: 16px; font-weight: 600; color: #4e73df;") layout.addWidget(title) # 进度条 progress_bar = QWidget() progress_bar.setFixedHeight(30) progress_bar.setStyleSheet(f""" background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #1cc88a, stop:{progress/100} #1cc88a, stop:{progress/100} #e74a3b, stop:1 #e74a3b); border-radius: 4px; """) layout.addWidget(progress_bar) # 进度文本 progress_text = QLabel(f"整体进度: {progress:.1f}% ({completed_stages}/{total_stages} 阶段完成)") progress_text.setAlignment(Qt.AlignCenter) layout.addWidget(progress_text) # 阶段详情 stages_label = QLabel("阶段完成情况:") stages_label.setStyleSheet("font-weight: 600; margin-top: 15px;") layout.addWidget(stages_label) stages_list = QListWidget() for stage in self.current_workflow["stages"]: status = "✓ 已完成" if stage["completed"] else "✗ 未完成" item = QListWidgetItem(f"{stage['name']} - {status}") item.setForeground(QColor("#1cc88a" if stage["completed"] else "#e74a3b")) stages_list.addItem(item) layout.addWidget(stages_list) stats_dialog.exec_() class WorkflowDialog(QDialog): """工作流程编辑对话框""" def __init__(self, parent=None, workflow=None): super().__init__(parent) self.setWindowTitle("编辑工作流程" if workflow else "新建工作流程") self.resize(600, 450) # 深拷贝工作流程数据,避免直接修改原始数据 self.workflow = copy.deepcopy(workflow) if workflow else { "name": "", "description": "", "stages": [], "tasks": [] } self.setup_ui() def setup_ui(self): """设置对话框UI""" layout = QVBoxLayout(self) layout.setContentsMargins(15, 15, 15, 15) # 标题 title_text = "编辑工作流程" if self.workflow.get("name") else "新建工作流程" title = QLabel(title_text) title.setStyleSheet("font-size: 16px; font-weight: 600; color: #4e73df; margin-bottom: 15px;") layout.addWidget(title) # 基本信息 form_layout = QFormLayout() form_layout.setLabelAlignment(Qt.AlignRight) form_layout.setVerticalSpacing(10) self.name_edit = QLineEdit(self.workflow["name"]) self.name_edit.setPlaceholderText("输入工作流程名称") form_layout.addRow("流程名称:", self.name_edit) self.desc_edit = QTextEdit(self.workflow["description"]) self.desc_edit.setPlaceholderText("输入工作流程描述") self.desc_edit.setFixedHeight(60) form_layout.addRow("流程描述:", self.desc_edit) layout.addLayout(form_layout) # 阶段管理 stages_label = QLabel("阶段管理") stages_label.setStyleSheet("font-size: 14px; font-weight: 600; color: #4e73df; margin-top: 15px; margin-bottom: 8px;") layout.addWidget(stages_label) # 阶段列表和编辑区 stages_container = QHBoxLayout() stages_container.setSpacing(15) # 阶段列表 stages_list_container = QVBoxLayout() self.stages_list = QListWidget() self.stages_list.setFixedHeight(150) self.update_stages_list() self.stages_list.itemSelectionChanged.connect(self.on_stage_selection_changed) stages_list_container.addWidget(self.stages_list) stages_container.addLayout(stages_list_container, 1) # 阶段编辑区 stage_edit_container = QVBoxLayout() self.stage_edit = QWidget() self.stage_edit.setStyleSheet("background-color: #f8f9fa; border-radius: 6px; padding: 10px;") stage_edit_layout = QFormLayout(self.stage_edit) stage_edit_layout.setVerticalSpacing(8) self.stage_name_edit = QLineEdit() self.stage_name_edit.setPlaceholderText("输入阶段名称") stage_edit_layout.addRow("阶段名称:", self.stage_name_edit) self.stage_desc_edit = QTextEdit() self.stage_desc_edit.setPlaceholderText("输入阶段描述") self.stage_desc_edit.setFixedHeight(50) stage_edit_layout.addRow("阶段描述:", self.stage_desc_edit) self.stage_color_edit = QComboBox() self.stage_color_edit.addItems(["#4e73df", "#1cc88a", "#36b9cc", "#f6c23e", "#e74a3b", "#6f42c1", "#fd7e14", "#20c997"]) stage_edit_layout.addRow("阶段颜色:", self.stage_color_edit) # 成果物编辑 artifacts_label = QLabel("成果物 (每行一个):") stage_edit_layout.addRow(artifacts_label) self.artifacts_edit = QTextEdit() self.artifacts_edit.setPlaceholderText("输入成果物名称,每行一个") self.artifacts_edit.setFixedHeight(80) stage_edit_layout.addRow(self.artifacts_edit) stage_edit_container.addWidget(self.stage_edit) # 阶段操作按钮 stage_btn_layout = QHBoxLayout() stage_btn_layout.setSpacing(8) self.add_stage_btn = QPushButton("添加") self.add_stage_btn.setStyleSheet("background-color: #1cc88a; color: white; padding: 5px;") self.add_stage_btn.clicked.connect(self.add_stage) stage_btn_layout.addWidget(self.add_stage_btn) self.update_stage_btn = QPushButton("更新") self.update_stage_btn.setStyleSheet("background-color: #4e73df; color: white; padding: 5px;") self.update_stage_btn.clicked.connect(self.update_current_stage) stage_btn_layout.addWidget(self.update_stage_btn) self.remove_stage_btn = QPushButton("删除") self.remove_stage_btn.setStyleSheet("background-color: #e74a3b; color: white; padding: 5px;") self.remove_stage_btn.clicked.connect(self.remove_stage) stage_btn_layout.addWidget(self.remove_stage_btn) stage_edit_container.addLayout(stage_btn_layout) stages_container.addLayout(stage_edit_container, 2) layout.addLayout(stages_container) # 对话框按钮 btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignRight) btn_layout.setSpacing(10) save_btn = QPushButton("保存") save_btn.setStyleSheet("background-color: #4e73df; color: white; min-width: 80px; padding: 6px;") save_btn.clicked.connect(self.on_save) btn_layout.addWidget(save_btn) cancel_btn = QPushButton("取消") cancel_btn.setStyleSheet("background-color: #6c757d; color: white; min-width: 80px; padding: 6px;") cancel_btn.clicked.connect(self.reject) btn_layout.addWidget(cancel_btn) layout.addLayout(btn_layout) # 初始化选择 if self.stages_list.count() > 0: self.stages_list.setCurrentRow(0) def update_stages_list(self): """更新阶段列表显示""" self.stages_list.clear() for stage in self.workflow["stages"]: item = QListWidgetItem(stage["name"]) item.setData(Qt.UserRole, stage) self.stages_list.addItem(item) def on_stage_selection_changed(self): """当阶段选择变化时更新编辑区域""" current_item = self.stages_list.currentItem() if current_item: self.select_stage(current_item) else: self.clear_stage_edit() def select_stage(self, item): """加载选中阶段的数据到编辑区域""" stage = item.data(Qt.UserRole) self.stage_name_edit.setText(stage["name"]) self.stage_desc_edit.setText(stage["description"]) # 设置颜色 color_index = self.stage_color_edit.findText(stage["color"]) if color_index >= 0: self.stage_color_edit.setCurrentIndex(color_index) # 加载成果物 artifact_names = [a["name"] for a in stage.get("artifacts", [])] self.artifacts_edit.setText("\n".join(artifact_names)) def add_stage(self): """添加新阶段""" name = self.stage_name_edit.text().strip() if not name: QMessageBox.warning(self, "警告", "请输入阶段名称!") return # 从文本创建成果物对象 artifacts = [ {"name": a.strip(), "completed": False} for a in self.artifacts_edit.toPlainText().split("\n") if a.strip() ] new_stage = { "name": name, "description": self.stage_desc_edit.toPlainText().strip(), "color": self.stage_color_edit.currentText(), "artifacts": artifacts, "completed": False } self.workflow["stages"].append(new_stage) self.update_stages_list() # 选中新添加的阶段 self.stages_list.setCurrentRow(len(self.workflow["stages"]) - 1) QMessageBox.information(self, "成功", f"阶段 '{name}' 已添加!", QMessageBox.Ok) def update_current_stage(self): """更新当前选中的阶段""" current_item = self.stages_list.currentItem() if not current_item: QMessageBox.warning(self, "警告", "请先选择一个阶段!") return name = self.stage_name_edit.text().strip() if not name: QMessageBox.warning(self, "警告", "请输入阶段名称!") return stage = current_item.data(Qt.UserRole) stage["name"] = name stage["description"] = self.stage_desc_edit.toPlainText().strip() stage["color"] = self.stage_color_edit.currentText() # 更新成果物列表 artifact_names = [a.strip() for a in self.artifacts_edit.toPlainText().split("\n") if a.strip()] # 创建新的成果物列表,保留现有成果物的状态 new_artifacts = [] existing_artifacts = {a["name"]: a for a in stage.get("artifacts", [])} for name in artifact_names: # 如果成果物已存在,保留其状态 if name in existing_artifacts: new_artifacts.append(existing_artifacts[name]) else: # 新增成果物,默认状态为未完成 new_artifacts.append({"name": name, "completed": False}) stage["artifacts"] = new_artifacts # 更新阶段列表并刷新显示 current_row = self.stages_list.currentRow() self.update_stages_list() self.stages_list.setCurrentRow(current_row) # 重新加载当前阶段的信息到编辑区域 self.select_stage(self.stages_list.currentItem()) QMessageBox.information(self, "成功", f"阶段 '{name}' 已更新!", QMessageBox.Ok) def remove_stage(self): """删除当前阶段""" current_item = self.stages_list.currentItem() if not current_item: QMessageBox.warning(self, "警告", "请先选择一个阶段!") return stage_name = current_item.text() # 检查是否是该工作流程的唯一阶段 if len(self.workflow["stages"]) <= 1: QMessageBox.warning(self, "警告", "工作流程必须至少包含一个阶段!") return reply = QMessageBox.question( self, '确认删除', f"确定要删除阶段【{stage_name}】吗? 所有关联任务也将被删除!", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if reply == QMessageBox.Yes: stage = current_item.data(Qt.UserRole) # 删除关联任务 self.workflow["tasks"] = [task for task in self.workflow["tasks"] if task.get("stage") != stage["name"]] # 删除阶段 self.workflow["stages"].remove(stage) # 更新列表 self.update_stages_list() # 清空编辑区域 self.clear_stage_edit() # 自动选择下一个阶段(如果有) if self.stages_list.count() > 0: self.stages_list.setCurrentRow(0) else: self.clear_stage_edit() QMessageBox.information(self, "成功", f"阶段 '{stage_name}' 已删除!", QMessageBox.Ok) def clear_stage_edit(self): """清空阶段编辑区域""" self.stage_name_edit.clear() self.stage_desc_edit.clear() self.stage_color_edit.setCurrentIndex(0) self.artifacts_edit.clear() def get_workflow(self): """获取编辑后的工作流程""" # 确保在关闭对话框前更新当前选中的阶段 if self.stages_list.currentItem(): self.update_current_stage() return { "name": self.name_edit.text().strip(), "description": self.desc_edit.toPlainText().strip(), "stages": self.workflow["stages"], "tasks": self.workflow["tasks"] } def on_save(self): """保存工作流程前的验证和处理""" # 检查工作流程名称 if not self.name_edit.text().strip(): QMessageBox.warning(self, "警告", "工作流程名称不能为空!") return # 检查阶段数量 if not self.workflow["stages"]: QMessageBox.warning(self, "警告", "工作流程必须包含至少一个阶段!") return # 检查阶段名称 for stage in self.workflow["stages"]: if not stage.get("name", "").strip(): QMessageBox.warning(self, "警告", "阶段名称不能为空!") return # 确保在关闭对话框前更新当前选中的阶段 if self.stages_list.currentItem(): self.update_current_stage() # 所有检查通过,接受对话框 self.accept() if __name__ == "__main__": app = QApplication(sys.argv) # 设置应用程序样式 app.setStyle("Fusion") palette = QPalette() palette.setColor(QPalette.Window, QColor(248, 249, 250)) palette.setColor(QPalette.WindowText, QColor(73, 80, 87)) palette.setColor(QPalette.Base, QColor(255, 255, 255)) palette.setColor(QPalette.AlternateBase, QColor(248, 249, 250)) palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 255)) palette.setColor(QPalette.ToolTipText, QColor(73, 80, 87)) palette.setColor(QPalette.Text, QColor(73, 80, 87)) palette.setColor(QPalette.Button, QColor(248, 249, 250)) palette.setColor(QPalette.ButtonText, QColor(73, 80, 87)) palette.setColor(QPalette.BrightText, QColor(255, 255, 255)) palette.setColor(QPalette.Highlight, QColor(78, 115, 223)) palette.setColor(QPalette.HighlightedText, QColor(255, 255, 255)) app.setPalette(palette) # 设置应用字体 app_font = QFont("Segoe UI", 9) app.setFont(app_font) manager = WorkflowManager() manager.show() sys.exit(app.exec_()) 这里按照你的要求新增之后有问题,无法运行了
最新发布
07-10
import sys import json import os from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QListWidget, QLabel, QDialog, QLineEdit, QTextEdit, QComboBox, QMessageBox, QTabWidget, QToolBar, QAction, QTreeWidget, QTreeWidgetItem, QSplitter, QStatusBar, QToolButton, QStackedWidget, QDateEdit, QListWidgetItem, QCheckBox, QFormLayout, QScrollArea, QSizePolicy) from PyQt5.QtCore import Qt, QSize, QDate from PyQt5.QtGui import QIcon, QFont, QColor, QPalette, QBrush, QLinearGradient class WorkflowManager(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("工作流程管理器") self.resize(1000, 650) # 缩小窗口尺寸 self.workflows = [] self.current_workflow = None self.current_stage_index = 0 self.load_workflows() # 设置主窗口样式 - 紧凑现代风格 self.setStyleSheet(""" QMainWindow { background-color: #f8f9fa; font-family: 'Segoe UI', 'Arial', sans-serif; } QToolBar { background-color: #ffffff; border-bottom: 1px solid #e9ecef; padding: 3px; spacing: 5px; } QPushButton { background-color: #4e73df; color: white; border-radius: 4px; padding: 6px 12px; min-width: 80px; font-size: 13px; font-weight: 500; border: none; } QListWidget, QTreeWidget { background-color: white; border: 1px solid #e0e0e0; border-radius: 6px; font-size: 13px; padding: 3px; } QTabWidget::pane { border: 1px solid #e0e0e0; border-radius: 6px; background: white; margin-top: 3px; } QTabBar::tab { background: #f8f9fa; border: 1px solid #e0e0e0; border-bottom: none; padding: 8px 15px; font-size: 13px; color: #495057; border-top-left-radius: 4px; border-top-right-radius: 4px; margin-right: 2px; } QLabel { font-size: 13px; color: #495057; } QLineEdit, QTextEdit, QComboBox { font-size: 13px; padding: 6px; border: 1px solid #ced4da; border-radius: 4px; } QCheckBox { font-size: 13px; spacing: 6px; } QStatusBar { font-size: 12px; } """) # 设置应用字体 app_font = QFont("Segoe UI", 9) # 使用更小字号 QApplication.setFont(app_font) # 创建主界面 self.create_main_ui() # 加载默认工作流程 if self.workflows: self.current_workflow = self.workflows[0] self.update_workflow_display() def create_main_ui(self): """创建主界面""" # 创建工具栏 toolbar = QToolBar("工具栏") toolbar.setIconSize(QSize(20, 20)) # 更小的图标 toolbar.setMovable(False) self.addToolBar(toolbar) # 添加工具栏动作 actions = [ ("document-new", "新建", self.new_workflow), ("document-edit", "编辑", self.edit_workflow), ("edit-delete", "删除", self.delete_workflow), ("document-save", "保存", self.save_workflows), ("view-statistics", "统计", self.show_stats) ] for icon, text, callback in actions: action = QAction(QIcon.fromTheme(icon), text, self) action.triggered.connect(callback) toolbar.addAction(action) toolbar.addSeparator() # 创建主内容区域 main_widget = QWidget() self.setCentralWidget(main_widget) main_layout = QHBoxLayout(main_widget) main_layout.setContentsMargins(10, 10, 10, 10) main_layout.setSpacing(10) # 左侧工作流程列表 (更窄) left_panel = QWidget() left_panel.setFixedWidth(220) # 缩小宽度 left_layout = QVBoxLayout(left_panel) left_layout.setContentsMargins(0, 0, 0, 0) workflow_label = QLabel("工作流程") workflow_label.setStyleSheet("font-size: 14px; font-weight: 600; color: #4e73df;") left_layout.addWidget(workflow_label) self.workflow_list = QTreeWidget() self.workflow_list.setHeaderLabel("") self.workflow_list.setRootIsDecorated(False) self.workflow_list.itemClicked.connect(self.select_workflow) left_layout.addWidget(self.workflow_list) main_layout.addWidget(left_panel) # 右侧主内容区 (使用分割器) right_splitter = QSplitter(Qt.Vertical) right_splitter.setHandleWidth(5) # 工作流程可视化区 (更紧凑) self.flow_visualization = QWidget() self.flow_visualization.setStyleSheet("background-color: white; border-radius: 6px; padding: 10px;") flow_viz_layout = QVBoxLayout(self.flow_visualization) flow_viz_layout.setContentsMargins(5, 5, 5, 5) self.flow_title = QLabel() self.flow_title.setStyleSheet("font-size: 16px; font-weight: 600; color: #4e73df; margin-bottom: 10px;") flow_viz_layout.addWidget(self.flow_title) # 滚动区域容器 scroll_container = QWidget() scroll_layout = QVBoxLayout(scroll_container) scroll_layout.setContentsMargins(0, 0, 0, 0) # 添加滚动区域 scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll_area.setStyleSheet("border: none;") scroll_widget = QWidget() self.flow_stages = QHBoxLayout(scroll_widget) self.flow_stages.setSpacing(10) self.flow_stages.setContentsMargins(5, 5, 5, 5) scroll_area.setWidget(scroll_widget) scroll_layout.addWidget(scroll_area) flow_viz_layout.addWidget(scroll_container, 1) # 添加权重 # 流程控制按钮 self.flow_control = QHBoxLayout() self.flow_control.setAlignment(Qt.AlignCenter) self.flow_control.setSpacing(15) self.flow_control.setContentsMargins(0, 10, 0, 0) self.prev_stage_btn = QPushButton("◀ 上一阶段") self.prev_stage_btn.setIcon(QIcon.fromTheme("go-previous")) self.prev_stage_btn.setStyleSheet(""" QPushButton { min-width: 100px; padding: 6px; background-color: #4e73df; color: white; border-radius: 4px; } QPushButton:disabled { background-color: #cccccc; color: #666666; } """) self.prev_stage_btn.clicked.connect(self.prev_stage) self.next_stage_btn = QPushButton("下一阶段 ▶") self.next_stage_btn.setIcon(QIcon.fromTheme("go-next")) self.next_stage_btn.setStyleSheet(""" QPushButton { min-width: 100px; padding: 6px; background-color: #1cc88a; color: white; border-radius: 4px; } QPushButton:disabled { background-color: #cccccc; color: #666666; } """) self.next_stage_btn.clicked.connect(self.next_stage) self.flow_control.addWidget(self.prev_stage_btn) self.flow_control.addWidget(self.next_stage_btn) # 创建按钮容器确保按钮始终可见 button_container = QWidget() button_container.setLayout(self.flow_control) button_container.setStyleSheet("background: transparent;") flow_viz_layout.addWidget(button_container) right_splitter.addWidget(self.flow_visualization) # 成果物管理区 (更紧凑) self.artifacts_management = QTabWidget() self.artifacts_management.setStyleSheet(""" QTabWidget::pane { border: none; } """) # 成果物检查 self.artifacts_check = QWidget() self.artifacts_check.setStyleSheet("background-color: white; border-radius: 6px; padding: 10px;") artifacts_layout = QVBoxLayout(self.artifacts_check) artifacts_layout.setContentsMargins(5, 5, 5, 5) self.artifacts_label = QLabel("成果物检查") self.artifacts_label.setStyleSheet("font-size: 14px; font-weight: 600; color: #4e73df;") artifacts_layout.addWidget(self.artifacts_label) self.artifacts_list = QListWidget() self.artifacts_list.setStyleSheet("border-radius: 4px;") artifacts_layout.addWidget(self.artifacts_list, 1) # 添加权重 # 成果物状态说明 (更紧凑) self.status_info = QLabel("") self.status_info.setStyleSheet(""" font-size: 12px; font-weight: 500; color: white; background-color: #6c757d; padding: 6px; border-radius: 4px; margin-top: 8px; """) artifacts_layout.addWidget(self.status_info) self.artifacts_management.addTab(self.artifacts_check, "成果物") # 统计 self.stats_widget = QWidget() self.stats_widget.setStyleSheet("background-color: white; border-radius: 6px; padding: 10px;") stats_layout = QVBoxLayout(self.stats_widget) self.stats_label = QLabel("工作流程统计信息将显示在这里") self.stats_label.setStyleSheet("font-size: 13px;") stats_layout.addWidget(self.stats_label) self.artifacts_management.addTab(self.stats_widget, "统计") right_splitter.addWidget(self.artifacts_management) # 设置分割器比例 right_splitter.setSizes([300, 400]) main_layout.addWidget(right_splitter, 1) # 状态栏 self.status_bar = QStatusBar() self.status_bar.setSizeGripEnabled(False) self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪") def load_workflows(self): """加载工作流程并处理兼容性""" if os.path.exists("workflows.json"): with open("workflows.json", "r") as f: self.workflows = json.load(f) # 兼容旧数据结构:将字符串成果物转换为对象 for workflow in self.workflows: for stage in workflow["stages"]: if stage.get("artifacts") and isinstance(stage["artifacts"][0], str): stage["artifacts"] = [{"name": name, "completed": False} for name in stage["artifacts"]] else: # 创建默认工作流程 - 使用新数据结构 default_workflow = { "name": "软件开发变更流程", "description": "标准的软件开发变更管理流程", "stages": [ {"name": "变更点分析", "description": "分析客户提供的变更需求", "color": "#4e73df", "artifacts": [{"name": "需求分析文档", "completed": False}], "completed": False}, {"name": "变更点差分", "description": "识别变更前后的差异", "color": "#1cc88a", "artifacts": [{"name": "差异分析报告", "completed": False}], "completed": False}, {"name": "设计书修改", "description": "更新设计文档", "color": "#36b9cc", "artifacts": [{"name": "更新后的设计文档", "completed": False}], "completed": False}, {"name": "代码修改", "description": "实施代码变更", "color": "#f6c23e", "artifacts": [{"name": "变更后的代码", "completed": False}], "completed": False}, {"name": "代码验证", "description": "测试变更代码", "color": "#e74a3b", "artifacts": [{"name": "测试报告", "completed": False}], "completed": False}, {"name": "代码审核", "description": "进行代码审查", "color": "#6f42c1", "artifacts": [{"name": "代码审查意见", "completed": False}], "completed": False}, {"name": "代码集成", "description": "合并到主分支", "color": "#fd7e14", "artifacts": [{"name": "集成后的代码", "completed": False}], "completed": False}, {"name": "成果物输出", "description": "生成最终交付物", "color": "#20c997", "artifacts": [{"name": "最终交付包", "completed": False}], "completed": False} ], "tasks": [] } self.workflows.append(default_workflow) self.save_workflows() def save_workflows(self): """保存工作流程""" with open("workflows.json", "w") as f: json.dump(self.workflows, f, indent=4) self.status_bar.showMessage("工作流程已保存", 2000) def update_workflow_display(self): """更新工作流程显示""" if not self.current_workflow: return # 更新工作流程列表 self.workflow_list.clear() for workflow in self.workflows: item = QTreeWidgetItem(self.workflow_list) item.setText(0, workflow["name"]) item.setFont(0, QFont("Segoe UI", 10)) item.setData(0, Qt.UserRole, workflow) # 高亮显示当前选中的工作流程 if workflow == self.current_workflow: item.setBackground(0, QColor("#e9ecef")) # 更新流程标题 self.flow_title.setText(f"{self.current_workflow['name']} - {self.current_workflow['description']}") # 更新流程阶段显示 self.clear_flow_stages() for i, stage in enumerate(self.current_workflow["stages"]): stage_widget = self.create_stage_widget(stage, i) self.flow_stages.addWidget(stage_widget) # 更新按钮状态 self.update_buttons_state() def clear_flow_stages(self): """清空流程阶段显示""" while self.flow_stages.count(): item = self.flow_stages.takeAt(0) if item.widget(): item.widget().deleteLater() def create_stage_widget(self, stage, index): """创建单个阶段显示组件""" stage_widget = QWidget() stage_widget.setFixedWidth(180) # 更窄的组件 # 根据阶段状态设置样式 if index == self.current_stage_index: # 当前阶段 - 高亮显示 stage_widget.setStyleSheet(f""" QWidget {{ background-color: {stage['color']}20; border: 2px solid {stage['color']}; border-radius: 8px; padding: 10px; }} QLabel {{ color: {stage['color']}; }} """) elif index < self.current_stage_index: # 已完成阶段 - 灰色显示 stage_widget.setStyleSheet(f""" QWidget {{ background-color: #e9ecef; border: 2px solid #ced4da; border-radius: 8px; padding: 10px; }} QLabel {{ color: #6c757d; }} """) else: # 未开始阶段 - 默认样式 stage_widget.setStyleSheet(f""" QWidget {{ background-color: #f8f9fa; border: 2px solid #e9ecef; border-radius: 8px; padding: 10px; }} QLabel {{ color: #6c757d; }} """) layout = QVBoxLayout(stage_widget) layout.setSpacing(5) title = QLabel(stage["name"]) title.setStyleSheet("font-weight: 600; font-size: 14px;") layout.addWidget(title) desc = QLabel(stage["description"]) desc.setWordWrap(True) desc.setStyleSheet("font-size: 11px;") layout.addWidget(desc) # 显示阶段状态 status_text = "已完成" if stage.get("completed", False) else "进行中" if index == self.current_stage_index else "未开始" status = QLabel(f"状态: {status_text}") status.setStyleSheet("font-size: 10px; font-style: italic; margin-top: 5px;") layout.addWidget(status) # 添加阶段选择按钮 select_btn = QPushButton("选择") select_btn.setStyleSheet(f""" QPushButton {{ background-color: {stage['color']}; color: white; font-weight: 500; margin-top: 8px; padding: 5px; font-size: 11px; }} QPushButton:hover {{ background-color: {self.darken_color(stage['color'], 20)}; }} """) select_btn.clicked.connect(lambda _, i=index: self.select_stage(i)) layout.addWidget(select_btn) return stage_widget def darken_color(self, hex_color, percent): """使颜色变暗指定百分比""" r = int(int(hex_color[1:3], 16) * (100 - percent) / 100) g = int(int(hex_color[3:5], 16) * (100 - percent) / 100) b = int(int(hex_color[5:7], 16) * (100 - percent) / 100) return f"#{max(0, min(255, r)):02x}{max(0, min(255, g)):02x}{max(0, min(255, b)):02x}" def select_workflow(self, item): """选择工作流程""" self.current_workflow = item.data(0, Qt.UserRole) self.current_stage_index = 0 self.update_workflow_display() self.update_artifacts_display() def select_stage(self, index): """选择阶段""" # 获取目标阶段 target_stage = self.current_workflow["stages"][index] # 如果目标阶段已完成,直接跳转 if target_stage.get("completed", False): self.current_stage_index = index self.update_workflow_display() self.update_artifacts_display() return # 如果跳转到已完成阶段之后的阶段,允许直接跳转 if index <= self.current_stage_index: self.current_stage_index = index self.update_workflow_display() self.update_artifacts_display() return # 只有跳转到未完成的未来阶段才需要警告 reply = QMessageBox.question( self, '确认跳转', '您正在跳转到未完成的阶段。确定要继续吗?', QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.current_stage_index = index self.update_workflow_display() self.update_artifacts_display() def prev_stage(self): """返回上一阶段""" if self.current_stage_index > 0: self.current_stage_index -= 1 self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"已返回阶段: {self.current_workflow['stages'][self.current_stage_index]['name']}", 2000) def next_stage(self): """进入下一阶段""" if self.current_stage_index < len(self.current_workflow["stages"]) - 1: # 检查当前阶段是否已完成 current_stage = self.current_workflow["stages"][self.current_stage_index] # 检查成果物是否全部完成 if not current_stage.get("completed", False): QMessageBox.warning(self, "警告", "当前阶段成果物未全部完成,无法进入下一阶段!") return self.current_stage_index += 1 self.save_workflows() self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"已进入新阶段: {self.current_workflow['stages'][self.current_stage_index]['name']}", 2000) def update_buttons_state(self): """更新按钮状态""" self.prev_stage_btn.setEnabled(self.current_stage_index > 0) self.next_stage_btn.setEnabled( self.current_stage_index < len(self.current_workflow["stages"]) - 1 ) def new_workflow(self): """新建工作流程""" dialog = WorkflowDialog(self) if dialog.exec_() == QDialog.Accepted: new_workflow = dialog.get_workflow() self.workflows.append(new_workflow) self.current_workflow = new_workflow self.current_stage_index = 0 self.save_workflows() self.update_workflow_display() self.update_artifacts_display() def edit_workflow(self): """编辑工作流程""" if not self.current_workflow: return dialog = WorkflowDialog(self, self.current_workflow) if dialog.exec_() == QDialog.Accepted: edited_workflow = dialog.get_workflow() index = self.workflows.index(self.current_workflow) self.workflows[index] = edited_workflow self.current_workflow = edited_workflow self.save_workflows() self.update_workflow_display() self.update_artifacts_display() def delete_workflow(self): """删除当前工作流程""" if not self.current_workflow: QMessageBox.warning(self, "警告", "请先选择一个工作流程!") return reply = QMessageBox.question( self, '确认删除', f"确定要删除工作流程【{self.current_workflow['name']}】吗?\n此操作不可恢复!", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: # 从工作流程列表中移除当前工作流程 self.workflows.remove(self.current_workflow) # 更新当前选中的工作流程 if self.workflows: self.current_workflow = self.workflows[0] self.current_stage_index = 0 else: self.current_workflow = None self.current_stage_index = -1 # 保存更改并更新界面 self.save_workflows() self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"工作流程已删除", 2000) def update_artifacts_display(self): """更新成果物显示并恢复状态""" self.artifacts_list.clear() if not self.current_workflow: return current_stage = self.current_workflow["stages"][self.current_stage_index] self.artifacts_label.setText(f"成果物检查 - {current_stage['name']}") for artifact in current_stage.get("artifacts", []): item = QListWidgetItem() widget = QWidget() layout = QHBoxLayout(widget) layout.setContentsMargins(10, 5, 10, 5) # 更紧凑的内边距 checkbox = QCheckBox(artifact["name"]) checkbox.setChecked(artifact["completed"]) # 恢复状态 checkbox.setStyleSheet("font-size: 19px;") checkbox.stateChanged.connect(lambda state, a=artifact: self.update_artifact_status(a, state)) layout.addWidget(checkbox) item.setSizeHint(widget.sizeHint()) self.artifacts_list.addItem(item) self.artifacts_list.setItemWidget(item, widget) # 更新状态信息 self.update_stage_status() def update_artifact_status(self, artifact, state): """更新成果物状态""" artifact["completed"] = (state == Qt.Checked) self.update_stage_status() def update_stage_status(self): """更新阶段状态""" if not self.current_workflow: return current_stage = self.current_workflow["stages"][self.current_stage_index] artifacts = current_stage.get("artifacts", []) total_artifacts = len(artifacts) completed_artifacts = sum(1 for a in artifacts if a["completed"]) # 更新状态文本 if total_artifacts == 0: status_text = "当前阶段没有成果物" status_color = "#6c757d" elif completed_artifacts == 0: status_text = "状态: 待开始 (0% 完成)" status_color = "#e74a3b" current_stage["completed"] = False elif completed_artifacts < total_artifacts: percent = (completed_artifacts / total_artifacts) * 100 status_text = f"状态: 进行中 ({percent:.0f}% 完成)" status_color = "#f6c23e" current_stage["completed"] = False else: status_text = "状态: 已完成 (100% 完成)" status_color = "#1cc88a" current_stage["completed"] = True self.status_info.setText(status_text) self.status_info.setStyleSheet(f""" font-size: 12px; font-weight: 500; color: white; background-color: {status_color}; padding: 6px; border-radius: 4px; margin-top: 8px; """) self.update_buttons_state() def show_stats(self): """显示统计信息""" if not self.current_workflow: return stats_text = f"<h2 style='color: #4e73df;'>{self.current_workflow['name']} 统计</h2>" # 按阶段统计 stats_text += "<h3 style='color: #5a5c69;'>按阶段统计</h3><ul style='list-style-type: none; padding-left: 0;'>" for i, stage in enumerate(self.current_workflow["stages"]): artifacts = stage.get("artifacts", []) total_artifacts = len(artifacts) completed_artifacts = sum(1 for a in artifacts if a["completed"]) status = "已完成" if stage.get("completed", False) else "进行中" if i == self.current_stage_index else "未开始" stats_text += f"<li style='margin-bottom: 6px;'>" stats_text += f"<span style='display: inline-block; width: 16px; height: 16px; background-color: {stage['color']}; border-radius: 3px; margin-right: 8px;'></span>" stats_text += f"<b>{stage['name']}</b>: {completed_artifacts}/{total_artifacts} 完成 (<span style='color: #4e73df;'>{status}</span>)" stats_text += "</li>" stats_text += "</ul>" # 完成率统计 completed_stages = sum(1 for stage in self.current_workflow["stages"] if stage.get("completed", False)) total_stages = len(self.current_workflow["stages"]) completion_rate = (completed_stages / total_stages) * 100 if total_stages > 0 else 0 # 进度条 stats_text += f"<h3 style='color: #5a5c69;'>流程完成率: {completion_rate:.0f}%</h3>" stats_text += f""" <div style='background-color: #e9ecef; height: 16px; border-radius: 8px; margin-bottom: 15px;'> <div style='background-color: #4e73df; width: {completion_rate}%; height: 100%; border-radius: 8px;'></div> </div> """ self.stats_label.setText(stats_text) self.artifacts_management.setCurrentWidget(self.stats_widget) class WorkflowDialog(QDialog): """工作流程编辑对话框""" def __init__(self, parent=None, workflow=None): super().__init__(parent) self.setWindowTitle("编辑工作流程" if workflow else "新建工作流程") self.resize(600, 450) # 更小的对话框 self.workflow = workflow if workflow else { "name": "", "description": "", "stages": [], "tasks": [] } self.setup_ui() def setup_ui(self): """设置对话框UI""" layout = QVBoxLayout(self) layout.setContentsMargins(15, 15, 15, 15) # 标题 title = QLabel("编辑工作流程" if self.workflow.get("name") else "新建工作流程") title.setStyleSheet("font-size: 16px; font-weight: 600; color: #4e73df; margin-bottom: 15px;") layout.addWidget(title) # 基本信息 form_layout = QFormLayout() form_layout.setLabelAlignment(Qt.AlignRight) form_layout.setVerticalSpacing(10) self.name_edit = QLineEdit(self.workflow["name"]) self.name_edit.setPlaceholderText("输入工作流程名称") form_layout.addRow("流程名称:", self.name_edit) self.desc_edit = QTextEdit(self.workflow["description"]) self.desc_edit.setPlaceholderText("输入工作流程描述") self.desc_edit.setFixedHeight(60) form_layout.addRow("流程描述:", self.desc_edit) layout.addLayout(form_layout) # 阶段管理 stages_label = QLabel("阶段管理") stages_label.setStyleSheet("font-size: 14px; font-weight: 600; color: #4e73df; margin-top: 15px; margin-bottom: 8px;") layout.addWidget(stages_label) # 阶段列表和编辑区 stages_container = QHBoxLayout() stages_container.setSpacing(15) # 阶段列表 stages_list_container = QVBoxLayout() self.stages_list = QListWidget() self.stages_list.setFixedHeight(150) self.update_stages_list() self.stages_list.itemClicked.connect(self.select_stage) stages_list_container.addWidget(self.stages_list) stages_container.addLayout(stages_list_container, 1) # 阶段编辑区 stage_edit_container = QVBoxLayout() self.stage_edit = QWidget() self.stage_edit.setStyleSheet("background-color: #f8f9fa; border-radius: 6px; padding: 10px;") stage_edit_layout = QFormLayout(self.stage_edit) stage_edit_layout.setVerticalSpacing(8) self.stage_name_edit = QLineEdit() self.stage_name_edit.setPlaceholderText("输入阶段名称") stage_edit_layout.addRow("阶段名称:", self.stage_name_edit) self.stage_desc_edit = QTextEdit() self.stage_desc_edit.setPlaceholderText("输入阶段描述") self.stage_desc_edit.setFixedHeight(50) stage_edit_layout.addRow("阶段描述:", self.stage_desc_edit) self.stage_color_edit = QComboBox() self.stage_color_edit.addItems(["#4e73df", "#1cc88a", "#36b9cc", "#f6c23e", "#e74a3b", "#6f42c1", "#fd7e14", "#20c997"]) stage_edit_layout.addRow("阶段颜色:", self.stage_color_edit) # 成果物编辑 self.artifacts_edit = QTextEdit() self.artifacts_edit.setPlaceholderText("每行输入一个成果物名称") stage_edit_layout.addRow("成果物:", self.artifacts_edit) stage_edit_container.addWidget(self.stage_edit) # 阶段操作按钮 stage_btn_layout = QHBoxLayout() stage_btn_layout.setSpacing(8) self.add_stage_btn = QPushButton("添加") self.add_stage_btn.setStyleSheet("background-color: #1cc88a; color: white; padding: 5px;") self.add_stage_btn.clicked.connect(self.add_stage) stage_btn_layout.addWidget(self.add_stage_btn) self.update_stage_btn = QPushButton("更新") self.update_stage_btn.setStyleSheet("background-color: #4e73df; color: white; padding: 5px;") self.update_stage_btn.clicked.connect(self.update_stage) stage_btn_layout.addWidget(self.update_stage_btn) self.remove_stage_btn = QPushButton("删除") self.remove_stage_btn.setStyleSheet("background-color: #e74a3b; color: white; padding: 5px;") self.remove_stage_btn.clicked.connect(self.remove_stage) stage_btn_layout.addWidget(self.remove_stage_btn) stage_edit_container.addLayout(stage_btn_layout) stages_container.addLayout(stage_edit_container, 2) layout.addLayout(stages_container) # 对话框按钮 btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignRight) btn_layout.setSpacing(10) save_btn = QPushButton("保存") save_btn.setStyleSheet("background-color: #4e73df; color: white; min-width: 80px; padding: 6px;") save_btn.clicked.connect(self.accept) btn_layout.addWidget(save_btn) cancel_btn = QPushButton("取消") cancel_btn.setStyleSheet("background-color: #6c757d; color: white; min-width: 80px; padding: 6px;") cancel_btn.clicked.connect(self.reject) btn_layout.addWidget(cancel_btn) layout.addLayout(btn_layout) def update_stages_list(self): """更新阶段列表""" self.stages_list.clear() for stage in self.workflow["stages"]: item = QListWidgetItem(stage["name"]) item.setData(Qt.UserRole, stage) self.stages_list.addItem(item) def select_stage(self, item): """选择阶段进行编辑""" stage = item.data(Qt.UserRole) self.stage_name_edit.setText(stage["name"]) self.stage_desc_edit.setText(stage["description"]) self.stage_color_edit.setCurrentText(stage["color"]) # 将成果物列表转换为文本 artifact_names = [a["name"] for a in stage.get("artifacts", [])] self.artifacts_edit.setText("\n".join(artifact_names)) def add_stage(self): """添加新阶段""" name = self.stage_name_edit.text().strip() if not name: QMessageBox.warning(self, "警告", "请输入阶段名称!") return # 从文本创建成果物对象 artifacts = [ {"name": a.strip(), "completed": False} for a in self.artifacts_edit.toPlainText().split("\n") if a.strip() ] new_stage = { "name": name, "description": self.stage_desc_edit.toPlainText().strip(), "color": self.stage_color_edit.currentText(), "artifacts": artifacts, "completed": False } self.workflow["stages"].append(new_stage) self.update_stages_list() self.clear_stage_edit() def update_stage(self): """更新当前阶段""" current_item = self.stages_list.currentItem() if not current_item: QMessageBox.warning(self, "警告", "请先选择一个阶段!") return name = self.stage_name_edit.text().strip() if not name: QMessageBox.warning(self, "警告", "请输入阶段名称!") return stage = current_item.data(Qt.UserRole) stage["name"] = name stage["description"] = self.stage_desc_edit.toPlainText().strip() stage["color"] = self.stage_color_edit.currentText() # 更新成果物列表 artifact_names = [a.strip() for a in self.artifacts_edit.toPlainText().split("\n") if a.strip()] # 保留现有成果物的状态 existing_artifacts = {a["name"]: a["completed"] for a in stage.get("artifacts", [])} stage["artifacts"] = [ {"name": name, "completed": existing_artifacts.get(name, False)} for name in artifact_names ] self.update_stages_list() def remove_stage(self): """删除当前阶段""" current_item = self.stages_list.currentItem() if not current_item: QMessageBox.warning(self, "警告", "请先选择一个阶段!") return reply = QMessageBox.question(self, '确认删除', f"确定要删除阶段【{current_item.text()}】吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: stage = current_item.data(Qt.UserRole) self.workflow["stages"].remove(stage) self.update_stages_list() self.clear_stage_edit() def clear_stage_edit(self): """清空阶段编辑区""" self.stage_name_edit.clear() self.stage_desc_edit.clear() self.stage_color_edit.setCurrentIndex(0) self.artifacts_edit.clear() def get_workflow(self): """获取编辑后的工作流程""" return { "name": self.name_edit.text().strip(), "description": self.desc_edit.toPlainText().strip(), "stages": self.workflow["stages"], "tasks": self.workflow["tasks"] } if __name__ == "__main__": app = QApplication(sys.argv) # 设置应用程序样式 app.setStyle("Fusion") palette = QPalette() palette.setColor(QPalette.Window, QColor(248, 249, 250)) palette.setColor(QPalette.WindowText, QColor(73, 80, 87)) palette.setColor(QPalette.Base, QColor(255, 255, 255)) palette.setColor(QPalette.AlternateBase, QColor(248, 249, 250)) palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 255)) palette.setColor(QPalette.ToolTipText, QColor(73, 80, 87)) palette.setColor(QPalette.Text, QColor(73, 80, 87)) palette.setColor(QPalette.Button, QColor(248, 249, 250)) palette.setColor(QPalette.ButtonText, QColor(73, 80, 87)) palette.setColor(QPalette.BrightText, QColor(255, 255, 255)) palette.setColor(QPalette.Highlight, QColor(78, 115, 223)) palette.setColor(QPalette.HighlightedText, QColor(255, 255, 255)) app.setPalette(palette) # 设置应用字体 app_font = QFont("Segoe UI", 9) app.setFont(app_font) manager = WorkflowManager() manager.show() sys.exit(app.exec_()) 1、点击进行编辑时,想要新增成果物,点击保存,新增的成果物并没有保存
07-09
import sys import json import os import warnings from datetime import datetime from PyQt5.QtCore import QUrl, Qt, QDateTime from PyQt5.QtWidgets import ( QApplication, QMainWindow, QLineEdit, QPushButton, QListWidget, QTabWidget, QVBoxLayout, QWidget, QHBoxLayout, QAction, QToolBar, QStatusBar, QProgressBar, QInputDialog, QFileDialog, QMessageBox ) from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage, QWebEngineSettings from PyQt5.QtNetwork import QNetworkProxy, QNetworkCookie # 修复代理设置导入问题 # 忽略弃用警告 warnings.filterwarnings("ignore", category=DeprecationWarning) class WebEnginePage(QWebEnginePage): def __init__(self, profile=None, parent=None): """修复参数传递问题:支持带profile和不带profile两种初始化方式""" if profile: super().__init__(profile, parent) # 使用profile初始化 else: super().__init__(parent) # 标准初始化 self.parent_window = parent.parent_window if parent and hasattr(parent, 'parent_window') else None def acceptNavigationRequest(self, url, _type, isMainFrame): # 检测视频流协议并调用播放器 if url.scheme() in ['rtmp', 'http-flv', 'ws-flv']: if self.parent_window: self.parent_window.play_with_jessibuca(url.toString()) return False return super().acceptNavigationRequest(url, _type, isMainFrame) def createWindow(self, type): # 在新标签页打开链接 if type == QWebEnginePage.WebBrowserTab: if self.parent_window: new_tab = self.parent_window.add_browser_tab("加载中...") return new_tab.page return super().createWindow(type) def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID): """修复:正确映射JavaScript控制台日志级别""" # 创建日志级别映射字典 level_map = { QWebEnginePage.InfoMessageLevel: "INFO", QWebEnginePage.WarningMessageLevel: "WARNING", QWebEnginePage.ErrorMessageLevel: "ERROR" } # 获取可读的日志级别名称 level_str = level_map.get(level, "UNKNOWN") log_msg = f"[JS {level_str}] {sourceID}:{lineNumber} - {message}" if self.parent_window: self.parent_window.status_bar.showMessage(log_msg, 5000) print(log_msg) # 同时输出到控制台 class WebEngineView(QWebEngineView): def __init__(self, parent=None): super().__init__(parent) self.parent_window = parent # 修复:正确传递profile参数 self.page = WebEnginePage(profile=self.parent_window.profile, parent=self) self.setPage(self.page) self.page.loadFinished.connect(self.inject_jessibuca) def inject_jessibuca(self): self.page.runJavaScript(""" if (typeof window.Jessibuca === 'undefined') { const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/jessibuca@latest/dist/jessibuca.js'; script.onerror = () => console.error('Jessibuca加载失败'); document.head.appendChild(script); const container = document.createElement('div'); container.id = 'jessibuca-container'; container.style.position = 'fixed'; container.style.zIndex = '99999'; container.style.top = '0'; container.style.left = '0'; container.style.width = '100%'; container.style.height = '100%'; container.style.backgroundColor = 'black'; container.style.display = 'none'; document.body.appendChild(container); } """) def createWindow(self, type): # 创建新标签页 if type == QWebEnginePage.WebBrowserTab: new_tab = WebEngineView(self.parent_window) # 修复:直接返回page属性而非调用page() return new_tab.page return super().createWindow(type) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("B站多账号浏览器") self.setMinimumSize(1200, 800) # 初始化数据存储 self.account_db = {} self.cookies_db = [] # 创建持久化Cookie配置 self.cookie_storage_path = "cookies_storage" os.makedirs(self.cookie_storage_path, exist_ok=True) self.profile = QWebEngineProfile("BiliCookieProfile", self) self.profile.setPersistentCookiesPolicy(QWebEngineProfile.ForcePersistentCookies) self.profile.setPersistentStoragePath(self.cookie_storage_path) # 关键性能优化1: 全局禁用网络代理[8](@ref) QNetworkProxy.setApplicationProxy(QNetworkProxy(QNetworkProxy.NoProxy)) # 关键性能优化2: 启用HTTP内存缓存[9](@ref) self.profile.setHttpCacheType(QWebEngineProfile.MemoryHttpCache) # 启用HTML5支持的设置 profile_settings = self.profile.settings() profile_settings.setAttribute(QWebEngineSettings.PlaybackRequiresUserGesture, False) profile_settings.setAttribute(QWebEngineSettings.FullScreenSupportEnabled, True) profile_settings.setAttribute(QWebEngineSettings.WebGLEnabled, True) profile_settings.setAttribute(QWebEngineSettings.Accelerated2dCanvasEnabled, True) profile_settings.setAttribute(QWebEngineSettings.PluginsEnabled, True) profile_settings.setAttribute(QWebEngineSettings.AllowRunningInsecureContent, True) # 主界面布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 导航工具栏 navbar = QToolBar("导航栏") navbar.setMovable(False) # 禁止工具栏拖动提升性能 self.addToolBar(navbar) back_btn = QAction("←", self) back_btn.triggered.connect(self.navigate_back) navbar.addAction(back_btn) forward_btn = QAction("→", self) forward_btn.triggered.connect(self.navigate_forward) navbar.addAction(forward_btn) reload_btn = QAction("↻", self) reload_btn.triggered.connect(self.reload_page) navbar.addAction(reload_btn) home_btn = QAction("🏠", self) home_btn.triggered.connect(self.navigate_home) navbar.addAction(home_btn) navbar.addSeparator() self.url_bar = QLineEdit("https://www.bilibili.com") self.url_bar.setPlaceholderText("输入网址或搜索内容...") self.url_bar.returnPressed.connect(self.load_url) navbar.addWidget(self.url_bar) # 浏览器标签页 self.browser_tabs = QTabWidget() self.browser_tabs.setTabsClosable(True) self.browser_tabs.tabCloseRequested.connect(self.close_tab) self.browser_tabs.currentChanged.connect(self.update_url_bar) # 关键性能优化3: 启用标签页滚动 self.browser_tabs.setUsesScrollButtons(True) main_layout.addWidget(self.browser_tabs, 8) # 添加初始标签页 self.add_browser_tab("首页", "https://www.bilibili.com") # 管理面板 self.tabs = QTabWidget() self.cookie_list = QListWidget() self.tabs.addTab(self.cookie_list, "Cookie列表") self.account_list = QListWidget() self.tabs.addTab(self.account_list, "账号切换") self.account_list.itemClicked.connect(self.switch_account) main_layout.addWidget(self.tabs, 2) # 功能按钮 btn_layout = QHBoxLayout() self.import_btn = QPushButton("导入Cookie") self.import_btn.clicked.connect(self.import_cookies) self.save_btn = QPushButton("保存账号") self.save_btn.clicked.connect(self.save_account) self.export_btn = QPushButton("导出Cookie") self.export_btn.clicked.connect(self.export_cookies) self.new_tab_btn = QPushButton("新建标签页") self.new_tab_btn.clicked.connect(self.create_new_tab) for btn in [self.import_btn, self.save_btn, self.export_btn, self.new_tab_btn]: btn_layout.addWidget(btn) main_layout.addLayout(btn_layout) # 状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.progress_bar = QProgressBar() self.progress_bar.setMaximum(100) self.progress_bar.setVisible(False) self.progress_bar.setFixedWidth(200) self.status_bar.addPermanentWidget(self.progress_bar) # 加载数据 self.load_accounts() def add_browser_tab(self, title, url=None): browser = WebEngineView(self) browser.titleChanged.connect(lambda title, b=browser: self.update_tab_title(b, title)) browser.loadProgress.connect(self.update_progress) browser.loadFinished.connect(self.on_load_finished) index = self.browser_tabs.addTab(browser, title) self.browser_tabs.setCurrentIndex(index) # 关键性能优化4: 延迟加载提升启动速度 if url: QApplication.processEvents() # 确保UI更新 browser.load(QUrl(url)) return browser def update_tab_title(self, browser, title): index = self.browser_tabs.indexOf(browser) if index != -1: self.browser_tabs.setTabText(index, title[:15] + "..." if len(title) > 15 else title) def update_progress(self, progress): self.progress_bar.setVisible(progress < 100) self.progress_bar.setValue(progress) def close_tab(self, index): """修复资源释放问题:移除多余的括号""" if self.browser_tabs.count() > 1: widget = self.browser_tabs.widget(index) # 关键修复:直接访问page属性而非调用page() page = widget.page if page: # 关键优化: 正确的资源释放顺序 profile = page.profile() profile.cookieStore().deleteAllCookies() # 先删除页面再删除视图 page.deleteLater() widget.deleteLater() self.browser_tabs.removeTab(index) else: self.create_new_tab() def create_new_tab(self): self.add_browser_tab("新标签页", "about:blank") def navigate_back(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.back() def navigate_forward(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.forward() def reload_page(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.reload() def navigate_home(self): self.load_url("https://www.bilibili.com") def on_load_finished(self, success): browser = self.sender() if browser and self.browser_tabs.currentWidget() == browser: current_url = browser.url().toString() self.url_bar.setText(current_url) self.status_bar.showMessage("页面加载完成" if success else "页面加载失败", 2000) # 关键优化: 登录页面自动处理 if "login" in current_url or "passport" in current_url: self.status_bar.showMessage("检测到登录页面,请完成登录", 5000) self.auto_handle_login_page(browser) def auto_handle_login_page(self, browser): """自动处理登录页面的JavaScript""" browser.page.runJavaScript(""" // 尝试自动填充已知的登录表单 const loginForm = document.querySelector('form[action*="login"]'); if (loginForm) { // 尝试填充测试账号 const usernameInput = loginForm.querySelector('input[name="username"], input[name="user"]'); const passwordInput = loginForm.querySelector('input[name="password"]'); if (usernameInput && passwordInput) { usernameInput.value = "test_account"; passwordInput.value = "test_password"; console.log("自动填充了登录表单"); } } """) def update_url_bar(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_url = current_browser.url().toString() self.url_bar.setText(current_url) # 登录页面特殊处理 if "login" in current_url or "passport" in current_url: self.url_bar.setStyleSheet("background-color: #FFF8E1;") else: self.url_bar.setStyleSheet("") def load_url(self, url_text=None): if url_text is None: url_text = self.url_bar.text().strip() if not url_text: return # 关键优化: 更智能的URL处理[10](@ref) if not url_text.startswith(("http://", "https://", "file://", "ftp://")): if "." in url_text: # 包含域名 url_text = "https://" + url_text else: # 可能是搜索内容 url_text = f"https://www.bilibili.com/search?keyword={url_text}" current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.load(QUrl(url_text)) self.status_bar.showMessage(f"正在加载: {url_text}", 3000) def play_with_jessibuca(self, stream_url): """使用Jessibuca播放视频流""" current_browser = self.browser_tabs.currentWidget() if not current_browser: return self.status_bar.showMessage("Jessibuca播放中...", 3000) current_browser.page.runJavaScript(f""" const container = document.getElementById('jessibuca-container'); if (container) {{ container.style.display = 'block'; if (!window.jessibucaPlayer) {{ window.jessibucaPlayer = new Jessibuca({{ container: container, videoBuffer: 0.2, isResize: true, text: '直播加载中...', decoder: 'ffmpeg.js', forceNoOffscreen: true }}); }} window.jessibucaPlayer.play('{stream_url}'); }} """) def import_cookies(self): """导入Cookie JSON文件""" file_path, _ = QFileDialog.getOpenFileName( self, "选择Cookie文件", "", "JSON文件 (*.json)" ) if file_path: try: with open(file_path, "r", encoding='utf-8') as f: cookies = json.load(f) if isinstance(cookies, list): self.cookies_db = cookies self.cookie_list.clear() self.cookie_list.addItems([f"{c['name']}: {c['value'][:10]}..." for c in cookies]) self.status_bar.showMessage(f"成功导入 {len(cookies)} 个Cookie", 3000) self.inject_cookies(cookies) else: QMessageBox.warning(self, "错误", "无效的Cookie格式") except Exception as e: QMessageBox.critical(self, "导入失败", f"错误: {str(e)}") def export_cookies(self): """导出Cookie到文件""" file_path, _ = QFileDialog.getSaveFileName( self, "保存Cookie文件", "", "JSON文件 (*.json)" ) if file_path: try: cookies = self.get_current_cookies() with open(file_path, "w", encoding='utf-8') as f: json.dump(cookies, f, indent=2, ensure_ascii=False) self.status_bar.showMessage("Cookie导出成功", 3000) except Exception as e: QMessageBox.critical(self, "导出失败", f"错误: {str(e)}") def get_current_cookies(self): """获取当前标签页的Cookie(模拟)""" # 实际实现需要异步获取,这里简化处理 return self.cookies_db if self.cookies_db else [] def inject_cookies(self, cookies): """将Cookie注入当前页面 - 修复登录问题[9](@ref)""" current_browser = self.browser_tabs.currentWidget() if current_browser: store = current_browser.page.profile().cookieStore() # 先清除现有Cookie store.deleteAllCookies() # 异步注入新的Cookie for cookie_data in cookies: # 创建Qt的Cookie对象 qt_cookie = QNetworkCookie( cookie_data['name'].encode('utf-8'), cookie_data['value'].encode('utf-8') ) # 设置Cookie属性 if 'domain' in cookie_data: qt_cookie.setDomain(cookie_data['domain']) if 'path' in cookie_data: qt_cookie.setPath(cookie_data['path']) if 'expiry' in cookie_data: # 转换为QDateTime expiry = QDateTime.fromSecsSinceEpoch(cookie_data['expiry']) qt_cookie.setExpirationDate(expiry) # 设置安全属性 qt_cookie.setSecure(cookie_data.get('secure', False)) qt_cookie.setHttpOnly(cookie_data.get('httpOnly', False)) # 注入Cookie store.setCookie(qt_cookie, QUrl(cookie_data.get('url', 'https://www.bilibili.com'))) self.status_bar.showMessage("Cookie注入成功,请刷新页面", 3000) def save_account(self): """保存当前账号配置""" account_name, ok = QInputDialog.getText( self, "保存账号", "输入账号名称:" ) if ok and account_name: cookies = self.get_current_cookies() if cookies: self.account_db[account_name] = { "cookies": cookies, "saved_date": datetime.now().isoformat() } self.account_list.addItem(account_name) # 持久化存储账号数据 try: with open("accounts.json", "w", encoding='utf-8') as f: json.dump(self.account_db, f, indent=2, ensure_ascii=False) QMessageBox.information(self, "成功", "账号保存成功") except Exception as e: QMessageBox.critical(self, "保存失败", f"账号保存失败: {str(e)}") else: QMessageBox.warning(self, "错误", "没有获取到有效Cookie") def switch_account(self, item): """切换账号""" account_name = item.text() if account_name in self.account_db: cookies = self.account_db[account_name].get("cookies", []) self.inject_cookies(cookies) self.status_bar.showMessage(f"已切换至账号: {account_name}", 3000) # 自动刷新当前页面 self.reload_page() else: QMessageBox.warning(self, "错误", "找不到该账号的Cookie信息") def load_accounts(self): """从文件加载保存的账号""" try: if os.path.exists("accounts.json"): with open("accounts.json", "r", encoding='utf-8') as f: self.account_db = json.load(f) self.account_list.addItems(self.account_db.keys()) except Exception as e: print(f"加载账号失败: {str(e)}") def closeEvent(self, event): """窗口关闭时释放全局资源[8](@ref)""" # 清理HTTP缓存和访问记录 self.profile.clearHttpCache() self.profile.clearAllVisitedLinks() # 保存账号数据 try: with open("accounts.json", "w", encoding='utf-8') as f: json.dump(self.account_db, f, indent=2, ensure_ascii=False) except Exception as e: print(f"保存账号失败: {str(e)}") super().closeEvent(event) if __name__ == "__main__": # 关键性能优化: 启用WebEngine调试日志[9](@ref) os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-logging" app = QApplication(sys.argv) # 关键性能优化: 启用Qt内置的OpenGL渲染[7](@ref) app.setAttribute(Qt.AA_UseOpenGLES) window = MainWindow() window.show() sys.exit(app.exec_())
07-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值