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 文件中避免硬编码。

相关新闻

Codex实战:基于LLM的PR自动化安全审查如何提升开发效率与代码安全

Codex实战:基于LLM的PR自动化安全审查如何提升开发效率与代码安全

如果你是一名开发者,每天要 Review 十几个 Pull Request (PR),最头疼的是什么? 不是代码逻辑复杂,而是那些 隐蔽的安全漏洞 :一个未经验证的用户输入、一个硬编码的密钥、一个过时的依赖版本……这些“定时炸弹”可…

2026/9/23 9:06:28 阅读更多 →
ArcGIS 3D Analyst栅格计算器:从基础到高级应用

ArcGIS 3D Analyst栅格计算器:从基础到高级应用

1. 项目概述 作为一名GIS行业从业者,我经常需要处理各种空间分析任务。在众多工具中,ArcToolbox的3D Analyst扩展模块一直是我的得力助手,特别是其中的栅格计算功能。这个看似简单的工具实际上蕴含着强大的空间分析能力,能够解决从…

2026/9/23 15:39:22 阅读更多 →
告别手动整理!OncePower批量重命名工具实战指南

告别手动整理!OncePower批量重命名工具实战指南

你是不是也遇到过这样的场景:电脑里堆积了几百个从不同渠道下载的图片、文档或视频,文件名五花八门,有带乱码的、有带日期戳的、有全是数字序列的。想要整理,手动一个个改?光是想想就头皮发麻。用系统自带的批量重命名…

2026/9/24 0:39:51 阅读更多 →

最新新闻

视觉设计:主题、配色、排版、间距与现成库

视觉设计:主题、配色、排版、间距与现成库

1. 背景:你的 App 是 ChatGPT 家中的客人 在画按钮、选字体之前,先接受一个现实:用户并未打开“你的网站”,他正身处 ChatGPT。ChatGPT 已经自带了: 配色方案, 字体与字号, 间距与元素布局。 你的小部件会展示在这个环境中,通常是在 iframe 里。重要结论是:视觉上 Ap…

2026/9/24 18:55:30 阅读更多 →
XSS系统性复习:从三类漏洞原理到SpringBoot与文件上传实战修复

XSS系统性复习:从三类漏洞原理到SpringBoot与文件上传实战修复

XSS这个知识点,说简单也简单,无非就是往页面里塞一段脚本;但说复杂,它可以从一枚alert(1)一路延伸到一个完整的攻击链。我刚入行那会儿也以为这题目太基础,结果在真实项目里被一个文件上传点卡了半天,才意识…

2026/9/24 18:55:30 阅读更多 →
基于EVE-NG抓包分析STP BPDU:从原理到故障排查实践

基于EVE-NG抓包分析STP BPDU:从原理到故障排查实践

1. 实验目标:为什么STP适合放进EVE-NG做流量洞察1.1 这期实验解决什么问题STP(Spanning Tree Protocol,生成树协议)是二层网络里无论如何都绕不开的协议,也是很多工程师觉得“原理背得挺熟,一到排障就发懵”…

2026/9/24 18:55:30 阅读更多 →
EVE-NG实战:Wireshark抓包深度解析STP与BPDU

EVE-NG实战:Wireshark抓包深度解析STP与BPDU

1. 为什么要在EVE-NG里研究STP流量搞网络的人都知道一句话:交换网络最怕的不是没有带宽,而是环没消掉。我早年在客户现场遇到过全网交换机疯狂刷MAC漂移告警,整个办公网时断时续,查了大半天才发现两台汇聚交换机之间被多接了一根跳…

2026/9/24 18:55:30 阅读更多 →
I2C 通信协议详解:时序、EEPROM 读写流程与实战总结

I2C 通信协议详解:时序、EEPROM 读写流程与实战总结

低速版本(免费)100hz高速版本(ghz以上的速率)正常高速设备大概在400hz串行同步半双工通信总线方式有主机 从机数据线时钟线要接上拉电阻4.7k----10k欧姆之间i2c时序scl高电平进行采样 sda数据线不能改变电平1.起始位起始信号:scl时钟信号线为…

2026/9/24 18:55:30 阅读更多 →
Java学生宿舍管理系统:从数据库设计到事务处理实战

Java学生宿舍管理系统:从数据库设计到事务处理实战

简介:这是一套面向高校计算机专业学生与Java Web初学者的学生宿舍管理系统完整项目资料,围绕住宿信息管理、宿舍与床位分配、日常行为记录等核心业务展开,可用于课程设计、毕业设计或Java Web入门实战。压缩包共661个文件,约77.19…

2026/9/24 18:54:29 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →