Files
nl-tools/ui/profile_card.py

546 lines
18 KiB
Python
Raw Permalink Normal View History

2025-12-11 09:47:00 +08:00
"""
个人信息卡片模态框
- 头像微信二维码邮箱
- 高级UI设计带装饰元素和动画
"""
import os
2025-12-11 10:30:15 +08:00
import sys
2025-12-11 09:47:00 +08:00
from pathlib import Path
from PySide6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
QWidget, QGraphicsOpacityEffect, QApplication
)
from PySide6.QtCore import (
Qt, QPropertyAnimation, QEasingCurve, QTimer,
QPoint, QRect, QParallelAnimationGroup, Signal
)
from PySide6.QtGui import (
QPixmap, QPainter, QColor, QLinearGradient, QRadialGradient,
QFont, QPen, QBrush, QPainterPath, QCursor
)
import math
def get_resource_path(relative_path: str) -> str:
2025-12-11 10:30:15 +08:00
"""获取资源文件的绝对路径(兼容打包后)"""
if getattr(sys, 'frozen', False):
# 打包后的路径
base_path = Path(sys._MEIPASS)
else:
# 开发时的路径
base_path = Path(__file__).parent.parent
2025-12-11 09:47:00 +08:00
return str(base_path / relative_path)
# 资源路径
AVATAR_PATH = get_resource_path("image/头像.jpg")
QRCODE_PATH = get_resource_path("image/二维码.jpg")
class FloatingOrb(QWidget):
"""浮动装饰球"""
def __init__(self, size: int, color: QColor, parent=None):
super().__init__(parent)
self.orb_size = size
self.orb_color = color
self.setFixedSize(size, size)
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
# 设置透明效果
self.opacity_effect = QGraphicsOpacityEffect(self)
self.opacity_effect.setOpacity(0.6)
self.setGraphicsEffect(self.opacity_effect)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
# 径向渐变
gradient = QRadialGradient(
self.orb_size / 2, self.orb_size / 2, self.orb_size / 2
)
gradient.setColorAt(0, QColor(self.orb_color.red(), self.orb_color.green(), self.orb_color.blue(), 120))
gradient.setColorAt(0.5, QColor(self.orb_color.red(), self.orb_color.green(), self.orb_color.blue(), 60))
gradient.setColorAt(1, QColor(self.orb_color.red(), self.orb_color.green(), self.orb_color.blue(), 0))
painter.setBrush(gradient)
painter.setPen(Qt.PenStyle.NoPen)
painter.drawEllipse(0, 0, self.orb_size, self.orb_size)
class GlowButton(QPushButton):
"""发光按钮"""
def __init__(self, text: str, primary: bool = True, parent=None):
super().__init__(text, parent)
self.primary = primary
self._hovered = False
self.setFixedHeight(44)
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.setFont(QFont("Microsoft YaHei UI", 10, QFont.Weight.Medium))
self.setStyleSheet("""
QPushButton {
border: none;
border-radius: 10px;
padding: 10px 24px;
color: white;
}
""")
def enterEvent(self, event):
self._hovered = True
self.update()
def leaveEvent(self, event):
self._hovered = False
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
rect = self.rect()
if self.primary:
# 主按钮 - 奶酪色渐变
gradient = QLinearGradient(0, 0, rect.width(), 0)
if self._hovered:
gradient.setColorAt(0, QColor("#fcd34d"))
gradient.setColorAt(1, QColor("#f59e0b"))
else:
gradient.setColorAt(0, QColor("#fbbf24"))
gradient.setColorAt(1, QColor("#d97706"))
painter.setBrush(gradient)
painter.setPen(Qt.PenStyle.NoPen)
painter.drawRoundedRect(rect, 10, 10)
# 发光效果
if self._hovered:
glow = QRadialGradient(rect.center().x(), rect.center().y(), rect.width() / 2)
glow.setColorAt(0, QColor(251, 191, 36, 60))
glow.setColorAt(1, QColor(251, 191, 36, 0))
painter.setBrush(glow)
painter.drawRoundedRect(rect.adjusted(-5, -5, 5, 5), 15, 15)
else:
# 次按钮 - 透明边框
if self._hovered:
painter.setBrush(QColor(71, 85, 105, 80))
else:
painter.setBrush(QColor(71, 85, 105, 40))
painter.setPen(QPen(QColor("#475569"), 1))
painter.drawRoundedRect(rect.adjusted(1, 1, -1, -1), 10, 10)
# 绘制文字
painter.setPen(QColor("white") if self.primary else QColor("#94a3b8"))
painter.setFont(self.font())
painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, self.text())
class ProfileCard(QDialog):
"""个人信息卡片模态框"""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowFlags(
Qt.WindowType.FramelessWindowHint |
Qt.WindowType.Dialog
)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setModal(True)
self.setFixedSize(480, 780)
self._drag_pos = None
self.setup_ui()
self.setup_animations()
self.add_decorations()
def setup_ui(self):
"""设置UI"""
# 主容器
self.container = QWidget(self)
self.container.setGeometry(0, 0, 480, 780)
main_layout = QVBoxLayout(self.container)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# 内容卡片
self.card = QWidget()
self.card.setObjectName("profile_card")
card_layout = QVBoxLayout(self.card)
card_layout.setContentsMargins(32, 24, 32, 24)
card_layout.setSpacing(12)
# 关闭按钮
close_btn = QPushButton("×")
close_btn.setFixedSize(36, 36)
close_btn.setCursor(Qt.CursorShape.PointingHandCursor)
close_btn.setStyleSheet("""
QPushButton {
background: rgba(71, 85, 105, 0.4);
border: none;
border-radius: 18px;
color: #94a3b8;
font-size: 20px;
font-weight: bold;
}
QPushButton:hover {
background: rgba(239, 68, 68, 0.6);
color: white;
}
""")
close_btn.clicked.connect(self.close_with_animation)
close_layout = QHBoxLayout()
close_layout.addStretch()
close_layout.addWidget(close_btn)
card_layout.addLayout(close_layout)
# 头像区域
avatar_container = QWidget()
avatar_container.setFixedHeight(100)
avatar_layout = QHBoxLayout(avatar_container)
avatar_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.avatar_label = QLabel()
self.avatar_label.setFixedSize(100, 100)
self.avatar_label.setStyleSheet("""
QLabel {
border-radius: 50px;
border: 3px solid #fbbf24;
}
""")
self._load_avatar()
avatar_layout.addWidget(self.avatar_label)
card_layout.addWidget(avatar_container)
# 名称
name_label = QLabel("奶酪云工具箱")
name_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
name_label.setStyleSheet("""
QLabel {
color: #f8fafc;
font-size: 22px;
font-weight: bold;
font-family: "Microsoft YaHei UI";
}
""")
card_layout.addWidget(name_label)
# 副标题
subtitle_label = QLabel("高效办公 · 精致生活")
subtitle_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
subtitle_label.setStyleSheet("""
QLabel {
color: #64748b;
font-size: 13px;
font-family: "Microsoft YaHei UI";
}
""")
card_layout.addWidget(subtitle_label)
card_layout.addSpacing(8)
# 分割线
divider = QWidget()
divider.setFixedHeight(1)
divider.setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 transparent, stop:0.5 #334155, stop:1 transparent);")
card_layout.addWidget(divider)
card_layout.addSpacing(8)
# 二维码区域
qr_container = QWidget()
qr_layout = QVBoxLayout(qr_container)
qr_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
qr_layout.setSpacing(8)
qr_title = QLabel("📱 微信扫码添加好友")
qr_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
qr_title.setStyleSheet("""
QLabel {
color: #94a3b8;
font-size: 12px;
font-family: "Microsoft YaHei UI";
}
""")
qr_layout.addWidget(qr_title)
self.qr_label = QLabel()
self.qr_label.setFixedSize(220, 220)
self.qr_label.setStyleSheet("""
QLabel {
background: white;
border-radius: 12px;
}
""")
self._load_qrcode()
qr_layout.addWidget(self.qr_label, 0, Qt.AlignmentFlag.AlignCenter)
card_layout.addWidget(qr_container)
card_layout.addSpacing(10)
# 邮箱区域
email_container = QWidget()
email_container.setStyleSheet("""
QWidget {
background: rgba(30, 41, 59, 0.6);
border-radius: 12px;
}
""")
email_layout = QVBoxLayout(email_container)
email_layout.setContentsMargins(20, 16, 20, 16)
email_layout.setSpacing(8)
email_title = QLabel("📧 合作联系邮箱")
email_title.setStyleSheet("""
QLabel {
color: #94a3b8;
font-size: 12px;
background: transparent;
}
""")
email_layout.addWidget(email_title)
email_row = QHBoxLayout()
self.email_label = QLabel("workerqi@163.com")
self.email_label.setStyleSheet("""
QLabel {
color: #fbbf24;
font-size: 16px;
font-weight: bold;
background: transparent;
}
""")
email_row.addWidget(self.email_label)
email_row.addStretch()
copy_btn = GlowButton("复制", False)
copy_btn.setFixedSize(70, 34)
copy_btn.clicked.connect(self.copy_email)
email_row.addWidget(copy_btn)
send_btn = GlowButton("发送邮件", True)
send_btn.setFixedSize(90, 34)
send_btn.clicked.connect(self.send_email)
email_row.addWidget(send_btn)
email_layout.addLayout(email_row)
card_layout.addWidget(email_container)
card_layout.addStretch()
# 底部提示
tip_label = QLabel("点击空白处关闭")
tip_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
tip_label.setStyleSheet("""
QLabel {
color: #475569;
font-size: 11px;
}
""")
card_layout.addWidget(tip_label)
main_layout.addWidget(self.card)
def _load_avatar(self):
"""加载头像"""
if os.path.exists(AVATAR_PATH):
pixmap = QPixmap(AVATAR_PATH)
if not pixmap.isNull():
# 创建圆形头像
scaled = pixmap.scaled(94, 94, Qt.AspectRatioMode.KeepAspectRatioByExpanding,
Qt.TransformationMode.SmoothTransformation)
# 裁剪为圆形
rounded = QPixmap(94, 94)
rounded.fill(Qt.GlobalColor.transparent)
painter = QPainter(rounded)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
path = QPainterPath()
path.addEllipse(0, 0, 94, 94)
painter.setClipPath(path)
# 居中裁剪
x = (scaled.width() - 94) // 2
y = (scaled.height() - 94) // 2
painter.drawPixmap(0, 0, scaled, x, y, 94, 94)
painter.end()
self.avatar_label.setPixmap(rounded)
return
# 默认头像
self.avatar_label.setText("🧀")
self.avatar_label.setStyleSheet(self.avatar_label.styleSheet() + """
font-size: 48px;
background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #fbbf24, stop:1 #d97706);
""")
def _load_qrcode(self):
"""加载二维码"""
if os.path.exists(QRCODE_PATH):
pixmap = QPixmap(QRCODE_PATH)
if not pixmap.isNull():
# 缩放到完整显示,保持宽高比
scaled = pixmap.scaled(210, 210, Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation)
self.qr_label.setPixmap(scaled)
self.qr_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
return
self.qr_label.setText("二维码")
self.qr_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
def add_decorations(self):
"""添加装饰元素"""
# 添加浮动装饰球
orb1 = FloatingOrb(120, QColor("#fbbf24"), self.container)
orb1.move(-30, -30)
orb2 = FloatingOrb(80, QColor("#3b82f6"), self.container)
orb2.move(420, 50)
orb3 = FloatingOrb(60, QColor("#8b5cf6"), self.container)
orb3.move(400, 500)
orb4 = FloatingOrb(100, QColor("#ec4899"), self.container)
orb4.move(-20, 480)
# 确保装饰在底层
orb1.lower()
orb2.lower()
orb3.lower()
orb4.lower()
def setup_animations(self):
"""设置动画"""
# 卡片淡入效果
self.card_opacity = QGraphicsOpacityEffect(self.card)
self.card_opacity.setOpacity(0)
self.card.setGraphicsEffect(self.card_opacity)
self.fade_anim = QPropertyAnimation(self.card_opacity, b"opacity")
self.fade_anim.setDuration(300)
self.fade_anim.setEasingCurve(QEasingCurve.Type.OutCubic)
def showEvent(self, event):
super().showEvent(event)
# 居中显示
if self.parent():
parent_rect = self.parent().rect()
x = (parent_rect.width() - self.width()) // 2
y = (parent_rect.height() - self.height()) // 2
self.move(self.parent().mapToGlobal(QPoint(x, y)))
# 播放淡入动画
self.fade_anim.setStartValue(0)
self.fade_anim.setEndValue(1)
self.fade_anim.start()
def close_with_animation(self):
"""带动画关闭"""
self.fade_anim.setStartValue(1)
self.fade_anim.setEndValue(0)
self.fade_anim.finished.connect(self.accept)
self.fade_anim.start()
def copy_email(self):
"""复制邮箱"""
clipboard = QApplication.clipboard()
clipboard.setText("workerqi@163.com")
# 临时显示"已复制"
original_text = self.email_label.text()
self.email_label.setText("✓ 已复制到剪贴板")
self.email_label.setStyleSheet("""
QLabel {
color: #22c55e;
font-size: 16px;
font-weight: bold;
background: transparent;
}
""")
QTimer.singleShot(1500, lambda: self._reset_email_label(original_text))
def _reset_email_label(self, text):
"""重置邮箱标签"""
self.email_label.setText(text)
self.email_label.setStyleSheet("""
QLabel {
color: #fbbf24;
font-size: 16px;
font-weight: bold;
background: transparent;
}
""")
def send_email(self):
"""发送邮件"""
import webbrowser
webbrowser.open("mailto:workerqi@163.com?subject=奶酪云工具箱-合作咨询")
def paintEvent(self, event):
"""绘制背景"""
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
# 半透明遮罩背景
painter.fillRect(self.rect(), QColor(0, 0, 0, 120))
# 卡片背景
card_rect = self.card.geometry().adjusted(20, 20, -20, -20)
# 卡片阴影
shadow_color = QColor(0, 0, 0, 80)
for i in range(10):
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(QColor(0, 0, 0, int(80 - i * 8)))
painter.drawRoundedRect(card_rect.adjusted(-i*2, -i*2, i*2, i*2), 24 + i, 24 + i)
# 卡片主体 - 玻璃质感
gradient = QLinearGradient(card_rect.topLeft(), card_rect.bottomRight())
gradient.setColorAt(0, QColor(30, 41, 59, 240))
gradient.setColorAt(0.5, QColor(15, 23, 42, 250))
gradient.setColorAt(1, QColor(30, 41, 59, 240))
painter.setBrush(gradient)
painter.setPen(QPen(QColor(71, 85, 105, 100), 1))
painter.drawRoundedRect(card_rect, 24, 24)
# 顶部高光
highlight_rect = QRect(card_rect.x() + 40, card_rect.y(), card_rect.width() - 80, 2)
highlight_gradient = QLinearGradient(highlight_rect.topLeft(), highlight_rect.topRight())
highlight_gradient.setColorAt(0, QColor(251, 191, 36, 0))
highlight_gradient.setColorAt(0.5, QColor(251, 191, 36, 150))
highlight_gradient.setColorAt(1, QColor(251, 191, 36, 0))
painter.setBrush(highlight_gradient)
painter.setPen(Qt.PenStyle.NoPen)
painter.drawRoundedRect(highlight_rect, 1, 1)
def mousePressEvent(self, event):
"""点击空白处关闭"""
card_rect = self.card.geometry().adjusted(20, 20, -20, -20)
if not card_rect.contains(event.pos()):
self.close_with_animation()
else:
self._drag_pos = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
def mouseMoveEvent(self, event):
"""拖动窗口"""
if self._drag_pos:
self.move(event.globalPosition().toPoint() - self._drag_pos)
def mouseReleaseEvent(self, event):
self._drag_pos = None