templat_editor.py文件已修改,但现在我的main_window.py的完整代码如下:#主窗口 - PyQt6 版本
import os
import cv2
import numpy as np
import sys
import logging
import faulthandler # 用于捕获崩溃信息
import ctypes # Windows API 调用支持
from typing import Optional
from pyzbar.pyzbar import decode
from PyQt6.QtGui import QPainter, QPolygonF, QPixmap
from PyQt6.QtCore import QPointF
from PyQt6.QtCore import PYQT_VERSION_STR, QRectF
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QDockWidget, QWidget,QMainWindow, QStatusBar, \
QTabWidget, QToolBar, QApplication, QMessageBox, QFileDialog, QDoubleSpinBox
from PyQt6.QtWidgets import (
QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox,
QGroupBox, QLineEdit, QFormLayout, QSpinBox, QColorDialog,
QRadioButton, QButtonGroup, QCheckBox, QTextEdit, QFontComboBox
)
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QColor, QFont, QTextCharFormat
from ui.template_editor import TemplateCanvas
# 启用故障处理程序,帮助调试崩溃问题
faulthandler.enable()
# 设置日志系统,记录运行时信息
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()}")
logger.info(f"PyQt6 版本: {'未导入' if 'PYQT_VERSION_STR' not in globals() else PYQT_VERSION_STR}")
# 检查是否运行在虚拟环境中
if not hasattr(sys, 'real_prefix') and not hasattr(sys, 'base_prefix'):
logging.warning("警告: 未在虚拟环境中运行,建议使用虚拟环境")
# 设置调试标志和环境变量
os.environ['QT_DEBUG_PLUGINS'] = '1'
os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = '--disable-gpu --no-sandbox'
# Windows 错误报告设置
if sys.platform == 'win32':
ctypes.windll.kernel32.SetErrorMode.argtypes = [ctypes.c_uint]
ctypes.windll.kernel32.SetErrorMode(ctypes.c_uint(0x0001 | 0x0002))
# 设置堆栈保护
os.environ['QT_FATAL_CRITICALS'] = '1'
os.environ['QT_FATAL_WARNINGS'] = '1'
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):
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):
"""文本属性面板 UI 实现 """
# 定义信号用于与画布通信
textChanged = pyqtSignal(str)
fontChanged = pyqtSignal(QFont)
colorChanged = pyqtSignal(QColor)
alignmentChanged = pyqtSignal(Qt.AlignmentFlag)
positionChanged = pyqtSignal(float, float)
sizeChanged = pyqtSignal(float, float)
def __init__(self, parent=None):
super().__init__(parent)
self.current_font = QFont()
self.current_color = QColor(0, 0, 0) # 默认黑色
self.current_alignment = Qt.AlignmentFlag.AlignLeft
def setup_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(10)
# 文本内容编辑区域
content_group = QGroupBox("文本内容")
content_layout = QVBoxLayout(content_group)
self.text_edit = QTextEdit()
self.text_edit.setPlaceholderText("输入文本内容...")
self.text_edit.setMinimumHeight(80)
self.text_edit.textChanged.connect(self.on_text_changed)
content_layout.addWidget(self.text_edit)
layout.addWidget(content_group)
# 字体属性区域
font_group = QGroupBox("字体属性")
font_layout = QFormLayout(font_group)
# 字体选择
self.font_combo = QFontComboBox()
self.font_combo.currentFontChanged.connect(self.on_font_changed)
# 字号选择
self.font_size_spin = QSpinBox()
self.font_size_spin.setRange(6, 120)
self.font_size_spin.setValue(12)
self.font_size_spin.valueChanged.connect(self.on_font_size_changed)
# 字体样式
style_layout = QHBoxLayout()
self.bold_check = QCheckBox("粗体")
self.italic_check = QCheckBox("斜体")
self.underline_check = QCheckBox("下划线")
self.bold_check.toggled.connect(self.on_style_changed)
self.italic_check.toggled.connect(self.on_style_changed)
self.underline_check.toggled.connect(self.on_style_changed)
style_layout.addWidget(self.bold_check)
style_layout.addWidget(self.italic_check)
style_layout.addWidget(self.underline_check)
# 字体颜色
color_layout = QHBoxLayout()
self.color_label = QLabel()
self.color_label.setFixedSize(20, 20)
self.color_label.setStyleSheet("background-color: black; border: 1px solid gray;")
self.color_btn = QPushButton("选择颜色...")
self.color_btn.clicked.connect(self.choose_color)
color_layout.addWidget(QLabel("颜色:"))
color_layout.addWidget(self.color_label)
color_layout.addWidget(self.color_btn)
color_layout.addStretch()
font_layout.addRow("字体:", self.font_combo)
font_layout.addRow("字号:", self.font_size_spin)
font_layout.addRow("样式:", style_layout)
font_layout.addRow(color_layout)
layout.addWidget(font_group)
# 对齐方式
align_group = QGroupBox("对齐方式")
align_layout = QHBoxLayout(align_group)
self.align_group = QButtonGroup(self)
self.align_left = QRadioButton("左对齐")
self.align_center = QRadioButton("居中对齐")
self.align_right = QRadioButton("右对齐")
self.align_justify = QRadioButton("两端对齐")
self.align_group.addButton(self.align_left, 1)
self.align_group.addButton(self.align_center, 2)
self.align_group.addButton(self.align_right, 3)
self.align_group.addButton(self.align_justify, 4)
self.align_left.setChecked(True)
self.align_group.buttonToggled.connect(self.on_alignment_changed)
align_layout.addWidget(self.align_left)
align_layout.addWidget(self.align_center)
align_layout.addWidget(self.align_right)
align_layout.addWidget(self.align_justify)
layout.addWidget(align_group)
# 位置和尺寸
position_group = QGroupBox("位置和尺寸")
position_layout = QFormLayout(position_group)
self.pos_x_spin = QDoubleSpinBox()
self.pos_x_spin.setRange(0, 1000)
self.pos_x_spin.setSuffix(" mm")
self.pos_x_spin.valueChanged.connect(self.on_position_changed)
self.pos_y_spin = QDoubleSpinBox()
self.pos_y_spin.setRange(0, 1000)
self.pos_y_spin.setSuffix(" mm")
self.pos_y_spin.valueChanged.connect(self.on_position_changed)
self.width_spin = QDoubleSpinBox()
self.width_spin.setRange(10, 500)
self.width_spin.setSuffix(" mm")
self.width_spin.setValue(100)
self.width_spin.valueChanged.connect(self.on_size_changed)
self.height_spin = QDoubleSpinBox()
self.height_spin.setRange(10, 500)
self.height_spin.setSuffix(" mm")
self.height_spin.setValue(30)
self.height_spin.valueChanged.connect(self.on_size_changed)
position_layout.addRow("X 位置:", self.pos_x_spin)
position_layout.addRow("Y 位置:", self.pos_y_spin)
position_layout.addRow("宽度:", self.width_spin)
position_layout.addRow("高度:", self.height_spin)
layout.addWidget(position_group)
self.setLayout(layout)
def set_text_properties(self, text, font, color, alignment, x, y, width, height):
"""设置文本属性值"""
# 文本内容
self.text_edit.setPlainText(text)
# 字体
self.current_font = font
self.font_combo.setCurrentFont(font)
self.font_size_spin.setValue(font.pointSize())
self.bold_check.setChecked(font.bold())
self.italic_check.setChecked(font.italic())
self.underline_check.setChecked(font.underline())
# 颜色
self.current_color = color
self.color_label.setStyleSheet(f"background-color: {color.name()}; border: 1px solid gray;")
# 对齐方式
self.current_alignment = alignment
if alignment == Qt.AlignmentFlag.AlignLeft:
self.align_left.setChecked(True)
elif alignment == Qt.AlignmentFlag.AlignHCenter:
self.align_center.setChecked(True)
elif alignment == Qt.AlignmentFlag.AlignRight:
self.align_right.setChecked(True)
elif alignment == Qt.AlignmentFlag.AlignJustify:
self.align_justify.setChecked(True)
# 位置和尺寸
self.pos_x_spin.setValue(x)
self.pos_y_spin.setValue(y)
self.width_spin.setValue(width)
self.height_spin.setValue(height)
def on_text_changed(self):
"""文本内容变化处理"""
text = self.text_edit.toPlainText()
self.textChanged.emit(text)
def on_font_changed(self, font):
"""字体变化处理"""
self.current_font.setFamily(font.family())
self.fontChanged.emit(self.current_font)
def on_font_size_changed(self, size):
"""字号变化处理"""
self.current_font.setPointSize(size)
self.fontChanged.emit(self.current_font)
def on_style_changed(self, checked):
"""字体样式变化处理"""
self.current_font.setBold(self.bold_check.isChecked())
self.current_font.setItalic(self.italic_check.isChecked())
self.current_font.setUnderline(self.underline_check.isChecked())
self.fontChanged.emit(self.current_font)
def choose_color(self):
"""选择字体颜色"""
color = QColorDialog.getColor()
if color.isValid():
self.current_color = color
self.color_label.setStyleSheet(f"background-color: {color.name()}; border: 1px solid gray;")
self.colorChanged.emit(color)
def on_alignment_changed(self, button, checked):
"""对齐方式变化处理"""
if not checked:
return
align_map = {
1: Qt.AlignmentFlag.AlignLeft,
2: Qt.AlignmentFlag.AlignHCenter,
3: Qt.AlignmentFlag.AlignRight,
4: Qt.AlignmentFlag.AlignJustify
}
align = align_map.get(self.align_group.checkedId(), Qt.AlignmentFlag.AlignLeft)
self.current_alignment = align
self.alignmentChanged.emit(align)
def on_position_changed(self):
"""位置变化处理"""
x = self.pos_x_spin.value()
y = self.pos_y_spin.value()
self.positionChanged.emit(x, y)
def on_size_changed(self):
"""尺寸变化处理"""
width = self.width_spin.value()
height = self.height_spin.value()
self.sizeChanged.emit(width, height)
class QRPropertiesPanel(BasePropertiesPanel):
"""二维码属性面板 UI 实现,包含自动识别功能"""
def __init__(self, parent=None):
super().__init__(parent)
self.qr_position_x = None
self.qr_correction_combo = None
self.qr_content_edit = None
self.qr_position_y = None
self.qr_fixed_check = None
self.apply_btn = None
self.qr_size_spin = None
self.qr_type_combo = None
self.qr_data_label = None
self.detection_status = None
self.qr_selector = None
self.qr_preview = None
self.detect_btn = None
self.detected_qr_codes = [] # 存储检测到的二维码信息
self.current_qr_index = -1 # 当前选中的二维码索引
def setup_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(10)
# 自动检测区域
auto_detect_group = QGroupBox("二维码自动检测")
auto_layout = QVBoxLayout(auto_detect_group)
self.detect_btn = QPushButton("自动识别二维码")
self.detect_btn.clicked.connect(self.auto_detect_qr_codes)
self.detect_btn.setStyleSheet("background-color: #4CAF50; color: white;")
self.detection_status = QLabel("就绪")
self.detection_status.setAlignment(Qt.AlignmentFlag.AlignCenter)
auto_layout.addWidget(self.detect_btn)
auto_layout.addWidget(self.detection_status)
# 二维码选择器
self.qr_selector = QComboBox()
self.qr_selector.currentIndexChanged.connect(self.select_qr_code)
auto_layout.addWidget(QLabel("检测到的二维码:"))
auto_layout.addWidget(self.qr_selector)
# 预览区域
preview_layout = QHBoxLayout()
self.qr_preview = QLabel()
self.qr_preview.setFixedSize(150, 150)
self.qr_preview.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;")
self.qr_preview.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.qr_data_label = QLabel("二维码数据将显示在这里")
self.qr_data_label.setWordWrap(True)
self.qr_data_label.setStyleSheet("background-color: #f8f8f8; padding: 5px;")
preview_layout.addWidget(self.qr_preview)
preview_layout.addWidget(self.qr_data_label, 1)
auto_layout.addLayout(preview_layout)
layout.addWidget(auto_detect_group)
# 二维码属性编辑区域
props_group = QGroupBox("二维码属性")
form_layout = QFormLayout(props_group)
self.qr_type_combo = QComboBox()
self.qr_type_combo.addItems(["QR Code", "Data Matrix", "PDF417", "Aztec"])
self.qr_content_edit = QLineEdit()
self.qr_content_edit.setPlaceholderText("输入二维码内容")
self.qr_size_spin = QDoubleSpinBox()
self.qr_size_spin.setRange(5, 100)
self.qr_size_spin.setValue(30)
self.qr_size_spin.setSuffix(" mm")
self.qr_correction_combo = QComboBox()
self.qr_correction_combo.addItems(["L (低)", "M (中)", "Q (高)", "H (最高)"])
self.qr_correction_combo.setCurrentIndex(1)
self.qr_position_x = QDoubleSpinBox()
self.qr_position_x.setRange(0, 1000)
self.qr_position_x.setSuffix(" mm")
self.qr_position_y = QDoubleSpinBox()
self.qr_position_y.setRange(0, 1000)
self.qr_position_y.setSuffix(" mm")
self.qr_fixed_check = QCheckBox("固定位置")
self.qr_fixed_check.setChecked(True)
form_layout.addRow("类型:", self.qr_type_combo)
form_layout.addRow("内容:", self.qr_content_edit)
form_layout.addRow("尺寸:", self.qr_size_spin)
form_layout.addRow("纠错级别:", self.qr_correction_combo)
form_layout.addRow("位置 X:", self.qr_position_x)
form_layout.addRow("位置 Y:", self.qr_position_y)
form_layout.addRow(self.qr_fixed_check)
# 应用按钮
self.apply_btn = QPushButton("应用更改")
self.apply_btn.clicked.connect(self.apply_qr_properties)
form_layout.addRow(self.apply_btn)
layout.addWidget(props_group)
layout.addStretch(1)
self.setLayout(layout)
# 连接信号
self.qr_content_edit.textChanged.connect(self.update_qr_preview)
self.qr_size_spin.valueChanged.connect(self.update_qr_preview)
self.qr_correction_combo.currentIndexChanged.connect(self.update_qr_preview)
def auto_detect_qr_codes(self):
"""自动检测二维码"""
if not self.region or not self.region.template_image:
self.detection_status.setText("错误: 没有可用的模板图像")
self.detection_status.setStyleSheet("color: red;")
return
try:
# 转换图像为OpenCV格式
qimage = self.region.template_image
width, height = qimage.width(), qimage.height()
ptr = qimage.bits()
ptr.setsize(qimage.sizeInBytes())
img_np = np.array(ptr).reshape(height, width, 4) # RGBA格式
# 转换为BGR格式并转为灰度图
img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGBA2BGR)
gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
# 使用pyzbar检测二维码
self.detected_qr_codes = decode(gray)
if not self.detected_qr_codes:
self.detection_status.setText("未检测到二维码")
self.detection_status.setStyleSheet("color: orange;")
return
# 更新UI显示结果
self.detection_status.setText(f"检测到 {len(self.detected_qr_codes)} 个二维码")
self.detection_status.setStyleSheet("color: green;")
# 更新二维码选择器
self.qr_selector.clear()
for i, qr in enumerate(self.detected_qr_codes):
self.qr_selector.addItem(f"二维码 {i + 1}")
# 自动选择第一个二维码
if self.detected_qr_codes:
self.qr_selector.setCurrentIndex(0)
except Exception as e:
self.detection_status.setText(f"检测失败: {str(e)}")
self.detection_status.setStyleSheet("color: red;")
logger.error(f"二维码检测失败: {str(e)}", exc_info=True)
def select_qr_code(self, index):
"""选择检测到的二维码"""
if index < 0 or index >= len(self.detected_qr_codes):
return
self.current_qr_index = index
qr = self.detected_qr_codes[index]
# 显示二维码数据
qr_data = qr.data.decode('utf-8')
self.qr_data_label.setText(f"类型: {qr.type}\n\n数据:\n{qr_data}")
# 更新属性编辑器
self.qr_content_edit.setText(qr_data)
# 计算二维码位置 (转换为毫米,假设300dpi)
points = qr.polygon
if len(points) >= 4:
# 计算中心点
center_x = sum([p.x for p in points]) / 4
center_y = sum([p.y for p in points]) / 4
# 转换为毫米 (300dpi: 1英寸=25.4mm, 300像素=25.4mm)
mm_per_pixel = 25.4 / 300
self.qr_position_x.setValue(center_x * mm_per_pixel)
self.qr_position_y.setValue(center_y * mm_per_pixel)
# 计算尺寸
width = max(p.x for p in points) - min(p.x for p in points)
height = max(p.y for p in points) - min(p.y for p in points)
size_mm = max(width, height) * mm_per_pixel
self.qr_size_spin.setValue(size_mm)
# 更新预览
self.update_qr_preview()
# 在模板画布上高亮显示选中的二维码
self.highlight_selected_qr(qr)
def highlight_selected_qr(self, qr):
"""在模板画布上高亮显示选中的二维码"""
if not self.region or not self.region.template_image:
return
# 创建原始图像的副本
image = self.region.template_image.copy()
# 创建QPainter绘制高亮框
painter = QPainter(image)
painter.setPen(QColor(255, 0, 0, 200)) # 半透明红色
painter.setBrush(Qt.BrushStyle.NoBrush)
# 绘制二维码边界框
points = qr.polygon
if len(points) == 4:
polygon = QPolygonF()
for point in points:
polygon.append(QPointF(point.x, point.y))
painter.drawPolygon(polygon)
# 绘制中心点
center_x = sum([p.x for p in points]) / 4
center_y = sum([p.y for p in points]) / 4
painter.setBrush(QColor(255, 0, 0, 150))
painter.drawEllipse(QPointF(center_x, center_y), 5, 5)
painter.end()
# 更新画布显示
self.region.setPixmap(QPixmap.fromImage(image))
def update_qr_preview(self):
"""更新二维码预览图像"""
# 在实际应用中,这里应该生成二维码预览图
# 为了简化,我们只显示一个占位符
pixmap = QPixmap(150, 150)
pixmap.fill(QColor(240, 240, 240))
painter = QPainter(pixmap)
painter.setPen(Qt.GlobalColor.darkGray)
painter.drawRect(10, 10, 130, 130)
# 显示二维码类型和内容摘要
qr_type = self.qr_type_combo.currentText()
content = self.qr_content_edit.text()[:15] + "..." if len(
self.qr_content_edit.text()) > 15 else self.qr_content_edit.text()
painter.drawText(QRectF(0, 0, 150, 150), Qt.AlignmentFlag.AlignCenter,
f"{qr_type}\n\n{content}")
painter.end()
self.qr_preview.setPixmap(pixmap)
def apply_qr_properties(self):
"""应用二维码属性更改"""
if not self.region:
return
# 在实际应用中,这里应该更新二维码对象的属性
qr_type = self.qr_type_combo.currentText()
content = self.qr_content_edit.text()
size = self.qr_size_spin.value()
position_x = self.qr_position_x.value()
position_y = self.qr_position_y.value()
logger.info(
f"更新二维码属性: 类型={qr_type}, 内容={content[:20]}..., 尺寸={size}mm, 位置=({position_x},{position_y})mm")
# 这里应该调用画布更新二维码显示
# self.region.update_qr_display()
def update_ui_from_region(self):
"""根据区域数据更新 UI"""
if not self.region:
return
# 在实际应用中,这里应该从区域对象加载二维码属性
# 现在只是设置一些示例值
self.qr_content_edit.setText("https://example.com/product/12345")
self.qr_size_spin.setValue(25.0)
self.qr_position_x.setValue(50.0)
self.qr_position_y.setValue(30.0)
self.update_qr_preview()
def update_region_from_ui(self):
"""根据 UI 数据更新区域属性"""
if not self.region:
return
# 在实际应用中,这里应该更新二维码对象的属性
content = self.qr_content_edit.text()
size = self.qr_size_spin.value()
position_x = self.qr_position_x.value()
position_y = self.qr_position_y.value()
self.region.set_content(content)
self.region.set_size(size)
self.region.set_position(position_x, position_y)
logger.info(f"二维码区域属性已更新")
class LogoPropertiesPanel(BasePropertiesPanel):
"""Logo属性面板 UI 实现"""
def setup_ui(self):
layout = QVBoxLayout(self)
layout.addWidget(QLabel("Logo属性面板"))
self.setLayout(layout)
class MainWindow(QMainWindow):
def __init__(self, data_path: Optional[str] = None):
super().__init__()
self.run_test_add_regions = None
self.logger = logging.getLogger(__name__)
self.logger.info("主窗口初始化开始")
self.setWindowTitle("自动打印系统")
self.resize(800, 600)
self.data_path = data_path or os.path.join(os.path.dirname(__file__), "../../../data")
# 初始化所有实例属性为 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.project_manager = None
self.font_manager = None
self.compositor = None
# 初始化控件
self.init_components()
try:
# 创建项目管理器
from src.auto_print_system.core.project_manager import ProjectManager
self.project_manager = ProjectManager(self.data_path)
# 创建字体管理器
from src.auto_print_system.core.font_manager import FontManager
self.font_manager = FontManager(self.data_path)
# 创建合成引擎
from src.auto_print_system.core.file_compositor import FileCompositor
self.compositor = FileCompositor(self.data_path, self.font_manager)
except ImportError as e:
self.logger.error(f"初始化核心组件失败: {str(e)}", exc_info=True)
QMessageBox.critical(self, "错误", f"初始化核心组件失败:\n{str(e)}")
# 设置状态栏
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) -> None:
"""初始化所有界面组件"""
self.logger.info("开始初始化组件")
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# 左侧模板编辑区
left_dock = QDockWidget("模板编辑区", self)
left_widget = QWidget()
self.left_layout = QVBoxLayout(left_widget)
btn_layout = QHBoxLayout()
self.btn_upload = QPushButton("上传模板")
self.btn_add_text = QPushButton("添加文本框")
self.btn_add_qr = QPushButton("添加二维码区")
self.btn_upload.clicked.connect(self.upload_template)
self.btn_add_text.clicked.connect(self.add_text_region)
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)
try:
from src.auto_print_system.ui.template_editor import TemplateCanvas
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)
except ImportError as e:
self.logger.error(f"初始化模板画布失败: {str(e)}", exc_info=True)
self.template_canvas = None
# 属性面板
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属性")
self.left_layout.addWidget(self.props_tabs)
left_dock.setWidget(left_widget)
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, left_dock)
# 右侧数据映射区
right_dock = QDockWidget("数据映射区", self)
right_widget = QWidget()
self.right_layout = QVBoxLayout(right_widget)
data_btn_layout = QHBoxLayout()
self.btn_import_data = QPushButton("导入数据")
self.btn_save_config = QPushButton("保存配置")
self.btn_import_data.clicked.connect(self.import_data)
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)
try:
from src.auto_print_system.ui.data_mapper import DataMapperWidget
self.data_mapper = DataMapperWidget(self)
self.right_layout.addWidget(self.data_mapper, 1)
except ImportError as e:
self.logger.error(f"初始化数据映射器失败: {str(e)}", exc_info=True)
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)
self.history_combo = QComboBox()
self.btn_history = QPushButton("管理")
self.btn_generate = QPushButton("开始合成")
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(QLabel("翻单历史:"))
bottom_layout.addWidget(self.history_combo)
bottom_layout.addWidget(self.btn_history)
bottom_layout.addSpacing(20)
bottom_layout.addWidget(self.btn_generate)
bottom_layout.addWidget(self.btn_rerun)
main_layout.addWidget(bottom_widget)
# 翻单按钮状态初始化
self.create_menus()
self.create_toolbar()
self.update_rerun_button_state()
# 连接文本属性面板信号到画布
if self.template_canvas:
# 文本内容变化
self.text_props_panel.textChanged.connect(
self.template_canvas.update_selected_text_region_text
)
# 字体变化
self.text_props_panel.fontChanged.connect(
self.template_canvas.update_selected_text_region_font
)
# 颜色变化
self.text_props_panel.colorChanged.connect(
self.template_canvas.update_selected_text_region_color
)
# 对齐方式变化
self.text_props_panel.alignmentChanged.connect(
self.template_canvas.update_selected_text_region_alignment
)
# 位置变化
self.text_props_panel.positionChanged.connect(
self.template_canvas.update_selected_text_region_position
)
# 尺寸变化
self.text_props_panel.sizeChanged.connect(
self.template_canvas.update_selected_text_region_size
)
# ===== 功能槽函数 =====
def update_rerun_button_state(self) -> None:
"""根据项目状态更新翻单按钮的可用性"""
if not hasattr(self, 'template_canvas') or not hasattr(self, 'data_mapper'):
self.logger.warning("模板画布或数据映射器未初始化")
return
can_rerun = (
self.template_canvas is not None and
self.template_canvas.template_item is not None and
self.data_mapper is not None
)
if self.btn_rerun:
self.btn_rerun.setEnabled(can_rerun)
else:
self.logger.warning("翻单按钮未初始化,无法更新状态")
def rerun_project(self) -> None:
"""执行翻单操作"""
if self.btn_rerun is not None:
self.btn_rerun.setEnabled(False) # 防止多次点击
else:
logging.error("btn_rerun 控件未正确初始化")
return
# ... 其他业务逻辑 ...
def create_menus(self) -> None:
"""创建菜单栏"""
menubar = self.menuBar()
file_menu = menubar.addMenu('文件')
new_action = QAction('新建项目', self)
open_action = QAction('打开项目', self)
save_action = QAction('保存项目', self)
exit_action = QAction('退出', self)
exit_action.triggered.connect(self.close)
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('重做')
def create_toolbar(self) -> None:
"""创建工具栏"""
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) -> None:
"""应用基本样式表"""
self.setStyleSheet("""
QMainWindow {
background-color: #f5f5f5;
}
QPushButton {
padding: 5px 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
QPushButton:hover {
background-color: #e9e9e9;
}
""")
def upload_template(self) -> None:
"""上传模板文件"""
self.logger.debug("上传模板操作被触发")
file_dialog = QFileDialog(self)
file_dialog.setNameFilter("模板文件 (*.pdf *.png *.jpg *.jpeg)")
file_dialog.setWindowTitle("选择模板文件")
if file_dialog.exec():
template_path = file_dialog.selectedFiles()[0]
self.logger.info(f"选择了模板文件: {template_path}")
try:
if self.template_canvas:
success = self.template_canvas.load_template(template_path)
if success:
self.status_bar.showMessage(f"模板加载成功: {os.path.basename(template_path)}")
else:
self.status_bar.showMessage("模板加载失败,请检查文件格式")
self.logger.error(f"模板加载失败: {template_path}")
else:
self.logger.error("模板画布未初始化")
self.status_bar.showMessage("模板画布未初始化,无法加载模板")
except Exception as e:
self.logger.error(f"模板加载失败: {str(e)}", exc_info=True)
self.status_bar.showMessage("模板加载失败,请检查文件格式或权限")
def add_text_region(self) -> None:
"""添加文本区域"""
self.logger.debug("添加文本区域操作被触发")
if self.template_canvas:
try:
self.template_canvas.set_mode(TemplateCanvas.MODE_TEXT)
self.status_bar.showMessage("已切换到【添加文本区域】模式")
except Exception as e:
self.logger.error(f"设置文本区域模式失败: {str(e)}", exc_info=True)
self.status_bar.showMessage("设置模式失败,请查看日志")
else:
self.logger.error("模板画布未初始化")
self.status_bar.showMessage("模板画布未初始化,无法添加文本区域")
def add_qr_region(self) -> None:
"""添加二维码区域"""
self.logger.debug("添加二维码区域操作被触发")
if self.template_canvas:
try:
self.template_canvas.set_mode(TemplateCanvas.MODE_QR)
self.status_bar.showMessage("添加二维码区域模式激活")
except Exception as e:
self.logger.error(f"设置二维码区域模式失败: {str(e)}", exc_info=True)
self.status_bar.showMessage("设置二维码区域模式失败,请重试")
else:
self.logger.error("模板画布未初始化")
self.status_bar.showMessage("模板画布未初始化,无法添加二维码区域")
def import_data(self) -> None:
"""导入数据文件"""
self.logger.debug("导入数据操作被触发")
file_dialog = QFileDialog(self)
file_dialog.setNameFilter("数据文件 (*.csv *.xlsx *.xls)")
file_dialog.setWindowTitle("选择数据文件")
if file_dialog.exec():
data_path = file_dialog.selectedFiles()[0]
self.logger.info(f"选择了数据文件: {data_path}")
try:
if self.data_mapper:
self.data_mapper.import_data()
self.status_bar.showMessage(f"数据文件已导入: {os.path.basename(data_path)}")
else:
self.logger.error("数据映射器未初始化")
self.status_bar.showMessage("数据映射器未初始化,无法导入数据文件")
except Exception as e:
self.logger.error(f"导入数据失败: {str(e)}", exc_info=True)
self.status_bar.showMessage("导入数据失败,请检查文件格式或权限")
if self.data_mapper:
self.data_mapper.import_data()
self.update_rerun_button_state() #更新按钮状态
def save_config(self) -> None:
"""保存配置文件"""
self.logger.debug("保存配置操作被触发")
try:
if self.data_mapper:
mapping_config = self.data_mapper.get_mapping()
if mapping_config:
# 假设配置文件保存到 JSON 文件
config_path = os.path.join(self.data_path, "config.json")
with open(config_path, "w", encoding="utf-8") as config_file:
import json
json.dump(mapping_config, config_file, ensure_ascii=False, indent=4)
self.logger.info(f"配置文件已保存至: {config_path}")
self.status_bar.showMessage("配置文件保存成功")
else:
self.logger.warning("无映射配置可保存")
self.status_bar.showMessage("无映射配置可保存")
else:
self.logger.error("数据映射器未初始化")
self.status_bar.showMessage("数据映射器未初始化,无法保存配置")
except Exception as e:
self.logger.error(f"保存配置失败: {str(e)}", exc_info=True)
self.status_bar.showMessage("保存配置失败,请检查文件权限")
def generate_output(self) -> None:
"""生成输出文件"""
self.logger.debug("开始合成操作被触发")
if not self.current_project:
self.logger.error("未选择项目,无法生成输出文件")
self.status_bar.showMessage("未选择项目,无法生成输出文件")
return
try:
# 检查是否有有效的模板和数据映射
if not self.template_canvas or not self.template_canvas.template_item:
self.logger.error("无有效模板,无法生成输出文件")
self.status_bar.showMessage("无有效模板,无法生成输出文件")
return
if not self.data_mapper or not self.data_mapper.data_frame:
self.logger.error("无有效数据,无法生成输出文件")
self.status_bar.showMessage("无有效数据,无法生成输出文件")
return
# 开始合成操作
self.status_bar.showMessage("正在生成输出文件...")
self.logger.info("开始合成输出文件")
output_path = os.path.join(self.data_path, "output.pdf")
self.compositor.compose(output_path)
self.status_bar.showMessage("输出文件生成成功")
self.logger.info(f"输出文件已生成至: {output_path}")
except Exception as e:
self.logger.error(f"生成输出文件失败: {str(e)}", exc_info=True)
self.status_bar.showMessage("生成输出文件失败,请检查设置")
def rerun_project(self) -> None:
"""执行翻单操作"""
self.logger.debug("执行翻单操作被触发")
if self.btn_rerun is not None:
self.btn_rerun.setEnabled(False) # 防止多次点击
else:
self.logger.error("btn_rerun 控件未正确初始化")
return
if not self.current_project:
self.logger.error("未选择项目,无法执行翻单")
self.status_bar.showMessage("未选择项目,无法执行翻单")
return
try:
# 检查是否有有效的模板和数据映射
if not self.template_canvas or not self.template_canvas.template_item:
self.logger.error("无有效模板,无法执行翻单")
self.status_bar.showMessage("无有效模板,无法执行翻单")
return
if not self.data_mapper or not self.data_mapper.data_frame:
self.logger.error("无有效数据,无法执行翻单")
self.status_bar.showMessage("无有效数据,无法执行翻单")
return
# 执行翻单操作
self.status_bar.showMessage("正在执行翻单...")
self.logger.info("开始执行翻单")
output_path = os.path.join(self.data_path, "rerun_output.pdf")
self.compositor.compose(output_path)
self.status_bar.showMessage("翻单执行成功")
self.logger.info(f"翻单输出文件已生成至: {output_path}")
# 恢复按钮状态
self.btn_rerun.setEnabled(True)
except Exception as e:
self.logger.error(f"翻单执行失败: {str(e)}", exc_info=True)
self.status_bar.showMessage("翻单执行失败,请检查设置")
# 恢复按钮状态
self.btn_rerun.setEnabled(True)
# 测试运行
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
根据以上修改建议,请帮我输出完整的修改后的main_window.py的代码
最新发布