以下是main_window.py的完整代码:
# 主窗口 - PyQt6 版本
import os
import sys
import datetime
import logging
import faulthandler # 启用详细日志和调试模式
import tracemalloc # 内存监控
import ctypes
# 启用故障处理程序
faulthandler.enable()
# 添加虚拟环境检查
if not hasattr(sys, 'real_prefix') and not hasattr(sys, 'base_prefix'):
logging.warning("警告: 未在虚拟环境中运行,建议使用虚拟环境")
# 配置详细日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("app_debug.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
logger.info("应用程序启动")
logger.info(f"Python 路径: {sys.executable}")
logger.info(f"参数: {sys.argv}")
# 添加更多诊断信息
logger.info(f"系统路径: {sys.path}")
logger.info(f"工作目录: {os.getcwd()}")
# 启用详细错误报告
faulthandler.enable()
os.environ['QT_DEBUG_PLUGINS'] = '1' # 启用 Qt 插件调试
os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = '--disable-gpu --no-sandbox' # 禁用 GPU 加速
# 设置 Windows 错误报告
if sys.platform == 'win32':
ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002) # SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX
# 设置堆栈保护
os.environ['QT_FATAL_CRITICALS'] = '1'
os.environ['QT_FATAL_WARNINGS'] = '1'
# 确保先导入PyQt6核心模块
try:
from PyQt6.QtCore import QObject, pyqtSignal, PYQT_VERSION_STR, Qt, QRectF, QTimer
from PyQt6.QtGui import QIcon, QPixmap, QColor, QFont, QAction
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QFileDialog, QSplitter,
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QComboBox,
QLabel, QStatusBar, QToolBar, QDockWidget, QTabWidget, QMessageBox,
QInputDialog, QDialog, QMenu
)
logger.info(f"PyQt6 版本: {PYQT_VERSION_STR}")
except ImportError as e:
logger.error(f"导入 PyQt6 失败: {str(e)}")
# 添加更详细的错误信息
import traceback
logger.error(traceback.format_exc())
# 无法继续运行,退出程序
sys.exit(1)
# 然后导入其他模块
from src.auto_print_system.ui.template_editor import TemplateCanvas, TextRegionItem, QRRegionItem, LogoRegionItem
from src.auto_print_system.ui.data_mapper import DataMapperWidget
from src.auto_print_system.ui.preview_dialog import PreviewDialog
from src.auto_print_system.ui.history_dialog import HistoryDialog
from src.auto_print_system.core.project_manager import ProjectManager
from src.auto_print_system.core.file_compositor import FileCompositor
from src.auto_print_system.core.font_manager import FontManager
logger = logging.getLogger(__name__)
# 定义属性面板类
class BasePropertiesPanel(QWidget):
propertyChanged = pyqtSignal(str, object) # 定义信号
def __init__(self, parent=None):
super().__init__(parent)
self.region = None
self.setup_ui()
def setup_ui(self):
"""子类实现具体UI布局"""
pass
def set_region(self, region):
"""设置当前区域"""
self.region = region
self.update_ui_from_region()
def update_ui_from_region(self):
"""根据区域更新UI"""
pass
def update_region_from_ui(self):
"""根据UI更新区域"""
pass
class TextPropertiesPanel(BasePropertiesPanel):
def setup_ui(self):
layout = QVBoxLayout(self)
layout.addWidget(QLabel("文本属性面板 - 实际实现需要添加更多控件"))
# 这里添加实际的文本属性控件
self.setLayout(layout)
def update_ui_from_region(self):
if self.region and isinstance(self.region, TextRegionItem):
# 根据区域更新UI控件
pass
def update_region_from_ui(self):
if self.region:
# 根据UI更新区域属性
pass
class QRPropertiesPanel(BasePropertiesPanel):
def setup_ui(self):
layout = QVBoxLayout(self)
layout.addWidget(QLabel("二维码属性面板 - 实际实现需要添加更多控件"))
# 这里添加实际的二维码属性控件
self.setLayout(layout)
def update_ui_from_region(self):
if self.region and isinstance(self.region, QRRegionItem):
# 根据区域更新UI控件
pass
class LogoPropertiesPanel(BasePropertiesPanel):
def setup_ui(self):
layout = QVBoxLayout(self)
layout.addWidget(QLabel("Logo属性面板 - 实际实现需要添加更多控件"))
# 这里添加实际的Logo属性控件
self.setLayout(layout)
def update_ui_from_region(self):
if self.region and isinstance(self.region, LogoRegionItem):
# 根据区域更新UI控件
pass
class MainWindow(QMainWindow):
def __init__(self, data_path=None):
super().__init__()
#简化初始化
self.central_widget = None
self.logo_path = None
self.project_manager = None
self.btn_generate = None
self.field_mapping_widget = None
self.btn_add_text = None
self.btn_add_qr = None
self.current_project = None
self.spot_color_path = None
self.compositor = None
self.font_manager = None
self.left_layout = None
self.btn_upload = None
self.btn_import_data = None
self.template_canvas = None
self.status_bar = None
self.btn_rerun = None
self.btn_history = None
self.data_mapper = None
self.btn_save_config = None
self.right_layout = None
self.logo_props_panel = None
self.text_props_panel = None
self.props_tabs = None
self.qr_props_panel = None
self.history_combo = None
self.logger = logging.getLogger(__name__)
self.logger.info("主窗口初始化开始")
self.data_path = data_path
try:
# 尝试简化初始化
self.logger.info("尝试基本初始化...")
self.setWindowTitle("自动打印系统")
self.resize(800, 600)
# 延迟加载复杂组件
self.logger.info("延迟加载复杂组件")
QTimer.singleShot(100, self.delayed_init)
except Exception as e:
self.logger.exception("主窗口初始化期间发生错误")
raise
def delayed_init(self):
try:
self.logger.info("开始延迟初始化")
self.init_components()
self.logger.info("组件初始化完成")
# 其他初始化...
except Exception as e:
self.logger.exception("延迟初始化期间发生错误")
# 显示错误消息
QMessageBox.critical(
self,
"初始化错误",
f"初始化过程中发生错误:\n{str(e)}",
QMessageBox.StandardButton.Ok
)
self.close()
self.data_path = self.data_path or os.path.join(os.path.dirname(__file__), "../../../data")
self.setWindowTitle("自动拼版系统")
self.setGeometry(100, 100, 1200, 800)
# 声明所有实例变量
self.project_manager = None
self.font_manager = None
self.compositor = None
self.status_bar = None
self.current_project = None
self.logo_path = None
self.spot_color_path = None
# 布局和控件变量
self.left_layout = None
self.btn_upload = None
self.btn_add_text = None
self.btn_add_qr = None
self.template_canvas = None
self.props_tabs = None
self.text_props_panel = None
self.qr_props_panel = None
self.logo_props_panel = None
self.right_layout = None
self.btn_import_data = None
self.btn_save_config = None
self.data_mapper = None
self.field_mapping_widget = None
self.history_combo = None
self.btn_history = None
self.btn_generate = None
self.btn_rerun = None
# 初始化核心组件
self.init_components()
# 创建项目管理器
self.project_manager = ProjectManager(self.data_path)
# 创建字体管理器
self.font_manager = FontManager(self.data_path)
# 创建合成引擎
self.compositor = FileCompositor(self.data_path, self.font_manager)
# 设置状态栏
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.status_bar.showMessage("就绪")
# 当前项目
self.current_project = None
self.logo_path = None
self.spot_color_path = None
# 加载样式
self.apply_styles()
def init_components(self):
"""初始化主界面组件"""
self.logger.info("开始初始化组件")
# 逐步初始化各个组件,添加日志
self.logger.info("创建中央部件...")
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.logger.info("创建布局...")
self.layout = QVBoxLayout(self.central_widget)
# 逐步添加其他组件,每一步都添加日志
# 例如先注释掉停靠窗口的创建
# self.logger.info("创建左侧停靠窗口...")
# self.create_left_dock()
# 只添加一个简单的标签用于测试
test_label = QLabel("应用程序启动成功,基本UI已加载")
self.layout.addWidget(test_label)
self.logger.info("基本组件初始化完成")
# def create_left_dock(self):
# # 暂时注释掉停靠窗口创建
# pass
# 创建菜单栏
self.create_menus()
# 创建工具栏
self.create_toolbar()
# 创建主中心区域
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# 左侧模板编辑区(占40%)
left_dock = QDockWidget("模板编辑区", self)
left_dock.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetMovable |
QDockWidget.DockWidgetFeature.DockWidgetFloatable)
left_widget = QWidget()
self.left_layout = QVBoxLayout(left_widget)
# 模板操作按钮
btn_layout = QHBoxLayout()
self.btn_upload = QPushButton("上传模板")
self.btn_upload.clicked.connect(self.upload_template)
self.btn_add_text = QPushButton("添加文本框")
self.btn_add_text.clicked.connect(self.add_text_region)
self.btn_add_qr = QPushButton("添加二维码区")
self.btn_add_qr.clicked.connect(self.add_qr_region)
btn_layout.addWidget(self.btn_upload)
btn_layout.addWidget(self.btn_add_text)
btn_layout.addWidget(self.btn_add_qr)
self.left_layout.addLayout(btn_layout)
# 模板编辑画布
self.template_canvas = TemplateCanvas(data_path=self.data_path)
self.template_canvas.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.template_canvas.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;")
self.left_layout.addWidget(self.template_canvas)
# 属性面板(标签页)
self.props_tabs = QTabWidget()
self.text_props_panel = TextPropertiesPanel()
self.qr_props_panel = QRPropertiesPanel()
self.logo_props_panel = LogoPropertiesPanel()
self.props_tabs.addTab(self.text_props_panel, "文本属性")
self.props_tabs.addTab(self.qr_props_panel, "二维码属性")
self.props_tabs.addTab(self.logo_props_panel, "Logo属性")
# 连接信号
if hasattr(self.text_props_panel, 'propertyChanged'):
self.text_props_panel.propertyChanged.connect(self.handle_property_change)
if hasattr(self.qr_props_panel, 'propertyChanged'):
self.qr_props_panel.propertyChanged.connect(self.handle_property_change)
if hasattr(self.logo_props_panel, 'propertyChanged'):
self.logo_props_panel.propertyChanged.connect(self.handle_property_change)
self.left_layout.addWidget(self.props_tabs)
left_dock.setWidget(left_widget)
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, left_dock)
# 右侧数据区域(占60%)
right_dock = QDockWidget("数据映射区", self)
right_widget = QWidget()
self.right_layout = QVBoxLayout(right_widget)
# 数据操作按钮
data_btn_layout = QHBoxLayout()
self.btn_import_data = QPushButton("导入数据")
self.btn_import_data.clicked.connect(self.import_data)
self.btn_save_config = QPushButton("保存配置")
self.btn_save_config.clicked.connect(self.save_config)
data_btn_layout.addWidget(self.btn_import_data)
data_btn_layout.addWidget(self.btn_save_config)
self.right_layout.addLayout(data_btn_layout)
# 数据映射表格
self.data_mapper = DataMapperWidget(self)
self.right_layout.addWidget(self.data_mapper, 1)
# 字段映射区域
self.right_layout.addWidget(QLabel("字段映射配置:"))
self.field_mapping_widget = QLabel("拖拽字段到模板区域进行映射")
self.field_mapping_widget.setStyleSheet("min-height: 100px; background-color: #fff; border: 1px dashed #999;")
self.right_layout.addWidget(self.field_mapping_widget)
right_dock.setWidget(right_widget)
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, right_dock)
# 底部操作区
bottom_widget = QWidget()
bottom_layout = QHBoxLayout(bottom_widget)
# 翻单历史选择
bottom_layout.addWidget(QLabel("翻单历史:"))
self.history_combo = QComboBox()
self.history_combo.setMinimumWidth(200)
self.history_combo.currentIndexChanged.connect(self.on_history_selected)
self.refresh_history_combo()
# 历史管理按钮
self.btn_history = QPushButton("管理")
self.btn_history.setToolTip("管理历史记录")
self.btn_history.clicked.connect(self.manage_history)
bottom_layout.addWidget(self.history_combo)
bottom_layout.addWidget(self.btn_history)
bottom_layout.addSpacing(20)
# 合成按钮
self.btn_generate = QPushButton("开始合成")
self.btn_generate.setStyleSheet(
"background-color: #4CAF50; color: white; font-weight: bold; padding: 8px 16px;")
self.btn_generate.clicked.connect(self.generate_output)
bottom_layout.addWidget(self.btn_generate)
# 翻单按钮
self.btn_rerun = QPushButton("执行翻单")
self.btn_rerun.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold; padding: 8px 16px;")
self.btn_rerun.clicked.connect(self.rerun_project)
self.btn_rerun.setEnabled(False)
bottom_layout.addWidget(self.btn_rerun)
main_layout.addWidget(bottom_widget)
def create_menus(self):
"""创建菜单栏"""
menubar = self.menuBar()
# 文件菜单
file_menu = menubar.addMenu('文件')
# 使用 QAction 创建菜单项
new_action = QAction('新建项目', self)
open_action = QAction('打开项目', self)
save_action = QAction('保存项目', self)
exit_action = QAction('退出', self)
exit_action.triggered.connect(self.close)
save_project_action = QAction('保存项目', self)
save_project_action.triggered.connect(self.save_project)
file_menu.addAction(save_project_action)
open_project_action = QAction('打开项目', self)
open_project_action.triggered.connect(self.open_project)
file_menu.addAction(open_project_action)
file_menu.addAction(new_action)
file_menu.addAction(open_action)
file_menu.addAction(save_action)
file_menu.addSeparator()
file_menu.addAction(exit_action)
# 编辑菜单
edit_menu = menubar.addMenu('编辑')
edit_menu.addAction('撤销')
edit_menu.addAction('重做')
edit_menu.addSeparator()
font_action = QAction('字体管理', self)
font_action.triggered.connect(self.open_font_manager)
edit_menu.addAction(font_action)
edit_menu.addAction('专色设置')
# 视图菜单
view_menu = menubar.addMenu('视图')
view_menu.addAction('放大')
view_menu.addAction('缩小')
view_menu.addAction('实际大小')
# 帮助菜单
help_menu = menubar.addMenu('帮助')
help_menu.addAction('用户手册')
about_action = QAction('关于', self)
about_action.triggered.connect(self.show_about)
help_menu.addAction(about_action)
def create_toolbar(self):
"""创建工具栏"""
toolbar = QToolBar("主工具栏")
self.addToolBar(toolbar)
# 添加工具按钮
toolbar.addAction("打开模板", self.upload_template)
toolbar.addAction("导入数据", self.import_data)
toolbar.addSeparator()
toolbar.addAction("添加文本", self.add_text_region)
toolbar.addAction("添加二维码", self.add_qr_region)
toolbar.addSeparator()
toolbar.addAction("开始合成", self.generate_output)
def apply_styles(self):
"""应用基本样式"""
self.setStyleSheet("""
QMainWindow {
background-color: #f5f5f5;
}
QPushButton {
padding: 5px 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
QPushButton:hover {
background-color: #e9e9e9;
}
QDockWidget {
background-color: #f0f0f0;
border: 1px solid #ddd;
titlebar-close-icon: url(resources/icons/close.png);
titlebar-normal-icon: url(resources/icons/restore.png);
}
QDockWidget::title {
background: #e0e0e0;
padding: 4px;
text-align: center;
font-weight: bold;
}
QTabWidget::pane {
border: 1px solid #ddd;
top: 1px;
background: white;
}
QTabBar::tab {
background: #e0e0e0;
border: 1px solid #ccc;
padding: 6px 12px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
QTabBar::tab:selected {
background: white;
border-bottom-color: white;
}
""")
# ===== 功能槽函数 =====
def upload_template(self):
"""上传模板图片"""
file_path, _ = QFileDialog.getOpenFileName(
self, "选择模板图片", self.data_path,
"图像文件 (*.png *.jpg *.jpeg *.bmp);;PDF文件 (*.pdf)"
)
if file_path:
# 加载模板到画布
if self.template_canvas.load_template(file_path):
self.status_bar.showMessage(f"已加载模板: {os.path.basename(file_path)}")
# 更新区域信息
self.update_regions()
def import_data(self):
"""导入数据文件"""
file_path, _ = QFileDialog.getOpenFileName(
self, "选择数据文件", self.data_path,
"CSV文件 (*.csv);;Excel文件 (*.xlsx *.xls)"
)
if file_path:
self.status_bar.showMessage(f"已导入数据: {file_path}")
# 解析并显示数据
self.data_mapper.import_data(file_path)
def add_text_region(self):
"""添加文本区域"""
self.template_canvas.set_mode(TemplateCanvas.MODE_TEXT)
self.status_bar.showMessage("请在模板上框选文本区域")
def add_qr_region(self):
"""添加二维码区域"""
self.template_canvas.set_mode(TemplateCanvas.MODE_QR)
self.status_bar.showMessage("请在模板上框选二维码区域")
def save_config(self):
"""保存当前配置到项目文件"""
if not self.template_canvas or not self.template_canvas.template_path:
self.status_bar.showMessage("请先加载模板")
return
# 获取所有区域数据
regions_data = self.template_canvas.get_regions()
# 获取映射关系
mapping = self.data_mapper.get_mapping()
# 创建项目数据
project_data = {
'template_path': self.template_canvas.template_path,
'regions': regions_data,
'mapping': mapping
}
# 保存到文件
project_name = os.path.basename(self.template_canvas.template_path).split('.')[0]
self.status_bar.showMessage(f"配置已保存: {project_name}")
# 实际保存到文件
config_path = self.project_manager.save_project(project_data, project_name)
if config_path:
QMessageBox.information(self, "保存成功", f"项目已保存:\n{config_path}")
self.refresh_history_combo()
def generate_output(self):
"""开始合成"""
self.status_bar.showMessage("合成中...")
# 检查必要组件
if not self.template_canvas or not self.template_canvas.template_path:
self.status_bar.showMessage("请先加载模板")
QMessageBox.warning(self, "错误", "请先加载模板")
return
if not self.data_mapper.data_frame:
self.status_bar.showMessage("请先导入数据")
QMessageBox.warning(self, "错误", "请先导入数据")
return
# 获取区域和映射
regions = self.template_canvas.get_regions()
mapping = self.data_mapper.get_mapping()
# 生成输出文件名
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
output_dir = os.path.join(self.data_path, "outputs")
os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(output_dir, f"output_{timestamp}.pdf")
# 执行合成
success, logs = self.compositor.composite(
template_path=self.template_canvas.template_path,
regions=regions,
mapping=mapping,
data_source=self.data_mapper.data_frame,
output_path=output_path,
logo_path=self.logo_path if hasattr(self, 'logo_path') else "",
spot_color_path=self.spot_color_path if hasattr(self, 'spot_color_path') else ""
)
# 显示结果
if success:
self.status_bar.showMessage(f"合成完成! 输出文件: {output_path}")
# 显示预览对话框
preview_dialog = PreviewDialog(output_path, self)
preview_dialog.exec()
else:
# 显示错误日志
error_msg = "\n".join(logs[-5:]) if logs else "未知错误"
QMessageBox.critical(self, "合成错误", f"合成过程中发生错误:\n{error_msg}")
def preview_current(self):
"""预览当前配置"""
if not self.template_canvas or not self.template_canvas.template_path:
QMessageBox.warning(self, "预览", "请先加载模板")
return
if not self.data_mapper.data_frame or len(self.data_mapper.data_frame) == 0:
QMessageBox.warning(self, "预览", "请先导入数据")
return
# 获取第一行数据
data_row = self.data_mapper.data_frame.iloc[0].to_dict()
# 获取区域和映射
regions = self.template_canvas.get_regions()
mapping = self.data_mapper.get_mapping()
# 创建临时预览文件
preview_path = os.path.join(self.compositor.temp_dir, "preview.png")
# 执行合成
if hasattr(self.compositor, 'composite_single'):
success, logs = self.compositor.composite_single(
template_path=self.template_canvas.template_path,
regions=regions,
mapping=mapping,
data_row=data_row,
output_path=preview_path,
logo_path=self.logo_path if hasattr(self, 'logo_path') else "",
spot_color_path=self.spot_color_path if hasattr(self, 'spot_color_path') else ""
)
else:
QMessageBox.warning(self, "预览错误", "composite_single方法未实现")
return
if success:
# 显示预览对话框
preview_dialog = PreviewDialog(preview_path, self)
preview_dialog.exec()
else:
error_msg = "\n".join(logs) if logs else "未知错误"
QMessageBox.critical(self, "预览错误", f"预览生成失败:\n{error_msg}")
def open_font_manager(self):
"""打开字体管理器"""
self.status_bar.showMessage("打开字体管理器")
QMessageBox.information(self, "字体管理", "字体管理功能将在后续版本中实现")
def update_regions(self):
"""更新模板区域信息"""
if not self.template_canvas:
return
# 获取所有区域
regions = [region.get_name() for region in self.template_canvas.regions]
# 更新数据映射模块
self.data_mapper.update_regions(regions)
def update_property_panel(self, region):
"""更新属性面板显示"""
if isinstance(region, TextRegionItem):
self.props_tabs.setCurrentIndex(0)
self.text_props_panel.set_region(region)
elif isinstance(region, QRRegionItem):
self.props_tabs.setCurrentIndex(1)
self.qr_props_panel.set_region(region)
elif isinstance(region, LogoRegionItem):
self.props_tabs.setCurrentIndex(2)
self.logo_props_panel.set_region(region)
def handle_property_change(self, key, value):
"""处理属性变化"""
self.template_canvas.set_region_property(key, value)
def refresh_history_combo(self):
"""刷新历史记录下拉框"""
self.history_combo.clear()
self.history_combo.addItem("选择历史项目...", None)
history = self.project_manager.get_history()
for item in history:
self.history_combo.addItem(item['name'], item['path'])
def on_history_selected(self, index):
"""历史项目选择事件"""
if index <= 0:
self.current_project = None
self.btn_rerun.setEnabled(False)
return
project_path = self.history_combo.itemData(index)
if project_path:
self.current_project = self.project_manager.load_project(project_path)
self.btn_rerun.setEnabled(True)
def manage_history(self):
"""打开历史记录管理对话框"""
dialog = HistoryDialog(self.project_manager, self)
if dialog.exec() == QDialog.DialogCode.Accepted:
self.refresh_history_combo()
def save_project(self):
"""保存当前项目配置"""
if not self.template_canvas or not self.template_canvas.template_path:
QMessageBox.warning(self, "保存项目", "请先加载模板")
return
if not self.data_mapper.data_frame:
QMessageBox.warning(self, "保存项目", "请先导入数据")
return
# 获取项目数据
project_data = {
'template_path': self.template_canvas.template_path,
'regions': self.template_canvas.get_regions(),
'mapping': self.data_mapper.get_mapping(),
'data_source': os.path.basename(self.data_mapper.data_path) if hasattr(self.data_mapper,
'data_path') else "",
'logo_path': self.logo_path if hasattr(self, 'logo_path') else "",
'spot_color_path': self.spot_color_path if hasattr(self, 'spot_color_path') else "",
'timestamp': datetime.datetime.now().isoformat(),
'description': ""
}
# 输入项目名称
name, ok = QInputDialog.getText(
self, "保存项目", "输入项目名称:",
text=f"项目_{datetime.datetime.now().strftime('%Y%m%d')}"
)
if ok and name:
# 保存项目
config_path = self.project_manager.save_project(project_data, name)
if config_path:
QMessageBox.information(self, "保存成功", f"项目已保存:\n{config_path}")
self.refresh_history_combo()
def open_project(self):
"""打开项目"""
# 使用历史记录对话框选择项目
dialog = HistoryDialog(self.project_manager, self)
if dialog.exec() == QDialog.DialogCode.Accepted:
selected_project = dialog.get_selected_project()
if selected_project:
self.load_project(selected_project['path'])
def load_project(self, project_dir):
"""加载项目到界面"""
project_data = self.project_manager.load_project(project_dir)
if not project_data:
QMessageBox.warning(self, "加载项目", "项目加载失败")
return
# 加载模板
if self.template_canvas.load_template(project_data['template_path']):
self.status_bar.showMessage(f"已加载模板: {os.path.basename(project_data['template_path'])}")
# 清除现有区域
self.template_canvas.clear_regions()
# 添加区域
for region_data in project_data['regions']:
self._create_region_from_data(region_data)
# 更新区域显示
self.update_regions()
# 加载Logo和专色路径
if 'logo_path' in project_data and os.path.exists(project_data['logo_path']):
self.logo_path = project_data['logo_path']
if 'spot_color_path' in project_data and os.path.exists(project_data['spot_color_path']):
self.spot_color_path = project_data['spot_color_path']
# 加载映射关系
self.data_mapper.mapping = project_data['mapping']
self.data_mapper.update_mapping_list()
# 提示加载数据源
data_source = project_data.get('data_source', '')
if data_source:
data_path = self._get_data_source_path(project_data)
if data_path:
self.data_mapper.import_data(data_path)
# 设置当前项目
self.current_project = project_data
self.btn_rerun.setEnabled(True)
# 更新历史记录下拉框
self.refresh_history_combo()
# 选中当前项目
for i in range(self.history_combo.count()):
if self.history_combo.itemData(i) == project_dir:
self.history_combo.setCurrentIndex(i)
break
def _create_region_from_data(self, region_data):
"""根据区域数据创建区域项"""
rect = QRectF(
region_data['rect']['x'],
region_data['rect']['y'],
region_data['rect']['width'],
region_data['rect']['height']
)
region = None
if region_data['type'] == 'text':
region = TextRegionItem(rect)
region.set_name(region_data.get('name', '文本区域'))
if 'font' in region_data:
font = QFont()
font.fromString(region_data['font'])
region.set_font(font)
if 'align' in region_data:
region.set_align(region_data['align'])
if 'color' in region_data:
region.set_color(QColor(region_data['color']))
elif region_data['type'] == 'qr':
region = QRRegionItem(rect)
region.set_name(region_data.get('name', '二维码区域'))
if 'code_type' in region_data:
region.code_type = region_data['code_type']
elif region_data['type'] == 'logo':
# 创建空区域,实际Logo在合成时加载
region = LogoRegionItem(rect, QPixmap())
region.set_name(region_data.get('name', 'Logo区域'))
if 'opacity' in region_data:
region.set_opacity(region_data['opacity'])
# 添加到场景
if region:
self.template_canvas.scene.addItem(region)
self.template_canvas.regions.append(region)
def rerun_project(self):
"""执行翻单操作"""
if not self.current_project:
QMessageBox.warning(self, "翻单", "请先选择项目")
return
# 确认数据源
data_source_path = self._get_data_source_path(self.current_project)
if not data_source_path:
return
# 生成输出文件名
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
output_dir = os.path.join(self.data_path, "outputs")
os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(output_dir, f"翻单_{self.current_project.get('name', 'project')}_{timestamp}.pdf")
# 执行合成
success, logs = self.compositor.composite(
template_path=self.current_project['template_path'],
regions=self.current_project['regions'],
mapping=self.current_project['mapping'],
data_source=data_source_path,
output_path=output_path,
logo_path=self.current_project.get('logo_path', ''),
spot_color_path=self.current_project.get('spot_color_path', '')
)
# 显示结果
if success:
self.status_bar.showMessage(f"翻单完成! 输出文件: {output_path}")
# 显示预览对话框
preview_dialog = PreviewDialog(output_path, self)
preview_dialog.exec()
else:
# 显示错误日志
error_msg = "\n".join(logs[-5:]) if logs else "未知错误"
QMessageBox.critical(self, "翻单错误", f"翻单过程中发生错误:\n{error_msg}")
def _get_data_source_path(self, project):
"""获取数据源文件路径"""
# 1. 检查项目是否指定了数据源
data_source_name = project.get('data_source', '')
if data_source_name:
# 尝试在项目目录中查找
project_dir = os.path.dirname(project['template_path'])
project_data_path = os.path.join(project_dir, data_source_name)
if os.path.exists(project_data_path):
return project_data_path
# 尝试在数据目录中查找
data_dir = os.path.join(self.data_path, "data")
data_path = os.path.join(data_dir, data_source_name)
if os.path.exists(data_path):
return data_path
# 2. 提示用户选择数据文件
file_path, _ = QFileDialog.getOpenFileName(
self, "选择数据文件", self.data_path,
"CSV文件 (*.csv);;Excel文件 (*.xlsx *.xls)"
)
return file_path if file_path else None
def show_about(self):
"""显示关于对话框"""
about_text = (
"<h2>自动拼版系统</h2>"
"<p>版本: 1.0.0</p>"
"<p>基于 PyQt6 构建</p>"
"<p>© 2023 自动拼版系统团队</p>"
)
QMessageBox.about(self, "关于", about_text)
# 测试运行
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
# 检查内存模块
def log_memory():
try:
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
logger.info("内存使用统计:")
for stat in top_stats[:10]:
logger.info(stat)
except Exception as e:
logger.error(f"内存监控失败: {str(e)}")
import atexit
atexit.register(log_memory)
请帮我分析该代码块,并修改调整不合理的地方,以及排查出刚才分析的AttributeError: ‘NoneType’ object has no attribute ‘setEnabled’问题,并输出完整的修改后的main_window.py的代码