558 lines
22 KiB
Python
558 lines
22 KiB
Python
import time
|
|
from enum import Enum
|
|
|
|
from loguru import logger
|
|
from PyQt6.QtCore import QDate, QDateTime, Qt, QThreadPool, QTime, QTimer
|
|
from PyQt6.QtGui import QAction, QColor, QIcon
|
|
from PyQt6.QtWidgets import (QApplication, QCheckBox, QDateEdit, QHBoxLayout,
|
|
QHeaderView, QMainWindow, QMenu, QMessageBox,
|
|
QPushButton, QSizePolicy, QSystemTrayIcon,
|
|
QTableWidget, QTableWidgetItem, QTabWidget,
|
|
QTextEdit, QVBoxLayout, QWidget)
|
|
|
|
from src.ui.config import ConfigManager
|
|
from src.core.message_client import send_message
|
|
from src.core.util import convert_data, resource_path
|
|
from src.entity.member import get_today_new_member_list
|
|
from src.entity.pay_record import get_latest_deposit_user
|
|
from src.entity.user import get_user_by_username_and_password
|
|
from src.ui import global_signals
|
|
from src.ui.data_query import ButtonTask, ReportTask
|
|
from src.ui.title_bar import CustomTitleBar
|
|
|
|
|
|
class Application(QMainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.config_manager = ConfigManager()
|
|
self.thread_pool = QThreadPool()
|
|
self.report_timer = None
|
|
self.date_update_timer = None
|
|
self.username = None
|
|
self.password = None
|
|
self.minimum = False
|
|
self.customTitleBar = None
|
|
self.tray_icon = None
|
|
self.tables = {}
|
|
self.is_dragging = False
|
|
self.drag_position = None
|
|
self.is_resizing = False
|
|
self.resize_direction = None
|
|
self.toaster_notify_enabled = True
|
|
self.telegram_notify_enabled = True
|
|
self.initialize_application()
|
|
|
|
def initialize_application(self):
|
|
# 1. 加载配置文件
|
|
self.load_config()
|
|
|
|
# 2. 设置 UI
|
|
self.setup_ui()
|
|
|
|
# 4. 初始化系统托盘图标
|
|
self.init_tray_icon()
|
|
|
|
# 5. 初始化表格数据
|
|
self.init_table_data()
|
|
|
|
self.setup_timers()
|
|
|
|
self.connect_signals()
|
|
|
|
def setup_ui(self):
|
|
self.apply_stylesheet()
|
|
self.set_window_properties()
|
|
self.create_central_widget()
|
|
self.setup_layouts()
|
|
|
|
def setup_timers(self):
|
|
self.setup_date_update_timer()
|
|
self.setup_report_timer()
|
|
|
|
def connect_signals(self):
|
|
self.tray_icon.activated.connect(self.tray_icon_clicked)
|
|
global_signals.user_data_updated.connect(self.refresh_user_data)
|
|
|
|
def set_window_properties(self):
|
|
self.resize(600, 400)
|
|
self.setWindowTitle("zayac的小工具")
|
|
self.setWindowIcon(QIcon("icons:icon.png"))
|
|
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
|
|
|
|
def create_central_widget(self):
|
|
self.central_widget = QWidget()
|
|
self.setCentralWidget(self.central_widget)
|
|
self.main_layout = QVBoxLayout(self.central_widget)
|
|
|
|
def setup_layouts(self):
|
|
self.setup_top_panel()
|
|
self.setup_middle_panel()
|
|
self.setup_bottom_panel()
|
|
self.customTitleBar = CustomTitleBar(self)
|
|
self.setMenuWidget(self.customTitleBar)
|
|
|
|
def init_tray_icon(self):
|
|
self.tray_icon = SystemTrayIcon(QIcon("icons:icon.png"), self) # 替换为正确的图标路径
|
|
self.tray_icon.show()
|
|
|
|
def load_config(self):
|
|
# 使用
|
|
try:
|
|
self.username = self.config_manager.get('Credentials', 'username')
|
|
self.password = self.config_manager.get('Credentials', 'password')
|
|
self.minimum = self.config_manager.get('Minimum', 'minimum')
|
|
self.user = get_user_by_username_and_password(self.username, self.password)
|
|
except FileNotFoundError as e:
|
|
QMessageBox.warning(None, "警告", "配置文件信息加载失败!请检查文件是否存在")
|
|
return None, None
|
|
|
|
def init_table_data(self):
|
|
# 初始化表格数据
|
|
data = self.query_initial_data(self.user)
|
|
for row in data:
|
|
time_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
|
|
self.update_table(row.agentCode, [time_str, row.registerMembers, row.firstDepositNum, row.netWinLose,
|
|
row.effectiveNew, row.activeMembers])
|
|
|
|
def query_initial_data(self, user):
|
|
return ReportTask.get_banner_info_by_user(user)
|
|
|
|
def setup_report_timer(self):
|
|
self.report_timer = QTimer(self)
|
|
self.report_timer.timeout.connect(self.update_reports)
|
|
self.report_timer.start(60000)
|
|
|
|
def update_reports(self):
|
|
for account in self.user.accounts:
|
|
report_task = ReportTask(account)
|
|
report_task.signals.table_updated.connect(self.update_table)
|
|
self.thread_pool.start(report_task)
|
|
|
|
def apply_stylesheet(self):
|
|
style_sheet = self.load_stylesheet(resource_path('ui/style.qss'))
|
|
self.setStyleSheet(style_sheet)
|
|
|
|
def refresh_user_data(self):
|
|
# 刷新用户数据的逻辑
|
|
self.user = get_user_by_username_and_password(self.username, self.password) # 重新加载用户
|
|
|
|
@staticmethod
|
|
def load_stylesheet(file_path):
|
|
try:
|
|
with open(file_path, "r", encoding='utf-8') as file:
|
|
return file.read()
|
|
except FileNotFoundError:
|
|
print(f"无法找到样式文件: {file_path}")
|
|
return ""
|
|
|
|
def get_account_by_account_username(self, username: str):
|
|
for account in self.user.accounts:
|
|
if account.username == username:
|
|
return account
|
|
|
|
def setup_top_panel(self):
|
|
try:
|
|
self.top_panel = QHBoxLayout()
|
|
self.add_buttons_to_top_panel()
|
|
self.add_date_picker_to_top_panel()
|
|
self.add_checkboxes_to_top_panel()
|
|
self.setup_report_button()
|
|
self.main_layout.addLayout(self.top_panel)
|
|
except Exception as e:
|
|
logger.error(f"Error setting up top panel: {e}")
|
|
|
|
def add_date_picker_to_top_panel(self):
|
|
# 创建日期选择器
|
|
self.dateEdit = QDateEdit()
|
|
self.dateEdit.setCalendarPopup(True)
|
|
self.dateEdit.setDate(QDate.currentDate())
|
|
|
|
# 设置日期范围
|
|
today = QDate.currentDate()
|
|
first_day_last_month = QDate(today.year(), today.month(), 1).addMonths(-1)
|
|
self.dateEdit.setMinimumDate(first_day_last_month)
|
|
self.dateEdit.setMaximumDate(today)
|
|
|
|
# 将日期选择器添加到顶部面板
|
|
self.top_panel.addWidget(self.dateEdit)
|
|
|
|
def setup_date_update_timer(self):
|
|
self.date_update_timer = QTimer(self)
|
|
self.date_update_timer.timeout.connect(self.update_date_edit)
|
|
self.start_date_update_timer()
|
|
|
|
def start_date_update_timer(self):
|
|
now = QDateTime.currentDateTime()
|
|
next_midnight = QDateTime(now.date().addDays(1), QTime(0, 0)) # 设置为次日的午夜 00:00
|
|
interval = now.msecsTo(next_midnight)
|
|
self.date_update_timer.start(interval if interval > 0 else 86400000) # 86400000ms = 24小时
|
|
|
|
def update_date_edit(self):
|
|
self.dateEdit.setDate(QDate.currentDate()) # 设置 QDateEdit 控件的日期为当前日期
|
|
self.update_date_range()
|
|
self.start_date_update_timer()
|
|
|
|
def update_date_range(self):
|
|
today = QDate.currentDate()
|
|
first_day_last_month = QDate(today.year(), today.month(), 1).addMonths(-1)
|
|
self.dateEdit.setMinimumDate(first_day_last_month)
|
|
self.dateEdit.setMaximumDate(today)
|
|
|
|
def setup_report_button(self):
|
|
self.report_button = QPushButton("停止喜报")
|
|
self.report_button.setCheckable(True)
|
|
self.report_button.setChecked(True) # 默认设置为选中状态
|
|
self.report_button.setObjectName("Warning")
|
|
self.report_button.clicked.connect(self.on_report_clicked)
|
|
self.top_panel.addWidget(self.report_button)
|
|
|
|
def add_buttons_to_top_panel(self):
|
|
for name, style in self.get_buttons_info():
|
|
self.create_and_add_button(name, style)
|
|
|
|
def get_buttons_info(self):
|
|
return [
|
|
("报数", "Primary"),
|
|
("存款失败用户", "Danger"),
|
|
("负盈利", "Success"),
|
|
("薪资", "Light"),
|
|
]
|
|
|
|
def create_and_add_button(self, name, style):
|
|
button = QPushButton(name)
|
|
button.setObjectName(style)
|
|
button.clicked.connect(lambda _, n=name: self.query_data(n))
|
|
self.top_panel.addWidget(button)
|
|
|
|
def add_checkboxes_to_top_panel(self):
|
|
# 创建垂直布局来放置复选框
|
|
checkbox_layout = QVBoxLayout()
|
|
|
|
# 添加复选框
|
|
self.system_notification_checkbox = QCheckBox("系统通知")
|
|
self.telegram_notification_checkbox = QCheckBox("飞机通知")
|
|
self.system_notification_checkbox.setChecked(True)
|
|
self.telegram_notification_checkbox.setChecked(True)
|
|
self.system_notification_checkbox.stateChanged.connect(self.toggle_system_notification)
|
|
self.telegram_notification_checkbox.stateChanged.connect(self.toggle_telegram_notification)
|
|
checkbox_layout.addWidget(self.system_notification_checkbox)
|
|
checkbox_layout.addWidget(self.telegram_notification_checkbox)
|
|
|
|
# 将复选框布局添加到顶部面板
|
|
self.top_panel.addLayout(checkbox_layout)
|
|
|
|
def toggle_system_notification(self, state):
|
|
self.toaster_notify_enabled = state == Qt.CheckState.Checked
|
|
|
|
def toggle_telegram_notification(self, state):
|
|
self.telegram_notify_enabled = state == Qt.CheckState.Checked
|
|
|
|
def query_data(self, btn_name):
|
|
# 获取日期控件的当前值
|
|
selected_date = self.dateEdit.date()
|
|
|
|
# 转换为所需的格式
|
|
selected_date_str = selected_date.toString("yyyy-MM-dd")
|
|
# 在文本框中显示查询中的消息
|
|
self.txt.append(f"正在查询{selected_date_str}的{btn_name},请等待...\n")
|
|
|
|
self.start_data_query(btn_name, selected_date_str)
|
|
|
|
def start_data_query(self, query_type, selected_date_str):
|
|
task = ButtonTask(query_type, selected_date_str, self.user)
|
|
task.signals.query_completed.connect(self.display_query_result)
|
|
self.thread_pool.start(task)
|
|
|
|
def display_query_result(self, result, auto_clipboard, need_notify):
|
|
if auto_clipboard:
|
|
copy_to_clipboard(result)
|
|
if need_notify and '无' not in result:
|
|
self.tray_icon.showMessage("", "自动复制成功", QSystemTrayIcon.MessageIcon.Information, 500)
|
|
self.txt.append(result)
|
|
|
|
def send_notification(self, emoji, account_name, title, count, results, total):
|
|
msg = f'{emoji} {account_name} {title}:{count} {results} 总数:*{total}*'
|
|
if self.toaster_notify_enabled:
|
|
self.tray_icon.showMessage(f"{title}通知", msg, QSystemTrayIcon.MessageIcon.Information, 2000)
|
|
if self.telegram_notify_enabled:
|
|
send_message(self.user.bot_token, self.user.group_id, msg)
|
|
|
|
def on_report_clicked(self):
|
|
try:
|
|
if self.report_button.isChecked():
|
|
self.report_timer.start(60000) # 启动定时器
|
|
self.report_button.setText("停止喜报") # 更改按钮文本
|
|
# 更改按钮样式为 "Warning"
|
|
self.report_button.setObjectName("Warning")
|
|
else:
|
|
self.report_timer.stop() # 停止定时器
|
|
self.report_button.setText("启动喜报") # 恢复按钮文本
|
|
self.report_button.setObjectName("Success")
|
|
# 重新应用样式来更新按钮外观
|
|
self.apply_stylesheet()
|
|
except Exception as e:
|
|
logger.debug(e)
|
|
|
|
def update_table(self, account_username, data):
|
|
try:
|
|
table = self.tables.get(account_username)
|
|
if not table:
|
|
return
|
|
self.ensure_table_row_limit(table)
|
|
self.insert_data_in_table(table, data, account_username)
|
|
except Exception as e:
|
|
logger.error(f"Error updating table for {account_username}: {e}")
|
|
|
|
def ensure_table_row_limit(self, table, row_limit=20):
|
|
if table.rowCount() >= row_limit:
|
|
table.removeRow(0)
|
|
|
|
def insert_data_in_table(self, table, data, account_username):
|
|
# 获取当前的行数
|
|
row_count = table.rowCount()
|
|
table.insertRow(row_count)
|
|
|
|
notifications = []
|
|
for col, cell_data in enumerate(data):
|
|
self.set_table_item(table, row_count, col, cell_data)
|
|
self.check_data_change_for_notifications(table, row_count, col, cell_data, account_username, notifications)
|
|
|
|
self.send_all_notifications(notifications)
|
|
|
|
def send_all_notifications(self, notifications):
|
|
"""发送所有通知"""
|
|
for notification in notifications:
|
|
self.send_notification(*notification)
|
|
|
|
def set_table_item(self, table, row, col, cell_data):
|
|
"""在表格指定行列设置单元格数据"""
|
|
cell = QTableWidgetItem(str(cell_data))
|
|
cell.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
table.setItem(row, col, cell)
|
|
|
|
def check_data_change_for_notifications(self, table, row, col, new_data, account_username, notifications):
|
|
"""检查数据变更并准备通知"""
|
|
if row > 0 and col != 0: # 忽略第一列,通常是时间戳
|
|
old_data_str = table.item(row - 1, col).text()
|
|
old_data = convert_data(old_data_str)
|
|
new_data = convert_data(new_data)
|
|
if old_data != new_data:
|
|
count_change = new_data - old_data
|
|
self.update_cell_color(table, row, col, count_change)
|
|
# 确保传递 cell_data 和 count_change 参数
|
|
self.generate_notifications(account_username, col, new_data, count_change, notifications)
|
|
def update_cell_color(self, table, row, col, count_change):
|
|
"""更新单元格颜色"""
|
|
cell = table.item(row, col)
|
|
if count_change > 0:
|
|
cell.setForeground(QColor(Qt.GlobalColor.green))
|
|
elif count_change < 0:
|
|
cell.setForeground(QColor(Qt.GlobalColor.red))
|
|
|
|
def generate_notifications(self, account_username, col, cell_data, count_change, notifications):
|
|
# 生成通知
|
|
account = self.get_account_by_account_username(account_username)
|
|
if count_change > 0:
|
|
if col == 1: # 第1列是注册用户数量
|
|
reg_results = ','.join(
|
|
[f'`{member.name}`' for member in get_today_new_member_list(account, count_change)])
|
|
notifications.append(
|
|
('👏', account.name, '注册', count_change, f'用户: {reg_results}', str(cell_data)))
|
|
elif col == 2: # 第2列是首存用户数量
|
|
deposit_results = '\n'.join(
|
|
[f"用户: `{member.name}`, 首存金额: *{member.deposit}*" for member in
|
|
get_latest_deposit_user(account, count_change)])
|
|
notifications.append(
|
|
('🎉', account.name, '首存', count_change, deposit_results, str(cell_data)))
|
|
|
|
def addToggleButton(self, text, style):
|
|
toggleButton = QPushButton(text)
|
|
toggleButton.setCheckable(True)
|
|
toggleButton.setStyleSheet(style)
|
|
self.top_panel.addWidget(toggleButton)
|
|
|
|
def setup_middle_panel(self):
|
|
# 底部面板,包括文本框
|
|
self.bottom_panel = QVBoxLayout()
|
|
self.txt = QTextEdit()
|
|
self.bottom_panel.addWidget(self.txt)
|
|
self.main_layout.addLayout(self.bottom_panel)
|
|
|
|
def setup_bottom_panel(self):
|
|
# 中间面板,包括笔记本(标签页)
|
|
self.middle_panel = QVBoxLayout()
|
|
self.notebook = QTabWidget()
|
|
self.middle_panel.addWidget(self.notebook)
|
|
self.add_tabs(self.notebook)
|
|
self.main_layout.addLayout(self.middle_panel)
|
|
|
|
def add_tabs(self, notebook):
|
|
column_headers = ColumnHeaders.list()
|
|
for account in self.user.accounts:
|
|
tab = QWidget()
|
|
tab_layout = QVBoxLayout(tab)
|
|
notebook.addTab(tab, account.name)
|
|
|
|
table = self.create_table_for_tab(column_headers)
|
|
tab_layout.addWidget(table)
|
|
self.tables[account.username] = table
|
|
|
|
def create_table_for_tab(self, column_headers):
|
|
table = QTableWidget()
|
|
table.setColumnCount(len(column_headers))
|
|
table.setHorizontalHeaderLabels(column_headers)
|
|
table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
|
|
|
header = table.horizontalHeader()
|
|
header.setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
|
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
|
|
|
|
table.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding))
|
|
return table
|
|
|
|
def mousePressEvent(self, event):
|
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
self.drag_position = event.globalPosition().toPoint()
|
|
self.resize_direction = self.get_resize_direction(event.pos())
|
|
if self.resize_direction:
|
|
self.is_resizing = True
|
|
elif self.is_draggable_area(event.pos()):
|
|
self.is_dragging = True
|
|
else:
|
|
return
|
|
super().mousePressEvent(event)
|
|
|
|
def is_draggable_area(self, pos):
|
|
# 将点转换为标题栏的局部坐标
|
|
title_bar_pos = self.customTitleBar.mapFromParent(pos)
|
|
|
|
# 检查点是否在标题栏内
|
|
return self.customTitleBar.rect().contains(title_bar_pos)
|
|
|
|
# 重置鼠标样式
|
|
def reset_cursor_style(self):
|
|
if not (self.is_dragging or self.is_resizing):
|
|
self.setCursor(Qt.CursorShape.ArrowCursor)
|
|
|
|
def mouseMoveEvent(self, event):
|
|
direction = self.get_resize_direction(event.pos())
|
|
# 更新鼠标样式
|
|
if direction == "left" or direction == "right":
|
|
self.setCursor(Qt.CursorShape.SizeHorCursor)
|
|
elif direction == "top" or direction == "bottom":
|
|
self.setCursor(Qt.CursorShape.SizeVerCursor)
|
|
elif direction in ["top-left", "bottom-right"]:
|
|
self.setCursor(Qt.CursorShape.SizeFDiagCursor)
|
|
elif direction in ["top-right", "bottom-left"]:
|
|
self.setCursor(Qt.CursorShape.SizeBDiagCursor)
|
|
else:
|
|
self.reset_cursor_style()
|
|
# 处理窗口拖动
|
|
if self.is_dragging:
|
|
self.move(self.pos() + (event.globalPosition().toPoint() - self.drag_position))
|
|
self.drag_position = event.globalPosition().toPoint()
|
|
|
|
# 处理窗口调整大小
|
|
elif self.is_resizing:
|
|
self.resize_window(event.globalPosition().toPoint())
|
|
|
|
super().mouseMoveEvent(event)
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
self.is_dragging = False
|
|
self.is_resizing = False
|
|
super().mouseReleaseEvent(event)
|
|
|
|
def get_resize_direction(self, pos):
|
|
border_width = 10 # 边缘感应区的宽度
|
|
rect = self.rect()
|
|
left, right, top, bottom = rect.left(), rect.right(), rect.top(), rect.bottom()
|
|
|
|
if pos.x() < left + border_width and pos.y() < top + border_width:
|
|
return "top-left"
|
|
if pos.x() > right - border_width and pos.y() < top + border_width:
|
|
return "top-right"
|
|
if pos.x() < left + border_width and pos.y() > bottom - border_width:
|
|
return "bottom-left"
|
|
if pos.x() > right - border_width and pos.y() > bottom - border_width:
|
|
return "bottom-right"
|
|
if pos.x() < left + border_width:
|
|
return "left"
|
|
if pos.x() > right - border_width:
|
|
return "right"
|
|
if pos.y() < top + border_width:
|
|
return "top"
|
|
if pos.y() > bottom - border_width:
|
|
return "bottom"
|
|
return None
|
|
|
|
def resize_window(self, current_pos):
|
|
if not self.resize_direction:
|
|
return
|
|
|
|
delta = current_pos - self.drag_position
|
|
rect = self.geometry()
|
|
|
|
if "left" in self.resize_direction:
|
|
rect.setLeft(rect.left() + delta.x())
|
|
if "right" in self.resize_direction:
|
|
rect.setRight(rect.right() + delta.x())
|
|
if "top" in self.resize_direction:
|
|
rect.setTop(rect.top() + delta.y())
|
|
if "bottom" in self.resize_direction:
|
|
rect.setBottom(rect.bottom() + delta.y())
|
|
|
|
self.setGeometry(rect)
|
|
self.drag_position = current_pos
|
|
|
|
def closeEvent(self, event):
|
|
if self.minimum:
|
|
event.ignore()
|
|
self.hide()
|
|
else:
|
|
super().closeEvent(event)
|
|
|
|
def exit_application(self):
|
|
self.tray_icon.hide() # 隐藏托盘图标
|
|
QApplication.quit()
|
|
|
|
def tray_icon_clicked(self, reason):
|
|
if reason == QSystemTrayIcon.ActivationReason.Trigger:
|
|
if self.isMinimized() or not self.isVisible():
|
|
self.showNormal() # 恢复窗口大小
|
|
self.activateWindow() # 激活窗口
|
|
else:
|
|
self.hide() # 点击托盘图标再次隐藏窗口
|
|
|
|
|
|
def copy_to_clipboard(text):
|
|
clipboard = QApplication.clipboard()
|
|
clipboard.setText(text)
|
|
|
|
|
|
class SystemTrayIcon(QSystemTrayIcon):
|
|
def __init__(self, icon, parent=None):
|
|
super().__init__(icon, parent)
|
|
self.menu = QMenu(parent)
|
|
self.setContextMenu(self.menu)
|
|
self.create_actions(parent)
|
|
|
|
def create_actions(self, parent):
|
|
exit_action = QAction("Exit", parent)
|
|
exit_action.triggered.connect(parent.exit_application)
|
|
self.menu.addAction(exit_action)
|
|
|
|
|
|
class ColumnHeaders(Enum):
|
|
TIME = "时间"
|
|
REGISTER = "注册"
|
|
FIRST_DEPOSIT = "首存"
|
|
NET_PROFIT = "负盈利"
|
|
EFFECTIVE = "有效"
|
|
ACTIVE = "活跃"
|
|
|
|
@classmethod
|
|
def list(cls):
|
|
return [cls.TIME.value, cls.REGISTER.value, cls.FIRST_DEPOSIT.value,
|
|
cls.NET_PROFIT.value, cls.EFFECTIVE.value, cls.ACTIVE.value]
|