wip: Mobile-Theme-Testphase und PHP-8.2-Guards (Stand Juni 2026)
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
<!-- mobile:content_item -->
|
||||
|
||||
{* MVP-Mobile-Item-Detail mit Bootstrap 5 Card-Layout.
|
||||
Variablen aus $shopArticle (siehe Desktop-content_item.tpl).
|
||||
POST geht zur selben URL (kein action= nötig). *}
|
||||
|
||||
{assign var=item_image_first value=""}
|
||||
{if isset($shopArticle->files[0]) && $shopArticle->files[0]->file_name}
|
||||
{assign var=item_image_first value=$shopArticle->files[0]->file_name}
|
||||
{/if}
|
||||
|
||||
<form method="post" id="item_buy_form">
|
||||
<input type="hidden" name="buy_quantity" value="{$shopArticle->buy_quantity}">
|
||||
<input type="hidden" name="min_quantity" value="{$shopArticle->min_quantity}">
|
||||
|
||||
<div class="mb-3">
|
||||
<a href="javascript:history.back()" class="text-muted text-decoration-none small">
|
||||
<i class="bi bi-arrow-left"></i> zurück
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{* --- Hauptbild + Bildergalerie --- *}
|
||||
<div class="card shop-item-card mb-3">
|
||||
{if $item_image_first}
|
||||
<div id="itemImageSlider" class="carousel slide" data-bs-ride="false">
|
||||
{if $shopArticle->files|@count > 1}
|
||||
<div class="carousel-indicators">
|
||||
{foreach from=$shopArticle->files item=file name=indi}
|
||||
<button type="button" data-bs-target="#itemImageSlider" data-bs-slide-to="{$smarty.foreach.indi.index}"
|
||||
{if $smarty.foreach.indi.first}class="active"{/if} aria-label="Bild {$smarty.foreach.indi.iteration}"></button>
|
||||
{/foreach}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="carousel-inner bg-white">
|
||||
{foreach from=$shopArticle->files item=file name=slides}
|
||||
<div class="carousel-item {if $smarty.foreach.slides.first}active{/if}">
|
||||
<a href="{$SHOP_DIR}images/article/{$file->file_name}" target="_blank">
|
||||
<img src="{$SHOP_DIR}images/article/details_{$file->file_name}"
|
||||
class="d-block w-100" style="height:280px;object-fit:contain;background:#fff;"
|
||||
alt="{if isset($file->alt)}{$file->alt|escape:'html'}{else}{$shopArticle->name|escape:'html'}{/if}">
|
||||
</a>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
{if $shopArticle->files|@count > 1}
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#itemImageSlider" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true" style="background-color:rgba(0,0,0,.3);border-radius:50%;"></span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#itemImageSlider" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true" style="background-color:rgba(0,0,0,.3);border-radius:50%;"></span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{else}
|
||||
<div class="card-body text-center text-muted py-5">
|
||||
<i class="bi bi-image" style="font-size:3rem;"></i><br>
|
||||
<small>Kein Bild verfügbar</small>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{* --- Titel + Hersteller + Artikelnummer --- *}
|
||||
<div class="card shop-item-card mb-3">
|
||||
<div class="card-body">
|
||||
<h1 class="h4 mb-2">
|
||||
{if !$user_lang || $user_lang == $system_configuration.default_system_language || !$shopArticle->name_en}
|
||||
{$shopArticle->name}
|
||||
{else}
|
||||
{$shopArticle->name_en}
|
||||
{/if}
|
||||
</h1>
|
||||
|
||||
{if isset($shopArticle->manufacturer_data->name) && $shopArticle->manufacturer_data->name}
|
||||
<span class="badge bg-secondary mb-2">{$shopArticle->manufacturer_data->name}</span>
|
||||
{/if}
|
||||
|
||||
{if $shopArticle->number}
|
||||
<div class="small text-muted mb-2">
|
||||
{$template_text.item_details.item_number}:
|
||||
<strong>
|
||||
{if $shopArticle->attribute_7 && $shopArticle->number != $shopArticle->attribute_7}
|
||||
{$shopArticle->attribute_7}
|
||||
<span class="text-muted">(alt: {$shopArticle->number})</span>
|
||||
{else}
|
||||
{$shopArticle->number}
|
||||
{/if}
|
||||
</strong>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{* Lagerbestand *}
|
||||
{if $shopArticle->inventory > 0}
|
||||
<div class="text-success small">
|
||||
<i class="bi bi-check-circle-fill"></i> Auf Lager — {$shopArticle->inventory} Stück verfügbar
|
||||
</div>
|
||||
{elseif $shopArticle->inventory === 0}
|
||||
<div class="text-danger small">
|
||||
<i class="bi bi-x-circle-fill"></i> Aktuell nicht verfügbar
|
||||
</div>
|
||||
{else}
|
||||
<div class="text-muted small">
|
||||
<i class="bi bi-info-circle"></i> Lieferzeit auf Anfrage
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* --- Preis + Add-to-Cart --- *}
|
||||
<div class="card shop-item-card mb-3" id="priceBox">
|
||||
<div class="card-body">
|
||||
{if $shopArticle->shop_price_1 > 0}
|
||||
<div class="mb-2">
|
||||
<span class="h3 text-danger">
|
||||
{$shopArticle->shop_price_1|number_format:"2":",":"."} €
|
||||
</span>
|
||||
<small class="text-muted">inkl. MwSt.</small>
|
||||
</div>
|
||||
{else}
|
||||
<div class="mb-2 text-muted">
|
||||
Preis auf Anfrage
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
<label for="qty" class="visually-hidden">Menge</label>
|
||||
<input type="number" name="quantity" id="qty"
|
||||
class="form-control text-center" style="max-width:90px;"
|
||||
value="{if $shopArticle->min_quantity > 0}{$shopArticle->min_quantity}{else}1{/if}"
|
||||
min="{if $shopArticle->min_quantity > 0}{$shopArticle->min_quantity}{else}1{/if}"
|
||||
step="{if $shopArticle->buy_quantity > 0}{$shopArticle->buy_quantity}{else}1{/if}">
|
||||
|
||||
<button type="submit" name="item_to_cart" value="1" class="btn btn-brand btn-lg flex-grow-1">
|
||||
<i class="bi bi-bag-plus"></i> In den Warenkorb
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* --- Beschreibung + weitere Tabs als Accordion --- *}
|
||||
{if isset($shopArticle->tab_content) && $shopArticle->tab_content}
|
||||
<div class="accordion mb-3" id="itemDetailsAcc">
|
||||
{foreach from=$shopArticle->tab_content item=content key=content_id name=tabs}
|
||||
{assign var=tab_label value=""}
|
||||
{if isset($shopArticle->tab_title[$content_id])}
|
||||
{assign var=tab_label value=$shopArticle->tab_title[$content_id]}
|
||||
{else}
|
||||
{assign var=tab_label value=$template_text.item_details.description}
|
||||
{/if}
|
||||
{if $content}
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button {if !$smarty.foreach.tabs.first}collapsed{/if}"
|
||||
type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#tabColl_{$content_id}"
|
||||
aria-expanded="{if $smarty.foreach.tabs.first}true{else}false{/if}">
|
||||
{$tab_label}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="tabColl_{$content_id}" class="accordion-collapse collapse {if $smarty.foreach.tabs.first}show{/if}" data-bs-parent="#itemDetailsAcc">
|
||||
<div class="accordion-body">
|
||||
{$content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/foreach}
|
||||
|
||||
{* Technische Daten als zusätzlicher Block, falls Attribute vorhanden *}
|
||||
{if isset($shopArticle->schema_data_attributes) && $shopArticle->schema_data_attributes}
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#tabColl_attributes" aria-expanded="false">
|
||||
Technische Daten
|
||||
</button>
|
||||
</h2>
|
||||
<div id="tabColl_attributes" class="accordion-collapse collapse" data-bs-parent="#itemDetailsAcc">
|
||||
<div class="accordion-body">
|
||||
<table class="table table-sm table-borderless mb-0">
|
||||
<tbody>
|
||||
{foreach from=$shopArticle->schema_data_attributes item=attribute}
|
||||
{if $attribute.name}
|
||||
<tr>
|
||||
<th scope="row" class="text-muted fw-normal" style="width:45%;">{$attribute.name}</th>
|
||||
<td>{$attribute.value|default:''}{if $attribute.unit} {$attribute.unit}{/if}</td>
|
||||
</tr>
|
||||
{/if}
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</form>
|
||||
|
||||
{* --- Sticky-Bottom-Bar (Quick Add) --- *}
|
||||
<div class="position-fixed bottom-0 start-0 end-0 bg-white shadow-lg p-2 d-md-none" style="z-index:1030;border-top:1px solid #eee;">
|
||||
<div class="d-flex gap-2">
|
||||
<a href="#priceBox" class="btn btn-brand flex-grow-1">
|
||||
<i class="bi bi-bag-plus"></i> Zum Warenkorb hinzufügen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>{if isset($html_meta.title)}{$html_meta.title}{else}{$system_configuration.full_shop_name}{/if}</title>
|
||||
|
||||
{if isset($html_meta.description)}<meta name="description" content="{$html_meta.description}">{/if}
|
||||
{if isset($html_meta.keywords)}<meta name="keywords" content="{$html_meta.keywords}">{/if}
|
||||
|
||||
<link rel="canonical" href="{$serverProtocol}://{$servername}{$currentURI}">
|
||||
<link rel="icon" href="{$SUB_DIR}favicon.ico" type="image/x-icon">
|
||||
|
||||
{* Bootstrap 5.3 *}
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
{* Eigenes Mobile-CSS *}
|
||||
<link rel="stylesheet" href="{$THEME_DIR}/media/css/mobile.css?v=6">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--brand-color: #c00;
|
||||
--brand-color-dark: #a00;
|
||||
}
|
||||
body { padding-top: 56px; padding-bottom: 80px; }
|
||||
.navbar-brand img { max-height: 36px; }
|
||||
.shop-item-card { border: 0; box-shadow: 0 1px 3px rgba(0,0,0,.08); margin-bottom: 12px; }
|
||||
.btn-brand { background: var(--brand-color); color: #fff; border: 0; }
|
||||
.btn-brand:hover { background: var(--brand-color-dark); color: #fff; }
|
||||
.theme-switch-hint {
|
||||
position: fixed; bottom: 8px; right: 8px; z-index: 999;
|
||||
background: rgba(0,0,0,.7); color: #fff; padding: 4px 10px;
|
||||
border-radius: 12px; font-size: 11px; text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{* --- Top-Bar --- *}
|
||||
<nav class="navbar navbar-expand-lg fixed-top bg-white shadow-sm" aria-label="Hauptnavigation">
|
||||
<div class="container-fluid">
|
||||
<button class="navbar-toggler border-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#mobileNav" aria-controls="mobileNav" aria-label="Menü öffnen">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<a class="navbar-brand mx-auto" href="{$SUB_DIR}">
|
||||
<strong>{$system_configuration.full_shop_name}</strong>
|
||||
</a>
|
||||
|
||||
<a href="{$SUB_DIR}index.php?website_modul=website_shoppingcart" class="btn btn-link text-dark p-0" aria-label="Warenkorb">
|
||||
<i class="bi bi-bag" style="font-size: 1.5rem;"></i>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{* --- Off-Canvas Nav --- *}
|
||||
<div class="offcanvas offcanvas-start" tabindex="-1" id="mobileNav" aria-labelledby="mobileNavLabel">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="mobileNavLabel">Menü</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form action="{$SUB_DIR}" method="get" class="mb-3">
|
||||
<div class="input-group">
|
||||
<input type="text" name="search_item[search_item]" class="form-control" placeholder="Suchen…" autocomplete="off">
|
||||
<button class="btn btn-brand" type="submit"><i class="bi bi-search"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item"><a class="nav-link" href="{$SUB_DIR}">Startseite</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{$SUB_DIR}Mein-Konto/">Mein Konto</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{$SUB_DIR}Kontakt/">Kontakt</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{$SUB_DIR}Widerrufsrecht/">Widerrufsbelehrung</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{$SUB_DIR}AGB/">AGB</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{$SUB_DIR}Datenschutz/">Datenschutz</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{$SUB_DIR}Impressum/">Impressum</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* --- Content --- *}
|
||||
<main class="container py-3">
|
||||
{$maincontent}
|
||||
</main>
|
||||
|
||||
{* --- Footer --- *}
|
||||
<footer class="bg-light py-4 mt-4 border-top">
|
||||
<div class="container text-center">
|
||||
<a class="btn btn-brand btn-lg mb-2" href="{$SUB_DIR}widerruf.php">
|
||||
Vertrag widerrufen
|
||||
</a>
|
||||
<p class="small text-muted mb-1">Elektronische Widerrufsfunktion gem. § 356a BGB</p>
|
||||
<hr class="my-3">
|
||||
<p class="small text-muted mb-0">
|
||||
<strong>{$system_configuration.full_shop_name}</strong> ·
|
||||
{$system_configuration.footer_copyright_text}
|
||||
</p>
|
||||
<p class="small mt-2 mb-0">
|
||||
<a href="?theme=theme2" class="text-muted">Zur Desktop-Version</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{* Theme-Switch Hint (nur Test-Phase) *}
|
||||
<a href="?theme=theme2" class="theme-switch-hint">📱 mobile · zurück zur Desktop-Version</a>
|
||||
|
||||
{* Bootstrap 5 JS bundle *}
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user