天天看点

Python学习笔记 | 数据库TinyDB配合多窗口界面及表格组件综合实例

作者:知者不惑FYK

本篇学习笔记将利用轻量级数据库TinyDB存储信息,配合Qt多窗口用户界面,实现数据库新增、修改、删除和查询等功能,最终使用Qt的表格组件将信息展现出来。

Python学习笔记 | 数据库TinyDB配合多窗口界面及表格组件综合实例

一、轻量级数据库TinyDB介绍

Python中的TinyDB,是一个纯Python编写的轻量级的面向文档的数据库,源码一共只有1800行代码,没有外部依赖项。TinyDB的数据储存方式主要是json文件,也就是 Python 中的字典类型。对于小型项目而言,使用TinyDB数据库非常适合。它有如下特点:

  • 轻便:源代码很少,小巧轻便
  • 可随意迁移:TinyDB基于面向文档的存储,数据库只有一个json文件,不依赖任何服务,拷贝即可迁移
  • 易用性:TinyDB的API非常简单和干净,易于使用
  • 非依赖性:TinyDB既不需要外部服务器,也不需要任何来自PyPI的依赖项,支持Python3.6以上的所有版本
  • 可扩展性:在中间件的帮助下,可以轻松扩展TinyDB行为,以满足特定的需求

(一)创建json数据库和表

TinyDB是一个第三方包,在使用之前需要先安装,如命令行格式:pip install tinydb,或者在集成开发环境中安装。

创建数据库和表的代码模板如下:

from tinydb import TinyDB  # 导入TinyDB类

db=TinyDB('my_db.json')  # 创建名为my_db的json数据库
table=db.table('student')  # 创建名为student的表

db.close()  # 关闭数据库           

(二)插入数据

1、插入单条数据

table.insert({'name':'张三','age':23,'class':'三年五班'})
table.insert({'name':'李四','age':'30','class':'三年一班'})           

【注】插入语句insert的返回值是所添加数据在表中的索引值,表的索引值从1开始,顺序递增。运行以下代码观察索引值变化情况:

from tinydb import TinyDB

db=TinyDB('my_db.json')
table=db.table('student')

index1=table.insert({'name':'张三','age':23,'class':'三年五班'})
index2=table.insert({'name':'李四','age':'30','class':'三年一班'})

print(index1,index2)
db.close()

运行结果:
第一次运行的结果是1 2,第二次是3 4,依此类推……           

2、插入多条数据

from tinydb import TinyDB

db = TinyDB('my_db.json')
table = db.table('student')

data = [{'name': '小明', 'age': 13, 'class': '三年五班'}, {'name': '小刚', 'age': '12', 'class': '三年一班'},
        {'name': '小强', 'age': '15', 'class': '三年一班'}, {'name': '小红', 'age': '13', 'class': '三年一班'},
        {'name': '小花', 'age': '14', 'class': '三年一班'}]
index = table.insert_multiple(data)  # 插入多条数据,即数据以列表形式添加

print(index)
db.close()

运行结果:[1, 2, 3, 4, 5]           

【注】在插入多条数据时,索引值是一个列表。

(三)查询数据

1、查询数据库中的表

num=db.tables()
print(num)

运行结果:
{'student', '_default'}           

【注】'_default'表是默认存在的

2、使用all函数查询所有数据

string=table.all()
print(string)  # 输出all函数查询到的所有结果

运行结果:
[{'name': '小明', 'age': 13, 'class': '三年五班'}, {'name': '小刚', 'age': '12', 'class': '三年一班'}, {'name': '小强', 'age': '15', 'class': '三年一班'}, {'name': '小红', 'age': '13', 'class': '三年一班'}, {'name': '小花', 'age': '14', 'class': '三年一班'}, {'name': '小明', 'age': 13, 'class': '三年五班'}, {'name': '小刚', 'age': '12', 'class': '三年一班'}, {'name': '小强', 'age': '15', 'class': '三年一班'}, {'name': '小红', 'age': '13', 'class': '三年一班'}, {'name': '小花', 'age': '14', 'class': '三年一班'}]           

3、使用where进行条件查询

from tinydb import TinyDB, where  # 需要导入where

str1 = table.search(where('name') == '小花')  # 在表中查询名字是小花的记录
str2 = table.search(where('age') > 13)
print(str1)
print(str2)

运行结果:
[{'name': '小花', 'age': '14', 'class': '三年一班'}]
[{'name': '小强', 'age': 15, 'class': '三年一班'}, {'name': '小花', 'age': 14, 'class': '三年一班'}]           

4、使用Query查询

Query是数据库查询组件,使用非常方便灵活,需要事先导入Query。演示代码如下:

stuQuery = Query()  # 创建查询对象

query_data = table.search(stuQuery.cls == '三年一班')  # 查询班级是三年一班的记录
print(query_data)

query_data = table.search(stuQuery.age <= 13)  # 查询年龄小于等于13的记录
print(query_data)

query_data=table.search(stuQuery.age<=13 and stuQuery.cls=='三年五班')
print(query_data)

query_data=table.search((stuQuery.age<13) | (stuQuery.age>14))
print(query_data)

