学生通讯录管理系统 基于PYQT5和Sqlite3

1 篇文章 0 订阅
1 篇文章 0 订阅

实现基本功能

  • 采用数据库管理用户名和密码

  • 添加学生地址 手机号 性别等信息

  • 管理信息包括增加 删除 修改 按不同信息查找

  • 导出信息到PDF文件

  • 采用PYQT框架,数据库链接前端TableWidget控件

github项目代码:https://github.com/Vegetable-Bird404/address-book

登陆界面

请添加图片描述
输入用户名和密码,可以进行注册或登录操作,注册则保存到user.db数据库中,登录则查询数据库中是否有匹配的username和password

class login_window(QtWidgets.QMainWindow, Ui_Form):
    """登录界面类
    使用同一组lineEdit登录或注册 初始化存储用户数据的数据库‘user.db’
    Methods:
        register_button:注册后用户名和密码将存储到user.db的user表格中
        login_button:登录时用当前lineEdit匹配数据库中的信息 验证密码
    """
    def __init__(self):
        super(login_window, self).__init__()
        self.setupUi(self)  # 创建窗体对象
        self.init()

    def init(self):
        self.pushButton.clicked.connect(self.login_button)  # 连接槽
        self.pushButtonreg.clicked.connect(self.register_button)

        # 连接数据库
        self.conn = sqlite3.connect('user.db')
        self.conn.row_factory = sqlite3.Row  # 设置返回结果为字典形式
        # 创建一个游标对象
        self.cursor = self.conn.cursor()
        # 创建表
        sql_text = '''CREATE TABLE IF NOT EXISTS user
                          (Username TEXT,
                           Password TEXT
                           );'''
        # 执行sql语句
        self.cursor.execute(sql_text)

    def register_button(self):
        # 直接用登陆界面输入的注册 就不新建一个窗口了
        username = self.lineEdit.text()
        password = self.lineEdit_2.text()
        data = [(username, password)]
        self.cursor.executemany('INSERT INTO user VALUES (?,?)', data)
        self.conn.commit()
        QMessageBox.information(self, '通知', '注册成功')

    def login_button(self):
        if self.lineEdit_2.text() == "":
            QMessageBox.warning(self, '警告', '密码为空,请重新输入!')
            return None
        username = self.lineEdit.text()
        password = self.lineEdit_2.text()

        # 查询用户表中是否存在匹配的用户名和密码
        self.cursor.execute("SELECT * FROM user WHERE username = ? AND password = ?", (username, password))
        user = self.cursor.fetchone()
        if user: # 验证通过
            Ui_Main = main_window()  # 生成主窗口的实例
            self.hide()
            # 关闭游标
            self.cursor.close()
            # 关闭连接 打开主界面
            self.conn.close()
            Ui_Main.show()
        else:
            QMessageBox.warning(self, '警告', '用户名或密码错误,请重新输入!')

主界面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zY3LXTpR-1685889751935)(source\database.jpg)]

class main_window(QtWidgets.QMainWindow, Ui_MainWindow):
    """ 主界面类
    初始化数据库'database.db' 如果没有表database则创建 读取已有数据刷新表格
    重要标志位:clickflag 功能:只有当人为编辑表格(双击为前提)才触发ChangeDatabase 通过代码修改的表格不执行该函数
    Methods:
        slot:(槽函数)
            closeEvent:重写关闭窗口事件
            handle_cell_clicked(row, column):表格点击事件 返回选中行列号 更新当前选中tablewidget行号和数据库的ROWID(当不存在删除条目情况时两者相差1)
            openDialog:打开插入条目子窗口
            ClearDatabase:清空数据库中内容(不包括表头)
            DeleteDatabase:删除选中数据,并刷新ROWID为相对索引
            SearchDatabase(value):两个按键同时连接,但传入不同参数,决定按姓名还是学号搜索,搜索结束后会在表中隐藏其他信息,当搜索框内无值时,搜索操作为refresh_table
            ChangeDatabase(item):直接编辑表格内容,并同步更新数据库,更新标志位clickflag为0
            flagset:双击触发事件,使能tablewidget编辑属性,设定clickflag为1
            ExportPDF:导出数据库条目到PDF
        AddDatabase(student_info):接收从子窗口返回的学生信息
        refresh_table:根据数据库刷新表格内容
    """
    def __init__(self):
        super(main_window, self).__init__()
        self.setupUi(self)  # 创建窗体对象
        # 初始化信号槽
        self.pushButtonAdd.clicked.connect(self.openDialog)  # 添加信息
        self.pushButtonExit.clicked.connect(self.close)
        self.pushButtonClear.clicked.connect(self.ClearDatabase)
        self.pushButtondelete.clicked.connect(self.DeleteDatabase)
        self.pushButtonSearchName.clicked.connect(lambda: self.SearchDatabase(1))
        self.pushButtonSearchID.clicked.connect(lambda: self.SearchDatabase(2))
        self.pushButton.clicked.connect(self.ExportPDF)
        # 连接数据库
        self.conn = sqlite3.connect('database.db')
        self.conn.row_factory = sqlite3.Row  # 设置返回结果为字典形式
        # 创建一个游标对象
        self.cursor = self.conn.cursor()
        # 创建表
        sql_text = '''CREATE TABLE IF NOT EXISTS database
                   (Name TEXT,
                    ID NUMBER,
                    Gender TEXT,
                    Grade NUMBER,
                    Phone NUMBER,
                    Major TEXT,
                    Address TEXT
                    );'''
        # 执行sql语句
        self.cursor.execute(sql_text)
        # 刷新Table UI
        self.refresh_table()
        # 连接鼠标点击事件
        self.clickflag = False
        self.current_row = -1
        self.current_row_sq = -1
        self.tableWidget.cellClicked.connect(self.handle_cell_clicked)
        self.tableWidget.itemChanged.connect(self.ChangeDatabase)
        self.tableWidget.doubleClicked.connect(self.flagset)

    def closeEvent(self, event: QtGui.QCloseEvent) -> None:
        """
        重写QWidget类的closeEvent方法,在窗口被关闭的时候自动触发
        """
        reply = QtWidgets.QMessageBox.question(self, '提示', "确认退出吗?",
                                               QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                                               QtWidgets.QMessageBox.No)
        if reply == QtWidgets.QMessageBox.Yes:
            super().closeEvent(event)  # 先添加父类的方法,以免导致覆盖父类方法(这是重点!!!)
            # 关闭游标
            self.cursor.close()
            # 关闭连接
            self.conn.close()
            event.accept()
        else:
            event.ignore()

