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