2026-10-23 10:17:31 +08:00
{# trade-pro product detail — cms 产品详情设计移植(Task 10b)。
区块序列对照 D:/project/cms/runtimes/astro-site-runtime/src/pages/[locale]/products/[slug].astro
(像素基准 dist/hesupipe/en/products/alloy-resin-system-window/):
面包屑 → 画廊(主图 + 单行横滚缩略图)+ 标题区(眉题/h1/导语/品牌行/
CTA 对)→ 正文 Product Details → 参数表 Specifications( fields 键值,
空则整段隐藏)→ 相关产品(卡片网格)→ 询盘板块(左联系信息 + 右 CTA/
外部表单)。
与 cms 的两处结构决策:
1. Tab 导航平铺:cms 的 Tab 序列承载每产品自定义区块,平台正文是单一
富文本 + 参数表,仅两个面板不值得引入 JS Tab;cms 无 JS 回退形态
本就是面板平铺,这里固定为平铺(无 JS 依赖、静态可抓取)。
2. 画廊交互复用平台 pro.js 既有钩子(.pro-product-media /
.pro-gallery-thumbs / [data-gallery-image]):缩略图点击换主图 +
主图灯箱,零新增 JS;无 JS 时全部图片仍平铺可读。无图回退封面,
再无则占位渐变(空值不破版)。 #}
< section class = "sf-tp-product-hero" >
< div class = "wrap" >
< nav class = "sf-tp-breadcrumb" aria-label = "Breadcrumb" data-reveal >
< a href = "/" > Home</ a >< a href = "/products/" > Products</ a >< span class = "sf-tp-breadcrumb-current" > {{ page.title }}</ span >
</ nav >
< div class = "sf-tp-product-grid" >
< div class = "sf-tp-product-gallery" data-reveal >
{% if gallery %}
< div class = "sf-tp-product-media pro-product-media" >
< img class = "sf-tp-product-main-img" src = "{{ gallery[0] }}" alt = "{{ page.title }}" width = "800" height = "600" loading = "eager" decoding = "async" >
</ div >
{% if gallery|length > 1 %}
< div class = "sf-tp-product-thumbs pro-gallery-thumbs" aria-label = "Product image gallery" >
{% for photo in gallery %}
< button type = "button" class = "sf-tp-product-thumb" data-gallery-image = "{{ photo }}" aria-label = "View image {{ loop.index }}" aria-pressed = "{{ 'true' if loop.first else 'false' }}" >< img src = "{{ photo }}" alt = "{{ page.title }} image {{ loop.index }}" width = "150" height = "100" loading = "lazy" decoding = "async" ></ button >
{% endfor %}
</ div >
{% endif %}
{% else %}
< div class = "sf-tp-product-media sf-tp-photo pro-product-media" >
< div class = "sf-tp-photo-plain" aria-hidden = "true" >< span > PRODUCT</ span ></ div >
</ div >
{% endif %}
</ div >
< div class = "sf-tp-product-summary" data-reveal style = "--reveal-i: 1" >
< span class = "eyebrow" > PRODUCT DETAILS / {{ fields.get('brand') or 'CATALOGUE' }}</ span >
< h1 class = "sf-tp-product-title" > {{ page.title }}</ h1 >
< p class = "sf-tp-product-lead" > {{ page.summary or 'Detailed product information, specifications and export packaging are published on this page.' }}</ p >
{% if fields.get('brand') or fields.get('model') %}
< p class = "sf-tp-product-meta" > {% if fields.get('brand') %}< span > Brand · {{ fields.get('brand') }}</ span > {% endif %}{% if fields.get('model') %}< span > Model · {{ fields.get('model') }}</ span > {% endif %}</ p >
{% endif %}
< div class = "sf-tp-product-actions" >
< a class = "sf-tp-btn" href = "/contact/" > Get a quote {{ icon('arrow-right') }}</ a >
< a class = "sf-tp-btn sf-tp-btn--ghost" href = "/products/" > All products {{ icon('arrow-right') }}</ a >
</ div >
</ div >
</ div >
</ div >
</ section >
< section class = "section sf-tp-product-overview" >
< div class = "wrap" >
< span class = "eyebrow" > OVERVIEW</ span >
< h2 class = "sf-tp-panel-title" > Product details</ h2 >
< div class = "sf-tp-product-body" data-reveal > {{ body_copy(content, page) }}</ div >
</ div >
</ section >
{% if specs %}
< section class = "section section--soft sf-tp-product-specs" >
< div class = "wrap" >
< span class = "eyebrow" > SPECIFICATIONS</ span >
< h2 class = "sf-tp-panel-title" > Specifications</ h2 >
< div class = "sf-tp-table-scroll" data-reveal >
< table class = "sf-tp-spec-table" >
< tbody >
{% for row in specs %}
< tr >< th scope = "row" > {{ row.label }}</ th >< td > {{ row.value }}</ td ></ tr >
{% endfor %}
</ tbody >
</ table >
</ div >
</ div >
</ section >
{% endif %}
{% set tp_related = (groups['product'] + groups['vehicle']) | rejectattr('route', 'equalto', page.route) | list %}
{% if tp_related %}
< section class = "section" >
< div class = "wrap" >
< div class = "sf-tp-section-head" data-reveal >
< div >
< span class = "eyebrow" > RELATED PRODUCTS</ span >
< h2 > You may also need</ h2 >
</ div >
< a class = "sf-tp-more" href = "/products/" > Browse all products {{ icon('arrow-right', cls='sf-tp-icon--sm') }}</ a >
</ div >
< div class = "sf-tp-card-grid" >
{% for tp_item in tp_related[:4] %}
< article class = "sf-tp-card" data-reveal style = "--reveal-i: {{ loop.index0 % 4 }}" >
< div class = "sf-tp-card-media" > {{ cover(tp_item, 'product') }}</ div >
< div class = "sf-tp-card-body" >
< h3 > {{ tp_item.title }}</ h3 >
< p > {{ (tp_item.summary or 'Published specifications available on request.').split('.')[0] }}.</ p >
< a class = "sf-tp-more" href = "{{ safe_href(tp_item.route) }}" > View details {{ icon('arrow-right', cls='sf-tp-icon--sm') }}</ a >
</ div >
</ article >
{% endfor %}
</ div >
</ div >
</ section >
{% endif %}
< section class = "section section--soft sf-tp-product-quote" >
< div class = "wrap sf-tp-quote-grid" >
< div class = "sf-tp-quote-info" data-reveal >
< span class = "eyebrow" > REQUEST A QUOTE</ span >
< h2 > Inquire about this product</ h2 >
< p > Send the specifications you need for < strong > {{ page.title }}</ strong > and our trade desk replies with pricing, lead time and export packaging within one business day.</ p >
< div class = "sf-tp-quote-list" >
{% if footer_email %}
< div class = "sf-tp-quote-item" >
< div class = "icon-circle" aria-hidden = "true" > {{ icon('mail') }}</ div >
< div >< strong > Email</ strong >< p >< a href = "mailto:{{ footer_email }}" > {{ footer_email }}</ a ></ p ></ div >
</ div >
{% endif %}
< div class = "sf-tp-quote-item" >
< div class = "icon-circle" aria-hidden = "true" > {{ icon('check') }}</ div >
< div >< strong > Direct sourcing desk</ strong >< p > One contact from inquiry to container loading.</ p ></ div >
</ div >
</ div >
</ div >
< div class = "sf-tp-quote-form" data-reveal style = "--reveal-i: 1" >
{% if form_endpoint %}
< h3 > Get a quote for {{ page.title }}</ h3 >
< form action = "{{ form_endpoint }}" method = "POST" >
< label > Name < input name = "name" autocomplete = "name" required maxlength = "100" ></ label >
< label > Email < input name = "email" type = "email" autocomplete = "email" required maxlength = "180" ></ label >
< label > Message < textarea name = "message" required minlength = "6" maxlength = "5000" placeholder = "I would like a quotation for {{ page.title }}..." ></ textarea ></ label >
< input name = "website" tabindex = "-1" autocomplete = "off" class = "pro-honeypot" aria-hidden = "true" >
< button type = "submit" class = "sf-tp-btn" > Submit enquiry {{ icon('arrow-right') }}</ button >
< p class = "sf-tp-quote-note" > Submissions are handled by the external form service configured by the site administrator.</ p >
</ form >
{% else %}
< h3 > Get a quote for {{ page.title }}</ h3 >
< p > Share your specifications and target market through the contact page; our trade desk replies within one business day.</ p >
< a class = "sf-tp-btn" href = "/contact/" > Contact us {{ icon('arrow-right') }}</ a >
{% endif %}
</ div >
</ div >
</ section >