feat: 24 individuelle SEO-Artikel-URLs (/artikel/<slug>)
- Jeder der 24 Artikel hat jetzt seine eigene Unterseite - SEO-Slugs aus Artikeltiteln (ti-anschlusspflicht-2025, etc.) - /blog/<slug> bleibt als Sektions-Uebersichtsseite - Sidebar-Links zeigen jetzt auf individuelle Artikel-URLs - _ARTIKEL um 4. Feld (SEO-Slug) erweitert - Neue /artikel/<slug>-Route mit vollstaendigem Artikel-Layout
This commit is contained in:
@@ -297,30 +297,30 @@ def datenschutz() -> str:
|
||||
|
||||
# ── Blog-Artikel-Datenbank ───────────────────────────────
|
||||
_ARTIKEL = [
|
||||
("/blog/telematik-vorschriften", "TI-Anschlusspflicht 2025", "artikel-01-ti-anschlusspflicht.html"),
|
||||
("/blog/telematik-vorschriften", "Elektronische Abrechnung ab 12/2026", "artikel-02-elektronische-abrechnung.html"),
|
||||
("/blog/telematik-vorschriften", "SMC-B-Karte — 33% Abdeckung", "artikel-03-smc-b-karte.html"),
|
||||
("/blog/dsgvo-it-sicherheit", "46,9 Mio. EUR DSGVO-Bussgelder", "artikel-04-dsgvo-bussgelder.html"),
|
||||
("/blog/dsgvo-it-sicherheit", "BSI: 16 Schwachstellen Pflegesoftware", "artikel-05-bsi-schwachstellen.html"),
|
||||
("/blog/dsgvo-it-sicherheit", "138 Sicherheitsvorfaelle Gesundheitswesen", "artikel-06-sicherheitsvorfaelle.html"),
|
||||
("/blog/telematik-vorschriften", "NIS-2 Registrierungsfrist", "artikel-07-nis2.html"),
|
||||
("/blog/fachkraeftemangel", "200.000 unbesetzte Pflegestellen", "artikel-08-fachkraeftemangel.html"),
|
||||
("/blog/fachkraeftemangel", "62% Altenpflege — Personalplanung", "artikel-09-personalplanung.html"),
|
||||
("/blog/fachkraeftemangel", "KI-Sprachassistent spart 27% Zeit", "artikel-10-ki-sprachassistent.html"),
|
||||
("/blog/foerderung-finanzierung", "Bis 12.000 EUR Digitalisierungszuschuss", "artikel-11-digitalisierungszuschuss.html"),
|
||||
("/blog/foerderung-finanzierung", "DiPA — 70 EUR monatlich ab 2026", "artikel-12-dipa.html"),
|
||||
("/blog/foerderung-finanzierung", "Pflegeversicherung 1 Mrd. Defizit", "artikel-13-pflegeversicherung.html"),
|
||||
("/blog/qualitaet-pruefung", "QPR ambulant ab Juli 2026", "artikel-14-qpr-ambulant.html"),
|
||||
("/blog/qualitaet-pruefung", "PeBeM verpflichtend ab 2026", "artikel-15-pebem.html"),
|
||||
("/blog/qualitaet-pruefung", "MDK-Pruefung digital bestehen", "artikel-16-mdk-pruefung.html"),
|
||||
("/blog/telematik-vorschriften", "ePA fuer alle — Opt-out", "artikel-17-epa.html"),
|
||||
("/blog/dsgvo-it-sicherheit", "Cloud-Hosting Deutschland", "artikel-18-cloud-hosting.html"),
|
||||
("/blog/praxis-tipps", "Tablets fuer die Pflege — Top 5", "artikel-19-tablets.html"),
|
||||
("/blog/praxis-tipps", "WLAN im Pflegeheim", "artikel-20-wlan.html"),
|
||||
("/blog/praxis-tipps", "Offline-Dokumentation", "artikel-21-offline.html"),
|
||||
("/blog/dsgvo-it-sicherheit", "E-Mail-Verschluesselung", "artikel-22-email-verschluesselung.html"),
|
||||
("/blog/praxis-tipps", "Medikationsplan digital", "artikel-23-medikationsplan.html"),
|
||||
("/blog/praxis-tipps", "IT-Notfallplan 10-Punkte", "artikel-24-it-notfallplan.html"),
|
||||
("/blog/telematik-vorschriften", "TI-Anschlusspflicht 2025", "artikel-01-ti-anschlusspflicht.html", "ti-anschlusspflicht-2025"),
|
||||
("/blog/telematik-vorschriften", "Elektronische Abrechnung ab 12/2026", "artikel-02-elektronische-abrechnung.html", "elektronische-abrechnung-2026"),
|
||||
("/blog/telematik-vorschriften", "SMC-B-Karte — 33% Abdeckung", "artikel-03-smc-b-karte.html", "smc-b-karte-pflege"),
|
||||
("/blog/dsgvo-it-sicherheit", "46,9 Mio. EUR DSGVO-Bussgelder", "artikel-04-dsgvo-bussgelder.html", "dsgvo-bussgelder-gesundheitswesen"),
|
||||
("/blog/dsgvo-it-sicherheit", "BSI: 16 Schwachstellen Pflegesoftware", "artikel-05-bsi-schwachstellen.html", "bsi-schwachstellen-pflegesoftware"),
|
||||
("/blog/dsgvo-it-sicherheit", "138 Sicherheitsvorfaelle Gesundheitswesen", "artikel-06-sicherheitsvorfaelle.html", "sicherheitsvorfaelle-gesundheitswesen"),
|
||||
("/blog/telematik-vorschriften", "NIS-2 Registrierungsfrist", "artikel-07-nis2.html", "nis2-registrierungsfrist"),
|
||||
("/blog/fachkraeftemangel", "200.000 unbesetzte Pflegestellen", "artikel-08-fachkraeftemangel.html", "fachkraeftemangel-pflege"),
|
||||
("/blog/fachkraeftemangel", "62% Altenpflege — Personalplanung", "artikel-09-personalplanung.html", "personalplanung-altenpflege"),
|
||||
("/blog/fachkraeftemangel", "KI-Sprachassistent spart 27% Zeit", "artikel-10-ki-sprachassistent.html", "ki-sprachassistent-pflege"),
|
||||
("/blog/foerderung-finanzierung", "Bis 12.000 EUR Digitalisierungszuschuss", "artikel-11-digitalisierungszuschuss.html", "digitalisierungszuschuss-pflege"),
|
||||
("/blog/foerderung-finanzierung", "DiPA — 70 EUR monatlich ab 2026", "artikel-12-dipa.html", "dipa-digitale-pflegeanwendungen"),
|
||||
("/blog/foerderung-finanzierung", "Pflegeversicherung 1 Mrd. Defizit", "artikel-13-pflegeversicherung.html", "pflegeversicherung-defizit"),
|
||||
("/blog/qualitaet-pruefung", "QPR ambulant ab Juli 2026", "artikel-14-qpr-ambulant.html", "qpr-ambulant-2026"),
|
||||
("/blog/qualitaet-pruefung", "PeBeM verpflichtend ab 2026", "artikel-15-pebem.html", "pebem-2026"),
|
||||
("/blog/qualitaet-pruefung", "MDK-Pruefung digital bestehen", "artikel-16-mdk-pruefung.html", "mdk-pruefung-digital"),
|
||||
("/blog/telematik-vorschriften", "ePA fuer alle — Opt-out", "artikel-17-epa.html", "epa-opt-out"),
|
||||
("/blog/dsgvo-it-sicherheit", "Cloud-Hosting Deutschland", "artikel-18-cloud-hosting.html", "cloud-hosting-deutschland"),
|
||||
("/blog/praxis-tipps", "Tablets fuer die Pflege — Top 5", "artikel-19-tablets.html", "tablets-pflege-top5"),
|
||||
("/blog/praxis-tipps", "WLAN im Pflegeheim", "artikel-20-wlan.html", "wlan-pflegeheim"),
|
||||
("/blog/praxis-tipps", "Offline-Dokumentation", "artikel-21-offline.html", "offline-dokumentation-pflege"),
|
||||
("/blog/dsgvo-it-sicherheit", "E-Mail-Verschluesselung", "artikel-22-email-verschluesselung.html", "email-verschluesselung-pflege"),
|
||||
("/blog/praxis-tipps", "Medikationsplan digital", "artikel-23-medikationsplan.html", "medikationsplan-digital"),
|
||||
("/blog/praxis-tipps", "IT-Notfallplan 10-Punkte", "artikel-24-it-notfallplan.html", "it-notfallplan-pflege"),
|
||||
]
|
||||
|
||||
# ── Leistungen-Daten ─────────────────────────────────────
|
||||
@@ -392,15 +392,15 @@ _IT_SERVICE = {
|
||||
|
||||
|
||||
# ── Helper: Seitenleiste ──────────────────────────────────
|
||||
def _sidebar(routes: list[tuple[str, str, str]]) -> str:
|
||||
def _sidebar(routes: list[tuple[str, str, str, str]]) -> str:
|
||||
"""Erstelle Seitenleiste mit Artikel-Links.
|
||||
|
||||
Args:
|
||||
routes: Liste von (Route, Titel, Datei)-Tupeln.
|
||||
routes: Liste von (Sektion, Titel, Datei, Slug)-Tupeln.
|
||||
"""
|
||||
links = []
|
||||
for route, titel, _datei in routes:
|
||||
links.append('<a href="' + route + '" class="sidebar-link">' + titel + "</a>")
|
||||
links: list[str] = []
|
||||
for _sektion, titel, _datei, slug in routes:
|
||||
links.append('<a href="/artikel/' + slug + '" class="sidebar-link">' + titel + "</a>")
|
||||
return (
|
||||
"<div class='article-sidebar'><div class='sidebar-title'>Weitere Artikel</div>"
|
||||
+ "".join(links)
|
||||
@@ -414,18 +414,19 @@ def _cards(titel: str, beschreibung: str) -> str:
|
||||
return '<div class="card"><h3>' + titel + "</h3><p>" + beschreibung + "</p></div>"
|
||||
|
||||
|
||||
# ── Blog-Route (FIX: Nutzt _ARTIKEL-Mapping) ─────────────
|
||||
# ── Blog-Sektionen (Übersichtsseiten) ─────────────────────
|
||||
@app.route("/blog/<path:slug>")
|
||||
def blog(slug: str) -> str:
|
||||
"""Blog-Artikel — nutzt _ARTIKEL-Mapping fuer korrekte Dateinamen."""
|
||||
def blog_section(slug: str) -> str:
|
||||
"""Blog-Sektion — zeigt ersten Artikel + Sidebar mit allen Artikeln der Sektion."""
|
||||
route_key = "/blog/" + slug
|
||||
|
||||
# Finde ALLE Artikel dieser Sektion
|
||||
sektions_artikel = [(r, t, d) for r, t, d in _ARTIKEL if r == route_key]
|
||||
# Finde ALLE Artikel dieser Sektion (4-Tupel)
|
||||
sektions_artikel: list[tuple[str, str, str, str]] = [
|
||||
(r, t, d, s) for r, t, d, s in _ARTIKEL if r == route_key
|
||||
]
|
||||
|
||||
if sektions_artikel:
|
||||
# Ersten Artikel als Hauptinhalt laden
|
||||
_, haupt_titel, datei = sektions_artikel[0]
|
||||
_, haupt_titel, datei, _ = sektions_artikel[0]
|
||||
pfad = ARTIKEL_DIR / datei
|
||||
if pfad.exists():
|
||||
inhalt = pfad.read_text(encoding="utf-8")
|
||||
@@ -434,11 +435,10 @@ def blog(slug: str) -> str:
|
||||
else:
|
||||
inhalt = "<p>Artikeltext folgt in Kuerze.</p>"
|
||||
else:
|
||||
inhalt = "<p>Artikel nicht gefunden.</p>"
|
||||
sektions_artikel = _ARTIKEL
|
||||
inhalt = "<p>Keine Artikel in dieser Sektion gefunden.</p>"
|
||||
haupt_titel = slug.replace("-", " ").title()
|
||||
sektions_artikel = _ARTIKEL
|
||||
|
||||
# Seitenleiste: alle Artikel dieser Sektion
|
||||
sidebar = _sidebar(sektions_artikel)
|
||||
|
||||
content = (
|
||||
@@ -453,6 +453,49 @@ def blog(slug: str) -> str:
|
||||
return _base_template("Blog: " + haupt_titel, content, active="blog")
|
||||
|
||||
|
||||
# ── Individuelle Artikel-Seiten (SEO-optimiert) ──────────
|
||||
_ARTIKEL_BY_SLUG: dict[str, tuple[str, str, str, str]] = {
|
||||
slug: (sektion, titel, datei, slug)
|
||||
for sektion, titel, datei, slug in _ARTIKEL
|
||||
}
|
||||
|
||||
|
||||
@app.route("/artikel/<path:slug>")
|
||||
def artikel_detail(slug: str) -> str:
|
||||
"""Einzelner Artikel — eigene SEO-URL mit vollstaendigem Inhalt."""
|
||||
eintrag = _ARTIKEL_BY_SLUG.get(slug)
|
||||
|
||||
if eintrag:
|
||||
sektion, titel, datei, _ = eintrag
|
||||
pfad = ARTIKEL_DIR / datei
|
||||
if pfad.exists():
|
||||
inhalt = pfad.read_text(encoding="utf-8")
|
||||
match = re.search(r"<body[^>]*>(.*?)</body>", inhalt, re.DOTALL | re.IGNORECASE)
|
||||
inhalt = match.group(1) if match else inhalt
|
||||
else:
|
||||
inhalt = "<p>Artikeltext folgt in Kuerze.</p>"
|
||||
|
||||
# Sidebar: andere Artikel derselben Sektion
|
||||
sektions_artikel: list[tuple[str, str, str, str]] = [
|
||||
(r, t, d, s) for r, t, d, s in _ARTIKEL if r == sektion and s != slug
|
||||
]
|
||||
sidebar = _sidebar(sektions_artikel)
|
||||
|
||||
content = (
|
||||
"<div class='article-layout'><div class='article-content'><h1>"
|
||||
+ titel
|
||||
+ "</h1>"
|
||||
+ inhalt
|
||||
+ "</div>"
|
||||
+ sidebar
|
||||
+ "</div>"
|
||||
)
|
||||
return _base_template(titel, content, active="blog")
|
||||
else:
|
||||
content = "<div class='content'><h1>Artikel nicht gefunden</h1><p>Dieser Artikel existiert leider nicht.</p></div>"
|
||||
return _base_template("Artikel nicht gefunden", content)
|
||||
|
||||
|
||||
# ── Weitere Routen ───────────────────────────────────────
|
||||
@app.route("/ueber-uns")
|
||||
def ueber_uns() -> str:
|
||||
|
||||
Reference in New Issue
Block a user