运行结果:
[{'name': '小刚', 'age': 12, 'cls': '三年一班'}, {'name': '小强', 'age': 15, 'cls': '三年一班'}, {'name': '小红', 'age': 13, 'cls': '三年一班'}, {'name': '小花', 'age': 14, 'cls': '三年一班'}]
[{'name': '小明', 'age': 13, 'cls': '三年五班'}, {'name': '小刚', 'age': 12, 'cls': '三年一班'}, {'name': '小红', 'age': 13, 'cls': '三年一班'}]
[{'name': '小明', 'age': 13, 'cls': '三年五班'}]
[{'name': '小刚', 'age': 12, 'cls': '三年一班'}, {'name': '小强', 'age': 15, 'cls': '三年一班'}]           

【注】数据库中的字段名一定不能与系统的保留关键字重复,例如上面的学生数据库的班级字段,如果写成class,那么在使用Query查询时程序就会出错。

(四)修改数据

1、使用where方法

index = table.update({'cls': '三年二班'}, where('name') == '小刚')
print('修改记录的索引是:', index)
print(table.search(where('name') == '小刚'))

运行结果:
修改记录的索引是: [2]
[{'name': '小刚', 'age': 12, 'cls': '三年二班'}]           

2、使用Query方法

index=table.update({'cls': '三年三班'}, stuQuery.name=='小强')
print('修改记录的索引是:', index)
print(table.search(where('name') == '小强'))

运行结果:
修改记录的索引是: [3]
[{'name': '小强', 'age': 15, 'cls': '三年三班'}]           

(五)删除数据

1、删除所有数据

table.truncate()
print(table.all())

运行结果:
[]           

2、删除符合条件的数据

del_index = table.remove(stuQuery.cls == '三年一班')  # 删除班级是三年一班的数据
print('删除的索引是:', del_index)
print(table.all())

运行结果:
删除的索引是: [4, 5]
[{'name': '小明', 'age': 13, 'cls': '三年五班'}, {'name': '小刚', 'age': 12, 'cls': '三年二班'}, {'name': '小强', 'age': 15, 'cls': '三年三班'}]           

二、多窗口图形界面介绍

在实际开发项目时,多窗口图形界面是经常要使用的,例如登录窗口、点击功能按钮弹出对应功能实现窗口等等。使用多窗口图形界面,必须要理清窗口的继承关系,以及设置窗口的模态属性,也就是子窗口是否阻塞主窗口。

(一)创建多窗口的基本模型

下面实例演示创建多窗口的基础模型。首先,在Qt设计大师中创建一个主窗口和两个子窗口,主窗口设置了背景颜色,分别保存这三个窗口文件,并将三个窗口的ui文件转换成py文件,准备静态加载。

1、加载多窗口代码

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtGui import Qt
from ui_test1 import Ui_MForm  # 导入窗口1的Ui类
from ui_test2 import Ui_SForm1  # 导入窗口2的Ui类
from ui_test3 import Ui_SForm2  # 导入窗口3的Ui类


# 自定义主窗口的类
class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui1 = Ui_MForm()
        self.ui1.setupUi(self)

        # 设置两个子窗口对象值为None,代表窗口未打开
        self.s_window1 = None
        self.s_window2 = None

        # 主窗口的按钮单击信号和槽
        self.ui1.pushButton.clicked.connect(self.show_Form)

    # 显示子窗口的方法
    def show_Form(self):
        # 如果子窗口1是未打开状态,则创建该窗口
        # 没有这个检测的环节,点击打开窗口按钮会反复打开子窗口
        if self.s_window1 is None:
            self.s_window1 = SubWindow1()
            self.s_window1.show()
            self.s_window1.move(300, 200)
        # 如果子窗口2是未打开状态,则创建该窗口
        if self.s_window2 is None:
            self.s_window2 = SubWindow2()
            self.s_window2.show()
            self.s_window2.move(900, 200)


# 自定义子窗口1的类,继承主窗口的类MainWindow,因此主窗口的背景颜色设置在子窗口1里生效
class SubWindow1(MainWindow):
    def __init__(self):
        super(SubWindow1, self).__init__()
        self.ui2 = Ui_SForm1()
        self.ui2.setupUi(self)


# 自定义子窗口2的类,继承通用组件QWidget,因此主窗口的背景颜色设置在子窗口2里不生效
class SubWindow2(QWidget):
    def __init__(self):
        super(SubWindow2, self).__init__()
        self.ui3 = Ui_SForm2()
        self.ui3.setupUi(self)


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()           

【注】子窗口1的自定义类继承了主窗口的自定义类MainWindow,子窗口2的自定义类继承通用组件QWidget,所以子窗口1的背景颜色跟主窗口一样,而子窗口2没有背景颜色。

2、效果演示

视频加载中...

(二)实现子窗口跟随主窗口一起关闭

在上面多窗口基本模型的视频演示中可以看到,主窗口和两个子窗口可以自由关闭,但是当主窗口关闭时,子窗口并不能跟随关闭。为了解决这个问题,我们要重写主类的closeEvent关闭事件方法。