增加条目:

主子界面传参,类的继承

StudentInfo
click事件
打开插入新数据的子界面:openDialog
子界面确定按键click事件
将界面填入的信息添加进数据库中:AddDatabase
self.pushButtonAdd.clicked.connect(self.openDialog)  # 添加信息
 def openDialog(self):
       insert_window = insert_dialog(self) # 使insert_dialog继承main_window类
       insert_window.exec_()
 def AddDatabase(self, StudentInfo):
 	try:
    	data = [(StudentInfo.Name, int(StudentInfo.ID), StudentInfo.Gender, int(StudentInfo.Grade), int(StudentInfo.Phone), StudentInfo.Major, StudentInfo.Addr )]
        self.cursor.executemany('INSERT INTO database VALUES (?,?,?,?,?,?,?)', data)
        self.conn.commit()
        self.refresh_table()
    except:
        QMessageBox.warning(self, '警告', '输入非法字符')
插入数据条目子界面

增加条目子界面:


class insert_dialog(QDialog, Ui_Dialog):
    """ 插入数据条目的子界面类
    继承parent:main_window
    Methods:
        send_data:读取textEdit中数据并通过parent的AddDatabase函数返回参数
    """
    def __init__(self, parent=None):
        super(insert_dialog, self).__init__(parent)
        self.setupUi(self)  # 创建窗体对象
        self.pushButtonOk.clicked.connect(self.send_data)
        self.pushButtonCancel.clicked.connect(self.reject)

    def send_data(self):
        student_info = StudentInfo(self.textEditName_2.toPlainText(), 				                                        self.textEditID.toPlainText(),                                                            self.textEditGender.toPlainText(),
                                   self.textEditGrade_2.toPlainText(),                                                      self.textEditPhone_2.toPlainText(),
                                   self.textEditMajor_2.toPlainText(),
                                   self.textEditAddr.toPlainText())
        self.accept()
        self.parent().AddDatabase(student_info)
class StudentInfo:
    """ 学生基本信息类,方便界面之间统一传参
        Name:  姓名 str
        ID:    学号 int
        Gender:性别 str
        Phone: 电话 int
        Addr:  地址 str
        Major: 专业 str
        Grade: 年级 int
    """
    def __init__(self, Name, ID, Gender, Grade, Phone, Major, Addr):
        self.Name = Name
        self.ID = ID
        self.Gender = Gender
        self.Phone = Phone
        self.Addr = Addr
        self.Major = Major
        self.Grade = Grade

删除条目

self.pushButtonClear.clicked.connect(self.ClearDatabase)
self.pushButtondelete.clicked.connect(self.DeleteDatabase)
清空所有数据
def ClearDatabase(self):
	self.cursor.execute("DELETE FROM database")
    self.conn.commit()
    self.refresh_table()  
清空选中条目
def DeleteDatabase(self):
    self.cursor.execute("DELETE FROM database WHERE ROWID = ?", (self.current_row_sq, ))
    # 执行 VACUUM 命令
    self.conn.commit()
    self.cursor.execute("VACUUM")
    self.conn.commit()
    self.tableWidget.removeRow(self.current_row)

如果不执行VACUUM命令,则ROWID在删除条目后不会重新刷新,而是继续累加。相当于执行命令前是绝对索引,一条数据对应一个ROWID,即使删掉也不会被分配给别的数据。执行命令后为相对索引。

