[Python] 纯文本查看 复制代码
import sys
import os
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLineEdit, QPushButton, QLabel, QFileDialog, QTextEdit, QMessageBox,
QScrollArea, QGridLayout
)
from PyQt5.QtGui import QPixmap, QFont, QIcon
from PyQt5.QtCore import Qt
import barcode
from barcode.writer import ImageWriter
# 获取程序所在目录的绝对路径
PROGRAM_DIR = os.path.dirname(os.path.abspath(__file__))
# 临时目录用于存放预览图片(程序关闭后自动清理)
TEMP_IMG_PATH = os.path.join(PROGRAM_DIR, "temp_code128.png")
# 共享的条码生成配置
BARCODE_CONFIG = {
'font_size': 8, # 条码下方文字大小
'text_distance': 5, # 文字与条码的距离(增加间距)
'height': 18, # 条码高度(减小五分之二)
'module_width': 0.25 # 条码宽度(增加)
}
# 现代配色方案
COLORS = {
'primary': '#2E86AB', # 主色调(低饱和蓝)
'primary_hover': '#1A5F7A', # 主色调 hover
'primary_pressed': '#154A60', # 主色调 pressed
'success': '#10B981', # 成功色(淡绿色)
'success_hover': '#059669', # 成功色 hover
'success_pressed': '#047857', # 成功色 pressed
'warning': '#F59E0B', # 警告色
'danger': '#EF4444', # 危险色
'text_primary': '#333333', # 主文本色
'text_secondary': '#666666', # 次文本色
'text_light': '#999999', # 轻文本色
'background': '#F5F7FA', # 背景色
'card_bg': '#FFFFFF', # 卡片背景
'input_bg': '#F8F9FA', # 输入框背景
'border': '#E5E9F0', # 边框色
'border_light': '#F0F2F5', # 轻边框色
'shadow': '0 2px 4px rgba(0, 0, 0, 0.05)', # 阴影
'shadow_hover': '0 4px 6px rgba(0, 0, 0, 0.1)', # hover阴影
}
# 批量生成窗口类
class BatchGenerateWindow(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
# 设置窗口属性
self.setWindowTitle("批量生成 By: Hanlin")
self.resize(900, 800) # 调整窗口大小,按比例分配空间
# 设置窗口图标
self.setWindowIcon(QIcon(os.path.join(PROGRAM_DIR, "favicon.ico")))
# 设置窗口背景
self.setStyleSheet(f"QMainWindow {{ background-color: #F5F7FA; }}")
# 中心部件和布局
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
main_layout.setSpacing(20) # 输入框与预览框间距20px
main_layout.setContentsMargins(20, 20, 20, 5) # 主布局边距,减小下边距
# ====== 标题栏 ======
title_widget = QWidget()
title_layout = QHBoxLayout(title_widget)
title_layout.setContentsMargins(0, 0, 0, 0)
title_layout.setSpacing(10)
title_label = QLabel("批量输入")
title_label.setFont(QFont("微软雅黑", 16, QFont.Weight.Bold))
title_label.setStyleSheet("color: #333333;")
title_layout.addWidget(title_label)
# 右上角悬浮红色小标签
self.count_label = QLabel("0/100")
self.count_label.setFont(QFont("微软雅黑", 12, QFont.Weight.Medium))
self.count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.count_label.setStyleSheet("""
QLabel {
color: white;
padding: 4px 12px;
background-color: #EF4444;
border-radius: 12px;
border: 1px solid #EF4444;
}
""")
title_layout.addStretch()
title_layout.addWidget(self.count_label)
main_layout.addWidget(title_widget)
# ====== 输入区 ======
input_widget = QWidget()
input_widget.setStyleSheet("""
QWidget {
background-color: white;
border-radius: 8px;
border: 1px solid #E5E9F0;
}
""")
input_layout = QVBoxLayout(input_widget)
input_layout.setSpacing(12) # 基础间距12px
input_layout.setContentsMargins(16, 16, 16, 16)
# 批量输入文本框
self.batch_input = QTextEdit()
self.batch_input.setPlaceholderText("请输入要批量生成的条码内容(每行一个,最多100条)\n例如:\n123456\nABC123\nTEST789")
self.batch_input.setFont(QFont("微软雅黑", 14))
self.batch_input.setStyleSheet("""
QTextEdit {
padding: 12px;
border: 1px solid #E5E9F0;
border-radius: 8px;
background-color: #F8F9FA;
color: #333333;
font-family: '微软雅黑';
}
QTextEdit:focus {
border-color: #2E86AB;
background-color: white;
box-shadow: 0 0 0 2px rgba(46, 134, 171, 0.1);
}
/* 优化滚动条样式 */
QScrollBar:vertical {
width: 8px;
background: #F0F2F5;
border-radius: 4px;
margin: 4px 0;
}
QScrollBar::handle:vertical {
background: #999999;
border-radius: 4px;
min-height: 40px;
}
QScrollBar::handle:vertical:hover {
background: #666666;
}
QScrollBar::add-line:vertical,
QScrollBar::sub-line:vertical {
border: none;
background: none;
}
""")
self.batch_input.setMinimumHeight(180) # 增加输入框高度
input_layout.addWidget(self.batch_input)
# 输入操作按钮
input_buttons = QWidget()
input_buttons_layout = QHBoxLayout(input_buttons)
input_buttons_layout.setSpacing(12)
input_buttons_layout.setContentsMargins(0, 0, 0, 0)
# 批量导入按钮
import_btn = QPushButton("批量导入")
import_btn.setFont(QFont("微软雅黑", 14))
import_btn.setStyleSheet("""
QPushButton {
background-color: #10B981;
color: white;
border: none;
padding: 10px 24px;
border-radius: 8px;
}
QPushButton:hover {
background-color: #059669;
}
QPushButton:pressed {
background-color: #047857;
}
""")
import_btn.clicked.connect(self.import_batch)
input_buttons_layout.addWidget(import_btn)
# 清空按钮
clear_btn = QPushButton("清空")
clear_btn.setFont(QFont("微软雅黑", 14))
clear_btn.setStyleSheet("""
QPushButton {
background-color: #999999;
color: white;
border: none;
padding: 10px 24px;
border-radius: 8px;
}
QPushButton:hover {
background-color: #666666;
}
""")
clear_btn.clicked.connect(lambda: self.batch_input.clear())
input_buttons_layout.addWidget(clear_btn)
# 错误提示标签
self.error_label = QLabel("")
self.error_label.setFont(QFont("微软雅黑", 12))
self.error_label.setStyleSheet("color: #EF4444;")
input_buttons_layout.addStretch()
input_buttons_layout.addWidget(self.error_label)
input_layout.addWidget(input_buttons)
main_layout.addWidget(input_widget)
# ====== 预览区 ======
preview_widget = QWidget()
preview_widget.setStyleSheet("""
QWidget {
background-color: white;
border-radius: 8px;
border: 1px solid #E5E9F0;
}
""")
preview_layout = QVBoxLayout(preview_widget)
preview_layout.setSpacing(12) # 基础间距12px
preview_layout.setContentsMargins(16, 16, 16, 16)
# 预览区域标题和刷新按钮
preview_header = QWidget()
preview_header_layout = QHBoxLayout(preview_header)
preview_header_layout.setContentsMargins(0, 0, 0, 0)
preview_header_layout.setSpacing(10)
preview_title = QLabel("条码预览")
preview_title.setFont(QFont("微软雅黑", 14, QFont.Weight.Bold))
preview_title.setStyleSheet("color: #333333;")
preview_header_layout.addWidget(preview_title)
# 刷新预览按钮
refresh_btn = QPushButton("刷新预览")
refresh_btn.setFont(QFont("微软雅黑", 12))
refresh_btn.setStyleSheet("""
QPushButton {
background-color: #F5F7FA;
color: #333333;
border: 1px solid #E5E9F0;
padding: 6px 16px;
border-radius: 6px;
}
QPushButton:hover {
background-color: #E5E9F0;
}
""")
refresh_btn.clicked.connect(self.update_batch_preview)
preview_header_layout.addStretch()
preview_header_layout.addWidget(refresh_btn)
preview_layout.addWidget(preview_header)
# 滚动区域
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setStyleSheet("""
QScrollArea {
border: 1px solid #E5E9F0;
border-radius: 8px;
background-color: white;
}
/* 优化滚动条样式 */
QScrollBar:vertical {
width: 8px;
background: #F0F2F5;
border-radius: 4px;
margin: 4px 0;
}
QScrollBar::handle:vertical {
background: #999999;
border-radius: 4px;
min-height: 40px;
}
QScrollBar::handle:vertical:hover {
background: #666666;
}
QScrollBar::add-line:vertical,
QScrollBar::sub-line:vertical {
border: none;
background: none;
}
""")
scroll_area.setMinimumHeight(400) # 预览区占比60%
# 滚动区域内容部件
scroll_content = QWidget()
self.preview_grid = QGridLayout(scroll_content)
self.preview_grid.setSpacing(8) # 项间距8px
self.preview_grid.setContentsMargins(10, 10, 10, 10)
scroll_area.setWidget(scroll_content)
preview_layout.addWidget(scroll_area)
main_layout.addWidget(preview_widget)
# ====== 操作区 ======
operation_widget = QWidget()
operation_layout = QVBoxLayout(operation_widget)
operation_layout.setContentsMargins(0, 0, 0, 0)
operation_layout.setSpacing(10)
# 批量保存按钮(通栏宽度)
self.generate_btn = QPushButton("批量保存")
self.generate_btn.setFont(QFont("微软雅黑", 14, QFont.Weight.Medium))
self.generate_btn.setStyleSheet("""
QPushButton {
background-color: #2E86AB;
color: white;
border: none;
padding: 10px 24px;
border-radius: 8px;
text-align: center;
}
QPushButton:hover {
background-color: #1A5F7A;
}
QPushButton:pressed {
background-color: #154A60;
transform: translateY(1px);
}
""")
self.generate_btn.clicked.connect(self.batch_generate)
operation_layout.addWidget(self.generate_btn)
# 导出PDF按钮
pdf_btn = QPushButton("导出PDF")
pdf_btn.setFont(QFont("微软雅黑", 14, QFont.Weight.Medium))
pdf_btn.setStyleSheet("""
QPushButton {
background-color: #F59E0B;
color: white;
border: none;
padding: 10px 24px;
border-radius: 8px;
text-align: center;
}
QPushButton:hover {
background-color: #D97706;
}
QPushButton:pressed {
background-color: #B45309;
transform: translateY(1px);
}
""")
pdf_btn.clicked.connect(self.export_pdf)
operation_layout.addWidget(pdf_btn)
main_layout.addWidget(operation_widget)
# 连接文本变化信号到预览更新
self.batch_input.textChanged.connect(self.update_batch_preview)
def format_file_size(self, size_bytes):
"""
将文件大小字节转换为人类可读格式
"""
if size_bytes == 0:
return "0 B"
units = ["B", "KB", "MB", "GB"]
i = 0
while size_bytes >= 1024 and i < len(units) - 1:
size_bytes /= 1024
i += 1
return f"{round(size_bytes, 2)} {units[i]}"
def import_batch(self):
"""批量导入条码内容"""
# 让用户选择文件
file_path, _ = QFileDialog.getOpenFileName(
self,
"选择批量导入文件",
"",
"文本文件 (*.txt);;所有文件 (*.*)"
)
if not file_path:
return
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read().strip()
if content:
# 更新批量输入文本
self.batch_input.setPlainText(content)
QMessageBox.information(self, "成功", f"批量导入成功!\n\n导入行数:{len([line for line in content.split('\n') if line.strip()])}")
else:
QMessageBox.warning(self, "提示", "文件内容为空")
except Exception as e:
QMessageBox.warning(self, "错误", f"导入失败:{str(e)}")
print(f"[ERROR] 批量导入失败:{str(e)}")
def export_pdf(self):
"""导出PDF文件"""
# 获取批量输入文本
batch_text = self.batch_input.toPlainText().strip()
if not batch_text:
QMessageBox.warning(self, "提示", "请先输入要生成条码的内容")
return
# 获取所有有效文本,限制最多100条
barcode_list = [line.strip() for line in batch_text.split('\n') if line.strip()][:100]
if not barcode_list:
QMessageBox.warning(self, "提示", "没有有效的条码内容")
return
# 内存管理优化:重用临时文件
temp_files = []
max_temp_files = 10 # 最多使用10个临时文件
try:
# 创建writer实例
writer = ImageWriter()
# 生成条码文件
for index, text in enumerate(barcode_list):
# 生成条码
code128 = barcode.get('code128', text, writer=writer)
# 重用临时文件(使用索引取模)
temp_path = os.path.join(PROGRAM_DIR, f"temp_pdf_{index % max_temp_files}.png")
# 只添加唯一的临时文件路径
if temp_path not in temp_files:
temp_files.append(temp_path)
code128.write(temp_path, options=BARCODE_CONFIG)
# 定期清理内存
if (index + 1) % 10 == 0:
import gc
gc.collect()
QApplication.processEvents()
# 导出为PDF
self.export_to_pdf(temp_files)
except Exception as e:
QMessageBox.warning(self, "错误", f"PDF导出失败:{str(e)}")
print(f"[ERROR] PDF导出失败:{str(e)}")
finally:
# 确保所有临时文件都被删除
for temp_path in temp_files:
if os.path.exists(temp_path):
try:
os.remove(temp_path)
except Exception as e:
print(f"[ERROR] 删除临时文件 {temp_path} 失败:{str(e)}")
# 清理所有可能的PDF临时文件
for i in range(20):
temp_path = os.path.join(PROGRAM_DIR, f"temp_pdf_{i}.png")
if os.path.exists(temp_path):
try:
os.remove(temp_path)
except:
pass
def export_to_pdf(self, file_paths):
"""
将生成的条码导出为PDF文件
"""
try:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.units import cm, inch
from reportlab.lib.utils import ImageReader
except ImportError as e:
QMessageBox.warning(self, "错误", f"PDF导出依赖缺失:{str(e)}")
print(f"[ERROR] PDF导出依赖缺失:{str(e)}")
return
# 让用户选择PDF保存路径
pdf_path, _ = QFileDialog.getSaveFileName(
self,
"保存PDF文件",
"Code128_Batch.pdf",
"PDF文件 (*.pdf)"
)
if not pdf_path:
return
try:
# 创建PDF画布
c = canvas.Canvas(pdf_path, pagesize=A4)
# 页面设置
page_width, page_height = A4
margin = 2 * cm
barcode_width = 8 * cm
barcode_height = 4 * cm
# 计算每行和每列可以放置的条码数量
cols = 2
rows = 4
# 计算条码之间的间距
x_spacing = (page_width - 2 * margin - cols * barcode_width) / (cols - 1) if cols > 1 else 0
y_spacing = (page_height - 2 * margin - rows * barcode_height) / (rows - 1) if rows > 1 else 0
current_col = 0
current_row = 0
for i, file_path in enumerate(file_paths):
# 计算当前条码位置
x = margin + current_col * (barcode_width + x_spacing)
y = page_height - margin - (current_row + 1) * barcode_height - current_row * y_spacing
# 绘制条码图片
img = ImageReader(file_path)
c.drawImage(img, x, y, barcode_width, barcode_height)
# 更新位置
current_col += 1
if current_col >= cols:
current_col = 0
current_row += 1
# 检查是否需要新页面
if current_row >= rows:
c.showPage()
current_row = 0
# 保存PDF
c.save()
QMessageBox.information(self, "成功", f"PDF文件已导出成功!\n\n保存路径:{pdf_path}")
except Exception as e:
QMessageBox.warning(self, "错误", f"PDF导出失败:{str(e)}")
print(f"[ERROR] PDF导出失败:{str(e)}")
def update_batch_preview(self):
"""更新批量预览"""
# 获取批量输入文本
batch_text = self.batch_input.toPlainText().strip()
# 获取所有有效文本,限制最多100条
barcode_list = [line.strip() for line in batch_text.split('\n') if line.strip()][:100]
# 更新数量显示
self.count_label.setText(f"{len(barcode_list)}/100")
# 清空错误提示
self.error_label.setText("")
# 检查是否超出限制
total_lines = len([line.strip() for line in batch_text.split('\n') if line.strip()])
if total_lines > 100:
self.error_label.setText(f"输入内容超出限制,最多100条,当前{total_lines}条")
# 清空现有的预览控件
for i in reversed(range(self.preview_grid.count())):
widget = self.preview_grid.itemAt(i).widget()
if widget is not None:
widget.deleteLater()
if not barcode_list:
# 显示空状态
empty_label = QLabel("暂无预览内容")
empty_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
empty_label.setFont(QFont("微软雅黑", 12))
empty_label.setStyleSheet("color: #999999;")
self.preview_grid.addWidget(empty_label, 0, 0, 1, 3)
return
# 预览加载优化:只显示前20条条码
max_preview = 20
display_list = barcode_list[:max_preview]
# 显示预览数量提示
if len(barcode_list) > max_preview:
preview_count_label = QLabel(f"显示前 {max_preview} 条预览(共 {len(barcode_list)} 条)")
preview_count_label.setFont(QFont("微软雅黑", 11))
preview_count_label.setStyleSheet("color: #666666;")
preview_count_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.preview_grid.addWidget(preview_count_label, 0, 0, 1, 3)
start_row = 1
else:
start_row = 0
temp_files = []
try:
# 创建writer实例
writer = ImageWriter()
# 生成并显示每个条码
for index, text in enumerate(display_list):
# 创建条码容器
barcode_item = QWidget()
barcode_item.setStyleSheet("""
QWidget {
background-color: white;
border-radius: 8px;
border: 1px solid #E5E9F0;
padding: 8px;
}
QWidget:hover {
border-color: #2E86AB;
background-color: #F8F9FA;
}
""")
# 创建垂直布局
item_layout = QVBoxLayout(barcode_item)
item_layout.setSpacing(6)
item_layout.setContentsMargins(0, 0, 0, 0)
# 生成条码
code128 = barcode.get('code128', text, writer=writer)
# 保存到临时文件用于预览(使用索引重用临时文件)
temp_path = os.path.join(PROGRAM_DIR, f"temp_preview_{index % 10}.png")
temp_files.append(temp_path)
code128.write(temp_path, options=BARCODE_CONFIG)
# 显示预览图片
pixmap = QPixmap(temp_path)
scaled_pixmap = pixmap.scaled(
240, 100, # 增大尺寸,提高巴枪识别率
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation
)
img_label = QLabel()
img_label.setPixmap(scaled_pixmap)
img_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
item_layout.addWidget(img_label)
# 计算网格位置(3列布局)
row = start_row + (index // 3)
col = index % 3
# 添加到网格布局
self.preview_grid.addWidget(barcode_item, row, col)
except Exception as e:
# 显示错误信息
error_label = QLabel(f"预览失败:{str(e)}")
error_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
error_label.setStyleSheet("color: #EF4444; font-size: 14px;")
self.preview_grid.addWidget(error_label, 0, 0, 1, 3)
print(f"[ERROR] 批量预览失败:{str(e)}")
finally:
# 确保所有临时文件都被删除
for temp_path in temp_files:
if os.path.exists(temp_path):
try:
os.remove(temp_path)
except Exception as e:
print(f"[ERROR] 删除临时文件 {temp_path} 失败:{str(e)}")
# 清理所有可能的临时预览文件
for i in range(20):
temp_path = os.path.join(PROGRAM_DIR, f"temp_preview_{i}.png")
if os.path.exists(temp_path):
try:
os.remove(temp_path)
except:
pass
def batch_generate(self):
"""批量保存条码"""
import time
try:
# 获取批量输入文本
batch_text = self.batch_input.toPlainText().strip()
if not batch_text:
QMessageBox.warning(self, "提示", "请输入要批量生成的条码内容")
return
# 按行分割内容,限制最多100条
barcode_list = [line.strip() for line in batch_text.split('\n') if line.strip()][:100]
if not barcode_list:
QMessageBox.warning(self, "提示", "没有有效的条码内容")
return
# 显示保存数量确认
QMessageBox.information(self, "提示", f"即将保存 {len(barcode_list)} 个条码")
# 让用户选择保存目录
save_dir = QFileDialog.getExistingDirectory(
self,
"选择保存目录",
PROGRAM_DIR
)
if not save_dir:
return
# 设置writer选项
writer = ImageWriter()
# 开始保存条码
start_time = time.time()
success_count = 0
fail_count = 0
total_size = 0
success_files = []
# 显示保存进度对话框
progress_dialog = QMessageBox(self)
progress_dialog.setWindowTitle("保存中")
progress_dialog.setText("正在保存条码,请稍候...")
progress_dialog.setStandardButtons(QMessageBox.StandardButton.NoButton)
progress_dialog.show()
# 内存管理优化:批量处理时定期清理内存
batch_size = 10 # 每处理10个条码清理一次内存
for i, text in enumerate(barcode_list):
try:
# 生成条码
code128 = barcode.get('code128', text, writer=writer)
# 保存条码到指定目录
save_path = os.path.join(save_dir, f"Code128_{text}.png")
# 处理重名文件
counter = 1
while os.path.exists(save_path):
save_path = os.path.join(save_dir, f"Code128_{text}_{counter}.png")
counter += 1
code128.write(save_path, options=BARCODE_CONFIG)
success_count += 1
success_files.append(save_path)
# 更新进度
progress_dialog.setText(f"正在保存:{i+1}/{len(barcode_list)}")
QApplication.processEvents()
# 定期清理内存
if (i + 1) % batch_size == 0:
# 清理内存
import gc
gc.collect()
QApplication.processEvents()
except Exception as e:
fail_count += 1
print(f"[ERROR] 生成条码 {text} 失败:{str(e)}")
# 关闭进度对话框
progress_dialog.close()
# 最终清理
import gc
gc.collect()
# 计算总文件大小
for file_path in success_files:
try:
total_size += os.path.getsize(file_path)
except Exception as e:
print(f"[ERROR] 获取文件大小失败 {file_path}:{str(e)}")
pass
# 计算耗时
elapsed_time = round(time.time() - start_time, 2)
# 更新最终状态
total_count = len(barcode_list)
# 格式化文件大小
try:
formatted_size = self.format_file_size(total_size)
except Exception as e:
formatted_size = f"{total_size} B"
print(f"[ERROR] 格式化文件大小失败:{str(e)}")
# 显示统计信息
stats_message = f"批量保存完成!\n\n"
stats_message += f"总生成数量:{total_count}\n"
stats_message += f"成功数量:{success_count}\n"
stats_message += f"失败数量:{fail_count}\n"
stats_message += f"总文件大小:{formatted_size}\n"
stats_message += f"生成耗时:{elapsed_time}秒"
if fail_count == 0:
# 询问是否导出为PDF
pdf_result = QMessageBox.question(
self, "PDF导出",
f"{stats_message}\n\n是否将生成的条码导出为PDF文件?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if pdf_result == QMessageBox.StandardButton.Yes:
self.export_to_pdf(success_files)
else:
QMessageBox.information(self, "结果", stats_message)
except Exception as e:
QMessageBox.critical(self, "错误", f"批量生成过程中发生错误:{str(e)}")
print(f"[ERROR] 批量生成失败:{str(e)}")
# 单条码生成主窗口类
class Code128Generator(QMainWindow):
def __init__(self):
super().__init__()
self.batch_window = None # 批量生成窗口实例
self.init_ui()
def init_ui(self):
# 窗口基础设置
self.setWindowTitle("条码生成器 By: Hanlin")
self.setFixedSize(450, 450) # 调整窗口大小,适合单条码生成
# 设置窗口图标
self.setWindowIcon(QIcon(os.path.join(PROGRAM_DIR, "favicon.ico")))
# 设置窗口背景
self.setStyleSheet(f"QMainWindow {{ background-color: {COLORS['background']}; }}")
# 中心部件和布局
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
main_layout.setSpacing(20)
main_layout.setContentsMargins(30, 15, 30, 15)
# 添加标题
app_title = QLabel("条码生成器")
app_title.setFont(QFont("微软雅黑", 20, QFont.Weight.Bold))
app_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
app_title.setStyleSheet(f"color: {COLORS['text_primary']}; margin-bottom: 10px;")
main_layout.addWidget(app_title)
# ============= 单条码生成区域 =============
# 1. 输入框
self.input_edit = QLineEdit()
self.input_edit.setPlaceholderText("请输入要生成条码的内容")
self.input_edit.setFont(QFont("微软雅黑", 14))
self.input_edit.setStyleSheet(f"""
QLineEdit {{
padding: 14px 18px;
border: 2px solid {COLORS['border']};
border-radius: 10px;
background-color: {COLORS['background']};
color: {COLORS['text_primary']};
}}
QLineEdit:focus {{
border-color: {COLORS['primary']};
background-color: {COLORS['card_bg']};
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}}
""")
# 输入内容变化时自动更新预览
self.input_edit.textChanged.connect(self.update_preview)
main_layout.addWidget(self.input_edit)
# 2. 条码预览区域
self.preview_label = QLabel("条码预览区")
self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.preview_label.setStyleSheet(f"""
QLabel {{
border: 2px dashed {COLORS['border']};
border-radius: 12px;
padding: 30px;
background-color: {COLORS['card_bg']};
color: {COLORS['text_light']};
font-size: 15px;
}}
""")
self.preview_label.setMinimumHeight(180) # 增加预览区域高度,确保条码完整显示
main_layout.addWidget(self.preview_label)
# 按钮容器
button_container = QWidget()
button_layout = QHBoxLayout(button_container)
button_layout.setSpacing(12)
button_layout.setContentsMargins(0, 0, 0, 0)
# 3. 打印按钮
self.print_btn = QPushButton("打印")
self.print_btn.setFont(QFont("微软雅黑", 14, QFont.Weight.Medium))
self.print_btn.setStyleSheet(f"""
QPushButton {{
background-color: {COLORS['primary']};
color: white;
border: none;
padding: 14px;
border-radius: 10px;
transition: all 0.3s ease;
}}
QPushButton:hover {{
background-color: {COLORS['primary_hover']};
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}}
QPushButton:pressed {{
background-color: {COLORS['primary_pressed']};
transform: translateY(0);
}}
QPushButton:disabled {{
background-color: #c0c4cc;
}}
""")
self.print_btn.clicked.connect(self.print_barcode)
# 初始状态按钮禁用(无输入时无法打印)
self.print_btn.setEnabled(False)
button_layout.addWidget(self.print_btn, 1) # 设置拉伸因子为1
# 4. 批量生成按钮
self.batch_btn = QPushButton("批量生成")
self.batch_btn.setFont(QFont("微软雅黑", 14, QFont.Weight.Medium))
self.batch_btn.setStyleSheet(f"""
QPushButton {{
background-color: {COLORS['success']};
color: white;
border: none;
padding: 14px;
border-radius: 10px;
transition: all 0.3s ease;
}}
QPushButton:hover {{
background-color: {COLORS['success_hover']};
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}}
QPushButton:pressed {{
background-color: {COLORS['success_pressed']};
transform: translateY(0);
}}
""")
self.batch_btn.clicked.connect(self.open_batch_window)
button_layout.addWidget(self.batch_btn, 1) # 设置拉伸因子为1
# 添加按钮容器到主布局,增加上下边距
button_container.setContentsMargins(0, 80, 0, 0) # 上边距设为80,让按钮向下移动更多
main_layout.addWidget(button_container)
def open_batch_window(self):
"""打开批量生成窗口"""
if self.batch_window is None or not self.batch_window.isVisible():
self.batch_window = BatchGenerateWindow()
self.batch_window.show()
else:
self.batch_window.activateWindow() # 激活已有窗口
self.batch_window.raise_() # 置于顶层
def update_preview(self):
"""实时更新条码预览"""
text = self.input_edit.text().strip()
if not text:
# 清空输入时重置预览
self.preview_label.setText("条码预览区")
self.print_btn.setEnabled(False)
if os.path.exists(TEMP_IMG_PATH):
try:
os.remove(TEMP_IMG_PATH)
except Exception as e:
print(f"[ERROR] 删除临时文件失败:{str(e)}")
return
# 生成Code128条码并保存为临时图片
try:
# 创建writer实例
writer = ImageWriter()
code128 = barcode.get('code128', text, writer=writer)
code128.write(TEMP_IMG_PATH, options=BARCODE_CONFIG)
# 检查文件是否存在
if not os.path.exists(TEMP_IMG_PATH):
self.preview_label.setText("生成失败:临时文件未创建")
self.print_btn.setEnabled(False)
return
# 显示预览图片
pixmap = QPixmap(TEMP_IMG_PATH)
if pixmap.isNull():
self.preview_label.setText("生成失败:图片加载失败")
self.print_btn.setEnabled(False)
return
# 缩放图片适配预览区域,保持比例
scaled_pixmap = pixmap.scaled(
self.preview_label.width() - 40,
160,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation
)
self.preview_label.setPixmap(scaled_pixmap)
self.print_btn.setEnabled(True)
except Exception as e:
self.preview_label.setText(f"生成失败:{str(e)}")
self.print_btn.setEnabled(False)
def print_barcode(self):
"""打印条码"""
from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
from PyQt5.QtGui import QPainter
text = self.input_edit.text().strip()
if not text:
return
# 检查临时文件是否存在
if not os.path.exists(TEMP_IMG_PATH):
QMessageBox.warning(self, "错误", "条码图片未生成,请先输入内容")
return
try:
# 创建打印机对象
printer = QPrinter(QPrinter.HighResolution)
# 自动使用系统默认打印机
printer.setPageSize(QPrinter.A4)
printer.setOrientation(QPrinter.Portrait)
# 创建 painter 对象
painter = QPainter()
if not painter.begin(printer):
QMessageBox.warning(self, "错误", "无法初始化打印机")
return
# 加载条码图片
pixmap = QPixmap(TEMP_IMG_PATH)
if pixmap.isNull():
QMessageBox.warning(self, "错误", "条码图片加载失败")
painter.end()
return
# 计算打印位置(居中打印)
page_rect = printer.pageRect(QPrinter.DevicePixel)
img_rect = pixmap.rect()
# 计算缩放比例,确保图片适合页面
scale = min(page_rect.width() / img_rect.width(), page_rect.height() / img_rect.height()) * 0.8
scaled_width = int(img_rect.width() * scale)
scaled_height = int(img_rect.height() * scale)
# 计算居中位置
x = (page_rect.width() - scaled_width) // 2
y = (page_rect.height() - scaled_height) // 2
# 绘制图片
painter.drawPixmap(x, y, scaled_width, scaled_height, pixmap)
# 结束打印
painter.end()
QMessageBox.information(self, "成功", "条码打印成功")
except Exception as e:
QMessageBox.warning(self, "错误", f"打印失败:{str(e)}")
print(f"[ERROR] 打印失败:{str(e)}")
def closeEvent(self, event):
"""程序关闭时清理临时文件"""
if os.path.exists(TEMP_IMG_PATH):
try:
os.remove(TEMP_IMG_PATH)
except Exception as e:
print(f"[ERROR] 删除临时文件失败:{str(e)}")
event.accept()
if __name__ == "__main__":
# 在Qt 6中,高DPI功能已经默认启用,不再需要手动设置
app = QApplication(sys.argv)
window = Code128Generator()
window.show()
sys.exit(app.exec())