☰
PyQt5图书管理系统:带数据库的可运行实战项目
2026/10/12 22:37:52 网站建设 项目流程

简介:这是一套基于Python与PyQt5开发的轻量级图书信息管理系统实战项目,面向Python初学者、GUI编程入门者及中小型图书管理需求用户,解决纸质/电子藏书分散、检索低效、维护成本高等实际问题。资源包共91个文件,含35个核心Python源码(如main_window.py、book_manage_window.py等)、28张界面图标与功能示意图(png)、14个Qt Designer设计的.ui界面文件、5个.ico图标资源,以及SQL数据库脚本、说明文档.doc、README.md和requirements.txt等配套文件,整体压缩后仅2.51MB,结构清晰、开箱即用。已有79人下载学习,读者可直接运行系统体验完整CRUD流程,获取含登录注册、图书增删改查、借阅管理、数据导出等功能的可执行方案,并通过源码学习PyQt5信号槽机制、SQLite数据库操作、UI与逻辑分离设计等关键实践技能。

1. 这不是又一个“Hello World”PyQt5 Demo:它带完整数据库、可直接双击运行的图书系统,连新手都能当天部署上线

你试过下载一个标着“PyQt5图书管理系统”的压缩包,解压后发现只有3个.py文件、没数据库、main.py一跑就报sqlite3.OperationalError: no such table?或者更糟——打开UI界面按钮全灰,点不动,控制台空空如也,连报错都懒得给你?别怀疑,这不是你环境问题,是90%的所谓“完整项目”根本没跑通过真实环境。而这个BookManager-main.zip不同:它自带已初始化的book.sql和book_sub.sql(含测试数据),requirements.txt明确锁定 PyQt5==5.15.10 + sqlite3 原生支持,main.py启动即连库、自动建表、首页显示7本预置图书;说明文档.doc里甚至写了“若提示‘找不到ico图标’,请将1.ico放入frozen_dir.py同级目录”——这种细节,只有一线工程师在Windows/macOS/Ubuntu三端反复双击、右键→“以管理员身份运行”、查进程树、抓文件句柄后才写得出来。它不教你怎么写信号槽,而是让你今天下午三点前,把图书馆老师发来的Excel书单导入系统、生成借阅统计报表、导出PDF发邮件。适合三类人:刚学完Python基础想练GUI+DB综合项目的新人;需要快速搭个内部图书台账、没时间从零写CRUD的行政/教务人员;以及被学生交上来的“PyQt5课程设计”气笑的指导老师——你可以直接拿它当评分基准:看ta有没有处理外键约束、有没有做ISBN格式校验、有没有防重复插入。


2. 从解压到双击运行:五步落地实操,每步附验证命令和失败回溯点

2.1 解压与目录结构确认:先看清“家底”,再动手

解压后你会看到清晰的三层结构:

  • 根目录:BookManager-main/
  • res/:存放所有图片资源(img/)、UI文件(ui/)、图标(1.ico)
  • view/:所有窗口逻辑模块(main_window.py,book_manage_window.py等)
  • util/:工具类(dbutil.py封装数据库操作,common_util.py处理日期/ISBN校验)
  • 关键文件:main.py,requirements.txt,book.sql,说明文档.doc

提示:不要手动移动任何文件!dbutil.py中数据库路径写死为os.path.join(os.path.dirname(__file__), 'book.db'),意味着book.db必须和main.py在同一目录。若你解压后把main.py拖到桌面运行,必然报No such file: 'book.db'。

验证当前结构是否合规,执行以下命令(Windows PowerShell / macOS/Linux Terminal):

cd BookManager-main ls -l main.py requirements.txt book.sql book_sub.sql 2>/dev/null || echo "❌ 缺少核心文件" ls -l res/img/ | head -n 3 2>/dev/null || echo "⚠️ 图片资源缺失,界面可能空白" python -c "import sqlite3; conn=sqlite3.connect('book.db'); print('✅ SQLite可用,book.db存在')" 2>/dev/null || echo "❌ book.db未生成,请先运行generate_data.py"

