278 lines
12 KiB
Python
278 lines
12 KiB
Python
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()
|