0ec826bd4b
Task 10g: rewrite pages/case.html from the thin 1-line variant to the cms projects/[slug].astro design (pixel baseline dist/hesupipe/en/projects/): PageHero (breadcrumb Home > Cases > title, cover bg) + 1.2fr/0.8fr detail grid (overview block with location/industry rows + rich body via the 10d typography layer; sticky side column with main photo, 3-col gallery tiles and Results card) + Recommended Products + Other Projects (groups['case'] minus self), all empty-data sections hidden. Results points come from the open fields.results convention (semicolon/newline separated); cms dictionary fallback copy is never ported (fake data across sites). Listing case branch (10c evaluation): core form (hero + cols-3 cards + pagination) already matches cms projects list; only the hero copy voice differed, so listing.html gains a case-aware eyebrow/lead fallback (OUR PROJECTS). cms-specific tabs (no data source) and the FEATURED CASE STUDY band (HESU-static dictionary content) stay unported, documented in the template header. Adds test_trade_pro_case_layout_structure (build-flow: gallery/results structure, related-case dedup, empty-data hiding, /cases/ hero voice). Co-Authored-By: Claude Code <noreply@anthropic.com>
145 lines
9.8 KiB
HTML
145 lines
9.8 KiB
HTML
{# trade-pro listing / category — cms 列表设计移植(Task 10c)。
|
||
设计源 D:/project/cms/runtimes/astro-site-runtime/src/pages/[locale]/:
|
||
- products/[...page].astro:PageHero + 260px 产品分类侧栏(sticky)+
|
||
SectionHead + 3 列卡片栅格(CardGrid cols-3)+ 窗口式分页
|
||
- news/[...page].astro:PageHero + 1fr/310px 双栏;列表列为横向新闻行
|
||
(290px 封面 + tag/标题/摘要/Read more),侧栏三盒
|
||
(Latest / Popular / Categories 计数)
|
||
与 cms 的结构决策:
|
||
1. kind 分支按列表内容类型:post 类(/news/、/post/ 等文章集合)用
|
||
新闻行形态;其余(product/vehicle/brand/case/service)复用产品卡片
|
||
栅格——cms 只定义了这两种列表设计,品牌/案例/服务映射到产品卡形态
|
||
(与 brand.html 相关产品、product.html 相关产品一致)。非空列表只信
|
||
首项内容类型;路由子串仅作空列表 fallback 且排除 category(否则
|
||
/categories/post-sales-service/ 这类 slug 会被误判成新闻分支)。
|
||
2. 产品分类侧栏仅产品类列表渲染(侧栏链接是产品分类语义,出现在
|
||
/brands/ 等页面会误导);文章分支的侧栏是 cms 新闻三盒。空分类
|
||
数据时侧栏整段隐藏,主列变单栏。
|
||
3. cms news 页的 Tab 行(All/Company News/...)与 products 页 hero 的
|
||
“Explore Products”锚点按钮是内容数据驱动 / 页内锚点跳转,平台无
|
||
对应数据源与目标锚点,不移植;筛选面板承担列表交互入口。
|
||
4. 平台筛选契约(test_theme_pro_v3 对所有主题 listing 断言)保留:
|
||
filters() 宏输出 .pro-filter-panel 且必须保持为 .pro-filter-scope
|
||
的紧邻前兄弟(pro.js 用 previousElementSibling 定位面板);卡片与
|
||
新闻行都携带 .pro-card + data-brand/data-category(pro.js 过滤的
|
||
数据源与零结果空态都依赖它们)。
|
||
5. 分页器是 cms Pagination 的窗口式页码(首尾必现 + 当前±2,其余折叠
|
||
为省略号),上一页/下一页用 arrow-left/arrow-right SVG(Task 9 图标
|
||
契约:交互箭头一律 SVG,无裸箭头字符);页码 URL 与构建期静态分页
|
||
路由一致(第 1 页 = 栏目根,形如 /products/page/2/ 递增)。
|
||
6. 空列表:筛选、栅格、分页整段隐藏,替换为友好空态提示(cms 站点
|
||
恒有内容,平台必须处理 noindex 栏目占位页的空集)。
|
||
7. 10g 评估 cms projects 列表([...page].astro)vs 本分支:核心形态
|
||
(hero + cols-3 卡栅格 + 分页)一致,仅补案例口径 hero 文案
|
||
(tp_case_like);cms 专属的 Tab 行(数据驱动分类,无数据源,同 3
|
||
不移植)与 FEATURED CASE STUDY 双栏带(HESU 特定项目的静态词典
|
||
内容,跨站即假数据)不移植。cms 卡片链到 /news/<slug>/ 是老站
|
||
/progect/ 同源遗留,平台链到案例详情(正确行为)。 #}
|
||
{% set tp_post_like = (listing[0].type == 'post') if listing else (kind == 'listing' and ('news' in route or 'post' in route)) %}
|
||
{# 10g:case 类列表走 cms projects 列表口径(OUR PROJECTS),其余非文章类
|
||
保持产品目录口径;判定与 select_listing 的路由推断同构(首项类型优先)#}
|
||
{% set tp_case_like = (listing[0].type == 'case') if listing else (kind == 'listing' and 'case' in route) %}
|
||
{% set tp_eyebrow = 'NEWS & INSIGHTS' if tp_post_like else ('OUR PROJECTS' if tp_case_like else 'PRODUCT CATALOGUE') %}
|
||
{% set tp_hero_img = safe_image(page.cover) %}
|
||
{% set tp_lead = page.summary or ('Industry updates, project stories and exhibition news from our team.' if tp_post_like else ('Project references and delivery stories from export customers worldwide.' if tp_case_like else 'Browse the full catalogue with specifications, brands and categories.')) %}
|
||
{% set pg_root = (route.split('/page/')[0]).rstrip('/') ~ '/' %}
|
||
{# 分页导航宏:显式传参(pg/base/icon),不依赖模板上下文闭包 #}
|
||
{% macro tp_pages(pg, base, icon) -%}
|
||
{% if pg and pg.pages > 1 %}
|
||
{% set cur = pg.page %}{% set last = pg.pages %}{% set win = [] %}
|
||
{% for i in range(1, last + 1) %}
|
||
{% if i == 1 or i == last or (i - cur)|abs <= 2 %}{% if win.append(i) %}{% endif %}
|
||
{% elif win and win[-1] != '…' %}{% if win.append('…') %}{% endif %}{% endif %}
|
||
{% endfor %}
|
||
<nav class="sf-tp-pagination" aria-label="Pagination">
|
||
{% if pg.previous %}<a class="sf-tp-page-arrow" href="{{ pg.previous }}" rel="prev" aria-label="Previous page">{{ icon('arrow-left', size='16') }}</a>
|
||
{% else %}<span class="sf-tp-page-arrow is-disabled" aria-hidden="true">{{ icon('arrow-left', size='16') }}</span>{% endif %}
|
||
{% for n in win %}{% if n == '…' %}<span class="sf-tp-page-dots">…</span>
|
||
{% elif n == cur %}<span class="sf-tp-page-num is-active" aria-current="page">{{ n }}</span>
|
||
{% else %}<a class="sf-tp-page-num" href="{{ base if n == 1 else base ~ 'page/' ~ n ~ '/' }}">{{ n }}</a>{% endif %}{% endfor %}
|
||
{% if pg.next %}<a class="sf-tp-page-arrow" href="{{ pg.next }}" rel="next" aria-label="Next page">{{ icon('arrow-right', size='16') }}</a>
|
||
{% else %}<span class="sf-tp-page-arrow is-disabled" aria-hidden="true">{{ icon('arrow-right', size='16') }}</span>{% endif %}
|
||
</nav>
|
||
{% endif %}
|
||
{%- endmacro %}
|
||
<section class="sf-tp-page-hero">
|
||
{% if tp_hero_img %}<img class="sf-tp-page-hero-bg" src="{{ tp_hero_img }}" alt="" width="1600" height="900" fetchpriority="high" decoding="async">{% endif %}
|
||
<div class="sf-tp-page-hero-overlay" aria-hidden="true"></div>
|
||
<div class="wrap">
|
||
<div class="sf-tp-page-hero-copy" data-reveal>
|
||
<span class="eyebrow">{{ tp_eyebrow }}</span>
|
||
<h1>{{ page.title }}</h1>
|
||
<p class="sf-tp-page-hero-lead">{{ tp_lead }}</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
{% if listing %}
|
||
{% if tp_post_like %}
|
||
<section class="section sf-tp-news-section"><div class="wrap"><div class="sf-tp-news-layout">
|
||
<div class="sf-tp-news-list">
|
||
{{ filters() }}
|
||
<div class="pro-filter-scope sf-tp-news-rows">
|
||
{% for item in listing %}
|
||
<article class="sf-tp-news-row pro-card" data-reveal style="--reveal-i: {{ loop.index0 % 3 }}" data-brand="{{ fields_of(item).get('brand','') }}" data-category="{{ fields_of(item).get('category','') }}">
|
||
<div class="sf-tp-news-row-media sf-tp-card-media">{{ cover(item, 'post') }}</div>
|
||
<div class="sf-tp-news-body">
|
||
<span class="sf-tp-tag">{{ fields_of(item).get('tag') or fields_of(item).get('category') or 'News' }}</span>
|
||
<h3><a href="{{ safe_href(item.route) }}">{{ item.title }}</a></h3>
|
||
<p>{{ (item.summary or '')[:200] }}</p>
|
||
<a class="sf-tp-more" href="{{ safe_href(item.route) }}">Read more {{ icon('arrow-right', cls='sf-tp-icon--sm') }}</a>
|
||
</div>
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
{{ tp_pages(pagination, pg_root, icon) }}
|
||
</div>
|
||
<aside class="sf-tp-news-aside">
|
||
<div class="sf-tp-aside-box" data-reveal><h3>Latest posts</h3>{% for p in listing[:5] %}<p><a href="{{ safe_href(p.route) }}">{{ p.title }}</a></p>{% endfor %}</div>
|
||
<div class="sf-tp-aside-box" data-reveal style="--reveal-i: 1"><h3>Popular reads</h3><ol>{% for p in listing[:5] %}<li><a href="{{ safe_href(p.route) }}">{{ p.title }}</a></li>{% endfor %}</ol></div>
|
||
<div class="sf-tp-aside-box" data-reveal style="--reveal-i: 2"><h3>Categories</h3><p>All news <strong>{{ groups['post']|length }}</strong></p></div>
|
||
</aside>
|
||
</div></div></section>
|
||
{% else %}
|
||
{% set tp_kind = listing[0].type %}
|
||
{% set tp_show_side = categories and tp_kind in ('product', 'vehicle') %}
|
||
<section class="section sf-tp-catalogue-section"><div class="wrap{{ ' sf-tp-catalogue-layout' if tp_show_side else ' sf-tp-catalogue-single' }}">
|
||
{% if tp_show_side %}
|
||
<aside class="sf-tp-side-panel" aria-label="Product categories" data-reveal>
|
||
<h3>Product categories</h3>
|
||
<a class="sf-tp-side-link{{ ' sf-tp-side-link--active' if route == '/products/' }}" href="/products/">All products</a>
|
||
{% for cat in categories[:12] %}<a class="sf-tp-side-link{{ ' sf-tp-side-link--active' if cat.url == route }}" href="{{ cat.url }}">{{ cat.name }}</a>{% endfor %}
|
||
</aside>
|
||
{% endif %}
|
||
<div class="sf-tp-catalogue-main">
|
||
<div class="sf-tp-section-head" data-reveal>
|
||
<div><span class="eyebrow">{{ tp_eyebrow }}</span><h2>{{ page.title }}</h2></div>
|
||
{# 导语不与 hero 重复:主列头行改条目/页数计数(cms SectionHead 的
|
||
intro 位在平台无独立文案来源,用真实数据代替装饰性复述) #}
|
||
<p class="sf-tp-intro">{{ listing|length }} items{% if pagination and pagination.pages > 1 %} · {{ pagination.pages }} pages{% endif %}</p>
|
||
</div>
|
||
{{ filters() }}
|
||
<div class="pro-filter-scope sf-tp-card-grid sf-tp-card-grid--listing">
|
||
{% for item in listing %}
|
||
<a class="sf-tp-card pro-card" href="{{ safe_href(item.route) }}" data-reveal style="--reveal-i: {{ loop.index0 % 4 }}" data-brand="{{ fields_of(item).get('brand','') }}" data-category="{{ fields_of(item).get('category','') }}">
|
||
<div class="sf-tp-card-media">{{ cover(item, tp_kind) }}</div>
|
||
<div class="sf-tp-card-body">
|
||
<h3>{{ item.title }}</h3>
|
||
<p>{{ (item.summary or 'Published specifications available on request.').split('.')[0] }}.</p>
|
||
<span class="sf-tp-more">View details {{ icon('arrow-right', cls='sf-tp-icon--sm') }}</span>
|
||
</div>
|
||
</a>
|
||
{% endfor %}
|
||
</div>
|
||
{{ tp_pages(pagination, pg_root, icon) }}
|
||
</div>
|
||
</div></section>
|
||
{% endif %}
|
||
{% else %}
|
||
<section class="section sf-tp-listing-section"><div class="wrap">
|
||
<div class="sf-tp-listing-empty" data-reveal>
|
||
<h2>Nothing published here yet</h2>
|
||
<p>Content added to this section in the admin console appears here automatically.</p>
|
||
</div>
|
||
</div></section>
|
||
{% endif %}
|