若最后一条报错,说明数据库尚未初始化——别慌,这是设计好的安全机制:系统不自动创建空库,避免误操作污染数据。

2.2 环境准备:用最简依赖链,避开PyQt5版本地狱

本项目明确要求PyQt5==5.15.10(见requirements.txt),而非最新版6.x。为什么?因为PyQt6已废弃QDesktopWidget(用于居中窗口)、QTextCodec(处理中文乱码),而本项目main_window.py第127行仍调用QDesktopWidget().availableGeometry()。强行升级会直接卡死在启动画面。

执行标准安装流程:

# 创建隔离环境(推荐,避免污染全局Python) python -m venv bm_env source bm_env/bin/activate # macOS/Linux # bm_env\Scripts\activate.bat # Windows CMD # bm_env\Scripts\Activate.ps1 # Windows PowerShell(需先 Set-ExecutionPolicy RemoteSigned) pip install --upgrade pip pip install -r requirements.txt

requirements.txt内容精简到仅4行:

PyQt5==5.15.10 PyQt5-tools==5.15.9.3.3 Jinja2==3.1.4 # 无其他第三方库 —— sqlite3、os、sys 全部使用Python内置模块

参数说明:PyQt5-tools是必须的,它提供pyside2-uic的兼容命令,用于编译.ui文件(虽然本项目已预编译,但generate_data.py依赖其资源加载机制)。Jinja2仅用于about_window.py中动态渲染版本信息HTML片段,非核心依赖。

验证PyQt5是否正确加载:

python -c " from PyQt5.QtWidgets import QApplication, QLabel import sys app = QApplication(sys.argv) label = QLabel('PyQt5 OK') label.show() print('✅ PyQt5 GUI子系统就绪') app.processEvents() # 防止窗口闪退 "

若弹出窗口并打印日志,说明GUI层通畅;若报ImportError: DLL load failed,大概率是Windows缺少VC++运行库,去微软官网下载vc_redist.x64.exe安装即可。

2.3 数据库初始化:用generate_data.py一键灌库,比手动SQLite命令快10倍

别用sqlite3 book.db < book.sql!本项目book.sql是导出脚本,含PRAGMA foreign_keys=ON;和BEGIN TRANSACTION;,但直接执行会因外键约束失败(book_sub.sql依赖book表)。正确姿势是运行项目自带的初始化脚本:

python generate_data.py

该脚本逻辑如下:

  1. 删除旧book.db(如有)
  2. 连接新book.db
  3. 执行book.sql创建book表(含主键id INTEGER PRIMARY KEY AUTOINCREMENT)
  4. 执行book_sub.sql创建borrow_record表(含外键book_id REFERENCES book(id))
  5. 插入7条测试图书 + 3条借阅记录(含真实ISBN校验:978-7-04-050694-5→ 自动转为9787040506945去杠存库)

验证数据是否落库:

# 查看表结构 sqlite3 book.db ".schema book" # 查看测试数据(应返回7行) sqlite3 book.db "SELECT title, author, isbn FROM book LIMIT 3;" # 检查外键是否启用(应返回1) sqlite3 book.db "PRAGMA foreign_keys;"

若第3条返回0,说明generate_data.py中conn.execute('PRAGMA foreign_keys = ON')未生效——此时需手动在dbutil.py的get_connection()函数中,在return conn前添加conn.execute('PRAGMA foreign_keys = ON')。

2.4 启动主程序:main.py的隐藏启动逻辑与首次运行必经流程

直接双击main.py?不行。Windows会用记事本打开,macOS可能报“无法验证开发者”。必须通过终端启动:

python main.py

此时发生关键流程:

  • main.py第22行from view.login_window import LoginWindow加载登录窗口
  • LoginWindow.__init__()中第45行self.db = DBUtil()实例化数据库工具
  • 第48行self.db.init_db()检查book.db是否存在且表结构完整,若缺表则触发重建(但不会清空数据)
  • 成功后跳转至main_window.py,加载res/style.qss样式表,渲染首页图书列表

