Настройка грузится быстро: список пользователей ЛК из кэша; морфинг — якорная привязка прокрутки
1) Страница «Настройка» висела до 10 с, когда ЛК ЕСИА Финанс медленный/недоступен: setupData синхронно тянул список пользователей из ЛК (lkUsersForSetup, таймаут 10 с). Теперь список берётся из кэша мгновенно, обновляется в фоне с коротким таймаутом (3 с); при недоступном ЛК показывается сохранённый список с пометкой. Страница больше не ждёт ЛК. 2) Морфинг карточки: добавлена якорная привязка — запоминаем верхний видимый блок и после обновления корректируем прокрутку, чтобы при изменении высоты карточек выше обзора вид не подпрыгивал (устраняет «прыгнула чуть выше»). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -174,12 +175,52 @@ func (h *setupHandlers) lkUsersSave(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lkUsersForSetup — список для страницы настроек; ошибка базы — строкой, чтобы страница открылась.
|
// lkUsersForSetup — список для страницы настроек; ошибка базы — строкой, чтобы страница открылась.
|
||||||
func lkUsersForSetup(rc *RuntimeConfig) ([]LKUser, string) {
|
// lkUsersCache — список пользователей ЛК для страницы «Настройка». Берётся из кэша (мгновенно),
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
// обновляется в фоне с коротким таймаутом. Иначе медленный/недоступный ЛК вешал страницу на 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()
|
defer cancel()
|
||||||
list, err := lkListUsers(ctx, rc)
|
list, err := lkListUsers(ctx, rc)
|
||||||
|
lkUsersCache.Lock()
|
||||||
|
defer lkUsersCache.Unlock()
|
||||||
|
lkUsersCache.refreshing = false
|
||||||
|
lkUsersCache.at = time.Now()
|
||||||
if err != nil {
|
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(r){ return r.ok ? r.text() : Promise.reject(); })
|
||||||
.then(function(html){
|
.then(function(html){
|
||||||
if (dirty || (window.bjSelecting && window.bjSelecting())) return; // правит форму или выделяет — не трогаем
|
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');
|
var doc = new DOMParser().parseFromString(html, 'text/html');
|
||||||
bjMorphKids(document.querySelector('main'), doc.querySelector('main'));
|
bjMorphKids(document.querySelector('main'), doc.querySelector('main'));
|
||||||
if (window.bjInitCopyBars) window.bjInitCopyBars();
|
if (window.bjInitCopyBars) window.bjInitCopyBars();
|
||||||
|
if (anchor && anchor.isConnected) { var d = anchor.getBoundingClientRect().top - aTop; if (d) window.scrollBy(0, d); }
|
||||||
})
|
})
|
||||||
.catch(function(){})
|
.catch(function(){})
|
||||||
.then(function(){ bjRefreshing = false; });
|
.then(function(){ bjRefreshing = false; });
|
||||||
|
|||||||
Reference in New Issue
Block a user