Files
tingquanzhushou/gui/components/widgets.py
2025-02-20 20:20:19 +08:00

635 lines
21 KiB
Python
Raw 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.

from typing import Dict
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
QTextEdit, QLabel, QFrame, QLineEdit, QMessageBox, QApplication, QDialog, QStyle, QProgressBar
)
from PyQt5.QtCore import (
Qt, QTimer, QPropertyAnimation, QEasingCurve,
QPoint, QRect, QSize, pyqtProperty
)
from PyQt5.QtGui import QPainter, QColor, QPen
class ActivationStatusWidget(QFrame):
"""激活状态显示组件"""
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
layout = QVBoxLayout(self)
# 状态标题
title_label = QLabel("激活状态", self)
title_label.setStyleSheet("font-size: 16px; font-weight: bold;")
layout.addWidget(title_label)
# 状态信息
self.status_label = QLabel(self)
self.status_label.setStyleSheet("font-size: 14px;")
layout.addWidget(self.status_label)
self.setStyleSheet("""
QFrame {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
}
""")
def update_status(self, status: Dict):
"""更新状态显示"""
if status["is_activated"]:
status_text = f"已激活 | 到期时间: {status['expire_time']} | 剩余天数: {status['days_left']}"
self.status_label.setStyleSheet("color: #28a745;")
else:
status_text = "未激活"
self.status_label.setStyleSheet("color: #dc3545;")
self.status_label.setText(status_text)
class ActivationWidget(QFrame):
"""激活码输入组件
职责:
1. 提供激活码输入界面
2. 处理输入框和激活按钮的基础交互
3. 将激活请求传递给父窗口处理
4. 处理激活相关的提示信息
属性:
code_input (QLineEdit): 激活码输入框
activate_btn (QPushButton): 激活按钮
信号流:
activate_btn.clicked -> activate() -> parent.handle_activation()
"""
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
# 连接回车键到激活功能
self.code_input.returnPressed.connect(self.activate)
def show_activation_required(self):
"""显示需要激活的提示"""
msg = QDialog(self)
msg.setWindowTitle("提示")
msg.setFixedWidth(360)
msg.setWindowFlags(msg.windowFlags() & ~Qt.WindowContextHelpButtonHint)
# 创建布局
layout = QVBoxLayout()
layout.setSpacing(12)
layout.setContentsMargins(20, 15, 20, 15)
# 创建顶部警告框
header_frame = QFrame()
header_frame.setStyleSheet("""
QFrame {
background-color: #fff3cd;
border: 1px solid #ffeeba;
border-radius: 4px;
}
QLabel {
background: transparent;
border: none;
}
""")
header_layout = QHBoxLayout(header_frame)
header_layout.setContentsMargins(12, 10, 12, 10)
header_layout.setSpacing(10)
icon_label = QLabel()
icon_label.setPixmap(self.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap(20, 20))
header_layout.addWidget(icon_label)
text_label = QLabel("请输入激活码")
text_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #856404; background: transparent;")
header_layout.addWidget(text_label)
header_layout.addStretch()
layout.addWidget(header_frame)
# 添加提示文本
info_text = QLabel(
"获取会员激活码,请通过以下方式:\n\n"
"官方自助网站cursorpro.com.cn\n"
"微信客服号bshkcigar\n"
"商店铺xxx\n\n"
"诚挚祝愿,欢迎加盟合作!"
)
info_text.setStyleSheet("""
QLabel {
font-size: 13px;
color: #333333;
margin: 5px 0;
}
""")
layout.addWidget(info_text)
# 添加按钮区域
btn_layout = QHBoxLayout()
btn_layout.setSpacing(8)
# 复制网站按钮
copy_web_btn = QPushButton("复制网址")
copy_web_btn.setCursor(Qt.PointingHandCursor)
copy_web_btn.clicked.connect(lambda: self._copy_to_clipboard("cursorpro.com.cn", "已复制官网地址"))
copy_web_btn.setStyleSheet("""
QPushButton {
background-color: #0d6efd;
color: white;
border: none;
padding: 6px 16px;
border-radius: 3px;
font-size: 13px;
}
QPushButton:hover {
background-color: #0b5ed7;
}
""")
btn_layout.addWidget(copy_web_btn)
# 复制微信按钮
copy_wx_btn = QPushButton("复制微信")
copy_wx_btn.setCursor(Qt.PointingHandCursor)
copy_wx_btn.clicked.connect(lambda: self._copy_to_clipboard("bshkcigar", "已复制微信号"))
copy_wx_btn.setStyleSheet("""
QPushButton {
background-color: #198754;
color: white;
border: none;
padding: 6px 16px;
border-radius: 3px;
font-size: 13px;
}
QPushButton:hover {
background-color: #157347;
}
""")
btn_layout.addWidget(copy_wx_btn)
# 确定按钮
ok_btn = QPushButton("确定")
ok_btn.setCursor(Qt.PointingHandCursor)
ok_btn.clicked.connect(msg.accept)
ok_btn.setStyleSheet("""
QPushButton {
background-color: #6c757d;
color: white;
border: none;
padding: 6px 16px;
border-radius: 3px;
font-size: 13px;
}
QPushButton:hover {
background-color: #5c636a;
}
""")
btn_layout.addWidget(ok_btn)
layout.addLayout(btn_layout)
# 设置对话框样式和布局
msg.setStyleSheet("""
QDialog {
background-color: white;
}
""")
msg.setLayout(layout)
# 显示对话框
msg.exec_()
def _copy_to_clipboard(self, text: str, status_msg: str):
"""复制文本到剪贴板并更新状态栏"""
QApplication.clipboard().setText(text)
main_window = self.window()
if main_window and hasattr(main_window, 'status_bar'):
main_window.status_bar.set_status(status_msg)
def setup_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 10) # 移除边距
# 标题
title = QLabel("激活(盖加)会员,多个激活码可叠加整体时长")
title.setStyleSheet("color: #28a745; font-size: 14px; font-weight: bold;") # 绿色标题
layout.addWidget(title)
# 激活码输入区域
input_layout = QHBoxLayout()
self.code_input = QLineEdit()
self.code_input.setPlaceholderText("请输入激活码")
self.code_input.setMinimumWidth(300)
self.code_input.setStyleSheet("""
QLineEdit {
background-color: #f8f9fa;
border: 1px solid #ced4da;
border-radius: 4px;
padding: 8px;
color: #495057;
}
""")
input_layout.addWidget(self.code_input)
# 激活按钮
self.activate_btn = QPushButton("激活")
self.activate_btn.clicked.connect(self.activate)
self.activate_btn.setStyleSheet("""
QPushButton {
background-color: #007bff;
color: white;
border: none;
padding: 8px 22px;
border-radius: 4px;
font-size: 13px;
}
QPushButton:hover {
background-color: #0056b3;
}
""")
input_layout.addWidget(self.activate_btn)
layout.addLayout(input_layout)
self.setStyleSheet("""
QFrame {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
}
""")
def activate(self):
"""处理激活"""
try:
code = self.code_input.text().strip()
# 获取主窗口实例
main_window = self.window()
if not main_window:
print("错误:找不到主窗口")
return
if not hasattr(main_window, 'handle_activation'):
print("错误:主窗口没有 handle_activation 方法")
return
if not code:
print("警告:激活码为空")
self.show_activation_required()
return
print(f"正在处理激活码:{code}")
main_window.handle_activation(code)
except Exception as e:
print(f"激活处理发生错误:{str(e)}")
def clear_input(self):
"""清空输入框"""
self.code_input.clear()
class LogWidget(QTextEdit):
"""日志显示组件"""
def __init__(self, parent=None):
super().__init__(parent)
self.setReadOnly(True)
self.setStyleSheet("""
QTextEdit {
background-color: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
font-family: 'Consolas', 'Microsoft YaHei', monospace;
font-size: 12px;
}
""")
def append_log(self, text: str):
"""添加日志并滚动到底部"""
self.append(text)
self.verticalScrollBar().setValue(
self.verticalScrollBar().maximum()
)
class StatusBar(QFrame):
"""状态栏组件"""
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
"""初始化UI"""
layout = QHBoxLayout(self)
layout.setContentsMargins(10, 5, 10, 5)
self.status_label = QLabel("就绪")
self.status_label.setStyleSheet("font-size: 14px; color: #333;")
layout.addWidget(self.status_label)
def set_status(self, text: str):
"""设置状态文本"""
self.status_label.setText(text)
class ActionButton(QPushButton):
"""操作按钮基类"""
def __init__(self, text: str, color: str, parent=None):
super().__init__(text, parent)
self.setStyleSheet(f"""
QPushButton {{
background-color: {color};
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
min-height: 36px;
}}
QPushButton:hover {{
background-color: {self._get_hover_color(color)};
}}
QPushButton:disabled {{
background-color: #ccc;
}}
""")
def _get_hover_color(self, color: str) -> str:
"""获取悬停颜色"""
# 简单的颜色加深处理
if color == "#007bff": # 蓝色
return "#0056b3"
elif color == "#28a745": # 绿色
return "#218838"
return color
class MemberStatusWidget(QFrame):
"""会员状态显示组件
职责:
1. 显示会员状态信息
2. 显示设备信息
3. 根据不同状态显示不同样式
属性:
status_text (QTextEdit): 状态信息显示区域
显示信息:
- 会员状态(正常/未激活)
- 到期时间
- 剩余天数
- 设备信息系统、设备名、IP、地区
"""
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 10)
# 创建一个容器 Frame
container = QFrame(self)
container.setObjectName("memberStatusContainer")
container_layout = QVBoxLayout(container)
container_layout.setSpacing(5) # 减小组件间距
container_layout.setContentsMargins(0,0,0,0) # 减小内边距
container.setFixedHeight(220) # 根据需要调整这个值
# 状态信息
self.status_text = QTextEdit()
self.status_text.setReadOnly(True)
self.status_text.setStyleSheet("""
QTextEdit {
background-color: #f8fafc;
border: none;
border-radius: 6px;
padding: 20px;
color: #4a5568;
font-size: 15px;
line-height: 1.5;
}
/* 滚动条整体样式 */
QScrollBar:vertical {
border: none;
background: #f8fafc;
width: 6px;
margin: 0px;
}
/* 滚动条滑块 */
QScrollBar::handle:vertical {
background: #e2e8f0;
border-radius: 3px;
min-height: 20px;
}
QScrollBar::handle:vertical:hover {
background: #cbd5e1;
}
/* 滚动条上下按钮 */
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
border: none;
background: none;
height: 0px;
}
/* 滚动条背景 */
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
""")
container_layout.addWidget(self.status_text)
# 设置容器样式
container.setStyleSheet("""
#memberStatusContainer {
background-color: white;
border: 1px solid #e2e8f0;
border-radius: 6px;
}
#titleContainer {
background: transparent;
}
""")
layout.addWidget(container)
def update_status(self, status: Dict):
"""更新状态显示"""
try:
# 获取状态信息
is_activated = status.get("is_activated", False)
expire_time = status.get("expire_time", "未知")
days_left = status.get("days_left", 0)
device_info = status.get("device_info", {})
# 构建状态文本
status_text = []
# 会员状态
status_color = "#10B981" if is_activated else "#EF4444" # 绿色或红色
status_text.append(f'会员状态:<span style="color: {status_color};">{("正常" if is_activated else "未激活")}</span>')
# 到期时间和剩余天数
if is_activated:
status_text.append(f"到期时间:{expire_time}")
# 根据剩余天数显示不同颜色
days_color = "#10B981" if days_left > 30 else "#F59E0B" if days_left > 7 else "#EF4444"
status_text.append(f'剩余天数:<span style="color: {days_color};">{days_left}天</span>')
# 设备信息
status_text.append("\n设备信息:")
status_text.append(f"系统:{device_info.get('os', 'Windows')}")
status_text.append(f"设备名:{device_info.get('device_name', '未知')}")
status_text.append(f"IP地址{device_info.get('ip', '未知')}")
status_text.append(f"地区:{device_info.get('location', '未知')}")
# 更新文本
self.status_text.setHtml("<br>".join(status_text))
except Exception as e:
# 如果发生异常,显示错误信息
error_text = f'<span style="color: #EF4444;">状态更新失败: {str(e)}</span>'
self.status_text.setHtml(error_text)
class LoadingSpinner(QWidget):
"""自定义加载动画组件"""
def __init__(self, parent=None, size=40, line_width=3):
super().__init__(parent)
self.setFixedSize(size, size)
self.angle = 0
self.line_width = line_width
self._size = size
self._speed = 30 # 每次更新转动的角度
# 设置动画
self.animation = QPropertyAnimation(self, b"rotation")
self.animation.setDuration(800) # 缩短动画时间到800ms使其更流畅
self.animation.setStartValue(0)
self.animation.setEndValue(360)
self.animation.setLoopCount(-1) # 无限循环
# 使用 InOutQuad 缓动曲线让动画更自然
curve = QEasingCurve(QEasingCurve.InOutQuad)
self.animation.setEasingCurve(curve)
# 创建定时器用于额外的动画效果
self.timer = QTimer(self)
self.timer.timeout.connect(self.rotate)
self.timer.setInterval(50) # 20fps的刷新率
def paintEvent(self, event):
"""绘制旋转的圆弧"""
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# 计算圆的位置
rect = QRect(self.line_width, self.line_width,
self._size - 2*self.line_width,
self._size - 2*self.line_width)
# 设置画笔
pen = QPen()
pen.setWidth(self.line_width)
pen.setCapStyle(Qt.RoundCap)
# 绘制渐变圆弧
segments = 8
for i in range(segments):
# 计算每个段的透明度
alpha = int(255 * (1 - i / segments))
# 使用更鲜艳的蓝色
pen.setColor(QColor(0, 122, 255, alpha))
painter.setPen(pen)
# 计算每个段的起始角度和跨度
start_angle = (self.angle - i * (360 // segments)) % 360
span = 30 # 每个段的跨度
# 转换角度为16分之一度(Qt的角度单位)
start_angle_16 = start_angle * 16
span_16 = span * 16
painter.drawArc(rect, -start_angle_16, -span_16)
def rotate(self):
"""更新旋转角度"""
self.angle = (self.angle + self._speed) % 360
self.update()
@pyqtProperty(int)
def rotation(self):
return self.angle
@rotation.setter
def rotation(self, angle):
self.angle = angle
self.update()
def start(self):
"""开始动画"""
self.animation.start()
self.timer.start()
def stop(self):
"""停止动画"""
self.animation.stop()
self.timer.stop()
class LoadingDialog(QDialog):
"""加载对话框组件"""
def __init__(self, parent=None, message="请稍候..."):
super().__init__(parent)
self.setWindowTitle("处理中")
self.setFixedSize(300, 120)
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
layout = QVBoxLayout()
layout.setSpacing(15)
# 消息标签
self.message_label = QLabel(message)
self.message_label.setAlignment(Qt.AlignCenter)
self.message_label.setStyleSheet("""
color: #0d6efd;
font-size: 14px;
font-weight: bold;
padding: 10px;
""")
layout.addWidget(self.message_label)
# 加载动画
self.spinner = LoadingSpinner(self, size=48, line_width=4) # 增大尺寸
spinner_layout = QHBoxLayout()
spinner_layout.addStretch()
spinner_layout.addWidget(self.spinner)
spinner_layout.addStretch()
layout.addLayout(spinner_layout)
self.setLayout(layout)
# 设置样式
self.setStyleSheet("""
QDialog {
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
}
""")
def showEvent(self, event):
"""显示时启动动画"""
super().showEvent(event)
self.spinner.start()
def hideEvent(self, event):
"""隐藏时停止动画"""
self.spinner.stop()
super().hideEvent(event)
def set_message(self, message: str):
"""更新消息文本"""
self.message_label.setText(message)