Compare commits
2 Commits
242facce26
...
f860c5a216
| Author | SHA1 | Date | |
|---|---|---|---|
| f860c5a216 | |||
| 3d860b22fd |
122
common/msg_db_helper.py
Normal file
122
common/msg_db_helper.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import sqlite3
|
||||||
|
from datetime import datetime
|
||||||
|
from PySide6.QtCore import QMutex
|
||||||
|
|
||||||
|
"""
|
||||||
|
sqlite消息数据库管理: db/messages.db
|
||||||
|
"""
|
||||||
|
|
||||||
|
class DBHelper:
|
||||||
|
_instance = None # 单例模式
|
||||||
|
_mutex = QMutex()
|
||||||
|
|
||||||
|
def __new__(cls, db_name="db/messages.db"):
|
||||||
|
"""消息数据库连接管理器"""
|
||||||
|
cls._mutex.lock()
|
||||||
|
try:
|
||||||
|
if not cls._instance:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
cls._instance.db_name = db_name
|
||||||
|
cls._instance._init_db()
|
||||||
|
finally:
|
||||||
|
cls._mutex.unlock()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def _init_db(self):
|
||||||
|
"""创建消息表"""
|
||||||
|
conn = sqlite3.connect(self.db_name)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
# 创建消息表:id(主键)、内容、类型(1=系统消息,2=预警)、是否处理、创建时间
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
message_type INTEGER NOT NULL, -- 1: normal, 2: warning
|
||||||
|
is_processed INTEGER NOT NULL DEFAULT 0, -- 为1表示已经解决/已经处理
|
||||||
|
create_time TEXT NOT NULL, -- 创建时间
|
||||||
|
last_modified TEXT NOT NULL -- 最后修改时间(新增/更新时都会变)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def insert_message(self, content, message_type, is_processed=0):
|
||||||
|
"""插入消息: create_time和last_modified一开始都设为当前时间"""
|
||||||
|
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
conn = sqlite3.connect(self.db_name)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO messages (content, message_type, is_processed, create_time, last_modified)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
''', (content, message_type, is_processed, current_time, current_time))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def update_processed_status(self, content, message_type, is_processed=1):
|
||||||
|
"""更新is_processed状态, 同时更新last_modified为当前时间"""
|
||||||
|
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
conn = sqlite3.connect(self.db_name)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
UPDATE messages
|
||||||
|
SET is_processed = ?, last_modified = ?
|
||||||
|
WHERE content = ? AND message_type = ?
|
||||||
|
''', (is_processed, current_time, content, message_type)) # 更新last_modified
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def get_latest_messages(self, message_type, limit=16):
|
||||||
|
"""查询最新limit条消息, 包含last_modified字段"""
|
||||||
|
conn = sqlite3.connect(self.db_name)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
# 按创建时间倒序(确保取最新的消息),返回时包含last_modified
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT content, is_processed, create_time, last_modified
|
||||||
|
FROM messages
|
||||||
|
WHERE message_type = ?
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
LIMIT ?
|
||||||
|
''', (message_type, limit))
|
||||||
|
messages = cursor.fetchall() # 结果:[(content, is_processed, create_time, last_modified), ...]
|
||||||
|
conn.close()
|
||||||
|
return messages[::-1] # 反转后按时间正序排列
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
"""测试用例:向数据库插入测试消息"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
# 创建DBHelper实例(会自动创建数据库和表)
|
||||||
|
db = DBHelper()
|
||||||
|
|
||||||
|
# # 插入系统状态消息(message_type=1)
|
||||||
|
# db.insert_message(
|
||||||
|
# content="开始自动智能浇筑系统",
|
||||||
|
# message_type=1, # 系统消息
|
||||||
|
# is_processed=False
|
||||||
|
# )
|
||||||
|
# time.sleep(1)
|
||||||
|
# db.insert_message(
|
||||||
|
# content="智能浇筑系统启动成功",
|
||||||
|
# message_type=1,
|
||||||
|
# is_processed=False
|
||||||
|
# )
|
||||||
|
# time.sleep(1)
|
||||||
|
# db.insert_message(
|
||||||
|
# content="上料斗载荷达到80%",
|
||||||
|
# message_type=1, # 系统消息
|
||||||
|
# is_processed=True # 已处理
|
||||||
|
# )
|
||||||
|
|
||||||
|
# 插入预警消息(message_type=2)
|
||||||
|
db.insert_message(
|
||||||
|
content="振动频率异常",
|
||||||
|
message_type=2, # 预警消息
|
||||||
|
is_processed=False # 未处理
|
||||||
|
)
|
||||||
|
time.sleep(1)
|
||||||
|
db.insert_message(
|
||||||
|
content="料斗重量超出阈值",
|
||||||
|
message_type=2, # 预警消息
|
||||||
|
is_processed=True # 已处理
|
||||||
|
)
|
||||||
@ -1,8 +1,12 @@
|
|||||||
# controller/bottom_control_controller.py
|
# controller/bottom_control_controller.py
|
||||||
from PySide6.QtCore import Qt, QPropertyAnimation, QRect, QParallelAnimationGroup, QEasingCurve
|
from PySide6.QtCore import Qt, QPropertyAnimation, QRect, QParallelAnimationGroup, QEasingCurve, QPoint, Slot
|
||||||
from view.widgets.system_center_dialog import SystemCenterDialog
|
from view.widgets.system_center_dialog import SystemCenterDialog
|
||||||
from view.widgets.bottom_control_widget import BottomControlWidget
|
from view.widgets.bottom_control_widget import BottomControlWidget
|
||||||
from view.widgets.system_diagnostics_dialog import SystemDiagnosticsDialog
|
from view.widgets.system_diagnostics_dialog import SystemDiagnosticsDialog
|
||||||
|
from view.widgets.message_popup_widget import MessagePopupWidget
|
||||||
|
|
||||||
|
from service.msg_recorder import MessageRecorder
|
||||||
|
from service.msg_query_thread import MsgQueryThread
|
||||||
|
|
||||||
"""
|
"""
|
||||||
控制主界面底部的所有按钮, 包括系统诊断、系统中心等的行为。
|
控制主界面底部的所有按钮, 包括系统诊断、系统中心等的行为。
|
||||||
@ -13,20 +17,55 @@ class BottomControlController:
|
|||||||
def __init__(self, bottom_control_widget:BottomControlWidget, main_window):
|
def __init__(self, bottom_control_widget:BottomControlWidget, main_window):
|
||||||
self.bottom_control_widget = bottom_control_widget
|
self.bottom_control_widget = bottom_control_widget
|
||||||
self.main_window = main_window
|
self.main_window = main_window
|
||||||
self._bind_buttons()
|
|
||||||
|
|
||||||
# 系统中心弹窗
|
|
||||||
self.system_center_dialog = SystemCenterDialog(self.main_window)
|
|
||||||
self.system_center_dialog.hide() # 初始隐藏 (必须)
|
|
||||||
self._init_system_center_dialog_hide_animations()
|
|
||||||
|
|
||||||
# 系统诊断弹窗
|
# 系统诊断弹窗
|
||||||
self.system_diagnostics_dialog = SystemDiagnosticsDialog(self.main_window)
|
self.system_diagnostics_dialog = SystemDiagnosticsDialog(self.main_window)
|
||||||
self.system_diagnostics_dialog.hide()
|
|
||||||
self._init_system_diagnostics_dialog_hide_animations()
|
self._init_system_diagnostics_dialog_hide_animations()
|
||||||
|
|
||||||
|
# 系统中心弹窗
|
||||||
|
self.system_center_dialog = SystemCenterDialog(self.main_window)
|
||||||
|
self._init_system_center_dialog_hide_animations()
|
||||||
|
|
||||||
|
# ===================== 消息列表相关 ====================================
|
||||||
|
# 系统状态消息列表控件
|
||||||
|
self.status_msg_widget = MessagePopupWidget("系统状态消息", self.main_window)
|
||||||
|
|
||||||
|
# 预警消息列表控件
|
||||||
|
self.warning_msg_widget = MessagePopupWidget("预警消息", self.main_window)
|
||||||
|
|
||||||
|
# 消息数据库查询线程
|
||||||
|
self.msg_query_thread = MsgQueryThread() # 默认1秒查询一次,每次16条消息
|
||||||
|
# 连接线程信号到UI更新函数
|
||||||
|
self.msg_query_thread.new_messages.connect(self.update_message_ui, Qt.QueuedConnection)
|
||||||
|
self.msg_query_thread.start() # 启动线程
|
||||||
|
# =======================================================================
|
||||||
|
|
||||||
|
# 绑定主界面底部按钮的信号(如:系统诊断按钮等,点击触发弹窗)
|
||||||
|
self._bind_buttons()
|
||||||
|
# 绑定弹窗中按钮的信号
|
||||||
self._bind_dialog_signals()
|
self._bind_dialog_signals()
|
||||||
|
|
||||||
|
@Slot(int, list)
|
||||||
|
def update_message_ui(self, message_type, messages):
|
||||||
|
"""根据消息类型更新对应UI, message_type为1表示系统状态消息, 为2表示预警消息"""
|
||||||
|
# 清空当前消息列表(避免重复)
|
||||||
|
target_widget = self.status_msg_widget if message_type == 1 else self.warning_msg_widget
|
||||||
|
target_widget.list_widget.clear()
|
||||||
|
# target_widget.messages.clear()
|
||||||
|
|
||||||
|
# 添加新消息 (第三个为 create_time, 第四个为 last_modified)
|
||||||
|
for content, is_processed, create_time, _ in messages:
|
||||||
|
# 从create_time中提取时分秒
|
||||||
|
# time_part = create_time.split()[1] # 空格分隔之后的[1]为时分秒
|
||||||
|
# 需要添加到消息列表的消息格式为 时分秒 + 消息原始内容
|
||||||
|
# msg_list_content = f"{create_time} {content}"
|
||||||
|
target_widget.add_message(content, is_processed = bool(is_processed), msg_time = create_time)
|
||||||
|
|
||||||
|
def stop_threads(self):
|
||||||
|
"""停止当前控制器中的所有线程"""
|
||||||
|
if hasattr(self, 'msg_query_thread') and self.msg_query_thread.isRunning():
|
||||||
|
self.msg_query_thread.stop()
|
||||||
|
|
||||||
def _bind_buttons(self):
|
def _bind_buttons(self):
|
||||||
# 底部系统中心按钮 → 触发弹窗显示/隐藏
|
# 底部系统中心按钮 → 触发弹窗显示/隐藏
|
||||||
self.bottom_control_widget.center_btn.clicked.connect(self.toggle_system_center_dialog)
|
self.bottom_control_widget.center_btn.clicked.connect(self.toggle_system_center_dialog)
|
||||||
@ -34,8 +73,15 @@ class BottomControlController:
|
|||||||
# 底部系统诊断按钮 → 触发弹窗显示/隐藏
|
# 底部系统诊断按钮 → 触发弹窗显示/隐藏
|
||||||
self.bottom_control_widget.diagnosis_btn.clicked.connect(self.toggle_system_diagnostics_dialog)
|
self.bottom_control_widget.diagnosis_btn.clicked.connect(self.toggle_system_diagnostics_dialog)
|
||||||
|
|
||||||
|
# 底部系统状态消息按钮 → 触发系统状态消息列表显示/隐藏
|
||||||
|
self.bottom_control_widget.status_msg_btn.clicked.connect(self.toggle_system_status_msg_list)
|
||||||
|
|
||||||
|
# 底部预警消息列表按钮 → 触发预警消息列表显示/隐藏
|
||||||
|
self.bottom_control_widget.warning_list_btn.clicked.connect(self.toggle_system_warning_msg_list)
|
||||||
|
|
||||||
def _bind_dialog_signals(self):
|
def _bind_dialog_signals(self):
|
||||||
"""绑定弹窗按钮的信号"""
|
"""绑定弹窗按钮的信号"""
|
||||||
|
# 系统中心弹窗的按钮信号
|
||||||
self.system_center_dialog.sys_setting_clicked.connect(self.handle_sys_setting)
|
self.system_center_dialog.sys_setting_clicked.connect(self.handle_sys_setting)
|
||||||
self.system_center_dialog.data_center_clicked.connect(self.handle_data_center)
|
self.system_center_dialog.data_center_clicked.connect(self.handle_data_center)
|
||||||
self.system_center_dialog.user_center_clicked.connect(self.handle_user_center)
|
self.system_center_dialog.user_center_clicked.connect(self.handle_user_center)
|
||||||
@ -155,25 +201,12 @@ class BottomControlController:
|
|||||||
self.system_diagnostics_dialog.show()
|
self.system_diagnostics_dialog.show()
|
||||||
|
|
||||||
def _calc_system_diagnostics_dialog_position(self):
|
def _calc_system_diagnostics_dialog_position(self):
|
||||||
"""计算系统诊断弹窗位置(显示在诊断按钮上方,与中心弹窗布局一致)"""
|
"""计算系统诊断弹窗位置(显示在系统诊断按钮上方)"""
|
||||||
btn = self.bottom_control_widget.diagnosis_btn # 诊断按钮
|
btn = self.bottom_control_widget.diagnosis_btn # 诊断按钮
|
||||||
bottom_widget = self.bottom_control_widget
|
|
||||||
|
|
||||||
# 计算按钮在主窗口中的绝对位置
|
btn_pos = btn.mapToGlobal(QPoint(0, 0))
|
||||||
bottom_pos_rel_main = bottom_widget.pos() # 底部控件相对于主窗口的位置
|
dialog_x = btn_pos.x()
|
||||||
btn_pos_rel_bottom = btn.pos() # 诊断按钮相对于底部控件的位置
|
dialog_y = btn_pos.y() - self.system_diagnostics_dialog.height()
|
||||||
btn_pos_rel_main = bottom_pos_rel_main + btn_pos_rel_bottom # 诊断按钮在主窗口中的绝对位置
|
|
||||||
|
|
||||||
# 计算弹窗坐标(显示在按钮上方,水平居中对齐)
|
|
||||||
btn_width = btn.width()
|
|
||||||
dialog_size = self.system_diagnostics_dialog.size()
|
|
||||||
# 水平方向:与按钮居中对齐
|
|
||||||
# dialog_x = btn_pos_rel_main.x() + (btn_width - dialog_size.width()) // 2
|
|
||||||
# dialog_x = btn_pos_rel_main.x() + (btn_width - dialog_size.width())
|
|
||||||
dialog_x = btn_pos_rel_main.x() # 与系统诊断按钮的左边平齐
|
|
||||||
# 垂直方向:在按钮上方(与按钮保持10px间距)
|
|
||||||
# dialog_y = btn_pos_rel_main.y() - dialog_size.height() - 10
|
|
||||||
dialog_y = btn_pos_rel_main.y() - dialog_size.height()
|
|
||||||
|
|
||||||
# 设置弹窗位置(动画会基于此位置执行滑入效果)
|
# 设置弹窗位置(动画会基于此位置执行滑入效果)
|
||||||
self.system_diagnostics_dialog.move(dialog_x, dialog_y)
|
self.system_diagnostics_dialog.move(dialog_x, dialog_y)
|
||||||
@ -192,3 +225,27 @@ class BottomControlController:
|
|||||||
self.dia_hide_pos_anim.setStartValue(current_geo)
|
self.dia_hide_pos_anim.setStartValue(current_geo)
|
||||||
self.dia_hide_pos_anim.setEndValue(end_rect)
|
self.dia_hide_pos_anim.setEndValue(end_rect)
|
||||||
self.dia_hide_anim_group.start()
|
self.dia_hide_anim_group.start()
|
||||||
|
|
||||||
|
# ================== 系统状态消息列表: 显示系统消息 ===================
|
||||||
|
def toggle_system_status_msg_list(self):
|
||||||
|
"""系统状态消息按钮点击之后 显示系统消息"""
|
||||||
|
if not self.status_msg_widget.isVisible():
|
||||||
|
btn_pos = self.bottom_control_widget.status_msg_btn.mapToGlobal(QPoint(0, 0))
|
||||||
|
popup_x = btn_pos.x()
|
||||||
|
popup_y = btn_pos.y() - self.status_msg_widget.height()
|
||||||
|
self.status_msg_widget.move(popup_x, popup_y)
|
||||||
|
self.status_msg_widget.show()
|
||||||
|
else:
|
||||||
|
self.status_msg_widget.close()
|
||||||
|
|
||||||
|
# ================== 预警消息列表: 显示预警消息 ===================
|
||||||
|
def toggle_system_warning_msg_list(self):
|
||||||
|
"""预警消息列表按钮点击之后 显示预警消息"""
|
||||||
|
if not self.warning_msg_widget.isVisible():
|
||||||
|
btn_pos = self.bottom_control_widget.warning_list_btn.mapToGlobal(QPoint(0, 0))
|
||||||
|
popup_x = btn_pos.x()
|
||||||
|
popup_y = btn_pos.y() - self.warning_msg_widget.height()
|
||||||
|
self.warning_msg_widget.move(popup_x, popup_y)
|
||||||
|
self.warning_msg_widget.show()
|
||||||
|
else:
|
||||||
|
self.warning_msg_widget.close()
|
||||||
@ -7,15 +7,23 @@ from .camera_controller import CameraController
|
|||||||
from .bottom_control_controller import BottomControlController
|
from .bottom_control_controller import BottomControlController
|
||||||
from .hopper_controller import HopperController
|
from .hopper_controller import HopperController
|
||||||
|
|
||||||
|
from service.msg_recorder import MessageRecorder
|
||||||
|
|
||||||
class MainController:
|
class MainController:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# 主界面
|
# 主界面
|
||||||
self.main_window = MainWindow()
|
self.main_window = MainWindow()
|
||||||
|
|
||||||
|
self.msg_recorder = MessageRecorder()
|
||||||
|
self.msg_recorder.normal_record("开始自动智能浇筑系统")
|
||||||
|
|
||||||
# 初始化子界面和控制器
|
# 初始化子界面和控制器
|
||||||
self._initSubViews()
|
self._initSubViews()
|
||||||
self._initSubControllers()
|
self._initSubControllers()
|
||||||
|
|
||||||
|
# 连接信号
|
||||||
|
self.__connectSignals()
|
||||||
|
|
||||||
def showMainWindow(self):
|
def showMainWindow(self):
|
||||||
self.main_window.showFullScreen()
|
self.main_window.showFullScreen()
|
||||||
# self.main_window.show()
|
# self.main_window.show()
|
||||||
@ -45,3 +53,14 @@ class MainController:
|
|||||||
|
|
||||||
def _initSubViews(self):
|
def _initSubViews(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def __connectSignals(self):
|
||||||
|
self.main_window.about_to_close.connect(self.handleMainWindowClose) # 处理主界面关闭
|
||||||
|
|
||||||
|
def handleMainWindowClose(self):
|
||||||
|
"""主界面关闭"""
|
||||||
|
self.msg_recorder.normal_record("关闭自动智能浇筑系统")
|
||||||
|
# 停止系统底部控制器中的线程
|
||||||
|
if hasattr(self, 'bottom_control_controller'):
|
||||||
|
self.bottom_control_controller.stop_threads()
|
||||||
BIN
db/three.db
BIN
db/three.db
Binary file not shown.
BIN
images/消息列表背景.png
Normal file
BIN
images/消息列表背景.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
52
service/msg_query_thread.py
Normal file
52
service/msg_query_thread.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from PySide6.QtCore import QThread, Signal
|
||||||
|
from .msg_recorder import MessageRecorder
|
||||||
|
|
||||||
|
"""
|
||||||
|
定时查询消息数据库, 并发送最新的消息数据
|
||||||
|
"""
|
||||||
|
|
||||||
|
class MsgQueryThread(QThread):
|
||||||
|
# 定义信号:发送消息类型(1=系统,2=预警)和 消息信息列表
|
||||||
|
new_messages = Signal(int, list) # (message_type, [(content, is_processed, create_time, last_modified), ...])
|
||||||
|
|
||||||
|
def __init__(self, interval=1000, query_limit = 16):
|
||||||
|
super().__init__()
|
||||||
|
self.interval = interval # 查询间隔(毫秒)
|
||||||
|
self.query_limit = query_limit # 单次查询的项目条数,如每次查询16条消息
|
||||||
|
self.running = True # 线程运行标志
|
||||||
|
self.recorder = MessageRecorder() # 消息管理器
|
||||||
|
|
||||||
|
# 记录上次查询到的“最新last_modified时间”(按类型区分)
|
||||||
|
self.last_normal_modified = ""
|
||||||
|
self.last_warning_modified = ""
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""线程主逻辑: 每隔interval查询一次消息"""
|
||||||
|
while self.running:
|
||||||
|
# 1. 处理系统消息(类型1)
|
||||||
|
normal_msgs = self.recorder.get_latest_normal_messages(self.query_limit)
|
||||||
|
if normal_msgs:
|
||||||
|
# 找到这16条消息中最新的last_modified时间(即最大的)
|
||||||
|
latest_modified = max([msg[3] for msg in normal_msgs]) # msg[3]是last_modified
|
||||||
|
# 比较是否有更新(新增或状态变化)
|
||||||
|
# 注意:是根据last_modified来决定是否进行界面更新的
|
||||||
|
if latest_modified != self.last_normal_modified:
|
||||||
|
self.last_normal_modified = latest_modified
|
||||||
|
self.new_messages.emit(1, normal_msgs) # 发送信号更新UI,1表示系统消息
|
||||||
|
|
||||||
|
# 2. 处理预警消息(类型2)
|
||||||
|
warning_msgs = self.recorder.get_latest_warning_messages(self.query_limit)
|
||||||
|
if warning_msgs:
|
||||||
|
latest_modified = max([msg[3] for msg in warning_msgs])
|
||||||
|
if latest_modified != self.last_warning_modified:
|
||||||
|
self.last_warning_modified = latest_modified
|
||||||
|
self.new_messages.emit(2, warning_msgs)
|
||||||
|
|
||||||
|
self.msleep(self.interval)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""停止消息查询线程"""
|
||||||
|
self.running = False
|
||||||
|
self.wait(500)
|
||||||
|
if self.isRunning():
|
||||||
|
self.terminate()
|
||||||
44
service/msg_recorder.py
Normal file
44
service/msg_recorder.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from common.msg_db_helper import DBHelper
|
||||||
|
from PySide6.QtCore import QMutex
|
||||||
|
|
||||||
|
"""
|
||||||
|
消息管理器: 系统状态消息 和 预警消息
|
||||||
|
"""
|
||||||
|
|
||||||
|
class MessageRecorder:
|
||||||
|
_instance = None # 单例模式
|
||||||
|
_mutex = QMutex()
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
cls._mutex.lock()
|
||||||
|
try:
|
||||||
|
if not cls._instance:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
cls._instance.db = DBHelper()
|
||||||
|
finally:
|
||||||
|
cls._mutex.unlock()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def normal_record(self, content, is_processed=0):
|
||||||
|
"""记录系统状态消息"""
|
||||||
|
self.db.insert_message(content, message_type=1, is_processed=is_processed)
|
||||||
|
|
||||||
|
def warning_record(self, content, is_processed=0):
|
||||||
|
"""记录预警消息"""
|
||||||
|
self.db.insert_message(content, message_type=2, is_processed=is_processed)
|
||||||
|
|
||||||
|
def update_warning_status(self, content, is_processed=1):
|
||||||
|
"""更新预警消息的处理状态: is_processed为1表示已经处理, 字体变为灰色"""
|
||||||
|
self.db.update_processed_status(content, message_type=2, is_processed=is_processed)
|
||||||
|
|
||||||
|
def update_normal_status(self, content, is_processed=1):
|
||||||
|
"""更新系统消息的处理状态: is_processed为1表示已经处理, 字体变为灰色"""
|
||||||
|
self.db.update_processed_status(content, message_type=1, is_processed=is_processed)
|
||||||
|
|
||||||
|
def get_latest_normal_messages(self, limit=16):
|
||||||
|
"""获取最新的系统状态消息(message_type=1), limit为获取的消息的条数, 默认16条"""
|
||||||
|
return self.db.get_latest_messages(message_type=1, limit=limit)
|
||||||
|
|
||||||
|
def get_latest_warning_messages(self, limit=16):
|
||||||
|
"""获取最新的预警消息(message_type=2), limit为获取的消息的条数, 默认16条"""
|
||||||
|
return self.db.get_latest_messages(message_type=2, limit=limit)
|
||||||
@ -115,3 +115,8 @@ class ImagePaths:
|
|||||||
DESPATCH_DETAILS_INFO_BAR_NORMAL = "images/派单任务信息栏1.png"
|
DESPATCH_DETAILS_INFO_BAR_NORMAL = "images/派单任务信息栏1.png"
|
||||||
DESPATCH_DETAILS_INFO_BAR_HOVER = "images/派单任务信息栏2.png"
|
DESPATCH_DETAILS_INFO_BAR_HOVER = "images/派单任务信息栏2.png"
|
||||||
DESPATCH_DETAILS_CLOSE_ICON = "images/关闭图标.png"
|
DESPATCH_DETAILS_CLOSE_ICON = "images/关闭图标.png"
|
||||||
|
|
||||||
|
# 功能:消息列表(系统状态消息、预警消息)
|
||||||
|
MESSAGE_LIST_POPUP_BG = "images/消息列表背景.png"
|
||||||
|
SYSTEM_MSG_HORN_ICON = "images/系统消息喇叭.png"
|
||||||
|
SYSTEM_MSG_ITEM_BG = "images/系统消息背景.png"
|
||||||
@ -26,6 +26,9 @@ from .widgets.dispatch_details_dialog import DispatchDetailsDialog
|
|||||||
|
|
||||||
|
|
||||||
class MainWindow(QWidget):
|
class MainWindow(QWidget):
|
||||||
|
# 定义“即将关闭”的信号
|
||||||
|
about_to_close = Signal()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.initWindow()
|
self.initWindow()
|
||||||
@ -329,6 +332,7 @@ class MainWindow(QWidget):
|
|||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
"""窗口关闭时的回调"""
|
"""窗口关闭时的回调"""
|
||||||
|
self.about_to_close.emit()
|
||||||
super().closeEvent(e)
|
super().closeEvent(e)
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
|
|||||||
277
view/widgets/message_popup_widget.py
Normal file
277
view/widgets/message_popup_widget.py
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QWidget,
|
||||||
|
QVBoxLayout,
|
||||||
|
QListWidget,
|
||||||
|
QListWidgetItem,
|
||||||
|
QLabel,
|
||||||
|
QHBoxLayout,
|
||||||
|
QDialog
|
||||||
|
)
|
||||||
|
from PySide6.QtCore import Qt, QRect, QSize
|
||||||
|
from PySide6.QtGui import QFont, QColor, QPixmap, QPainter
|
||||||
|
from utils.image_paths import ImagePaths
|
||||||
|
|
||||||
|
"""
|
||||||
|
系统状态消息按钮 和 预警消息按钮点击之后出现的消息列表, 显示系统状态消息 和 预警消息
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 自定义消息项组件
|
||||||
|
class MessageItemWidget(QWidget):
|
||||||
|
def __init__(self, content, is_processed, msg_time:str = "", parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.content = content
|
||||||
|
self.is_processed = is_processed
|
||||||
|
self.msg_time = msg_time # 对应产生该消息的时间,如: 2025-11-9 19:09:09
|
||||||
|
self.bg_pixmap = QPixmap(ImagePaths.SYSTEM_MSG_ITEM_BG) # 消息项背景图
|
||||||
|
self.icon_pixmap = QPixmap(ImagePaths.SYSTEM_MSG_HORN_ICON) # 左侧喇叭图标
|
||||||
|
self.setStyleSheet("background: none; border: none;")
|
||||||
|
self.setFixedWidth(310) # 内部显示的消息条目的宽度,控制每条消息的宽度
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
# 布局:仅显示消息文本
|
||||||
|
layout = QHBoxLayout(self)
|
||||||
|
|
||||||
|
layout.setContentsMargins(6, 6, 6, 6)
|
||||||
|
layout.setSpacing(5) # 调整图标与文本的间距
|
||||||
|
|
||||||
|
# 1. 左侧喇叭图标
|
||||||
|
self.icon_label = QLabel()
|
||||||
|
if not self.icon_pixmap.isNull():
|
||||||
|
# 设置图标大小(例如20x20,可根据需求调整)
|
||||||
|
self.icon_label.setFixedSize(13, 21)
|
||||||
|
# 图片自适应label大小,保持比例
|
||||||
|
self.icon_label.setScaledContents(True)
|
||||||
|
self.icon_label.setPixmap(self.icon_pixmap)
|
||||||
|
# 图标背景透明
|
||||||
|
self.icon_label.setStyleSheet("background: transparent;padding-top:3px;")
|
||||||
|
layout.addWidget(self.icon_label, alignment=Qt.AlignTop)
|
||||||
|
|
||||||
|
# 2. 右侧消息时间
|
||||||
|
# if self.msg_time:
|
||||||
|
# self.time_label = QLabel(self.msg_time)
|
||||||
|
# self.time_label.setFont(QFont("微软雅黑", 8)) # 消息时间的字体
|
||||||
|
# self.time_label.setStyleSheet("color: #03f5ff;")
|
||||||
|
# self.time_label.setFixedWidth(106) # 显示时间的标签的宽度,需要根据上面的消息时间字体来调整
|
||||||
|
# self.time_label.setFixedHeight(19)
|
||||||
|
# layout.addWidget(self.time_label, alignment=Qt.AlignTop)
|
||||||
|
|
||||||
|
# # 3. 右侧消息内容文本
|
||||||
|
# self.label = QLabel(self.content)
|
||||||
|
# self.label.setFont(QFont("微软雅黑", 12)) # 消息内容中的字体
|
||||||
|
# self.label.setWordWrap(True)
|
||||||
|
|
||||||
|
# # 已处理消息文字变灰
|
||||||
|
# if self.is_processed:
|
||||||
|
# self.label.setStyleSheet("color: gray;")
|
||||||
|
# else:
|
||||||
|
# self.label.setStyleSheet("color: white;")
|
||||||
|
# layout.addWidget(self.label)
|
||||||
|
|
||||||
|
# 2. 右侧:时间 + 内容的垂直布局
|
||||||
|
right_layout = QVBoxLayout()
|
||||||
|
right_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
right_layout.setSpacing(0)
|
||||||
|
|
||||||
|
# 时间标签(可选:若有时间则显示)
|
||||||
|
if self.msg_time:
|
||||||
|
self.time_label = QLabel(self.msg_time)
|
||||||
|
self.time_label.setFont(QFont("微软雅黑", 8))
|
||||||
|
self.time_label.setStyleSheet("color: #03f5ff; margin: 0px; padding: 0px;")
|
||||||
|
right_layout.addWidget(self.time_label, alignment=Qt.AlignTop)
|
||||||
|
|
||||||
|
# 内容标签
|
||||||
|
self.label = QLabel(self.content)
|
||||||
|
# font = QFont("微软雅黑", 11)
|
||||||
|
# font.setStyleStrategy(QFont.PreferAntialias | QFont.PreferDefault)
|
||||||
|
self.label.setFont(QFont("微软雅黑", 11))
|
||||||
|
self.label.setWordWrap(True)
|
||||||
|
if self.is_processed:
|
||||||
|
self.label.setStyleSheet("color: gray; margin: 0px; padding: 0px;")
|
||||||
|
else:
|
||||||
|
self.label.setStyleSheet("color: white; margin: 0px; padding: 0px;")
|
||||||
|
right_layout.addWidget(self.label, alignment=Qt.AlignTop)
|
||||||
|
|
||||||
|
# 将右侧垂直布局加入主水平布局
|
||||||
|
layout.addLayout(right_layout)
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
# 绘制消息项背景图(自适应组件大小)
|
||||||
|
if not self.bg_pixmap.isNull():
|
||||||
|
painter = QPainter(self)
|
||||||
|
scaled_pixmap = self.bg_pixmap.scaled(
|
||||||
|
self.size(),
|
||||||
|
Qt.KeepAspectRatioByExpanding, # Qt.KeepAspectRatioByExpanding按比例缩放填满
|
||||||
|
Qt.SmoothTransformation, # 平滑缩放
|
||||||
|
)
|
||||||
|
painter.drawPixmap(self.rect(), scaled_pixmap)
|
||||||
|
super().paintEvent(event)
|
||||||
|
|
||||||
|
|
||||||
|
# 消息列表弹窗 (整个显示消息的区域)
|
||||||
|
class MessagePopupWidget(QWidget):
|
||||||
|
def __init__(self, message_type, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.message_type = message_type
|
||||||
|
self.bg_pixmap = QPixmap(ImagePaths.MESSAGE_LIST_POPUP_BG) # 列表背景图
|
||||||
|
# 弹窗样式:无框 + 置顶,宽度固定为339(与按钮一致)
|
||||||
|
self.setWindowFlags(Qt.FramelessWindowHint)
|
||||||
|
self.hide() # 初始状态为隐藏 (必须)
|
||||||
|
self.setFixedWidth(339) # 宽度与下方的按钮一致
|
||||||
|
# self.setMinimumHeight(181) # 整个消息列表的高度,需要根据实际情况调整
|
||||||
|
# self.setMinimumHeight(211) # 整个消息列表的高度,需要根据实际情况调整
|
||||||
|
# self.setFixedHeight(260) # 整个消息列表的高度,需要根据实际情况调整
|
||||||
|
# self.setFixedHeight(290) # 整个消息列表的高度,需要根据实际情况调整
|
||||||
|
self.setFixedHeight(299) # 整个消息列表弹窗的高度,需要根据实际情况调整 (一页能够有六行消息[单行])
|
||||||
|
|
||||||
|
# 布局:标题 + 消息列表
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setContentsMargins(10, 10, 10, 20)
|
||||||
|
layout.setSpacing(3) # 消息项间距
|
||||||
|
|
||||||
|
# 标题
|
||||||
|
self.title_label = QLabel(self.message_type)
|
||||||
|
self.title_label.setFont(QFont("微软雅黑", 14, QFont.Bold))
|
||||||
|
self.title_label.setStyleSheet(
|
||||||
|
"background: none; color: #03f5ff; text-align: center;"
|
||||||
|
)
|
||||||
|
self.title_label.setAlignment(Qt.AlignCenter)
|
||||||
|
layout.addWidget(self.title_label)
|
||||||
|
|
||||||
|
# 消息列表(支持选中)
|
||||||
|
self.list_widget = QListWidget()
|
||||||
|
# 禁用列表选择
|
||||||
|
self.list_widget.setSelectionMode(QListWidget.SelectionMode.NoSelection)
|
||||||
|
# 禁用水平滚动条
|
||||||
|
self.list_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
|
self.list_widget.setStyleSheet(
|
||||||
|
"""
|
||||||
|
QListWidget { background-color: transparent; border: none; }
|
||||||
|
QListWidget::item { height: 30px; }
|
||||||
|
|
||||||
|
QListWidget QScrollBar:vertical {
|
||||||
|
background: transparent; /* 轨道背景色 */
|
||||||
|
width: 8px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条滑块 */
|
||||||
|
QListWidget QScrollBar::handle:vertical {
|
||||||
|
background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #039ec3, stop:1 #03f5ff);
|
||||||
|
border-radius: 4px;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滑块悬停效果 */
|
||||||
|
QListWidget QScrollBar::handle:vertical:hover {
|
||||||
|
background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #16ffff, stop:1 #00347e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏上下箭头按钮 */
|
||||||
|
QListWidget QScrollBar::add-line:vertical, QListWidget QScrollBar::sub-line:vertical {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滑块上下的空白区域(透明) */
|
||||||
|
QListWidget QScrollBar::add-page:vertical, QListWidget QScrollBar::sub-page:vertical {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
layout.addWidget(self.list_widget)
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
# 这里需要根据不变的图片部分来决定
|
||||||
|
self.top = 32 # 上分割线距离顶部
|
||||||
|
self.right = 32 # 右分割线距离右侧
|
||||||
|
self.bottom = 32 # 下分割线距离底部
|
||||||
|
self.left = 32 # 左分割线距离左侧
|
||||||
|
|
||||||
|
# 按九宫格来加载图片
|
||||||
|
painter = QPainter(self)
|
||||||
|
w, h = self.width(), self.height() # 控件当前大小
|
||||||
|
img_w, img_h = self.bg_pixmap.width(), self.bg_pixmap.height() # 原图大小
|
||||||
|
|
||||||
|
# 1. 绘制四个角(不拉伸)
|
||||||
|
# 左上
|
||||||
|
painter.drawPixmap(
|
||||||
|
0, 0, self.left, self.top,
|
||||||
|
self.bg_pixmap, 0, 0, self.left, self.top
|
||||||
|
)
|
||||||
|
# 右上
|
||||||
|
painter.drawPixmap(
|
||||||
|
w - self.right, 0, self.right, self.top,
|
||||||
|
self.bg_pixmap, img_w - self.right, 0, self.right, self.top
|
||||||
|
)
|
||||||
|
# 左下
|
||||||
|
painter.drawPixmap(
|
||||||
|
0, h - self.bottom, self.left, self.bottom,
|
||||||
|
self.bg_pixmap, 0, img_h - self.bottom, self.left, self.bottom
|
||||||
|
)
|
||||||
|
# 右下
|
||||||
|
painter.drawPixmap(
|
||||||
|
w - self.right, h - self.bottom, self.right, self.bottom,
|
||||||
|
self.bg_pixmap, img_w - self.right, img_h - self.bottom, self.right, self.bottom
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. 绘制四条边(拉伸中间部分)
|
||||||
|
# 上边(水平拉伸)
|
||||||
|
painter.drawPixmap(
|
||||||
|
self.left, 0, w - self.left - self.right, self.top,
|
||||||
|
self.bg_pixmap, self.left, 0, img_w - self.left - self.right, self.top
|
||||||
|
)
|
||||||
|
# 下边(水平拉伸)
|
||||||
|
painter.drawPixmap(
|
||||||
|
self.left, h - self.bottom, w - self.left - self.right, self.bottom,
|
||||||
|
self.bg_pixmap, self.left, img_h - self.bottom, img_w - self.left - self.right, self.bottom
|
||||||
|
)
|
||||||
|
# 左边(垂直拉伸)
|
||||||
|
painter.drawPixmap(
|
||||||
|
0, self.top, self.left, h - self.top - self.bottom,
|
||||||
|
self.bg_pixmap, 0, self.top, self.left, img_h - self.top - self.bottom
|
||||||
|
)
|
||||||
|
# 右边(垂直拉伸)
|
||||||
|
painter.drawPixmap(
|
||||||
|
w - self.right, self.top, self.right, h - self.top - self.bottom,
|
||||||
|
self.bg_pixmap, img_w - self.right, self.top, self.right, img_h - self.top - self.bottom
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. 绘制中心区域(自由拉伸)
|
||||||
|
painter.drawPixmap(
|
||||||
|
self.left, self.top, w - self.left - self.right, h - self.top - self.bottom,
|
||||||
|
self.bg_pixmap, self.left, self.top, img_w - self.left - self.right, img_h - self.top - self.bottom
|
||||||
|
)
|
||||||
|
|
||||||
|
super().paintEvent(event)
|
||||||
|
|
||||||
|
# 显示设置
|
||||||
|
def showEvent(self, event):
|
||||||
|
super().showEvent(event)
|
||||||
|
# 弹窗显示时,强制滚动到底部,以便显示最新的消息
|
||||||
|
self.list_widget.scrollToBottom()
|
||||||
|
|
||||||
|
"""
|
||||||
|
对外接口: 添加消息到消息列表
|
||||||
|
is_processed状态为True表示 已经处理/已经解决, 此时显示的字体为灰色
|
||||||
|
"""
|
||||||
|
def add_message(self, content, is_processed:bool = False, msg_time:str = ""):
|
||||||
|
"""添加带背景的消息项
|
||||||
|
Args:
|
||||||
|
content: 消息的内容
|
||||||
|
is_processed: 消息的状态 (是否已经解决, 已经解决,字体变为灰色)
|
||||||
|
msg_time: 该消息对应的时间 (如: 产生消息的时间等), 会显示在消息内容之前,如: 2025-11-12 消息内容
|
||||||
|
"""
|
||||||
|
# 1、创建自定义消息项组件
|
||||||
|
item_widget = MessageItemWidget(content, is_processed, msg_time)
|
||||||
|
# 2、创建列表项并关联组件
|
||||||
|
item = QListWidgetItem()
|
||||||
|
# item.setSizeHint(item_widget.sizeHint()) # 自适应组件大小
|
||||||
|
# item.setSizeHint(QSize(item_widget.sizeHint().width(), 30)) # 固定行高,设置为30px (不行,必须根据item_widget的高度决定)
|
||||||
|
|
||||||
|
# 行高减去6px, 刚好可以显示出4行, 对应了消息的字体大小为11px的情况
|
||||||
|
item.setSizeHint(QSize(item_widget.sizeHint().width(), item_widget.sizeHint().height() - 6))
|
||||||
|
self.list_widget.addItem(item)
|
||||||
|
self.list_widget.setItemWidget(item, item_widget)
|
||||||
|
# 3、添加新消息后,滚动到列表底部,以便显示最新的消息
|
||||||
|
self.list_widget.scrollToBottom()
|
||||||
@ -68,6 +68,7 @@ class SystemCenterDialog(QDialog):
|
|||||||
self.setWindowTitle("系统中心")
|
self.setWindowTitle("系统中心")
|
||||||
self.setWindowFlags(Qt.FramelessWindowHint) # 隐藏默认边框
|
self.setWindowFlags(Qt.FramelessWindowHint) # 隐藏默认边框
|
||||||
self.setWindowOpacity(0.0) # 初始状态为完全透明,实现动画效果
|
self.setWindowOpacity(0.0) # 初始状态为完全透明,实现动画效果
|
||||||
|
self.hide() # 初始状态为隐藏 (必须)
|
||||||
|
|
||||||
# 加载背景图
|
# 加载背景图
|
||||||
self.background = QPixmap(ImagePaths.SYSTEM_CENTER_POPUP_BG)
|
self.background = QPixmap(ImagePaths.SYSTEM_CENTER_POPUP_BG)
|
||||||
|
|||||||
@ -186,6 +186,7 @@ class SystemDiagnosticsDialog(QDialog):
|
|||||||
def _init_ui(self):
|
def _init_ui(self):
|
||||||
# 无边框模式
|
# 无边框模式
|
||||||
self.setWindowFlags(Qt.FramelessWindowHint)
|
self.setWindowFlags(Qt.FramelessWindowHint)
|
||||||
|
self.hide() # 初始状态为隐藏 (必须)
|
||||||
|
|
||||||
# 加载系统诊断弹窗的背景图片
|
# 加载系统诊断弹窗的背景图片
|
||||||
self.bg_pixmap = QPixmap(ImagePaths.SYSTEM_DIAGNOSTICS_POPUP_BG)
|
self.bg_pixmap = QPixmap(ImagePaths.SYSTEM_DIAGNOSTICS_POPUP_BG)
|
||||||
|
|||||||
@ -171,9 +171,13 @@ class TaskWidget(QWidget):
|
|||||||
# --------------------------
|
# --------------------------
|
||||||
def set_task_volume(self, task_name:str, volume: float):
|
def set_task_volume(self, task_name:str, volume: float):
|
||||||
"""修改指定任务的方量, 传入具体的方量值,如: 200.0"""
|
"""修改指定任务的方量, 传入具体的方量值,如: 200.0"""
|
||||||
|
# 褚工说 管片任务 和 派单任务的方量只有一位小数。 2025-11-8
|
||||||
|
# 所以这里限制为 保留一位小数的浮点数
|
||||||
|
current_volume = round(float(volume), 1)
|
||||||
|
|
||||||
if task_name in self.task_controls:
|
if task_name in self.task_controls:
|
||||||
volume_label = self.task_controls[task_name]["volume_label"]
|
volume_label = self.task_controls[task_name]["volume_label"]
|
||||||
volume_label.setText(f"方量 {volume}")
|
volume_label.setText(f"方量 {current_volume}")
|
||||||
|
|
||||||
def set_task_time(self, task_name:str, time_str: str):
|
def set_task_time(self, task_name:str, time_str: str):
|
||||||
"""修改指定任务的时间, 传入对应格式的时间,如: 03:22PM"""
|
"""修改指定任务的时间, 传入对应格式的时间,如: 03:22PM"""
|
||||||
|
|||||||
Reference in New Issue
Block a user