v2: визуальная SVG-схема маршрутизации из данных реестра

This commit is contained in:
S.Moldir 2026-09-25 10:24:34 +00:00
parent 475830f348
commit 7baa0c4f49
2 changed files with 132 additions and 2 deletions

View File

@ -43,6 +43,12 @@
.steps-list li { display: flex; gap: var(--kt-ai-space-4); }
.step-n { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%; background: var(--vibe-tint); color: var(--kt-ai-fg); display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; }
.steps-list p { margin: 0; }
.scheme-wrap { margin-top: var(--kt-ai-space-7); overflow-x: auto; }
#schemeSvg svg { display: block; width: 100%; min-width: 820px; height: auto; }
.scheme-legend { display: flex; gap: var(--kt-ai-space-6); flex-wrap: wrap; margin-top: var(--kt-ai-space-4); font-size: 13px; color: var(--kt-ai-fg-muted); }
.scheme-legend-item { display: inline-flex; align-items: center; gap: var(--kt-ai-space-2); }
.scheme-legend-line { display: inline-block; width: 28px; border-top: 2px solid var(--vibe-accent); }
</style>
</head>
<body>
@ -51,8 +57,9 @@
<div class="kt-ai-wrap kt-ai-pagehead-in">
<strong>Помощник: экстренные службы</strong>
<span class="kt-ai-spacer"></span>
<a class="kt-ai-btn" data-variant="ghost" href="#scheme">Схема</a>
<a class="kt-ai-btn" data-variant="ghost" href="#register">Реестр окон</a>
<a class="kt-ai-btn" data-variant="ghost" href="#routes">Схема</a>
<a class="kt-ai-btn" data-variant="ghost" href="#routes">Как это работает</a>
</div>
</header>
@ -122,6 +129,20 @@
</div>
</section>
<section id="scheme" class="kt-ai-section" data-divider="true">
<div class="kt-ai-wrap">
<div class="kt-ai-section-head">
<h2>Схема маршрутизации для 727103 (Алматы)</h2>
<p>Схема построена из данных реестра: владелец номера пересчёта 727103 — BRW SIP (7273130333), резерв — 7272222222 (SIP Trunk, принадлежит ГТС Алматы).</p>
</div>
<div class="scheme-wrap"><div id="schemeSvg"></div></div>
<div class="scheme-legend">
<span class="scheme-legend-item"><span class="scheme-legend-line" style="border-top:2px solid var(--vibe-accent)"></span>маршрут (мобильный и фиксированный)</span>
<span class="scheme-legend-item"><span class="scheme-legend-line" style="border-top:2px dashed var(--kt-ai-fg-muted)"></span>резерв при недоступности SIP</span>
</div>
</div>
</section>
<section id="routes" class="kt-ai-section" data-divider="true">
<div class="kt-ai-wrap" data-width="narrow">
<div class="kt-ai-section-head">
@ -140,7 +161,7 @@
<section class="kt-ai-cta">
<div class="kt-ai-wrap">
<h2>Формат оконечного номера — ABC21UV</h2>
<p>Для Алматы: 7271UV. Реестр пополняется, а схема пересчитывается автоматически — введите номер в поле поиска и сверьте маршрут.</p>
<p>Для Алматы: 7271UV. Введите номер в поле поиска и сверьте: его данные подставятся в цепочки и в схему маршрутизации.</p>
<a class="kt-ai-btn" data-variant="primary" data-size="lg" href="#search">Перейти к поиску</a>
</div>
</section>

109
script.js
View File

