夜雨聆风学习资料网

ARTICLE · 1159969

《几百个PDF要打印,手动点到手抽筋?Python一键全打完,省心省力》

《几百个PDF要打印,手动点到手抽筋?Python一键全打完,省心省力》

“带你横跨办公自动化的数据江海”

@码海听潮

前言

关于摸鱼

(

ABOUT MOYU

)

闻道有先后,术业有专攻。

各位大佬朋友们好!

~我依旧是你们的老朋友摸鱼~

在职场摸爬滚打的这十多年里,我用Python悄悄干了不少“正事”——不知不觉攒下了一整套办公自动化的实用项目技巧。自2025年10月初创立了公众号 「码海听潮」 ,初衷很简单:把重复的劳动交给代码,把摸鱼的时间留给生活。

目前已经吭哧吭哧更新了近170篇原创文章,每一篇都是实操干货,不讲虚的,只聊怎么用代码真正解放双手,帮大家早点下班、准点摸鱼!

好了,多了不说,少了不唠,今天给大佬们分享一款本人原创的【PDF批量打印工具】。经常打印PDF文档资料的朋友,大概都遇到过这种麻烦:PDF 一份份点开打印太费时间,打印参数还得反复调,文件一多就乱成一团。

今天这款工具,正是为了解决这份尴尬。它能自动识别添加 PDF,支持单个文件或整个文件夹批量导入,也能直接拖拽到窗口快速添加,自动过滤隐藏文件、临时文件和超大文件,重复文件不会重复加入。列表清晰展示文件名、大小、页数、状态和路径,双击可预览,右键可定位或移除。一键统计总页数,方便估算打印量。打印前可配置打印机、纸张、方向、份数、颜色和双面打印,设置自动保存,下次直接沿用。点击开始打印后逐个发送,进度条实时显示,成功失败一目了然,状态自动更新,未打印的文件也会保留方便后续处理。界面清爽优雅,窗口位置自动记忆,日常 PDF 批量打印和文档归档都省心。

《该办公工具成品展示:》

《该办公工具核心功能亮点:》

