|
|
import sys
|
|
|
import os
|
|
|
from PyQt5.QtCore import Qt, QTimer, QAbstractTableModel, QModelIndex, QPoint, QSize, pyqtSignal, QFile,QTextStream
|
|
|
from PyQt5.QtGui import QBrush, QColor, QFont, QStandardItemModel, QStandardItem
|
|
|
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableView,
|
|
|
QPushButton, QVBoxLayout, QWidget, QHBoxLayout,
|
|
|
QLabel, QCheckBox, QSpinBox, QMenu, QFileDialog,
|
|
|
QTabWidget, QTabBar, QListWidget, QListWidgetItem, QDialog,
|
|
|
QLineEdit, QFormLayout, QDialogButtonBox, QMessageBox,
|
|
|
QHeaderView, QToolBar, QAction, QStatusBar, QComboBox, QSplitter, QAbstractItemView)
|
|
|
from docx import Document
|
|
|
from docx.shared import Pt, RGBColor
|
|
|
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
|
|
import qtawesome as qta
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
# 导入其他模块
|
|
|
from model.ProcedureModel.ProcedureProcessor import ExcelParser, StepTableModel
|
|
|
from utils.DBModels.ProcedureModel import DatabaseManager
|
|
|
# 修改导入路径
|
|
|
|
|
|
|
|
|
class StepExecutor(QWidget):
|
|
|
# 添加标签锁定信号
|
|
|
tabLockRequired = pyqtSignal(bool)
|
|
|
|
|
|
def __init__(self, procedureData, procedureId, dbManager):
|
|
|
super().__init__()
|
|
|
self.procedureData = procedureData
|
|
|
self.procedureId = procedureId # 新增规程ID
|
|
|
self.dbManager = dbManager # 新增数据库管理器
|
|
|
self.isRunning = False
|
|
|
self.isActive = False
|
|
|
|
|
|
testSteps = procedureData["测试步骤"]
|
|
|
|
|
|
self.initUi(testSteps)
|
|
|
self.currentIndex = 0
|
|
|
self.timer = QTimer()
|
|
|
self.timer.timeout.connect(self.autoExecuteStep)
|
|
|
self.remainingCycles = 1
|
|
|
self.infiniteCycles = False
|
|
|
self.isFirstRun = True # 新增标志位,用于区分首次执行
|
|
|
self.stepResults = [] # 新增:存储所有步骤执行结果的列表
|
|
|
|
|
|
def initUi(self, testSteps):
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
info_layout = QFormLayout()
|
|
|
info_layout.setLabelAlignment(Qt.AlignRight)
|
|
|
info_layout.addRow("规程名称:", QLabel(self.procedureData["规程信息"]["规程名称"]))
|
|
|
info_layout.addRow("规程编号:", QLabel(self.procedureData["规程信息"]["规程编号"]))
|
|
|
info_layout.addRow("规程类型:", QLabel(self.procedureData["规程信息"]["规程类型"]))
|
|
|
info_layout.addRow("测试用例:", QLabel(self.procedureData["测试用例信息"]["测试用例"]))
|
|
|
info_layout.addRow("用例编号:", QLabel(self.procedureData["测试用例信息"]["用例编号"]))
|
|
|
info_layout.addRow("工况描述:", QLabel(self.procedureData["测试用例信息"]["工况描述"]))
|
|
|
|
|
|
layout.addLayout(info_layout)
|
|
|
layout.addSpacing(20)
|
|
|
|
|
|
self.tableModel = StepTableModel(testSteps)
|
|
|
self.tableView = QTableView()
|
|
|
self.tableView.setModel(self.tableModel)
|
|
|
self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
|
self.tableView.customContextMenuRequested.connect(self.showContextMenu)
|
|
|
|
|
|
self.tableView.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
|
|
self.tableView.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
|
|
|
self.tableView.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
|
|
self.tableView.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeToContents)
|
|
|
self.tableView.horizontalHeader().setSectionResizeMode(5, QHeaderView.Stretch) # 新增:备注列自适应宽度
|
|
|
|
|
|
layout.addWidget(QLabel("测试步骤:"))
|
|
|
layout.addWidget(self.tableView)
|
|
|
|
|
|
# 创建控制按钮布局
|
|
|
control_layout = QHBoxLayout()
|
|
|
|
|
|
self.autoButton = QPushButton(" 开始自动执行")
|
|
|
self.autoButton.clicked.connect(self.startAutoExecute)
|
|
|
self.autoButton.setIcon(qta.icon('fa5s.play', color='green'))
|
|
|
|
|
|
self.stopButton = QPushButton(" 停止自动执行")
|
|
|
self.stopButton.clicked.connect(self.stopAutoExecute)
|
|
|
self.stopButton.setEnabled(False)
|
|
|
self.stopButton.setIcon(qta.icon('fa5s.stop', color='red'))
|
|
|
|
|
|
self.nextButton = QPushButton(" 执行下一步")
|
|
|
self.nextButton.clicked.connect(self.executeNextStep)
|
|
|
self.nextButton.setIcon(qta.icon('fa5s.step-forward', color='blue'))
|
|
|
|
|
|
self.resetButton = QPushButton(" 完全重置")
|
|
|
self.resetButton.clicked.connect(self.resetExecution)
|
|
|
self.resetButton.setIcon(qta.icon('fa5s.redo', color='orange'))
|
|
|
|
|
|
self.exportButton = QPushButton(" 生成报告")
|
|
|
self.exportButton.clicked.connect(self.generateReport)
|
|
|
self.exportButton.setIcon(qta.icon('fa5s.file-alt', color='purple'))
|
|
|
|
|
|
control_layout.addWidget(self.autoButton)
|
|
|
control_layout.addWidget(self.stopButton)
|
|
|
control_layout.addWidget(self.nextButton)
|
|
|
control_layout.addWidget(self.resetButton)
|
|
|
control_layout.addWidget(self.exportButton)
|
|
|
|
|
|
# 添加循环设置
|
|
|
cycle_layout = QHBoxLayout()
|
|
|
cycle_layout.addWidget(QLabel("执行轮次:"))
|
|
|
|
|
|
self.cycleSpin = QSpinBox()
|
|
|
self.cycleSpin.setRange(1, 999)
|
|
|
self.cycleSpin.setValue(1)
|
|
|
cycle_layout.addWidget(self.cycleSpin)
|
|
|
|
|
|
self.infiniteCheckbox = QCheckBox("无限循环")
|
|
|
cycle_layout.addWidget(self.infiniteCheckbox)
|
|
|
cycle_layout.addStretch()
|
|
|
|
|
|
# 将所有布局添加到主布局
|
|
|
layout.addLayout(control_layout)
|
|
|
layout.addLayout(cycle_layout)
|
|
|
|
|
|
# 设置主布局
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
# 初始化工具栏
|
|
|
self.toolbar = QToolBar("执行工具栏")
|
|
|
self.toolbar.setIconSize(QSize(24, 24))
|
|
|
|
|
|
# 工具栏操作
|
|
|
self.toolbar.addAction(qta.icon('fa5s.play', color='green'), "开始执行", self.startAutoExecute)
|
|
|
self.toolbar.addAction(qta.icon('fa5s.stop', color='red'), "停止执行", self.stopAutoExecute)
|
|
|
self.toolbar.addAction(qta.icon('fa5s.step-forward', color='blue'), "下一步", self.executeNextStep)
|
|
|
self.toolbar.addSeparator()
|
|
|
self.toolbar.addAction(qta.icon('fa5s.redo', color='orange'), "重置", self.resetExecution)
|
|
|
self.toolbar.addSeparator()
|
|
|
self.toolbar.addAction(qta.icon('fa5s.file-alt', color='purple'), "生成报告", self.generateReport)
|
|
|
|
|
|
def showContextMenu(self, pos):
|
|
|
index = self.tableView.indexAt(pos)
|
|
|
if index.isValid():
|
|
|
menu = QMenu()
|
|
|
jumpAction = menu.addAction("从该步骤开始执行")
|
|
|
jumpAction.triggered.connect(lambda: self.jumpExecute(index.row()))
|
|
|
detailAction = menu.addAction("查看步骤详情")
|
|
|
detailAction.triggered.connect(lambda: self.showStepDetail(index.row()))
|
|
|
menu.exec_(self.tableView.viewport().mapToGlobal(pos))
|
|
|
|
|
|
def jumpExecute(self, rowIndex):
|
|
|
if 0 <= rowIndex < self.tableModel.rowCount():
|
|
|
stepInfo = self.tableModel.getStepInfo(rowIndex)
|
|
|
if stepInfo and not stepInfo['isMain']:
|
|
|
self.executeStep(rowIndex)
|
|
|
self.currentIndex = rowIndex + 1
|
|
|
|
|
|
def showStepDetail(self, row):
|
|
|
step_info = self.tableModel.getStepInfo(row)
|
|
|
if not step_info:
|
|
|
return
|
|
|
|
|
|
detail_dialog = QDialog(self)
|
|
|
detail_dialog.setWindowTitle("步骤详情")
|
|
|
detail_dialog.setMinimumWidth(500)
|
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
form_layout = QFormLayout()
|
|
|
form_layout.addRow("步骤ID", QLabel(step_info['stepId']))
|
|
|
form_layout.addRow("步骤类型", QLabel("主步骤" if step_info['isMain'] else "子步骤"))
|
|
|
form_layout.addRow("步骤描述", QLabel(step_info['description']))
|
|
|
|
|
|
if step_info['time']:
|
|
|
form_layout.addRow("执行时间"); QLabel(step_info['time'].strftime("%Y-%m-%d %H:%M:%S"))
|
|
|
|
|
|
if step_info['result'] is not None:
|
|
|
status = "成功" if step_info['result'] else "失败"
|
|
|
form_layout.addRow("执行结果"); QLabel(status)
|
|
|
|
|
|
layout.addLayout(form_layout)
|
|
|
|
|
|
button_box = QDialogButtonBox(QDialogButtonBox.Ok)
|
|
|
button_box.accepted.connect(detail_dialog.accept)
|
|
|
layout.addWidget(button_box)
|
|
|
|
|
|
detail_dialog.setLayout(layout)
|
|
|
detail_dialog.exec_()
|
|
|
|
|
|
def startAutoExecute(self):
|
|
|
self.isRunning = True
|
|
|
self.isActive = True
|
|
|
# 发送标签锁定信号
|
|
|
self.tabLockRequired.emit(True)
|
|
|
self.autoButton.setEnabled(False)
|
|
|
self.stopButton.setEnabled(True)
|
|
|
self.nextButton.setEnabled(False)
|
|
|
self.resetButton.setEnabled(False)
|
|
|
self.exportButton.setEnabled(False)
|
|
|
|
|
|
# 如果是首次执行,则初始化步骤结果集合
|
|
|
if self.isFirstRun:
|
|
|
self.stepResults = [] # 重置步骤结果集合
|
|
|
self.tableModel.resetExecutionState()
|
|
|
self.isFirstRun = False # 标记已执行过
|
|
|
|
|
|
self.remainingCycles = self.cycleSpin.value()
|
|
|
self.infiniteCycles = self.infiniteCheckbox.isChecked()
|
|
|
self.timer.start(1000)
|
|
|
|
|
|
def stopAutoExecute(self):
|
|
|
self.isRunning = False # 清除自动执行状态
|
|
|
self.timer.stop()
|
|
|
self.autoButton.setEnabled(True)
|
|
|
self.stopButton.setEnabled(False)
|
|
|
self.nextButton.setEnabled(True)
|
|
|
self.resetButton.setEnabled(True)
|
|
|
self.exportButton.setEnabled(True)
|
|
|
# 注意: 这里不重置isActive,因为执行器仍处于激活状态
|
|
|
# 执行结束时更新完整步骤结果到数据库
|
|
|
if hasattr(self, 'current_execution_id'):
|
|
|
self.dbManager.updateStepResults(self.current_execution_id, self.stepResults)
|
|
|
|
|
|
def autoExecuteStep(self):
|
|
|
if self.currentIndex < self.tableModel.rowCount():
|
|
|
step_info = self.tableModel.getStepInfo(self.currentIndex)
|
|
|
if step_info and not step_info['isMain']:
|
|
|
self.executeStep(self.currentIndex)
|
|
|
self.currentIndex += 1
|
|
|
else:
|
|
|
if self.infiniteCycles or self.remainingCycles > 0:
|
|
|
if not self.infiniteCycles:
|
|
|
self.remainingCycles -= 1
|
|
|
self.timer.stop()
|
|
|
self.currentIndex = 0
|
|
|
|
|
|
should_continue = self.infiniteCycles or self.remainingCycles > 0
|
|
|
if should_continue:
|
|
|
self.tableModel.resetAll()
|
|
|
if hasattr(self, 'current_execution_id'):
|
|
|
self.dbManager.updateStepResults(self.current_execution_id, self.stepResults)
|
|
|
self.timer.start()
|
|
|
else:
|
|
|
self.stopAutoExecute()
|
|
|
else:
|
|
|
self.stopAutoExecute()
|
|
|
|
|
|
def executeNextStep(self):
|
|
|
self.isActive = True
|
|
|
# 发送标签锁定信号
|
|
|
self.tabLockRequired.emit(True)
|
|
|
if self.currentIndex < self.tableModel.rowCount():
|
|
|
step_info = self.tableModel.getStepInfo(self.currentIndex)
|
|
|
if step_info and not step_info['isMain']:
|
|
|
self.executeStep(self.currentIndex)
|
|
|
self.currentIndex += 1
|
|
|
# 手动执行不需要修改isRunning状态
|
|
|
|
|
|
def executeStep(self, row):
|
|
|
stepInfo = self.tableModel.getStepInfo(row)
|
|
|
if not stepInfo:
|
|
|
return False
|
|
|
|
|
|
print(f"开始执行步骤 {stepInfo['stepId']}: {stepInfo['description']}")
|
|
|
|
|
|
result = self.handleStep(row, stepInfo)
|
|
|
|
|
|
# 确保result总是布尔值(避免None导致数据库错误)
|
|
|
if result is None:
|
|
|
print(f"警告:步骤 {stepInfo['stepId']} 返回了None结果,设置为False")
|
|
|
result = False
|
|
|
|
|
|
# 存储执行结果到数据库
|
|
|
execution_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
|
# 新增:获取当前执行的ID(每次执行一个唯一的execution_id)
|
|
|
if not hasattr(self, 'current_execution_id'):
|
|
|
self.current_execution_id = self.dbManager.insertProcedureExecution(
|
|
|
self.procedureId,
|
|
|
execution_time
|
|
|
)
|
|
|
|
|
|
# 存储执行结果到内存集合
|
|
|
step_result = {
|
|
|
'step_id': stepInfo['stepId'],
|
|
|
'step_description': stepInfo['description'],
|
|
|
'execution_time': execution_time,
|
|
|
'result': result
|
|
|
}
|
|
|
self.stepResults.append(step_result)
|
|
|
|
|
|
# 更新数据库中的步骤结果集合
|
|
|
self.dbManager.updateStepResults(self.current_execution_id, self.stepResults)
|
|
|
|
|
|
success = self.tableModel.updateStepResult(row, result, datetime.now())
|
|
|
|
|
|
return result
|
|
|
|
|
|
def handleStep(self, rowIndex, stepInfo): # 修改参数名
|
|
|
description = stepInfo['description']
|
|
|
stepId = stepInfo['stepId']
|
|
|
|
|
|
print(f"处理步骤 {stepId}: {description}")
|
|
|
|
|
|
if "设置" in description:
|
|
|
return self.performLogin(stepId, description) # 修改方法名
|
|
|
elif "数据导入" in description:
|
|
|
return self.performDataImport(stepId, description) # 修改方法名
|
|
|
elif "验证" in description:
|
|
|
return self.performValidation(stepId, description) # 修改方法名
|
|
|
elif "导出" in description:
|
|
|
return self.performExport(stepId, description) # 修改方法名
|
|
|
elif "备份" in description:
|
|
|
return self.performBackup(stepId, description) # 修改方法名
|
|
|
elif "发送" in description:
|
|
|
return self.performNotification(stepId, description) # 修改方法名
|
|
|
|
|
|
def simulateExecution(self, stepId, description): # 修改方法名
|
|
|
import random
|
|
|
return random.random() < 0.9
|
|
|
|
|
|
def performLogin(self, stepId, description): # 修改方法名
|
|
|
print(f"执行登录操作 (步骤 {stepId}): {description}")
|
|
|
return True
|
|
|
|
|
|
def performDataImport(self, stepId, description): # 修改方法名
|
|
|
import random
|
|
|
return random.random() < 0.95
|
|
|
|
|
|
def performValidation(self, stepId, description): # 修改方法名
|
|
|
import random
|
|
|
return random.random() < 0.85
|
|
|
|
|
|
def performExport(self, stepId, description): # 修改方法名
|
|
|
import random
|
|
|
return random.random() < 0.98
|
|
|
|
|
|
def performBackup(self, stepId, description): # 修改方法名
|
|
|
return True
|
|
|
|
|
|
def performNotification(self, stepId, description): # 修改方法名
|
|
|
import random
|
|
|
return random.random() < 0.99
|
|
|
|
|
|
def performWait(self, stepId, description): # 修改方法名
|
|
|
import time
|
|
|
waitTime = 1 # 修改变量名
|
|
|
if "s" in description:
|
|
|
try:
|
|
|
waitTime = int(description.split("等待")[1].split("s")[0].strip())
|
|
|
except:
|
|
|
pass
|
|
|
time.sleep(waitTime)
|
|
|
return True
|
|
|
|
|
|
def performSetting(self, stepId, description): # 修改方法名
|
|
|
return True
|
|
|
|
|
|
def resetExecution(self):
|
|
|
self.tableModel.resetAll()
|
|
|
self.currentIndex = 0
|
|
|
self.stopAutoExecute()
|
|
|
self.cycleSpin.setValue(1)
|
|
|
self.infiniteCheckbox.setChecked(False)
|
|
|
self.isRunning = False
|
|
|
self.isActive = False
|
|
|
self.isFirstRun = True # 重置标志位
|
|
|
self.stepResults = [] # 重置步骤结果集合
|
|
|
|
|
|
# 新增:重置当前执行ID
|
|
|
if hasattr(self, 'current_execution_id'):
|
|
|
del self.current_execution_id
|
|
|
|
|
|
# 发送标签解锁信号
|
|
|
self.tabLockRequired.emit(False)
|
|
|
|
|
|
# 修改方法签名,添加file_path参数
|
|
|
def generateReport(self):
|
|
|
# 生成规程全名(名称+编号)
|
|
|
proc_full_name = f"{self.procedureData['规程信息']['规程名称']}({self.procedureData['规程信息']['规程编号']})"
|
|
|
|
|
|
# 弹出文件选择对话框
|
|
|
default_name = f"{proc_full_name}_报告_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
|
|
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
|
self,
|
|
|
"保存报告",
|
|
|
default_name,
|
|
|
"Word文档 (*.docx)"
|
|
|
)
|
|
|
|
|
|
# 如果用户取消选择,则直接返回
|
|
|
if not file_path:
|
|
|
return
|
|
|
|
|
|
doc = Document()
|
|
|
|
|
|
# 生成规程全名(名称+编号)
|
|
|
proc_full_name = f"{self.procedureData['规程信息']['规程名称']}({self.procedureData['规程信息']['规程编号']})"
|
|
|
title = doc.add_paragraph(f"{proc_full_name} - 测试报告")
|
|
|
title.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
|
|
title.runs[0].font.size = Pt(18)
|
|
|
title.runs[0].bold = True
|
|
|
|
|
|
info = doc.add_paragraph()
|
|
|
info.add_run("规程信息:\n").bold = True
|
|
|
info.add_run(f"规程编号: {self.procedureData['规程信息']['规程编号']}\n")
|
|
|
info.add_run(f"规程类型: {self.procedureData['规程信息']['规程类型']}\n")
|
|
|
|
|
|
case_info = doc.add_paragraph()
|
|
|
case_info.add_run("测试用例信息:\n").bold = True
|
|
|
case_info.add_run(f"测试用例: {self.procedureData['测试用例信息']['测试用例']}\n")
|
|
|
case_info.add_run(f"用例编号: {self.procedureData['测试用例信息']['用例编号']}\n")
|
|
|
case_info.add_run(f"工况描述: {self.procedureData['测试用例信息']['工况描述']}\n")
|
|
|
|
|
|
stats = doc.add_paragraph()
|
|
|
stats.add_run("执行统计:\n").bold = True
|
|
|
total = self.tableModel.rowCount()
|
|
|
success = sum(1 for s in self.tableModel.stepData if s['result'])
|
|
|
failure = total - success
|
|
|
stats.add_run(f"总步骤数: {total}\n成功数: {success}\n失败数: {failure}\n")
|
|
|
|
|
|
# 优化步骤表格展示
|
|
|
steps_table = doc.add_table(rows=1, cols=6)
|
|
|
steps_table.style = 'Table Grid' # 添加表格边框样式
|
|
|
hdr_cells = steps_table.rows[0].cells
|
|
|
|
|
|
# 设置表头
|
|
|
headers = ['步骤ID', '步骤类型', '步骤描述', '执行时间', '执行结果', '状态']
|
|
|
for i, header in enumerate(headers):
|
|
|
hdr_cells[i].text = header
|
|
|
# 设置表头样式(加粗居中)
|
|
|
for paragraph in hdr_cells[i].paragraphs:
|
|
|
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
|
|
run = paragraph.runs[0]
|
|
|
run.font.bold = True
|
|
|
|
|
|
# 填充表格数据
|
|
|
for step in self.tableModel.stepData:
|
|
|
row_cells = steps_table.add_row().cells
|
|
|
|
|
|
# 步骤ID
|
|
|
row_cells[0].text = step['stepId']
|
|
|
|
|
|
# 步骤类型
|
|
|
step_type = "主步骤" if step['isMain'] else "子步骤"
|
|
|
row_cells[1].text = step_type
|
|
|
|
|
|
# 步骤描述
|
|
|
row_cells[2].text = step['description']
|
|
|
|
|
|
# 执行时间
|
|
|
time_text = step['time'].strftime("%Y-%m-%d %H:%M:%S") if step['time'] else 'N/A'
|
|
|
row_cells[3].text = time_text
|
|
|
|
|
|
# 执行结果(带颜色标记)
|
|
|
result_cell = row_cells[4]
|
|
|
if step['result'] is True:
|
|
|
result_text = '成功'
|
|
|
# 确保单元格有run对象
|
|
|
if not result_cell.paragraphs[0].runs:
|
|
|
result_cell.paragraphs[0].add_run(result_text)
|
|
|
result_cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(0, 128, 0) # 绿色
|
|
|
elif step['result'] is False:
|
|
|
result_text = '失败'
|
|
|
# 确保单元格有run对象
|
|
|
if not result_cell.paragraphs[0].runs:
|
|
|
result_cell.paragraphs[0].add_run(result_text)
|
|
|
result_cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 0, 0) # 红色
|
|
|
else:
|
|
|
result_text = '未执行'
|
|
|
result_cell.text = result_text
|
|
|
|
|
|
# 状态
|
|
|
status = '已执行' if step['executed'] else '未执行'
|
|
|
row_cells[5].text = status
|
|
|
|
|
|
# 设置表格自动适应宽度
|
|
|
for col in steps_table.columns:
|
|
|
col.width = doc.sections[0].page_width // len(headers)
|
|
|
|
|
|
# 设置默认文件名(包含规程全名)
|
|
|
if not file_path:
|
|
|
filename = f"{proc_full_name}_报告_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
|
|
|
else:
|
|
|
filename = file_path
|
|
|
|
|
|
doc.save(filename)
|
|
|
|
|
|
# 确保导出成功时显示提示信息
|
|
|
QMessageBox.information(self, "报告生成", f"报告已生成: {filename}")
|
|
|
return filename |