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(