feat: 修复线程间隔自动运行倒计时功能,增强状态管理

This commit is contained in:
QLHazyCoder
2026-04-14 15:26:02 +08:00
parent 5ce2d5d057
commit ee44367167
2 changed files with 164 additions and 23 deletions
+60 -4
View File
@@ -118,6 +118,9 @@ const DEFAULT_STATE = {
autoRunRoundSummaries: [], // 自动运行轮次摘要。 autoRunRoundSummaries: [], // 自动运行轮次摘要。
scheduledAutoRunAt: null, // 自动运行计划启动时间戳。 scheduledAutoRunAt: null, // 自动运行计划启动时间戳。
scheduledAutoRunPlan: null, // 自动运行计划参数快照。 scheduledAutoRunPlan: null, // 自动运行计划参数快照。
autoRunCountdownAt: null,
autoRunCountdownTitle: '',
autoRunCountdownNote: '',
signupVerificationRequestedAt: null, signupVerificationRequestedAt: null,
loginVerificationRequestedAt: null, loginVerificationRequestedAt: null,
currentHotmailAccountId: null, currentHotmailAccountId: null,
@@ -2337,7 +2340,20 @@ function getAutoRunStatusPayload(phase, payload = {}) {
? (payload.scheduledAt ?? payload.scheduledAutoRunAt ?? null) ? (payload.scheduledAt ?? payload.scheduledAutoRunAt ?? null)
: null; : null;
const scheduledAt = rawScheduledAt === null ? null : Number(rawScheduledAt); const scheduledAt = rawScheduledAt === null ? null : Number(rawScheduledAt);
const autoRunning = phase === 'scheduled' || phase === 'running' || phase === 'waiting_step' || phase === 'waiting_email' || phase === 'retrying'; const rawCountdownAt = payload.countdownAt ?? payload.autoRunCountdownAt ?? null;
const countdownAt = rawCountdownAt === null ? null : Number(rawCountdownAt);
const countdownTitle = payload.countdownTitle === undefined
? ''
: String(payload.countdownTitle || '');
const countdownNote = payload.countdownNote === undefined
? ''
: String(payload.countdownNote || '');
const autoRunning = phase === 'scheduled'
|| phase === 'running'
|| phase === 'waiting_step'
|| phase === 'waiting_email'
|| phase === 'retrying'
|| phase === 'waiting_interval';
return { return {
autoRunning, autoRunning,
@@ -2346,6 +2362,9 @@ function getAutoRunStatusPayload(phase, payload = {}) {
autoRunTotalRuns: totalRuns, autoRunTotalRuns: totalRuns,
autoRunAttemptRun: attemptRun, autoRunAttemptRun: attemptRun,
scheduledAutoRunAt: Number.isFinite(scheduledAt) ? scheduledAt : null, scheduledAutoRunAt: Number.isFinite(scheduledAt) ? scheduledAt : null,
autoRunCountdownAt: Number.isFinite(countdownAt) ? countdownAt : null,
autoRunCountdownTitle: countdownTitle,
autoRunCountdownNote: countdownNote,
}; };
} }
@@ -2353,12 +2372,16 @@ async function broadcastAutoRunStatus(phase, payload = {}, extraState = {}) {
const rawScheduledAt = phase === 'scheduled' const rawScheduledAt = phase === 'scheduled'
? (payload.scheduledAt ?? payload.scheduledAutoRunAt ?? null) ? (payload.scheduledAt ?? payload.scheduledAutoRunAt ?? null)
: null; : null;
const rawCountdownAt = payload.countdownAt ?? payload.autoRunCountdownAt ?? null;
const statusPayload = { const statusPayload = {
phase, phase,
currentRun: payload.currentRun ?? autoRunCurrentRun, currentRun: payload.currentRun ?? autoRunCurrentRun,
totalRuns: payload.totalRuns ?? autoRunTotalRuns, totalRuns: payload.totalRuns ?? autoRunTotalRuns,
attemptRun: payload.attemptRun ?? autoRunAttemptRun, attemptRun: payload.attemptRun ?? autoRunAttemptRun,
scheduledAt: rawScheduledAt === null ? null : Number(rawScheduledAt), scheduledAt: rawScheduledAt === null ? null : Number(rawScheduledAt),
countdownAt: rawCountdownAt === null ? null : Number(rawCountdownAt),
countdownTitle: payload.countdownTitle === undefined ? '' : String(payload.countdownTitle || ''),
countdownNote: payload.countdownNote === undefined ? '' : String(payload.countdownNote || ''),
}; };
await setState({ await setState({
@@ -2372,7 +2395,13 @@ async function broadcastAutoRunStatus(phase, payload = {}, extraState = {}) {
} }
function isAutoRunLockedState(state) { function isAutoRunLockedState(state) {
return Boolean(state.autoRunning) && (state.autoRunPhase === 'running' || state.autoRunPhase === 'waiting_step' || state.autoRunPhase === 'retrying'); return Boolean(state.autoRunning)
&& (
state.autoRunPhase === 'running'
|| state.autoRunPhase === 'waiting_step'
|| state.autoRunPhase === 'retrying'
|| state.autoRunPhase === 'waiting_interval'
);
} }
function isAutoRunPausedState(state) { function isAutoRunPausedState(state) {
@@ -2452,6 +2481,9 @@ async function scheduleAutoRun(totalRuns, options = {}) {
totalRuns: plan.totalRuns, totalRuns: plan.totalRuns,
attemptRun: 0, attemptRun: 0,
scheduledAt, scheduledAt,
countdownAt: scheduledAt,
countdownTitle: '已计划自动运行',
countdownNote: `计划于 ${formatAutoRunScheduleTime(scheduledAt)} 开始`,
}, },
{ {
autoRunSkipFailures: plan.autoRunSkipFailures, autoRunSkipFailures: plan.autoRunSkipFailures,
@@ -4025,6 +4057,18 @@ async function logAutoRunFinalSummary(totalRuns, roundSummaries = []) {
} }
} }
async function sleepWithAutoRunCountdown(waitMs, payload = {}) {
if (!Number.isFinite(waitMs) || waitMs <= 0) {
return;
}
await broadcastAutoRunStatus('waiting_interval', {
...payload,
countdownAt: Date.now() + waitMs,
});
await sleepWithStop(waitMs);
}
async function waitBetweenAutoRunRounds(targetRun, totalRuns, roundSummary) { async function waitBetweenAutoRunRounds(targetRun, totalRuns, roundSummary) {
if (totalRuns <= 1 || targetRun >= totalRuns) { if (totalRuns <= 1 || targetRun >= totalRuns) {
return; return;
@@ -4042,7 +4086,13 @@ async function waitBetweenAutoRunRounds(targetRun, totalRuns, roundSummary) {
`线程间隔:第 ${targetRun}/${totalRuns} 轮已${statusLabel},等待 ${fallbackThreadIntervalMinutes} 分钟后开始下一轮。`, `线程间隔:第 ${targetRun}/${totalRuns} 轮已${statusLabel},等待 ${fallbackThreadIntervalMinutes} 分钟后开始下一轮。`,
'info' 'info'
); );
await sleepWithStop(fallbackThreadIntervalMinutes * 60 * 1000); await sleepWithAutoRunCountdown(fallbackThreadIntervalMinutes * 60 * 1000, {
currentRun: targetRun,
totalRuns,
attemptRun: autoRunAttemptRun,
countdownTitle: '线程间隔中',
countdownNote: `${Math.min(targetRun + 1, totalRuns)}/${totalRuns} 轮即将开始`,
});
} }
async function waitBeforeAutoRunRetry(targetRun, totalRuns, nextAttemptRun) { async function waitBeforeAutoRunRetry(targetRun, totalRuns, nextAttemptRun) {
@@ -4057,7 +4107,13 @@ async function waitBeforeAutoRunRetry(targetRun, totalRuns, nextAttemptRun) {
`线程间隔:等待 ${fallbackThreadIntervalMinutes} 分钟后开始第 ${targetRun}/${totalRuns} 轮第 ${nextAttemptRun} 次尝试。`, `线程间隔:等待 ${fallbackThreadIntervalMinutes} 分钟后开始第 ${targetRun}/${totalRuns} 轮第 ${nextAttemptRun} 次尝试。`,
'info' 'info'
); );
await sleepWithStop(fallbackThreadIntervalMinutes * 60 * 1000); await sleepWithAutoRunCountdown(fallbackThreadIntervalMinutes * 60 * 1000, {
currentRun: targetRun,
totalRuns,
attemptRun: nextAttemptRun,
countdownTitle: '线程间隔中',
countdownNote: `${targetRun}/${totalRuns} 轮第 ${nextAttemptRun} 次尝试即将开始`,
});
} }
async function handleAutoRunLoopUnhandledError(error) { async function handleAutoRunLoopUnhandledError(error) {
+99 -14
View File
@@ -153,6 +153,9 @@ let currentAutoRun = {
totalRuns: 1, totalRuns: 1,
attemptRun: 0, attemptRun: 0,
scheduledAt: null, scheduledAt: null,
countdownAt: null,
countdownTitle: '',
countdownNote: '',
}; };
let settingsDirty = false; let settingsDirty = false;
let settingsSaveInFlight = false; let settingsSaveInFlight = false;
@@ -574,26 +577,45 @@ function syncLatestState(nextState) {
}; };
} }
function hasOwnStateValue(source, key) {
return Object.prototype.hasOwnProperty.call(source, key);
}
function readAutoRunStateValue(source, keys, fallback) {
for (const key of keys) {
if (hasOwnStateValue(source, key)) {
return source[key];
}
}
return fallback;
}
function syncAutoRunState(source = {}) { function syncAutoRunState(source = {}) {
const phase = source.autoRunPhase ?? source.phase ?? currentAutoRun.phase; const phase = source.autoRunPhase ?? source.phase ?? currentAutoRun.phase;
const autoRunning = source.autoRunning !== undefined const autoRunning = source.autoRunning !== undefined
? Boolean(source.autoRunning) ? Boolean(source.autoRunning)
: (source.autoRunPhase !== undefined || source.phase !== undefined : (source.autoRunPhase !== undefined || source.phase !== undefined
? ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying'].includes(phase) ? ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase)
: currentAutoRun.autoRunning); : currentAutoRun.autoRunning);
currentAutoRun = { currentAutoRun = {
autoRunning, autoRunning,
phase, phase,
currentRun: source.autoRunCurrentRun ?? source.currentRun ?? currentAutoRun.currentRun, currentRun: readAutoRunStateValue(source, ['autoRunCurrentRun', 'currentRun'], currentAutoRun.currentRun),
totalRuns: source.autoRunTotalRuns ?? source.totalRuns ?? currentAutoRun.totalRuns, totalRuns: readAutoRunStateValue(source, ['autoRunTotalRuns', 'totalRuns'], currentAutoRun.totalRuns),
attemptRun: source.autoRunAttemptRun ?? source.attemptRun ?? currentAutoRun.attemptRun, attemptRun: readAutoRunStateValue(source, ['autoRunAttemptRun', 'attemptRun'], currentAutoRun.attemptRun),
scheduledAt: source.scheduledAutoRunAt ?? source.scheduledAt ?? currentAutoRun.scheduledAt, scheduledAt: readAutoRunStateValue(source, ['scheduledAutoRunAt', 'scheduledAt'], currentAutoRun.scheduledAt),
countdownAt: readAutoRunStateValue(source, ['autoRunCountdownAt', 'countdownAt'], currentAutoRun.countdownAt),
countdownTitle: readAutoRunStateValue(source, ['autoRunCountdownTitle', 'countdownTitle'], currentAutoRun.countdownTitle),
countdownNote: readAutoRunStateValue(source, ['autoRunCountdownNote', 'countdownNote'], currentAutoRun.countdownNote),
}; };
} }
function isAutoRunLockedPhase() { function isAutoRunLockedPhase() {
return currentAutoRun.phase === 'running' || currentAutoRun.phase === 'waiting_step' || currentAutoRun.phase === 'retrying'; return currentAutoRun.phase === 'running'
|| currentAutoRun.phase === 'waiting_step'
|| currentAutoRun.phase === 'retrying'
|| currentAutoRun.phase === 'waiting_interval';
} }
function isAutoRunPausedPhase() { function isAutoRunPausedPhase() {
@@ -705,18 +727,46 @@ function stopScheduledCountdownTicker() {
scheduledCountdownTimer = null; scheduledCountdownTimer = null;
} }
function getActiveAutoRunCountdown() {
if (isAutoRunScheduledPhase() && Number.isFinite(currentAutoRun.scheduledAt)) {
return {
at: currentAutoRun.scheduledAt,
title: '已计划自动运行',
note: `计划于 ${formatScheduleTime(currentAutoRun.scheduledAt)} 开始`,
tone: 'scheduled',
};
}
if (!Number.isFinite(currentAutoRun.countdownAt)) {
return null;
}
return {
at: currentAutoRun.countdownAt,
title: currentAutoRun.countdownTitle || '等待中',
note: currentAutoRun.countdownNote || '',
tone: 'running',
};
}
function renderScheduledAutoRunInfo() { function renderScheduledAutoRunInfo() {
if (!autoScheduleBar) { if (!autoScheduleBar) {
return; return;
} }
if (!isAutoRunScheduledPhase() || !Number.isFinite(currentAutoRun.scheduledAt)) { const countdown = getActiveAutoRunCountdown();
if (!countdown) {
autoScheduleBar.style.display = 'none'; autoScheduleBar.style.display = 'none';
return; return;
} }
const remainingMs = currentAutoRun.scheduledAt - Date.now(); const remainingMs = countdown.at - Date.now();
autoScheduleBar.style.display = 'flex'; autoScheduleBar.style.display = 'flex';
autoScheduleTitle.textContent = countdown.title;
autoScheduleMeta.textContent = remainingMs > 0
? `${countdown.note ? `${countdown.note}` : ''}剩余 ${formatCountdown(remainingMs)}`
: '倒计时即将结束,正在准备继续...';
return;
autoScheduleTitle.textContent = '已计划自动运行'; autoScheduleTitle.textContent = '已计划自动运行';
autoScheduleMeta.textContent = remainingMs > 0 autoScheduleMeta.textContent = remainingMs > 0
? `计划于 ${formatScheduleTime(currentAutoRun.scheduledAt)} 开始,剩余 ${formatCountdown(remainingMs)}` ? `计划于 ${formatScheduleTime(currentAutoRun.scheduledAt)} 开始,剩余 ${formatCountdown(remainingMs)}`
@@ -725,11 +775,7 @@ function renderScheduledAutoRunInfo() {
function syncScheduledCountdownTicker() { function syncScheduledCountdownTicker() {
renderScheduledAutoRunInfo(); renderScheduledAutoRunInfo();
if (!isAutoRunScheduledPhase() || !Number.isFinite(currentAutoRun.scheduledAt)) { if (getActiveAutoRunCountdown()) {
stopScheduledCountdownTicker();
return;
}
if (scheduledCountdownTimer) { if (scheduledCountdownTimer) {
return; return;
} }
@@ -738,6 +784,11 @@ function syncScheduledCountdownTicker() {
renderScheduledAutoRunInfo(); renderScheduledAutoRunInfo();
updateStatusDisplay(latestState); updateStatusDisplay(latestState);
}, 1000); }, 1000);
return;
}
stopScheduledCountdownTicker();
return;
} }
function setDefaultAutoRunButton() { function setDefaultAutoRunButton() {
@@ -1045,6 +1096,10 @@ function applyAutoRunStatus(payload = currentAutoRun) {
autoContinueBar.style.display = 'none'; autoContinueBar.style.display = 'none';
btnAutoRun.innerHTML = `重试中${runLabel}`; btnAutoRun.innerHTML = `重试中${runLabel}`;
break; break;
case 'waiting_interval':
autoContinueBar.style.display = 'none';
btnAutoRun.innerHTML = `等待中${runLabel}`;
break;
default: default:
autoContinueBar.style.display = 'none'; autoContinueBar.style.display = 'none';
setDefaultAutoRunButton(); setDefaultAutoRunButton();
@@ -1881,6 +1936,16 @@ function updateStatusDisplay(state) {
statusBar.className = 'status-bar'; statusBar.className = 'status-bar';
const countdown = getActiveAutoRunCountdown();
if (countdown) {
const remainingMs = countdown.at - Date.now();
displayStatus.textContent = remainingMs > 0
? `${countdown.title},剩余 ${formatCountdown(remainingMs)}`
: `${countdown.title},即将结束...`;
statusBar.classList.add(countdown.tone === 'scheduled' ? 'scheduled' : 'running');
return;
}
if (isAutoRunScheduledPhase()) { if (isAutoRunScheduledPhase()) {
const remainingMs = Number.isFinite(currentAutoRun.scheduledAt) const remainingMs = Number.isFinite(currentAutoRun.scheduledAt)
? currentAutoRun.scheduledAt - Date.now() ? currentAutoRun.scheduledAt - Date.now()
@@ -2775,6 +2840,9 @@ btnReset.addEventListener('click', async () => {
autoRunTotalRuns: 1, autoRunTotalRuns: 1,
autoRunAttemptRun: 0, autoRunAttemptRun: 0,
scheduledAutoRunAt: null, scheduledAutoRunAt: null,
autoRunCountdownAt: null,
autoRunCountdownTitle: '',
autoRunCountdownNote: '',
}); });
displayOauthUrl.textContent = '等待中...'; displayOauthUrl.textContent = '等待中...';
displayOauthUrl.classList.remove('has-value'); displayOauthUrl.classList.remove('has-value');
@@ -3089,6 +3157,9 @@ chrome.runtime.onMessage.addListener((message) => {
stepStatuses: STEP_DEFAULT_STATUSES, stepStatuses: STEP_DEFAULT_STATUSES,
logs: [], logs: [],
scheduledAutoRunAt: null, scheduledAutoRunAt: null,
autoRunCountdownAt: null,
autoRunCountdownTitle: '',
autoRunCountdownNote: '',
}); });
displayOauthUrl.textContent = '等待中...'; displayOauthUrl.textContent = '等待中...';
displayOauthUrl.classList.remove('has-value'); displayOauthUrl.classList.remove('has-value');
@@ -3100,6 +3171,17 @@ chrome.runtime.onMessage.addListener((message) => {
logArea.innerHTML = ''; logArea.innerHTML = '';
document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row'); document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row');
document.querySelectorAll('.step-status').forEach(el => el.textContent = ''); document.querySelectorAll('.step-status').forEach(el => el.textContent = '');
syncAutoRunState({
autoRunning: false,
autoRunPhase: 'idle',
autoRunCurrentRun: 0,
autoRunTotalRuns: 1,
autoRunAttemptRun: 0,
scheduledAutoRunAt: null,
autoRunCountdownAt: null,
autoRunCountdownTitle: '',
autoRunCountdownNote: '',
});
applyAutoRunStatus(currentAutoRun); applyAutoRunStatus(currentAutoRun);
updateProgressCounter(); updateProgressCounter();
updateButtonStates(); updateButtonStates();
@@ -3157,12 +3239,15 @@ chrome.runtime.onMessage.addListener((message) => {
case 'AUTO_RUN_STATUS': { case 'AUTO_RUN_STATUS': {
syncLatestState({ syncLatestState({
autoRunning: ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying'].includes(message.payload.phase), autoRunning: ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(message.payload.phase),
autoRunPhase: message.payload.phase, autoRunPhase: message.payload.phase,
autoRunCurrentRun: message.payload.currentRun, autoRunCurrentRun: message.payload.currentRun,
autoRunTotalRuns: message.payload.totalRuns, autoRunTotalRuns: message.payload.totalRuns,
autoRunAttemptRun: message.payload.attemptRun, autoRunAttemptRun: message.payload.attemptRun,
scheduledAutoRunAt: message.payload.scheduledAt ?? null, scheduledAutoRunAt: message.payload.scheduledAt ?? null,
autoRunCountdownAt: message.payload.countdownAt ?? null,
autoRunCountdownTitle: message.payload.countdownTitle ?? '',
autoRunCountdownNote: message.payload.countdownNote ?? '',
}); });
applyAutoRunStatus(message.payload); applyAutoRunStatus(message.payload);
updateStatusDisplay(latestState); updateStatusDisplay(latestState);