fix: 1C response json parsing in bridge, checkup page cutouts and doctor assets

This commit is contained in:
Dana Medical Developer 2026-10-14 14:44:59 +00:00
parent 3bb44c4e80
commit e06d8a0de5
53 changed files with 81 additions and 47 deletions

View File

@ -140,7 +140,15 @@ class BridgeHandler(BaseHTTPRequestHandler):
if err:
self.send_json(502, {"code": "1c_error", "message": err})
else:
self.send_json(200, data or [])
slots = data or []
while isinstance(slots, str):
try:
slots = json.loads(slots)
except Exception:
break
if not isinstance(slots, list):
slots = []
self.send_json(200, slots)
return
if path == "/doctors":
@ -154,7 +162,15 @@ class BridgeHandler(BaseHTTPRequestHandler):
self.send_json(502, {"code": "1c_error", "message": err})
else:
doctors = data or []
while isinstance(doctors, str):
try:
doctors = json.loads(doctors)
except Exception:
break
if not isinstance(doctors, list):
doctors = []
for d in doctors:
if isinstance(d, dict):
try:
d["kids_from"] = int(d.get("kids_from", 0))
except (ValueError, TypeError):
@ -211,8 +227,15 @@ class BridgeHandler(BaseHTTPRequestHandler):
self.send_json(502, {"code": "1c_error", "message": err})
else:
services = data or []
# Guarantee types for Go unmarshaling
while isinstance(services, str):
try:
services = json.loads(services)
except Exception:
break
if not isinstance(services, list):
services = []
for s in services:
if isinstance(s, dict):
try:
s["duration_min"] = int(s.get("duration_min", 30))
except (ValueError, TypeError):

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -99,12 +99,12 @@ export function directionImage(service) {
const CHECKUP_MEDIA = {
'zdorovaya-devoch-a': { photo: ckGirl, icon: icoFemale, hero: checkupHeroGirl },
'proverit-svoe-zhenskoe-zdorove-': { photo: ckWoman, icon: icoCheckup, cutout: cutWomanHealth },
'planirovannie-beremennosti': { photo: ckPregnancy, icon: icoPregnant, cutout: cutPlanning },
'profilaktika-raka-sheyki-matki': { photo: ckCervix, icon: icoRibbon, cutout: cutCervix, cutoutCenter: true },
'proverit-svoe-zhenskoe-zdorove-': { photo: ckWoman, icon: icoCheckup, cutout: cutWomanHealth, cutoutRatio: 0.943 },
'planirovannie-beremennosti': { photo: ckPregnancy, icon: icoPregnant, cutout: cutPlanning, cutoutRatio: 1.056 },
'profilaktika-raka-sheyki-matki': { photo: ckCervix, icon: icoRibbon, cutout: cutCervix, cutoutRatio: 1.452 },
// серо-синий фон шапки (#7886a9) — давление и родители
'davlenie-v-poryadke': { photo: ckPressure, icon: icoTonometer, cutout: cutPressure, heroBg: '#7886a9' },
'zdorovye-roditeli': { photo: ckParents, icon: icoFamily, cutout: cutParents, heroBg: '#7886a9' },
'davlenie-v-poryadke': { photo: ckPressure, icon: icoTonometer, cutout: cutPressure, cutoutRatio: 1.374, heroBg: '#7886a9' },
'zdorovye-roditeli': { photo: ckParents, icon: icoFamily, cutout: cutParents, cutoutRatio: 1.202, heroBg: '#7886a9' },
'mne-god': { photo: ckYear, icon: icoBaby },
'chek-ap-ya-v-sad': { photo: ckKindergarten, icon: icoPlayground },
pervoklashka: { photo: ckSchool, icon: icoSchool },
@ -112,11 +112,11 @@ const CHECKUP_MEDIA = {
detskiy: { photo: ckMini, icon: icoStethoscope },
'zdorovyy-zhkt': { photo: ckGut, icon: icoStomach },
// программ ниже в макете нет — берём близкие по смыслу фото макета
'vedenie-beremennosti-1-trimestr': { photo: ckPregnancy, icon: icoPregnant, cutout: cutTrimester1 },
'vedenie-beremennosti-2-trim': { photo: ckPregnancy, icon: icoPregnant, cutout: cutTrimester2 },
'vedenie-beremennosti-3-trim': { photo: ckPregnancy, icon: icoPregnant, cutout: cutTrimester3 },
'poslerodovoy-chek-ap-cherez-mesyats': { photo: ckWoman, icon: icoFemale, cutout: cutPostpartum },
'zdorovaya-zhenshchina-40': { photo: ckWoman, icon: icoFemale, cutout: cutWoman40 },
'vedenie-beremennosti-1-trimestr': { photo: ckPregnancy, icon: icoPregnant, cutout: cutTrimester1, cutoutRatio: 1.048 },
'vedenie-beremennosti-2-trim': { photo: ckPregnancy, icon: icoPregnant, cutout: cutTrimester2, cutoutRatio: 1.215 },
'vedenie-beremennosti-3-trim': { photo: ckPregnancy, icon: icoPregnant, cutout: cutTrimester3, cutoutRatio: 1.191 },
'poslerodovoy-chek-ap-cherez-mesyats': { photo: ckWoman, icon: icoFemale, cutout: cutPostpartum, cutoutRatio: 1.007 },
'zdorovaya-zhenshchina-40': { photo: ckWoman, icon: icoFemale, cutout: cutWoman40, cutoutRatio: 1.054 },
'chek-ap-ya-rodilsya': { photo: ckYear, icon: icoBaby },
};
@ -133,8 +133,8 @@ export function checkupMedia(checkup) {
cutout: m.cutout || '',
// свой цвет фона шапки, если в макете он не коралловый
heroBg: m.heroBg || '',
// главное в кадре по центру — при нехватке места фигура обрезается с двух сторон, а не только слева
cutoutCenter: Boolean(m.cutoutCenter),
// ширина/высота файла фигуры: по ней шапка понимает, поместится ли фигура справа от текста
cutoutRatio: m.cutoutRatio || 0,
};
}

View File

@ -443,53 +443,55 @@
}
/* ---------- вырезанная фигура в шапке ----------
Фигура срезана сверху и снизу, поэтому всегда на всю высоту шапки (срез совпадает с краями)
и не выходит за правую линию сетки. Если снимок шире места справа от текста, лишнее уходит
влево под плавное растворение — без прямого среза. */
Фигура срезана сверху и снизу, поэтому всегда на всю высоту шапки и целиком — без обрезки
и растворения. Справа может выходить за сетку к краю экрана, как в макете. Если справа от текста
места не хватает, фигура встаёт сразу за текстом и уходит правым краем за экран, но на текст не
налезает. Ширину фигуры при текущей высоте даёт --cutout-ratio (ширина/высота файла). */
.checkup-hero__row--cutout .checkup-hero__copy {
max-width: 520px;
max-width: 480px;
}
.checkup-hero__image--cutout {
position: absolute;
top: 0;
bottom: 0;
left: 548px;
right: 0;
left: 504px;
/* правый край — край экрана, на широких экранах не дальше 220px за сеткой */
right: max(calc((100% - 100vw) / 2), -220px);
width: auto;
height: auto;
overflow: hidden;
overflow: visible;
pointer-events: none;
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 72px);
mask-image: linear-gradient(to right, transparent 0, #000 72px);
container-type: size;
}
.checkup-hero__image--cutout img {
position: absolute;
right: 0;
bottom: 0;
left: max(0px, calc(100% - var(--cutout-ratio, 1.2) * 100cqh));
width: auto;
height: 100%;
max-width: none;
}
/* главное в кадре по центру (лента у «Профилактики рака шейки матки») — центрируем, а не к линии */
.checkup-hero__image--center img {
right: auto;
left: 50%;
transform: translateX(-50%);
}
/* планшет: места справа от текста мало — фигура целиком у правого края экрана, а часть под текстом
бледнеет, чтобы текст читался */
@media (max-width: 1023px) {
.checkup-hero__row--cutout .checkup-hero__copy {
max-width: 55%;
}
.checkup-hero__image--cutout {
left: 56%;
right: 0;
left: calc(-1 * var(--gutter));
right: calc(-1 * var(--gutter));
width: auto;
height: auto;
}
.checkup-hero__image--cutout img {
left: auto;
right: 0;
-webkit-mask-image: linear-gradient(to right, rgba(0, 0, 0, 0.18) 35%, #000 70%);
mask-image: linear-gradient(to right, rgba(0, 0, 0, 0.18) 35%, #000 70%);
}
}
/* телефон: фигура уходит за текст и приглушена, чтобы текст читался */
@ -506,7 +508,13 @@
height: auto;
}
.checkup-hero__image--cutout img {
left: 0;
width: 100%;
object-fit: cover;
object-position: 70% bottom;
opacity: 0.5;
-webkit-mask-image: none;
mask-image: none;
}
}

View File

@ -27,7 +27,7 @@ export default function CheckupPage() {
if (!checkup.data || checkup.data.slug !== slug) return <p className="state checkup-loading">Загружаем…</p>;
const c = checkup.data;
const { hero, photo, cutout, heroBg, cutoutCenter } = checkupMedia(c);
const { hero, photo, cutout, heroBg, cutoutRatio } = checkupMedia(c);
const items = c.items?.length ? c.items.map(cleanText) : checklistItems(c.blocks);
const intro = introText(c.blocks);
const lead = cleanText(c.lead);
@ -64,7 +64,10 @@ export default function CheckupPage() {
</div>
</div>
{cutout ? (
<div className={`checkup-hero__image checkup-hero__image--cutout${cutoutCenter ? ' checkup-hero__image--center' : ''}`}>
<div
className="checkup-hero__image checkup-hero__image--cutout"
style={cutoutRatio ? { '--cutout-ratio': cutoutRatio } : undefined}
>
<img src={cutout} alt="" />
</div>
) : hero ? (