玩命加载中🤣🤣🤣

PyQt-图书馆管理系统


PyQt6 library

PyQt部分

前置步骤:

  • ui 界面设计
  • 数据库设计
  • python.exe -m PyQt6.uic.pyuic xxx -o xxx.py

ui-py 文件初始改造

  • 将 ui 生成的 py 文件复制一份至相应模块下
  • 将类继承自 QWidget 或者 QMainWindow 等组件
  • 新增构造器 __init__(self)
  • 关注是否有引用的图片是否需要修改相对路径
  • 启动测试

初始化代码:

def __init__(self):
    super(Ui_Form, self).__init__()
    # 初始化当前页面绝对路径
    self.current_dir = os.path.dirname(os.path.abspath(__file__))
    self.setupUi(self)

启动测试验证:

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    ui_form = Ui_title() # 初始化
    ui_form.show()
    sys.exit(app.exec())

一些组件的零星设置

# 主界面背景设置
self.centralwidget.setStyleSheet("border-image:url('./images/158.jpg')")

# 设置状态栏内容
label = QLabel()
self.statusbar.addWidget(label)

# 状态栏右侧同理
label = QLabel()
self.statusbar.addPermanentWidget(label)

# 输入框设置只读以及设置颜色
self.input_number.setReadOnly(True)
self.input_number.setStyleSheet("background-color: gray")

# 链接跳转
QtGui.QDesktopServices.openUrl(QtCore.QUrl("https://saddyfire.cn"))

窗体间的切换

示例1:登录后的主窗体切换
### 登录逻辑...
# 当前界面隐藏
self.hide()
self.m = main.Ui_mainWindow() # 实例化主窗体
self.m.show() # 显示主窗体
示例2:主窗体点击菜单显示功能窗体
  1. 回调事件函数
# 打开图书类别窗口
def open_book_type_window(self, action):
    print(action.text())
    if action.text() == "图书类别添加":
        self.book_type_add = book_type_add.Ui_Form()
        self.book_type_add.show()
    elif action.text() == "图书类别信息管理":
        self.book_type_manage = book_type_manage.Ui_book_type_manage()
        self.book_type_manage.show()
  1. 触发回调
self.menu_book_type = QtWidgets.QMenu(parent=self.menubar)
self.menu_book_type.setObjectName("menu_book_type")

# 图书类别菜单点击回调
self.menu_book_type.triggered[QAction].connect(self.open_book_type_window)
相对路径与绝对路径的引用

这里采用的方式是在初始化时同时保存绝对路径,此后都采用绝对路径的方式引用外部文件

# 初始化当前页面
self.current_dir = os.path.dirname(os.path.abspath(__file__))

