找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

510

积分

0

好友

74

主题
发表于 昨天 05:59 | 查看: 0| 回复: 0

本文将详细介绍如何使用 Python 的 Tkinter 库和 SQLite 数据库构建一个功能完善的桌面图书管理系统。该系统实现了完整的增删改查 (CRUD)、数据导入导出及数据库备份等功能。我们将按照代码执行顺序,详细解析每个模块的设计与实现。

图书管理系统界面效果图

第一部分:环境初始化与模块导入

本项目的核心是Python编程语言,配合其内置的 Tkinter 库构建图形界面,并使用轻量级文件数据库 SQLite 进行数据持久化存储。

import tkinter as tk
from tkinter import ttk, messagebox, filedialog  # ttk提供增强组件
import sqlite3    # 轻量级数据库
import csv        # CSV文件处理
from datetime import datetime  # 时间处理
import os         # 系统操作

class LibraryManagementSystem:
    def __init__(self, root):
        self.root = root  # Tkinter主窗口
        self.root.title("图书管理系统")  # 窗口标题
        self.root.geometry("1200x700")  # 窗口大小
        self.root.configure(bg='#f0f0f0')  # 背景色
        # SQLite数据库文件路径
        self.db_path = "library.db"  # 数据库文件名
        # 连接/创建数据库
        self.connection = None  # 数据库连接对象
        self.connect_to_db()    # 建立数据库连接
        # 初始化数据库表
        self.init_db()  # 创建数据表
        # 初始化界面
        self.setup_ui()  # 构建GUI界面
        # 加载图书数据
        self.load_books()  # 显示已有图书

技术要点

  • 模块导入:仅导入项目必需的模块,避免不必要的资源开销。
  • 类设计:采用面向对象的方式组织代码,将数据和操作封装在类中,提高了代码的可维护性和复用性。
  • 配置集中:窗口属性、数据库路径等配置信息在类的初始化方法中统一设置,便于管理。

第二部分:数据库连接与表结构初始化

2.1 建立数据库连接

SQLite是一个无需独立服务进程的嵌入式数据库,非常适合小型桌面应用。

def connect_to_db(self):
    """连接到SQLite数据库"""
    try:
        self.connection = sqlite3.connect(self.db_path)  # 连接数据库
        print(f"成功连接到SQLite数据库: {self.db_path}")
    except Exception as e:
        messagebox.showerror("数据库连接错误", f"无法连接到数据库: {e}")

作用:建立与本地 SQLite 数据库文件的连接。连接成功后,后续的所有数据库操作都基于此连接对象。

2.2 初始化数据表结构

在连接成功后,需要确保存储图书信息的表结构存在。

