From 01e810077426de5d09c497acf54008a701634742 Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Mon, 16 Mar 2026 16:55:52 +0800 Subject: [PATCH 01/14] =?UTF-8?q?feat(LibCheckin):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=A0=A1=E5=9B=AD=E7=BD=91=E7=8E=AF=E5=A2=83=E4=B8=8B=E5=9B=BE?= =?UTF-8?q?=E4=B9=A6=E9=A6=86=E8=BF=9C=E7=A8=8B=E7=AD=BE=E5=88=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 __enableCheckinBtn 方法,通过 JavaScript 移除签到按钮的 disabled 属性 - 在检测到签到按钮不可用时,自动尝试启用按钮而非直接失败 - 支持在校园网环境下无需连接图书馆网络即可完成签到 - 优化签到流程的用户提示信息" --- src/operators/LibCheckin.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/operators/LibCheckin.py b/src/operators/LibCheckin.py index 7e5ffd6..37cd23c 100644 --- a/src/operators/LibCheckin.py +++ b/src/operators/LibCheckin.py @@ -88,6 +88,31 @@ class LibCheckin(LibOperator): return False + def __enableCheckinBtn( + self + ) -> bool: + + script = """ + try { + var checkin_btn = document.getElementById('btnCheckIn'); + if (checkin_btn) { + checkin_btn.classList.remove('disabled'); + return true; + } + return false; + } catch (e) { + return false; + } + """ + result = self.__driver.execute_script(script) + time.sleep(0.1) + if result: + self._showTrace("签到按钮已启用") + else: + self._showTrace("签到按钮启用失败") + return result + + def checkin( self, username: str @@ -104,8 +129,10 @@ class LibCheckin(LibOperator): self._showTrace(f"用户 {username} 签到界面加载失败 !") return False if "disabled" in checkin_btn.get_attribute("class"): - self._showTrace("签到按钮不可用, 可能不在场馆内, 请连接图书馆网络后重试") - return False + self._showTrace("签到按钮不可用, 可能不在场馆内, 正在尝试启用......") + if not self.__enableCheckinBtn(): + self._showTrace(f"签到按钮启用失败 !") + return False checkin_btn.click() if self._waitResponseLoad(): self._showTrace(f"用户 {username} 签到成功 !") From 5af6120be87d16302e83cc998e71e4a42fa8a3f8 Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Mon, 16 Mar 2026 21:15:15 +0800 Subject: [PATCH 02/14] =?UTF-8?q?feat(TimerUtils):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E4=BB=BB=E5=8A=A1=E6=97=B6=E9=97=B4=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 TimerUtils.calculateNextRepeatTime 方法 - 支持基于重复日期和目标时间计算下次执行时间 - 如果当天在重复日期且目标时间未过,则返回今天;否则查找下一个匹配日期 --- src/utils/TimerUtils.py | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/utils/TimerUtils.py diff --git a/src/utils/TimerUtils.py b/src/utils/TimerUtils.py new file mode 100644 index 0000000..ffecafb --- /dev/null +++ b/src/utils/TimerUtils.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +""" +Copyright (c) 2025 - 2026 KenanZhu. +All rights reserved. + +This software is provided "as is", without any warranty of any kind. +You may use, modify, and distribute this file under the terms of the MIT License. +See the LICENSE file for details. +""" +from datetime import datetime, timedelta + + +def calculateNextRepeatTime( + repeat_days: list, + hour: int, + minute: int, + second: int +) -> datetime: + """ + Calculate the next repeat time based on repeat days and target time. + + This function calculates the next execution time for a repeatable task. + If the current day is in repeat_days and the target time has not passed, + it returns today's target time. Otherwise, it finds the next matching day. + + Args: + repeat_days (list): List of weekdays to repeat (0=Monday, 6=Sunday). + hour (int): Target hour (0-23). + minute (int): Target minute (0-59). + second (int): Target second (0-59). + + Returns: + datetime: The next repeat execution time. + """ + + current_time = datetime.now() + current_weekday = current_time.weekday() + target_time = current_time.replace(hour=hour, minute=minute, second=second, microsecond=0) + if current_weekday in repeat_days: + if target_time > current_time: + return target_time + repeat_days_sorted = sorted(repeat_days) + for day in repeat_days_sorted: + if day > current_weekday: + days_until = day - current_weekday + next_time = target_time + timedelta(days=days_until) + return next_time + days_until = 7 - current_weekday + repeat_days_sorted[0] + next_time = target_time + timedelta(days=days_until) + return next_time From b0d1c0e99edbfb9b740e633cd9a235cc15eccacb Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Mon, 16 Mar 2026 21:15:56 +0800 Subject: [PATCH 03/14] =?UTF-8?q?feat(TimerTask):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=89=A7=E8=A1=8C=E5=8E=86=E5=8F=B2=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ALTimerTaskHistoryDialog 显示重复任务执行历史 - 支持查看执行时间、运行结果、运行耗时 - 提供清空历史记录功能 - 表格显示:执行时间、结果、耗时(秒/s)、uuid --- src/gui/ALTimerTaskHistoryDialog.py | 124 ++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/gui/ALTimerTaskHistoryDialog.py diff --git a/src/gui/ALTimerTaskHistoryDialog.py b/src/gui/ALTimerTaskHistoryDialog.py new file mode 100644 index 0000000..e125a9b --- /dev/null +++ b/src/gui/ALTimerTaskHistoryDialog.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +""" +Copyright (c) 2026 KenanZhu. +All rights reserved. + +This software is provided "as is", without any warranty of any kind. +You may use, modify, and distribute this file under the terms of the MIT License. +See the LICENSE file for details. +""" +from datetime import datetime + +from PySide6.QtCore import Slot, Qt +from PySide6.QtWidgets import ( + QDialog, QTableWidget, QTableWidgetItem, + QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QHeaderView +) + + +class ALTimerTaskHistoryDialog(QDialog): + + def __init__( + self, + parent = None, + task_data: dict = None + ): + + super().__init__(parent) + + self.__task_data = task_data + self.__history = task_data.get("history", []) + + self.modifyUi() + + def modifyUi( + self + ): + + self.setWindowTitle("定时任务执行历史 - AutoLibrary") + self.setMinimumSize(600, 400) + + MainLayout = QVBoxLayout(self) + + InfoLayout = QHBoxLayout() + TaskNameLabel = QLabel(f"任务: {self.__task_data.get('name', '未命名')}") + TaskNameLabel.setStyleSheet("font-weight: bold; font-size: 14px;") + InfoLayout.addWidget(TaskNameLabel) + InfoLayout.addStretch() + + if self.__task_data.get("repeat", False): + repeat_label = QLabel("重复任务") + repeat_label.setStyleSheet("color: #2294FF; font-weight: bold;") + InfoLayout.addWidget(repeat_label) + MainLayout.addLayout(InfoLayout) + self.HistoryTableWidget = QTableWidget() + self.HistoryTableWidget.setColumnCount(4) + self.HistoryTableWidget.setHorizontalHeaderLabels(["执行时间", "结果", "耗时(秒/s)", "uuid"]) + self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) + self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) + self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) + self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) + self.HistoryTableWidget.verticalHeader().setVisible(False) + self.HistoryTableWidget.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) + self.HistoryTableWidget.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) + self._loadHistory() + MainLayout.addWidget(self.HistoryTableWidget) + + ButtonLayout = QHBoxLayout() + ButtonLayout.addStretch() + self.CloseButton = QPushButton("关闭") + self.CloseButton.setFixedSize(80, 25) + self.CloseButton.clicked.connect(self.accept) + self.ClearHistoryButton = QPushButton("清空历史") + self.ClearHistoryButton.setFixedSize(80, 25) + self.ClearHistoryButton.clicked.connect(self._clearHistory) + ButtonLayout.addWidget(self.ClearHistoryButton) + ButtonLayout.addWidget(self.CloseButton) + MainLayout.addLayout(ButtonLayout) + + + def _loadHistory( + self + ): + + self.HistoryTableWidget.setRowCount(len(self.__history)) + for row, record in enumerate(self.__history): + self._addHistoryRow(row, record) + + + def _addHistoryRow( + self, + row: int, + record: dict + ): + + execute_time_str = record.get("execute_time", "") + result = record.get("result", "未知") + duration = record.get("duration", 0) + uuid = record.get("uuid", "") + self.HistoryTableWidget.setItem(row, 0, QTableWidgetItem(execute_time_str)) + self.HistoryTableWidget.setItem(row, 1, QTableWidgetItem(result)) + duration_item = QTableWidgetItem(f"{duration:.2f}") + duration_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + self.HistoryTableWidget.setItem(row, 2, duration_item) + self.HistoryTableWidget.setItem(row, 3, QTableWidgetItem(uuid)) + if result == "成功": + self.HistoryTableWidget.item(row, 1).setForeground(Qt.GlobalColor.green) + elif result == "失败": + self.HistoryTableWidget.item(row, 1).setForeground(Qt.GlobalColor.red) + + @Slot() + def _clearHistory( + self + ): + + self.__history.clear() + self.HistoryTableWidget.setRowCount(0) + self.__task_data["history"] = self.__history + + + def getHistory( + self + ) -> list: + + return self.__history From f37bcf836bf573d6711aa5bbf6f0f8ffaaef3a8c Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Mon, 16 Mar 2026 21:16:46 +0800 Subject: [PATCH 04/14] =?UTF-8?q?feat(TimerTaskAddDialog):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E9=87=8D=E5=A4=8D=E4=BB=BB=E5=8A=A1=20UI=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UI 添加重复配置控件:复选框、周一到周日复选框 - 新增 onRepeatCheckBoxToggled 槽函数控制日期选择显示 - getTimerTask 支持提取重复配置(日期、时分秒) - 调用 TimerUtils 计算首次执行时间 - 重构导入语句格式 --- src/gui/ALTimerTaskAddDialog.py | 64 ++++- src/gui/resources/ui/ALTimerTaskAddDialog.ui | 263 ++++++++++++++++++- 2 files changed, 308 insertions(+), 19 deletions(-) diff --git a/src/gui/ALTimerTaskAddDialog.py b/src/gui/ALTimerTaskAddDialog.py index 6c99bb8..4c73715 100644 --- a/src/gui/ALTimerTaskAddDialog.py +++ b/src/gui/ALTimerTaskAddDialog.py @@ -12,15 +12,11 @@ import uuid from enum import Enum from datetime import datetime, timedelta -from PySide6.QtCore import ( - Slot, QDateTime -) -from PySide6.QtWidgets import ( - QLabel, QDialog, QWidget, QSpinBox, - QHBoxLayout, QGridLayout, QDateTimeEdit -) +from PySide6.QtCore import Slot, QDateTime +from PySide6.QtWidgets import QLabel, QDialog, QWidget, QSpinBox, QHBoxLayout, QGridLayout, QDateTimeEdit from gui.resources.ui.Ui_ALTimerTaskAddDialog import Ui_ALTimerTaskAddDialog +import utils.TimerUtils as TimerUtils class ALTimerTaskStatus(Enum): @@ -43,8 +39,8 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog): super().__init__(parent) self.setupUi(self) - self.connectSignals() self.modifyUi() + self.connectSignals() def modifyUi( @@ -97,6 +93,7 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog): self.CancelButton.clicked.connect(self.reject) self.ConfirmButton.clicked.connect(self.accept) self.TimerTypeComboBox.currentIndexChanged.connect(self.onTimerTypeComboBoxIndexChanged) + self.RepeatCheckBox.toggled.connect(self.onRepeatCheckBoxToggled) def getTimerTask( @@ -121,7 +118,7 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog): minutes = self.RelativeMinuteSpinBox.value(), seconds = self.RelativeSecondSpinBox.value() ) - return { + task_data = { "name": name, "task_uuid": uuid.uuid4().hex.upper() + f"-{added_time.strftime("%Y%m%d%H%M%S")}", "time_type": self.TimerTypeComboBox.currentText(), @@ -129,9 +126,40 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog): "silent": silent, "add_time": added_time, "status": ALTimerTaskStatus.PENDING, - "executed": False + "executed": False, + "repeat": self.RepeatCheckBox.isChecked(), + "repeat_records": [] } + if task_data["repeat"]: + repeat_days = [] + if self.MonCheckBox.isChecked(): + repeat_days.append(0) + if self.TueCheckBox.isChecked(): + repeat_days.append(1) + if self.WedCheckBox.isChecked(): + repeat_days.append(2) + if self.ThuCheckBox.isChecked(): + repeat_days.append(3) + if self.FriCheckBox.isChecked(): + repeat_days.append(4) + if self.SatCheckBox.isChecked(): + repeat_days.append(5) + if self.SunCheckBox.isChecked(): + repeat_days.append(6) + if not repeat_days: + repeat_days = [0, 1, 2, 3, 4, 5, 6] + task_data["repeat_days"] = repeat_days + task_data["repeat_hour"] = execute_time.hour + task_data["repeat_minute"] = execute_time.minute + task_data["repeat_second"] = execute_time.second + task_data["execute_time"] = TimerUtils.calculateNextRepeatTime( + task_data["repeat_days"], + task_data["repeat_hour"], + task_data["repeat_minute"], + task_data["repeat_second"] + ) + return task_data @Slot(int) def onTimerTypeComboBoxIndexChanged( @@ -140,4 +168,18 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog): ): self.SpecificTimerWidget.setVisible(index == 0) - self.RelativeTimerWidget.setVisible(index == 1) \ No newline at end of file + self.RelativeTimerWidget.setVisible(index == 1) + + @Slot(bool) + def onRepeatCheckBoxToggled( + self, + checked: bool + ): + + self.MonCheckBox.setEnabled(checked) + self.TueCheckBox.setEnabled(checked) + self.WedCheckBox.setEnabled(checked) + self.ThuCheckBox.setEnabled(checked) + self.FriCheckBox.setEnabled(checked) + self.SatCheckBox.setEnabled(checked) + self.SunCheckBox.setEnabled(checked) \ No newline at end of file diff --git a/src/gui/resources/ui/ALTimerTaskAddDialog.ui b/src/gui/resources/ui/ALTimerTaskAddDialog.ui index 63a06a4..1d3155f 100644 --- a/src/gui/resources/ui/ALTimerTaskAddDialog.ui +++ b/src/gui/resources/ui/ALTimerTaskAddDialog.ui @@ -6,20 +6,20 @@ 0 0 - 300 - 300 + 350 + 400 - 0 - 300 + 350 + 400 - 500 - 300 + 350 + 500 @@ -149,8 +149,20 @@ 5 - + + + + 0 + 25 + + + + + 16777215 + 25 + + 静默运行 @@ -168,13 +180,248 @@ - + + + + 0 + 25 + + + + + 16777215 + 25 + + 运行前提示 + + + + 重复运行 + + + + 5 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 0 + 25 + + + + + 16777215 + 25 + + + + 启用重复执行 + + + + + + + + 0 + 25 + + + + + 16777215 + 25 + + + + 重复周期(全选为每日运行): + + + + + + + 0 + + + + + false + + + + 50 + 25 + + + + + 50 + 25 + + + + 周四 + + + + + + + false + + + + 50 + 25 + + + + + 50 + 25 + + + + 周三 + + + + + + + false + + + + 50 + 25 + + + + + 50 + 25 + + + + 周一 + + + + + + + false + + + + 50 + 25 + + + + + 50 + 25 + + + + 周二 + + + + + + + false + + + + 50 + 25 + + + + + 50 + 25 + + + + 周五 + + + + + + + false + + + + 50 + 25 + + + + + 50 + 25 + + + + 周六 + + + + + + + false + + + + 50 + 25 + + + + + 50 + 25 + + + + 周日 + + + + + + + + From 883859d1f9d598edd3faa762e295bc6c388dd5ac Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Mon, 16 Mar 2026 21:17:48 +0800 Subject: [PATCH 05/14] =?UTF-8?q?feat(TimerTaskManageWidget):=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E9=87=8D=E5=A4=8D=E4=BB=BB=E5=8A=A1=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=B8=8E=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - onTimerTaskIsExecuted/onTimerTaskIsError 添加历史记录 - 历史记录包含:execute_time、executed_time、result、duration - 重复任务执行后自动计算并更新下次执行时间 --- src/gui/ALTimerTaskManageWidget.py | 74 ++++++++++++++++++- .../resources/ui/ALTimerTaskManageWidget.ui | 2 +- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/gui/ALTimerTaskManageWidget.py b/src/gui/ALTimerTaskManageWidget.py index 662b44c..735114c 100644 --- a/src/gui/ALTimerTaskManageWidget.py +++ b/src/gui/ALTimerTaskManageWidget.py @@ -21,11 +21,14 @@ from PySide6.QtWidgets import ( QDialog, QWidget, QListWidgetItem, QMessageBox, QHBoxLayout, QVBoxLayout, QLabel, QPushButton ) + +import gui.ALTimerTaskHistoryDialog as ALTimerTaskHistoryDialog from PySide6.QtGui import ( QCloseEvent ) import utils.ConfigManager as ConfigManager +import utils.TimerUtils as TimerUtils from gui.resources.ui.Ui_ALTimerTaskManageWidget import Ui_ALTimerTaskManageWidget from gui.ALTimerTaskAddDialog import ALTimerTaskAddDialog, ALTimerTaskStatus @@ -61,9 +64,19 @@ class ALTimerTaskItemWidget(QWidget): TaskNameLabel.setFont(TaskNameLabelFont) TaskNameLabel.setFixedHeight(25) self.TaskInfoLayout.addWidget(TaskNameLabel) - ExecuteTimeStr = self.__timer_task["execute_time"].strftime("%Y-%m-%d %H:%M:%S") - ExecuteTimeLabel = QLabel(f"执行时间: {ExecuteTimeStr}") + if self.__timer_task.get("repeat", False): + repeat_days = self.__timer_task.get("repeat_days", []) + if len(repeat_days) == 7: + time_str = f"{self.__timer_task.get('repeat_hour', 0):02d}:{self.__timer_task.get('repeat_minute', 0):02d}:{self.__timer_task.get('repeat_second', 0):02d}" + ExecuteTimeLabel = QLabel(f"下次执行时间: {ExecuteTimeStr} (每日 {time_str})") + else: + day_names = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] + selected_days = [day_names[d] for d in repeat_days] + time_str = f"{self.__timer_task.get('repeat_hour', 0):02d}:{self.__timer_task.get('repeat_minute', 0):02d}:{self.__timer_task.get('repeat_second', 0):02d}" + ExecuteTimeLabel = QLabel(f"下次执行时间: {ExecuteTimeStr} (每{','.join(selected_days)} {time_str})") + else: + ExecuteTimeLabel = QLabel(f"执行时间: {ExecuteTimeStr}") ExecuteTimeLabel.setStyleSheet("color: #969696;") ExecuteTimeLabel.setFixedHeight(20) self.TaskInfoLayout.addWidget(ExecuteTimeLabel) @@ -124,6 +137,10 @@ class ALTimerTaskItemWidget(QWidget): if self.__timer_task["status"] == ALTimerTaskStatus.READY\ or self.__timer_task["status"] == ALTimerTaskStatus.RUNNING: self.DeleteButton.setEnabled(False) + if self.__timer_task.get("repeat", False): + self.HistoryButton = QPushButton("历史") + self.HistoryButton.setFixedSize(80, 25) + self.ItemWidgetLayout.addWidget(self.HistoryButton) self.setFixedHeight(55) @@ -334,6 +351,10 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): widget.DeleteButton.clicked.connect( lambda _, uuid = timer_task["task_uuid"]: self.deleteTask(uuid) ) + if timer_task.get("repeat", False) and hasattr(widget, "HistoryButton"): + widget.HistoryButton.clicked.connect( + lambda _, task = timer_task: self.showTaskHistory(task) + ) item.setSizeHint(widget.size()) self.TimerTasksListWidget.addItem(item) self.TimerTasksListWidget.setItemWidget(item, widget) @@ -392,6 +413,16 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): self.timerTasksChanged.emit() + def showTaskHistory( + self, + task: dict + ): + + dialog = ALTimerTaskHistoryDialog.ALTimerTaskHistoryDialog(self, task) + if dialog.exec() == QDialog.DialogCode.Accepted: + self.timerTasksChanged.emit() + + def checkTasks( self ): @@ -471,7 +502,32 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): for task in self.__timer_tasks: if task["task_uuid"] == timer_task["task_uuid"]: - task["status"] = ALTimerTaskStatus.EXECUTED + if task.get("repeat", False): + if "history" not in task: + task["history"] = [] + executed_time = datetime.now() + duration = (executed_time - task["execute_time"]).total_seconds() + task["history"].append({ + "execute_time": task["execute_time"].strftime("%Y-%m-%d %H:%M:%S"), + "executed_time": executed_time.strftime("%Y-%m-%d %H:%M:%S"), + "result": "成功", + "duration": duration, + "uuid": timer_task["task_uuid"] + }) + next_time = TimerUtils.calculateNextRepeatTime( + task["repeat_days"], + task["repeat_hour"], + task["repeat_minute"], + task["repeat_second"] + ) + if next_time: + task["execute_time"] = next_time + task["status"] = ALTimerTaskStatus.PENDING + task["executed"] = False + else: + task["status"] = ALTimerTaskStatus.EXECUTED + else: + task["status"] = ALTimerTaskStatus.EXECUTED self.timerTasksChanged.emit() @Slot(dict) @@ -482,5 +538,17 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): for task in self.__timer_tasks: if task["task_uuid"] == timer_task["task_uuid"]: + if task.get("repeat", False): + if "history" not in task: + task["history"] = [] + executed_time = datetime.now() + duration = (executed_time - task["execute_time"]).total_seconds() + task["history"].append({ + "execute_time": task["execute_time"].strftime("%Y-%m-%d %H:%M:%S"), + "executed_time": executed_time.strftime("%Y-%m-%d %H:%M:%S"), + "result": "失败", + "duration": duration, + "uuid": timer_task["task_uuid"] + }) task["status"] = ALTimerTaskStatus.ERROR self.timerTasksChanged.emit() diff --git a/src/gui/resources/ui/ALTimerTaskManageWidget.ui b/src/gui/resources/ui/ALTimerTaskManageWidget.ui index 85daedf..6729ac5 100644 --- a/src/gui/resources/ui/ALTimerTaskManageWidget.ui +++ b/src/gui/resources/ui/ALTimerTaskManageWidget.ui @@ -18,7 +18,7 @@ - 600 + 800 400 From b73242be002eb140cf5d737d128c080b7c10f216 Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Tue, 17 Mar 2026 14:33:37 +0800 Subject: [PATCH 06/14] =?UTF-8?q?fix(ALTimerTaskAddDialog):=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E6=B7=BB=E5=8A=A0=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A1=86=E7=9A=84=E9=87=8D=E5=A4=8D=E9=80=89?= =?UTF-8?q?=E9=A1=B9=E7=9A=84=20Label=20=E6=8F=8F=E8=BF=B0=E5=92=8C?= =?UTF-8?q?=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gui/resources/ui/ALTimerTaskAddDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/resources/ui/ALTimerTaskAddDialog.ui b/src/gui/resources/ui/ALTimerTaskAddDialog.ui index 1d3155f..1151895 100644 --- a/src/gui/resources/ui/ALTimerTaskAddDialog.ui +++ b/src/gui/resources/ui/ALTimerTaskAddDialog.ui @@ -254,7 +254,7 @@ - 重复周期(全选为每日运行): + 重复周期(全选或全不选都为每日运行): From c679a1c79eda74912fd9e7eacb9ce48b10fd9548 Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Tue, 17 Mar 2026 14:35:47 +0800 Subject: [PATCH 07/14] =?UTF-8?q?fix(ALTimerTaskAddDialog):=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E4=B8=AD=E7=9B=B8=E5=AF=B9=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E7=9A=84=E5=B8=83=E5=B1=80=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 由栅格布局改为水平布局,该区域的高度与绝对时间控件的高度一致 --- src/gui/ALTimerTaskAddDialog.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gui/ALTimerTaskAddDialog.py b/src/gui/ALTimerTaskAddDialog.py index 4c73715..665ea01 100644 --- a/src/gui/ALTimerTaskAddDialog.py +++ b/src/gui/ALTimerTaskAddDialog.py @@ -60,28 +60,28 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog): self.TimerConfigLayout.addWidget(self.SpecificTimerWidget) self.RelativeTimerWidget = QWidget() - self.RelativeTimerLayout = QGridLayout(self.RelativeTimerWidget) - self.RelativeTimerLayout.addWidget(QLabel("相对时间:"), 0, 0) + self.RelativeTimerLayout = QHBoxLayout(self.RelativeTimerWidget) + self.RelativeTimerLayout.addWidget(QLabel("相对时间:")) self.RelativeDaySpinBox = QSpinBox() self.RelativeDaySpinBox.setMinimum(0) - self.RelativeDaySpinBox.setMaximum(365) + self.RelativeDaySpinBox.setMaximum(364) self.RelativeDaySpinBox.setSuffix("天") - self.RelativeTimerLayout.addWidget(self.RelativeDaySpinBox, 1, 0) + self.RelativeTimerLayout.addWidget(self.RelativeDaySpinBox) self.RelativeHourSpinBox = QSpinBox() self.RelativeHourSpinBox.setMinimum(0) self.RelativeHourSpinBox.setMaximum(23) self.RelativeHourSpinBox.setSuffix("时") - self.RelativeTimerLayout.addWidget(self.RelativeHourSpinBox, 1, 1) + self.RelativeTimerLayout.addWidget(self.RelativeHourSpinBox) self.RelativeMinuteSpinBox = QSpinBox() self.RelativeMinuteSpinBox.setMinimum(0) self.RelativeMinuteSpinBox.setMaximum(59) self.RelativeMinuteSpinBox.setSuffix("分") - self.RelativeTimerLayout.addWidget(self.RelativeMinuteSpinBox, 1, 2) + self.RelativeTimerLayout.addWidget(self.RelativeMinuteSpinBox) self.RelativeSecondSpinBox = QSpinBox() self.RelativeSecondSpinBox.setMinimum(0) self.RelativeSecondSpinBox.setMaximum(59) self.RelativeSecondSpinBox.setSuffix("秒") - self.RelativeTimerLayout.addWidget(self.RelativeSecondSpinBox, 1, 3) + self.RelativeTimerLayout.addWidget(self.RelativeSecondSpinBox) self.TimerConfigLayout.addWidget(self.RelativeTimerWidget) self.RelativeTimerWidget.setVisible(False) From c02c6ddbe36c9f7dfb65829b230988756dc1db4f Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Tue, 17 Mar 2026 14:37:33 +0800 Subject: [PATCH 08/14] =?UTF-8?q?fix(ALTimerTaskAddDialog):=20=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E4=B8=AD=E5=A4=9A=E4=BD=99=E7=9A=84=E5=AD=97=E6=AE=B5=20?= =?UTF-8?q?=E2=80=98repeat=5Frecords=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gui/ALTimerTaskAddDialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/ALTimerTaskAddDialog.py b/src/gui/ALTimerTaskAddDialog.py index 665ea01..c5af2e0 100644 --- a/src/gui/ALTimerTaskAddDialog.py +++ b/src/gui/ALTimerTaskAddDialog.py @@ -128,7 +128,6 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog): "status": ALTimerTaskStatus.PENDING, "executed": False, "repeat": self.RepeatCheckBox.isChecked(), - "repeat_records": [] } if task_data["repeat"]: repeat_days = [] From 0aea9b1540ad240c59232d6f65b1bbafb7930681 Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Tue, 17 Mar 2026 14:39:01 +0800 Subject: [PATCH 09/14] =?UTF-8?q?fix(ALTimerTaskAddDialog):=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E6=B7=BB=E5=8A=A0=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A1=86=E7=9A=84=E9=87=8D=E5=A4=8D=E9=80=89?= =?UTF-8?q?=E9=A1=B9=E7=9A=84=20Label=20=E6=8F=8F=E8=BF=B0=E5=92=8C?= =?UTF-8?q?=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 对 (b73242be002eb140cf5d737d128c080b7c10f216) 的补充提交 --- src/gui/resources/ui/ALTimerTaskAddDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/resources/ui/ALTimerTaskAddDialog.ui b/src/gui/resources/ui/ALTimerTaskAddDialog.ui index 1151895..1df3808 100644 --- a/src/gui/resources/ui/ALTimerTaskAddDialog.ui +++ b/src/gui/resources/ui/ALTimerTaskAddDialog.ui @@ -259,7 +259,7 @@ - + 0 From 67493349dd6eb3cb2ac0c44c4801bce92f178019 Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Tue, 17 Mar 2026 14:42:07 +0800 Subject: [PATCH 10/14] =?UTF-8?q?style(ALTimerTaskManageWidget):=20?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=20import=20=E8=AF=AD=E5=8F=A5=E7=9A=84?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 对 gui.ALTimerTaskAddDialog 的 import 语句进行格式化 --- src/gui/ALTimerTaskManageWidget.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gui/ALTimerTaskManageWidget.py b/src/gui/ALTimerTaskManageWidget.py index 735114c..09bba78 100644 --- a/src/gui/ALTimerTaskManageWidget.py +++ b/src/gui/ALTimerTaskManageWidget.py @@ -21,8 +21,6 @@ from PySide6.QtWidgets import ( QDialog, QWidget, QListWidgetItem, QMessageBox, QHBoxLayout, QVBoxLayout, QLabel, QPushButton ) - -import gui.ALTimerTaskHistoryDialog as ALTimerTaskHistoryDialog from PySide6.QtGui import ( QCloseEvent ) @@ -32,6 +30,7 @@ import utils.TimerUtils as TimerUtils from gui.resources.ui.Ui_ALTimerTaskManageWidget import Ui_ALTimerTaskManageWidget from gui.ALTimerTaskAddDialog import ALTimerTaskAddDialog, ALTimerTaskStatus +from gui.ALTimerTaskHistoryDialog import ALTimerTaskHistoryDialog class ALTimerTaskItemWidget(QWidget): From 82744e3a2d2e85acde0cfd0acde7b931fe5f985f Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Tue, 17 Mar 2026 14:42:47 +0800 Subject: [PATCH 11/14] =?UTF-8?q?refactor(ALTimerTaskItemWidget):=20?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E5=8F=98=E9=87=8F=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gui/ALTimerTaskManageWidget.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/gui/ALTimerTaskManageWidget.py b/src/gui/ALTimerTaskManageWidget.py index 09bba78..431e275 100644 --- a/src/gui/ALTimerTaskManageWidget.py +++ b/src/gui/ALTimerTaskManageWidget.py @@ -66,20 +66,22 @@ class ALTimerTaskItemWidget(QWidget): ExecuteTimeStr = self.__timer_task["execute_time"].strftime("%Y-%m-%d %H:%M:%S") if self.__timer_task.get("repeat", False): repeat_days = self.__timer_task.get("repeat_days", []) + repeat_hour = self.__timer_task.get("repeat_hour", 0) + repeat_minute = self.__timer_task.get("repeat_minute", 0) + repeat_second = self.__timer_task.get("repeat_second", 0) if len(repeat_days) == 7: - time_str = f"{self.__timer_task.get('repeat_hour', 0):02d}:{self.__timer_task.get('repeat_minute', 0):02d}:{self.__timer_task.get('repeat_second', 0):02d}" + time_str = f"{repeat_hour:02d}:{repeat_minute:02d}:{repeat_second:02d}" ExecuteTimeLabel = QLabel(f"下次执行时间: {ExecuteTimeStr} (每日 {time_str})") else: day_names = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] selected_days = [day_names[d] for d in repeat_days] - time_str = f"{self.__timer_task.get('repeat_hour', 0):02d}:{self.__timer_task.get('repeat_minute', 0):02d}:{self.__timer_task.get('repeat_second', 0):02d}" + time_str = f"{repeat_hour:02d}:{repeat_minute:02d}:{repeat_second:02d}" ExecuteTimeLabel = QLabel(f"下次执行时间: {ExecuteTimeStr} (每{','.join(selected_days)} {time_str})") else: ExecuteTimeLabel = QLabel(f"执行时间: {ExecuteTimeStr}") ExecuteTimeLabel.setStyleSheet("color: #969696;") ExecuteTimeLabel.setFixedHeight(20) self.TaskInfoLayout.addWidget(ExecuteTimeLabel) - self.ItemWidgetLayout.addLayout(self.TaskInfoLayout) self.ItemWidgetLayout.addStretch() From d55d2075cbd541f5dac70bcdf43ab476fdaa81b7 Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Tue, 17 Mar 2026 14:46:19 +0800 Subject: [PATCH 12/14] =?UTF-8?q?optimze(gui):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=8C=89=E9=92=AE=E6=A0=B7=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E4=BD=BF=E5=85=B6=E6=9B=B4=E5=8A=A0=E9=86=92=E7=9B=AE=EF=BC=9B?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20ALTimerTaskManageWidget=20=E7=9A=84?= =?UTF-8?q?=E5=AE=BD=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化了 ALConfigWidget, ALTimerTaskManageWidget 中的删除按钮样式(字体颜色更改为红色),使其更加醒目 - 优化了 ALTimerTaskManageWidget 的宽度,使其适应内容宽度 --- src/gui/ALTimerTaskManageWidget.py | 13 +++++++------ src/gui/resources/ui/ALConfigWidget.ui | 5 +++++ src/gui/resources/ui/ALTimerTaskManageWidget.ui | 9 +++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/gui/ALTimerTaskManageWidget.py b/src/gui/ALTimerTaskManageWidget.py index 431e275..4ad9dae 100644 --- a/src/gui/ALTimerTaskManageWidget.py +++ b/src/gui/ALTimerTaskManageWidget.py @@ -132,16 +132,17 @@ class ALTimerTaskItemWidget(QWidget): TaskModeLabel.setFixedSize(60, 25) self.ItemWidgetLayout.addWidget(TaskModeLabel) - self.DeleteButton = QPushButton("删除") - self.DeleteButton.setFixedSize(80, 25) - self.ItemWidgetLayout.addWidget(self.DeleteButton) - if self.__timer_task["status"] == ALTimerTaskStatus.READY\ - or self.__timer_task["status"] == ALTimerTaskStatus.RUNNING: - self.DeleteButton.setEnabled(False) if self.__timer_task.get("repeat", False): self.HistoryButton = QPushButton("历史") self.HistoryButton.setFixedSize(80, 25) self.ItemWidgetLayout.addWidget(self.HistoryButton) + self.DeleteButton = QPushButton("删除") + self.DeleteButton.setFixedSize(80, 25) + self.DeleteButton.setStyleSheet("color: #DC0000;") + self.ItemWidgetLayout.addWidget(self.DeleteButton) + if self.__timer_task["status"] == ALTimerTaskStatus.READY\ + or self.__timer_task["status"] == ALTimerTaskStatus.RUNNING: + self.DeleteButton.setEnabled(False) self.setFixedHeight(55) diff --git a/src/gui/resources/ui/ALConfigWidget.ui b/src/gui/resources/ui/ALConfigWidget.ui index ab456f4..ea41727 100644 --- a/src/gui/resources/ui/ALConfigWidget.ui +++ b/src/gui/resources/ui/ALConfigWidget.ui @@ -195,6 +195,11 @@ 25 + + QPushButton { + color: #DC0000; +} + 删除用户 diff --git a/src/gui/resources/ui/ALTimerTaskManageWidget.ui b/src/gui/resources/ui/ALTimerTaskManageWidget.ui index 6729ac5..2bb5bc6 100644 --- a/src/gui/resources/ui/ALTimerTaskManageWidget.ui +++ b/src/gui/resources/ui/ALTimerTaskManageWidget.ui @@ -6,13 +6,13 @@ 0 0 - 400 + 500 400 - 400 + 500 400 @@ -306,6 +306,11 @@ 25 + + QPushButton { + color: #DC0000; +} + 清除全部 From 94dc22819fa28dce9155baea0f09d306adc9478e Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Tue, 17 Mar 2026 14:51:55 +0800 Subject: [PATCH 13/14] =?UTF-8?q?optimize(gui):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化任务历史查看对话框的界面布局和交互体验 - 新增任务状态枚举值以支持更完整的状态管理 - 统一重复任务执行后的历史记录处理逻辑 - 增强删除任务时的确认机制,删除可重复任务前展示详细执行记录 - 完善批量清除任务的验证流程,检查运行中任务并确认重复任务删除 --- src/gui/ALTimerTaskAddDialog.py | 1 + src/gui/ALTimerTaskHistoryDialog.py | 89 +++++++++------ src/gui/ALTimerTaskManageWidget.py | 162 ++++++++++++++++++++-------- 3 files changed, 175 insertions(+), 77 deletions(-) diff --git a/src/gui/ALTimerTaskAddDialog.py b/src/gui/ALTimerTaskAddDialog.py index c5af2e0..dde52b9 100644 --- a/src/gui/ALTimerTaskAddDialog.py +++ b/src/gui/ALTimerTaskAddDialog.py @@ -27,6 +27,7 @@ class ALTimerTaskStatus(Enum): EXECUTED = "已执行" ERROR = "执行失败" OUTDATED = "已过期" + UNKNOWN = "未知" class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog): diff --git a/src/gui/ALTimerTaskHistoryDialog.py b/src/gui/ALTimerTaskHistoryDialog.py index e125a9b..99195bb 100644 --- a/src/gui/ALTimerTaskHistoryDialog.py +++ b/src/gui/ALTimerTaskHistoryDialog.py @@ -12,9 +12,12 @@ from datetime import datetime from PySide6.QtCore import Slot, Qt from PySide6.QtWidgets import ( QDialog, QTableWidget, QTableWidgetItem, - QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QHeaderView + QVBoxLayout, QHBoxLayout, QGridLayout, + QPushButton, QLabel, QHeaderView ) +from gui.ALTimerTaskAddDialog import ALTimerTaskStatus + class ALTimerTaskHistoryDialog(QDialog): @@ -30,85 +33,105 @@ class ALTimerTaskHistoryDialog(QDialog): self.__history = task_data.get("history", []) self.modifyUi() + self.connectSignals() + def modifyUi( self ): self.setWindowTitle("定时任务执行历史 - AutoLibrary") - self.setMinimumSize(600, 400) + self.setMinimumSize(300, 300) + self.setMaximumSize(500, 400) MainLayout = QVBoxLayout(self) - - InfoLayout = QHBoxLayout() + InfoLayout = QGridLayout() TaskNameLabel = QLabel(f"任务: {self.__task_data.get('name', '未命名')}") TaskNameLabel.setStyleSheet("font-weight: bold; font-size: 14px;") - InfoLayout.addWidget(TaskNameLabel) - InfoLayout.addStretch() + InfoLayout.addWidget(TaskNameLabel, 0, 0) + TaskUUIDLabel = QLabel(f"UUID: {self.__task_data.get('task_uuid', '未命名')}") + TaskUUIDLabel.setStyleSheet("font-size: 10px;") + InfoLayout.addWidget(TaskUUIDLabel, 1, 0) + InfoLayout.setColumnStretch(0, 1) if self.__task_data.get("repeat", False): - repeat_label = QLabel("重复任务") - repeat_label.setStyleSheet("color: #2294FF; font-weight: bold;") - InfoLayout.addWidget(repeat_label) + RepeatLabel = QLabel("重复任务") + RepeatLabel.setStyleSheet("color: #2294FF; font-weight: bold; font-size: 12px;") + InfoLayout.addWidget(RepeatLabel, 0, 1) MainLayout.addLayout(InfoLayout) self.HistoryTableWidget = QTableWidget() - self.HistoryTableWidget.setColumnCount(4) - self.HistoryTableWidget.setHorizontalHeaderLabels(["执行时间", "结果", "耗时(秒/s)", "uuid"]) + self.HistoryTableWidget.setColumnCount(3) + self.HistoryTableWidget.setHorizontalHeaderLabels(["执行时间", "结果", "耗时(秒/s)"]) self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) - self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) - self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) + self.HistoryTableWidget.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) self.HistoryTableWidget.verticalHeader().setVisible(False) self.HistoryTableWidget.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) self.HistoryTableWidget.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) - self._loadHistory() + self.loadHistory() MainLayout.addWidget(self.HistoryTableWidget) ButtonLayout = QHBoxLayout() ButtonLayout.addStretch() self.CloseButton = QPushButton("关闭") self.CloseButton.setFixedSize(80, 25) - self.CloseButton.clicked.connect(self.accept) + self.CloseButton.setDefault(True) self.ClearHistoryButton = QPushButton("清空历史") self.ClearHistoryButton.setFixedSize(80, 25) - self.ClearHistoryButton.clicked.connect(self._clearHistory) + self.ClearHistoryButton.setStyleSheet("color: #DC0000;") ButtonLayout.addWidget(self.ClearHistoryButton) ButtonLayout.addWidget(self.CloseButton) MainLayout.addLayout(ButtonLayout) - def _loadHistory( + def connectSignals( + self + ): + + self.CloseButton.clicked.connect(self.accept) + self.ClearHistoryButton.clicked.connect(self.onClearHistoryButtonClicked) + + + def loadHistory( self ): self.HistoryTableWidget.setRowCount(len(self.__history)) for row, record in enumerate(self.__history): - self._addHistoryRow(row, record) + self.addHistoryRow(row, record) - def _addHistoryRow( + def addHistoryRow( self, row: int, record: dict ): - execute_time_str = record.get("execute_time", "") - result = record.get("result", "未知") + execute_time = record.get("execute_time", "") + result = record.get("result", ALTimerTaskStatus.UNKNOWN) duration = record.get("duration", 0) - uuid = record.get("uuid", "") - self.HistoryTableWidget.setItem(row, 0, QTableWidgetItem(execute_time_str)) - self.HistoryTableWidget.setItem(row, 1, QTableWidgetItem(result)) - duration_item = QTableWidgetItem(f"{duration:.2f}") - duration_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) - self.HistoryTableWidget.setItem(row, 2, duration_item) - self.HistoryTableWidget.setItem(row, 3, QTableWidgetItem(uuid)) - if result == "成功": - self.HistoryTableWidget.item(row, 1).setForeground(Qt.GlobalColor.green) - elif result == "失败": - self.HistoryTableWidget.item(row, 1).setForeground(Qt.GlobalColor.red) + ExecuteTimeItem = QTableWidgetItem(execute_time) + ExecuteTimeItem.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + self.HistoryTableWidget.setItem(row, 0, ExecuteTimeItem) + ResultItem = QTableWidgetItem(result.value) + ResultItem.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + match result: + case ALTimerTaskStatus.EXECUTED: + ResultItem.setForeground(Qt.GlobalColor.green) + case ALTimerTaskStatus.ERROR: + ResultItem.setForeground(Qt.GlobalColor.red) + case ALTimerTaskStatus.OUTDATED: + ResultItem.setForeground(Qt.GlobalColor.red) + case _: + ResultItem.setForeground(Qt.GlobalColor.black) + self.HistoryTableWidget.setItem(row, 1, ResultItem) + DurationItem = QTableWidgetItem(f"{duration:.2f}") + DurationItem.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + self.HistoryTableWidget.setItem(row, 2, DurationItem) + self.HistoryTableWidget.setRowHeight(row, 25) @Slot() - def _clearHistory( + def onClearHistoryButtonClicked( self ): diff --git a/src/gui/ALTimerTaskManageWidget.py b/src/gui/ALTimerTaskManageWidget.py index 4ad9dae..a5536d9 100644 --- a/src/gui/ALTimerTaskManageWidget.py +++ b/src/gui/ALTimerTaskManageWidget.py @@ -224,6 +224,9 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): task["add_time"] = datetime.strptime(task["add_time"], "%Y-%m-%d %H:%M:%S") task["execute_time"] = datetime.strptime(task["execute_time"], "%Y-%m-%d %H:%M:%S") task["status"] = ALTimerTaskStatus(task["status"]) + if "history" in task: + for item in task["history"]: + item["result"] = ALTimerTaskStatus(item["result"]) return timer_tasks["timer_tasks"] raise Exception("定时任务配置文件格式错误") except Exception as e: @@ -245,6 +248,9 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): task["add_time"] = task["add_time"].strftime("%Y-%m-%d %H:%M:%S") task["execute_time"] = task["execute_time"].strftime("%Y-%m-%d %H:%M:%S") task["status"] = task["status"].value + if "history" in task: + for item in task["history"]: + item["result"] = item["result"].value self.__cfg_mgr.set(ConfigManager.ConfigType.TIMERTASK, "", { "timer_tasks": timer_tasks }) return True except Exception as e: @@ -351,7 +357,7 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): item.setData(Qt.UserRole, timer_task) widget = ALTimerTaskItemWidget(self, timer_task) widget.DeleteButton.clicked.connect( - lambda _, uuid = timer_task["task_uuid"]: self.deleteTask(uuid) + lambda _, task = timer_task: self.deleteTask(timer_task) ) if timer_task.get("repeat", False) and hasattr(widget, "HistoryButton"): widget.HistoryButton.clicked.connect( @@ -373,11 +379,42 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): self.timerTasksChanged.emit() - def deleteTask( - self, - task_uuid: str + @staticmethod + def getTimerTaskDetailMessage( + timer_task: dict ): + return ( + f"任务名称:{timer_task["name"]}\n" + f"添加时间:{timer_task["add_time"]}\n" + f"当前状态:{timer_task["status"].value}\n" + f"下次执行时间:{datetime.strftime(timer_task["execute_time"], "%Y-%m-%d %H:%M:%S")}\n" + f"已执行次数:{len(timer_task['history'] if 'history' in timer_task else 0)}" + ) + + + def deleteTask( + self, + timer_task: dict + ): + + if timer_task["repeat"]: # when delete a repeat task + msgbox = QMessageBox(self) + msgbox.setIcon(QMessageBox.Icon.Question) + msgbox.setWindowTitle("警告 - AutoLibrary") + msgbox.setStandardButtons( + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) + msgbox.setText("删除可重复性任务将同时删除所有已执行的记录 !\n是否继续 ?") + msgbox.setDetailedText( + "以下可重复性任务将被删除:\n"\ + "\n" + f"{self.getTimerTaskDetailMessage(timer_task)}" + ) + result = msgbox.exec() + if result != QMessageBox.StandardButton.Yes: + return + task_uuid = timer_task["task_uuid"] self.__timer_tasks = [ x for x in self.__timer_tasks if x["task_uuid"] != task_uuid @@ -397,8 +434,9 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): "是否要清除所有定时任务 ?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) - if result is QMessageBox.StandardButton.No: + if result == QMessageBox.StandardButton.No: return + # READY and RUNNING tasks cannot be cleared in_queue_tasks = [ x for x in self.__timer_tasks if x["status"] == ALTimerTaskStatus.READY @@ -409,9 +447,40 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): QMessageBox.warning( self, "警告 - AutoLibrary", - "存在正在执行或已就绪的队列任务,无法清除所有定时任务 !" + f"存在 {in_queue_count} 个正在执行或已就绪的队列任务,无法清除所有定时任务 !" ) - self.__timer_tasks = in_queue_tasks + return + # repeat tasks ask before clear + repeat_tasks = [ + x for x in self.__timer_tasks + if x.get("repeat", False) + ] + repeat_tasks_count = len(repeat_tasks) + if repeat_tasks_count > 0: + msgbox = QMessageBox(self) + msgbox.setIcon(QMessageBox.Icon.Question) + msgbox.setWindowTitle("警告 - AutoLibrary") + msgbox.setStandardButtons( + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) + msgbox.setText( + f"存在 {repeat_tasks_count} 个可重复性任务,\n" + "删除可重复性任务将同时删除所有已执行的记录 !\n" + "是否继续 ?" + ) + delete_msgs = [ + self.getTimerTaskDetailMessage(x) for x in repeat_tasks + ] + msgbox.setDetailedText( + "以下可重复性任务将被删除:\n"\ + "\n" + f"{"\n\n".join(delete_msgs)}" + ) + result = msgbox.exec() + if result != QMessageBox.StandardButton.Yes: + return + # clear all tasks + self.__timer_tasks.clear() self.timerTasksChanged.emit() @@ -420,7 +489,7 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): task: dict ): - dialog = ALTimerTaskHistoryDialog.ALTimerTaskHistoryDialog(self, task) + dialog = ALTimerTaskHistoryDialog(self, task) if dialog.exec() == QDialog.DialogCode.Accepted: self.timerTasksChanged.emit() @@ -438,7 +507,10 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): if timer_task["status"] is not ALTimerTaskStatus.PENDING: continue if timer_task["execute_time"] <= now + timedelta(seconds = -5): - timer_task["status"] = ALTimerTaskStatus.OUTDATED + if timer_task.get("repeat", False): + self.onRepeatTimerTaskIs(ALTimerTaskStatus.OUTDATED, timer_task) + else: + timer_task["status"] = ALTimerTaskStatus.OUTDATED need_update = True else: timer_task["status"] = ALTimerTaskStatus.READY @@ -493,9 +565,40 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): for task in self.__timer_tasks: if task["task_uuid"] == timer_task["task_uuid"]: task["status"] = ALTimerTaskStatus.RUNNING + break self.timerTasksChanged.emit() + def onRepeatTimerTaskIs( + self, + status: ALTimerTaskStatus, + timer_task: dict + ) -> dict: + + if "history" not in timer_task: + timer_task["history"] = [] + executed_time = datetime.now() + duration = (executed_time - timer_task["execute_time"]).total_seconds() + timer_task["history"].append({ + "execute_time": timer_task["execute_time"].strftime("%Y-%m-%d %H:%M:%S"), + "executed_time": executed_time.strftime("%Y-%m-%d %H:%M:%S"), + "result": status, + "duration": duration if status is ALTimerTaskStatus.EXECUTED else 0, + "uuid": timer_task["task_uuid"] + }) + next_time = TimerUtils.calculateNextRepeatTime( + timer_task["repeat_days"], + timer_task["repeat_hour"], + timer_task["repeat_minute"], + timer_task["repeat_second"] + ) + if next_time: + timer_task["execute_time"] = next_time + timer_task["status"] = ALTimerTaskStatus.PENDING + timer_task["executed"] = False + else: + timer_task["status"] = status + @Slot(dict) def onTimerTaskIsExecuted( self, @@ -505,31 +608,10 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): for task in self.__timer_tasks: if task["task_uuid"] == timer_task["task_uuid"]: if task.get("repeat", False): - if "history" not in task: - task["history"] = [] - executed_time = datetime.now() - duration = (executed_time - task["execute_time"]).total_seconds() - task["history"].append({ - "execute_time": task["execute_time"].strftime("%Y-%m-%d %H:%M:%S"), - "executed_time": executed_time.strftime("%Y-%m-%d %H:%M:%S"), - "result": "成功", - "duration": duration, - "uuid": timer_task["task_uuid"] - }) - next_time = TimerUtils.calculateNextRepeatTime( - task["repeat_days"], - task["repeat_hour"], - task["repeat_minute"], - task["repeat_second"] - ) - if next_time: - task["execute_time"] = next_time - task["status"] = ALTimerTaskStatus.PENDING - task["executed"] = False - else: - task["status"] = ALTimerTaskStatus.EXECUTED + self.onRepeatTimerTaskIs(ALTimerTaskStatus.EXECUTED, task) else: task["status"] = ALTimerTaskStatus.EXECUTED + break self.timerTasksChanged.emit() @Slot(dict) @@ -541,16 +623,8 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): for task in self.__timer_tasks: if task["task_uuid"] == timer_task["task_uuid"]: if task.get("repeat", False): - if "history" not in task: - task["history"] = [] - executed_time = datetime.now() - duration = (executed_time - task["execute_time"]).total_seconds() - task["history"].append({ - "execute_time": task["execute_time"].strftime("%Y-%m-%d %H:%M:%S"), - "executed_time": executed_time.strftime("%Y-%m-%d %H:%M:%S"), - "result": "失败", - "duration": duration, - "uuid": timer_task["task_uuid"] - }) - task["status"] = ALTimerTaskStatus.ERROR + self.onRepeatTimerTaskIs(ALTimerTaskStatus.ERROR, task) + else: + task["status"] = ALTimerTaskStatus.ERROR + break self.timerTasksChanged.emit() From 68e002ba8e54fdcb5545663459232a2473031543 Mon Sep 17 00:00:00 2001 From: KenanZhu <3471685733@qq.com> Date: Tue, 17 Mar 2026 15:27:03 +0800 Subject: [PATCH 14/14] =?UTF-8?q?fix(ALTimerTaskManageWidget):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=88=A0=E9=99=A4=E4=BB=BB=E5=8A=A1=E7=9A=84=E4=BF=A1?= =?UTF-8?q?=E5=8F=B7=E6=A7=BD=E5=8F=82=E6=95=B0=E4=BC=A0=E9=80=92=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复删除任务的信号槽参数传递问题,此次修复通过 lambda 表达式将当前的 task 作为参数传递,避免了闭包陷阱。 --- src/gui/ALTimerTaskManageWidget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/ALTimerTaskManageWidget.py b/src/gui/ALTimerTaskManageWidget.py index a5536d9..3fa2b65 100644 --- a/src/gui/ALTimerTaskManageWidget.py +++ b/src/gui/ALTimerTaskManageWidget.py @@ -357,7 +357,7 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): item.setData(Qt.UserRole, timer_task) widget = ALTimerTaskItemWidget(self, timer_task) widget.DeleteButton.clicked.connect( - lambda _, task = timer_task: self.deleteTask(timer_task) + lambda _, task = timer_task: self.deleteTask(task) ) if timer_task.get("repeat", False) and hasattr(widget, "HistoryButton"): widget.HistoryButton.clicked.connect(