sozday-sayt-pomoschnik-dlya/script.js

352 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function () {
"use strict";
const REG = [
{
num: "7271UV",
type: "BRW SIP",
station: "BRW SIP платформа (Алматы)",
city: "Алматы",
desc: "Основной оконечный номер экстренных служб",
reserve: "Есть: SIP Trunk 7272222222 (принадлежит ОПТС-6, Алматы)",
},
{
num: "727103",
type: "BRW SIP",
station: "BRW SIP платформа (7273130333)",
city: "Алматы",
desc: "Номер пересчёта принадлежит BRW SIP 7273130333",
reserve: "Есть: SIP Trunk 7272222222 (принадлежит ОПТС-6, Алматы)",
},
{
num: "7272222222",
type: "ТДМ",
station: "ОПТС-6 (Алматы)",
city: "Алматы",
desc: "Резервный номер в SIP Trunk, принадлежит ОПТС-6",
reserve: "Резерв для 7271UV / 727103",
},
];
const STATIONS = {
almaty: {
name: "Алматы",
x: 520, y: 505,
sub: { label: "ОПТС3 · ОПТС-72 · ОПТС-6", dx: 0, dy: 14, anchor: "middle" },
op: {
id: "op6", name: "ОПТС-6", x: 578, y: 552, w: 112, h: 26,
note: "резерв 7272222222",
anchor: "start",
},
},
astana: {
name: "Астана",
x: 478, y: 208,
sub: { label: "SSW рег. · SSW транзит", dx: 0, dy: 14, anchor: "middle" },
},
aktobe: { name: "Актобе", x: 198, y: 438 },
kostanay: { name: "Костанай", x: 352, y: 272 },
kokshetau: { name: "Кокшетау", x: 478, y: 268 },
petropavlovsk: { name: "Петропавловск", x: 418, y: 118 },
taraz: { name: "Тараз", x: 438, y: 578 },
shytkent: { name: "Шымкент", x: 468, y: 626 },
pavlodar: { name: "Павлодар", x: 598, y: 162 },
ustkam: { name: "Усть-Каменогорск", x: 738, y: 248 },
};
const NS = "http://www.w3.org/2000/svg";
function normalize(v) {
return String(v == null ? "" : v).replace(/\s+/g, "").toUpperCase();
}
function findEntry(num) {
const n = normalize(num);
if (!n) return null;
return REG.find(function (e) { return normalize(e.num) === n; }) || null;
}
function derive(raw) {
let n = normalize(raw);
n = n.replace(/^(?:[8]\+?7|0)/, "");
if (!/^[0-9A-Z]+$/.test(n)) return null;
const digits = n.replace(/[^0-9]/g, "");
const city = digits.startsWith("727") ? "Алматы" : "неизвестен";
const entry = findEntry(raw);
let owner, reserve, viaStation, isReserve = false;
if (entry) {
owner = ownerByType(entry.type);
reserve = entry.reserve;
viaStation = entry.station;
isReserve = /резерв/i.test(entry.desc);
} else {
owner = digits.length >= 8 ? "pbx" : "zone";
reserve = "Не определён (номер не в реестре)";
viaStation = "неизвестна";
}
const ownerName = ownerLabel(owner);
const endLabel = entry ? "Оконечный номер " + entry.num : "Оконечный номер";
const mobile = [
{ t: "Мобильный оператор" },
{ t: "SSW транзит (ОДС / Астана)" },
{ t: "SSW рег. (регион номера)" },
{ t: endLabel, active: true },
];
const fixed = [
{ t: "Станция владельца номера абонента" },
{ t: "SSW рег. (своя)" },
{ t: "SSW рег. (регион номера)" },
{ t: endLabel, active: true },
];
if (owner === "brw-sip") {
fixed.splice(3, 0, { t: "BRW SIP платформа (стык со всеми SSW рег.)", active: true });
}
if (reserve && !/не определён/i.test(reserve) && !isReserve) {
const rnum = reserve.replace(/^Есть[:\s]*/, "").split(/[\s(]/)[0];
mobile.push({ t: "Резерв", sub: rnum + " · ОПТС-6, при недоступности BRW SIP" });
fixed.push({ t: "Резерв", sub: rnum + " · ОПТС-6, при недоступности BRW SIP" });
}
return { entry: entry, city: city, owner: owner, ownerName: ownerName, reserve: reserve, viaStation: viaStation, mobile: mobile, fixed: fixed };
}
function ownerByType(type) {
const t = normalize(type);
if (t.indexOf("BRW") === 0) return "brw-sip";
if (t === "SIP" || t === "GPON") return "pbx";
if (t === "ТДМ") return "own";
return "zone";
}
function ownerLabel(o) {
if (o === "brw-sip") return "BRW SIP платформа";
if (o === "pbx") return "АТС (станция)";
if (o === "own") return "Собственный номер станции";
return "АТС зоны";
}
function renderChain(id, nodes) {
const el = document.getElementById(id);
el.innerHTML = "";
nodes.forEach(function (node, i) {
const d = document.createElement("div");
d.className = "chain-node" + (node.active ? " active" : "");
const s = document.createElement("strong");
s.textContent = node.t;
d.appendChild(s);
if (node.sub) {
const sp = document.createElement("span");
sp.textContent = node.sub;
d.appendChild(sp);
}
el.appendChild(d);
if (i < nodes.length - 1) {
const a = document.createElement("span");
a.className = "chain-arrow";
a.setAttribute("aria-hidden", "true");
a.innerHTML = "&rarr;";
el.appendChild(a);
}
});
}
function chip(text, tone) {
const s = document.createElement("span");
s.className = "kt-ai-chip";
if (tone) s.setAttribute("data-tone", tone);
s.textContent = text;
return s;
}
function show(num) {
const d = derive(num);
const detail = document.getElementById("resDetail");
if (!d) {
detail.style.display = "none";
const empty = '<div class="chain-node"><strong>Проверьте номер</strong><span>Введите ABC21UV</span></div>';
document.getElementById("chain1").innerHTML = empty;
document.getElementById("chain2").innerHTML = empty;
document.getElementById("r1src").textContent = "";
document.getElementById("r2src").textContent = "";
return;
}
detail.style.display = "block";
document.getElementById("rdNum").textContent = d.entry ? d.entry.num : num.trim().toUpperCase();
const chips = document.getElementById("rdChips");
chips.innerHTML = "";
if (d.entry) {
chips.appendChild(chip(d.entry.type, "blue"));
chips.appendChild(chip(d.entry.station, "gray"));
chips.appendChild(chip("Город: " + d.entry.city, "gray"));
} else {
chips.appendChild(chip("Город: " + d.city, "gray"));
}
document.getElementById("rdMeta").textContent = d.entry ? d.entry.desc : "Номер не найден в реестре — показан базовый маршрут.";
document.getElementById("rdOwn").textContent = "Владелец (по пересчёту): " + d.ownerName;
const r = document.getElementById("rdReserve");
if (/не определён/i.test(d.reserve)) {
r.style.display = "none";
} else {
r.style.display = "block";
r.textContent = "Резерв: " + d.reserve;
}
document.getElementById("r1src").textContent = "Мобильный оператор → SSW транзит (ОДС/Астана) → SSW рег. → " + (d.entry ? d.entry.num : "оконечный номер");
document.getElementById("r2src").textContent = "Станция → SSW рег. (своя) → SSW рег. (регион) → " + (d.entry ? d.entry.num : "оконечный номер");
renderChain("chain1", d.mobile);
renderChain("chain2", d.fixed);
renderScheme(d.entry ? d.entry.num : num);
}
function renderRegister() {
const list = document.getElementById("regList");
list.innerHTML = "";
REG.forEach(function (e) {
const row = document.createElement("div");
row.className = "kt-ai-card reg-row";
const top = document.createElement("div");
top.className = "reg-row-top";
const num = document.createElement("span");
num.className = "reg-row-num";
num.textContent = e.num;
top.appendChild(num);
top.appendChild(chip(e.type, "blue"));
top.appendChild(chip(e.station, "gray"));
top.appendChild(chip("Резерв: есть", "green"));
row.appendChild(top);
const meta = document.createElement("div");
meta.className = "reg-row-meta";
meta.textContent = e.desc + " · Город: " + e.city + " · Формат набора: ABC21UV";
row.appendChild(meta);
const res = document.createElement("div");
res.className = "reg-row-reserve";
res.textContent = "Резерв: " + e.reserve;
row.appendChild(res);
list.appendChild(row);
});
document.getElementById("regCounter").textContent = "Каждый номер: тип (ТДМ / GPON / SIP), станция владения, формат набора ABC21UV и резерв. В реестре: " + REG.length + ".";
}
function renderScheme(focusNum) {
const svg = document.createElementNS(NS, "svg");
svg.setAttribute("viewBox", "0 0 900 700");
svg.setAttribute("role", "img");
svg.setAttribute("aria-label", "Схема маршрутизации вызовов экстренных служб по карте Казахстана");
function el(tag, attrs) {
const e = document.createElementNS(NS, tag);
Object.keys(attrs || {}).forEach(function (k) { e.setAttribute(k, attrs[k]); });
return e;
}
function line(x1, y1, x2, y2, opts) {
const l = el("line", {
x1: x1, y1: y1, x2: x2, y2: y2,
stroke: opts.accent ? "var(--vibe-accent)" : "var(--kt-ai-fg-muted)",
"stroke-width": opts.width || 1.4,
});
if (opts.dash) l.setAttribute("stroke-dasharray", opts.dash);
if (opts.marker) l.setAttribute("marker-end", "url(#arrow" + (opts.accent ? "A" : "M") + ")");
l.setAttribute("opacity", opts.opacity || 0.8);
return l;
}
function text(x, y, s, size, fill, weight, anchor) {
const t = el("text", { x: x, y: y, "text-anchor": anchor || "middle" });
t.setAttribute("style", "font-size:" + size + "px; fill:" + fill + ";" + (weight ? " font-weight:" + weight + ";" : ""));
t.textContent = s;
return t;
}
const defs = el("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);
// Стили точек городов и подписей
svg.setAttribute("style", "svg text { font-family: var(--kt-ai-font-sans); }");
// Контур Казахстана (упрощённый)
const outline = el("path", {
d: "M60,200 L150,115 L255,82 L368,60 L480,52 L592,62 L688,85 L772,122 L828,172 L856,252 L848,338 L808,404 L848,458 L806,528 L738,588 L656,632 L564,654 L484,668 L408,652 L344,606 L284,552 L222,496 L162,442 L114,378 L82,302 L60,252 Z",
fill: "var(--kt-ai-bg)",
stroke: "var(--kt-ai-border)",
"stroke-width": 1.5,
});
svg.appendChild(outline);
// Вспомогательные линии SSW (серые)
[[198, 438, 478, 208], [352, 272, 478, 208], [478, 268, 478, 208], [418, 118, 478, 208],
[598, 162, 478, 208], [738, 248, 478, 208], [438, 578, 520, 505], [468, 626, 520, 505],
[60, 200, 478, 208],
].forEach(function (p) {
svg.appendChild(line(p[0], p[1], p[2], p[3], { opacity: 0.5 }));
});
// Основная цепочка ЭС для Алматы (акцент): мобильные/транзит Астана → SSW рег. Алматы → BRW SIP → оконечный
const almaty = STATIONS.almaty;
svg.appendChild(line(478, 208 + 28, 520, 505 - 18, { accent: true, marker: true }));
svg.appendChild(line(520, 505, 520, 588, { accent: true, marker: true }));
// Резерв: SSW рег. Алматы → ОПТС-6 (пунктир)
const op6 = almaty.op;
svg.appendChild(line(555, 525, 578, 552, { dash: "5 4", marker: true, width: 1.4 }));
// Узел BRW SIP
svg.appendChild(el("rect", { x: 428, y: 588, width: 184, height: 26, rx: 6, fill: "var(--vibe-tint)", stroke: "var(--vibe-accent)", "stroke-width": 1.2 }));
svg.appendChild(text(520, 605, "BRW SIP · стык со всеми SSW рег.", 11, "var(--kt-ai-fg)", 600));
// Узел оконечного номера (под Алматы)
svg.appendChild(el("rect", { x: 448, y: 618, width: 144, height: 24, rx: 6, fill: "var(--kt-ai-card-bg)", stroke: "var(--vibe-accent)", "stroke-width": 1.5 }));
const endNum = focusNum ? normalize(focusNum) : "7271UV";
svg.appendChild(text(520, 634, "Оконечный: " + (endNum || "7271UV"), 11, "var(--kt-ai-fg)", 600));
// Точки городов
Object.keys(STATIONS).forEach(function (key) {
const c = STATIONS[key];
const isAlmaty = key === "almaty";
const isAstana = key === "astana";
const circle = el("circle", {
cx: c.x, cy: c.y, r: isAlmaty ? 6 : 4.5,
fill: isAlmaty || isAstana ? "var(--vibe-accent)" : "var(--kt-ai-card-bg)",
stroke: "var(--kt-ai-border-strong)",
"stroke-width": 1.2,
});
svg.appendChild(circle);
let anchor = "middle", dy = -10;
if (c.x < 240) { anchor = "start"; dy = 4; }
if (c.x > 690) { anchor = "end"; dy = 4; }
svg.appendChild(text(c.x, c.y + dy, c.name, 12, "var(--kt-ai-fg)", 500, anchor));
if (c.sub) {
svg.appendChild(text(c.x, c.y + dy + 12, c.sub.label, 10, "var(--kt-ai-fg-muted)", null, anchor));
}
});
// Блок «ОПТС-6»
svg.appendChild(el("rect", { x: op6.x - 4, y: op6.y - 16, width: op6.w, height: 34, rx: 6, fill: "var(--kt-ai-card-bg)", stroke: "var(--kt-ai-border-strong)", "stroke-width": 1 }));
svg.appendChild(text(op6.x + 2, op6.y, op6.name, 12, "var(--kt-ai-fg)", 600, "start"));
svg.appendChild(text(op6.x + 2, op6.y + 14, op6.note, 10, "var(--kt-ai-fg-muted)", null, "start"));
const elBox = document.getElementById("schemeSvg");
elBox.innerHTML = "";
elBox.appendChild(svg);
}
document.addEventListener("DOMContentLoaded", function () {
renderRegister();
renderScheme("7271UV");
const input = document.getElementById("num");
const btn = document.getElementById("findBtn");
btn.addEventListener("click", function () { show(input.value); });
input.addEventListener("keydown", function (ev) {
if (ev.key === "Enter") show(input.value);
});
input.addEventListener("input", function () { show(input.value); });
show("7271UV");
});
})();