You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter中ReportLab生成的销售报表无法打印问题排查求助

客户销售报表打印异常问题

我用ReportLab在Tkinter中开发了客户销售报表功能,PDF可正常生成并保存:

  • 未连接打印机时,点击打印按钮会提示无可用打印机,同时正常保存PDF;
  • 连接打印机后,点击打印按钮无报错、PDF正常保存,但无法完成打印操作。

当前环境:Windows 11、最新版Python、最新版ReportLab。


报表核心代码

def print_cutomer_bill(self, data, code, id):
    pdf_bill = str(code)+"pdf"

    # 插入销售记录到数据库
    cursor.execute("INSERT INTO tbl_sale (customer_id, bill_code, total_amount, discount, previous_amount, amount_paid, amount_remaining, return_amount, sale_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", (id, code, self.cr_bill, self.discount, self.pre_balance, self.cash, self.total_remains, self.return_amount, self.cr_date))

    sale_id = cursor.lastrowid
    page_height = 1000

    c = canvas.Canvas(pdf_bill, pagesize=(600,1000))
    
    c.setFont("Helvetica", 15)
    c.drawString(200, page_height - 20, "HAZRAT CLOTH HOUSE")

    c.setFont("Helvetica", 12)
    c.drawString(40, page_height - 50, f"Address: Rangpur Adda, Rangpur Road")
    c.drawString(350, page_height - 50, f"Contact: 03459236354, 03463868838")
    c.drawString(40, page_height - 60, "_" * 80)

    # 账单头部
    c.drawString(40, page_height - 80, f"Invoice No. {code}")
    c.drawString(40, page_height - 100, f"Fullname: {data[0]}")
    c.drawString(220, page_height - 100, f"Phone No. : {data[3]}")
    c.drawString(420, page_height - 100, f"Address: {data[4]}")
    c.drawString(40, page_height - 120, f"Date: {self.cr_date}")

    # 绘制分隔线
    c.drawString(40, page_height - 140, "_" * 80)
    # 表格表头
    c.drawString(40, page_height - 180, "SNo.")
    c.drawString(100, page_height - 180, "Name")
    c.drawString(280, page_height - 180, "Qty")
    c.drawString(350, page_height - 180, "Price")
    c.drawString(420, page_height - 180, "Total")

    y_position = page_height -200
    s_no = 1
    for rows in self.product_rows:
        prodid = self.product_rows[rows]["id"].get()
        
        if prodid:
            quantity = self.product_rows[rows]["quantity"].get()
            name = self.product_rows[rows]["name"].get()
            price = float(self.product_rows[rows]["price"].get())
            total = float(self.product_rows[rows]["total"].get())
            c.drawString(50, y_position, str(s_no))
            c.drawString(110, y_position, name)
            c.drawString(290, y_position, quantity)
            c.drawString(360, y_position, str(int(price // 1)))
            c.drawString(430, y_position, str(int(total // 1)))

            # 绘制单元格边框
            c.rect(40, y_position - 5, 60, 20)  # SNo.列
            c.rect(100, y_position - 5, 180, 20)  # Name列
            c.rect(280, y_position - 5, 70, 20)  # Qty列
            c.rect(350, y_position - 5, 70, 20)  # Price列
            c.rect(420, y_position - 5, 80, 20)  # Total列

            y_position -= 20
            s_no = int(s_no) + 1

            # 插入销售明细
            cursor.execute("INSERT INTO tbl_sale_detail (sale_id, product_id, purchased_units, product_price, subtotal) VALUES (?, ?, ?, ?, ?)", (sale_id, prodid, quantity, price, total))

            # 更新产品库存
            cursor.execute("SELECT stock FROM tbl_product WHERE product_id=?",(prodid,))
            qty = cursor.fetchone()
            stock = qty[0] - int(quantity)

            cursor.execute("UPDATE tbl_product SET stock=? WHERE product_id=?",(stock, prodid,))
    
    c.drawString(40, y_position - 50, "****** WAPSI ******")
    c.drawString(40, y_position - 60, "_" * 80)

    y_position = y_position - 100
    s_no = 1
    for rows in self.product_return_rows:
        pro_ret_id = self.product_return_rows[rows]["id"].get()
        
        if pro_ret_id:
            quantity = self.product_return_rows[rows]["quantity"].get()
            name = self.product_return_rows[rows]["name"].get()
            price = float(self.product_return_rows[rows]["price"].get())
            total = float(self.product_return_rows[rows]["total"].get())

            c.drawString(50, y_position, str(s_no))
            c.drawString(110, y_position, name)
            c.drawString(290, y_position, quantity)
            c.drawString(360, y_position, str(int(price // 1)))
            c.drawString(430, y_position, str(int(total // 1)))

            # 绘制单元格边框
            c.rect(40, y_position - 5, 60, 20)  # SNo.列
            c.rect(100, y_position - 5, 180, 20)  # Name列
            c.rect(280, y_position - 5, 70, 20)  # Qty列
            c.rect(350, y_position - 5, 70, 20)  # Price列
            c.rect(420, y_position - 5, 80, 20)  # Total列

            y_position -= 20
            s_no = int(s_no) + 1

            # 插入退货明细
            cursor.execute("INSERT INTO tbl_return (sale_id, product_id, return_units, product_price, subtotal) VALUES (?, ?, ?, ?, ?)", (sale_id, pro_ret_id, quantity, price, total))

            # 更新产品库存
            cursor.execute("SELECT stock FROM tbl_product WHERE product_id=?",(pro_ret_id,))
            qty = cursor.fetchone()
            stock = qty[0] + int(quantity)

            cursor.execute("UPDATE tbl_product SET stock=? WHERE product_id=?",(stock, pro_ret_id,))

    conn.commit()
    y_position = y_position - 20

    c.drawString(40, y_position - 10, f"Cr-Bill: {str(int(self.cr_bill // 1))}")
    c.drawString(220, y_position - 10, f"Discount. : {str(int(self.discount // 1))}")
    c.drawString(420, y_position - 10, f"Pre-Balance: {str(int(self.pre_balance // 1))}")

    c.drawString(40, y_position - 30, f"Return: {str(int(self.return_amount // 1))}")
    c.drawString(220, y_position - 30, f"Cash. : {str(int(self.cash // 1))}")
    c.drawString(420, y_position - 30, f"Due Balance: {str(int(self.total_remains // 1))}")
    

    c.drawString(40, y_position - 100, "_" * 80)
    c.drawString(40, y_position - 120, "Developed By: www.easywebsols.com")

    try:
        c.save()
        os.startfile(pdf_bill,'Print')
    except OSError as e:
        if e.winerror == 1155:
            messagebox.showerror("Printer Error","No printer is available or connected!")
        else:
            messagebox.showerror("Error",f"An error occured while trying to print: {str(e)}")
    else:
        messagebox.showinfo("SUCCESS","Bill Saved Successfully")

问题排查与修复建议

核心问题点

  1. 文件名缺失后缀:生成的PDF文件名是str(code)+"pdf"(比如INV001pdf),没有.pdf后缀。部分系统程序或打印服务可能因识别问题无法处理该文件的打印请求。
  2. os.startfile的局限性:该方法仅将打印请求交给系统默认程序,不会等待打印完成,也不反馈执行状态。如果默认PDF阅读器的打印设置异常、后台打印队列阻塞,就会出现"无报错但不打印"的情况。

修复步骤

  1. 添加PDF文件后缀
    修改文件名生成代码:

    pdf_bill = f"{code}.pdf"
    
  2. 替换为更可靠的打印方式
    用subprocess调用系统打印命令,替代os.startfile,可获取打印执行状态:

    import subprocess
    
    # 替换原有try-except块
    try:
        c.save()
        # 调用系统打印命令,等待执行完成
        subprocess.run(["start", "/wait", "", pdf_bill, "/p"], shell=True, check=True)
    except subprocess.CalledProcessError as e:
        messagebox.showerror("打印错误", f"打印任务执行失败: {str(e)}")
    except OSError as e:
        if e.winerror == 1155:
            messagebox.showerror("打印机错误", "无可用打印机或未连接!")
        else:
            messagebox.showerror("错误", f"操作出错: {str(e)}")
    else:
        messagebox.showinfo("成功", "账单已保存并提交打印")
    
  3. 系统层面检查

    • 手动打开生成的PDF,尝试手动打印,验证打印机本身是否正常工作;
    • 打开Windows「设置-蓝牙和设备-打印机和扫描仪」,清空对应打印机的任务队列;
    • 确认默认PDF阅读器的打印设置(如默认打印机选择正确)。

内容的提问来源于stack exchange,提问作者Muhammad Hafizullah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 14:05:56