a13rollcall/error_dialog.py
2026-10-06 18:38:55 +08:00

304 行
10 KiB
Python

# -*- coding: utf-8 -*-
"""
A13课堂点名系统 - 现代化错误提示对话框
美观的错误展示窗口,支持错误详情查看、复制、报告等功能
"""
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
QTextEdit, QFrame, QSizePolicy, QApplication, QWidget
)
from PyQt5.QtCore import Qt, QTimer, QPropertyAnimation, QEasingCurve, QSize
from PyQt5.QtGui import QFont, QColor, QPainter, QBrush, QPen, QIcon, QPixmap
class ErrorIconWidget(QWidget):
"""错误图标组件"""
def __init__(self, error_type="error", parent=None):
super().__init__(parent)
self.error_type = error_type
self.setFixedSize(64, 64)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
colors = {
"error": ("#c42b1c", "#e81123"),
"warning": ("#ca5010", "#ff8c00"),
"info": ("#0067c0", "#0078d4"),
"success": ("#107c10", "#0e6c0e")
}
color1, color2 = colors.get(self.error_type, colors["error"])
painter.setPen(Qt.NoPen)
painter.setBrush(QBrush(QColor(color1)))
painter.drawEllipse(4, 4, 56, 56)
painter.setBrush(QBrush(QColor(color2)))
painter.drawEllipse(8, 8, 48, 48)
painter.setPen(QPen(QColor("white"), 4, Qt.SolidLine, Qt.RoundCap))
if self.error_type == "error":
painter.drawLine(22, 22, 42, 42)
painter.drawLine(42, 22, 22, 42)
elif self.error_type == "warning":
painter.drawLine(32, 18, 32, 36)
painter.drawEllipse(30, 40, 4, 4)
elif self.error_type == "info":
painter.drawLine(32, 18, 32, 22)
painter.drawEllipse(30, 14, 4, 4)
painter.drawLine(32, 28, 32, 44)
elif self.error_type == "success":
painter.drawLine(20, 32, 28, 40)
painter.drawLine(28, 40, 44, 24)
painter.end()
class ModernErrorDialog(QDialog):
"""
现代化错误提示对话框
美观的错误展示,支持详情查看、复制、报告
"""
def __init__(self, title="发生错误", message="", details="",
error_type="error", parent=None):
super().__init__(parent)
self.title_text = title
self.message_text = message
self.details_text = details
self.error_type = error_type
self._show_details = False
self._setup_ui()
self._setup_animation()
def _setup_ui(self):
self.setWindowTitle(self.title_text)
self.setMinimumWidth(480)
self.setMaximumWidth(600)
self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
main_container = QFrame()
main_container.setStyleSheet("""
QFrame {
background: white;
border-radius: 12px;
border: 1px solid #e0e0e0;
}
""")
shadow_layout = QVBoxLayout(self)
shadow_layout.setContentsMargins(12, 12, 12, 12)
shadow_layout.addWidget(main_container)
layout = QVBoxLayout(main_container)
layout.setContentsMargins(24, 24, 24, 20)
layout.setSpacing(16)
header = QHBoxLayout()
header.setSpacing(16)
self.icon_widget = ErrorIconWidget(self.error_type)
header.addWidget(self.icon_widget)
text_layout = QVBoxLayout()
text_layout.setSpacing(4)
title_label = QLabel(self.title_text)
title_label.setStyleSheet("""
font-size: 18px;
font-weight: 800;
color: #1a1a1a;
""")
text_layout.addWidget(title_label)
if self.message_text:
message_label = QLabel(self.message_text)
message_label.setStyleSheet("""
font-size: 13px;
color: #666666;
""")
message_label.setWordWrap(True)
text_layout.addWidget(message_label)
header.addLayout(text_layout, 1)
layout.addLayout(header)
self.details_container = QFrame()
self.details_container.setStyleSheet("""
QFrame {
background: #f8f8f8;
border-radius: 8px;
border: 1px solid #e8e8e8;
}
""")
details_layout = QVBoxLayout(self.details_container)
details_layout.setContentsMargins(12, 12, 12, 12)
details_layout.setSpacing(8)
details_header = QHBoxLayout()
details_title = QLabel("错误详情")
details_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #333;")
details_header.addWidget(details_title)
details_header.addStretch()
details_layout.addLayout(details_header)
self.details_text = QTextEdit()
self.details_text.setReadOnly(True)
self.details_text.setMaximumHeight(150)
self.details_text.setStyleSheet("""
QTextEdit {
background: white;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 8px;
font-family: Consolas, monospace;
font-size: 11px;
color: #333;
}
""")
self.details_text.setPlainText(self.details_text or "暂无详细信息")
details_layout.addWidget(self.details_text)
self.details_container.setVisible(False)
layout.addWidget(self.details_container)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(8)
if self.details_text:
self.toggle_btn = QPushButton("显示详情")
self.toggle_btn.setStyleSheet("""
QPushButton {
background: transparent;
color: #0067c0;
border: 1px solid #0067c0;
border-radius: 6px;
padding: 8px 16px;
font-weight: 600;
font-size: 12px;
}
QPushButton:hover {
background: #e5f1fb;
}
""")
self.toggle_btn.setCursor(Qt.PointingHandCursor)
self.toggle_btn.clicked.connect(self._toggle_details)
btn_layout.addWidget(self.toggle_btn)
copy_btn = QPushButton("复制错误")
copy_btn.setStyleSheet("""
QPushButton {
background: transparent;
color: #666;
border: 1px solid #ddd;
border-radius: 6px;
padding: 8px 16px;
font-weight: 600;
font-size: 12px;
}
QPushButton:hover {
background: #f5f5f5;
}
""")
copy_btn.setCursor(Qt.PointingHandCursor)
copy_btn.clicked.connect(self._copy_error)
btn_layout.addWidget(copy_btn)
btn_layout.addStretch()
ok_btn = QPushButton("确定")
ok_btn.setStyleSheet("""
QPushButton {
background: #0067c0;
color: white;
border: none;
border-radius: 6px;
padding: 8px 24px;
font-weight: 700;
font-size: 12px;
}
QPushButton:hover {
background: #106ebe;
}
QPushButton:pressed {
background: #005a9e;
}
""")
ok_btn.setCursor(Qt.PointingHandCursor)
ok_btn.clicked.connect(self.accept)
ok_btn.setDefault(True)
btn_layout.addWidget(ok_btn)
layout.addLayout(btn_layout)
def _setup_animation(self):
self._opacity = 0.0
self.setWindowOpacity(0.0)
self._fade_timer = QTimer(self)
self._fade_timer.timeout.connect(self._fade_in_step)
self._fade_timer.start(16)
def _fade_in_step(self):
self._opacity += 0.08
if self._opacity >= 1.0:
self._opacity = 1.0
self._fade_timer.stop()
self.setWindowOpacity(self._opacity)
def _toggle_details(self):
self._show_details = not self._show_details
self.details_container.setVisible(self._show_details)
self.toggle_btn.setText("隐藏详情" if self._show_details else "显示详情")
if self._show_details:
self.setMinimumHeight(400)
else:
self.setMinimumHeight(0)
self.adjustSize()
def _copy_error(self):
try:
clipboard = QApplication.clipboard()
error_info = f"错误标题: {self.title_text}\n错误信息: {self.message_text}\n\n错误详情:\n{self.details_text}"
clipboard.setText(error_info)
self._show_toast("错误信息已复制到剪贴板")
except Exception as e:
print(f"复制失败: {e}")
def _show_toast(self, message):
try:
toast = QLabel(message, self)
toast.setStyleSheet("""
background: #333;
color: white;
padding: 8px 16px;
border-radius: 6px;
font-size: 12px;
""")
toast.adjustSize()
x = (self.width() - toast.width()) // 2
y = self.height() - 60
toast.move(x, y)
toast.show()
QTimer.singleShot(1500, toast.close)
except Exception:
pass
def show_error_dialog(title="发生错误", message="", details="",
error_type="error", parent=None):
"""显示现代化错误对话框的便捷函数"""
dialog = ModernErrorDialog(title, message, details, error_type, parent)
return dialog.exec_()
def show_exception_dialog(exception: Exception, title="程序异常", parent=None):
"""显示异常对话框的便捷函数"""
import traceback
details = traceback.format_exc()
message = str(exception)
return show_error_dialog(title, message, details, "error", parent)