####
# 外部文件的引用
icon.addPixmap(QtGui.QPixmap(f"{self.current_dir}/../images/搜索.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)

计时器(QTimer + QLabel)

事件回调:

# 添加一个标签
label_timer = QLabel()
# 初始化一个计时器
timer = QTimer(self)
# 间隔1s执行一次
timer.start(1000)
# 触发事件
timer.timeout.connect(lambda: self.show_timer(label_timer))
# 再将标签绑定到组件(本次示例是将标签放在状态栏中)
self.statusbar.addPermanentWidget(label_timer)

回调函数:

# 显示时间
def show_timer(self, label: QLabel):
    time = QDateTime.currentDateTime()
    label.setText(time.toString("yyyy-MM-dd hh:mm:ss"))

事件绑定

self.组件对象名.clicked.connect(lambda: 触发事件())

## 示例
# 绑定添加事件
self.btn_add.clicked.connect(self.add)

消息框

QtWidgets.QMessageBox.warning(None, "提示", "需要警告的信息:xxx")
QtWidgets.QMessageBox.information(None, "提示", "需要提示的信息:xxx")

消息框的多个按钮示例:删除提醒

# 问题消息框
confirm = QMessageBox.question(
    None, "提示", "确定删除吗?",
    QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
    QMessageBox.StandardButton.No # 默认选择项
)
if confirm == QMessageBox.StandardButton.No:
    return

表格👍

数据的显示

表格初始化

def show_table(self):
    # 模拟初始化的数据
    xxx_list = 	((1, '历史类', '历史类图书'), (3, '文学类', ''), (5, '艺术类', ''))
    # 初始化行数
    row_count = 0
    # 初始化列数
    columns = ["id", "图书类别名称", "图书类别描述"]
    if xxx_list:
        row_count = len(xxx_list)
    self.table_book_type.setRowCount(row_count) # 设置行数
    self.table_book_type.setColumnCount(len(columns)) # 设置列数
    self.table_book_type.setColumnHidden(0, True) # 隐藏id列
    # self.table_book_type.verticalHeader().setVisible(False) # 隐藏行头序号
    self.table_book_type.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) # 禁止编辑单元格
    self.table_book_type.setHorizontalHeaderLabels(columns) # 设置表头
    self.table_book_type.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # 列头自适应
    self.table_book_type.setSelectionBehavior(
        QAbstractItemView.SelectionBehavior.SelectRows # 设置选择行为,以行为单位
    )
    # 行
    for i in range(row_count):
        # 列
        for j in range(len(columns)):
            data = QTableWidgetItem(str(book_type_list[i][j])) # 这里注意,列索引从1开始
            self.table_book_type.setItem(i, j, data)

窗口初始化时同时展示表格

def __init__(self):
    super(Ui_book_type_manage, self).__init__()
    self.setupUi(self)
    # 初始化表格
    self.show_table()
表格的点击事件(支持空白监听)

回调函数

def click_table(self):
    # 选中的行中作为 list(这里隐藏列是没有的)
    selected_as_list = self.tableWidget.selectedItems()
    # 没有选中行则是 [], 则重置表单
    if not selected_as_list:
        self.reset_form()
        return
    #### 因为id是隐藏列,所以需要通过坐标定位
    # 当前行号
    current_row = self.tableWidget.currentRow()
    # 定位坐标
    book_id = self.tableWidget.item(current_row, 0).text()
    self.input_book_id.setText(book_id)
    
    #### 其他列则通过当前行即可获取
    self.input_book_name.setText(selected_as_list[0].text())
    self.input_auth_name.setText(selected_as_list[1].text())

绑定回调函数

# 绑定表格点击事件
self.tableWidget.itemSelectionChanged.connect(self.click_table)
表格遇到需要映射的字段

比如数据库中查回来的【图书类型】是【图书类型id】,实际要展示的是【图书类型名称】,基于此采用一个字典映射的方案

__init__ 方法中定义字典

# 初始化下拉框字典
self.book_type_dict: Optional[dict] = None

获取表格数据后做一次数据处理

# 假设拿到数据
book_list = ((1, '人类简史', 'xx', '男', 100.0, 1, None), (2, '乖摸摸头', '', '', None, 3, ''))
# 针对具体列做出处理,示例:第5列
processed_list = [
    # 这里要注意,因为原数据是元组,因此需要将每一段都转为list才行,最终再用一层list()包裹,否则会转换失败
    list(list(book[:5]) + [self.book_type_dict.get(book[5], "未知类型")] + list(book[6:]))
    for book in book_list
]
# 后续通过 processed_list 来构建表格

下拉框的初始化及重置

初始化:让 text 保持显示的中文,data 为实际的映射值

def init_combox(self):
    # 先获取列表
    book_type_list = book_type_dao.list()
    # 先清空
    self.combox_bok_type.clear()
    # 设置默认项, 并且数据为 -1
    self.combox_bok_type.addItem("请选择图书类别", -1)
    for book_type in book_type_list:
        self.combox_bok_type.addItem(book_type[1], book_type[0])

重置

# 重置下拉框
self.combox_bok_type.setCurrentIndex(0)

设置当前下拉框,使用 text 中文去设置

# 设置值可以使用中文,这样的话可以
book_type = self.tableWidget.item(row_index, 5).text()
self.combo_book_type.setCurrentText(book_type)

结果获取:通过 data 去获取实际映射值

# 获取实际映射值
book_type_id = self.combox_bok_type.currentData()
if book_type_id == -1:
    QMessageBox.warning(None, "提示", "图书名称或图书类别缺失")
    return
else:
    pass

单选框radio的不选择技巧

因为性别这类不是必填项,但是 QRadioButton 默认会保持互斥,一旦选择就无法清楚,因此本方案采取添加一个隐藏的单选框作为"无"选项

setup 时新增一个隐藏的"无选项"

# 隐藏的无选项(parent照抄其他的)
self.radio_none = QtWidgets.QRadioButton(parent=self.gridLayoutWidget)
self.radio_none.hide()
# 将选项放入父组件中(QGridLayout 也照抄其他的)
self.gridLayout.addWidget(self.radio_none)
self.radio_none.setChecked(True)

重置

# 复选框重置,选择隐藏按钮
self.radio_none.setChecked(True)

结果获取

# 性别
if self.radio_man.isChecked():
    sex = self.radio_man.text()
elif self.radio_woman.isChecked():
    sex = self.radio_woman.text()
else:
    sex = self.radio_none.text() # 避免硬编码

dao层mysql

db_util.py

from pymysql import Connection

def getConn():
    conn = Connection(
        host='localhost',
        port=3306,
        user='root',
        password='2323',
        database='dee_library',
        charset='utf8',
        autocommit=True
    )
    return conn

def closeConn(conn: Connection):
    if conn:
        conn.close()

craete 模板

def add(book_type: BookType) -> int:
    conn = None
    try:
        conn = db_util.getConn()
        cursor = conn.cursor()
        cursor.execute(f"insert into t_book_type values (null, %s, %s)", [book_type.book_type_name, book_type.book_type_desc])
        return cursor.rowcount
    except Exception as e:
        print(e)
        if conn:
            conn.rollback()
            print(e)
        return 0
    finally:
        db_util.closeConn(conn)

select 模板

查询列表

def list(s_book_type_name: str):
    conn = None
    try:
        conn = db_util.getConn()
        cursor = conn.cursor()
        sql = "select id, book_type_name, book_type_desc from t_book_type where 1=1"
        param = []
        if s_book_type_name:
            sql += " and book_type_name like %s"
            param.append(f"%{s_book_type_name}%")
        cursor.execute(sql, param)
        return cursor.fetchall()
    except Exception as e:
        print(e)
        if conn:
            return None
    finally:
        db_util.closeConn(conn)

count

def count_by_book_type(book_type_id: int):
    conn = None
    try:
        conn = db_util.getConn()
        cursor = conn.cursor()
        sql = "select count(1) from t_book where book_type_id=%s"
        cursor.execute(sql, [book_type_id])
        return cursor.fetchone()[0]
    except Exception as e:
        print(e)
        if conn:
            conn.rollback()
        return None
    finally:
        db_util.closeConn(conn)

update 模板

def modify(book_type: BookType):
    conn = None
    try:
        conn = db_util.getConn()
        cursor = conn.cursor()
        param = []
        sql = "update t_book_type set book_type_name=%s, book_type_desc=%s where id=%s"
        param = [book_type.book_type_name, book_type.book_type_desc, book_type.id]
        cursor.execute(sql, param)
        return cursor.rowcount
    except Exception as e:
        print(e)
        if conn:
            conn.rollback()
            return 0
    finally:
        db_util.closeConn(conn)

delete 模板

def remove(id: int):
    conn = None
    try:
        conn = db_util.getConn()
        cursor = conn.cursor()
        cursor.execute("delete from t_book_type where id=%s", [id])
        return cursor.rowcount
    except Exception as e:
        print(e)
        if conn:
            conn.rollback()
            return 0
    finally:
        db_util.closeConn(conn)

dao层sqlite

db_util.py

import sqlite3
from pathlib import Path

from pymysql import Connection

# 数据库文件路径(自动创建在程序目录下)
DB_PATH = Path(__file__).parent.parent / "db" / "dee_library.db"

def get_conn():
    """获取SQLite数据库连接"""
    conn = sqlite3.connect(DB_PATH)
    # 启用外键约束(如果需要)
    conn.execute("PRAGMA foreign_keys = ON")
    return conn

def close_conn(conn: Connection):
    if conn:
        conn.close()

create 模板

from entity.book_type_model import BookType
from util.db_util import get_conn, close_conn
import sqlite3

def add(book_type: BookType) -> int:
    """
    添加图书类别
    :param book_type: 图书类别实体
    :return: 影响的行数 (0表示失败)
    """
    conn = None
    try:
        conn = get_conn()
        cursor = conn.cursor()
        cursor.execute(
            "INSERT INTO t_book_type (book_type_name, book_type_desc) VALUES (?, ?)",
            (book_type.book_type_name, book_type.book_type_desc)
        )
        conn.commit()
        return cursor.rowcount
    except sqlite3.Error as e:
        print(f"添加图书类别出错: {e}")
        if conn:
            conn.rollback()
        return 0
    finally:
        close_conn(conn)

select 模板

def list(s_book_type_name: str = None) -> list:
    """
    查询图书类别列表
    :param s_book_type_name: 图书类别名称(模糊查询)
    :return: 图书类别列表
    """
    conn = None
    try:
        conn = get_conn()
        cursor = conn.cursor()
        sql = "SELECT id, book_type_name, book_type_desc FROM t_book_type WHERE 1=1"
        params = []

        if s_book_type_name:
            sql += " AND book_type_name LIKE ?"
            params.append(f"%{s_book_type_name}%")

        cursor.execute(sql, params)
        return cursor.fetchall()
    except sqlite3.Error as e:
        print(f"查询图书类别出错: {e}")
        return []
    finally:
        close_conn(conn)

update 模板

def modify(book_type: BookType) -> int:
    """
    修改图书类别
    :param book_type: 图书类别实体
    :return: 影响的行数 (0表示失败)
    """
    conn = None
    try:
        conn = get_conn()
        cursor = conn.cursor()
        cursor.execute(
            "UPDATE t_book_type SET book_type_name=?, book_type_desc=? WHERE id=?",
            (book_type.book_type_name, book_type.book_type_desc, book_type.id)
        )
        conn.commit()
        return cursor.rowcount
    except sqlite3.Error as e:
        print(f"修改图书类别出错: {e}")
        if conn:
            conn.rollback()
        return 0
    finally:
        close_conn(conn)

delete 模板

def remove(id: int) -> int:
    """
    删除图书类别
    :param id: 图书类别ID
    :return: 影响的行数 (0表示失败)
    """
    conn = None
    try:
        conn = get_conn()
        cursor = conn.cursor()
        cursor.execute(
            "DELETE FROM t_book_type WHERE id=?",
            (id,)
        )
        conn.commit()
        return cursor.rowcount
    except sqlite3.Error as e:
        print(f"删除图书类别出错: {e}")
        if conn:
            conn.rollback()
        return 0
    finally:
        close_conn(conn)

count 模板

def count_by_type_name(book_type_name: str) -> int:
    """
    统计同名图书类别数量
    :param book_type_name: 图书类别名称
    :return: 数量 (查询失败返回0)
    """
    conn = None
    try:
        conn = get_conn()
        cursor = conn.cursor()
        cursor.execute(
            "SELECT COUNT(1) FROM t_book_type WHERE book_type_name=?",
            (book_type_name,)
        )
        return cursor.fetchone()[0]
    except sqlite3.Error as e:
        print(f"统计图书类别出错: {e}")
        return 0
    finally:
        close_conn(conn)

entity 封装

"""
    图书类别实体类
"""
from dataclasses import dataclass, field


@dataclass
class BookType:
    id: int | None = field(default=None, repr=False)
    book_type_name: str = ""
    book_type_desc: str = ""

    @classmethod
    def create_default(cls, book_type_name: str, book_type_desc: str):
        return cls(book_type_name=book_type_name, book_type_desc=book_type_desc)

    @property
    def price_value(self):
        """可以对属性做相应的转换"""
        return int(self.price.replace("¥", "").replace(",", ""))

entity 的初始化

# 初始化实体
book_type = BookType(int(id), book_type_name, self.input_book_desc_add.toPlainText())

# 通过重载方法初始化实体
book_type = BookType.create_default(book_type_name, book_type_desc)

文章作者: 👑Dee👑
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 👑Dee👑 !
  目录