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/10/21 6:15:30 阅读更多 →
ArcGIS 3D Analyst栅格计算器:从基础到高级应用

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

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

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

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

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

2026/10/21 0:34:09 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/22 21:33:42 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/22 19:06:46 阅读更多 →