@ -171,6 +171,7 @@
document.getElementById("r2src").textContent = "Станция владельца номера → SSW/NGN рег. → " + (d.entry ? d.entry.num : "оконечный номер");
renderChain("chain1", d.mobile);
renderChain("chain2", d.fixed);
renderScheme(d.entry ? d.entry.num : num);
}
function renderRegister() {
@ -202,8 +203,116 @@
document.getElementById("regCounter").textContent = "Каждый номер: тип (ТДМ / GPON / SIP), станция владения, формат набора ABC21UV и резерв. В реестре: " + REG.length + ".";
}
function renderScheme(focusNum) {
const NS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(NS, "svg");
svg.setAttribute("viewBox", "0 0 1100 460");
svg.setAttribute("role", "img");
svg.setAttribute("aria-label", "Схема маршрутизации вызовов экстренных служб");
const entry = findEntry(focusNum) || REG[0] || null;
const endNum = entry ? entry.num : "—";
const hasBrw = entry ? normalize(entry.type).indexOf("BRW") === 0 : false;
function rect(x, y, w, h, rx, fill, stroke, sw) {
const r = document.createElementNS(NS, "rect");
r.setAttribute("x", x); r.setAttribute("y", y);
r.setAttribute("width", w); r.setAttribute("height", h);
r.setAttribute("rx", rx);
if (fill) r.setAttribute("fill", fill);
if (stroke) r.setAttribute("stroke", stroke);
r.setAttribute("stroke-width", sw || 1);
return r;
}
function txt(x, y, s, size, fill, weight) {
const t = document.createElementNS(NS, "text");
t.setAttribute("x", x); t.setAttribute("y", y);
t.setAttribute("text-anchor", "middle");
t.setAttribute("style", "font-size:" + size + "px; fill:" + fill + ";" + (weight ? " font-weight:" + weight + ";" : ""));
t.textContent = s;
return t;
}
function edge(x1, y1, x2, y2, dashed, label, ldy) {
const l = document.createElementNS(NS, "line");
l.setAttribute("x1", x1); l.setAttribute("y1", y1);
l.setAttribute("x2", x2); l.setAttribute("y2", y2);
l.setAttribute("stroke", dashed ? "var(--kt-ai-fg-muted)" : "var(--vibe-accent)");
l.setAttribute("stroke-width", "1.6");
if (dashed) l.setAttribute("stroke-dasharray", "5 4");
l.setAttribute("marker-end", "url(#arrow" + (dashed ? "M" : "A") + ")");
l.setAttribute("opacity", "0.85");
svg.appendChild(l);
if (label) {
const t = document.createElementNS(NS, "text");
t.setAttribute("x", (x1 + x2) / 2); t.setAttribute("y", ((y1 + y2) / 2) + (ldy || -7));
t.setAttribute("text-anchor", "middle");
t.setAttribute("style", "font-size:10px; fill:var(--kt-ai-fg-muted)");
t.textContent = label;
svg.appendChild(t);
}
}
const defs = document.createElementNS(NS, "defs");
defs.innerHTML = '<marker id="arrowA" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="var(--vibe-accent)"></path></marker><marker id="arrowM" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="var(--kt-ai-fg-muted)"></path></marker>';
svg.appendChild(defs);
edge(215, 60, 390, 170);
edge(385, 60, 530, 170);
edge(560, 204, 620, 266);
if (hasBrw) {
edge(695, 300, 656, 334);
}
edge(620, 300, 350, 335, true, "резерв при недоступности SIP", -7);
const boxFill = "var(--kt-ai-card-bg)";
const bStroke = "var(--kt-ai-border)";
// Мобильные операторы
svg.appendChild(rect(50, 32, 165, 56, 10, boxFill, bStroke));
svg.appendChild(txt(132, 56, "Мобильные", 14, "var(--kt-ai-fg)", 600));
svg.appendChild(txt(132, 73, "операторы", 14, "var(--kt-ai-fg)", 600));
// ГТС
svg.appendChild(rect(220, 32, 165, 56, 10, boxFill, bStroke));
svg.appendChild(txt(302, 63, "ГТС", 15, "var(--kt-ai-fg)", 600));
// SSW/NGN транзит
svg.appendChild(rect(390, 170, 170, 56, 10, boxFill, bStroke));
svg.appendChild(txt(475, 193, "SSW/NGN транзит", 14, "var(--kt-ai-fg)", 600));
svg.appendChild(txt(475, 210, "транзитная шлюзовая система", 11, "var(--kt-ai-fg-muted)"));
// SSW/NGN рег.
svg.appendChild(rect(550, 266, 170, 56, 10, boxFill, bStroke));
svg.appendChild(txt(635, 289, "SSW/NGN рег.", 14, "var(--kt-ai-fg)", 600));
svg.appendChild(txt(635, 306, "региональная маршрутизация", 11, "var(--kt-ai-fg-muted)"));
// Резерв
svg.appendChild(rect(200, 335, 150, 56, 10, "var(--kt-ai-bg)", bStroke));
svg.appendChild(txt(275, 358, "SIP Trunk (резерв)", 12, "var(--kt-ai-fg)", 600));
svg.appendChild(txt(275, 375, "7272222222 · ГТС Алматы", 11, "var(--kt-ai-fg-muted)"));
// Оконечный номер
svg.appendChild(rect(700, 335, 190, 56, 10, "var(--kt-ai-bg)", hasBrw ? "var(--vibe-accent)" : bStroke, hasBrw ? 2 : 1));
svg.appendChild(txt(795, 358, "Оконечный номер", 13, "var(--kt-ai-fg)", 600));
svg.appendChild(txt(795, 375, endNum, 12, "var(--vibe-accent)", 600));
// BRW SIP
if (hasBrw) {
svg.appendChild(rect(450, 335, 205, 56, 10, boxFill, bStroke));
svg.appendChild(txt(552, 358, "BRW SIP", 14, "var(--kt-ai-fg)", 600));
svg.appendChild(txt(552, 375, "7273130333", 12, "var(--kt-ai-fg-muted)", 600));
const l1 = document.createElementNS(NS, "line");
l1.setAttribute("x1", 655); l1.setAttribute("y1", 363);
l1.setAttribute("x2", 695); l1.setAttribute("y2", 363);
l1.setAttribute("stroke", "var(--vibe-accent)");
l1.setAttribute("stroke-width", "1.6");
l1.setAttribute("marker-end", "url(#arrowA)");
l1.setAttribute("opacity", "0.85");
svg.appendChild(l1);
}
const el = document.getElementById("schemeSvg");
el.innerHTML = "";
el.appendChild(svg);
}
document.addEventListener("DOMContentLoaded", function () {
renderRegister();
renderScheme("727103");
const input = document.getElementById("num");
const btn = document.getElementById("findBtn");
btn.addEventListener("click", function () { show(input.value); });