欢迎来到徐庆高(Tea)的个人博客网站
磨难很爱我,一度将我连根拔起。从惊慌失措到心力交瘁,我孤身一人,但并不孤独无依。依赖那些依赖我的人,信任那些信任我的人,帮助那些给予我帮助的人。如果我愿意,可以分裂成无数面镜子,让他们看见我,就像看见自己。察言观色和模仿学习是我的领域。像每个深受创伤的人那样,最终,我学会了随遇而安。
当前位置: 日志文章 > 详细内容

python+tkinter智能实现票据生成

2025年07月09日 Python
效果如下使用python与tkinter实现的一个简单的票据生成,键入收款方,通过“添加明细”键入项目详细信息,生成的收据将有pdf格式保存。完整代码如下import tkin

效果如下 

使用python与tkinter实现的一个简单的票据生成,

键入收款方,通过“添加明细”键入项目详细信息,生成的收据将有pdf格式保存。

完整代码如下

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from fpdf import fpdf
from fpdf.enums import xpos, ypos  # 添加缺失的导入
from datetime import datetime
import platform
from pathlib import path
import logging
from tkcalendar import dateentry

# 日志配置
logging.basicconfig(filename='receipt.log', level=logging.info)


class amountutils:
    """金额处理工具类"""

    @staticmethod
    def to_chinese(num: float) -> str:
        """精确实现中文大写金额转换"""
        units = ['', '拾', '佰', '仟']
        big_units = ['', '万', '亿']
        digits = ('零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖')

        num = round(num, 2)
        integer_part = int(num)
        decimal_part = int(round(num - integer_part, 2) * 100)

        result = []

        # 处理整数部分
        if integer_part > 0:
            unit_index = 0
            while integer_part > 0:
                section = integer_part % 10000
                integer_part = integer_part // 10000
                section_str = ''

                for i in range(4):
                    digit = section % 10
                    section = section // 10
                    if digit != 0:
                        section_str = digits[digit] + units[i] + section_str
                    elif section_str and section_str[0] != '零':
                        section_str = '零' + section_str

                if section_str:
                    section_str += big_units[unit_index]
                result.append(section_str)
                unit_index += 1

            result = list(reversed(result))
            result_str = ''.join(result).replace('零零', '零').rstrip('零') + '元'
            result_str = result_str.replace('零零', '零')
            result = [result_str]
        else:
            result.append('零元')

        # 处理小数部分
        if decimal_part > 0:
            jiao = decimal_part // 10
            fen = decimal_part % 10

            if jiao > 0:
                result.append(digits[jiao] + '角')
            if fen > 0:
                result.append(digits[fen] + '分')
        else:
            result.append('整')

        return ''.join(result).replace('零元', '').replace('零零', '零')


