Python + MySQL + Tkinter 桌面版图书借阅管理系统

发布时间:2026/8/10 8:37:23
Python + MySQL + Tkinter 桌面版图书借阅管理系统 Python MySQL Tkinter 桌面版图书借阅管理系统这是一套经典的C/S架构桌面应用适合作为课程设计或毕业设计。下面我将从系统设计、数据库建模、核心功能代码、界面布局等方面为你完整呈现。️ 系统总体设计功能模块划分图书借阅管理系统 ├── 登录模块 ├── 图书管理 │ ├── 图书录入/编辑/删除 │ ├── 图书检索书名、作者、ISBN │ └── 库存查看 ├── 读者管理 │ ├── 读者注册/编辑/注销 │ └── 读者信息查询 ├── 借书管理 │ ├── 借书操作扫描ISBN读者证号 │ └── 续借操作 ├── 还书管理 │ ├── 还书操作 │ └── 逾期罚款计算 └── 统计报表 ├── 热门图书排行 ├── 读者借阅排行榜 └── 每日/月流通量技术栈层次技术客户端Python 3.10, Tkinter, ttkbootstrap美化数据库MySQL 8.0连接池PyMySQL 或 mysql-connector-python辅助库Pillow图片处理, pandas报表导出️ 数据库设计 (MySQL)-- 创建数据库 CREATE DATABASE library_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE library_db; -- 管理员表 CREATE TABLE admins ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(32) UNIQUE NOT NULL, password_hash VARCHAR(256) NOT NULL, real_name VARCHAR(50), role ENUM(admin,operator) DEFAULT operator ); -- 图书类别表 CREATE TABLE categories ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, parent_id INT DEFAULT NULL, FOREIGN KEY (parent_id) REFERENCES categories(id) ); -- 图书表 CREATE TABLE books ( id INT AUTO_INCREMENT PRIMARY KEY, isbn VARCHAR(20) UNIQUE NOT NULL, title VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), publish_year YEAR, category_id INT, total_stock INT DEFAULT 1, -- 总库存 current_stock INT DEFAULT 1, -- 当前可借数量 location VARCHAR(50), -- 书架位置 cover_image BLOB, -- 封面图片二进制 description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_title (title), INDEX idx_isbn (isbn), FOREIGN KEY (category_id) REFERENCES categories(id) ); -- 读者表 CREATE TABLE readers ( id INT AUTO_INCREMENT PRIMARY KEY, reader_no VARCHAR(18) UNIQUE NOT NULL, -- 读者证号 name VARCHAR(50) NOT NULL, gender ENUM(男,女), phone VARCHAR(15), email VARCHAR(100), id_card VARCHAR(18), -- 身份证号 register_date DATE DEFAULT (CURRENT_DATE), max_borrow INT DEFAULT 5, -- 最大借阅数量 current_borrow INT DEFAULT 0, -- 当前已借数量 status ENUM(正常,挂失,注销) DEFAULT 正常 ); -- 借阅记录表 CREATE TABLE borrow_records ( id BIGINT AUTO_INCREMENT PRIMARY KEY, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_date DATETIME DEFAULT CURRENT_TIMESTAMP, due_date DATE NOT NULL, -- 应还日期 return_date DATETIME DEFAULT NULL, -- 实际归还日期 renew_count INT DEFAULT 0, -- 续借次数 fine_amount DECIMAL(8,2) DEFAULT 0.00, -- 罚款金额 operator_id INT, -- 办理管理员ID status ENUM(借出,已还,逾期,丢失) DEFAULT 借出, FOREIGN KEY (book_id) REFERENCES books(id), FOREIGN KEY (reader_id) REFERENCES readers(id), FOREIGN KEY (operator_id) REFERENCES admins(id), INDEX idx_reader (reader_id), INDEX idx_book (book_id), INDEX idx_status (status) ); -- 罚款记录表 CREATE TABLE fines ( id INT AUTO_INCREMENT PRIMARY KEY, record_id BIGINT NOT NULL, amount DECIMAL(8,2) NOT NULL, paid TINYINT DEFAULT 0, pay_date DATETIME DEFAULT NULL, FOREIGN KEY (record_id) REFERENCES borrow_records(id) );️ Tkinter 界面设计与核心代码项目结构library_manager/ ├── main.py # 程序入口 ├── config.py # 数据库配置 ├── database.py # 数据库操作类 ├── models/ │ ├── book.py # 图书模型 │ ├── reader.py # 读者模型 │ └── borrow.py # 借阅模型 ├── views/ │ ├── login_window.py # 登录窗口 │ ├── main_window.py # 主界面包含菜单栏、工具栏 │ ├── book_management.py # 图书管理页面 │ ├── reader_management.py # 读者管理页面 │ ├── borrow_return.py # 借还书页面 │ └── statistics.py # 统计报表页面 ├── utils/ │ ├── validators.py # 输入验证 │ └── helpers.py # 辅助函数日期计算、罚款等 └── resources/ ├── icons/ # 图标文件 └── images/ # 默认封面等1. 数据库连接与操作基类 (database.py)import pymysql from config import DB_CONFIG class DatabaseManager: _instance None def __new__(cls): if cls._instance is None: cls._instance super().__new__(cls) cls._instance.connection None return cls._instance def connect(self): if not self.connection or not self.connection.open: self.connection pymysql.connect(**DB_CONFIG) return self.connection def execute_query(self, sql, paramsNone): conn self.connect() with conn.cursor(pymysql.cursors.DictCursor) as cursor: cursor.execute(sql, params) result cursor.fetchall() return result def execute_update(self, sql, paramsNone): conn self.connect() with conn.cursor() as cursor: affected cursor.execute(sql, params) conn.commit() return affected def close(self): if self.connection and self.connection.open: self.connection.close()2. 登录窗口 (views/login_window.py)import tkinter as tk from tkinter import messagebox from database import DatabaseManager import hashlib class LoginWindow: def __init__(self): self.window tk.Tk() self.window.title(图书借阅管理系统 - 登录) self.window.geometry(400x300) self.window.resizable(False, False) # 居中显示 screen_w self.window.winfo_screenwidth() screen_h self.window.winfo_screenheight() x (screen_w - 400) // 2 y (screen_h - 300) // 2 self.window.geometry(f{x}{y}) self.setup_ui() def setup_ui(self): # 标题 tk.Label(self.window, text图书借阅管理系统, font(微软雅黑, 18)).pack(pady30) # 用户名 frame_user tk.Frame(self.window) frame_user.pack(pady10) tk.Label(frame_user, text用户名:, font(微软雅黑, 12)).pack(sidetk.LEFT) self.entry_user tk.Entry(frame_user, font(微软雅黑, 12), width25) self.entry_user.pack(sidetk.LEFT, padx10) # 密码 frame_pass tk.Frame(self.window) frame_pass.pack(pady10) tk.Label(frame_pass, text密 码:, font(微软雅黑, 12)).pack(sidetk.LEFT) self.entry_pass tk.Entry(frame_pass, show*, font(微软雅黑, 12), width25) self.entry_pass.pack(sidetk.LEFT, padx10) # 登录按钮 btn_login tk.Button(self.window, text登 录, font(微软雅黑, 14), commandself.login, bg#4CAF50, fgwhite, width15) btn_login.pack(pady20) # 回车绑定 self.window.bind(Return, lambda e: self.login()) def login(self): username self.entry_user.get().strip() password self.entry_pass.get().strip() if not username or not password: messagebox.showwarning(提示, 请输入用户名和密码) return # 密码加密SHA256 pwd_hash hashlib.sha256(password.encode()).hexdigest() db DatabaseManager() sql SELECT id, username, real_name, role FROM admins WHERE username%s AND password_hash%s result db.execute_query(sql, (username, pwd_hash)) if result: admin_info result[0] self.window.destroy() from views.main_window import MainWindow MainWindow(admin_info).run() else: messagebox.showerror(错误, 用户名或密码错误)3. 主窗口 (views/main_window.py)import tkinter as tk from tkinter import ttk, Menu from views.book_management import BookManagement from views.reader_management import ReaderManagement from views.borrow_return import BorrowReturn from views.statistics import Statistics class MainWindow: def __init__(self, admin_info): self.admin admin_info self.root tk.Tk() self.root.title(f图书借阅管理系统 - 欢迎 {admin_info[real_name]}) self.root.geometry(1200x700) self.setup_menu() self.setup_notebook() self.setup_status_bar() def setup_menu(self): menubar Menu(self.root) self.root.config(menumenubar) # 系统菜单 sys_menu Menu(menubar, tearoff0) sys_menu.add_command(label修改密码, commandself.change_password) sys_menu.add_separator() sys_menu.add_command(label退出系统, commandself.exit_app) menubar.add_cascade(label系统, menusys_menu) # 帮助菜单 help_menu Menu(menubar, tearoff0) help_menu.add_command(label关于, commandself.show_about) menubar.add_cascade(label帮助, menuhelp_menu) def setup_notebook(self): self.notebook ttk.Notebook(self.root) self.notebook.pack(filltk.BOTH, expandTrue, padx5, pady5) # 添加各个功能标签页 self.book_mgr BookManagement(self.notebook, self.admin) self.reader_mgr ReaderManagement(self.notebook, self.admin) self.borrow_mgr BorrowReturn(self.notebook, self.admin) self.statistics Statistics(self.notebook, self.admin) self.notebook.add(self.book_mgr, text图书管理) self.notebook.add(self.reader_mgr, text读者管理) self.notebook.add(self.borrow_mgr, text借还书) self.notebook.add(self.statistics, text统计报表) def setup_status_bar(self): status_frame tk.Frame(self.root, relieftk.SUNKEN, bd1) status_frame.pack(sidetk.BOTTOM, filltk.X) self.lbl_admin tk.Label(status_frame, textf当前管理员: {self.admin[real_name]}, anchortk.W) self.lbl_admin.pack(sidetk.LEFT, padx5) self.lbl_time tk.Label(status_frame, anchortk.E) self.lbl_time.pack(sidetk.RIGHT, padx5) self.update_time() def update_time(self): from datetime import datetime now datetime.now().strftime(%Y-%m-%d %H:%M:%S) self.lbl_time.config(textnow) self.root.after(1000, self.update_time) def change_password(self): # 弹出修改密码对话框 pass def exit_app(self): if tk.messagebox.askokcancel(确认, 确定要退出系统吗): self.root.destroy() def show_about(self): tk.messagebox.showinfo(关于, 图书借阅管理系统 v1.0\nPython MySQL Tkinter) def run(self): self.root.mainloop()4. 图书管理页面 (views/book_management.py) 核心部分import tkinter as tk from tkinter import ttk, messagebox from database import DatabaseManager class BookManagement(tk.Frame): def __init__(self, parent, admin): super().__init__(parent) self.admin admin self.db DatabaseManager() self.setup_ui() self.load_books() def setup_ui(self): # 顶部搜索区域 search_frame tk.Frame(self) search_frame.pack(filltk.X, padx10, pady5) tk.Label(search_frame, text关键词:).pack(sidetk.LEFT) self.search_entry tk.Entry(search_frame, width30) self.search_entry.pack(sidetk.LEFT, padx5) self.search_entry.bind(KeyRelease, self.search_books) tk.Label(search_frame, text类别:).pack(sidetk.LEFT, padx(20,0)) self.category_combo ttk.Combobox(search_frame, statereadonly, width15) self.category_combo.pack(sidetk.LEFT, padx5) self.load_categories() self.category_combo.bind(ComboboxSelected, self.search_books) # 操作按钮 btn_frame tk.Frame(self) btn_frame.pack(filltk.X, padx10, pady5) tk.Button(btn_frame, text新增图书, commandself.add_book).pack(sidetk.LEFT, padx5) tk.Button(btn_frame, text编辑图书, commandself.edit_book).pack(sidetk.LEFT, padx5) tk.Button(btn_frame, text删除图书, commandself.delete_book).pack(sidetk.LEFT, padx5) tk.Button(btn_frame, text刷新, commandself.load_books).pack(sidetk.LEFT, padx5) # 表格显示 columns (id, isbn, title, author, publisher, stock, current, location) self.tree ttk.Treeview(self, columnscolumns, showheadings, selectmodebrowse) self.tree.heading(id, textID) self.tree.heading(isbn, textISBN) self.tree.heading(title, text书名) self.tree.heading(author, text作者) self.tree.heading(publisher, text出版社) self.tree.heading(stock, text总库存) self.tree.heading(current, text可借) self.tree.heading(location, text位置) self.tree.column(id, width50, anchortk.CENTER) self.tree.column(isbn, width150) self.tree.column(title, width250) self.tree.column(author, width100) self.tree.column(publisher, width150) self.tree.column(stock, width70, anchortk.CENTER) self.tree.column(current, width70, anchortk.CENTER) self.tree.column(location, width100) scrollbar ttk.Scrollbar(self, orienttk.VERTICAL, commandself.tree.yview) self.tree.configure(yscrollcommandscrollbar.set) self.tree.pack(sidetk.LEFT, filltk.BOTH, expandTrue, padx10, pady5) scrollbar.pack(sidetk.RIGHT, filltk.Y, pady5) self.tree.bind(Double-1, self.edit_book) def load_categories(self): sql SELECT id, name FROM categories cats self.db.execute_query(sql) names [全部] [c[name] for c in cats] self.category_combo[values] names self.category_combo.current(0) self.cat_map {c[name]: c[id] for c in cats} def load_books(self, keyword, cat_idNone): # 清空现有数据 for item in self.tree.get_children(): self.tree.delete(item) sql SELECT id, isbn, title, author, publisher, total_stock, current_stock, location FROM books WHERE 11 params [] if keyword: sql AND (title LIKE %s OR author LIKE %s OR isbn LIKE %s) kw f%{keyword}% params.extend([kw, kw, kw]) if cat_id: sql AND category_id %s params.append(cat_id) sql ORDER BY id DESC books self.db.execute_query(sql, tuple(params)) for b in books: self.tree.insert(, tk.END, values( b[id], b[isbn], b[title], b[author], b[publisher], b[total_stock], b[current_stock], b[location] )) def search_books(self, eventNone): keyword self.search_entry.get().strip() cat_name self.category_combo.get() cat_id self.cat_map.get(cat_name) if cat_name ! 全部 else None self.load_books(keyword, cat_id) def add_book(self): # 弹出新增对话框这里简化 from dialogs.book_dialog import BookDialog BookDialog(self, 新增图书, None, self.on_book_saved) def edit_book(self, eventNone): selected self.tree.selection() if not selected: messagebox.showwarning(提示, 请选择一本图书) return item self.tree.item(selected[0]) book_id item[values][0] from dialogs.book_dialog import BookDialog BookDialog(self, 编辑图书, book_id, self.on_book_saved) def delete_book(self): selected self.tree.selection() if not selected: messagebox.showwarning(提示, 请选择要删除的图书) return item self.tree.item(selected[0]) book_id item[values][0] title item[values][2] if messagebox.askyesno(确认删除, f确定要删除《{title}》吗\n此操作不可恢复): try: sql DELETE FROM books WHERE id %s self.db.execute_update(sql, (book_id,)) self.load_books() messagebox.showinfo(成功, 图书已删除) except Exception as e: messagebox.showerror(错误, f删除失败{str(e)}) def on_book_saved(self): self.load_books()5. 借还书页面核心逻辑 (views/borrow_return.py)import tkinter as tk from tkinter import ttk, messagebox from datetime import datetime, timedelta from database import DatabaseManager class BorrowReturn(tk.Frame): def __init__(self, parent, admin): super().__init__(parent) self.admin admin self.db DatabaseManager() self.setup_ui() def setup_ui(self): # 借书区域 borrow_frame tk.LabelFrame(self, text借书操作, padx10, pady10) borrow_frame.pack(filltk.X, padx10, pady5) tk.Label(borrow_frame, text读者证号:).grid(row0, column0, stickytk.W) self.entry_reader tk.Entry(borrow_frame, width25) self.entry_reader.grid(row0, column1, padx5) tk.Button(borrow_frame, text查询读者, commandself.query_reader).grid(row0, column2, padx5) tk.Label(borrow_frame, text图书ISBN:).grid(row1, column0, stickytk.W, pady10) self.entry_isbn tk.Entry(borrow_frame, width25) self.entry_isbn.grid(row1, column1, padx5) tk.Button(borrow_frame, text查询图书, commandself.query_book).grid(row1, column2, padx5) self.btn_borrow tk.Button(borrow_frame, text确认借书, commandself.do_borrow, bg#2196F3, fgwhite, width15) self.btn_borrow.grid(row2, column0, columnspan3, pady10) # 显示读者和图书信息 self.info_text tk.Text(borrow_frame, height4, width70, statetk.DISABLED) self.info_text.grid(row3, column0, columnspan3, pady5) # 还书区域 return_frame tk.LabelFrame(self, text还书操作, padx10, pady10) return_frame.pack(filltk.X, padx10, pady5) tk.Label(return_frame, text借阅记录ID:).pack(sidetk.LEFT) self.entry_record tk.Entry(return_frame, width20) self.entry_record.pack(sidetk.LEFT, padx5) tk.Button(return_frame, text查询记录, commandself.query_record).pack(sidetk.LEFT, padx5) tk.Button(return_frame, text确认还书, commandself.do_return, bg#FF9800, fgwhite).pack(sidetk.LEFT, padx20) # 当前借阅列表 list_frame tk.LabelFrame(self, text当前借阅记录, padx10, pady10) list_frame.pack(filltk.BOTH, expandTrue, padx10, pady5) columns (id, reader, book, borrow_date, due_date, status) self.tree ttk.Treeview(list_frame, columnscolumns, showheadings, height10) self.tree.heading(id, text记录ID) self.tree.heading(reader, text读者) self.tree.heading(book, text图书) self.tree.heading(borrow_date, text借书日期) self.tree.heading(due_date, text应还日期) self.tree.heading(status, text状态) self.tree.column(id, width60, anchortk.CENTER) self.tree.column(reader, width100) self.tree.column(book, width200) self.tree.column(borrow_date, width130) self.tree.column(due_date, width110) self.tree.column(status, width80, anchortk.CENTER) self.tree.pack(filltk.BOTH, expandTrue) self.load_current_borrows() def query_reader(self): reader_no self.entry_reader.get().strip() if not reader_no: messagebox.showwarning(提示, 请输入读者证号) return sql SELECT id, name, current_borrow, max_borrow, status FROM readers WHERE reader_no%s result self.db.execute_query(sql, (reader_no,)) if result: self.current_reader result[0] self.show_info(f读者{result[0][name]} 已借{result[0][current_borrow]}/{result[0][max_borrow]} 状态{result[0][status]}) else: messagebox.showerror(错误, 未找到该读者) def query_book(self): isbn self.entry_isbn.get().strip() if not isbn: messagebox.showwarning(提示, 请输入ISBN) return sql SELECT id, title, author, current_stock FROM books WHERE isbn%s result self.db.execute_query(sql, (isbn,)) if result: self.current_book result[0] self.show_info(f图书《{result[0][title]}》 可借{result[0][current_stock]}本) else: messagebox.showerror(错误, 未找到该书) def do_borrow(self): if not hasattr(self, current_reader) or not hasattr(self, current_book): messagebox.showwarning(提示, 请先查询读者和图书) return reader self.current_reader book self.current_book # 检查读者状态 if reader[status] ! 正常: messagebox.showerror(错误, 该读者状态异常无法借书) return if reader[current_borrow] reader[max_borrow]: messagebox.showerror(错误, 该读者已达到最大借阅数量) return if book[current_stock] 0: messagebox.showerror(错误, 该书库存不足) return # 执行借书事务 try: conn self.db.connect() with conn.cursor() as cursor: # 插入借阅记录 due_date (datetime.now() timedelta(days30)).date() # 默认借期30天 sql_insert INSERT INTO borrow_records (book_id, reader_id, due_date, operator_id, status) VALUES (%s, %s, %s, %s, 借出) cursor.execute(sql_insert, (book[id], reader[id], due_date, self.admin[id])) # 更新图书库存 sql_update_book UPDATE books SET current_stock current_stock - 1 WHERE id %s cursor.execute(sql_update_book, (book[id],)) # 更新读者当前借阅数 sql_update_reader UPDATE readers SET current_borrow current_borrow 1 WHERE id %s cursor.execute(sql_update_reader, (reader[id],)) conn.commit() messagebox.showinfo(成功, f借书成功应还日期{due_date}) self.clear_borrow_form() self.load_current_borrows() except Exception as e: conn.rollback() messagebox.showerror(错误, f借书失败{str(e)}) def do_return(self): record_id self.entry_record.get().strip() if not record_id: messagebox.showwarning(提示, 请输入借阅记录ID) return # 查询借阅记录 sql SELECT br.*, b.id as book_id, b.title, r.id as reader_id, r.name as reader_name FROM borrow_records br JOIN books b ON br.book_id b.id JOIN readers r ON br.reader_id r.id WHERE br.id %s AND br.status 借出 result self.db.execute_query(sql, (record_id,)) if not result: messagebox.showerror(错误, 未找到该借阅记录或已归还) return record result[0] # 计算逾期罚款假设每本每天0.5元 due record[due_date] now datetime.now().date() overdue_days (now - due).days fine max(overdue_days * 0.5, 0) if overdue_days 0 else 0 msg f图书《{record[title]}》\n读者{record[reader_name]}\n应还日期{due}\n逾期天数{overdue_days if overdue_days0 else 0}\n罚款金额{fine:.2f}元 if fine 0: if not messagebox.askyesno(逾期罚款, f{msg}\n\n是否确认还书并缴纳罚款): return else: messagebox.showinfo(还书确认, msg) # 执行还书事务 try: conn self.db.connect() with conn.cursor() as cursor: # 更新借阅记录 sql_update_record UPDATE borrow_records SET return_dateNOW(), status已还, fine_amount%s WHERE id%s cursor.execute(sql_update_record, (fine, record_id)) # 增加图书库存 sql_update_book UPDATE books SET current_stock current_stock 1 WHERE id %s cursor.execute(sql_update_book, (record[book_id],)) # 减少读者当前借阅数 sql_update_reader UPDATE readers SET current_borrow current_borrow - 1 WHERE id %s cursor.execute(sql_update_reader, (record[reader_id],)) # 如果有罚款插入罚款记录 if fine 0: sql_fine INSERT INTO fines (record_id, amount, paid) VALUES (%s, %s, 1) cursor.execute(sql_fine, (record_id, fine)) conn.commit() messagebox.showinfo(成功, f还书成功罚款{fine:.2f}元已缴纳。) self.entry_record.delete(0, tk.END) self.load_current_borrows() except Exception as e: conn.rollback() messagebox.showerror(错误, f还书失败{str(e)}) def load_current_borrows(self): for item in self.tree.get_children(): self.tree.delete(item) sql SELECT br.id, r.name as reader_name, b.title, br.borrow_date, br.due_date, br.status FROM borrow_records br JOIN readers r ON br.reader_id r.id JOIN books b ON br.book_id b.id WHERE br.status IN (借出,逾期) ORDER BY br.borrow_date DESC LIMIT 100 records self.db.execute_query(sql) for rec in records: self.tree.insert(, tk.END, values( rec[id], rec[reader_name], rec[title], rec[borrow_date].strftime(%Y-%m-%d %H:%M), rec[due_date], rec[status] )) def clear_borrow_form(self): self.entry_reader.delete(0, tk.END) self.entry_isbn.delete(0, tk.END) if hasattr(self, current_reader): del self.current_reader if hasattr(self, current_book): del self.current_book self.info_text.config(statetk.NORMAL) self.info_text.delete(1.0, tk.END) self.info_text.config(statetk.DISABLED) def show_info(self, text): self.info_text.config(statetk.NORMAL) self.info_text.delete(1.0, tk.END) self.info_text.insert(tk.END, text) self.info_text.config(statetk.DISABLED) 统计报表示例 (views/statistics.py)import tkinter as tk from tkinter import ttk from database import DatabaseManager class Statistics(tk.Frame): def __init__(self, parent, admin): super().__init__(parent) self.admin admin self.db DatabaseManager() self.setup_ui() self.load_statistics() def setup_ui(self): notebook ttk.Notebook(self) notebook.pack(filltk.BOTH, expandTrue) # 热门图书 tab1 tk.Frame(notebook) notebook.add(tab1, text热门图书TOP10) self.popular_tree ttk.Treeview(tab1, columns(rank,title,author,count), showheadings) self.popular_tree.heading(rank, text排名) self.popular_tree.heading(title, text书名) self.popular_tree.heading(author, text作者) self.popular_tree.heading(count, text借阅次数) self.popular_tree.pack(filltk.BOTH, expandTrue, padx10, pady10) # 读者排行 tab2 tk.Frame(notebook) notebook.add(tab2, text读者借阅排行) self.reader_tree ttk.Treeview(tab2, columns(rank,name,count), showheadings) self.reader_tree.heading(rank, text排名) self.reader_tree.heading(name, text读者姓名) self.reader_tree.heading(count, text借阅次数) self.reader_tree.pack(filltk.BOTH, expandTrue, padx10, pady10) # 日/月流通量 tab3 tk.Frame(notebook) notebook.add(tab3, text流通量统计) self.flow_tree ttk.Treeview(tab3, columns(date,borrow,return), showheadings) self.flow_tree.heading(date, text日期) self.flow_tree.heading(borrow, text借书数) self.flow_tree.heading(return, text还书数) self.flow_tree.pack(filltk.BOTH, expandTrue, padx10, pady10) def load_statistics(self): # 热门图书 sql_popular SELECT b.title, b.author, COUNT(br.id) as cnt FROM borrow_records br JOIN books b ON br.book_id b.id GROUP BY b.id ORDER BY cnt DESC LIMIT 10 popular self.db.execute_query(sql_popular) for i, book in enumerate(popular, 1): self.popular_tree.insert(, tk.END, values(i, book[title], book[author], book[cnt])) # 读者排行 sql_reader SELECT r.name, COUNT(br.id) as cnt FROM borrow_records br JOIN readers r ON br.reader_id r.id GROUP BY r.id ORDER BY cnt DESC LIMIT 10 readers self.db.execute_query(sql_reader) for i, reader in enumerate(readers, 1): self.reader_tree.insert(, tk.END, values(i, reader[name], reader[cnt])) # 近7天流通量 sql_flow SELECT DATE(borrow_date) as date, SUM(CASE WHEN status IN (借出,逾期) THEN 1 ELSE 0 END) as borrow_cnt, SUM(CASE WHEN status 已还 THEN 1 ELSE 0 END) as return_cnt FROM borrow_records WHERE borrow_date CURDATE() - INTERVAL 7 DAY GROUP BY DATE(borrow_date) ORDER BY date DESC flows self.db.execute_query(sql_flow) for flow in flows: self.flow_tree.insert(, tk.END, values(flow[date], flow[borrow_cnt], flow[return_cnt])) UI美化建议使用ttkbootstrap库可以让界面更加现代化pip install ttkbootstrap然后在主窗口中替换ttk为ttkbootstrapimport ttkbootstrap as tb from ttkbootstrap.constants import * class MainWindow: def __init__(self, admin_info): self.root tb.Window(themenameflatly) # 主题可选superhero, darkly, flatly等 ... 打包发布使用 PyInstaller 打包成 exepip install pyinstaller pyinstaller --onefile --windowed --iconresources/icon.ico main.py注意需要将 MySQL 连接配置放在外部 config.ini 文件中避免硬编码。