Files
nl-tools/ui/profile_card.py
2025-12-11 10:30:15 +08:00

546 lines
18 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
个人信息卡片模态框
- 头像、微信二维码、邮箱
- 高级UI设计带装饰元素和动画
"""
import os
import sys
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:
"""获取资源文件的绝对路径(兼容打包后)"""
if getattr(sys, 'frozen', False):
# 打包后的路径
base_path = Path(sys._MEIPASS)
else:
# 开发时的路径
base_path = Path(__file__).parent.parent
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