Настройка грузится быстро: список пользователей ЛК из кэша; морфинг — якорная привязка прокрутки

1) Страница «Настройка» висела до 10 с, когда ЛК ЕСИА Финанс медленный/недоступен: setupData
   синхронно тянул список пользователей из ЛК (lkUsersForSetup, таймаут 10 с). Теперь список
   берётся из кэша мгновенно, обновляется в фоне с коротким таймаутом (3 с); при недоступном ЛК
   показывается сохранённый список с пометкой. Страница больше не ждёт ЛК.
2) Морфинг карточки: добавлена якорная привязка — запоминаем верхний видимый блок и после
   обновления корректируем прокрутку, чтобы при изменении высоты карточек выше обзора вид не
   подпрыгивал (устраняет «прыгнула чуть выше»).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
fontvielle
2026-10-13 18:16:42 +03:00
parent a6c925572b
commit d154506464
2 changed files with 50 additions and 4 deletions
+45 -4
View File
@@ -16,6 +16,7 @@ import (
"net/http"
"strconv"
"strings"
"sync"
"time"
)
@@ -174,12 +175,52 @@ func (h *setupHandlers) lkUsersSave(w http.ResponseWriter, r *http.Request) {
}
// lkUsersForSetup — список для страницы настроек; ошибка базы — строкой, чтобы страница открылась.
func lkUsersForSetup(rc *RuntimeConfig) ([]LKUser, string) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
// lkUsersCache — список пользователей ЛК для страницы «Настройка». Берётся из кэша (мгновенно),
// обновляется в фоне с коротким таймаутом. Иначе медленный/недоступный ЛК вешал страницу на 10 с.
var lkUsersCache struct {
sync.Mutex
list []LKUser
errMsg string
at time.Time
refreshing bool
}
func lkUsersRefresh(rc *RuntimeConfig) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
list, err := lkListUsers(ctx, rc)
lkUsersCache.Lock()
defer lkUsersCache.Unlock()
lkUsersCache.refreshing = false
lkUsersCache.at = time.Now()
if err != nil {
return nil, err.Error()
if lkUsersCache.list != nil { // ЛК не ответил — оставляем прошлый список с пометкой
lkUsersCache.errMsg = "ЛК сейчас недоступен — показан сохранённый список (" + err.Error() + ")"
return
}
lkUsersCache.errMsg = err.Error()
return
}
return list, ""
lkUsersCache.list, lkUsersCache.errMsg = list, ""
}
// lkUsersForSetup — список пользователей ЛК для страницы «Настройка»: отдаёт кэш сразу, при
// устаревании обновляет в фоне (страница не ждёт ЛК). Первый раз (холодный кэш) — одно короткое ожидание.
func lkUsersForSetup(rc *RuntimeConfig) ([]LKUser, string) {
lkUsersCache.Lock()
cold := lkUsersCache.at.IsZero()
stale := time.Since(lkUsersCache.at) > 20*time.Second
if !cold && stale && !lkUsersCache.refreshing {
lkUsersCache.refreshing = true
go lkUsersRefresh(rc)
}
list, msg := lkUsersCache.list, lkUsersCache.errMsg
lkUsersCache.Unlock()
if cold {
lkUsersRefresh(rc) // первый заход — подождём коротко (≤3 с), дальше всегда из кэша
lkUsersCache.Lock()
list, msg = lkUsersCache.list, lkUsersCache.errMsg
lkUsersCache.Unlock()
}
return list, msg
}
@@ -893,9 +893,14 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
.then(function(r){ return r.ok ? r.text() : Promise.reject(); })
.then(function(html){
if (dirty || (window.bjSelecting && window.bjSelecting())) return; // правит форму или выделяет — не трогаем
// Якорь: верхний видимый блок — чтобы при изменении высоты карточек выше обзора
// вид не «подпрыгивал». После морфинга вернём этот блок на то же место по вертикали.
var anchor = null, aTop = 0, cards = document.querySelectorAll('main .card, main aside');
for (var ci = 0; ci < cards.length; ci++) { var r = cards[ci].getBoundingClientRect(); if (r.bottom > 0) { anchor = cards[ci]; aTop = r.top; break; } }
var doc = new DOMParser().parseFromString(html, 'text/html');
bjMorphKids(document.querySelector('main'), doc.querySelector('main'));
if (window.bjInitCopyBars) window.bjInitCopyBars();
if (anchor && anchor.isConnected) { var d = anchor.getBoundingClientRect().top - aTop; if (d) window.scrollBy(0, d); }
})
.catch(function(){})
.then(function(){ bjRefreshing = false; });