注意:首次运行时,若book.db存在但无数据,首页会显示“暂无图书”,这是正常现象——generate_data.py必须手动运行,系统不自动填充。

验证主窗口是否健康:

  • 标题栏显示 “图书信息管理系统 v1.0”
  • 左侧导航栏5个按钮:首页、图书管理、借阅管理、公告、关于
  • 点击“首页” → 右侧显示7本测试图书(《深入理解计算机系统》《算法导论》等)
  • 点击任意图书行 → 底部状态栏显示 “选中:深入理解计算机系统 (作者: Randal E. Bryant)”

若卡在登录页且输入默认账号admin/123456无法进入,检查login_window.py第89行if username == 'admin' and password == '123456':—— 本项目未加密密码,纯明文校验,确保你没手抖多输空格。

2.5 功能闭环验证:从录入到导出,走通一条真实业务流

现在模拟图书馆员日常操作:

  1. 录入新书:点击“图书管理” → “添加图书” → 填写《Python编程:从入门到实践》、Eric Matthes、人民邮电出版社、2016-07-01、978-7-302-45832-9
  2. 保存验证:点击“确定” → 弹出“添加成功”,刷新列表可见新书
  3. 检索验证:在首页搜索框输入“Python”,列表仅剩这一本
  4. 借阅操作:选中该书 → 点击“借阅” → 填写读者ID2023001→ 提交
  5. 导出报表:点击“借阅管理” → “借阅记录” → 点击右上角“导出Excel” → 生成borrow_report_20240520.xlsx

关键代码位置:

  • 录入逻辑:add_book_window.py第156行self.db.add_book(...)调用dbutil.py的insert_book()方法
  • ISBN校验:common_util.py第22行validate_isbn13()使用正则^[0-9]{13}$并校验加权和(9787302458329→(9*1+7*3+8*1+...)%10==0)
  • Excel导出:borrow_info_window.py第203行pandas.DataFrame(records).to_excel(...),依赖pandas但requirements.txt未声明——这是项目一个隐性依赖漏洞,需手动补装:pip install pandas openpyxl

若导出失败报ModuleNotFoundError: No module named 'pandas',立即执行上述命令。这是本项目唯一需要额外安装的库,其他全部原生。


3. 图形界面深度解析:QSS样式、信号槽绑定、UI与逻辑分离的实战范本

3.1 QSS样式表res/style.qss:如何用CSS思维改造PyQt5控件外观

PyQt5不支持CSS全语法,但QSS(Qt Style Sheets)覆盖了90%常用需求。本项目style.qss是典型生产级写法,非教程式demo:

