(一)管理系统功能展示
管理员界面(服务端)
展示界面(客户端)
(二)开发环境
处理器:Apple M1 Max (内存:64G)
系统版本:MacOS Sonoma 14.1.2
开发环境:Anaconda(Python3.9) + MySql 8.0.29 + Pycharm + PyQt5
实现功能:对JSON数据的增删改查,包含一个简单的身份验证(密码输入不正确功能不能用),并且使用多线程通过socket服务端与客户端进行通讯
(三)主窗口代码详解(知识点!)
注:完整代码在整个文章的最后,并附有下载链接!
导入程序所需要的模块
import socket # 导入 Socket 编程相关的模块
import sys # 导入 sys 模块,提供对 Python 解释器的访问
import json # 导入处理 JSON 数据的模块
import os # 导入提供与操作系统交互的功能的模块
import threading # 导入创建多线程的模块
from PyQt5.QtCore import Qt # 导入 PyQt5 中的 Qt 模块
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QPushButton, QDialog, QTableWidget, QTableWidgetItem # 导入 PyQt5 中的 GUI 元素相关的模块
新增数据和修改数据的窗口
这里很容易犯错,我们的JSON文件是要用字典形式去存储,不要使用列表的方式,仅仅是符号上的区别但是差别很大!列表的符号是[ ] 而字典是{ }
- JSON 中的字典使用花括号
{}
来表示,形如{"key1": "value1", "key2": "value2"}
。 - JSON 中的列表使用方括号
[]
来表示,形如["item1", "item2", "item3"]
。
这里特别注意要用字典!!!
新增数据窗口
# 新增数据窗口
class AddDataDialog(QDialog):
def __init__(self, parent=None):
super(AddDataDialog, self).__init__(parent)
# 设置对话框标题和大小
self.setWindowTitle('新增数据')
self.setGeometry(200, 200, 200, 258)
# 创建标签显示 "新增数据"
self.t_add = QLabel('新增数据', self)
self.t_add.setGeometry(70, 20, 60, 16)
# 创建文本框输入注册地址
self.t_data1_input = QLineEdit(self)
self.t_data1_input.setPlaceholderText('注册地址')
self.t_data1_input.setGeometry(30, 50, 141, 31)
# 创建文本框输入用户名
self.t_data2_input = QLineEdit(self)
self.t_data2_input.setPlaceholderText('用户名')
self.t_data2_input.setGeometry(30, 90, 141, 31)
# 创建文本框输入密码
self.t_data3_input = QLineEdit(self)
self.t_data3_input.setPlaceholderText('密码')
self.t_data3_input.setGeometry(30, 130, 141, 31)
# 创建按钮 "新增数据",点击按钮触发 accept() 方法
self.t_Button = QPushButton('新增数据', self)
self.t_Button.setGeometry(40, 170, 113, 41)
self.t_Button.clicked.connect(self.accept)
def get_data(self):
# 获取输入的数据,以字典形式返回
return {
'address': self.t_data1_input.text(),
'username': self.t_data2_input.text(),
'password': self.t_data3_input.text()
}
# 修改数据窗口
class UpDataDialog(QDialog):
def __init__(self, parent=None):
super(UpDataDialog, self).__init__(parent)
self.setWindowTitle('修改数据')
self.setGeometry(200, 200, 200, 258)
self.t2_add = QLabel('修改数据', self)
self.t2_add.setGeometry(70, 20, 60, 16)
self.t2_data1_input = QLineEdit(self)
self.t2_data1_input.setPlaceholderText('注册地址')
self.t2_data1_input.setGeometry(30, 50, 141, 31)
self.t2_data2_input = QLineEdit(self)
self.t2_data2_input.setPlaceholderText('用户名')
self.t2_data2_input.setGeometry(30, 90, 141, 31)
self.t2_data3_input = QLineEdit(self)
self.t2_data3_input.setPlaceholderText('密码')
self.t2_data3_input.setGeometry(30, 130, 141, 31)
self.t2_Button = QPushButton('修改数据', self)
self.t2_Button.setGeometry(40, 170, 113, 41)
self.t2_Button.clicked.connect(self.accept)
def get_data(self):
return {
'address': self.t2_data1_input.text(),
'username': self.t2_data2_input.text(),
'password': self.t2_data3_input.text()
}
详细解释上述代码(重点内容):
def __init__(self, parent=None):
super(AddDataDialog, self).__init__(parent)
def __init__(self, parent=None):
这是一个类的构造函数,它在类的实例被创建时被调用。__init__
用于初始化对象的属性。self
表示类的实例本身。
super(AddDataDialog, self).__init__(parent)
:这一行调用了父类的构造函数。在这里,super()
是一个用于调用父类方法的内建函数。AddDataDialog
是当前类的名称,而 self
是当前类的实例。通过 super(AddDataDialog, self)
,你告诉 Python 在 AddDataDialog
类的父类中查找,并调用父类的构造函数 __init__
。
parent=None
:这是构造函数的参数,表示一个可选的父对象。在 GUI 编程中,通常用于指定新创建的窗口或对话框的父窗口。如果没有提供父对象,就使用默认值 None
。
主窗口(整个程序的核心部分!)
class ManageApp(QMainWindow):
def __init__(self):
super(ManageApp, self).__init__()
self.admin_password = "admin" # 设置管理员密码
self.logged_in = False
self.init_ui()
self.load_data()
self.init_socket() # 初始化 socket
self.receive_data() # 启动接收数据的线程
print("Current Working Directory:", os.getcwd())
def init_socket(self):
self.server_address = ('127.0.0.1', 8080)
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.bind(self.server_address)
def receive_data(self):
# 启动一个线程来接收数据
thread = threading.Thread(target=self._receive_data_thread)
thread.start()
def _receive_data_thread(self):
while True:
try:
data, address = self.socket.recvfrom(1024) # 接收数据
# 在这里处理接收到的数据,更新 UI
data_str = data.decode('utf-8')
self.update_ui_with_received_data(data_str)
except Exception as e:
print(f"Error receiving data: {e}")
def update_ui_with_received_data(self, received_data):
self.result_data_label.setText(received_data)
def init_ui(self):
self.setWindowTitle('用户管理系统')
self.setFixedSize(655, 338) # 设置窗口为固定大小
# 数据展示区域
self.data_label = QLabel('数据展示', self)
self.data_label.setGeometry(30, 10, 60, 16)
self.result_data_text = QTableWidget(self)
self.result_data_text.setGeometry(30, 40, 320, 261)
self.result_data_text.setColumnCount(3)
self.result_data_text.setHorizontalHeaderLabels(['注册地址', '用户名', '密码'])
self.result_data_text.horizontalHeader().setStretchLastSection(True)
self.result_data_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# 数据操作区域
self.data_control = QLabel('数据操作区', self)
self.data_control.setGeometry(360, 10, 71, 16)
self.search_input = QLineEdit(self)
self.search_input.setPlaceholderText('输入注册地址查询...')
self.search_input.setGeometry(370, 70, 131, 31)
self.search_button = QPushButton('查询数据', self)
self.search_button.setGeometry(510, 67, 113, 41)
self.search_button.clicked.connect(self.submit_score)
self.del_input = QLineEdit(self)
self.del_input.setPlaceholderText('输入注册地址删除...')
self.del_input.setGeometry(370, 130, 131, 31)
self.del_button = QPushButton('删除数据', self)
self.del_button.setGeometry(510, 128, 113, 41)
self.del_button.clicked.connect(self.submit_del)
self.add_button = QPushButton('新增数据', self)
self.add_button.setGeometry(365, 190, 123, 41)
self.add_button.clicked.connect(self.show_add_dialog)
self.updata_button = QPushButton('修改数据', self)
self.updata_button.setGeometry(500, 190, 123, 41)
self.updata_button.clicked.connect(self.show_updata_dialog)
self.admin_button = QPushButton('管理员登录', self)
self.admin_button.setGeometry(360, 255, 113, 41)
self.admin_button.clicked.connect(self.admin_login)
self.adminout_button = QPushButton('管理员退出', self)
self.adminout_button.setGeometry(550, 0, 100, 30)
self.adminout_button.clicked.connect(self.admin_logout)
self.admin_input = QLineEdit(self)
self.admin_input.setGeometry(490, 260, 131, 31)
self.admin_input.setPlaceholderText('输入管理员密码...')
self.admin_input.setEchoMode(QLineEdit.Password)
self.result_data_label = QLabel('信息返回值', self)
self.result_data_label.setGeometry(20, 310, 500, 15)
# 登录逻辑
def admin_login(self):
admin_login_input = self.admin_input.text()
if admin_login_input == self.admin_password:
self.result_data_label.setText("管理员登录成功")
self.logged_in = True
self.admin_input.clear()
self.load_data()
else:
self.result_data_label.setText("管理员密码错误")
# 登出逻辑
def admin_logout(self):
self.result_data_label.setText("管理员已退出登录")
self.logged_in = False
# 清除输入框的文本
self.admin_input.clear()
# 清除表格内容
self.result_data_text.setRowCount(0)
# 新增数据
def show_add_dialog(self):
if not self.logged_in:
self.result_data_label.setText("请先进行管理员登录")
return
add_dialog = AddDataDialog(self)
result = add_dialog.exec_()
if result == QDialog.Accepted:
new_data = add_dialog.get_data()
# 进行输入校验,确保三个输入框都不为空
if all(value != '' for value in new_data.values()):
# 读取原有数据
with open('data.json', 'r') as file:
existing_data = json.load(file)
# 添加新数据
existing_data.append(new_data)
# 将更新后的数据写回文件
with open('data.json', 'w') as file:
json.dump(existing_data, file, indent=2)
self.result_data_label.setText(f'New Data: {new_data}')
self.load_data()
else:
self.result_data_label.setText("请确保输入不为空")
# 更新数据
def show_updata_dialog(self):
if not self.logged_in:
self.result_data_label.setText("请先进行管理员登录")
return
updata_dialog = UpDataDialog(self)
result = updata_dialog.exec_()
if result == QDialog.Accepted:
new_data = updata_dialog.get_data()
address = new_data.get('address', '')
# 进行输入校验,确保要修改的数据存在并且输入框都不为空
if address != '' and all(value != '' for value in new_data.values()):
# 读取原有数据
with open('data.json', 'r') as file:
existing_data = json.load(file)
# 查找要修改的数据
for data in existing_data:
if data.get('address') == address:
# 修改数据
data.update(new_data)
break
else:
self.result_data_label.setText("该数据不存在")
print("该数据不存在")
# 将更新后的数据写回文件
with open('data.json', 'w') as file:
json.dump(existing_data, file, indent=2)
self.result_data_label.setText(f'New Data: {new_data}')
self.load_data()
else:
self.result_data_label.setText("请确保要修改的数据存在并且输入不为空")
# 查询数据
def submit_score(self):
if not self.logged_in:
self.result_data_label.setText("请先进行管理员登录")
return
try:
search_address = self.search_input.text()
if search_address != '':
# 读取原有数据
with open('data.json', 'r') as file:
existing_data = json.load(file)
# 查找具有相应注册地址的数据
search_result = [data for data in existing_data if data.get('address') == search_address]
if not search_result: # 如果 search_result 为空
self.result_data_label.setText("暂无查询结果")
return
self.result_data_text.setRowCount(len(search_result))
for row, data in enumerate(search_result):
self.result_data_text.setItem(row, 0, QTableWidgetItem(data.get('address')))
self.result_data_text.setItem(row, 1, QTableWidgetItem(str(data.get('username'))))
self.result_data_text.setItem(row, 2, QTableWidgetItem(data.get('password')))
else:
self.result_data_label.setText("暂无查询结果")
self.load_data()
except Exception as e:
self.result_data_label.setText("出错啦")
# 查询所有数据
def load_data(self):
if not self.logged_in:
self.result_data_label.setText("请先进行管理员登录")
return
try:
# 读取原有数据
with open('data.json', 'r') as file:
existing_data = json.load(file)
self.result_data_text.setRowCount(len(existing_data))
for row, data in enumerate(existing_data):
self.result_data_text.setItem(row, 0, QTableWidgetItem(data.get('address')))
self.result_data_text.setItem(row, 1, QTableWidgetItem(str(data.get('username'))))
self.result_data_text.setItem(row, 2, QTableWidgetItem(data.get('password')))
except FileNotFoundError:
print("File 'data.json' not found.")
self.result_data_label.setText("出错啦!")
except Exception as e:
self.result_data_label.setText("出错啦!")
print('Error loading data:', e)
# 删除数据
def submit_del(self):
if not self.logged_in:
self.result_data_label.setText("请先进行管理员登录")
return
address_to_delete = self.del_input.text()
# 读取原有数据
with open('data.json', 'r') as file:
existing_data = json.load(file)
# 查找并删除对应地址的数据
existing_data = [data for data in existing_data if data.get('address') != address_to_delete]
# 将更新后的数据写回文件
with open('data.json', 'w') as file:
json.dump(existing_data, file, indent=2)
self.result_data_label.setText(f'Data with address "{address_to_delete}" deleted.')
self.load_data()
代码详解(划重点!!!)
def init_socket(self):
self.server_address = ('127.0.0.1', 8080)
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.bind(self.server_address)
这个方法初始化一个UDP socket,以便后续进行网络通信。
self.server_address = ('127.0.0.1', 8080)
: 将服务器的IP地址和端口号设置为 ('127.0.0.1', 8080)。这个IP地址是本地回环地址,端口号是 8080。这是一个常见的设置,用于在本地进行测试。
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
: 创建一个UDP socket。socket.AF_INET
表示使用IPv4地址,socket.SOCK_DGRAM
表示这是一个数据报式socket,即UDP socket。
self.socket.bind(self.server_address)
: 将创建的socket绑定到指定的服务器地址。这是为了确保socket监听指定的IP地址和端口号,以便接收从该地址发送过来的数据。
def receive_data(self):
# 启动一个线程来接收数据
thread = threading.Thread(target=self._receive_data_thread)
thread.start()
这个方法的主要目的是启动一个线程,用于在后台异步地接收数据。
thread = threading.Thread(target=self._receive_data_thread)
: 这行代码创建了一个线程对象 thread
,并指定了该线程的目标函数(target)为 self._receive_data_thread
。这里使用了多线程的概念,允许程序同时执行多个任务。
thread.start()
: 这一行代码启动了线程。一旦调用了 start()
方法,线程就会执行 self._receive_data_thread
函数中的代码。
def _receive_data_thread(self):
while True:
try:
data, address = self.socket.recvfrom(1024) # 接收数据
# 在这里处理接收到的数据,更新 UI
data_str = data.decode('utf-8')
self.update_ui_with_received_data(data_str)
except Exception as e:
print(f"Error receiving data: {e}")
它通常被用作一个线程的目标函数,由 receive_data
方法中的线程调用。
while True:
: 这是一个无限循环,表示线程会一直执行下去。
data, address = self.socket.recvfrom(1024)
: 在循环中,通过 self.socket.recvfrom(1024)
从网络接收数据。这个方法是阻塞的,它会一直等待,直到接收到数据为止。1024
是接收数据的缓冲区大小,可以根据实际情况调整。
data_str = data.decode('utf-8')
: 将接收到的二进制数据解码为字符串,使用 UTF-8 编码。这是因为网络传输的数据通常是以字节形式存在,而解码为字符串后更容易处理和理解。
self.update_ui_with_received_data(data_str)
: 调用类中的另一个方法 update_ui_with_received_data
,用于处理接收到的数据并更新用户界面(UI)。这个方法的实现没有在提供的代码片段中给出,但可以设想它负责将接收到的数据以某种方式呈现给用户。
except Exception as e:
: 捕获可能出现的异常。如果在接收数据的过程中发生了错误,将错误信息打印到控制台,以便进行调试和错误处理。
def update_ui_with_received_data(self, received_data):
self.result_data_label.setText(received_data)
它的主要功能是将接收到的数据更新到用户界面上
self.result_data_label.setText(received_data)
: 用于更新界面上标签文本内容。self.result_data_label
是一个 UI 元素,通过 setText
方法将 received_data
中的数据显示在该标签上。
self.result_data_label
是一个用于显示数据的标签控件,那么这一行代码的效果是,每当有新的数据接收到时,就会更新这个标签上显示的文本内容为接收到的数据。
def admin_login(self):
admin_login_input = self.admin_input.text()
if admin_login_input == self.admin_password:
self.result_data_label.setText("管理员登录成功")
self.logged_in = True
self.admin_input.clear()
self.load_data()
else:
self.result_data_label.setText("管理员密码错误")
这段代码主要是用来校验管理员登录的功能
我们在ManageApp这个类中的def _init_(self):方法中定义了
self.logged_in = False 默认的登录状态是False
self.admin_password = "admin" 设置了登录的密码(大家如果想深入去写登录的校验过程,可以将这一段进行单独的JSON文件进行查询,当查询的密码与文件中的密码一致时更改登录状态,并且把密码进行加密后存储,从文件中取密码的时候再进行解密)
只有在登录成功后,我们才对更新表格中的数据,通过调用self.load_data()来加载表格中的数据,并且通过self.admin_input.clear()方法,将输入框的密码清空,对密码进行保护!
def admin_logout(self):
self.result_data_label.setText("管理员已退出登录")
self.logged_in = False
# 清除输入框的文本
self.admin_input.clear()
# 清除表格内容
self.result_data_text.setRowCount(0)
退出登录时,就将登录状态改为false,清空输入框,清空表格里的数据!
if not self.logged_in:
self.result_data_label.setText("请先进行管理员登录")
return
通过在每个方法里面加入登录状态的校验,必须输入管理员密码进行登录后,才能使用每个功能,否则就提示用户,先登录后使用!
# 读取原有数据
with open('data.json', 'r') as file:
existing_data = json.load(file)
-
with open('data.json', 'r') as file:
打开文件 'data.json',并使用with
语句,这样可以确保在读取完数据后正确关闭文件。'r'
表示以只读模式打开文件。 -
existing_data = json.load(file)
: 使用json
模块中的load
函数,将打开的文件对象file
中的 JSON 数据加载到existing_data
变量中。这样,'data.json' 文件中的原有数据就被读取到了一个 Python 数据结构中。
# 将更新后的数据写回文件
with open('data.json', 'w') as file:
json.dump(existing_data, file, indent=2)
self.result_data_label.setText(f'New Data: {new_data}')
-
with open('data.json', 'w') as file:
: 打开文件 'data.json' 以进行写入操作,使用with
语句确保在写入完成后正确关闭文件。'w'
参数表示以写入模式打开文件。 -
json.dump(existing_data, file, indent=2)
: 使用json
模块中的dump
函数,将existing_data
中的数据写入到文件对象file
中。indent=2
是为了让写入的 JSON 数据更加可读。这一步实际上是将原有的数据覆盖掉,用更新后的数据进行替换。 -
self.result_data_label.setText(f'New Data: {new_data}')
: 更新用户界面上的标签self.result_data_label
中的文本内容,以显示新的数据。 -
# 查找具有相应注册地址的数据 search_result = [data for data in existing_data if data.get('address') == search_address] if not search_result: # 如果 search_result 为空 self.result_data_label.setText("暂无查询结果") return self.result_data_text.setRowCount(len(search_result)) for row, data in enumerate(search_result): self.result_data_text.setItem(row, 0, QTableWidgetItem(data.get('address'))) self.result_data_text.setItem(row, 1, QTableWidgetItem(str(data.get('username')))) self.result_data_text.setItem(row, 2, QTableWidgetItem(data.get('password')))
这段代码是查找具有特定注册地址的数据,并将查询结果在用户界面上显示
-
search_result = [data for data in existing_data if data.get('address') == search_address]
: 使用列表推导式,从existing_data
中筛选出所有具有与search_address
相匹配注册地址的数据,并将结果存储在search_result
列表中。 -
if not search_result:
: 如果search_result
列表为空,即没有找到匹配的数据,则执行下面的代码块。 -
return
: 结束函数的执行,因为没有查询结果,后续的显示操作也就没有必要了。 -
self.result_data_text.setRowCount(len(search_result))
: 设置用户界面上的表格self.result_data_text
的行数为查询结果的长度,以确保表格有足够的行数来显示所有查询结果。 -
使用循环遍历
search_result
,并将每条数据的地址、用户名和密码分别设置到表格的相应位置: -
for row, data in enumerate(search_result): self.result_data_text.setItem(row, 0, QTableWidgetItem(data.get('address'))) self.result_data_text.setItem(row, 1, QTableWidgetItem(str(data.get('username')))) self.result_data_text.setItem(row, 2, QTableWidgetItem(data.get('password')))
self.result_data_text
是一个表格控件,setItem
方法用于设置表格中的每个单元格的内容。行号row
表示表格中的行索引,列号 0、1、2 分别表示地址、用户名和密码所在的列。 -
if __name__ == '__main__': app = QApplication(sys.argv) score_app = ManageApp() score_app.show() sys.exit(app.exec_())
-
if __name__ == '__main__':
: 这是一个条件语句,用于检查脚本是否被直接执行而不是被导入为一个模块。当脚本被直接执行时,__name__
的值会是'__main__'
。 -
app = QApplication(sys.argv)
: 创建一个Qt应用程序实例。QApplication
是Qt库提供的用于管理应用程序的类,sys.argv
是命令行参数的列表,用于传递一些启动参数给应用程序。 -
score_app = ManageApp()
: 创建一个ManageApp
类的实例 -
score_app.show()
: 显示主窗口或应用程序的主要界面。 -
sys.exit(app.exec_())
: 启动应用程序的主事件循环。app.exec_()
开始 Qt 的事件循环,并阻塞程序直到窗口被关闭。sys.exit
确保在退出应用程序时返回正确的退出码。 -
(四)主窗口完整代码:
-
(可直接复制运行!)
-
import socket import sys import json import os import threading from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QPushButton, QDialog, QTableWidget, QTableWidgetItem class AddDataDialog(QDialog): def __init__(self, parent=None): super(AddDataDialog, self).__init__(parent) self.setWindowTitle('新增数据') self.setGeometry(200, 200, 200, 258) self.t_add = QLabel('新增数据', self) self.t_add.setGeometry(70, 20, 60, 16) self.t_data1_input = QLineEdit(self) self.t_data1_input.setPlaceholderText('注册地址') self.t_data1_input.setGeometry(30, 50, 141, 31) self.t_data2_input = QLineEdit(self) self.t_data2_input.setPlaceholderText('用户名') self.t_data2_input.setGeometry(30, 90, 141, 31) self.t_data3_input = QLineEdit(self) self.t_data3_input.setPlaceholderText('密码') self.t_data3_input.setGeometry(30, 130, 141, 31) self.t_Button = QPushButton('新增数据', self) self.t_Button.setGeometry(40, 170, 113, 41) self.t_Button.clicked.connect(self.accept) def get_data(self): return { 'address': self.t_data1_input.text(), 'username': self.t_data2_input.text(), 'password': self.t_data3_input.text() } class UpDataDialog(QDialog): def __init__(self, parent=None): super(UpDataDialog, self).__init__(parent) self.setWindowTitle('修改数据') self.setGeometry(200, 200, 200, 258) self.t2_add = QLabel('修改数据', self) self.t2_add.setGeometry(70, 20, 60, 16) self.t2_data1_input = QLineEdit(self) self.t2_data1_input.setPlaceholderText('注册地址') self.t2_data1_input.setGeometry(30, 50, 141, 31) self.t2_data2_input = QLineEdit(self) self.t2_data2_input.setPlaceholderText('用户名') self.t2_data2_input.setGeometry(30, 90, 141, 31) self.t2_data3_input = QLineEdit(self) self.t2_data3_input.setPlaceholderText('密码') self.t2_data3_input.setGeometry(30, 130, 141, 31) self.t2_Button = QPushButton('修改数据', self) self.t2_Button.setGeometry(40, 170, 113, 41) self.t2_Button.clicked.connect(self.accept) def get_data(self): return { 'address': self.t2_data1_input.text(), 'username': self.t2_data2_input.text(), 'password': self.t2_data3_input.text() } class ManageApp(QMainWindow): def __init__(self): super(ManageApp, self).__init__() self.admin_password = "admin" # 设置管理员密码 self.logged_in = False self.init_ui() self.load_data() self.init_socket() # 初始化 socket self.receive_data() # 启动接收数据的线程 print("Current Working Directory:", os.getcwd()) def init_socket(self): self.server_address = ('127.0.0.1', 8080) self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.bind(self.server_address) def receive_data(self): # 启动一个线程来接收数据 thread = threading.Thread(target=self._receive_data_thread) thread.start() def _receive_data_thread(self): while True: try: data, address = self.socket.recvfrom(1024) # 接收数据 # 在这里处理接收到的数据,更新 UI data_str = data.decode('utf-8') self.update_ui_with_received_data(data_str) except Exception as e: print(f"Error receiving data: {e}") def update_ui_with_received_data(self, received_data): self.result_data_label.setText(received_data) def init_ui(self): self.setWindowTitle('用户管理系统') self.setFixedSize(655, 338) # 设置窗口为固定大小 # 数据展示区域 self.data_label = QLabel('数据展示', self) self.data_label.setGeometry(30, 10, 60, 16) self.result_data_text = QTableWidget(self) self.result_data_text.setGeometry(30, 40, 320, 261) self.result_data_text.setColumnCount(3) self.result_data_text.setHorizontalHeaderLabels(['注册地址', '用户名', '密码']) self.result_data_text.horizontalHeader().setStretchLastSection(True) self.result_data_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 数据操作区域 self.data_control = QLabel('数据操作区', self) self.data_control.setGeometry(360, 10, 71, 16) self.search_input = QLineEdit(self) self.search_input.setPlaceholderText('输入注册地址查询...') self.search_input.setGeometry(370, 70, 131, 31) self.search_button = QPushButton('查询数据', self) self.search_button.setGeometry(510, 67, 113, 41) self.search_button.clicked.connect(self.submit_score) self.del_input = QLineEdit(self) self.del_input.setPlaceholderText('输入注册地址删除...') self.del_input.setGeometry(370, 130, 131, 31) self.del_button = QPushButton('删除数据', self) self.del_button.setGeometry(510, 128, 113, 41) self.del_button.clicked.connect(self.submit_del) self.add_button = QPushButton('新增数据', self) self.add_button.setGeometry(365, 190, 123, 41) self.add_button.clicked.connect(self.show_add_dialog) self.updata_button = QPushButton('修改数据', self) self.updata_button.setGeometry(500, 190, 123, 41) self.updata_button.clicked.connect(self.show_updata_dialog) self.admin_button = QPushButton('管理员登录', self) self.admin_button.setGeometry(360, 255, 113, 41) self.admin_button.clicked.connect(self.admin_login) self.adminout_button = QPushButton('管理员退出', self) self.adminout_button.setGeometry(550, 0, 100, 30) self.adminout_button.clicked.connect(self.admin_logout) self.admin_input = QLineEdit(self) self.admin_input.setGeometry(490, 260, 131, 31) self.admin_input.setPlaceholderText('输入管理员密码...') self.admin_input.setEchoMode(QLineEdit.Password) self.result_data_label = QLabel('信息返回值', self) self.result_data_label.setGeometry(20, 310, 500, 15) # 登录逻辑 def admin_login(self): admin_login_input = self.admin_input.text() if admin_login_input == self.admin_password: self.result_data_label.setText("管理员登录成功") self.logged_in = True self.admin_input.clear() self.load_data() else: self.result_data_label.setText("管理员密码错误") # 登出逻辑 def admin_logout(self): self.result_data_label.setText("管理员已退出登录") self.logged_in = False # 清除输入框的文本 self.admin_input.clear() # 清除表格内容 self.result_data_text.setRowCount(0) # 新增数据 def show_add_dialog(self): if not self.logged_in: self.result_data_label.setText("请先进行管理员登录") return add_dialog = AddDataDialog(self) result = add_dialog.exec_() if result == QDialog.Accepted: new_data = add_dialog.get_data() # 进行输入校验,确保三个输入框都不为空 if all(value != '' for value in new_data.values()): # 读取原有数据 with open('data.json', 'r') as file: existing_data = json.load(file) # 添加新数据 existing_data.append(new_data) # 将更新后的数据写回文件 with open('data.json', 'w') as file: json.dump(existing_data, file, indent=2) self.result_data_label.setText(f'New Data: {new_data}') self.load_data() else: self.result_data_label.setText("请确保输入不为空") # 更新数据 def show_updata_dialog(self): if not self.logged_in: self.result_data_label.setText("请先进行管理员登录") return updata_dialog = UpDataDialog(self) result = updata_dialog.exec_() if result == QDialog.Accepted: new_data = updata_dialog.get_data() address = new_data.get('address', '') # 进行输入校验,确保要修改的数据存在并且输入框都不为空 if address != '' and all(value != '' for value in new_data.values()): # 读取原有数据 with open('data.json', 'r') as file: existing_data = json.load(file) # 查找要修改的数据 for data in existing_data: if data.get('address') == address: # 修改数据 data.update(new_data) break else: self.result_data_label.setText("该数据不存在") print("该数据不存在") # 将更新后的数据写回文件 with open('data.json', 'w') as file: json.dump(existing_data, file, indent=2) self.result_data_label.setText(f'New Data: {new_data}') self.load_data() else: self.result_data_label.setText("请确保要修改的数据存在并且输入不为空") # 查询数据 def submit_score(self): if not self.logged_in: self.result_data_label.setText("请先进行管理员登录") return try: search_address = self.search_input.text() if search_address != '': # 读取原有数据 with open('data.json', 'r') as file: existing_data = json.load(file) # 查找具有相应注册地址的数据 search_result = [data for data in existing_data if data.get('address') == search_address] if not search_result: # 如果 search_result 为空 self.result_data_label.setText("暂无查询结果") return self.result_data_text.setRowCount(len(search_result)) for row, data in enumerate(search_result): self.result_data_text.setItem(row, 0, QTableWidgetItem(data.get('address'))) self.result_data_text.setItem(row, 1, QTableWidgetItem(str(data.get('username')))) self.result_data_text.setItem(row, 2, QTableWidgetItem(data.get('password'))) else: self.result_data_label.setText("暂无查询结果") self.load_data() except Exception as e: self.result_data_label.setText("出错啦") # 查询所有数据 def load_data(self): if not self.logged_in: self.result_data_label.setText("请先进行管理员登录") return try: # 读取原有数据 with open('data.json', 'r') as file: existing_data = json.load(file) self.result_data_text.setRowCount(len(existing_data)) for row, data in enumerate(existing_data): self.result_data_text.setItem(row, 0, QTableWidgetItem(data.get('address'))) self.result_data_text.setItem(row, 1, QTableWidgetItem(str(data.get('username')))) self.result_data_text.setItem(row, 2, QTableWidgetItem(data.get('password'))) except FileNotFoundError: print("File 'data.json' not found.") self.result_data_label.setText("出错啦!") except Exception as e: self.result_data_label.setText("出错啦!") print('Error loading data:', e) # 删除数据 def submit_del(self): if not self.logged_in: self.result_data_label.setText("请先进行管理员登录") return address_to_delete = self.del_input.text() # 读取原有数据 with open('data.json', 'r') as file: existing_data = json.load(file) # 查找并删除对应地址的数据 existing_data = [data for data in existing_data if data.get('address') != address_to_delete] # 将更新后的数据写回文件 with open('data.json', 'w') as file: json.dump(existing_data, file, indent=2) self.result_data_label.setText(f'Data with address "{address_to_delete}" deleted.') self.load_data() if __name__ == '__main__': app = QApplication(sys.argv) score_app = ManageApp() score_app.show() sys.exit(app.exec_())
(五)客户端程序
import json
import sys
import socket
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import QApplication, QDialog, QLabel, QLineEdit, QPushButton, QVBoxLayout, QMainWindow, \
QTableWidget, QTableWidgetItem, QMessageBox
# --------数据展示类-----------
class UserDisplay(QMainWindow):
def __init__(self):
super(UserDisplay, self).__init__()
self.setWindowTitle('用户端数据展示')
self.setFixedSize(390, 400)
self.init_ui()
# ------------------------------------------------------
# ---------数据展示界面ui----------
def init_ui(self):
self.data_label = QLabel('数据展示', self)
self.data_label.setGeometry(30, 10, 60, 16)
self.result_data_text = QTableWidget(self)
self.result_data_text.setGeometry(30, 40, 320, 261)
self.result_data_text.setColumnCount(3)
self.result_data_text.setHorizontalHeaderLabels(['注册地址', '用户名', '密码'])
self.result_data_text.horizontalHeader().setStretchLastSection(True)
self.result_data_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.load_data_button = QPushButton('加载数据', self)
self.load_data_button.setGeometry(30, 310, 150, 30)
self.load_data_button.clicked.connect(self.load_data)
self.logout_button = QPushButton('LOGOUT', self)
self.logout_button.setGeometry(170, 310, 100, 30)
self.logout_button.clicked.connect(self.logout)
self.result_data_label = QLabel('系统操作提示', self)
self.result_data_label.setGeometry(20, 370, 200, 20)
# -------------------------------------------------------------
# -----------数据加载-----------
def load_data(self):
try:
# 读取原有数据
with open('data.json', 'r') as file:
existing_data = json.load(file)
self.result_data_text.setRowCount(len(existing_data))
for row, data in enumerate(existing_data):
self.result_data_text.setItem(row, 0, QTableWidgetItem(data.get('address')))
self.result_data_text.setItem(row, 1, QTableWidgetItem(str(data.get('username'))))
self.result_data_text.setItem(row, 2, QTableWidgetItem(data.get('password')))
self.result_data_label.setText("JSON数据加载完成!")
except FileNotFoundError:
print("File 'data.json' not found.")
self.result_data_label.setText("出错啦!")
except Exception as e:
self.result_data_label.setText("出错啦!")
print('Error loading data:', e)
def logout(self):
# 执行退出时的清理工作
# 例如:关闭当前界面,显示登录界面
self.close()
login_and_display.show()
# 在登录成功后发送UDP消息到服务器
send_udp_message("User logged out... ")
# ----------登录界面类-------------
class LoginAndDisplay(QDialog):
login_successful = pyqtSignal()
def __init__(self, parent=None):
super(LoginAndDisplay, self).__init__(parent)
self.logged_in = False
self.user_username = "user" # 设置用户账号
self.user_password = "pass" # 设置用户密码
self.setWindowTitle('登录')
self.setGeometry(200, 200, 300, 150)
self.label_username = QLabel('账号:', self)
self.input_username = QLineEdit(self)
self.label_password = QLabel('密码:', self)
self.input_password = QLineEdit(self)
self.input_password.setEchoMode(QLineEdit.Password)
self.btn_login = QPushButton('登录', self)
self.btn_login.clicked.connect(self.login)
# -------账号密码免输入--------
self.input_username.setText(self.user_username)
self.input_password.setText(self.user_password)
layout = QVBoxLayout(self)
layout.addWidget(self.label_username)
layout.addWidget(self.input_username)
layout.addWidget(self.label_password)
layout.addWidget(self.input_password)
layout.addWidget(self.btn_login)
# ------------------------------------------------------
# ------------登录功能-------------
def login(self):
username_input = self.input_username.text()
password_input = self.input_password.text()
if username_input == self.user_username and password_input == self.user_password:
self.logged_in = True
self.login_successful.emit() # 发出登录成功的信号
else:
QMessageBox.warning(self, "登录失败", "账号或密码错误!", QMessageBox.Ok)
# -------------UDP通讯设置----------------
def send_udp_message(message):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
server_address = ('127.0.0.1', 8080) # 服务器地址和端口
s.sendto(message.encode('utf-8'), server_address)
if __name__ == '__main__':
app = QApplication(sys.argv)
login_and_display = LoginAndDisplay()
user_display = UserDisplay()
def show_user_display():
login_and_display.close()
user_display.show()
# 在登录成功后发送UDP消息到服务器
send_udp_message("User logged in... ")
login_and_display.login_successful.connect(show_user_display)
login_and_display.exec_()
sys.exit(app.exec_())
-
UserDisplay 类:
- 该类继承自
QMainWindow
,用于展示用户数据。 init_ui
方法初始化用户界面,包括数据展示的表格、加载数据的按钮、注销按钮等。load_data
方法用于从 'data.json' 文件中加载数据,并在表格中展示。logout
方法用于执行用户注销的操作,关闭当前界面,并显示登录界面。
- 该类继承自
-
LoginAndDisplay 类:
- 该类继承自
QDialog
,用于实现登录功能。 login_successful
是一个自定义的信号,当登录成功时发出。login
方法用于验证用户输入的用户名和密码,如果匹配成功则发出登录成功的信号。
- 该类继承自
-
UDP通讯设置:
send_udp_message
函数用于通过UDP发送消息到指定的服务器地址和端口。
-
if __name__ == '__main__':
部分:- 创建了一个
QApplication
实例。 - 创建了
LoginAndDisplay
和UserDisplay
的对象。 - 通过连接
login_successful
信号和show_user_display
槽函数,实现了在登录成功后关闭登录界面,显示用户数据展示界面,并发送UDP消息到服务器的功能。 - 最后通过
sys.exit(app.exec_())
启动了应用程序的主事件循环。
- 创建了一个
客户端可以复制多个!!!可以同时运行!!!在主函数里面已经开启了多线程,不会阻塞主线程的正常使用!
(六)JSON文件:
[
{
"address": "rdi@pf.io",
"username": "\u79b9\u5b97",
"password": "pwd446"
},
{
"address": "r2ulhht8@hrtnfsic.io",
"username": "\u6a0a\u5b50",
"password": "pwd304"
},
{
"address": "evhqx@orv.io",
"username": "\u6743\u677e\u5357",
"password": "pwd176"
},
{
"address": "qtq4qybp4j@swtfdk.im",
"username": "\u59ec\u8fdc",
"password": "pwd340"
},
{
"address": "oiax41g6j@n4mi.im",
"username": "\u8a00\u671d",
"password": "pwd030"
},
{
"address": "vslp7@vc.me",
"username": "\u53f2\u987a",
"password": "pwd983"
},
{
"address": "yf8ydn@hfdcq.im",
"username": "\u51b7\u5a23\u806a",
"password": "pwd501"
},
{
"address": "5dj3qrym6@g3.cn",
"username": "\u9ea6\u632f",
"password": "pwd512"
},
{
"address": "f2w3i0@dn2.cc",
"username": "\u5b8b\u671d",
"password": "pwd311"
},
{
"address": "uctg9@kpjg.cn",
"username": "123",
"password": "123"
}
]
本期的PyQt的程序案例就分享到这了!后期会更新更多PyQt的程序!感谢支持!