python+tkinter智能實現(xiàn)票據(jù)生成
更新時間:2025年07月09日 10:28:16 作者:Atlas?Shepherd
這篇文章主要為大家詳細介紹了如何結(jié)合python和tkinter智能實現(xiàn)票據(jù)生成,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
效果如下
使用python與tkinter實現(xiàn)的一個簡單的票據(jù)生成,
鍵入收款方,通過“添加明細”鍵入項目詳細信息,生成的收據(jù)將有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: """精確實現(xiàn)中文大寫金額轉(zhuǎn)換""" units = ['', '拾', '佰', '仟'] big_units = ['', '萬', '億'] digits = ('零', '壹', '貳', '叁', '肆', '伍', '陸', '柒', '捌', '玖') num = round(num, 2) integer_part = int(num) decimal_part = int(round(num - integer_part, 2) * 100) result = [] # 處理整數(shù)部分 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('零元') # 處理小數(shù)部分 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, "收 款 收 據(jù)", 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): """繪制與圖片完全一致的表格""" # 列寬(根據(jù)圖片比例精確計算) col_width = [40, 20, 20, 25, 30, 35] headers = ['項目', '單位', '數(shù)量', '單價', '金額', '備注'] # 表頭 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() # 表格內(nèi)容 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 = ['項目', '單位', '數(shù)量', '單價', '金額', '備注'] 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: """主界面系統(tǒng)""" def __init__(self): self.root = tk.Tk() self.root.title("智能收據(jù)系統(tǒng)") self.root.geometry("800x600") self.font_config = self._get_font_config() self.items = [] self.total = 0.0 self._create_ui() self.reset() # 初始化表單數(shù)據(jù) def _get_font_config(self): """獲取系統(tǒng)字體配置""" 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): """創(chuàng)建與圖片一致的UI布局""" main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20) # 標題 ttk.Label(main_frame, text="收 款 收 據(jù)", 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=('項目', '單位', '數(shù)量', '單價', '金額', '備注'), show='headings', height=5) # 嚴格按照圖片比例設置列寬 columns = [ ('項目', 160), ('單位', 80), ('數(shù)量', 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="生成收據(jù)", 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), ('數(shù)量', 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['數(shù)量'].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("數(shù)量和單價必須大于零") # 更新表格 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()
此為簡單示例,如果針對一些固定商品,在增添項目過程中引用數(shù)據(jù)庫商品通過檢索方式會更加的高效。
到此這篇關(guān)于python+tkinter智能實現(xiàn)票據(jù)生成的文章就介紹到這了,更多相關(guān)python票據(jù)生成內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python網(wǎng)絡編程學習筆記(五):socket的一些補充
前面已經(jīng)為大家介紹了python socket的一些相關(guān)知識,這里為大家補充下,方便需要的朋友2014-06-06Python?如何將?matplotlib?圖表集成進到PDF?中
這篇文章主要介紹了Python?如何將?matplotlib?圖表集成進到PDF?中,文章介紹內(nèi)容詳細,具有一定的參考價值,需要的小伙伴可以參考一下,希望對你的學習有所幫助2022-03-03