1、重写主类closeEvent方法代码

# 一定要在主窗口的自定义类里书写此代码
def closeEvent(self, event):
    sys.exit(0)           

2、效果演示

视频加载中...

【注】程序运行后,关闭主窗口则子窗口1和子窗口2都跟着关闭;关闭子窗口1,主窗口和子窗口2也跟着关闭,因为子窗口1继承自主窗口类,关闭事件跟主窗口是相同的;关闭子窗口2不会影响主窗口和子窗口1。

(三)设置子窗口模态属性

在做多窗口项目开发时,有时要求子窗口不能同时打开,以免处理不好发生数据混乱的情况,例如新增数据和删除数据子窗口。为此,我们就需要用子窗口来阻塞主窗口进程,这里就需要设置子窗口的模态属性。

1、在Qt设计大师中设置

点击需要设置模态属性的子窗口,在窗口的QWidget属性设置中找到windowModality,将属性值改成ApplicationModal,保存窗口并将相应的ui文件转换成py文件即可。模态属性设置如下图:

Python学习笔记 | 数据库TinyDB配合多窗口界面及表格组件综合实例

设置子窗口的模态属性

2、在代码中设置

self.s_window2 = SubWindow2()
# 子窗口模态属性设置一定要放在show()方法之前
self.s_window2.setWindowModality(Qt.ApplicationModal)
self.s_window2.show()           

(四)多窗口演示完整代码

import sys
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtGui import Qt
from ui_test1 import Ui_MForm  # 导入窗口1的Ui类
from ui_test2 import Ui_SForm1  # 导入窗口2的Ui类
from ui_test3 import Ui_SForm2  # 导入窗口3的Ui类


# 自定义主窗口的类
class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui1 = Ui_MForm()
        self.ui1.setupUi(self)

        # 设置两个子窗口对象值为None,代表窗口未打开
        self.s_window1 = None
        self.s_window2 = None

        # 主窗口的按钮单击信号和槽
        self.ui1.pushButton.clicked.connect(self.show_Form)

    # 显示子窗口的方法
    def show_Form(self):
        # 如果子窗口1是未打开状态,则创建该窗口
        if self.s_window1 is None:
            self.s_window1 = SubWindow1()
            self.s_window1.show()
            self.s_window1.move(300, 200)
        # 如果子窗口2是未打开状态,则创建该窗口
        if self.s_window2 is None:
            self.s_window2 = SubWindow2()
            # 设置子窗口2的模态方式为阻塞主程序,或者在Qt设计大师的窗口属性里设置
            # 程序运行后,只有子窗口2是活动的,主窗口不可点击,只有子窗口2关闭后才可以操作主窗口
            self.s_window2.setWindowModality(Qt.ApplicationModal)
            self.s_window2.show()
            self.s_window2.move(900, 200)

    # 重写父类的关闭事件方法,实现关闭主窗口后子窗口跟着关闭的功能
    def closeEvent(self, event):
        sys.exit(0)


# 自定义子窗口1的类,继承主窗口的类MainWindow,因此主窗口的背景颜色设置在子窗口1里生效
class SubWindow1(MainWindow):
    def __init__(self):
        super(SubWindow1, self).__init__()
        self.ui2 = Ui_SForm1()
        self.ui2.setupUi(self)


# 自定义子窗口2的类,继承通用组件QWidget,因此主窗口的背景颜色设置在子窗口2里不生效
class SubWindow2(QWidget):
    def __init__(self):
        super(SubWindow2, self).__init__()
        self.ui3 = Ui_SForm2()
        self.ui3.setupUi(self)


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()           

视频加载中...

三、综合实例开发

在介绍图形界面开发之PySimpleGUI模块时,我们使用SQLite3和PySimpleGUI模块制作了一个员工档案信息管理系统,现在我们使用PySide6和TinyDB对该系统进行重新开发,旨在加深对PySide6和TinyDB的理解,并未在安全性方面做任何处理。

(一)准备数据库

1、数据库描述

本案例采用轻量级数据库TinyDB存储员工档案信息。数据库、表和字段设置如下:

【数据库文件】:company.json

【档案信息表】:employee
【字段】:编号、姓名、性别、出生年月、民族、籍贯、党派、学历、职称、身份证号码
电话号码、所在部门、获得的荣誉、所受的处罚

【用户名密码保存表】:user
字段:uid、pwd           

2、初始化数据库

首先创建一个init.py文件,使用代码创建数据库和用户名密码保存表,并插入一条默认用户名和密码数据,代码如下:

from tinydb import TinyDB

db = TinyDB('company.json')  # 创建json数据库,数据库名称为company.json

table = db.table('user')  # 创建用来存储用户名和密码的表,表名为user

table.insert({'uid': 'admin', 'pwd': '1111'})  # 设置初始用户名和密码

db.close()           

(二)设计窗口

窗口包括:主窗口,登录窗口,新增信息窗口,查询/删除信息窗口,修改信息窗口和修改密码窗口。

1、主窗口

