From 4847003a0536c6a2fa9746d110ed57112e38d1f5 Mon Sep 17 00:00:00 2001 From: hub-gif <2487812171@qq.com> Date: Tue, 14 Apr 2026 14:20:17 +0800 Subject: [PATCH] fix(frontend): keep regen in-flight lock on client disconnect (tab switch) Made-with: Cursor --- .../src/composables/useGenerationInFlight.js | 24 +++++++++++++++++-- frontend/src/views/jd/JdAnalysisBuildView.vue | 12 +++++----- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/frontend/src/composables/useGenerationInFlight.js b/frontend/src/composables/useGenerationInFlight.js index 5243fd9..29d0be2 100644 --- a/frontend/src/composables/useGenerationInFlight.js +++ b/frontend/src/composables/useGenerationInFlight.js @@ -58,6 +58,20 @@ export function generationInFlightKey() { return inFlightKey } +/** + * 切换页签/隐藏标签时浏览器可能中止 fetch,表现为 TypeError 等;此时服务端可能仍在执行, + * 不应清除「进行中」标记(否则按钮误恢复可点)。HTTP 4xx/5xx 仍由业务层 return,走正常清除。 + */ +function isAmbiguousClientFailure(err) { + if (err == null) return false + const name = err.name || '' + if (name === 'AbortError') return true + const msg = String(err.message || err) + return /Failed to fetch|NetworkError|Load failed|ERR_NETWORK|INTERNET_DISCONNECTED|aborted|cancel/i.test( + msg, + ) +} + /** * @param {string} key * @param {() => Promise} fn @@ -67,11 +81,17 @@ export async function withGenerationInFlight(key, fn) { inFlightKey.value = key writePersisted(key) try { - return await fn() - } finally { + const out = await fn() if (inFlightKey.value === key) { inFlightKey.value = null writePersisted(null) } + return out + } catch (e) { + if (inFlightKey.value === key && !isAmbiguousClientFailure(e)) { + inFlightKey.value = null + writePersisted(null) + } + throw e } } diff --git a/frontend/src/views/jd/JdAnalysisBuildView.vue b/frontend/src/views/jd/JdAnalysisBuildView.vue index f923dc3..b1df5dd 100644 --- a/frontend/src/views/jd/JdAnalysisBuildView.vue +++ b/frontend/src/views/jd/JdAnalysisBuildView.vue @@ -156,8 +156,8 @@ async function regenerateReport() { if (!id) return regenErr.value = '' const key = `${REGEN_PREFIX}${id}` - await withGenerationInFlight(key, async () => { - try { + try { + await withGenerationInFlight(key, async () => { const r = await api(`/api/jobs/${id}/regenerate-report/`, { method: 'POST', body: JSON.stringify({ @@ -177,10 +177,10 @@ async function regenerateReport() { const updated = JSON.parse(text) const idx = jobs.value.findIndex((x) => x.id === updated.id) if (idx >= 0) jobs.value[idx] = updated - } catch (e) { - regenErr.value = String(e) - } - }) + }) + } catch (e) { + regenErr.value = String(e) + } } onMounted(loadList)