def init_db(self):
    """初始化数据库表"""
    try:
        cursor = self.connection.cursor()  # 创建游标对象
        # 创建图书表(如果不存在)
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS books (
                id INTEGER PRIMARY KEY AUTOINCREMENT,  -- 自增主键
                title TEXT NOT NULL,                   -- 书名,必填
                author TEXT NOT NULL,                  -- 作者,必填
                isbn TEXT UNIQUE NOT NULL,             -- ISBN,唯一约束
                publisher TEXT,                        -- 出版社,可选
                publication_year INTEGER,              -- 出版年份,整数
                category TEXT,                         -- 类别,可选
                quantity INTEGER DEFAULT 0,            -- 总数量,默认0
                available_quantity INTEGER DEFAULT 0,  -- 可借数量,默认0
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP  -- 创建时间
            )
        ''')
        # 检查表中是否有数据
        cursor.execute("SELECT COUNT(*) FROM books")
        count = cursor.fetchone()[0]  # 获取记录数
        if count == 0:
            print("数据库中无数据,请添加数据")
        self.connection.commit()  # 提交事务
        cursor.close()  # 关闭游标
    except Exception as e:
        messagebox.showerror("数据库初始化错误", f"初始化数据库失败: {e}")

表结构设计解析

  • AUTOINCREMENT:ID字段自动递增,确保每本书有唯一标识。
  • UNIQUE:对ISBN字段施加唯一性约束,防止重复录入。
  • DEFAULT:为数量字段设置默认值,简化插入操作。
  • TIMESTAMP:自动记录每本书的入库时间。

第三部分:图形用户界面(GUI)构建

3.1 主窗口与标题区域

使用pack()布局管理器进行快速的基础布局。

def setup_ui(self):
    """设置用户界面"""
    # 标题区域
    title_label = tk.Label(
        self.root,
        text="图书管理系统",
        font=("微软雅黑", 24, "bold"),
        bg='#2c3e50',  # 深蓝色背景
        fg='white',    # 白色文字
        height=2       # 高度为2行文字
    )
    title_label.pack(fill=tk.X)  # 填充X方向(水平)

3.2 输入表单区域

采用字典动态管理所有输入框,便于后续统一读取数据。

    # 图书信息输入字段定义
    fields = [
        ("书名:", "title"),
        ("作者:", "author"),
        ("ISBN:", "isbn"),
        ("出版社:", "publisher"),
        ("出版年份:", "publication_year"),
        ("类别:", "category"),
        ("总数量:", "quantity"),
        ("可借数量:", "available_quantity")
    ]
    self.entries = {}  # 存储所有输入框的字典
    for i, (label_text, field_name) in enumerate(fields):
        frame = tk.Frame(left_frame, bg='#f0f0f0')  # 每行一个框架
        frame.pack(fill=tk.X, pady=5)  # X方向填充,上下边距5px
        label = tk.Label(frame, text=label_text, width=12,
                         anchor='e', bg='#f0f0f0')  # 标签右对齐
        label.pack(side=tk.LEFT, padx=(0, 5))
        entry = tk.Entry(frame, font=("微软雅黑", 10))  # 输入框
        entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
        self.entries[field_name] = entry  # 保存到字典便于访问

3.3 功能按钮区域

使用 grid 布局将多个按钮排列成整齐的矩阵。

    # 按钮配置:文字、对应函数、颜色
    buttons = [
        ("添加图书", self.add_book, '#27ae60'),      # 绿色
        ("更新图书", self.update_book, '#2980b9'),    # 蓝色
        ("删除图书", self.delete_book, '#e74c3c'),    # 红色
        ("清空字段", self.clear_fields, '#95a5a6'),   # 灰色
        ("搜索图书", self.search_books, '#f39c12'),   # 橙色
        ("导出CSV", self.export_to_csv, '#8e44ad'),   # 紫色
        ("导入CSV", self.import_from_csv, '#16a085'), # 青色
        ("数据库备份", self.backup_database, '#d35400')# 棕色
    ]
    # 使用grid布局排列按钮(2行4列)
    for i, (text, command, color) in enumerate(buttons):
        btn = tk.Button(
            button_frame,
            text=text,
            command=command,         # 绑定点击事件
            bg=color,                # 背景色
            fg='white',              # 文字颜色
            font=("微软雅黑", 10, "bold"),
            width=12,
            height=1,
            cursor='hand2'           # 鼠标悬停手型
        )
        btn.grid(row=i // 4, column=i % 4, padx=5, pady=5)  # 计算行列位置

布局技巧i // 4计算行号,i % 4计算列号,实现了8个按钮在2行4列中的自动排列。

3.4 数据展示表格

使用功能更强大的 ttk.Treeview 组件来展示和操作表格数据。

    # 定义表格列
    columns = ("ID", "书名", "作者", "ISBN", "出版社",
               "出版年份", "类别", "总数量", "可借数量", "添加时间")
    # 创建Treeview表格组件
    self.tree = ttk.Treeview(
        right_frame,
        columns=columns,
        show='headings',  # 只显示列标题
        height=20         # 显示20行
    )
    # 设置每列宽度
    column_widths = [40, 120, 80, 90, 100, 70, 70, 60, 70, 120]
    for col, width in zip(columns, column_widths):
        self.tree.heading(col, text=col)    # 设置列标题
        self.tree.column(col, width=width)  # 设置列宽度
    # 绑定选择事件
    self.tree.bind('<<TreeviewSelect>>', self.on_tree_select)

第四部分:核心业务逻辑实现

4.1 加载与刷新数据

从数据库查询数据并填充到前台表格是基础操作。

def load_books(self):
    """从数据库加载图书数据"""
    try:
        cursor = self.connection.cursor()
        cursor.execute("SELECT * FROM books ORDER BY id")  # 按ID排序
        rows = cursor.fetchall()  # 获取所有记录
        # 清空现有数据
        for item in self.tree.get_children():
            self.tree.delete(item)
        # 插入新数据
        for row in rows:
            self.tree.insert('', tk.END, values=row)  # 插入到末尾
        self.update_status(f"共加载 {len(rows)} 本图书")
        cursor.close()
    except Exception as e:
        messagebox.showerror("数据库错误", f"加载数据失败: {e}")

4.2 实现图书添加功能

添加功能是CRUD中的“C”(Create),包含了完整的数据验证和数据库插入流程。

def add_book(self):
    """添加新图书"""
    # 1. 收集并验证输入数据
    data = {}
    for field, entry in self.entries.items():
        value = entry.get().strip()  # 去除首尾空格
        if not value and field in ['title', 'author', 'isbn']:
            messagebox.showwarning("输入错误", f"{field} 字段不能为空!")
            return
        data[field] = value

    # 2. 验证数字字段有效性
    try:
        if data['publication_year']:
            int(data['publication_year'])  # 尝试转换为整数
        if data['quantity']:
            quantity = int(data['quantity'])
            if quantity < 0:  # 检查是否为负数
                messagebox.showwarning("输入错误", "数量不能为负数!")
                return
    except ValueError:
        messagebox.showwarning("输入错误", "出版年份和数量必须是数字!")
        return

    # 3. 检查ISBN唯一性
    try:
        cursor = self.connection.cursor()
        cursor.execute("SELECT id FROM books WHERE isbn = ?", (data['isbn'],))
        if cursor.fetchone():  # 如果查询到结果
            messagebox.showwarning("添加失败", "ISBN已存在!")
            cursor.close()
            return
        cursor.close()
    except Exception as e:
        messagebox.showerror("数据库错误", f"检查ISBN失败: {e}")
        return

    # 4. 执行数据插入
    try:
        cursor = self.connection.cursor()
        query = """
        INSERT INTO books
        (title, author, isbn, publisher, publication_year,
         category, quantity, available_quantity)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        """
        # 处理空值转换
        publication_year = int(data['publication_year']) if data['publication_year'] else None
        quantity = int(data['quantity']) if data['quantity'] else 0
        available_quantity = int(data['available_quantity']) if data['available_quantity'] else quantity

        # 参数化查询(防止SQL注入)
        values = (
            data['title'],
            data['author'],
            data['isbn'],
            data['publisher'] or None,
            publication_year,
            data['category'] or None,
            quantity,
            available_quantity
        )
        cursor.execute(query, values)
        self.connection.commit()  # 提交事务
        messagebox.showinfo("成功", "图书添加成功!")
        self.clear_fields()
        self.load_books()  # 刷新列表
        cursor.close()
    except Exception as e:
        messagebox.showerror("数据库错误", f"添加图书失败: {e}")

安全与实践要点

  • 参数化查询:使用 ? 作为占位符,将数据与SQL语句分离,有效防止SQL注入攻击。
  • 事务提交:执行插入、更新、删除后调用 commit(),确保更改持久化到数据库文件。
  • 异常处理:对可能出错的操作进行 try-except 包装,并向用户提供友好的错误提示。

4.3 实现多字段搜索功能

搜索功能(CRUD中的“R”-Read的拓展)允许用户通过书名、作者等多个条件快速定位图书。

def search_books(self):
    """搜索图书"""
    search_text = self.search_var.get().strip()
    if not search_text:
        self.load_books()  # 空搜索显示全部
        return
    try:
        cursor = self.connection.cursor()
        query = """
        SELECT * FROM books
        WHERE title LIKE ? OR author LIKE ? OR isbn LIKE ?
           OR publisher LIKE ? OR category LIKE ?
        ORDER BY id
        """
        # 构建模糊搜索模式
        search_pattern = f"%{search_text}%"
        # 执行查询(五个字段都搜索)
        cursor.execute(query, (search_pattern, search_pattern, search_pattern,
                               search_pattern, search_pattern))
        rows = cursor.fetchall()
        # 刷新表格显示
        for item in self.tree.get_children():
            self.tree.delete(item)
        for row in rows:
            self.tree.insert('', tk.END, values=row)
        self.update_status(f"找到 {len(rows)} 条匹配记录")
        cursor.close()
    except Exception as e:
        messagebox.showerror("数据库错误", f"搜索失败: {e}")

SQL LIKE 操作符% 是通配符,%text% 表示匹配包含 “text” 子串的任何文本。

第五部分:数据导入导出与备份

5.1 导出数据到CSV文件

数据库中的图书数据导出为通用的CSV格式,方便用Excel等工具查看和分析。

def export_to_csv(self):
    """导出数据到CSV文件"""
    # 1. 选择保存位置
    file_path = filedialog.asksaveasfilename(
        defaultextension=".csv",
        filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")]
    )
    if not file_path:  # 用户取消
        return
    try:
        # 2. 查询数据
        cursor = self.connection.cursor()
        cursor.execute("SELECT * FROM books")
        rows = cursor.fetchall()
        # 3. 动态获取列名
        column_names = [description[0] for description in cursor.description]
        cursor.close()
        # 4. 写入CSV文件
        with open(file_path, 'w', newline='', encoding='utf-8-sig') as file:
            writer = csv.writer(file)
            writer.writerow(column_names)  # 写入标题行
            writer.writerows(rows)         # 写入数据行
        messagebox.showinfo("导出成功", f"数据已导出到: {file_path}")
    except Exception as e:
        messagebox.showerror("导出错误", f"导出失败: {e}")

编码注意:使用 utf-8-sig 编码可确保生成的CSV文件在微软Excel中打开时中文字符正常显示。

5.2 从CSV文件导入数据

实现批量数据导入,可用于初始化数据或从其他系统迁移数据。

def import_from_csv(self):
    """从CSV文件导入数据"""
    # 1. 选择文件
    file_path = filedialog.askopenfilename(
        filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")]
    )
    if not file_path:
        return
    # 2. 确认提示
    if not messagebox.askyesno("确认导入", "导入操作将覆盖现有数据,是否继续?建议先执行备份。"):
        return
    try:
        # 3. 读取CSV文件
        with open(file_path, 'r', encoding='utf-8-sig') as file:
            reader = csv.reader(file)
            rows = list(reader)  # 转换为列表
        if len(rows) < 2:  # 至少要有标题行和数据行
            messagebox.showwarning("导入失败", "CSV文件数据不足!")
            return
        # 4. 清空现有表并插入新数据
        cursor = self.connection.cursor()
        cursor.execute("DELETE FROM books")
        for row in rows[1:]:  # 跳过标题行
            if len(row) >= 9:  # 确保列数足够
                # 处理空字符串,转换为数据库NULL
                for i in range(len(row)):
                    if row[i] == '':
                        row[i] = None
                # 插入数据(跳过ID列,使用自增)
                cursor.execute('''
                    INSERT INTO books
                    (title, author, isbn, publisher, publication_year,
                     category, quantity, available_quantity, created_at)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                ''', row[1:10])  # 从第二列开始是数据
        self.connection.commit()
        cursor.close()
        messagebox.showinfo("导入成功", f"已从 {file_path} 导入数据")
        self.load_books()
    except Exception as e:
        messagebox.showerror("导入错误", f"导入失败: {e}")

5.3 数据库文件备份

定期备份是数据安全的基本要求,这里实现了整个数据库文件的复制备份。

def backup_database(self):
    """备份数据库文件"""
    # 1. 生成带时间戳的备份文件名
    backup_path = filedialog.asksaveasfilename(
        defaultextension=".db",
        filetypes=[("SQLite数据库文件", "*.db"), ("所有文件", "*.*")],
        initialfile=f"library_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.db"
    )
    if not backup_path:
        return
    try:
        # 2. 关闭当前连接(避免文件被锁定)
        self.connection.close()
        # 3. 复制数据库文件
        import shutil
        shutil.copy2(self.db_path, backup_path)  # copy2保留文件元数据
        # 4. 重新连接数据库
        self.connection = sqlite3.connect(self.db_path)
        messagebox.showinfo("备份成功", f"数据库已备份到: {backup_path}")
    except Exception as e:
        messagebox.showerror("备份错误", f"备份失败: {e}")
        self.connect_to_db()  # 尝试重新连接

第六部分:辅助功能与资源管理

6.1 状态栏提示

用于向用户实时反馈系统状态和操作结果。

def update_status(self, message):
    """更新状态栏"""
    timestamp = datetime.now().strftime("%H:%M:%S")
    self.status_bar.config(text=f"{timestamp} - {message}")

6.2 程序退出时的清理

良好的应用程序应该在退出时主动释放占用的资源,如数据库连接。

def on_closing(self):
    """关闭窗口时的清理工作"""
    if self.connection:
        self.connection.close()  # 关闭数据库连接
        print("数据库连接已关闭")
    self.root.destroy()  # 销毁窗口

第七部分:应用程序入口

定义程序启动的主函数,创建主窗口实例并进入事件循环。

def main():
    root = tk.Tk()  # 创建Tkinter根窗口
    app = LibraryManagementSystem(root)  # 创建应用实例
    # 设置关闭窗口时的行为
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    root.mainloop()  # 启动事件循环

if __name__ == "__main__":
    main()  # 程序入口

项目总结与扩展思路

核心技术要点回顾

  1. SQLite数据库操作:掌握了连接管理、CRUD操作、事务控制以及通过Python进行交互的完整流程。
  2. Tkinter GUI开发:学习了窗口、框架、标签、输入框、按钮、表格等核心组件的使用与布局管理。
  3. 文件与数据交换:实现了CSV格式的导入导出,以及数据库文件的物理备份。
  4. 健壮性编程:通过全面的异常处理和输入验证,提升了程序的稳定性和用户体验。
  5. 代码结构设计:采用面向对象的类封装,使代码结构清晰,易于维护和扩展。

功能扩展方向建议

  1. 增加用户系统:引入登录界面,区分管理员和普通用户权限,实现借阅、归还功能。
  2. 增强数据可视化:集成MatplotlibPlotly库,绘制图书类别分布、借阅趋势等统计图表。
  3. 添加网络功能:通过API接口在线查询图书封面、豆瓣评分等信息,丰富图书数据。
  4. 迁移至Web平台:使用FlaskDjangoPython Web框架重构项目,将其转变为B/S架构的在线图书管理系统。

这个项目虽然代码规模适中,但完整涵盖了桌面应用从数据存储、业务逻辑到用户界面的核心开发环节。建议读者在理解本项目的基础上,尝试实现上述扩展功能之一,这将是巩固和提升Python全栈开发能力的绝佳实践。




上一篇:Python依赖倒置原则实践指南:用ABC与Protocol构建低耦合系统
下一篇:微信公众平台内容删除恢复指南:文章被删的排查策略与应对方法
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-12 10:42 , Processed in 0.088890 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表