在Qt设计大师里创建主窗口时,最好使用Main Window模版,如果不需要菜单栏、工具栏和地址栏可以手动去除。Main Window类的窗口功能比较健全,设置背景图片、布局等都不会出现问题。采用Form类创建的窗口,在代码中创建窗口类时,如果继承QMainWindow类,那么布局会混乱;如果继承QWidget类,那么背景图片就会不显示。

我在做这个实例时,就是采用Form模版创建的主窗口,然后在代码中创建窗口类时继承QWidget类,就发生了背景图片不显示的情况,折磨了很久才找到原因。由于不想重新设计主窗口,于是将主窗口的最大尺寸和最小尺寸均设置成窗口实际尺寸,让窗口不能通过拖动改变尺寸,也就不需要布局了,最后在创建窗口类的代码中继承QMainWindow类,总算解决了背景图片不显示的问题。

  • 窗口ui文件名称:ui_main.ui
  • 窗口对象名称:MainForm,由于是多窗口应用,因此窗口对象的名称最好不采用默认的Form或者MainWindow
  • 包含组件:四个Tool Button,工具按钮的toolButtonStyle属性设置为ToolButtonTextUnderIcon,即按钮文本在图标下面。autoRaise属性设置为真,即工具按钮自动浮起,否则是凹陷状态,按钮周围有外框影响美观
  • 背景图片:在窗口对象的样式表中,使用#MainForm{}的形式设置背景图片,这是为了只给名称为MainForm的组件即窗口添加背景图片,否则窗口上的所有元素都会加载这个背景图片。样式表代码如下:
#MainForm{background-image: url(:/images/icon/background.jpg);}           
  • 窗口外观:如下图
Python学习笔记 | 数据库TinyDB配合多窗口界面及表格组件综合实例

主窗口示例图片

2、登录窗口

设计登录窗口不要采用Dialog模版,无论点击OK还是Cancel按钮,Dialog类的窗口都会关闭,不能持久显示。

  • 窗口ui文件名称:ui_login.ui
  • 窗口对象名称:LoginForm
  • 包含组件:两个标签组件,两个单行文本输入框组件,一个按钮组件
  • 背景图片:与主窗口设置背景图片方法相同
  • 窗口外观:如下图
Python学习笔记 | 数据库TinyDB配合多窗口界面及表格组件综合实例

登录窗口示例图片

3、新增信息窗口

  • 窗口ui文件名称:ui_add.ui
  • 窗口对象名称:AddForm
  • 包含组件:一个Group Box容器类组件,用来装载录入员工基本信息的所有组件,起到归类的作用。一个Tab Widget标签组件,用来放置“获得的荣誉”和“所受的处罚”两个多行文本输入框,节省空间。一个Table Widget表格组件,用来显示新录入的员工信息。剩余是标签、单行文本输入框和按钮组件,整个窗口设置了布局,支持拖放改变窗口大小。另外,在窗口的样式表中对表格组件的标题栏进行了设置,样式表代码如下:
QHeaderView::section{;background-color:rgb(225,225,225);height:28px;}           
  • 背景图片:无
  • 窗口外观:如下图
Python学习笔记 | 数据库TinyDB配合多窗口界面及表格组件综合实例

新增信息窗口示例图片

4、查询/删除信息窗口

  • 窗口ui文件名称:ui_search_del.ui
  • 窗口对象名称:SDForm
  • 包含组件:一个Table Widget表格组件,标题栏设置与新增信息中相同。剩余是标签、单行文本输入框和按钮组件,整个窗口设置了布局,支持拖放改变窗口大小
  • 背景图片:无
  • 窗口外观:如下图
Python学习笔记 | 数据库TinyDB配合多窗口界面及表格组件综合实例

查询/删除信息窗口示例图片

5、修改信息窗口

  • 窗口ui文件名称:ui_change.ui
  • 窗口对象名称:ChangeForm
  • 包含组件:与新增信息窗口基本相同
  • 背景图片:无
  • 窗口外观:如下图
Python学习笔记 | 数据库TinyDB配合多窗口界面及表格组件综合实例

修改信息窗口示例图片

(三)代码部分

1、登录窗口和主窗口显示代码

(1)思路分析

  • 创建登录窗口类
  • 创建主窗口类
  • 创建登录窗口实例对象
  • 显示登录窗口,即程序运行首先显示登录窗口
  • 判断登录是否成功,如果登录成功,则关闭登录窗口本身,然后创建主窗口实例对象并显示主窗口

(2)实例代码

from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox
from tinydb import TinyDB, Query
from ui_login import Ui_LoginForm  # 导入登录窗口ui文件
from ui_main import Ui_MainForm  # 导入主窗口ui文件