class pdfgenerator:
    """严格按照图片样式的pdf生成器"""

    def __init__(self, font_config):
        self.font_config = font_config

    def generate(self, data: dict, save_path: str):
        """生成pdf文件"""
        pdf = fpdf()
        pdf.add_page()
        pdf.set_auto_page_break(auto=false)

        # 加载字体
        if self.font_config['path'] and path(self.font_config['path']).exists():
            pdf.add_font(self.font_config['name'], fname=self.font_config['path'])
            pdf.set_font(self.font_config['name'], size=12)
        else:
            pdf.set_font("arial", size=12)

        # 标题
        pdf.set_font_size(18)
        pdf.cell(0, 15, "收 款 收 据", align='c', new_x=xpos.lmargin, new_y=ypos.next)

        # 交款方和日期
        pdf.set_font_size(12)
        pdf.cell(50, 10, f"交款方:{data['payer']}", new_x=xpos.lmargin)
        pdf.cell(0, 10, f"日期:{data['date']}", align='r', new_x=xpos.lmargin, new_y=ypos.next)

        # 绘制表格
        self._draw_table(pdf, data)

        # 合计人民币
        self._draw_total(pdf, data['total'])

        pdf.output(save_path)

    def _draw_table(self, pdf, data):
        """绘制与图片完全一致的表格"""
        # 列宽(根据图片比例精确计算)
        col_width = [40, 20, 20, 25, 30, 35]
        headers = ['项目', '单位', '数量', '单价', '金额', '备注']

        # 表头
        pdf.set_xy(25, 45)
        pdf.set_fill_color(240, 240, 240)
        for i, header in enumerate(headers):
            pdf.cell(col_width[i], 10, header, border=1, fill=true, align='c')
        pdf.ln()

        # 表格内容
        pdf.set_font_size(12)
        y_position = 55
        for item in data['items']:
            if y_position > 250:  # 处理分页
                pdf.add_page()
                y_position = 45
                self._draw_table_header(pdf)
                y_position = 55

            pdf.set_xy(25, y_position)
            # 项目列左对齐,其他列居中/右对齐
            alignments = ['l', 'c', 'r', 'r', 'r', 'l']
            values = [
                item['name'],
                item['unit'],
                f"{item['quantity']:.2f}",
                f"¥{item['price']:.2f}",
                f"¥{item['quantity'] * item['price']:.2f}",
                item['remark']
            ]

            for i in range(6):
                pdf.cell(col_width[i], 10, values[i], border=1, align=alignments[i])

            y_position += 10

    def _draw_table_header(self, pdf):
        """绘制表头(分页时使用)"""
        col_width = [40, 20, 20, 25, 30, 35]
        headers = ['项目', '单位', '数量', '单价', '金额', '备注']
        pdf.set_xy(25, 45)
        pdf.set_fill_color(240, 240, 240)
        for i, header in enumerate(headers):
            pdf.cell(col_width[i], 10, header, border=1, fill=true, align='c')
        pdf.ln()

    def _draw_total(self, pdf, total):
        """绘制合计行(精确复制图片样式)"""
        pdf.set_xy(25, 105)
        # 第一行
        pdf.cell(100, 10, "合计人民币", border=1)
        pdf.cell(45, 10, f"¥{total:.2f}", border=1, align='r')
        pdf.cell(35, 10, "", border=1)  # 备注列

        # 第二行
        pdf.set_xy(25, 115)
        pdf.cell(100, 10, "", border=1)
        pdf.cell(80, 10, f"(大写){amountutils.to_chinese(total)}", border=1, align='l')


