0.目前已经实现的功能
其实存储用户信息数据库最好,这里使用本地的csv文件代替数据库功能
1.登录
2.注册,将信息写入csv文件
3.忘记密码,将更改信息重新写入csv文件
4.输入信息提示,用户名、密码、手机号不满足格式会提示格式问题
5.输入密码可以实现输入内容隐藏
1.前言
一般思路实现登录、注册和忘记密码功能需要设计三个界面,通过判断用户操作实现界面切换,本人在实际操作过程中感觉太过繁琐。
在偶然间发现Pyside2组件可以禁止和隐藏,那么把三个界面的所有功能按钮集合在一起,切换界面时只需要把不需要的功能组件全部隐藏即可,就可以实现界面切换的效果。
2.效果展示
3.所需要的库和版本
python版本3.9.18
库 | 版本 |
---|---|
PySide2 | 5.15.2.1 |
threading | --- |
re | 2.2.1 |
requests | 2.31.0 |
csv | 1.0 |
os | --- |
python版本和库的选择只要不是太老,应该可以随意,本身实现的功能也不是很复杂。但也可能会出现一些库比较新,有些方法名称改变等情况。
4.文件目录
images:存放登录界面用到的图片
Storage:存放用户登录信息的目录,目录下csv文件能够自动生成,不需要自动创建
login.py:实现登录界面的主体功能
connnnet.py:在登录之前判断电脑网络是否可用
ReadFile.py:判断在Storage文件下是否有指定名称的csv文件,如果没有则创建。此外登录时读取csv文件,可输入信息匹配;注册时将输入信息写入csv文件;忘记密码时可以修改csv文件指定用户的内容。
settings.py:设置文件,可以禁用connnnet.py的功能和设置请求的网站,设置ReadFile.py在Storage文件夹下生成的csv文件名称(Storage文件夹名称这个也能改,但未测试)。
代码
login.py
# Pyside2库中组件
from PySide2.QtWidgets import QApplication, QMessageBox, QLineEdit
from PySide2.QtUiTools import QUiLoader
from PySide2.QtCore import QFile, Signal, QObject
from PySide2 import QtGui
# 创建子线程
from threading import Thread
# 判断是否联网
from connnnet import ConnNet
# 导入读写csv的类
from ReadFile import RWcsv
# 用于检测字符串
import re
# 自定义信号源对象类型,一定要继承自 QObject
class MySignals(QObject):
# 定义一种信号,两个参数类型分别是: QTextBrowser 和 字符串
# 调用 emit方法发信号时,传入参数 必须是这里指定的参数类型
alarm_text_print = Signal(str) # 弹窗警告信号
remind_text_print = Signal(dict) # 输入提示信号
class Login_stats:
"""登录界面"""
def __init__(self):
# 从文件中加载UI定义
file_stats = QFile("LoginUi.ui")
file_stats.open(QFile.ReadOnly)
file_stats.close()
# 从UI定义中动态 创建一个相应的窗口对象
self.MainWindow = QUiLoader().load(file_stats)
# 初始化变量存储输入框输入数据
self.user = None
self.password = None
self.passwordagain = None
self.telphone = None
self.registernumber = None
self.registernumberSystem = None
# 登录界面索引,分为登录界面、注册界面和忘记密码界面
self.guiindex = "loginGui" # 起始为主界面
# 创建信号
self.global_signal = MySignals()
# 操作csv文件
self.rwcsvfile = RWcsv()
# 初始化图标和尺寸
self.size = 30
self.user_picture = QtGui.QPixmap('images/user.png') # 读取图片
self.password_picture = QtGui.QPixmap('images/password.png')
self.telphone = QtGui.QPixmap('images/tel.png')
self.zecema = QtGui.QPixmap('images/zuce.png')
self.MainWindow.user_picture.setPixmap(self.user_picture) # 加载图片
self.MainWindow.password_picture.setPixmap(self.password_picture)
self.MainWindow.passwordagainpicture.setPixmap(self.password_picture)
self.MainWindow.tel_picture.setPixmap(self.telphone)
self.MainWindow.zecema_picture.setPixmap(self.zecema)
self.MainWindow.user_picture.setFixedSize(self.size, self.size) # 设置图片尺寸
self.MainWindow.password_picture.setFixedSize(self.size, self.size)
self.MainWindow.passwordagainpicture.setFixedSize(self.size, self.size)
self.MainWindow.tel_picture.setFixedSize(self.size, self.size)
self.MainWindow.zecema_picture.setFixedSize(self.size, self.size)
self.MainWindow.user_picture.setScaledContents(True) # 图片自适应
self.MainWindow.password_picture.setScaledContents(True)
self.MainWindow.passwordagainpicture.setScaledContents(True)
self.MainWindow.tel_picture.setScaledContents(True)
self.MainWindow.zecema_picture.setScaledContents(True)
# 进入密码输入模式
self.MainWindow.password.setEchoMode(QLineEdit.Password)
self.MainWindow.passwordagain.setEchoMode(QLineEdit.Password)
# 输入框关联
self.MainWindow.user.textChanged.connect(self.user_returnPressed_handel)
self.MainWindow.password.textChanged.connect(self.password_returnPressed_handel)
self.MainWindow.passwordagain.textChanged.connect(self.passwordagain_returnPressed_handel)
self.MainWindow.tel.textChanged.connect(self.tel_returnPressed_handel)
self.MainWindow.zecema.textChanged.connect(self.zecema_returnPressed_handel)
# 按钮关联
self.MainWindow.login.clicked.connect(self.login_click)
self.MainWindow.registe.clicked.connect(self.registe_click)
self.MainWindow.forget_pd.clicked.connect(self.forget_click)
# 产生信号调用函数显示警告信息
self.global_signal.alarm_text_print.connect(self.alarm_text)
self.global_signal.remind_text_print.connect(self.remind_Information)
# 登录界面其他组件不可见
self.login_gui()
# 导入判断连接网络模块
self.connnet = ConnNet()
def user_returnPressed_handel(self):
"""获取用户名"""
def task():
text = self.MainWindow.user.text()
if self.is_alphanumeric_underscore(text):
self.user = text
self.global_signal.remind_text_print.emit({"code": "userOk"})
else:
self.user = None
self.global_signal.remind_text_print.emit({"code": "userError"})
thread = Thread(target=task)
thread.start()
def password_returnPressed_handel(self):
"""获取密码"""
def task():
text = self.MainWindow.password.text()
if self.is_valid_string(text):
self.password = text
self.global_signal.remind_text_print.emit({"code": "pdOk"})
else:
self.password = None
self.global_signal.remind_text_print.emit({"code": "pdError"})
if self.passwordagain is not None:
if self.password == self.passwordagain:
self.global_signal.remind_text_print.emit({"code": "pdagainOk"})
else:
self.global_signal.remind_text_print.emit({"code": "pdagainError"})
thread = Thread(target=task)
thread.start()
def passwordagain_returnPressed_handel(self):
"""再次获取密码"""
def task():
text = self.MainWindow.passwordagain.text()
if self.password is not None:
if self.password == text:
self.passwordagain = text
self.global_signal.remind_text_print.emit({"code": "pdagainOk"})
else:
self.passwordagain = None
self.global_signal.remind_text_print.emit({"code": "pdagainError"})
thread = Thread(target=task)
thread.start()
def tel_returnPressed_handel(self):
"""获取手机号"""
def task():
text = self.MainWindow.tel.text()
if self.is_n_digit_number(text, 11):
self.telphone = text
self.global_signal.remind_text_print.emit({"code": "telOk"})
else:
self.telphone = None
self.global_signal.remind_text_print.emit({"code": "telError"})
thread = Thread(target=task)
thread.start()
def zecema_returnPressed_handel(self):
"""获取注册码"""
def task():
text = self.MainWindow.zecema.text()
self.registernumber = text
thread = Thread(target=task)
thread.start()
def login_click(self):
"""登录处理"""
if self.guiindex == "loginGui":
if not self.connnet.isconnected(): # 网络是否正常
self.global_signal.alarm_text_print.emit("网络不可用")
else:
if (self.user == None) or (self.password == None):
self.global_signal.alarm_text_print.emit("请补全信息")
else:
result = self.rwcsvfile.read_data(self.user, self.password)
if result[0]:
print("登录成功")
else:
self.global_signal.alarm_text_print.emit(result[1])
else:
self.guiindex = "loginGui"
self.login_gui()
def registe_click(self):
"""注册"""
if self.guiindex == "loginGui": # 不在注册界面切换至注册界面
self.guiindex = "SignGui"
self.regite_gui()
else: # 不在登录界面进来为确定按钮,实现确定功能
if not self.connnet.isconnected(): # 网络是否正常
self.global_signal.alarm_text_print.emit("网络不可用")
else:
if self.guiindex == "ForgetPdGui":
if (self.user is not None) and (self.password is not None) and (self.passwordagain is not None) \
and (self.telphone is not None) and (self.zecema is not None):
if self.passwordagain != self.password:
self.global_signal.alarm_text_print.emit("两次密码不一致")
else:
result = self.rwcsvfile.change_csvfile(self.user, self.password,
self.telphone, self.registernumber)
if result[0]:
QMessageBox.information(self.MainWindow, '修改成功', '恭喜,您已成功修改信息!') # 提示修改成功
else:
self.global_signal.alarm_text_print.emit(result[1])
else:
self.global_signal.alarm_text_print.emit("信息不全或者格式不正确")
elif self.guiindex == "SignGui":
if (self.user is not None) and (self.password is not None) and (self.passwordagain is not None) \
and (self.telphone is not None) and (self.zecema is not None):
if self.passwordagain != self.password:
self.global_signal.alarm_text_print.emit("两次密码不一致")
else:
userlogininformation = [self.user, self.password,
self.telphone, self.registernumber] # 将要写入的数据
result = self.rwcsvfile.check_data_uniqueness(userlogininformation)
if result[0]:
self.rwcsvfile.write_to_csv_row(userlogininformation) # 写入数据
QMessageBox.information(self.MainWindow, '注册成功', '恭喜,您已成功注册!') # 提示注册成功
else:
self.global_signal.alarm_text_print.emit(result[1])
else: # 信息填写不全或者格式错误
self.global_signal.alarm_text_print.emit("信息不全或者格式不正确")
def forget_click(self):
"""忘记密码操作"""
if self.guiindex != "ForgetPdGui": # 不在忘记密码界面切换到密码界面
self.guiindex = "ForgetPdGui"
self.forget_password()
def is_n_digit_number(self, str, length):
"""检查字符串的是否只包含数字并满足相应位数"""
if len(str) != length:
return False
# 检查字符串是否仅包含数字
try:
int(str) # 尝试将字符串转换为整数,如果失败(例如,包含非数字字符),则抛出ValueError
return True
except ValueError:
return False
def is_alphanumeric_underscore(self, s):
"""匹配只包含英文字母、数字和下划线的字符串 """
pattern = r'^[A-Za-z0-9_]+$'
if re.match(pattern, s):
return True
else:
return False
def is_valid_string(self, s):
"""
检查字符串s是否只包含ASCII码表中除控制字符外的可打印字符(但排除了空格)。
"""
# \x21-\x7E 表示ASCII码值从33到126的字符,即除了控制字符和空格外的所有可打印字符
# 但由于我们可能不希望空格,所以直接用这个范围,如果你确实想要空格,可以修改为 [\x20-\x7E]
pattern = r'^[A-Za-z0-9_\x21-\x7E]*$'
# 使用re.match检查字符串是否从开头到结尾都符合模式
if re.match(pattern, s):
return True
else:
return False
def alarm_text(self, text):
"""提示框"""
QMessageBox.warning(self.MainWindow, '警告', text)
def remind_Information(self, text):
"""提示输入框内容"""
try:
if text['code'] == "userOk":
self.MainWindow.userInfor.setVisible(False)
elif text['code'] == "userError":
self.MainWindow.userInfor.setVisible(True)
self.MainWindow.userInfor.setText(" 用户名只能包含英文字母、数字和下划线")
elif text['code'] == "pdOk":
self.MainWindow.psswordInfor.setVisible(False)
elif text['code'] == "pdError":
self.MainWindow.psswordInfor.setVisible(True)
self.MainWindow.psswordInfor.setText(" 密码只能包含英文字母、数字和特殊字符")
elif text['code'] == "telOk":
self.MainWindow.telInfro.setVisible(False)
elif text['code'] == "telError":
self.MainWindow.telInfro.setVisible(True)
self.MainWindow.telInfro.setText(" 手机号应为11位数字")
elif text['code'] == "pdagainOk":
self.MainWindow.passwordagainInfor.setVisible(False)
elif text['code'] == "pdagainError":
self.MainWindow.passwordagainInfor.setVisible(True)
self.MainWindow.passwordagainInfor.setText(" 两次密码输入不一致")
except:
QMessageBox.warning(self.MainWindow, '警告', "提示输入框内容服务程序出错")
def login_gui(self):
"""显示登录界面"""
# 显示和隐藏图标
self.MainWindow.Title.setText("请登录系统")
self.MainWindow.user_picture.setVisible(True)
self.MainWindow.password_picture.setVisible(True)
self.MainWindow.tel_picture.setVisible(False)
self.MainWindow.zecema_picture.setVisible(False)
self.MainWindow.passwordagainpicture.setVisible(False)
# 隐藏输入框并禁止输入
self.MainWindow.user.setVisible(True)
self.MainWindow.user.setEnabled(True)
self.MainWindow.password.setVisible(True)
self.MainWindow.password.setEnabled(True)
self.MainWindow.passwordagain.setVisible(False)
self.MainWindow.passwordagain.setEnabled(False)
self.MainWindow.tel.setVisible(False)
self.MainWindow.tel.setEnabled(False)
self.MainWindow.zecema.setVisible(False)
self.MainWindow.zecema.setEnabled(False)
self.MainWindow.password.setPlaceholderText("密码")
# 隐藏按钮并失能
self.MainWindow.login.setVisible(True) # 影藏按钮
self.MainWindow.login.setEnabled(True) # 失能按钮
self.MainWindow.registe.setVisible(True)
self.MainWindow.registe.setEnabled(True)
self.MainWindow.registe.setText("注册")
self.MainWindow.forget_pd.setVisible(True)
self.MainWindow.forget_pd.setEnabled(True)
# 隐藏提示框
self.MainWindow.userInfor.setVisible(False)
self.MainWindow.psswordInfor.setVisible(False)
self.MainWindow.passwordagainInfor.setVisible(False)
self.MainWindow.telInfro.setVisible(False)
def regite_gui(self):
"""注册"""
# 显示图标
self.MainWindow.Title.setText("请注册")
self.MainWindow.user_picture.setVisible(True)
self.MainWindow.password_picture.setVisible(True)
self.MainWindow.tel_picture.setVisible(True)
self.MainWindow.zecema_picture.setVisible(True)
self.MainWindow.passwordagainpicture.setVisible(True)
# 隐藏输入框并禁止输入
self.MainWindow.user.setVisible(True)
self.MainWindow.user.setEnabled(True)
self.MainWindow.password.setVisible(True)
self.MainWindow.user.setEnabled(True)
self.MainWindow.passwordagain.setVisible(True)
self.MainWindow.passwordagain.setEnabled(True)
self.MainWindow.tel.setVisible(True)
self.MainWindow.tel.setEnabled(True)
self.MainWindow.zecema.setVisible(True)
self.MainWindow.zecema.setEnabled(True)
self.MainWindow.user.setPlaceholderText("用户名")
self.MainWindow.password.setPlaceholderText("密码")
self.MainWindow.tel.setPlaceholderText("手机号码")
self.MainWindow.zecema.setPlaceholderText("注册码")
# 隐藏按钮并失能
self.MainWindow.login.setVisible(True) # 影藏按钮
self.MainWindow.login.setEnabled(True) # 失能按钮
self.MainWindow.registe.setVisible(True)
self.MainWindow.registe.setEnabled(True)
self.MainWindow.registe.setText("确定")
self.MainWindow.forget_pd.setVisible(False)
self.MainWindow.forget_pd.setEnabled(False)
# 隐藏提示框
self.MainWindow.userInfor.setVisible(False)
self.MainWindow.psswordInfor.setVisible(False)
self.MainWindow.passwordagainInfor.setVisible(False)
self.MainWindow.telInfro.setVisible(False)
def forget_password(self):
"""忘记密码"""
# 隐藏图标
self.MainWindow.Title.setText("按提示找回密码")
self.MainWindow.user_picture.setVisible(True)
self.MainWindow.password_picture.setVisible(True)
self.MainWindow.tel_picture.setVisible(True)
self.MainWindow.zecema_picture.setVisible(True)
self.MainWindow.passwordagainpicture.setVisible(True)
# 隐藏输入框并禁止输入
self.MainWindow.user.setVisible(True)
self.MainWindow.user.setEnabled(True)
self.MainWindow.password.setVisible(True)
self.MainWindow.password.setEnabled(True)
self.MainWindow.passwordagain.setVisible(True)
self.MainWindow.passwordagain.setEnabled(True)
self.MainWindow.tel.setVisible(True)
self.MainWindow.tel.setEnabled(True)
self.MainWindow.zecema.setVisible(True)
self.MainWindow.zecema.setEnabled(True)
self.MainWindow.user.setPlaceholderText("用户名")
self.MainWindow.password.setPlaceholderText("重新输入密码")
self.MainWindow.tel.setPlaceholderText("输入注册时的手机号码")
self.MainWindow.zecema.setPlaceholderText("输入注册时的注册码")
# 隐藏按钮并失能
self.MainWindow.login.setVisible(True) # 影藏按钮
self.MainWindow.login.setEnabled(True) # 失能按钮
self.MainWindow.registe.setVisible(True)
self.MainWindow.registe.setEnabled(True)
self.MainWindow.registe.setText("确定")
self.MainWindow.forget_pd.setVisible(False)
self.MainWindow.forget_pd.setEnabled(False)
# 隐藏提示框
self.MainWindow.userInfor.setVisible(False)
self.MainWindow.psswordInfor.setVisible(False)
self.MainWindow.passwordagainInfor.setVisible(False)
self.MainWindow.telInfro.setVisible(False)
if __name__ == '__main__':
# QApplication做初始化操作,在任何界面控件对象创建前,先创建它
app = QApplication([])
stats = Login_stats()
stats.MainWindow.show()
app.exec_() # 死循环,处理界面事件
connnnet.py
import requests
from settings import Seetings
class ConnNet:
"""判断电脑网络是否可用的类"""
def __init__(self):
"""初始化"""
self.settings = Seetings()
self.request_site = self.settings.request_site
def isconnected(self):
"""是否连接网络"""
if self.settings.network_detection:
try:
requests.get(self.request_site, timeout=2)
except:
return False
return True
else:
return True
ReadFile.py
import csv
import os
# 设置文件
from settings import Seetings
class RWcsv:
"""一个读写csv文件的类"""
def __init__(self):
self.settings = Seetings() # 导入设置
self.directory_path = self.settings.directory_path # 用户数据存在的目录
self.userfilename = self.settings.user_file_name # 用户数据文件
self.headers = self.settings.user_headers # 用户数据文件标签
def write_to_csv_row(self, data):
"""写入数据"""
self.create_file() # 初始化文件
file_path = os.path.join(self.directory_path, self.userfilename) # 添加路径
# 使用'w'模式打开文件,如果文件不存在则创建,如果已存在则覆盖
with open(file_path, mode='a', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
# 写入数据
writer.writerow(data)
def create_file(self):
"""登录界面将注册信息写入文件"""
file_path = os.path.join(self.directory_path, self.userfilename) # 添加路径
# 检查文件是否存在
if os.path.exists(file_path):
# 尝试以读取模式打开文件
with open(file_path, mode='r', newline='', encoding='utf-8') as file:
reader = csv.reader(file)
# 读取第一行(表头)
first_row = next(reader, None)
# 检查第一行(如果存在)是否与已知标签相同
if first_row != self.headers:
# 则清空文件内容
with open(file_path, mode='w', newline='', encoding='utf-8') as file:
pass
# 写入新标签
with open(file_path, mode='w', newline='', encoding='utf-8') as new_file:
writer = csv.writer(new_file)
# 写入新的标签
writer.writerow(self.headers)
else: # 不存在文件则创建它
directory = os.path.dirname(file_path)
if not os.path.exists(directory):
os.makedirs(directory)
with open(file_path, mode='w', newline='', encoding='utf-8') as new_file:
writer = csv.writer(new_file)
# 写入新的标签
writer.writerow(self.headers)
def check_data_uniqueness(self, new_data):
"""检测新插入数据是否重复"""
self.create_file() # 检查文件是否存在
csv_file_path = os.path.join(self.directory_path, self.userfilename) # 添加路径
# 检查新数据
new_user = new_data[self.settings.user_headers.index('user')]
new_telphone = new_data[self.settings.user_headers.index('telphone')]
new_signnumbers = new_data[self.settings.user_headers.index('signnumbers')]
# 逐行读取CSV文件
with open(csv_file_path, mode='r', encoding='utf-8', newline='') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
user = row.get('user')
telphone = row.get('telphone')
signnumbers = row.get('signnumbers')
if user == new_user:
return False, "用户名已存在"
if telphone == new_telphone:
return False, "手机号已注册"
if signnumbers == new_signnumbers:
return False, "注册码已使用"
return True, "注册成功"
def read_data(self, user, password):
"""登录界面登录读取csv文件数据并进行匹配"""
self.create_file() # 先检查文件是否存在
founduser = False
csv_file_path = os.path.join(self.directory_path, self.userfilename) # 添加路径
# 逐行读取CSV文件
with open(csv_file_path, mode='r', encoding='utf-8', newline='') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
userfile = row.get('user')
passwordflie = row.get('password')
# 已经注册并匹配
if user == userfile:
founduser = True
if passwordflie == password:
return True, "Ok"
else:
return False, "密码错误"
if not founduser:
return False, "用户未注册"
def change_csvfile(self, user, newpassword, tel, signnumbers):
"""登录界面忘记密码操作"""
csv_file_path = os.path.join(self.directory_path, self.userfilename) # 添加路径
founduser = False
# 读取CSV文件
with open(csv_file_path, mode='r', newline='', encoding='utf-8') as file:
reader = csv.reader(file)
data = list(reader) # 列表化处理
for i in range(1, len(data)):
for j in range(len(self.settings.user_headers)):
if data[i][j] == user and data[i][self.settings.user_headers.index("telphone")] == tel\
and data[i][self.settings.user_headers.index("signnumbers")] == signnumbers:
data[i][self.settings.user_headers.index("password")] = newpassword
founduser = True
break
if not founduser: # 说明没有查询到
return False, "请检查用户名、手机号和注册码是否和注册时一致"
with open(csv_file_path, mode='w', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
writer.writerows(data)
return True, "修改信息成功"
settings.py
class Seetings:
"""GUI界面设置的类"""
def __init__(self):
"""初始化"""
# connnnet.py文件设置(判断电脑网路是否可用)
self.request_site = "https://www.baidu.com" # 请求网址用来判断网络是否可用
self.network_detection = True # 是否启用网路检测
# ReadFile.py设置文件
self.directory_path = 'Storage' # 存储用户登录文件的目录路径
self.user_file_name = "LoginUserFile.csv" # 存储用户登录数据的文件名称
self.user_headers = ["user", "password", "telphone", "signnumbers"] # 注册标签
视频展示
Pyside2制作一个界面实现登录、注册和忘记密码功能
如何跑起来
根据目录3.所需要的库和版本下载对应库,根据目录4.文件目录建好目录结构,将目录代码部分放在相应的文件中。images目录下没有图片可以去往阿里巴巴矢量图自行下载,在login.py文件中搜索“images”字符或在类初始化里面根据注释自己找到,将几张图片索引换成自己的就可以。