Compare commits

...

2 Commits

13 changed files with 615 additions and 29 deletions

122
common/msg_db_helper.py Normal file
View 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 # 已处理
)

View File

@ -1,8 +1,12 @@
# 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.bottom_control_widget import BottomControlWidget
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,29 +17,71 @@ class BottomControlController:
def __init__(self, bottom_control_widget:BottomControlWidget, main_window):
self.bottom_control_widget = bottom_control_widget
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.hide()
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()
@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):
# 底部系统中心按钮 → 触发弹窗显示/隐藏
self.bottom_control_widget.center_btn.clicked.connect(self.toggle_system_center_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):
"""绑定弹窗按钮的信号"""
# 系统中心弹窗的按钮信号
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.user_center_clicked.connect(self.handle_user_center)
@ -155,25 +201,12 @@ class BottomControlController:
self.system_diagnostics_dialog.show()
def _calc_system_diagnostics_dialog_position(self):
"""计算系统诊断弹窗位置(显示在诊断按钮上方,与中心弹窗布局一致"""
"""计算系统诊断弹窗位置(显示在系统诊断按钮上方)"""
btn = self.bottom_control_widget.diagnosis_btn # 诊断按钮
bottom_widget = self.bottom_control_widget
# 计算按钮在主窗口中的绝对位置
bottom_pos_rel_main = bottom_widget.pos() # 底部控件相对于主窗口的位置
btn_pos_rel_bottom = btn.pos() # 诊断按钮相对于底部控件的位置
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()
btn_pos = btn.mapToGlobal(QPoint(0, 0))
dialog_x = btn_pos.x()
dialog_y = btn_pos.y() - self.system_diagnostics_dialog.height()
# 设置弹窗位置(动画会基于此位置执行滑入效果)
self.system_diagnostics_dialog.move(dialog_x, dialog_y)
@ -191,4 +224,28 @@ class BottomControlController:
# 设置动画参数并启动
self.dia_hide_pos_anim.setStartValue(current_geo)
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()

View File

@ -7,15 +7,23 @@ from .camera_controller import CameraController
from .bottom_control_controller import BottomControlController
from .hopper_controller import HopperController
from service.msg_recorder import MessageRecorder
class MainController:
def __init__(self):
# 主界面
self.main_window = MainWindow()
self.msg_recorder = MessageRecorder()
self.msg_recorder.normal_record("开始自动智能浇筑系统")
# 初始化子界面和控制器
self._initSubViews()
self._initSubControllers()
# 连接信号
self.__connectSignals()
def showMainWindow(self):
self.main_window.showFullScreen()
# self.main_window.show()
@ -44,4 +52,15 @@ class MainController:
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()

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View 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) # 发送信号更新UI1表示系统消息
# 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
View 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)

View File

@ -114,4 +114,9 @@ class ImagePaths:
DESPATCH_DETAILS_TITLE_BG = "images/详情标题.png"
DESPATCH_DETAILS_INFO_BAR_NORMAL = "images/派单任务信息栏1.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"

View File

@ -26,6 +26,9 @@ from .widgets.dispatch_details_dialog import DispatchDetailsDialog
class MainWindow(QWidget):
# 定义“即将关闭”的信号
about_to_close = Signal()
def __init__(self):
super().__init__()
self.initWindow()
@ -329,6 +332,7 @@ class MainWindow(QWidget):
def closeEvent(self, e):
"""窗口关闭时的回调"""
self.about_to_close.emit()
super().closeEvent(e)
def keyPressEvent(self, event):

View 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()

View File

@ -68,6 +68,7 @@ class SystemCenterDialog(QDialog):
self.setWindowTitle("系统中心")
self.setWindowFlags(Qt.FramelessWindowHint) # 隐藏默认边框
self.setWindowOpacity(0.0) # 初始状态为完全透明,实现动画效果
self.hide() # 初始状态为隐藏 (必须)
# 加载背景图
self.background = QPixmap(ImagePaths.SYSTEM_CENTER_POPUP_BG)

View File

@ -186,6 +186,7 @@ class SystemDiagnosticsDialog(QDialog):
def _init_ui(self):
# 无边框模式
self.setWindowFlags(Qt.FramelessWindowHint)
self.hide() # 初始状态为隐藏 (必须)
# 加载系统诊断弹窗的背景图片
self.bg_pixmap = QPixmap(ImagePaths.SYSTEM_DIAGNOSTICS_POPUP_BG)

View File

@ -171,9 +171,13 @@ class TaskWidget(QWidget):
# --------------------------
def set_task_volume(self, task_name:str, volume: float):
"""修改指定任务的方量, 传入具体的方量值,如: 200.0"""
# 褚工说 管片任务 和 派单任务的方量只有一位小数。 2025-11-8
# 所以这里限制为 保留一位小数的浮点数
current_volume = round(float(volume), 1)
if task_name in self.task_controls:
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):
"""修改指定任务的时间, 传入对应格式的时间,如: 03:22PM"""