From 0fb0597b8d22810ab283336f37efeb46de8b923d Mon Sep 17 00:00:00 2001 From: wasrusgen Date: Mon, 18 May 2026 09:27:53 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=2015-=D1=81=D0=B5=D0=BA=D1=83=D0=BD=D0=B4=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B0=D1=83=D1=82=20fet?= =?UTF-8?q?ch=20=D0=B2=D0=BE=20=D0=B2=D1=81=D0=B5=20=D0=BC=D0=BE=D0=B4?= =?UTF-8?q?=D1=83=D0=BB=D0=B8=20(measurements,=20assembly,=20proposals,=20?= =?UTF-8?q?request)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Кнопки больше не зависают бесконечно при медленном или недоступном бэкенде. AbortController + дружелюбное сообщение «Сервер не отвечает — попробуйте ещё раз». Co-Authored-By: Claude Sonnet 4.6 --- miniapp/assets/assembly.js | 36 +++++++++++++------------- miniapp/assets/measurements.js | 46 ++++++++++++++++------------------ miniapp/assets/proposals.js | 24 ++++++++++++------ miniapp/assets/request.js | 31 ++++++++++++++--------- miniapp/index.html | 8 +++--- 5 files changed, 81 insertions(+), 64 deletions(-) diff --git a/miniapp/assets/assembly.js b/miniapp/assets/assembly.js index 90ad221..94c2e00 100644 --- a/miniapp/assets/assembly.js +++ b/miniapp/assets/assembly.js @@ -17,6 +17,20 @@ const Assembly = (function () { manager_note: "", }; + async function _fetchWithTimeout(url, body, timeoutMs = 15000) { + const ctrl = new AbortController(); + const timer = setTimeout(() => ctrl.abort(), timeoutMs); + try { + const res = await fetch(url, { method: "POST", signal: ctrl.signal, body: JSON.stringify(body) }); + return await res.json(); + } catch (e) { + if (e.name === "AbortError") throw new Error("Сервер не отвечает — попробуйте ещё раз"); + throw e; + } finally { + clearTimeout(timer); + } + } + function mount(container) { root = container; document.body.classList.remove("has-bottom-nav"); @@ -198,11 +212,7 @@ const Assembly = (function () { }; try { - const res = await fetch(`${BACKEND_URL}/api/assembly_create`, { - method: "POST", - body: JSON.stringify(body), - }); - const data = await res.json(); + const data = await _fetchWithTimeout(`${BACKEND_URL}/api/assembly_create`, body); if (data.error) { result.innerHTML = `
Ошибка: ${escHtml(data.error)}
`; btn.disabled = false; @@ -247,14 +257,10 @@ const Assembly = (function () { const loading = el(`
`); root.appendChild(loading); try { - const res = await fetch(`${BACKEND_URL}/api/assembly_list`, { - method: "POST", - body: JSON.stringify({ + const data = await _fetchWithTimeout(`${BACKEND_URL}/api/assembly_list`, { initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, - }), - }); - const data = await res.json(); + }); loading.remove(); if (data.error) { root.appendChild(el(`
${escHtml(data.error)}
`)); @@ -309,15 +315,11 @@ const Assembly = (function () { root.appendChild(loading); let a; try { - const res = await fetch(`${BACKEND_URL}/api/assembly_detail`, { - method: "POST", - body: JSON.stringify({ + a = await _fetchWithTimeout(`${BACKEND_URL}/api/assembly_detail`, { initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, assembly_id: id, - }), - }); - a = await res.json(); + }); } catch (e) { loading.remove(); root.appendChild(el(`
Сеть: ${escHtml(e.message)}
`)); diff --git a/miniapp/assets/measurements.js b/miniapp/assets/measurements.js index 7b05563..2f99d70 100644 --- a/miniapp/assets/measurements.js +++ b/miniapp/assets/measurements.js @@ -94,20 +94,30 @@ const Measurements = (function () { render(); } + async function _fetchWithTimeout(url, body, timeoutMs = 15000) { + const ctrl = new AbortController(); + const timer = setTimeout(() => ctrl.abort(), timeoutMs); + try { + const res = await fetch(url, { method: "POST", signal: ctrl.signal, body: JSON.stringify(body) }); + return await res.json(); + } catch (e) { + if (e.name === "AbortError") throw new Error("Сервер не отвечает — попробуйте ещё раз"); + throw e; + } finally { + clearTimeout(timer); + } + } + async function loadRequestAndStart() { root.innerHTML = ""; root.appendChild(renderHeader("Закрыть заявку")); root.appendChild(el(`
`)); try { - const res = await fetch(`${BACKEND_URL}/api/measurement_detail`, { - method: "POST", - body: JSON.stringify({ + const data = await _fetchWithTimeout(`${BACKEND_URL}/api/measurement_detail`, { initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, measurement_id: measurementId, - }), - }); - const data = await res.json(); + }); if (data.error) { root.innerHTML = ""; root.appendChild(renderHeader("Ошибка")); @@ -332,14 +342,10 @@ const Measurements = (function () { async function fetchNextZamerNo(node) { try { - const res = await fetch(`${BACKEND_URL}/api/measurement_next_no`, { - method: "POST", - body: JSON.stringify({ + const data = await _fetchWithTimeout(`${BACKEND_URL}/api/measurement_next_no`, { initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, - }), - }); - const data = await res.json(); + }); const hint = node.querySelector("#zamerNoHint"); const input = node.querySelector("#zamerNoInput"); if (data.ok && data.next_no && input && !state.zamer_no) { @@ -529,14 +535,10 @@ const Measurements = (function () { let clients = []; try { - const res = await fetch(`${BACKEND_URL}/api/clients`, { - method: "POST", - body: JSON.stringify({ + const data = await _fetchWithTimeout(`${BACKEND_URL}/api/clients`, { initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, - }), - }); - const data = await res.json(); + }); clients = (data.clients || []).sort((a, b) => (a.client_name || "").localeCompare(b.client_name || "", "ru") ); @@ -937,15 +939,11 @@ const Measurements = (function () { }; try { - const res = await fetch(`${BACKEND_URL}/api/measurement`, { - method: "POST", - body: JSON.stringify({ + const data = await _fetchWithTimeout(`${BACKEND_URL}/api/measurement`, { initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, measurement, - }), - }); - const data = await res.json(); + }); if (data.error) { result.innerHTML = `
Ошибка: ${data.error}
`; btn.disabled = false; btn.textContent = isUpdate ? "Закрыть заявку" : "Сохранить замер"; diff --git a/miniapp/assets/proposals.js b/miniapp/assets/proposals.js index f2b3709..b9efa33 100644 --- a/miniapp/assets/proposals.js +++ b/miniapp/assets/proposals.js @@ -19,13 +19,23 @@ const Proposals = (function () { return { initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null }; } - async function apiFetch(path, extra = {}) { - const res = await fetch(`${BACKEND_URL}/api/${path}`, { - method: "POST", - body: JSON.stringify({ ...authBody(), ...extra }), - }); - if (!res.ok) throw new Error("HTTP " + res.status); - return res.json(); + async function apiFetch(path, extra = {}, timeoutMs = 15000) { + const ctrl = new AbortController(); + const timer = setTimeout(() => ctrl.abort(), timeoutMs); + try { + const res = await fetch(`${BACKEND_URL}/api/${path}`, { + method: "POST", + signal: ctrl.signal, + body: JSON.stringify({ ...authBody(), ...extra }), + }); + if (!res.ok) throw new Error("HTTP " + res.status); + return res.json(); + } catch (e) { + if (e.name === "AbortError") throw new Error("Сервер не отвечает — попробуйте ещё раз"); + throw e; + } finally { + clearTimeout(timer); + } } // ── Constants ───────────────────────────────────────────── diff --git a/miniapp/assets/request.js b/miniapp/assets/request.js index a24882e..046d76c 100644 --- a/miniapp/assets/request.js +++ b/miniapp/assets/request.js @@ -15,6 +15,20 @@ const MeasurementRequest = (function () { }; let measurers = []; + async function _fetchWithTimeout(url, body, timeoutMs = 15000) { + const ctrl = new AbortController(); + const timer = setTimeout(() => ctrl.abort(), timeoutMs); + try { + const res = await fetch(url, { method: "POST", signal: ctrl.signal, body: JSON.stringify(body) }); + return await res.json(); + } catch (e) { + if (e.name === "AbortError") throw new Error("Сервер не отвечает — попробуйте ещё раз"); + throw e; + } finally { + clearTimeout(timer); + } + } + function mount(container) { root = container; document.body.classList.remove("has-bottom-nav"); @@ -116,11 +130,9 @@ const MeasurementRequest = (function () { async function loadMeasurers() { try { - const res = await fetch(`${BACKEND_URL}/api/staff_list`, { - method: "POST", - body: JSON.stringify({ initData: tg?.initData || "", role: "measurer" }), - }); - const data = await res.json(); + const data = await _fetchWithTimeout(`${BACKEND_URL}/api/staff_list`, { + initData: tg?.initData || "", role: "measurer", + }); measurers = data.staff || []; const sel = document.getElementById("measurerSelect"); const hint = document.getElementById("measurerHint"); @@ -163,21 +175,16 @@ const MeasurementRequest = (function () { result.innerHTML = ""; try { - const res = await fetch(`${BACKEND_URL}/api/measurement_request`, { - method: "POST", - body: JSON.stringify({ + const data = await _fetchWithTimeout(`${BACKEND_URL}/api/measurement_request`, { initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, client_name: name, client_phone: phone, address: state.address || "", assigned_to_tg_id: state.assigned_to_tg_id || "", - // Примечание (рекомендации по дате + особенности) — единое поле preferred_note: state.preferred_note || "", preferred_type: "tbd", - }), - }); - const data = await res.json(); + }); if (data.error) { result.innerHTML = `
Ошибка: ${data.error}
`; btn.disabled = false; diff --git a/miniapp/index.html b/miniapp/index.html index 8bfbc73..4573615 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -41,10 +41,10 @@ - - - - + + + +