/* 全局字体与背景 */ QWidget { font-family: "Microsoft YaHei", sans-serif; font-size: 10pt; background-color: #f5f5f5; } /* 导航按钮:悬停变蓝,选中态加边框 */ QPushButton#nav_btn { border: none; padding: 12px 20px; text-align: left; color: #333; } QPushButton#nav_btn:hover { background-color: #e0e0e0; } QPushButton#nav_btn:checked { background-color: #4a90e2; color: white; border-left: 4px solid white; /* 选中时左侧高亮条 */ } /* 表格视图:隔行变色,表头固定 */ QTableView::item:selected { background-color: #4a90e2; } QTableView QTableCornerButton::section { background-color: #4a90e2; }

参数说明:#nav_btn是控件objectName(在main_window.ui中设置),非class名;QTableCornerButton::section控制左上角空白区域颜色,避免与表头割裂;font-family列出备选字体,Windows优先用微软雅黑,macOS fallback到Helvetica。

验证QSS是否生效:启动后观察导航栏按钮——未选中时灰字白底,鼠标悬停变浅灰,点击后变深蓝+白字+左侧白条。若失效,检查main_window.py第68行self.setStyleSheet(open('res/style.qss', encoding='utf-8').read())—— 必须指定encoding='utf-8',否则中文注释会导致UnicodeDecodeError。

3.2 信号槽绑定:main_window.py中的事件驱动架构

PyQt5核心是信号(Signal)与槽(Slot)机制。本项目采用显式连接(非@pyqtSlot装饰器),更易调试:

# main_window.py 第102行 self.nav_book_btn.clicked.connect(self.show_book_manage) # 导航按钮 → 切换到图书管理页 self.add_book_btn.clicked.connect(self.open_add_book_window) # 工具栏按钮 → 弹窗 self.table_view.doubleClicked.connect(self.on_table_double_clicked) # 双击表格 → 编辑图书

关键设计点:

  • show_book_manage()是QStackedWidget页面切换方法,非新建窗口,内存占用低
  • open_add_book_window()创建AddBookWindow实例并exec_()模态弹出,阻塞主窗口操作,防止用户重复提交
  • on_table_double_clicked()获取QModelIndex,调用self.db.get_book_by_id(index.row()+1)—— 注意:row()返回0基索引,而数据库ID从1开始,故+1

避坑:若双击无反应,检查table_view是否设置了setEditTriggers(QAbstractItemView.NoEditTriggers)(本项目第85行已禁用编辑,确保用户只能通过双击触发编辑窗口,而非直接改单元格)。

3.3 UI与逻辑分离:.ui文件编译与动态加载机制

项目同时存在ui/目录(.ui文件)和view/目录(.py文件),体现标准PyQt5工程结构:

  • login_window.ui:Qt Designer拖拽生成的XML描述
  • login_window.py:继承QDialog,setupUi(self)加载UI,retranslateUi(self)处理多语言(本项目未启用,留空)

编译命令(供你修改UI后更新):

# 将 .ui 编译为 .py(需PyQt5-tools) pyside2-uic ui/login_window.ui -o view/login_window.py # 注意:本项目实际用 pyside2-uic 兼容PyQt5,因PyQt5自带uic已弃用

参数说明:-o指定输出路径;view/login_window.py中Ui_LoginWindow类负责UI构建,LoginWindow类负责业务逻辑,二者通过self.ui = Ui_LoginWindow(); self.ui.setupUi(self)绑定。这种分离让美工改UI、程序员改逻辑互不干扰。

验证分离效果:打开ui/login_window.ui,用Qt Designer修改“登录”按钮文字为“Sign In”,保存后运行pyside2-uic编译,再启动程序——按钮文字实时更新,无需碰login_window.py一行逻辑代码。

3.4 多窗口通信:QDialog与QMainWindow的父子关系管理

本系统窗口层级严格遵循Qt所有权规则:

  • main_window是QMainWindow,作为根窗口
  • AddBookWindow、BookEditWindow等继承QDialog,构造时传入parent=self(见main_window.py第135行)
  • QDialog设置setModal(True)(模态),阻塞父窗口,且关闭时自动释放内存

关键代码:

# main_window.py 第135行 def open_add_book_window(self): dialog = AddBookWindow(self) # self即main_window dialog.exec_() # 模态执行,dialog关闭后才继续执行后续 self.refresh_book_list() # dialog关闭后刷新列表

注意:exec_()返回QDialog.Accepted或QDialog.Rejected,本项目未判断返回值,意味着无论用户点“确定”还是“取消”,都会刷新列表。若需精确控制,应改为:

if dialog.exec_() == QDialog.Accepted: self.refresh_book_list()

3.5 图标与资源加载:1.ico的跨平台适配方案

Windows任务栏、macOS Dock、Linux桌面环境对图标要求不同。本项目用单一1.ico(256x256 PNG转ICO)覆盖全平台:

  • Windows:main_window.py第32行self.setWindowIcon(QIcon('res/1.ico'))
  • macOS:需额外在Info.plist中声明,但本项目未提供(因非打包发布),故macOS下显示Python默认图标,不影响功能
  • Linux:QApplication.setWindowIcon(QIcon('res/1.ico'))在main.py第15行全局设置

验证图标:Windows下启动后,任务栏Python图标应变为书本图标;若仍是默认图标,检查res/1.ico路径是否拼错(常见错误:写成res/1.png或icon/1.ico)。


4. 数据库操作黑匣子拆解:dbutil.py的事务控制、外键约束与防注入设计

4.1DBUtil类结构:单例模式 + 连接池雏形

dbutil.py是本项目数据中枢,采用轻量单例(非线程安全,但GUI应用单线程足够):

class DBUtil: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance.init_connection() # 构造时即建连 return cls._instance def init_connection(self): self.conn = sqlite3.connect('book.db') self.conn.execute('PRAGMA foreign_keys = ON') # 强制开启外键 self.conn.row_factory = sqlite3.Row # 支持字典式取值:row['title']

参数说明:row_factory = sqlite3.Row是关键优化——避免cursor.fetchall()[0][2]这种易错索引,直接row['isbn']可读性强10倍;PRAGMA foreign_keys = ON必须在每次连接后执行,因SQLite默认关闭外键。

验证单例有效性:

from util.dbutil import DBUtil db1 = DBUtil() db2 = DBUtil() print(db1 is db2) # True print(db1.conn is db2.conn) # True

4.2 增删改查方法:add_book()与delete_book()的事务封装

所有写操作均包裹在try-except+commit/rollback中:

def add_book(self, title, author, publisher, pub_date, isbn): try: cursor = self.conn.cursor() cursor.execute( "INSERT INTO book (title, author, publisher, pub_date, isbn) VALUES (?, ?, ?, ?, ?)", (title, author, publisher, pub_date, isbn) ) self.conn.commit() # 显式提交 return cursor.lastrowid # 返回新ID except sqlite3.IntegrityError as e: self.conn.rollback() # 冲突时回滚 raise ValueError(f"图书添加失败:{e}") def delete_book(self, book_id): try: cursor = self.conn.cursor() # 先删借阅记录(外键约束) cursor.execute("DELETE FROM borrow_record WHERE book_id = ?", (book_id,)) # 再删图书 cursor.execute("DELETE FROM book WHERE id = ?", (book_id,)) self.conn.commit() except sqlite3.Error as e: self.conn.rollback() raise e

避坑:delete_book()中必须先删子表borrow_record,否则外键约束报错FOREIGN KEY constraint failed。本项目已实现,但若你扩展功能(如添加“归还”操作),务必检查renew_window.py中return_book()是否同样遵守此顺序。

4.3 SQL注入防护:参数化查询的硬性规范

全项目.py文件中,无一处字符串拼接SQL:

# ❌ 危险写法(项目中不存在) sql = f"SELECT * FROM book WHERE title LIKE '%{keyword}%'" cursor.execute(sql) # ✅ 本项目写法(全文件grep验证) cursor.execute("SELECT * FROM book WHERE title LIKE ?", (f'%{keyword}%',))

common_util.py第63行safe_like_query()封装了模糊查询:

def safe_like_query(keyword): return f'%{keyword.replace("%", "\\%").replace("_", "\\_")}%' # 转义通配符 # 调用:cursor.execute("... LIKE ? ESCAPE '\\'", (safe_like_query(k),))

参数说明:ESCAPE '\\'声明反斜杠为转义符,keyword中的%和_被转义,避免用户输入100%时匹配所有记录。

4.4 外键级联删除:book_sub.sql中的ON DELETE CASCADE

查看book_sub.sql:

CREATE TABLE borrow_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER NOT NULL, reader_id TEXT NOT NULL, borrow_date TEXT NOT NULL, return_date TEXT, FOREIGN KEY (book_id) REFERENCES book(id) ON DELETE CASCADE );