# 创建登录窗口类,这里必须继承QMainWindow类,如果继承QWidget,那么背景图片无法显示
class LoginWindow(QMainWindow):
    def __init__(self):
        super(LoginWindow, self).__init__()
        self.uiLogin = Ui_LoginForm()
        self.uiLogin.setupUi(self)

        self.main_window = None  # 预设主窗口对象

        # 密码输入框回车和登录按钮单击信号,都连接到登录方法
        self.uiLogin.lineEdit_2.returnPressed.connect(self.login)
        self.uiLogin.pushButton.clicked.connect(self.login)

    # 登录方法
    def login(self):
        if self.uiLogin.lineEdit.text() == '' or self.uiLogin.lineEdit_2.text() == '':
            return
        db = TinyDB('company.json')
        table = db.table('user')
        query = Query()
        username = 'admin'
        # 搜索结果是列表包含字典的格式,因此需要先用索引值获得字典数据,再用字典的key获取value值
        password = table.search(query.uid == username)[0]['pwd']
        if self.uiLogin.lineEdit.text() == username and self.uiLogin.lineEdit_2.text() == password:
            self.close()  # 如果登录成功,则登录窗口本身关闭。因为不再使用登录窗口,所以没有使用隐藏方法。
            self.main_window = MainWindow()  # 创建主窗口实例对象
            self.main_window.show()  # 显示主窗口
        else:
            QMessageBox.information(self, '提示信息', '用户名或密码错误,请重新输入!')


# 创建主窗口类,同样要继承QMainWindow类,以显示背景图片
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.uiMain = Ui_MainForm()
        self.uiMain.setupUi(self)


if __name__ == '__main__':
    app = QApplication([])
    window = LoginWindow()  # 创建登录窗口实例对象
    window.show()  # 显示登录窗口
    app.exec()           

2、主程序完整代码

在主程序中,除了上面介绍的登录窗口和主程序窗口显示代码外,剩下就是显示新增信息、查询/删除信息和修改信息窗口的代码。每个窗口具体功能实现部分,都在各自的py文件中,在主程序中将这些py文件以模块的形式导入,再引用模块内的窗口类就可以了。修改密码功能,在主程序中直接采用QInputDialog输入对话框完成。主程序文件名为mainProgram.py,完整代码如下:

from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox, QInputDialog
from PySide6.QtGui import Qt
from tinydb import TinyDB, Query
from ui_login import Ui_LoginForm
from ui_main import Ui_MainForm
from add import AddWindow  # 导入新增信息模块即py文件中的窗口类
from search_del import SDWindow  # 导入搜索/删除信息模块中的窗口类
from change import ChangeWindow  # 导入修改信息模块中的窗口类


# 创建登录窗口类,这里必须继承QMainWindow类,如果继承QWidget,那么背景图片无法显示
class LoginWindow(QMainWindow):
    def __init__(self):
        super(LoginWindow, self).__init__()
        self.uiLogin = Ui_LoginForm()
        self.uiLogin.setupUi(self)

        self.main_window = None  # 预设主窗口对象

        # 密码输入框回车和登录按钮单击信号,都连接到登录方法
        self.uiLogin.lineEdit_2.returnPressed.connect(self.login)
        self.uiLogin.pushButton.clicked.connect(self.login)

    # 登录方法
    def login(self):
        if self.uiLogin.lineEdit.text() == '' or self.uiLogin.lineEdit_2.text() == '':
            return
        db = TinyDB('company.json')
        table = db.table('user')
        query = Query()
        username = 'admin'
        # 搜索结果是列表包含字典的格式,因此需要先用索引值获得字典数据,再用字典的key获取value值
        password = table.search(query.uid == username)[0]['pwd']
        db.close()
        if self.uiLogin.lineEdit.text() == username and self.uiLogin.lineEdit_2.text() == password:
            self.close()  # 如果登录成功,则登录窗口本身关闭。因为不再使用登录窗口,所以没有使用隐藏方法。
            self.main_window = MainWindow()  # 创建主窗口实例对象
            self.main_window.show()  # 显示主窗口
        else:
            QMessageBox.information(self, '提示信息', '用户名或密码错误,请重新输入!')


# 创建主窗口类,同样要继承QMainWindow类,以显示背景图片
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.uiMain = Ui_MainForm()
        self.uiMain.setupUi(self)

        self.add_window = None  # 预设新增信息窗口对象
        self.search_window = None  # 预设搜索删除信息窗口对象
        self.change_window = None  # 预设修改信息窗口对象

        self.uiMain.toolButton_add.clicked.connect(self.show_add)  # 新增信息按钮点击信号和槽
        self.uiMain.toolButton_search.clicked.connect(self.show_search)  # 查询删除信息按钮点击信号和槽
        self.uiMain.toolButton_change.clicked.connect(self.show_change)  # 新增信息按钮点击信号和槽
        self.uiMain.toolButton_pwd.clicked.connect(self.show_pwd)  # 修改密码按钮单击信号和槽

    # 显示新增信息窗口方法
    def show_add(self):
        self.add_window = AddWindow()
        self.add_window.setWindowModality(Qt.ApplicationModal)  # 为窗口设置模态属性
        self.add_window.show()

    # 显示查询/删除信息窗口方法
    def show_search(self):
        self.search_window = SDWindow()
        self.search_window.setWindowModality(Qt.ApplicationModal)  # 为窗口设置模态属性
        self.search_window.show()

    # 显示修改信息窗口方法
    def show_change(self):
        self.change_window = ChangeWindow()
        self.change_window.setWindowModality(Qt.ApplicationModal)  # 为窗口设置模态属性
        self.change_window.show()

    # 修改密码方法
    def show_pwd(self):
        pwd1, okPressed1 = QInputDialog.getText(self, '修改密码', '请输入新密码:')
        if okPressed1:
            pwd2, okPressed2 = QInputDialog.getText(self, '确认修改', '请再次输入新密码:')
            if okPressed2:
                if pwd1 == pwd2:
                    db = TinyDB('company.json')
                    table = db.table('user')
                    query = Query()
                    table.update({'pwd': pwd1}, query.uid == 'admin')
                    db.close()
                    QMessageBox.information(self, '修改密码', '密码修改成功!')
                else:
                    QMessageBox.information(self, '修改密码', '两次输入不相同,请重新输入!')