class receiptsystem:
    """主界面系统"""

    def __init__(self):
        self.root = tk.tk()
        self.root.title("智能收据系统")
        self.root.geometry("800x600")

        self.font_config = self._get_font_config()
        self.items = []
        self.total = 0.0

        self._create_ui()
        self.reset()  # 初始化表单数据

    def _get_font_config(self):
        """获取系统字体配置"""
        system = platform.system()
        font_config = {'name': 'arial', 'path': none}

        if system == 'windows':
            font_path = 'c:/windows/fonts/simhei.ttf'
            if path(font_path).exists():
                font_config.update(name='simhei', path=font_path)
        elif system == 'linux':
            font_path = '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc'
            if path(font_path).exists():
                font_config.update(name='wenquanyi zen hei', path=font_path)
        elif system == 'darwin':
            font_path = '/system/library/fonts/stheiti light.ttc'
            if path(font_path).exists():
                font_config.update(name='stheiti', path=font_path)

        return font_config

    def _create_ui(self):
        """创建与图片一致的ui布局"""
        main_frame = ttk.frame(self.root)
        main_frame.pack(fill=tk.both, expand=true, padx=20, pady=20)

        # 标题
        ttk.label(main_frame, text="收 款 收 据", font=('', 16, 'bold')).pack(pady=10)

        # 交款方和日期
        info_frame = ttk.frame(main_frame)
        info_frame.pack(fill=tk.x, pady=10)

        ttk.label(info_frame, text="交款方:").grid(row=0, column=0, sticky='e')
        self.payer_entry = ttk.entry(info_frame, width=25)
        self.payer_entry.grid(row=0, column=1, padx=5, sticky='ew')

        ttk.label(info_frame, text="日期:").grid(row=0, column=2, padx=10)
        self.date_entry = dateentry(info_frame,
                                    locale='zh_cn',
                                    date_pattern='yyyy-mm-dd',
                                    width=12)
        self.date_entry.grid(row=0, column=3, padx=5, sticky='ew')

        info_frame.columnconfigure(1, weight=1)
        info_frame.columnconfigure(3, weight=1)

        # 表格(精确列宽)
        self.tree = ttk.treeview(main_frame,
                                 columns=('项目', '单位', '数量', '单价', '金额', '备注'),
                                 show='headings',
                                 height=5)

        # 严格按照图片比例设置列宽
        columns = [
            ('项目', 160), ('单位', 80),
            ('数量', 80), ('单价', 100),
            ('金额', 100), ('备注', 100)
        ]
        for col, width in columns:
            self.tree.heading(col, text=col)
            self.tree.column(col, width=width, anchor='center')

        self.tree.pack(fill=tk.both, expand=true, pady=10)

        # 操作按钮
        btn_frame = ttk.frame(main_frame)
        btn_frame.pack(pady=10)
        ttk.button(btn_frame, text="添加明细", command=self.add_item).grid(row=0, column=0, padx=5)
        ttk.button(btn_frame, text="生成收据", command=self.generate_pdf).grid(row=0, column=1, padx=5)
        ttk.button(btn_frame, text="重置", command=self.reset).grid(row=0, column=2, padx=5)

    def add_item(self):
        """添加明细(包含备注字段)"""
        if len(self.items) >= 5:
            messagebox.showwarning("提示", "最多只能添加5条明细")
            return

        input_win = tk.toplevel(self.root)
        input_win.title("输入明细")

        fields = [
            ('项目', 0), ('单位', 1),
            ('数量', 2), ('单价', 3),
            ('备注', 4)
        ]

        entries = {}
        for idx, (label, row) in enumerate(fields):
            ttk.label(input_win, text=f"{label}:").grid(row=row, column=0, padx=5, pady=5)
            entry = ttk.entry(input_win)
            entry.grid(row=row, column=1, padx=5, pady=5)
            entries[label] = entry

        ttk.button(input_win, text="确认",
                   command=lambda: self._validate_entry(entries, input_win)).grid(row=5, columnspan=2)

    def _validate_entry(self, entries, window):
        """验证输入"""
        try:
            data = {
                'name': entries['项目'].get().strip(),
                'unit': entries['单位'].get().strip(),
                'quantity': float(entries['数量'].get()),
                'price': float(entries['单价'].get()),
                'remark': entries['备注'].get().strip()
            }

            if not data['name'] or not data['unit']:
                raise valueerror("项目和单位必须填写")
            if data['quantity'] <= 0 or data['price'] <= 0:
                raise valueerror("数量和单价必须大于零")

            # 更新表格
            amount = data['quantity'] * data['price']
            self.tree.insert('', 'end', values=(
                data['name'],
                data['unit'],
                f"{data['quantity']:.2f}",
                f"¥{data['price']:.2f}",
                f"¥{amount:.2f}",
                data['remark']
            ))
            self.items.append(data)
            self.total += amount
            window.destroy()

        except exception as e:
            messagebox.showerror("输入错误", str(e))

    def generate_pdf(self):
        """生成pdf"""
        try:
            if not self.items:
                raise valueerror("请先添加明细项目")
            if not self.payer_entry.get().strip():
                raise valueerror("交款方不能为空")

            data = {
                'payer': self.payer_entry.get(),
                'date': self.date_entry.get(),
                'items': self.items,
                'total': self.total
            }

            save_path = filedialog.asksaveasfilename(
                defaultextension=".pdf",
                filetypes=[("pdf文件", "*.pdf")]
            )

            if save_path:
                # 确保目录存在
                path = path(save_path)
                path.parent.mkdir(parents=true, exist_ok=true)

                pdfgenerator(self.font_config).generate(data, save_path)
                messagebox.showinfo("成功", f"文件已保存至:{save_path}")
                self.root.update()

        except exception as e:
            messagebox.showerror("生成错误", str(e))
            logging.error(f"生成错误: {str(e)}")

    def reset(self):
        """重置表单"""
        self.items = []
        self.total = 0.0
        self.tree.delete(*self.tree.get_children())
        self.payer_entry.delete(0, tk.end)
        self.date_entry.set_date(datetime.now())


if __name__ == "__main__":
    app = receiptsystem()
    app.root.mainloop()

此为简单示例,如果针对一些固定商品,在增添项目过程中引用数据库商品通过检索方式会更加的高效。

到此这篇关于python+tkinter智能实现票据生成的文章就介绍到这了,更多相关python票据生成内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!