获取当前选中条目行索引
self.tableWidget.cellClicked.connect(self.handle_cell_clicked) # tablewidget点击事件
# 更新选中条目的行索引及其ROWID
def handle_cell_clicked(self, row, column):
    self.current_row = self.tableWidget.currentRow()
    # 以下代码可删 self.current_row 和 self.current_row_sq可合并
    item = self.tableWidget.item(self.current_row, 1)  # 取唯一标识符ID
    # 查询选中的ROWID
    self.cursor.execute("SELECT ROWID, * FROM database WHERE ID = ?", (int(item.text()),))
    results = self.cursor.fetchall()
    for result in results:
        self.current_row_sq = result['ROWID']

(在执行vacuum之后self.current_row_sq和self.current_row应一致,可合并)

更改条目信息

直接双击表格更改信息

self.tableWidget.itemChanged.connect(self.ChangeDatabase) # tablewidget更改事件,返回更改信息
self.tableWidget.doubleClicked.connect(self.flagset) # 双击事件
双击事件触发编辑标志位置位
def flagset(self): 
    self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.AllEditTriggers)
    self.clickflag = True

只有双击才使能tablewidget的编辑属性,不然之前删除选中条目时,同样会触发itemchange信号

表格更改结果更新数据库
def ChangeDatabase(self, item):
    if self.clickflag == True:
        self.clickflag = False  # 重置标志位
        self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
        column = item.column()
        value = item.text()
        self.cursor.execute('SELECT * FROM database')
        col_name_list = [tuple[0] for tuple in self.cursor.description]
        if col_name_list[column] in ['ID', 'Grade', 'Phone']:
            try:
                value = int(value)  # 如果是数值类型的数据库字段要转成int型
            except:
                QMessageBox.warning(self, '警告', '输入非法字符')
                self.refresh_table()
                return
        # 执行 UPDATE 命令来修改数据
        query = "UPDATE database SET " + col_name_list[column] + "= ? WHERE ROWID = ?"
        data = (value, self.current_row_sq)
        self.cursor.execute(query, data)
        # 提交事务并关闭连接
        self.conn.commit()
        self.tableWidget.resizeColumnsToContents()
输入非法字符时警告

根据当前数据库内容更新表格

def refresh_table(self):
    self.cursor.execute("SELECT * FROM database")
    data = self.cursor.fetchall()
    # 创建一个副本或拷贝的数据列表
    self.tableWidget.setRowCount(len(data))
    for row, record in enumerate(data):
        for col, value in enumerate(record):
            item = QTableWidgetItem(str(value))
            self.tableWidget.setItem(row, col, item)
    self.tableWidget.resizeColumnsToContents()

按姓名和学号查找条目

  • 思路是找到你要找的条目,在界面暂时删除其他条目

  • 在查找框中删除信息重新点击查询按钮时恢复显示所有条目


s

def SearchDatabase(self, value):
    if value == 1:  # 按Name查询
        if self.plainTextEditName.toPlainText() is not '':
            self.cursor.execute("SELECT ROWID, * FROM database WHERE Name = ?", (self.plainTextEditName.toPlainText(),))
        else:
            self.refresh_table()
            return
    else:
        if self.plainTextEditID.toPlainText() is not '':
            try:
                self.cursor.execute("SELECT ROWID, * FROM database WHERE ID = ?", (int(self.plainTextEditID.toPlainText()), ))
            except:
                QMessageBox.warning(self, '警告', '输入非法字符!')
                return
        else:
            self.refresh_table()
            return
        results = self.cursor.fetchall()
        if results is None:
            QMessageBox.warning(self, '警告', '查询为空!')
            return None
        search_list = []   # 存放搜寻所有行号的列表
        # 检查查询结果
		for result in results:
            rowid = result[0]
            search_list.append(rowid - 1)
        # 找到暂时在界面删除的其他行
        all_list = range(self.tableWidget.rowCount())
        set1 = set(all_list)
        set2 = set(search_list)
        complement = set1 - set2  # 获取 list1 相对于 list2 的补集
        complement_list = list(complement)  # 将补集转换为列表
        for i in sorted(complement_list, reverse=True):
            self.tableWidget.removeRow(i)

导出数据库内容到PDF

点击按钮打开文件路径存储选择窗口

self.pushButton.clicked.connect(self.ExportPDF)
def ExportPDF(self):
    self.cursor.execute("SELECT * FROM database")
    results = self.cursor.fetchall()
    if results is None:
        QMessageBox.warning(self, '警告', '数据库为空!')
        return None
    # 创建PDF文档
    pdf_file = QFileDialog.getSaveFileName(self, 'Save PDF', '', 'PDF Files (*.pdf)')[0]
    if pdf_file:
        pdf = canvas.Canvas(pdf_file)
        font_path = "source/simhei.ttf"  # 替换为你的字体文件路径
        pdfmetrics.registerFont(TTFont("ChineseFont", font_path))
        pdf.setFont("ChineseFont", 12)
        # 在PDF文档中添加结果
        y = 700  # Y坐标起始位置
        for row in results:
            data = ', '.join(str(value) for value in row)
            pdf.drawString(100, y, data)
            y -= 20  # 调整下一个数据的Y坐标位置
        # 保存并关闭PDF文档
        pdf.save()
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值