if __name__ == '__main__':
    app = QApplication([])
    window = LoginWindow()  # 创建登录窗口实例对象
    window.show()  # 显示登录窗口
    app.exec()           

3、新增信息模块代码

新增信息模块名为add.py,代码如下:

import copy
from PySide6.QtWidgets import QApplication, QWidget, QMessageBox, QTableWidgetItem
from PySide6.QtGui import Qt
from tinydb import TinyDB
from ui_add import Ui_AddForm


# 创建新增信息窗口类
class AddWindow(QWidget):
    def __init__(self):
        super(AddWindow, self).__init__()
        self.uiAdd = Ui_AddForm()
        self.uiAdd.setupUi(self)

        self.add_list = []  # 定义存储所有新增信息的列表,为的是往数据库里添加
        self.add_dict = dict()  # 定义存储每行新增信息的字典

        self.uiAdd.pushButton.clicked.connect(self.addList)  # 添加到新增列表按钮信号和槽
        self.uiAdd.pushButton_2.clicked.connect(self.saveDB)  # 保存到数据库按钮信号和槽

        self.table_init()  # 调用table表格初始化方法
        self.id_init()  # 调用编号初始化方法

    # 编号初始化方法,自动生成编号
    def id_init(self):
        db = TinyDB('company.json')
        table = db.table('employee')
        content = table.all()  # 读取数据表的所有内容
        rows = self.uiAdd.tableWidget.rowCount()  # 获取Table的行数
        
        if rows == 0 and content == []:
            self.uiAdd.edit_0.setText('A001')  # 如果数据表和Table中的内容都是空的,则编号设置为A001
            self.uiAdd.edit_1.setFocus()  # 将焦点设置在姓名输入框
            return
        if rows != 0:  # 如果Table中有数据,则取得最后一行的编号
            id_data = self.uiAdd.tableWidget.item(rows - 1, 0).text()
        else:
            id_data = content[-1]['编号']  # 如果Table中没有数据,则取得数据表中最后一条记录的编号数据

        id_new = 'A%03d' % (int(id_data[1:]) + 1)  # 将原有编号后面数字加1,一共显示3位,前面用0补充
        self.uiAdd.edit_0.setText(id_new)  # 将新增信息窗口的编号输入框内容设置为新编号
        self.uiAdd.edit_1.setFocus()  # 将焦点设置在姓名输入框

    # 表格初始化方法
    def table_init(self):
        # 调整各列的列宽
        self.uiAdd.tableWidget.setColumnWidth(0, 50)
        self.uiAdd.tableWidget.setColumnWidth(1, 60)
        self.uiAdd.tableWidget.setColumnWidth(2, 40)
        self.uiAdd.tableWidget.setColumnWidth(3, 60)
        self.uiAdd.tableWidget.setColumnWidth(4, 40)
        self.uiAdd.tableWidget.setColumnWidth(5, 60)
        self.uiAdd.tableWidget.setColumnWidth(6, 60)
        self.uiAdd.tableWidget.setColumnWidth(7, 60)
        self.uiAdd.tableWidget.setColumnWidth(8, 80)
        self.uiAdd.tableWidget.setColumnWidth(9, 150)
        self.uiAdd.tableWidget.setColumnWidth(10, 90)

    # 添加到新增列表按钮单击方法
    def addList(self):
        # 判断编号和姓名输入框内容是否为空
        if self.uiAdd.edit_0.text() == '' or self.uiAdd.edit_1.text() == '':
            QMessageBox.critical(self, '错误信息', '编号和姓名不能为空,请重新输入')
        else:
            row = self.uiAdd.tableWidget.rowCount()  # 获取表格总行数
            self.uiAdd.tableWidget.setRowCount(row + 1)  # 表格当前行等于总行数加1

            # 将单行文本输入框的名称设置为连续序列形式,如edit_0、edit_1……,利用eval函数循环将各个单行文本输入框内容添加到表格当前行中
            for col in range(12):
                label_text = eval(f'self.uiAdd.label_{col}.text()')
                edit_text = eval(f'self.uiAdd.edit_{col}.text()')
                self.uiAdd.tableWidget.setItem(row, col, QTableWidgetItem(edit_text))
                # 设置单元格对齐方式,需要使用Qt类的AlignmentFlag属性
                self.uiAdd.tableWidget.item(row, col).setTextAlignment(Qt.AlignmentFlag.AlignCenter)

                # 构建新增信息的字典数据,下面语句还可以写成self.add_dict.update({f'{label_text}':f'{edit_text}'})
                self.add_dict[f'{label_text}'] = edit_text

            # 单行文本输入框内容添加完毕后,将两个多行文本输入框的内容添加到最后两列中
            edit_text = self.uiAdd.plainTextEdit_0.toPlainText()
            self.uiAdd.tableWidget.setItem(row, 12, QTableWidgetItem(edit_text))
            self.add_dict['获得的荣誉'] = edit_text  # 字典里也添加该信息
            edit_text = self.uiAdd.plainTextEdit_1.toPlainText()
            self.uiAdd.tableWidget.setItem(row, 13, QTableWidgetItem(edit_text))
            self.add_dict['所受的处罚'] = edit_text
            # 将一行信息的字典添加到列表中,这里必须对字典进行深拷贝,否则下次输入改变字典内容,列表内容随着更改
            self.add_list.append(copy.deepcopy(self.add_dict))

            # 使用跟上面相同方法,将所有输入框内容清除
            for i in range(12):
                eval(f'self.uiAdd.edit_{i}.clear()')
            self.uiAdd.plainTextEdit_0.clear()
            self.uiAdd.plainTextEdit_1.clear()
            self.id_init()  # 所有输入框内容清空后,调用编号初始化方法,自动生成编号

    # 保存到数据库按钮单击方法
    def saveDB(self):
        choice = QMessageBox.question(self, '确认信息', '确认要将数据保存到数据库吗?')
        if choice == QMessageBox.No:
            return
        db = TinyDB('company.json')
        table = db.table('employee')
        table.insert_multiple(self.add_list)
        QMessageBox.information(self, '提示信息', '数据保存成功!')
        print(table.all())
        self.uiAdd.tableWidget.clearContents()  # 清除表格内容,但不清除标题,clear方法会连标题都清除
        self.uiAdd.tableWidget.setRowCount(0)
        self.add_list.clear()