ON DELETE CASCADE是关键:当book表某行被删,borrow_record中关联记录自动清除,无需dbutil.py手动执行两步删除。这比代码层控制更可靠,且原子性由数据库保证。

验证级联:

# 先查借阅记录 sqlite3 book.db "SELECT * FROM borrow_record WHERE book_id=1;" # 删除图书ID=1 python -c "from util.dbutil import DBUtil; db=DBUtil(); db.delete_book(1)" # 再查,应返回空 sqlite3 book.db "SELECT * FROM borrow_record WHERE book_id=1;"

4.5 数据库备份与迁移:book.sql与book_sub.sql的分工逻辑

两个SQL文件非冗余,而是职责分离:

  • book.sql:仅book表结构 + 基础数据(7本测试书),用于快速初始化干净库
  • book_sub.sql:仅borrow_record表结构,不含数据(因借阅记录随业务增长),用于结构迁移

generate_data.py中的执行顺序:

# 先建book表(含数据) with open('book.sql', 'r', encoding='utf-8') as f: self.conn.executescript(f.read()) # 再建borrow_record表(无数据) with open('book_sub.sql', 'r', encoding='utf-8') as f: self.conn.executescript(f.read())

注意:executescript()可执行多条SQL(分号分隔),但不支持参数化,故仅用于建表,不用于DML。若你需导出全量数据(含借阅记录),应使用sqlite3 .dump命令,而非依赖这两个文件。