01

 亮点一:多线程打印 + 信号驱动 UI,不卡界面

        • 打印几十上百个文件时界面不冻结

        • 进度条、状态列实时更新

        • 打印期间按钮锁定,避免误操作

        02

         亮点二:拖拽导入

            • 支持把 PDF 文件或整个文件夹直接拖进窗口,文件夹还会递归搜索子目录。这是批量工具里最常用的交互方式。

            03

            亮点三:完整的打印参数配置

            • 打印机选择(自动枚举本地 + 网络打印机)

            • 纸张尺寸(A4/A3/A5/B5/Letter/Legal/Tabloid)

            • 页面方向(纵向/横向)

            • 打印份数(1-999)

            • 颜色模式(彩色/黑白)

            • 双面打印(开关)

            覆盖了绝大多数日常打印场景。

            《该办公工具代码展示:》

            下面,我就奉上python代码让各位大佬见识一下,什么叫"传统文化"遇上"赛博效率"

            import sysfrom pathlib import Pathfrom PyQt6.QtWidgets import (    QApplication,    QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton,    QTableWidget, QTableWidgetItem, QHeaderView, QLabel, QFrame,    QProgressBar, QFileDialog, QMessageBox, QAbstractItemView,    QMenu, QStackedWidget)from PyQt6.QtCore import Qt, QTimerfrom PyQt6.QtGui import QAction, QFont, QColor, QPainter, QPenPRIMARY = '#2D7FF9'SUCCESS = '#28A745'DANGER = '#DC3545'TEXT_PRIMARY = '#1F2937'TEXT_SECONDARY = '#6B7280'BORDER = '#E5E7EB'class StatCard(QFrame):    """统计卡片"""    def __init__(self, icon: str, title: str, value: str = '0', parent=None):        super().__init__(parent)        self.setObjectName('statCard')        self.setStyleSheet(f"""            QFrame#statCard {{                background-color: white;                border: 1px solid {BORDER};                border-radius: 10px;            }}        """)        self.setFixedHeight(72)        layout = QHBoxLayout(self)        layout.setContentsMargins(16, 12, 16, 12)        layout.setSpacing(12)        self.icon_label = QLabel(icon)        self.icon_label.setStyleSheet("font-size: 22px; background: transparent;")        self.icon_label.setFixedWidth(32)        self.icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)        text_layout = QVBoxLayout()        text_layout.setSpacing(2)        text_layout.setContentsMargins(0, 0, 0, 0)        self.title_label = QLabel(title)        self.title_label.setStyleSheet(            f"color: {TEXT_SECONDARY}; font-size: 12px; background: transparent;"        )        self.value_label = QLabel(value)        self.value_label.setStyleSheet(            f"color: {TEXT_PRIMARY}; font-size: 18px; font-weight: 600; background: transparent;"        )        text_layout.addWidget(self.title_label)        text_layout.addWidget(self.value_label)        layout.addWidget(self.icon_label)        layout.addLayout(text_layout, 1)    def set_value(self, value: str):        self.value_label.setText(value)class DropArea(QFrame):    """拖拽区域提示"""    def __init__(self, parent=None):        super().__init__(parent)        self.setAcceptDrops(True)        self.setStyleSheet("QFrame { background-color: transparent; border: none; }")    def paintEvent(self, event):        painter = QPainter(self)        painter.setRenderHint(QPainter.RenderHint.Antialiasing)        pen = QPen(QColor(BORDER))        pen.setWidth(2)        pen.setStyle(Qt.PenStyle.DashLine)        painter.setPen(pen)        rect = self.rect().adjusted(1, 1, -1, -1)        painter.drawRoundedRect(rect, 12, 12)        painter.setPen(QColor(TEXT_SECONDARY))        font = QFont('Microsoft YaHei UI', 11)        painter.setFont(font)        painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter,                         '拖拽 PDF 文件或文件夹到此处\n或点击上方按钮添加')class Toast(QLabel):    """轻量级提示消息"""    def __init__(self, parent, message: str, duration: int = 2000,                 level: str = 'info'):        super().__init__(message, parent)        colors = {            'info': (PRIMARY, 'white'),            'success': ('#28A745', 'white'),            'warning': ('#FFC107', '#1F2937'),            'error': ('#DC3545', 'white'),        }        bg, fg = colors.get(level, colors['info'])        self.setStyleSheet(f"""            QLabel {{                background-color: {bg};                color: {fg};                border-radius: 8px;                padding: 10px 20px;                font-size: 13px;                font-weight: 500;            }}        """)        self.setAlignment(Qt.AlignmentFlag.AlignCenter)        self.adjustSize()        self._reposition()        self.show()        self.raise_()        QTimer.singleShot(duration, self.close)    def _reposition(self):        parent = self.parent()        if parent:            x = (parent.width() - self.width()) // 2            y = parent.height() - self.height() - 30            self.move(x, y)# ==================== 主窗口 ====================class MainWindow(QMainWindow):    """主窗口(纯界面版)"""    def __init__(self):        super().__init__()        # 界面演示用的文档列表        # 每项结构: {'name': str, 'size_mb': float, 'pages': int|None,        #           'status': str, 'path': str}        self._documents = []        self._is_printing_ui = False        self._current_printer = '未设置'        self._init_ui()        self._update_status()    # ==================== UI 构建 ====================    def _init_ui(self):        self.setWindowTitle('PDF批量打印工具(欢迎关注微信公众号:码海听潮)')        self.setMinimumSize(800, 650)        self.setAcceptDrops(True)        central = QWidget()        self.setCentralWidget(central)        main_layout = QVBoxLayout(central)        main_layout.setContentsMargins(16, 16, 16, 16)        main_layout.setSpacing(12)        main_layout.addWidget(self._build_toolbar())        main_layout.addWidget(self._build_stats_row())        main_layout.addWidget(self._build_list_area(), 1)        main_layout.addWidget(self._build_bottom_bar())    def _build_toolbar(self) -> QFrame:        bar = QFrame()        bar.setObjectName('toolbarFrame')        layout = QHBoxLayout(bar)        layout.setContentsMargins(12, 10, 12, 10)        layout.setSpacing(8)        self.btn_add_files = QPushButton(' 添加PDF')        self.btn_add_files.setToolTip('选择一个或多个 PDF 文件')        self.btn_add_files.clicked.connect(self._add_files)        self.btn_add_folder = QPushButton(' 添加文件夹')        self.btn_add_folder.setToolTip('添加文件夹内的所有 PDF')        self.btn_add_folder.clicked.connect(self._add_folder)        self.btn_remove = QPushButton(' 移除选中')        self.btn_remove.setToolTip('从列表中移除选中的文档')        self.btn_remove.clicked.connect(self._remove_selected)        self.btn_clear = QPushButton(' 清空列表')        self.btn_clear.setObjectName('dangerButton')        self.btn_clear.setToolTip('清空所有已添加的文档')        self.btn_clear.clicked.connect(self._clear_documents)        layout.addWidget(self.btn_add_files)        layout.addWidget(self.btn_add_folder)        layout.addWidget(self.btn_remove)        layout.addWidget(self.btn_clear)        layout.addStretch()        self.btn_settings = QPushButton(' 打印设置')        self.btn_settings.setToolTip('配置打印机、纸张、方向等')        self.btn_settings.clicked.connect(self._show_print_settings)        self.btn_pages = QPushButton(' 计算页数')        self.btn_pages.setToolTip('统计所有 PDF 的页数')        self.btn_pages.clicked.connect(self._calculate_pages)        self.btn_print = QPushButton(' 开始打印')        self.btn_print.setObjectName('primaryButton')        self.btn_print.setToolTip('批量打印所有文档')        self.btn_print.clicked.connect(self._start_printing)        layout.addWidget(self.btn_settings)        layout.addWidget(self.btn_pages)        layout.addWidget(self.btn_print)        return bar    def _build_stats_row(self) -> QWidget:        widget = QWidget()        layout = QHBoxLayout(widget)        layout.setContentsMargins(0, 0, 0, 0)        layout.setSpacing(12)        self.card_count = StatCard('📄', '文档总数', '0')        self.card_size = StatCard('💾', '总大小', '0 MB')        self.card_pages = StatCard('📑', '总页数', '0')        self.card_printer = StatCard('🖨️', '打印机', '未设置')        layout.addWidget(self.card_count)        layout.addWidget(self.card_size)        layout.addWidget(self.card_pages)        layout.addWidget(self.card_printer)        return widget    def _build_list_area(self) -> QFrame:        card = QFrame()        card.setObjectName('card')        card.setStyleSheet(f"""            QFrame#card {{                background-color: white;                border: 1px solid {BORDER};                border-radius: 10px;            }}        """)        layout = QVBoxLayout(card)        layout.setContentsMargins(0, 0, 0, 0)        layout.setSpacing(0)        header = QWidget()        header.setStyleSheet(f"""            background-color: #F9FAFB;            border-top-left-radius: 10px;            border-top-right-radius: 10px;            border-bottom: 1px solid {BORDER};        """)        header_layout = QHBoxLayout(header)        header_layout.setContentsMargins(16, 12, 16, 12)        title = QLabel('📋  PDF 文档列表')        title.setStyleSheet(f"font-size: 14px; font-weight: 600; color: {TEXT_PRIMARY};")        header_layout.addWidget(title)        header_layout.addStretch()        self.list_hint = QLabel('双击可打开文件预览')        self.list_hint.setStyleSheet(f"font-size: 12px; color: {TEXT_SECONDARY};")        header_layout.addWidget(self.list_hint)        layout.addWidget(header)        self.stack = QStackedWidget()        self.table = QTableWidget()        self.table.setColumnCount(5)        self.table.setHorizontalHeaderLabels(['文件名', '大小 (MB)', '页数', '状态', '文件路径'])        self.table.verticalHeader().setVisible(False)        self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)        self.table.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)        self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)        self.table.setAlternatingRowColors(False)        self.table.setShowGrid(False)        self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)        self.table.customContextMenuRequested.connect(self._show_context_menu)        self.table.doubleClicked.connect(self._on_double_click)        header_view = self.table.horizontalHeader()        header_view.setSectionResizeMode(0, QHeaderView.ResizeMode.Interactive)        header_view.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)        header_view.setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed)        header_view.setSectionResizeMode(3, QHeaderView.ResizeMode.Fixed)        header_view.setSectionResizeMode(4, QHeaderView.ResizeMode.Stretch)        header_view.setHighlightSections(False)        self.table.setColumnWidth(0, 280)        self.table.setColumnWidth(1, 90)        self.table.setColumnWidth(2, 70)        self.table.setColumnWidth(3, 80)        self.table.verticalHeader().setDefaultSectionSize(40)        self.empty_area = DropArea()        self.stack.addWidget(self.table)        self.stack.addWidget(self.empty_area)        self.stack.setCurrentWidget(self.empty_area)        layout.addWidget(self.stack, 1)        return card    def _build_bottom_bar(self) -> QFrame:        bar = QFrame()        bar.setObjectName('card')        bar.setStyleSheet(f"""            QFrame#card {{                background-color: white;                border: 1px solid {BORDER};                border-radius: 10px;            }}        """)        bar.setFixedHeight(72)        layout = QHBoxLayout(bar)        layout.setContentsMargins(16, 12, 16, 12)        layout.setSpacing(16)        left = QVBoxLayout()        left.setSpacing(4)        self.lbl_status_title = QLabel('就绪')        self.lbl_status_title.setStyleSheet(            f"font-size: 13px; font-weight: 600; color: {TEXT_PRIMARY};"        )        self.lbl_status_detail = QLabel('等待操作')        self.lbl_status_detail.setStyleSheet(            f"font-size: 12px; color: {TEXT_SECONDARY};"        )        left.addWidget(self.lbl_status_title)        left.addWidget(self.lbl_status_detail)        right = QVBoxLayout()        right.setSpacing(4)        self.progress = QProgressBar()        self.progress.setRange(0, 100)        self.progress.setValue(0)        self.progress.setTextVisible(False)        self.progress.setFixedHeight(12)        self.progress.setMinimumWidth(400)        self.lbl_progress_text = QLabel('0 / 0')        self.lbl_progress_text.setAlignment(Qt.AlignmentFlag.AlignRight)        self.lbl_progress_text.setStyleSheet(            f"font-size: 12px; color: {TEXT_SECONDARY};"        )        right.addWidget(self.progress)        right.addWidget(self.lbl_progress_text)        layout.addLayout(left, 0)        layout.addStretch()        layout.addLayout(right, 1)        return bar    # ==================== 事件处理 ====================    def _add_files(self):        files, _ = QFileDialog.getOpenFileNames(            self, '选择要打印的PDF文档', '',            'PDF文件 (*.pdf);;所有文件 (*)'        )        if not files:            return        added = 0        for f in files:            p = Path(f)            size_mb = round(p.stat().st_size / 1048576, 2) if p.exists() else 0            self._documents.append({                'name': p.name,                'size_mb': size_mb,                'pages': None,                'status': '待打印',                'path': str(p),            })            added += 1        if added:            self._refresh_list()            self._update_status()            self._toast(f'成功添加 {added} 个文档', 'success')    def _add_folder(self):        folder = QFileDialog.getExistingDirectory(self, '选择包含PDF文档的文件夹')        if not folder:            return        folder_path = Path(folder)        pdf_files = [f for f in folder_path.glob('**/*.pdf') if f.is_file()]        if not pdf_files:            self._toast('未找到PDF文档', 'warning')            return        for p in pdf_files:            size_mb = round(p.stat().st_size / 1048576, 2)            self._documents.append({                'name': p.name,                'size_mb': size_mb,                'pages': None,                'status': '待打印',                'path': str(p),            })        self._refresh_list()        self._update_status()        self._toast(f'成功添加 {len(pdf_files)} 个文档', 'success')    def _remove_selected(self):        rows = sorted(set(idx.row() for idx in self.table.selectedIndexes()),                      reverse=True)        if not rows:            self._toast('请先选择要移除的文档', 'warning')            return        reply = QMessageBox.question(            self, '确认', f'确定要移除选中的 {len(rows)} 个文档吗?',            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,            QMessageBox.StandardButton.No        )        if reply != QMessageBox.StandardButton.Yes:            return        for row in rows:            if 0 <= row < len(self._documents):                self._documents.pop(row)        self._refresh_list()        self._update_status()        self._toast(f'已移除 {len(rows)} 个文档', 'success')    def _clear_documents(self):        if not self._documents:            return        reply = QMessageBox.question(            self, '确认清空', '确定要清空所有文档吗?',            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,            QMessageBox.StandardButton.No        )        if reply == QMessageBox.StandardButton.Yes:            self._documents.clear()            self._refresh_list()            self._update_status()            self._toast('列表已清空', 'info')    def _calculate_pages(self):        """计算页数(界面演示:占位为 1 页)"""        if not self._documents:            self._toast('请先添加要统计的PDF文档', 'warning')            return        self.btn_pages.setEnabled(False)        self.btn_pages.setText(' 统计中...')        self.progress.setValue(0)        total = len(self._documents)        for i, doc in enumerate(self._documents):            if doc['pages'] is None:                doc['pages'] = 1            self.progress.setValue(int((i + 1) / total * 100))            self.lbl_progress_text.setText(f'{i + 1} / {total}')        self.btn_pages.setEnabled(True)        self.btn_pages.setText(' 计算页数')        self._refresh_list()        self._update_status()        self._toast('页数统计完成(演示)', 'success')        QTimer.singleShot(1500, lambda: self.progress.setValue(0))    def _show_print_settings(self):        """显示打印设置对话框(界面演示:只弹提示)"""        self._toast('打印设置(演示,未接入实际逻辑)', 'info')    def _start_printing(self):        """开始打印(界面演示:模拟逐个打印)"""        if self._is_printing_ui:            self._toast('打印任务正在进行中', 'warning')            return        if not self._documents:            self._toast('请先添加要打印的PDF文档', 'warning')            return        reply = QMessageBox.question(            self, '确认打印',            f'确定要打印 {len(self._documents)} 个PDF文档吗?',            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,            QMessageBox.StandardButton.No        )        if reply != QMessageBox.StandardButton.Yes:            return        # 锁定 UI        self._is_printing_ui = True        self.btn_print.setEnabled(False)        self.btn_print.setText(' 打印中...')        self.btn_add_files.setEnabled(False)        self.btn_add_folder.setEnabled(False)        self.btn_remove.setEnabled(False)        self.btn_clear.setEnabled(False)        self.btn_settings.setEnabled(False)        self.progress.setValue(0)        total = len(self._documents)        self.lbl_progress_text.setText(f'0 / {total}')        self.lbl_status_title.setText('正在打印...')        self.lbl_status_detail.setText('准备中')        self._simulate_print_step(0, total)    def _simulate_print_step(self, index: int, total: int):        """演示:模拟逐个打印"""        if index >= total:            self.lbl_status_title.setText('打印完成')            self.lbl_status_detail.setText('所有任务已结束')            self._reset_print_ui()            QTimer.singleShot(2000, lambda: self.progress.setValue(0))            return        doc = self._documents[index]        doc['status'] = '打印中'        self._refresh_list()        self.progress.setValue(int((index + 1) / total * 100))        self.lbl_progress_text.setText(f'{index + 1} / {total}')        self.lbl_status_title.setText(f'正在打印 ({index + 1}/{total})')        self.lbl_status_detail.setText(f'正在打印: {doc["name"]}')        QTimer.singleShot(500, lambda: self._finish_one(index, total))    def _finish_one(self, index: int, total: int):        doc = self._documents[index]        doc['status'] = '已完成'        self._refresh_list()        QTimer.singleShot(100, lambda: self._simulate_print_step(index + 1, total))    def _reset_print_ui(self):        """恢复打印相关按钮"""        self._is_printing_ui = False        self.btn_print.setEnabled(len(self._documents) > 0)        self.btn_print.setText(' 开始打印')        self.btn_add_files.setEnabled(True)        self.btn_add_folder.setEnabled(True)        self.btn_remove.setEnabled(True)        self.btn_clear.setEnabled(True)        self.btn_settings.setEnabled(True)    # ==================== 拖拽 ====================    def dragEnterEvent(self, event):        if event.mimeData().hasUrls():            event.acceptProposedAction()    def dropEvent(self, event):        paths = []        for url in event.mimeData().urls():            p = url.toLocalFile()            if p:                paths.append(Path(p))        if not paths:            return        added = 0        for p in paths:            if p.is_file() and p.suffix.lower() == '.pdf':                size_mb = round(p.stat().st_size / 1048576, 2)                self._documents.append({                    'name': p.name, 'size_mb': size_mb,                    'pages': None, 'status': '待打印', 'path': str(p),                })                added += 1            elif p.is_dir():                for f in p.glob('**/*.pdf'):                    if f.is_file():                        size_mb = round(f.stat().st_size / 1048576, 2)                        self._documents.append({                            'name': f.name, 'size_mb': size_mb,                            'pages': None, 'status': '待打印', 'path': str(f),                        })                        added += 1        if added:            self._refresh_list()            self._update_status()            self._toast(f'拖拽导入成功: {added} 个文档', 'success')        else:            self._toast('未找到有效的PDF文档', 'warning')    # ==================== 右键菜单 ====================    def _show_context_menu(self, pos):        rows = set(idx.row() for idx in self.table.selectedIndexes())        if not rows:            return        menu = QMenu(self)        act_open = QAction('📂  打开文件位置', self)        act_open.triggered.connect(self._open_file_location)        menu.addAction(act_open)        act_preview = QAction('📄  打开文件预览', self)        act_preview.triggered.connect(self._open_file_preview)        menu.addAction(act_preview)        menu.addSeparator()        act_remove = QAction(f'❌  移除选中 ({len(rows)} 个)', self)        act_remove.triggered.connect(self._remove_selected)        menu.addAction(act_remove)        menu.exec(self.table.viewport().mapToGlobal(pos))    def _on_double_click(self, index):        self._open_file_preview()    def _open_file_location(self):        rows = set(idx.row() for idx in self.table.selectedIndexes())        if not rows:            return        row = list(rows)[0]        item = self.table.item(row, 4)        if item:            import subprocess            file_path = Path(item.text())            if file_path.exists():                subprocess.run(['explorer', '/select,', str(file_path)])            else:                self._toast('文件不存在', 'error')    def _open_file_preview(self):        rows = set(idx.row() for idx in self.table.selectedIndexes())        if not rows:            return        row = list(rows)[0]        item = self.table.item(row, 4)        if item:            import os            try:                os.startfile(item.text())            except Exception as e:                self._toast(f'无法打开文件: {e}', 'error')    # ==================== 刷新与状态 ====================    def _refresh_list(self):        """刷新列表"""        if not self._documents:            self.stack.setCurrentWidget(self.empty_area)            self.table.setRowCount(0)            return        self.stack.setCurrentWidget(self.table)        self.table.setRowCount(len(self._documents))        status_map = {            '待打印': TEXT_SECONDARY,            '打印中': PRIMARY,            '已完成': SUCCESS,            '失败': DANGER,        }        for row, doc in enumerate(self._documents):            name_item = QTableWidgetItem(doc['name'])            name_item.setToolTip(doc['path'])            size_item = QTableWidgetItem(f'{doc["size_mb"]}')            size_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)            pages_text = str(doc['pages']) if doc['pages'] else '-'            pages_item = QTableWidgetItem(pages_text)            pages_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)            status_text = doc['status']            status_color = status_map.get(status_text, TEXT_PRIMARY)            status_item = QTableWidgetItem(status_text)            status_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)            status_item.setForeground(QColor(status_color))            path_item = QTableWidgetItem(doc['path'])            path_item.setToolTip(doc['path'])            self.table.setItem(row, 0, name_item)            self.table.setItem(row, 1, size_item)            self.table.setItem(row, 2, pages_item)            self.table.setItem(row, 3, status_item)            self.table.setItem(row, 4, path_item)    def _update_status(self):        """更新统计卡片"""        total = len(self._documents)        total_size = sum(d['size_mb'] for d in self._documents)        total_pages = sum((d['pages'] or 0) for d in self._documents)        self.card_count.set_value(str(total))        self.card_size.set_value(f'{round(total_size, 2)} MB')        self.card_pages.set_value(str(total_pages))        printer_name = self._current_printer or '未设置'        if len(printer_name) > 15:            printer_name = printer_name[:13] + '...'        self.card_printer.set_value(printer_name)        if not self._is_printing_ui:            self.btn_print.setEnabled(total > 0)    def _toast(self, message: str, level: str = 'info'):        Toast(self, message, level=level)    # ==================== 窗口关闭 ====================    def closeEvent(self, event):        if self._is_printing_ui:            reply = QMessageBox.question(                self, '确认退出',                '打印任务正在进行中,确定要退出吗?',                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,                QMessageBox.StandardButton.No            )            if reply != QMessageBox.StandardButton.Yes:                event.ignore()                return        event.accept()def main():    app = QApplication(sys.argv)    app.setStyle('Fusion')    app.setFont(QFont('Microsoft YaHei UI', 9))    window = MainWindow()    window.show()    sys.exit(app.exec())if __name__ == '__main__':    main()

            这款「PDF批量打印工具」,可自动完成原需手动找文件、逐个打开、反复设置打印机、一张张点击打印、再逐一确认是否漏打错序的一整天打印工作。从面对几百上千个 PDF、逐个打开、反复配置纸张方向、手动等打印队列的繁琐与崩溃,到只需添加 PDF 或整个文件夹、选好打印机与纸张方向、设置份数颜色双面、一键启动,工具即可自动排序、自动跳过重复文件与临时文件、自动按设置逐份打印、自动统计页数大小、自动实时刷新每格状态并居中显示进度,还能在完成后汇总成功与失败数量,全程无需人工干预,终于解放了双手和时间!

            大佬们也可以举一反三,参照上面的代码思路根据自己工作中的实际情况来具体问题具体分析,实现自己定制化的需求。

            《该办公工具的应用场景:》

              如果你的场景符合下面任意几条,这个工具会特别有用:

              • 文件数量多:几十个起步,几百上千个更好

              • 格式统一:都是 PDF,打印参数基本一致

              • 集中存放:在同一个文件夹或几个子文件夹里

              • 命名有规律:需要按文件名顺序打印

              • 重复性高:每月 / 每周 / 每天都有一批要打

              • 参数固定:纸张、方向、双面、份数基本不变

              结语

              当Python遇见办公,牛马打工人终于笑出了猪叫声

              【职场人必看】每天早上一睁眼,想到又要面对:

              1.📊 堆积如山的Excel表格

              2.📑 机械重复的复制粘贴

              3.✍️ 永远改不完的各类文档

              4.诸如此类的更多........

              是不是连Ctrl+Alt+Delete的心都有了?别慌!别急,摸鱼这位“职场外挂”已经带着Python代码来拯救你了!

              另外,本文的代码和打包好的 exe 已同步上传至 NAS 私有云盘。有需要的大佬请私信摸鱼,仅需9.9 元进群即可获取下载链接,链接统一放在群公告里。目前公众号九成以上的文章资源都已上传 NAS 云盘,仅群内大佬可免费下载!

              最后,臣无大佬,无以至今日;大佬无臣,无以摸大鱼。感谢各位大佬看到最后。码字不易,摸鱼更不易,还望抬抬贵手,关注、点赞、转发、评论四连走起。大佬们每一下支持,都是摸鱼下次继续更新原创干货的动力!

              相关学习资料