if __name__ == '__main__':
    app = QApplication([])
    window = AddWindow()
    window.show()
    app.exec()           

运行效果:

视频加载中...

4、查询/删除信息模块代码

新增信息模块名为search_del.py,代码如下:

from PySide6.QtWidgets import QApplication, QWidget, QTableWidgetItem, QMessageBox
from PySide6 import QtCore
from PySide6.QtGui import Qt
from ui_search_del import Ui_SDForm
from tinydb import TinyDB, Query


class SDWindow(QWidget):
    def __init__(self):
        super(SDWindow, self).__init__()
        self.uiSD = Ui_SDForm()
        self.uiSD.setupUi(self)

        self.table_init()  # 调用表格初始化方法

        self.uiSD.button_s.clicked.connect(self.search)  # 提交搜索按钮单击信号
        self.uiSD.button_d.clicked.connect(self.delete)  # 删除按钮单击信号

    # 表格初始化方法
    def table_init(self):
        # 调整各列的列宽
        self.uiSD.tableWidget.setColumnWidth(0, 50)
        self.uiSD.tableWidget.setColumnWidth(1, 60)
        self.uiSD.tableWidget.setColumnWidth(2, 40)
        self.uiSD.tableWidget.setColumnWidth(3, 60)
        self.uiSD.tableWidget.setColumnWidth(4, 40)
        self.uiSD.tableWidget.setColumnWidth(5, 60)
        self.uiSD.tableWidget.setColumnWidth(6, 60)
        self.uiSD.tableWidget.setColumnWidth(7, 60)
        self.uiSD.tableWidget.setColumnWidth(8, 80)
        self.uiSD.tableWidget.setColumnWidth(9, 150)
        self.uiSD.tableWidget.setColumnWidth(10, 90)

    def search(self):
        db = TinyDB('company.json')
        table = db.table('employee')
        query = Query()
        self.query_data = []
        # 按照所输入位置和内容搜索信息
        if self.uiSD.edit_id.text():
            self.query_data = table.search(query.编号 == self.uiSD.edit_id.text())
        if self.uiSD.edit_name.text():
            self.query_data = table.search(query.姓名 == self.uiSD.edit_name.text())
        if self.uiSD.edit_card.text():
            self.query_data = table.search(query.身份证号码 == self.uiSD.edit_card.text())
        if self.uiSD.edit_cls.text():
            self.query_data = table.search(query.所在部门 == self.uiSD.edit_cls.text())

        db.close()
        self.show_data(self.query_data)  # 调用显示数据方法

    # 显示数据方法
    def show_data(self, data):
        row = 0  # 初始行为0
        self.uiSD.tableWidget.setRowCount(len(data))  # 设置表格总行数为搜索到的记录数
        for item in data:
            col = 0  # 初始列为0
            for key in item:
                self.uiSD.tableWidget.setItem(row, col, QTableWidgetItem(item[key]))
                # 设置单元格对齐方式,需要使用Qt类的AlignmentFlag属性
                self.uiSD.tableWidget.item(row, col).setTextAlignment(Qt.AlignmentFlag.AlignCenter)
                col += 1
            row += 1

    # 删除信息按钮单击方法
    def delete(self):
        selected_data = self.uiSD.tableWidget.selectedItems()  # 获取选中的所有表格项目
        self.id_list = []  # 定义存储选中信息的编号列表
        rows = len(selected_data) // 14  # 计算一共选中几行信息,因为获取的数据是所有单元格,每行有14个单元格

        for i in range(rows):
            self.id_list.append(selected_data[i * 14].text())  # 循环获取所有准备删除的编号数据,放置在列表中

        choice = QMessageBox.question(self, '删除信息', f'确认要删除编号为:{self.id_list}的数据吗?')
        if choice == QMessageBox.No:
            return

        db = TinyDB('company.json')
        table = db.table('employee')
        query = Query()
        # 将数据库中包含要删除编号的记录全部删掉
        for i in self.id_list:
            table.remove(query.编号 == i)
        db.close()

        self.search()  # 调用查询方法,更新表格显示

        QMessageBox.information(self,'删除信息','信息删除成功!')


