diff --git a/QT5_Project/KD_ZM_8_XCF/APPWindow.py b/QT5_Project/KD_ZM_8_XCF/APPWindow.py index b9612b0..210c8c1 100644 --- a/QT5_Project/KD_ZM_8_XCF/APPWindow.py +++ b/QT5_Project/KD_ZM_8_XCF/APPWindow.py @@ -1,7 +1,7 @@ # This Python file uses the following encoding: utf-8 import sys import time -from PyQt5.QtWidgets import QFileDialog +from PyQt5.QtWidgets import QFileDialog,QHeaderView from PyQt5.QtWidgets import QApplication, QMainWindow, QStackedWidget, QWidget, QLayout, QLabel, QLineEdit, QPushButton, QMessageBox, QShortcut, QDialog,QTextEdit from PyQt5 import uic from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QRunnable, QMutex, QTimer, QEvent @@ -455,8 +455,18 @@ class QFaceCameraViewPage(PageTemplate): self.face_verify_result = None # 用于存储人脸验证结果 self.auto_connect_serial() - # 串口管理 + + self.refresh() + header = self.table.horizontalHeader() + # 用户ID列固定宽度 + self.table.setColumnWidth(0, 100) + # 用户名列自适应内容 + header.setSectionResizeMode(1, QHeaderView.ResizeToContents) + # 注册时间列填充剩余空间 + header.setSectionResizeMode(2, QHeaderView.Stretch) + + # 串口管理 def auto_connect_serial(self): ports = [p.device for p in serial.tools.list_ports.comports()] if not ports: @@ -540,15 +550,15 @@ class QFaceCameraViewPage(PageTemplate): if msg_id == MID_REPLY: info = parse_reply(data) self.log(f"[REPLY] {info}") - if info.get("mid") in (CMD_ENROLL, CMD_ENROLL_ITG) and info.get("result") == 0x00: - user_id = info.get("user_id") - # 如果用户名为空,使用用户ID作为用户名 - user_name = self.last_enroll_name if self.last_enroll_name else str(user_id) - if user_id: # 只需检查user_id存在即可,因为user_name已确保有值 - if save_user(user_id, user_name): - self.log(f"[INFO] 用户 {user_name}(ID={user_id}) 已保存") - else: - QMessageBox.warning(self, "提示", f"用户ID {user_id} 已存在!") + if info.get("mid") == CMD_ENROLL_ITG: + if info.get("result") == 0x00: + user_id = info.get("user_id") + if user_id: + if save_user(user_id): + self.log(f"[INFO] 用户ID={user_id} 已保存") + else: + inform_box : DialogInform = DialogInform() + inform_box.information("提示", f"用户ID {user_id} 已存在!") elif info.get("mid") == CMD_VERIFY : if info.get("result") == 0x00: @@ -762,7 +772,9 @@ class APPWindow(QMainWindow): # self.setGeometry(0, 0, 1024, 768) # self.stack.setGeometry(0, 0, 1024, 768) - + QFaceCameraViewPage.video_worker = None + QFaceCameraViewPage.ser = None + self.showFullScreen() self.menu_sequence_list : PageTemplate = [] @@ -875,10 +887,10 @@ class APPWindow(QMainWindow): for camera_thread in self.camera_thread_list: thread_to_stop : CameraThread = camera_thread - if self.video_worker and self.video_worker.isRunning(): - self.video_worker.stop() - self.video_worker.wait(300) - if self.ser and getattr(self.ser,"is_open",False): + if QFaceCameraViewPage.video_worker and QFaceCameraViewPage.video_worker.isRunning(): + QFaceCameraViewPage.video_worker.stop() + QFaceCameraViewPage.video_worker.wait(300) + if QFaceCameraViewPage.ser and getattr(QFaceCameraViewPage.ser,"is_open",False): self.close_serial() if thread_to_stop != None : thread_to_stop.close() diff --git a/QT5_Project/KD_ZM_8_XCF/UI/P03SwitchAction.ui b/QT5_Project/KD_ZM_8_XCF/UI/P03SwitchAction.ui index 7a6ed76..40ea548 100644 --- a/QT5_Project/KD_ZM_8_XCF/UI/P03SwitchAction.ui +++ b/QT5_Project/KD_ZM_8_XCF/UI/P03SwitchAction.ui @@ -93,7 +93,7 @@ color: rgb(207, 0, 13); - SwitchOn,Index=2,Action=CmdExecute, SelectImag=SR_001.png, groupstart=6 + SwitchOn,Index=2,Action=CmdExecute, SelectImag=SR_001.png, password,groupstart=6 @@ -115,7 +115,7 @@ color: rgb(207, 0, 13); - SwitchOff,Index=3,Action=CmdExecute, SelectImag=SR_001.png + SwitchOff,Index=3,Action=CmdExecute, password,SelectImag=SR_001.png @@ -218,7 +218,7 @@ color: rgb(207, 0, 13); - SwitchOff,Index=4,Action=CmdExecute, SelectImag=SR_001.png + SwitchOff,Index=4,Action=CmdExecute, password,SelectImag=SR_001.png diff --git a/QT5_Project/KD_ZM_8_XCF/UI/P05_01_FaceCameraView.ui b/QT5_Project/KD_ZM_8_XCF/UI/P05_01_FaceCameraView.ui index 5508ad7..9d929f7 100644 --- a/QT5_Project/KD_ZM_8_XCF/UI/P05_01_FaceCameraView.ui +++ b/QT5_Project/KD_ZM_8_XCF/UI/P05_01_FaceCameraView.ui @@ -334,6 +334,22 @@ color: rgb(177, 229, 252); 人脸框 + + + + 10 + 240 + 80 + 23 + + + + Index=0, Action=DeleteUser,SelectImag=IMxx_00F.png + + + 删除用户 + + @@ -351,11 +367,40 @@ color: rgb(177, 229, 252); ../image/FaceCameraView.png + + + + 1080 + 520 + 821 + 371 + + + + background-color: rgb(255, 255, 255); + + + + 用户ID + + + + + 用户名 + + + + + 注册时间 + + + P05_01BG groupBox_sys group_log group_video group_cmd + table diff --git a/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py b/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py index 8701006..8771e71 100644 --- a/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py +++ b/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py @@ -2,6 +2,7 @@ import sys import os import time +import csv import inspect import cv2 import json @@ -10,7 +11,7 @@ from PyQt5 import uic from PyQt5.QtGui import QImage, QPixmap, QColor,QBrush, QKeySequence, QIcon, QPalette from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QRunnable, QMutex, QTimer, QEvent, QSize, QDateTime from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QFrame, QWidget, QLayout, QLabel -from PyQt5.QtWidgets import QLineEdit, QPushButton, QMessageBox, QShortcut, QDialog +from PyQt5.QtWidgets import QLineEdit, QPushButton, QMessageBox, QShortcut, QDialog,QTableWidgetItem sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from Shared_CODE.CameraThread import CameraThread import menu_utils as utils @@ -22,7 +23,7 @@ from Shared_CODE.DialogModifyText import DialogModifyText from Shared_CODE.DialogInform import DialogInform from QT5_Project.Shared_CODE.FaceRecognitionProtocol import parse_reply from QT5_Project.Shared_CODE.DialogFaceEnrollItgSingle import EnrollItgSingleDialog -from QT5_Project.Shared_CODE.DialogFaceUserManage import UserManageDialog +from QT5_Project.Shared_CODE.DialogFaceUserManage import UserManageDialog, load_users, save_user, save_users_list, CSV_FILE from Shared_CODE.get_tip_prop import * from print_color import * @@ -801,6 +802,8 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): print("密码认证成功") elif choice == "face": + # 初始化锁标志 + self._face_verify_locked = True # 开始验证时锁定 # 人脸认证异步处理 face_frame = self.parent_window.P05_01_FaceCameraView face_frame.face_verify_result = None # 重置标志位 @@ -809,6 +812,12 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): timeout = system_parameter().get_verify_timeout() start_time = time.time() + # 停止并删除旧定时器 + if hasattr(self, "_face_verify_timer"): + if self._face_verify_timer.isActive(): + self._face_verify_timer.stop() + self._face_verify_timer.deleteLater() + # 创建定时器轮询标志位 self._face_verify_timer = QTimer(self) self._face_verify_timer.setInterval(50) # 每50ms检查一次 @@ -817,6 +826,7 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): nonlocal input if face_frame.face_verify_result is not None: self._face_verify_timer.stop() + self._face_verify_locked = False # 解锁 if face_frame.face_verify_result: input = True inform_box = DialogInform() @@ -828,10 +838,15 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): inform_box.information("提示", "人脸认证失败") elif time.time() - start_time > timeout: self._face_verify_timer.stop() + self._face_verify_locked = False # 解锁 input = False inform_box = DialogInform() inform_box.information("提示", "人脸认证超时") - + # 解绑旧槽,绑定新槽 + try: + self._face_verify_timer.timeout.disconnect() + except Exception: + pass self._face_verify_timer.timeout.connect(check_face_result) self._face_verify_timer.start() return # 异步处理,直接返回 @@ -875,6 +890,11 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): elif "FaceBox" in action_str: self.toggle_face_box() + + elif "DeleteUser" in action_str: + self.delete_user_by_id() + elif "UserCount" in action_str: + self.query_user_count() self.virtual_widget_action_process(select_object, action_str) @@ -919,6 +939,12 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): elif "FaceBox" in action_str: self.toggle_face_box() + elif "DeleteUser" in action_str: + self.delete_user_by_id() + + elif "UserCount" in action_str: + self.query_user_count() + self.virtual_widget_action_process(select_object, action_str) def search_menu_match_object(self, object) : @@ -936,7 +962,8 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): def do_verify(self): pd_val = 0 timeout_val = system_parameter().get_verify_timeout() - self.send(build_verify(pd_val, timeout_val)) + face_send = self.parent_window.P05_01_FaceCameraView + face_send.send(build_verify(pd_val, timeout_val)) def do_enroll_itg_single(self): @@ -1019,6 +1046,69 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): else: self.log("[WARN] 串口未连接,无法控制人脸框") + def delete_user_by_id(self, CSV_FILE = CSV_FILE): + users = load_users() + + # 弹出对话框选择用户ID + dialog_modify_text = DialogModifyValue(self) + caption_str = "选择用户ID删除" + dialog_modify_text.update_modify_info("", 0, caption_str) + + if dialog_modify_text.exec() != QDialog.Accepted: + return + + # 获取用户输入的ID并转换为整数 + try: + user_id = int(dialog_modify_text.value) + except ValueError: + DialogInform(self).information("提示", "请输入有效数字ID") + return + + # 查找用户 + user = next((u for u in users if u["user_id"] == user_id), None) + if not user: + DialogInform(self).information("提示", "用户不存在") + return + + try: + # 1️⃣ 下发删除命令 + self.send(build_delete_user(user_id)) + + # 2️⃣ 删除 CSV 文件对应行 + if os.path.exists(CSV_FILE): + with open(CSV_FILE, "r", encoding="utf-8", newline="") as f: + reader = csv.DictReader(f) + fieldnames = reader.fieldnames + new_rows = [row for row in reader if str(row.get("user_id")) != str(user_id)] + + with open(CSV_FILE, "w", encoding="utf-8", newline="") as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(new_rows) + + except Exception as e: + DialogInform(self).information("提示", f"删除失败: {e}") + return + + # 3️⃣ 更新内存用户列表 + users = [u for u in users if u["user_id"] != user_id] + save_users_list(users) + + # 4️⃣ 提示删除成功 + DialogInform(self).information("提示", f"用户 {user.get('user_name', '')} (ID={user_id}) 已删除") + + + def query_user_count(self): + self.send(build_get_all_userid()) + + def refresh(self): + users = load_users() + self.table.setRowCount(len(users)) + for r, u in enumerate(users): + self.table.setItem(r, 0, QTableWidgetItem(str(u.get("user_id", "")))) + self.table.setItem(r, 1, QTableWidgetItem(u.get("user_name", ""))) + self.table.setItem(r, 2, QTableWidgetItem(u.get("created_at", ""))) + ################################################################################ #刷新屏幕上的系统信息, 例如时间日期之类 diff --git a/QT5_Project/Shared_CODE/DialogFaceUserManage.py b/QT5_Project/Shared_CODE/DialogFaceUserManage.py index 506bcd9..963d8e3 100644 --- a/QT5_Project/Shared_CODE/DialogFaceUserManage.py +++ b/QT5_Project/Shared_CODE/DialogFaceUserManage.py @@ -57,24 +57,23 @@ def save_users_list(users): w.writerow([u.get("user_id", ""), u.get("user_name", ""), u.get("created_at", "")]) -def save_user(user_id: int, user_name: str) -> bool: +def save_user(user_id: int) -> bool: users = load_users() for u in users: if str(u["user_id"]) == str(user_id): return False created_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - users.append({"user_id": str(user_id), "user_name": user_name, "created_at": created_at}) + users.append({"user_id": str(user_id), "user_name": str(user_id), "created_at": created_at}) save_users_list(users) return True + class UserManageDialog(QDialog): def __init__(self, parent=None, send_func=None): super().__init__(parent) self.send_func = send_func uic.loadUi(users_ui_file_path, self) - self.btn_delete.clicked.connect(self.delete_selected) - self.btn_refresh.clicked.connect(self.refresh) self.btn_get.clicked.connect(self.get_from_device) self.btn_del_all.clicked.connect(self.delete_all_users) self.refresh() @@ -105,31 +104,15 @@ class UserManageDialog(QDialog): QShortcut(QKeySequence(Qt.Key_Home), self, activated=self.key_escape_process) QShortcut(QKeySequence(Qt.Key_Return), self, activated=self.key_enter_process) # 普通回车 - + def refresh(self): - users = load_users() - self.table.setRowCount(len(users)) - for r, u in enumerate(users): - self.table.setItem(r, 0, QTableWidgetItem(str(u.get("user_id", "")))) - self.table.setItem(r, 1, QTableWidgetItem(u.get("user_name", ""))) - self.table.setItem(r, 2, QTableWidgetItem(u.get("created_at", ""))) + users = load_users() + self.table.setRowCount(len(users)) + for r, u in enumerate(users): + self.table.setItem(r, 0, QTableWidgetItem(str(u.get("user_id", "")))) + self.table.setItem(r, 1, QTableWidgetItem(u.get("user_name", ""))) + self.table.setItem(r, 2, QTableWidgetItem(u.get("created_at", ""))) - def delete_selected(self): - row = self.table.currentRow() - if row < 0: - QMessageBox.warning(self, "提示", "删除选择用户") - return - uid = self.table.item(row, 0).text() - uname = self.table.item(row, 1).text() - try: - uid_int = int(uid) - self.send_func(build_delete_user(uid_int)) - except: - pass - users = [u for u in load_users() if str(u["user_id"]) != uid] - save_users_list(users) - self.refresh() - QMessageBox.information(self, "提示", f"用户 {uname}(ID={uid}) 已删除") def get_from_device(self): self.send_func(build_get_all_userid()) diff --git a/QT5_Project/Shared_UI/users.csv b/QT5_Project/Shared_UI/users.csv index 65407b1..aa1e8c7 100644 --- a/QT5_Project/Shared_UI/users.csv +++ b/QT5_Project/Shared_UI/users.csv @@ -1 +1,3 @@ -1,1,2025-09-13 10:06:01 +1,1,2025-09-15 10:48:28 +2,2,2025-09-15 10:56:46 +3,3,2025-09-15 11:00:04