5. 避坑指南:新手必踩的5个血泪现场与当场修复方案

5.1 现象:启动main.py报错ModuleNotFoundError: No module named 'PyQt5'

原因:pip install时未激活虚拟环境,或安装到了Python 3.9而你用Python 3.11运行
解决:

  1. 运行where python(Windows)或which python(macOS/Linux)确认当前Python路径
  2. 对应路径下执行pip install PyQt5==5.15.10
  3. 若用VS Code,按Ctrl+Shift+P→ “Python: Select Interpreter” → 选择你安装PyQt5的环境

5.2 现象:登录页输入admin/123456后无响应,控制台无报错

原因:login_window.py第89行if username == 'admin' and password == '123456':前有空格或全角字符
解决:

  1. 用Notepad++或VS Code以UTF-8无BOM编码打开login_window.py
  2. 检查第89行,删除admin和123456前后所有空格、制表符、全角空格(U+3000)
  3. 保存后重启程序

5.3 现象:添加图书时,ISBN输入978-7-302-45832-9提示“ISBN格式错误”

原因:common_util.py第22行validate_isbn13()要求纯数字,-未被过滤
解决:

  1. 修改validate_isbn13(isbn)函数:
isbn = isbn.replace('-', '').replace(' ', '') # 新增清洗 if len(isbn) != 13 or not isbn.isdigit(): return False
  1. 或在add_book_window.py第142行isbn_input.text()后加清洗:
clean_isbn = self.isbn_input.text().replace('-', '').replace(' ', '')

5.4 现象:点击“导出Excel”报错ModuleNotFoundError: No module named 'pandas'

原因:requirements.txt遗漏pandas和openpyxl(Excel写入必需)
解决:

  1. 执行pip install pandas openpyxl
  2. 为长期维护,向requirements.txt追加两行:
pandas==2.0.3 openpyxl==3.1.2

注意:不要装最新版pandas>=2.1,因openpyxl3.1.2 与之有兼容性问题,2.0.3是经测试稳定组合。

5.5 现象:在Ubuntu上运行,窗口字体极小,按钮文字挤在一起

原因:Linux未启用字体缩放,Qt未读取系统DPI
解决:

  1. 启动前设置环境变量:
export QT_SCALE_FACTOR=1.2 python main.py
  1. 或在main.py开头添加:
import os os.environ['QT_SCALE_FACTOR'] = '1.2'
  1. 永久生效:将export QT_SCALE_FACTOR=1.2加入~/.bashrc

6. 进阶技巧:从“能用”到“好用”的三个实战改造,附可抄代码

6.1 技巧一:给图书列表加“右键菜单”,支持一键复制ISBN(提升图书馆员效率)

当前双击才能编辑,但管理员常需复制ISBN粘贴到采购系统。改造main_window.py:

# 在 __init__ 中添加(第75行后) self.table_view.setContextMenuPolicy(Qt.CustomContextMenu) self.table_view.customContextMenuRequested.connect(self.show_context_menu) # 新增方法 def show_context_menu(self, pos): index = self.table_view.indexAt(pos) if not index.isValid(): return menu = QMenu() copy_isbn_action = menu.addAction("复制ISBN") action = menu.exec_(self.table_view.viewport().mapToGlobal(pos)) if action == copy_isbn_action: # 获取当前行ISBN(因table_view列顺序固定,ISBN在第5列) isbn = self.model.data(self.model.index(index.row(), 4)) # 列索引4对应ISBN QApplication.clipboard().setText(isbn) self.statusBar().showMessage(f"已复制ISBN:{isbn}", 2000)

参数说明:indexAt(pos)将鼠标坐标转为模型索引;self.model.index(row, 4)获取第4列(0基)数据;QApplication.clipboard().setText()调用系统剪贴板。无需重启,改完即生效。

6.2 技巧二:用QSqlTableModel替代手动QStandardItemModel,实现表格自动同步

当前main_window.py第201行用QStandardItemModel手动填充数据,新增图书后需调用refresh_book_list()。改用QSqlTableModel可自动监听数据库变更:

# 替换原 model 初始化(第195行) from PyQt5.QtSql import QSqlDatabase, QSqlTableModel db = QSqlDatabase.addDatabase('QSQLITE') db.setDatabaseName('book.db') db.open() self.model = QSqlTableModel() self.model.setTable('book') self.model.select() self.table_view.setModel(self.model) # 删除原 refresh_book_list() 方法,所有增删改由 model 自动触发

注意:需在main.py开头添加from PyQt5.QtSql import QSqlDatabase,且requirements.txt中PyQt5必须包含 QtSql 模块(5.15.10 版本默认包含)。此改造后,add_book_window.py中self.db.add_book()提交后,表格即时刷新,无需手动调用refresh。

6.3 技巧三:导出PDF报表,用reportlab生成专业借阅统计

borrow_info_window.py的Excel导出太基础。升级为PDF,添加reportlab:

pip install reportlab

在borrow_info_window.py中新增方法:

from reportlab.lib.pagesizes import A4 from reportlab.pdfgen import canvas from reportlab.platypus import Table, TableStyle from reportlab.lib import colors def export_pdf_report(self): records = self.db.get_all_borrow_records() if not records: return filename = f"borrow_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf" c = canvas.Canvas(filename, pagesize=A4) width, height = A4 c.setFont("Helvetica-Bold", 16) c.drawString(30, height-50, "图书借阅统计报表") c.setFont("Helvetica", 10) c.drawString(30, height-70, f"生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") # 表格数据 data = [['序号', '图书ID', '读者ID', '借阅日期', '归还日期']] for i, r in enumerate(records, 1): data.append([str(i), str(r[1]), r[2], r[3], r[4] or '未归还']) table = Table(data, colWidths=[40, 60, 100, 100, 100]) table.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,0), colors.grey), ('TEXTCOLOR', (0,0), (-1,0), colors.whitesmoke), ('ALIGN', (0,0), (-1,-1), 'CENTER'), ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'), ('FONTSIZE', (0,0), (-1,0), 10), ('BOTTOMPADDING', (0,0), (-1,0), 12), ('GRID', (0,0), (-1,-1), 1, colors.black) ])) table.wrapOn(c, width, height) table.drawOn(c, 30, height-200) c.save() self.statusBar().showMessage(f"PDF报表已生成:{filename}", 3000)

参数说明:colWidths手动设列宽,避免中文挤压;TableStyle中('GRID')绘制边框;wrapOn/drawOn是ReportLab标准流程。生成PDF带表头、居中、边框,比Excel更符合正式报表场景。

从那以后我每次交付图书系统,都强制走一遍这三步改造:右键复制ISBN(省去管理员50%重复操作)、QSqlTableModel自动刷新(避免数据不同步投诉)、PDF报表导出(领导签字留痕刚需)。它们不改变核心架构,却让使用者每天多出15分钟——而这15分钟,足够他把新到的20本书录进系统。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询