if __name__ == '__main__':
    app = QApplication([])
    window = SDWindow()
    window.show()
    app.exec()           

运行效果:

视频加载中...

5、修改信息模块代码

修改信息模块名为change.py,代码如下:

from PySide6.QtWidgets import QApplication, QWidget, QMessageBox
from tinydb import TinyDB, Query
from ui_change import Ui_ChangeForm


# 创建修改信息窗口类
class ChangeWindow(QWidget):
    def __init__(self):
        super(ChangeWindow, self).__init__()
        self.uiChange = Ui_ChangeForm()
        self.uiChange.setupUi(self)
        self.load_data = []  # 预设存储要修改信息列表变量
        self.uiChange.pushButton.clicked.connect(self.load)  # 读入信息按钮信号
        self.uiChange.pushButton_2.clicked.connect(self.save)  # 保存信息按钮信号

    # 读入信息方法
    def load(self):
        if not self.uiChange.edit_0.text():
            QMessageBox.information(self, '提示信息', '请输入要修改信息的编号,然后点读入信息按钮!')
            return
        db = TinyDB('company.json')
        table = db.table('employee')
        query = Query()
        self.load_data = table.search(query.编号 == self.uiChange.edit_0.text())
        db.close()
        if len(self.load_data) == 0:
            QMessageBox.information(self, '提示信息', '输入的编号不存在,请重新输入!')
            return
        i = 0
        for item in self.load_data[0].values():
            if i in (0, 12, 13):  # 跳过第0、12和13条项目
                i+=1
                continue
            eval(f'self.uiChange.edit_{i}.setEnabled(True)')  # 循环设置单行文本输入框的可用状态为真
            eval(f'self.uiChange.edit_{i}.setText(item)')  # 设置文本输入框的内容为读入的相应数据
            i += 1

        # 设置两个多行文本编辑器的状态和内容
        self.uiChange.plainTextEdit_0.setEnabled(True)
        self.uiChange.plainTextEdit_1.setEnabled(True)
        self.uiChange.plainTextEdit_0.setPlainText(self.load_data[0]['获得的荣誉'])
        self.uiChange.plainTextEdit_1.setPlainText(self.load_data[0]['所受的处罚'])

    # 保存信息方法
    def save(self):
        db = TinyDB('company.json')
        table = db.table('employee')
        query = Query()
        for i in range(12):  # 以相应的标签的文本为key,以对应的单行文本输入框的内容为value,更新数据库内容
            key=eval(f'self.uiChange.label_{i}.text()')
            value=eval(f'self.uiChange.edit_{i}.text()')
            table.update({key:value},query.编号==self.uiChange.edit_0.text())

        table.update({'获得的荣誉':self.uiChange.plainTextEdit_0.toPlainText()},query.编号==self.uiChange.edit_0.text())
        table.update({'所受的处罚':self.uiChange.plainTextEdit_1.toPlainText()},query.编号==self.uiChange.edit_0.text())

        QMessageBox.information(self,'提示信息','信息保存成功!')


if __name__ == '__main__':
    app = QApplication([])
    window = ChangeWindow()
    window.show()
    app.exec()           

运行效果:

视频加载中...

6、修改密码功能

修改密码功能没有设计窗口,而是在主程序中直接采用QInputDialog输入对话框完成。代码如下:

# 修改密码方法
def show_pwd(self):
    pwd1, okPressed1 = QInputDialog.getText(self, '修改密码', '请输入新密码:')
    if okPressed1:
        pwd2, okPressed2 = QInputDialog.getText(self, '确认修改', '请再次输入新密码:')
        if okPressed2:
            if pwd1 == pwd2:
                db = TinyDB('company.json')
                table = db.table('user')
                query = Query()
                table.update({'pwd': pwd1}, query.uid == 'admin')
                db.close()
                QMessageBox.information(self, '修改密码', '密码修改成功!')
            else:
                QMessageBox.information(self, '修改密码', '两次输入不相同,请重新输入!')           

(四)整体效果演示

视频加载中...