feat(ui): 初步完成历史记录前端适配

This commit is contained in:
DLmaster
2025-02-21 12:04:52 +08:00
parent 3a9c670172
commit 5d7227c009
10 changed files with 606 additions and 272 deletions

View File

@@ -30,6 +30,9 @@ import sqlite3
import json
import sys
import shutil
import re
from datetime import datetime, timedelta
from collections import defaultdict
from pathlib import Path
from qfluentwidgets import (
QConfig,
@@ -42,6 +45,7 @@ from qfluentwidgets import (
OptionsValidator,
qconfig,
)
from typing import Union, Dict, List
class AppConfig:
@@ -54,7 +58,7 @@ class AppConfig:
self.log_path = self.app_path / "debug/AUTO_MAA.log"
self.database_path = self.app_path / "data/data.db"
self.config_path = self.app_path / "config/config.json"
self.history_path = self.app_path / "config/history.json"
self.history_path = self.app_path / "history/main.json"
self.key_path = self.app_path / "data/key"
self.gameid_path = self.app_path / "data/gameid.txt"
self.version_path = self.app_path / "resources/version.json"
@@ -74,6 +78,7 @@ class AppConfig:
(self.app_path / "config").mkdir(parents=True, exist_ok=True)
(self.app_path / "data").mkdir(parents=True, exist_ok=True)
(self.app_path / "debug").mkdir(parents=True, exist_ok=True)
(self.app_path / "history").mkdir(parents=True, exist_ok=True)
# 生成版本信息文件
if not self.version_path.exists():
@@ -461,6 +466,206 @@ class AppConfig:
cur.close()
db.close()
def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> None:
"""保存MAA日志"""
log_path.parent.mkdir(parents=True, exist_ok=True)
data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = {
"recruit_statistics": defaultdict(int),
"drop_statistics": defaultdict(dict),
"maa_result": maa_result,
}
# 公招统计(仅统计招募到的)
confirmed_recruit = False
current_star_level = None
i = 0
while i < len(logs):
if "公招识别结果:" in logs[i]:
current_star_level = None # 每次识别公招时清空之前的星级
i += 1
while i < len(logs) and "Tags" not in logs[i]: # 读取所有公招标签
i += 1
if i < len(logs) and "Tags" in logs[i]: # 识别星级
star_match = re.search(r"(\d+)\s*★ Tags", logs[i])
if star_match:
current_star_level = f"{star_match.group(1)}"
if "已确认招募" in logs[i]: # 只有确认招募后才统计
confirmed_recruit = True
if confirmed_recruit and current_star_level:
data["recruit_statistics"][current_star_level] += 1
confirmed_recruit = False # 重置,等待下一次公招
current_star_level = None # 清空已处理的星级
i += 1
# 掉落统计
current_stage = None
stage_drops = {}
for i, line in enumerate(logs):
drop_match = re.search(r"([A-Za-z0-9\-]+) 掉落统计:", line)
if drop_match:
# 发现新关卡,保存前一个关卡数据
if current_stage and stage_drops:
data["drop_statistics"][current_stage] = stage_drops
current_stage = drop_match.group(1)
stage_drops = {}
continue
if current_stage:
item_match: List[str] = re.findall(
r"([\u4e00-\u9fa5]+)\s*:\s*([\d,]+)(?:\s*\(\+[\d,]+\))?", line
)
for item, total in item_match:
# 解析数值时去掉逗号 (如 2,160 -> 2160
total = int(total.replace(",", ""))
# 黑名单
if item not in [
"当前次数",
"理智",
"最快截图耗时",
"专精等级",
"剩余时间",
]:
stage_drops[item] = total
# 处理最后一个关卡的掉落数据
if current_stage and stage_drops:
data["drop_statistics"][current_stage] = stage_drops
# 保存日志
with log_path.open("w", encoding="utf-8") as f:
f.writelines(logs)
with log_path.with_suffix(".json").open("w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
logger.info(f"处理完成:{log_path}")
self.merge_maa_logs(log_path.parent)
def merge_maa_logs(self, logs_path: Path) -> None:
"""合并所有 .log 文件"""
data = {
"recruit_statistics": defaultdict(int),
"drop_statistics": defaultdict(dict),
"maa_result": defaultdict(str),
}
for json_file in logs_path.glob("*.json"):
with json_file.open("r", encoding="utf-8") as f:
single_data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = (
json.load(f)
)
# 合并公招统计
for star_level, count in single_data["recruit_statistics"].items():
data["recruit_statistics"][star_level] += count
# 合并掉落统计
for stage, drops in single_data["drop_statistics"].items():
if stage not in data["drop_statistics"]:
data["drop_statistics"][stage] = {} # 初始化关卡
for item, count in drops.items():
if item in data["drop_statistics"][stage]:
data["drop_statistics"][stage][item] += count
else:
data["drop_statistics"][stage][item] = count
# 合并MAA结果
data["maa_result"][
json_file.name.replace(".json", "").replace("-", ":")
] = single_data["maa_result"]
# 生成汇总 JSON 文件
with logs_path.with_suffix(".json").open("w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
logger.info(f"统计完成:{logs_path.with_suffix(".json")}")
def load_maa_logs(
self, mode: str, json_path: Path
) -> Dict[str, Union[str, list, Dict[str, list]]]:
"""加载MAA日志统计信息"""
if mode == "总览":
with json_path.open("r", encoding="utf-8") as f:
info: Dict[str, Dict[str, Union[int, dict]]] = json.load(f)
data = {}
data["条目索引"] = [
[k, "完成" if v == "Success!" else "异常"]
for k, v in info["maa_result"].items()
]
data["条目索引"].insert(0, ["数据总览", "运行"])
data["统计数据"] = {"公招统计": list(info["recruit_statistics"].items())}
for game_id, drops in info["drop_statistics"].items():
data["统计数据"][f"掉落统计:{game_id}"] = list(drops.items())
data["统计数据"]["报错汇总"] = [
[k, v] for k, v in info["maa_result"].items() if v != "Success!"
]
elif mode == "单项":
with json_path.open("r", encoding="utf-8") as f:
info: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = json.load(f)
data = {}
data["统计数据"] = {"公招统计": list(info["recruit_statistics"].items())}
for game_id, drops in info["drop_statistics"].items():
data["统计数据"][f"掉落统计:{game_id}"] = list(drops.items())
with json_path.with_suffix(".log").open("r", encoding="utf-8") as f:
log = f.read()
data["日志信息"] = log
return data
def search_history(self) -> dict:
"""搜索所有历史记录"""
history_dict = {}
for date_folder in (Config.app_path / "history").iterdir():
if not date_folder.is_dir():
continue # 只处理日期文件夹
try:
date = datetime.strptime(date_folder.name, "%Y-%m-%d")
history_dict[date.strftime("%Y年 %m月 %d")] = list(
date_folder.glob("*.json")
)
except ValueError:
logger.warning(f"非日期格式的目录: {date_folder}")
return {
k: v
for k, v in sorted(
history_dict.items(),
key=lambda x: datetime.strptime(x[0], "%Y年 %m月 %d"),
reverse=True,
)
}
def save_history(self, key: str, content: dict) -> None:
"""保存历史记录"""
@@ -540,6 +745,9 @@ class AppConfig:
class GlobalConfig(QConfig):
"""全局配置"""
function_HistoryRetentionTime = OptionsConfigItem(
"Function", "HistoryRetentionTime", 7, OptionsValidator([7, 15, 30, 60, 0])
)
function_IfAllowSleep = ConfigItem(
"Function", "IfAllowSleep", False, BoolValidator()
)
@@ -582,12 +790,6 @@ class GlobalConfig(QConfig):
update_UpdateType = OptionsConfigItem(
"Update", "UpdateType", "main", OptionsValidator(["main", "dev"])
)
# 日志管理
function_IfEnableLog = ConfigItem("Function", "IfEnableLog", False, BoolValidator())
function_LogRetentionDays = OptionsConfigItem(
"Function", "LogRetentionDays", "7 天",
OptionsValidator(["7 天", "15 天", "30 天", "60 天", "永不清理"])
)
class QueueConfig(QConfig):

View File

@@ -38,7 +38,6 @@ from typing import List
from app.core import Config
from app.services import Notify, System
from app.services.maa_log_analyzer import analyze_maa_logs
class MaaManager(QObject):
@@ -288,6 +287,14 @@ class MaaManager(QObject):
# 移除静默进程标记
Config.silence_list.remove(self.emulator_path)
# 保存运行日志
Config.save_maa_log(
Config.app_path
/ f"history/{curdate}/{self.data[user[2]][0]}/{start_time.strftime("%H-%M-%S")}.log",
self.check_maa_log(start_time, mode_book[j]),
self.maa_result,
)
# 成功完成代理的用户修改相关参数
if run_book[0] and run_book[1]:
if self.data[user[2]][14] == 0 and self.data[user[2]][3] != -1:
@@ -531,8 +538,8 @@ class MaaManager(QObject):
with self.maa_log_path.open(mode="r", encoding="utf-8") as f:
pass
def check_maa_log(self, start_time: datetime, mode: str) -> None:
"""检查MAA日志以判断MAA程序运行状态"""
def check_maa_log(self, start_time: datetime, mode: str) -> list:
"""获取MAA日志并检查以判断MAA程序运行状态"""
# 获取日志
logs = []
@@ -616,6 +623,8 @@ class MaaManager(QObject):
self.quit_monitor()
return logs
def start_monitor(self, start_time: datetime, mode: str) -> None:
"""开始监视MAA日志"""
@@ -638,70 +647,6 @@ class MaaManager(QObject):
self.log_monitor_timer.stop()
self.monitor_loop.quit()
# 检查用户是否开启日志保存功能
if not Config.global_config.get(Config.global_config.function_IfEnableLog):
logger.info(f"{self.name} | 用户未启用日志保存功能,跳过保存")
return
# 获取当前运行的用户
current_user = next((user[0].split(" - ")[0] for user in self.user_list if user[1] == "运行"), "UnknownUser")
# 新增日志保存功能
try:
# 获取当前日期和时间
now = datetime.now()
date_str = now.strftime("%Y-%m-%d")
time_str = now.strftime("%H-%M-%S")
# 停三秒,保证日志完全写入
time.sleep(3)
# 设定日志保存路径:/maa_run_history/{date}/{实例}/{用户}/{time}.log
base_path = Path(f"./maa_run_history/{date_str}/{self.name}/{current_user}")
base_path.mkdir(parents=True, exist_ok=True) # 确保目录存在
log_file_path = base_path / f"{time_str}.log"
# 读取 MAA 运行日志
with self.maa_log_path.open(mode="r", encoding="utf-8") as f:
logs = f.readlines()
# **只获取最后一次 MAA 运行日志**
last_start_idx = None
last_exit_idx = None
# 反向查找最后一个 "MaaAssistantArknights GUI exited"
for i in range(len(logs) - 1, -1, -1):
if "MaaAssistantArknights GUI exited" in logs[i]:
last_exit_idx = i
break
# 反向查找最近的 "MaaAssistantArknights GUI started"
if last_exit_idx is not None:
for i in range(last_exit_idx, -1, -1):
if "MaaAssistantArknights GUI started" in logs[i]:
last_start_idx = i
break
# 确保找到了完整的日志片段
if last_start_idx is not None and last_exit_idx is not None:
relevant_logs = logs[last_start_idx: last_exit_idx + 1]
# 只保存最后一次的完整日志
with log_file_path.open(mode="w", encoding="utf-8") as f:
f.writelines(relevant_logs)
logger.info(f"{self.name} | 运行日志已保存: {log_file_path}")
# ========== **调用分析函数** ==========
analyze_maa_logs(base_path)
else:
logger.warning(f"{self.name} | 未找到完整的 MAA 运行日志片段,跳过保存")
except Exception as e:
logger.error(f"{self.name} | 日志保存失败: {str(e)}")
def set_maa(self, mode, index):
"""配置MAA运行参数"""
logger.info(f"{self.name} | 配置MAA运行参数: {mode}/{index}")

View File

@@ -1,123 +0,0 @@
import json
import os
import re
from datetime import datetime, timedelta
from pathlib import Path
from collections import defaultdict
from loguru import logger
from app.core import Config
def analyze_maa_logs(logs_directory: Path):
"""
遍历 logs_directory 下所有 .log 文件,解析公招和掉落信息,并保存为 JSON 文件
"""
if not logs_directory.exists():
logger.error(f"目录不存在: {logs_directory}")
return
# **检查并删除超期日志**
clean_old_logs(logs_directory)
# 设定 JSON 输出路径
json_output_path = logs_directory / f"{logs_directory.name}.json" if logs_directory.parent.name == "maa_run_history" else logs_directory.parent / f"{logs_directory.name}.json"
aggregated_data = {
# "recruit_statistics": defaultdict(int),
"drop_statistics": defaultdict(lambda: defaultdict(int)),
}
log_files = list(logs_directory.rglob("*.log"))
if not log_files:
logger.error(f"没有找到 .log 文件: {logs_directory}")
return
for log_file in log_files:
analyze_single_log(log_file, aggregated_data)
# 生成 JSON 文件
with open(json_output_path, "w", encoding="utf-8") as json_file:
json.dump(aggregated_data, json_file, ensure_ascii=False, indent=4)
logger.info(f"统计完成:{json_output_path}")
def analyze_single_log(log_file_path: Path, aggregated_data):
"""
解析单个 .log 文件,提取公招结果 & 关卡掉落数据
"""
# recruit_data = aggregated_data["recruit_statistics"]
drop_data = aggregated_data["drop_statistics"]
with open(log_file_path, "r", encoding="utf-8") as f:
logs = f.readlines()
# # **公招统计**
# i = 0
# while i < len(logs):
# if "公招识别结果:" in logs[i]:
# tags = []
# i += 1
# while i < len(logs) and "Tags" not in logs[i]: # 读取所有公招标签
# tags.append(logs[i].strip())
# i += 1
#
# if i < len(logs) and "Tags" in logs[i]: # 确保 Tags 行存在
# star_match = re.search(r"(\d+)\s*Tags", logs[i]) # 提取 3,4,5,6 星
# if star_match:
# star_level = f"{star_match.group(1)}★"
# recruit_data[star_level] += 1
# i += 1
# **掉落统计**
current_stage = None
for i, line in enumerate(logs):
drop_match = re.search(r"(\d+-\d+) 掉落统计:", line)
if drop_match:
current_stage = drop_match.group(1)
continue
if current_stage and re.search(r"(\S+)\s*:\s*(\d+)\s*\(\+\d+\)", line):
item_match = re.findall(r"(\S+)\s*:\s*(\d+)\s*\(\+(\d+)\)", line)
for item, total, increment in item_match:
drop_data[current_stage][item] += int(increment)
logger.info(f"处理完成:{log_file_path}")
def clean_old_logs(logs_directory: Path):
"""
删除超过用户设定天数的日志文件
"""
retention_setting = Config.global_config.get(Config.global_config.function_LogRetentionDays)
retention_days_mapping = {
"7 天": 7,
"15 天": 15,
"30 天": 30,
"60 天": 60,
"永不清理": None
}
retention_days = retention_days_mapping.get(retention_setting, None)
if retention_days is None:
logger.info("🔵 用户设置日志保留时间为【永不清理】,跳过清理")
return
cutoff_time = datetime.now() - timedelta(days=retention_days)
deleted_count = 0
for log_file in logs_directory.rglob("*.log"):
file_time = datetime.fromtimestamp(log_file.stat().st_mtime) # 获取文件的修改时间
if file_time < cutoff_time:
try:
os.remove(log_file)
deleted_count += 1
logger.info(f"🗑️ 已删除超期日志: {log_file}")
except Exception as e:
logger.error(f"❌ 删除日志失败: {log_file}, 错误: {e}")
logger.info(f"✅ 清理完成: {deleted_count} 个日志文件")
# # 运行代码
# logs_directory = Path("")
# analyze_maa_logs(logs_directory)

View File

@@ -39,6 +39,10 @@ from qfluentwidgets import (
Signal,
ComboBox,
CheckBox,
IconWidget,
FluentIcon,
CardWidget,
BodyLabel,
qconfig,
ConfigItem,
TimeEdit,
@@ -319,3 +323,53 @@ class TimeEditSettingCard(SettingCard):
qconfig.set(self.configItem_time, value)
self.TimeEdit.setTime(QTime.fromString(value, "HH:mm"))
class StatefulItemCard(CardWidget):
def __init__(self, item: list, parent=None):
super().__init__(parent)
self.Layout = QHBoxLayout(self)
self.Label = BodyLabel(item[0], self)
self.icon = IconWidget(FluentIcon.MORE, self)
self.icon.setFixedSize(16, 16)
self.update_status(item[1])
self.Layout.addWidget(self.icon)
self.Layout.addWidget(self.Label)
self.Layout.addStretch(1)
def update_status(self, status: str):
if status == "完成":
self.icon.setIcon(FluentIcon.ACCEPT)
self.Label.setTextColor("#0eb840", "#0eb840")
elif status == "等待":
self.icon.setIcon(FluentIcon.MORE)
self.Label.setTextColor("#161823", "#e3f9fd")
elif status == "运行":
self.icon.setIcon(FluentIcon.PLAY)
self.Label.setTextColor("#177cb0", "#70f3ff")
elif status == "跳过":
self.icon.setIcon(FluentIcon.REMOVE)
self.Label.setTextColor("#75878a", "#7397ab")
elif status == "异常":
self.icon.setIcon(FluentIcon.CLOSE)
self.Label.setTextColor("#ff2121", "#ff2121")
class QuantifiedItemCard(CardWidget):
def __init__(self, item: list, parent=None):
super().__init__(parent)
self.Layout = QHBoxLayout(self)
self.Name = BodyLabel(item[0], self)
self.Numb = BodyLabel(str(item[1]), self)
self.Layout.addWidget(self.Name)
self.Layout.addStretch(1)
self.Layout.addWidget(self.Numb)

View File

@@ -34,8 +34,6 @@ from PySide6.QtWidgets import (
)
from qfluentwidgets import (
CardWidget,
IconWidget,
BodyLabel,
Pivot,
ScrollArea,
FluentIcon,
@@ -53,6 +51,7 @@ import json
from app.core import Config, TaskManager, Task, MainInfoBar
from .Widget import StatefulItemCard
class DispatchCenter(QWidget):
@@ -335,7 +334,7 @@ class DispatchBox(QWidget):
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.task_cards: List[ItemCard] = []
self.task_cards: List[StatefulItemCard] = []
def create_task(self, task_list: list):
"""创建任务队列"""
@@ -351,7 +350,7 @@ class DispatchBox(QWidget):
for task in task_list:
self.task_cards.append(ItemCard(task))
self.task_cards.append(StatefulItemCard(task))
self.Layout.addWidget(self.task_cards[-1])
self.Layout.addStretch(1)
@@ -373,7 +372,7 @@ class DispatchBox(QWidget):
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.user_cards: List[ItemCard] = []
self.user_cards: List[StatefulItemCard] = []
def create_user(self, user_list: list):
"""创建用户队列"""
@@ -389,7 +388,7 @@ class DispatchBox(QWidget):
for user in user_list:
self.user_cards.append(ItemCard(user))
self.user_cards.append(StatefulItemCard(user))
self.Layout.addWidget(self.user_cards[-1])
self.Layout.addStretch(1)
@@ -419,38 +418,3 @@ class DispatchBox(QWidget):
self.text.moveCursor(QTextCursor.End)
self.text.ensureCursorVisible()
class ItemCard(CardWidget):
def __init__(self, task_item: list, parent=None):
super().__init__(parent)
self.Layout = QHBoxLayout(self)
self.Label = BodyLabel(task_item[0], self)
self.icon = IconWidget(FluentIcon.MORE, self)
self.icon.setFixedSize(16, 16)
self.update_status(task_item[1])
self.Layout.addWidget(self.icon)
self.Layout.addWidget(self.Label)
self.Layout.addStretch(1)
def update_status(self, status: str):
if status == "完成":
self.icon.setIcon(FluentIcon.ACCEPT)
self.Label.setTextColor("#0eb840", "#0eb840")
elif status == "等待":
self.icon.setIcon(FluentIcon.MORE)
self.Label.setTextColor("#7397ab", "#7397ab")
elif status == "运行":
self.icon.setIcon(FluentIcon.PLAY)
self.Label.setTextColor("#2e4e7e", "#2e4e7e")
elif status == "跳过":
self.icon.setIcon(FluentIcon.REMOVE)
self.Label.setTextColor("#606060", "#d2d2d2")
elif status == "异常":
self.icon.setIcon(FluentIcon.CLOSE)
self.Label.setTextColor("#ff2121", "#ff2121")

264
app/ui/history.py Normal file
View File

@@ -0,0 +1,264 @@
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
# Copyright © <2024> <DLmaster361>
# This file is part of AUTO_MAA.
# AUTO_MAA is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
# AUTO_MAA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
# the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
# DLmaster_361@163.com
"""
AUTO_MAA
AUTO_MAA历史记录界面
v4.2
作者DLmaster_361
"""
from loguru import logger
from PySide6.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
)
from qfluentwidgets import (
ScrollArea,
FluentIcon,
HeaderCardWidget,
PushButton,
ExpandGroupSettingCard,
TextBrowser,
)
from PySide6.QtCore import Signal
import os
from functools import partial
from pathlib import Path
from typing import List
from app.core import Config
from .Widget import StatefulItemCard, QuantifiedItemCard
class History(QWidget):
def __init__(
self,
parent=None,
):
super().__init__(parent)
self.setObjectName("历史记录")
content_widget = QWidget()
self.content_layout = QVBoxLayout(content_widget)
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setWidget(content_widget)
layout = QVBoxLayout()
layout.addWidget(scrollArea)
self.setLayout(layout)
self.history_card_list = []
self.refresh()
def refresh(self):
"""刷新脚本实例界面"""
while self.content_layout.count() > 0:
item = self.content_layout.takeAt(0)
if item.spacerItem():
self.content_layout.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
self.history_card_list = []
history_dict = Config.search_history()
for date, user_list in history_dict.items():
self.history_card_list.append(HistoryCard(date, user_list, self))
self.content_layout.addWidget(self.history_card_list[-1])
self.content_layout.addStretch(1)
class HistoryCard(ExpandGroupSettingCard):
def __init__(self, date: str, user_list: List[Path], parent=None):
super().__init__(
FluentIcon.HISTORY,
date,
f"{date}的历史运行记录与统计信息",
parent,
)
widget = QWidget()
Layout = QVBoxLayout(widget)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.addGroupWidget(widget)
self.user_history_card_list = []
for user_path in user_list:
self.user_history_card_list.append(self.UserHistoryCard(user_path, self))
Layout.addWidget(self.user_history_card_list[-1])
class UserHistoryCard(HeaderCardWidget):
def __init__(
self,
user_history_path: Path,
parent=None,
):
super().__init__(parent)
self.setTitle(user_history_path.name.replace(".json", ""))
self.user_history_path = user_history_path
self.main_history = Config.load_maa_logs("总览", user_history_path)
self.index_card = self.IndexCard(self.main_history["条目索引"], self)
self.statistics_card = QHBoxLayout()
self.log_card = self.LogCard(self)
self.index_card.index_changed.connect(self.update_info)
self.viewLayout.addWidget(self.index_card)
self.viewLayout.addLayout(self.statistics_card)
self.viewLayout.addWidget(self.log_card)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.viewLayout.setStretch(0, 1)
self.viewLayout.setStretch(2, 4)
self.update_info("数据总览")
def update_info(self, index: str) -> None:
"""更新信息"""
if index == "数据总览":
while self.statistics_card.count() > 0:
item = self.statistics_card.takeAt(0)
if item.spacerItem():
self.statistics_card.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
for name, item_list in self.main_history["统计数据"].items():
statistics_card = self.StatisticsCard(name, item_list, self)
self.statistics_card.addWidget(statistics_card)
self.log_card.hide()
else:
single_history = Config.load_maa_logs(
"单项",
self.user_history_path.with_suffix("")
/ f"{index.replace(":","-")}.json",
)
while self.statistics_card.count() > 0:
item = self.statistics_card.takeAt(0)
if item.spacerItem():
self.statistics_card.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
for name, item_list in single_history["统计数据"].items():
statistics_card = self.StatisticsCard(name, item_list, self)
self.statistics_card.addWidget(statistics_card)
self.log_card.text.setText(single_history["日志信息"])
self.log_card.button.clicked.disconnect()
self.log_card.button.clicked.connect(
lambda: os.startfile(
self.user_history_path.with_suffix("")
/ f"{index.replace(":","-")}.log"
)
)
self.log_card.show()
self.viewLayout.setStretch(1, self.statistics_card.count())
self.setMinimumHeight(300)
class IndexCard(HeaderCardWidget):
index_changed = Signal(str)
def __init__(self, index_list: list, parent=None):
super().__init__(parent)
self.setTitle("记录条目")
self.Layout = QVBoxLayout()
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.index_cards: List[StatefulItemCard] = []
for index in index_list:
self.index_cards.append(StatefulItemCard(index))
self.index_cards[-1].clicked.connect(
partial(self.index_changed.emit, index[0])
)
self.Layout.addWidget(self.index_cards[-1])
self.Layout.addStretch(1)
class StatisticsCard(HeaderCardWidget):
def __init__(self, name: str, item_list: list, parent=None):
super().__init__(parent)
self.setTitle(name)
self.Layout = QVBoxLayout()
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.item_cards: List[QuantifiedItemCard] = []
for item in item_list:
self.item_cards.append(QuantifiedItemCard(item))
self.Layout.addWidget(self.item_cards[-1])
if len(item_list) == 0:
self.Layout.addWidget(QuantifiedItemCard(["暂无记录", ""]))
self.Layout.addStretch(1)
class LogCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("日志")
self.text = TextBrowser(self)
self.button = PushButton("打开日志文件", self)
self.button.clicked.connect(lambda: print("打开日志文件"))
Layout = QVBoxLayout()
Layout.addWidget(self.text)
Layout.addWidget(self.button)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.viewLayout.addLayout(Layout)

View File

@@ -46,6 +46,8 @@ from qfluentwidgets import (
from PySide6.QtGui import QIcon, QCloseEvent
from PySide6.QtCore import Qt, QTimer
import json
from datetime import datetime, timedelta
import shutil
from app.core import Config, TaskManager, MainTimer, MainInfoBar
from app.services import Notify, Crypto, System
@@ -53,6 +55,7 @@ from .setting import Setting
from .member_manager import MemberManager
from .queue_manager import QueueManager
from .dispatch_center import DispatchCenter
from .history import History
class AUTO_MAA(MSFluentWindow):
@@ -76,6 +79,7 @@ class AUTO_MAA(MSFluentWindow):
self.member_manager = MemberManager(self)
self.queue_manager = QueueManager(self)
self.dispatch_center = DispatchCenter(self)
self.history = History(self)
self.addSubInterface(
self.setting,
@@ -105,6 +109,13 @@ class AUTO_MAA(MSFluentWindow):
FluentIcon.IOT,
NavigationItemPosition.TOP,
)
self.addSubInterface(
self.history,
FluentIcon.HISTORY,
"历史记录",
FluentIcon.HISTORY,
NavigationItemPosition.BOTTOM,
)
self.stackedWidget.currentChanged.connect(
lambda index: (self.member_manager.refresh() if index == 1 else None)
)
@@ -123,6 +134,9 @@ class AUTO_MAA(MSFluentWindow):
self.dispatch_center.update_top_bar() if index == 3 else None
)
)
self.stackedWidget.currentChanged.connect(
lambda index: (self.history.refresh() if index == 4 else None)
)
# 创建系统托盘及其菜单
self.tray = QSystemTrayIcon(
@@ -197,6 +211,9 @@ class AUTO_MAA(MSFluentWindow):
qconfig.load(Config.config_path, Config.global_config)
Config.global_config.save()
# 清理旧日志
self.clean_old_logs()
# 检查密码
self.setting.check_PASSWORD()
@@ -247,6 +264,40 @@ class AUTO_MAA(MSFluentWindow):
if reason == QSystemTrayIcon.DoubleClick:
self.show_ui("显示主窗口")
def clean_old_logs(self):
"""
删除超过用户设定天数的日志文件(基于目录日期)
"""
if (
Config.global_config.get(Config.global_config.function_HistoryRetentionTime)
== 0
):
logger.info("由于用户设置日志永久保留,跳过日志清理")
return
deleted_count = 0
for date_folder in (Config.app_path / "history").iterdir():
if not date_folder.is_dir():
continue # 只处理日期文件夹
try:
# 只检查 `YYYY-MM-DD` 格式的文件夹
folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d")
if datetime.now() - folder_date > timedelta(
days=Config.global_config.get(
Config.global_config.function_HistoryRetentionTime
)
):
shutil.rmtree(date_folder, ignore_errors=True)
deleted_count += 1
logger.info(f"已删除超期日志目录: {date_folder}")
except ValueError:
logger.warning(f"非日期格式的目录: {date_folder}")
logger.info(f"清理完成: {deleted_count} 个日期目录")
def start_main_task(self) -> None:
"""启动主任务"""

View File

@@ -77,7 +77,6 @@ class Setting(QWidget):
self.function = FunctionSettingCard(self)
self.start = StartSettingCard(self)
self.ui = UiSettingCard(self)
self.log_settings = LogSettingCard(self)
self.notification = NotifySettingCard(self)
self.security = SecuritySettingCard(self)
self.updater = UpdaterSettingCard(self)
@@ -93,7 +92,6 @@ class Setting(QWidget):
content_layout.addWidget(self.function)
content_layout.addWidget(self.start)
content_layout.addWidget(self.ui)
content_layout.addWidget(self.log_settings)
content_layout.addWidget(self.notification)
content_layout.addWidget(self.security)
content_layout.addWidget(self.updater)
@@ -419,6 +417,13 @@ class FunctionSettingCard(HeaderCardWidget):
super().__init__(parent)
self.setTitle("功能")
self.card_HistoryRetentionTime = ComboBoxSettingCard(
configItem=Config.global_config.function_HistoryRetentionTime,
icon=FluentIcon.PAGE_RIGHT,
title="历史记录保留时间",
content="选择历史记录的保留时间,超期自动清理",
texts=["7 天", "15 天", "30 天", "60 天", "永久"],
)
self.card_IfAllowSleep = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="启动时阻止系统休眠",
@@ -434,6 +439,7 @@ class FunctionSettingCard(HeaderCardWidget):
)
Layout = QVBoxLayout()
Layout.addWidget(self.card_HistoryRetentionTime)
Layout.addWidget(self.card_IfAllowSleep)
Layout.addWidget(self.card_IfSilence)
Layout.addWidget(self.card_IfAgreeBilibili)
@@ -799,35 +805,6 @@ class OtherSettingCard(HeaderCardWidget):
self.viewLayout.setSpacing(0)
self.addGroupWidget(widget)
class LogSettingCard(HeaderCardWidget):
"""日志管理设置"""
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("日志管理")
# 日志存储开关
self.card_IfEnableLog = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="启用日志存储",
content="记录并存储每次运行的日志,用于多账号日常掉落统计",
configItem=Config.global_config.function_IfEnableLog,
)
# 日志保留天数设置
self.card_LogRetentionDays = ComboBoxSettingCard(
configItem=Config.global_config.function_LogRetentionDays,
icon=FluentIcon.PAGE_RIGHT,
title="日志保留天数",
content="选择日志的保留时间,超期自动清理",
texts=["7 天", "15 天", "30 天", "60 天", "永不清理"],
)
Layout = QVBoxLayout()
Layout.addWidget(self.card_IfEnableLog)
Layout.addWidget(self.card_LogRetentionDays)
self.viewLayout.addLayout(Layout)
def version_text(version_numb: list) -> str:
"""将版本号列表转为可读的文本信息"""