From 9ea8369e2b4a112757172b693655e605dd387885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Fri, 23 Oct 2026 14:06:15 +0800 Subject: [PATCH] feat: port cms listing layout into trade-pro (kind-branched cards/filters/pagination) Task 10c. listing.html (category.html now includes it) replaces the thin pro-inner-hero variant with the cms list designs: - product-like listings (product/vehicle/brand/case/service) get the products/[...page].astro form: ported PageHero, 260px sticky category side panel, 3-col sf-tp-card grid reusing the 10a card components - post-like listings (/news/, /post/) get the news/[...page].astro form: 1fr+310px two-col layout, horizontal news rows (290px media + tag/ title/summary/Read more), three aside boxes (Latest/Popular/count) - pagination is the cms Pagination window (first/last + current-2..+2, ellipsis collapse) with arrow-left/arrow-right SVG icons and builder-consistent URLs (page 1 = section root) - platform filter contract kept: .pro-filter-panel stays the immediate previous sibling of .pro-filter-scope (pro.js previousElementSibling lookup); cards/rows carry .pro-card + data-brand/data-category - empty sections hide filters/grid/pagination behind a friendly empty state (noindex scaffold pages) Test: test_trade_pro_listing_layout_structure drives a real build with 105 imported products + 3 posts, asserting both branches, static pagination on /categories/pumps/ pages 1-2, contract adjacency, and no bare arrow glyphs. Co-Authored-By: Claude Code --- tests/test_theme_registry.py | 103 +++++++++++++++++++++ themes/trade-pro/pages/category.html | 6 +- themes/trade-pro/pages/listing.html | 133 ++++++++++++++++++++++++++- themes/trade-pro/pro.css | 88 ++++++++++++++++++ 4 files changed, 326 insertions(+), 4 deletions(-) diff --git a/tests/test_theme_registry.py b/tests/test_theme_registry.py index 069e93d..c5f534e 100644 --- a/tests/test_theme_registry.py +++ b/tests/test_theme_registry.py @@ -231,6 +231,109 @@ def test_trade_pro_product_layout_structure(factory): assert len(soup.find_all('h1')) == 1 +def test_trade_pro_listing_layout_structure(factory): + # Task 10c:listing.html(category.html 同构复用)移植 cms 列表设计后的结构契约。 + # 设计源 D:/project/cms/runtimes/astro-site-runtime/src/pages/[locale]/ + # products/[...page].astro(260px 分类侧栏 + 3 列卡片栅格 + 窗口式分页) + # news/[...page].astro(310px 侧栏 + 横向新闻行列表 + 侧栏盒组) + # 平台契约保留:[data-pro-filter-search] 筛选、.pro-filter-panel 必须是 + # .pro-filter-scope 的紧邻前兄弟(pro.js previousElementSibling 定位)、 + # 卡片/行携带 .pro-card + data-brand/data-category(pro.js 过滤数据源)。 + # 分页器:cms 窗口式页码(首尾 + 当前±2),上一页/下一页为 SVG 箭头。 + import io + import re + sid = site(factory) + assert factory.post(f'/api/factory/sites/{sid}/bootstrap').status_code == 200 + # 105 个产品:/categories/pumps/ 走 render_paginated(LIST_PAGE_SIZE=100, + # 两页真实静态分页路由),比 101 次 /entries POST 便宜得多 + products = [{'type': 'product', 'title': f'Bulk Pump {i:03d}', + 'summary': f'Bulk pump model {i:03d} for export.', + 'meta': {'fields': {'brand': 'Quanta', 'category': 'Pumps'}}} + for i in range(105)] + posts = [{'type': 'post', 'title': f'Worksite Report {i}', + 'summary': f'Installation report from worksite {i}.'} for i in range(3)] + payload = json.dumps(products + posts).encode('utf-8') + imported = factory.post(f'/api/sites/{sid}/import', + files={'file': ('listing-batch.json', io.BytesIO(payload), 'application/json')}) + assert imported.status_code == 200, imported.text[:300] + assert imported.json()['imported'] == 108, imported.json() + assert factory.put(f'/api/factory/sites/{sid}/theme', json={'theme': 'trade-pro', 'version': '1.0.0'}).status_code == 200 + bid = factory.post(f'/api/sites/{sid}/builds', json={}).json()['version'] + + # ── 产品分支(/products/):hero + 分类侧栏 + 3 列卡片栅格 + 筛选契约 ── + html = factory.get(f'/preview/{sid}/{bid}/products/').text + soup = BeautifulSoup(html, 'html.parser') + assert soup.select_one('section.sf-tp-page-hero .sf-tp-page-hero-copy[data-reveal] h1') + assert len(soup.find_all('h1')) == 1 + side = soup.select_one('.sf-tp-side-panel') + assert side is not None and side.find('h3') + assert side.select_one('a.sf-tp-side-link[href$="/products/"]') + assert side.select_one(f'a.sf-tp-side-link[href$="/categories/pumps/"]') + # 筛选契约:面板在 scope 紧邻前兄弟位(pro.js 依赖),卡片带过滤数据 + assert soup.select_one('.pro-filter-panel [data-pro-filter-search]') + scope = soup.select_one('.pro-filter-scope') + assert scope is not None and scope.find_previous_sibling(class_='pro-filter-panel') is not None + cards = soup.select('.sf-tp-card-grid--listing a.sf-tp-card.pro-card') + assert len(cards) == 105 + assert cards[0].get('data-brand') == 'Quanta' and cards[0].get('data-category') == 'Pumps' + assert cards[0].select_one('.sf-tp-card-media') and cards[0].select_one('.sf-tp-more svg') + + # ── 分页(派生分类页走构建期静态分页):窗口页码 + SVG 箭头 ── + cat1 = factory.get(f'/preview/{sid}/{bid}/categories/pumps/') + assert cat1.status_code == 200, cat1.text[:200] + s1 = BeautifulSoup(cat1.text, 'html.parser') + nav1 = s1.select_one('nav.sf-tp-pagination') + assert nav1 is not None + assert len(s1.select('.sf-tp-card-grid--listing .sf-tp-card')) == 100 + active = nav1.select_one('.sf-tp-page-num.is-active[aria-current="page"]') + assert active is not None and active.get_text(strip=True) == '1' + nxt = nav1.select_one('a[rel="next"]') + assert nxt is not None and nxt.get('href', '').endswith('/categories/pumps/page/2/') + assert nxt.select_one('svg') is not None # 下一页箭头为 SVG + assert nav1.select_one('a[rel="prev"]') is None # 首页无上一页链接 + assert nav1.select_one('.sf-tp-page-arrow.is-disabled') is not None # 禁用态占位 + cat2 = factory.get(f'/preview/{sid}/{bid}/categories/pumps/page/2/') + assert cat2.status_code == 200, cat2.text[:200] + s2 = BeautifulSoup(cat2.text, 'html.parser') + nav2 = s2.select_one('nav.sf-tp-pagination') + assert len(s2.select('.sf-tp-card-grid--listing .sf-tp-card')) == 5 + prev = nav2.select_one('a[rel="prev"]') + assert prev is not None and prev.get('href', '').endswith('/categories/pumps/') + assert prev.select_one('svg') is not None + assert nav2.select_one('a[rel="next"]') is None + assert nav2.select_one('.sf-tp-page-num.is-active').get_text(strip=True) == '2' + # 页码链接回根路由(第 1 页 = 栏目根,与 cms Pagination 一致) + assert nav2.select_one('a.sf-tp-page-num[href$="/categories/pumps/"]') + for nav in (nav1, nav2): + assert not re.search(r'[‹›→←]', nav.get_text()) # 无裸箭头字符 + + # ── 文章分支(/news/):新闻行列表 + 310px 侧栏盒组,筛选契约同样保留 ── + news = factory.get(f'/preview/{sid}/{bid}/news/').text + nsoup = BeautifulSoup(news, 'html.parser') + assert len(nsoup.find_all('h1')) == 1 + assert nsoup.select_one('.pro-filter-panel [data-pro-filter-search]') + nscope = nsoup.select_one('.pro-filter-scope') + assert nscope is not None and nscope.find_previous_sibling(class_='pro-filter-panel') is not None + rows = nsoup.select('.sf-tp-news-row.pro-card[data-reveal]') + assert len(rows) == 3 + row = rows[0] + assert row.select_one('.sf-tp-news-row-media img, .sf-tp-news-row-media .pro-art') + assert row.select_one('h3 a[href$="/post/worksite-report-0/"]') + assert row.select_one('a.sf-tp-more svg') # Read more → SVG 箭头 + aside = nsoup.select_one('.sf-tp-news-aside') + assert aside is not None + assert len(aside.select('.sf-tp-aside-box')) == 3 # Latest / Popular / Categories + assert aside.select_one('.sf-tp-aside-box a[href$="/post/worksite-report-0/"]') + assert not nsoup.select_one('.sf-tp-side-panel') # 文章分支不带产品分类侧栏 + assert len(nsoup.select('nav.sf-tp-pagination')) == 0 # 3 条不分页,整段隐藏 + + # ── 空列表(/cases/ 无案例):整段隐藏换友好空态,不渲染筛选 ── + cases = factory.get(f'/preview/{sid}/{bid}/cases/').text + assert 'sf-tp-listing-empty' in cases + assert '[data-pro-filter-search]' not in cases + assert 'nav.sf-tp-pagination' not in cases + + def test_frontend_entries_include_trade_pro(): from pathlib import Path base = Path(__file__).resolve().parent.parent diff --git a/themes/trade-pro/pages/category.html b/themes/trade-pro/pages/category.html index 57e1a27..4fa0931 100644 --- a/themes/trade-pro/pages/category.html +++ b/themes/trade-pro/pages/category.html @@ -1,2 +1,4 @@ -
DISCOVER / {{'CATEGORY' if kind=='category' else 'COLLECTION'}}

{{page.title}}

{{page.summary or 'Browse all published content in this section.'}}

Home/{{page.title}}
-
{{ filters() }}
{{grid(listing,'catalog')}}
{% if pagination and pagination.pages>1 %}{% endif %}
+{# 派生分类页与 listing 同构(Task 10c 起共用同一列表布局; + 分类上下文由 theme_pro.select_listing 按 page.category 过滤产品组, + 分页来自构建期 render_paginated 的 _pagination)。 #} +{% include 'pages/listing.html' %} diff --git a/themes/trade-pro/pages/listing.html b/themes/trade-pro/pages/listing.html index 57e1a27..10dcb30 100644 --- a/themes/trade-pro/pages/listing.html +++ b/themes/trade-pro/pages/listing.html @@ -1,2 +1,131 @@ -
DISCOVER / {{'CATEGORY' if kind=='category' else 'COLLECTION'}}

{{page.title}}

{{page.summary or 'Browse all published content in this section.'}}

Home/{{page.title}}
-
{{ filters() }}
{{grid(listing,'catalog')}}
{% if pagination and pagination.pages>1 %}{% endif %}
+{# 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 相关产品一致)。 + 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 栏目占位页的空集)。 #} +{% set tp_post_like = (listing and listing[0].type == 'post') or ('news' in route or 'post' in route) %} +{% set tp_eyebrow = 'NEWS & INSIGHTS' if tp_post_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 '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 %} + +{% endif %} +{%- endmacro %} +
+ {% if tp_hero_img %}{% endif %} + +
+
+ {{ tp_eyebrow }} +

{{ page.title }}

+

{{ tp_lead }}

+
+
+
+{% if listing %} +{% if tp_post_like %} +
+
+ {{ filters() }} +
+ {% for item in listing %} + + {% endfor %} +
+ {{ tp_pages(pagination, pg_root, icon) }} +
+ +
+{% else %} +{% set tp_kind = listing[0].type %} +{% set tp_show_side = categories and tp_kind in ('product', 'vehicle') %} +
+ {% if tp_show_side %} + + {% endif %} +
+
+
{{ tp_eyebrow }}

{{ page.title }}

+

{{ tp_lead }}

+
+ {{ filters() }} + + {{ tp_pages(pagination, pg_root, icon) }} +
+
+{% endif %} +{% else %} +
+
+

Nothing published here yet

+

Content added to this section in the admin console appears here automatically.

+
+
+{% endif %} diff --git a/themes/trade-pro/pro.css b/themes/trade-pro/pro.css index 86899c9..68ec328 100644 --- a/themes/trade-pro/pro.css +++ b/themes/trade-pro/pro.css @@ -273,3 +273,91 @@ /* 询盘蜜罐:隐藏但不移出 DOM(pro-base 的 .pro-inquiry 作用域规则不覆盖新表单容器) */ .sf-theme-trade-pro .pro-honeypot{position:absolute;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none} + +/* ── Task 10c:listing 列表页(products/news 列表移植)── + 源 src/pages/[locale]/products/[...page].astro(PageHero / 260px 分类 + 侧栏 / CardGrid cols-3 / Pagination)与 news/[...page].astro + (1fr+310px 双栏 / news-row 横向行 / aside-box 侧栏盒)。 */ + +/* 内页 PageHero:浅品牌横幅;有封面时叠图 + brand-soft 渐变罩(无图纯渐变) */ +.sf-theme-trade-pro .sf-tp-page-hero{position:relative;display:flex;align-items:center;overflow:hidden;min-height:calc(var(--sf-tp-space-24) * 3.5);padding-block:var(--sf-tp-space-12) calc(var(--sf-tp-space-10) + var(--sf-tp-space-1));background:linear-gradient(115deg,var(--sf-tp-color-surface-brand-soft),color-mix(in srgb,var(--sf-tp-color-surface-brand-soft) 26%,var(--sf-tp-color-surface-default)));} +.sf-theme-trade-pro .sf-tp-page-hero-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:calc(var(--sf-tp-z-base) - 1)} +.sf-theme-trade-pro .sf-tp-page-hero-overlay{position:absolute;inset:0;z-index:var(--sf-tp-z-base);background:linear-gradient(90deg,var(--sf-tp-color-surface-brand-soft) 0,var(--sf-tp-color-surface-brand-soft) 44%,color-mix(in srgb,var(--sf-tp-color-surface-brand-soft) 74%,transparent) 53%,color-mix(in srgb,var(--sf-tp-color-surface-brand-soft) 8%,transparent) 68%)} +.sf-theme-trade-pro .sf-tp-page-hero-copy{position:relative;z-index:var(--sf-tp-z-raised);width:min(calc(var(--sf-tp-container-content-max) - var(--sf-tp-space-16)),56%)} +.sf-theme-trade-pro .sf-tp-page-hero-copy h1{margin-block:var(--sf-tp-space-2) var(--sf-tp-space-3)} +.sf-theme-trade-pro .sf-tp-page-hero-lead{color:var(--sf-tp-color-text-secondary);font-size:var(--sf-tp-font-size-lg);line-height:var(--sf-tp-line-height-relaxed);max-width:var(--sf-tp-container-content-max);margin-block-end:var(--sf-tp-space-6)} + +/* 产品目录布局:260px 分类侧栏 + 主列(cms layout-sidebar / side-panel) */ +.sf-theme-trade-pro .sf-tp-catalogue-layout{display:grid;grid-template-columns:260px 1fr;gap:var(--sf-tp-space-8)} +.sf-theme-trade-pro .sf-tp-catalogue-single{display:block} +.sf-theme-trade-pro .sf-tp-side-panel{align-self:start;padding:var(--sf-tp-space-5);border-radius:var(--sf-tp-card-border-radius);background:var(--sf-tp-color-surface-muted);position:sticky;top:calc(var(--sf-tp-space-16) + var(--sf-tp-space-16))} +.sf-theme-trade-pro .sf-tp-side-panel h3{font-size:var(--sf-tp-font-size-sm);letter-spacing:var(--sf-tp-letter-spacing-wide);text-transform:uppercase;margin-block-end:var(--sf-tp-space-2)} +.sf-theme-trade-pro .sf-tp-side-link{display:block;padding:calc(var(--sf-tp-space-4) - var(--sf-tp-space-1)) var(--sf-tp-space-4);border-block-end:var(--sf-tp-border-width-hairline) solid var(--sf-tp-color-border-default);font-weight:var(--sf-tp-font-weight-semibold);color:var(--sf-tp-color-text-secondary);font-size:var(--sf-tp-font-size-sm)} +.sf-theme-trade-pro .sf-tp-side-link:hover{color:var(--sf-tp-color-text-brand)} +.sf-theme-trade-pro .sf-tp-side-link--active{color:var(--sf-tp-color-text-on-brand);background:var(--sf-tp-color-brand-primary);border-radius:var(--sf-tp-radius-xs)} +/* 列表栅格 = cms cols-3(主列比首页窄);双类共存时显式压过 Phase 2 的 + .pro-card 平台着色(更高特异性,与文件顺序无关) */ +.sf-theme-trade-pro .sf-tp-card-grid--listing{grid-template-columns:repeat(3,minmax(0,1fr))} +.sf-theme-trade-pro .sf-tp-card.pro-card{background:var(--sf-tp-color-surface-default);border:var(--sf-tp-border-width-hairline) solid var(--sf-tp-color-border-subtle);border-radius:var(--sf-tp-card-border-radius)} +/* pro.js 零结果空态注入的 .pro-filter-empty:栅格内整行展示 */ +.sf-theme-trade-pro .pro-filter-scope>.pro-filter-empty{grid-column:1/-1;padding:var(--sf-tp-space-6);border:var(--sf-tp-border-width-hairline) dashed var(--sf-tp-color-border-default);color:var(--sf-tp-color-text-muted)} + +/* 新闻分支:1fr + 310px 双栏;行 = 290px 封面 + 文本列,行间发丝线分隔 */ +.sf-theme-trade-pro .sf-tp-news-layout{display:grid;grid-template-columns:1fr 310px;gap:calc(var(--sf-tp-space-8) + var(--sf-tp-space-2))} +.sf-theme-trade-pro .sf-tp-news-row{display:grid;grid-template-columns:290px 1fr;gap:var(--sf-tp-space-6);padding-block-end:var(--sf-tp-space-6);margin-block-end:var(--sf-tp-space-6);border-block-end:var(--sf-tp-border-width-hairline) solid var(--sf-tp-color-border-default)} +/* 行也是 pro-card(pro.js 过滤钩子):还原为无框行形态,压过 Phase 2 着色 */ +.sf-theme-trade-pro .sf-tp-news-row.pro-card{display:grid;background:transparent;border:0;border-radius:0;overflow:visible;box-shadow:none} +.sf-theme-trade-pro .sf-tp-news-row.pro-card:hover{transform:none;box-shadow:none} +.sf-theme-trade-pro .sf-tp-news-row .sf-tp-card-media{border-radius:var(--sf-tp-radius-sm);background:var(--sf-tp-color-surface-muted)} +.sf-theme-trade-pro .sf-tp-news-row:hover .sf-tp-card-media img{transform:scale(1.04)} +.sf-theme-trade-pro .sf-tp-news-body .sf-tp-tag{margin-block-end:var(--sf-tp-space-2)} +.sf-theme-trade-pro .sf-tp-news-body h3{font-size:var(--sf-tp-font-size-2xl);margin-block:var(--sf-tp-space-2)} +.sf-theme-trade-pro .sf-tp-news-body h3 a{color:inherit} +.sf-theme-trade-pro .sf-tp-news-body h3 a:hover{color:var(--sf-tp-color-text-brand)} +.sf-theme-trade-pro .sf-tp-news-body p{color:var(--sf-tp-color-text-muted);font-size:var(--sf-tp-font-size-sm);margin-block-end:var(--sf-tp-space-3)} +/* 侧栏三盒(cms aside-box):灰底圆角卡 + 右浮计数 */ +.sf-theme-trade-pro .sf-tp-aside-box{background:var(--sf-tp-color-surface-muted);border-radius:var(--sf-tp-card-border-radius);padding:calc(var(--sf-tp-space-5) + var(--sf-tp-space-1));margin-block-end:var(--sf-tp-space-4)} +.sf-theme-trade-pro .sf-tp-aside-box h3{font-size:var(--sf-tp-font-size-md);margin-block-end:var(--sf-tp-space-3)} +.sf-theme-trade-pro .sf-tp-aside-box p{margin-block-end:var(--sf-tp-space-3);font-size:var(--sf-tp-font-size-sm)} +.sf-theme-trade-pro .sf-tp-aside-box p:last-child{margin-block-end:0} +.sf-theme-trade-pro .sf-tp-aside-box a:hover{color:var(--sf-tp-color-text-brand)} +.sf-theme-trade-pro .sf-tp-aside-box strong{float:inline-end;color:var(--sf-tp-color-text-brand)} +.sf-theme-trade-pro .sf-tp-aside-box ol{padding-inline-start:var(--sf-tp-space-6);margin-block-end:0} +.sf-theme-trade-pro .sf-tp-aside-box li{margin-block:var(--sf-tp-space-4);font-size:var(--sf-tp-font-size-sm);color:var(--sf-tp-color-text-muted)} +.sf-theme-trade-pro .sf-tp-aside-box li:last-child{margin-block-end:0} + +/* 分页器(cms Pagination):窗口页码 + SVG 箭头,居中可换行 */ +.sf-theme-trade-pro .sf-tp-pagination{display:flex;justify-content:center;align-items:center;gap:var(--sf-tp-space-1);margin-block-start:var(--sf-tp-space-10);flex-wrap:wrap} +.sf-theme-trade-pro .sf-tp-page-num,.sf-theme-trade-pro .sf-tp-page-arrow{display:inline-grid;place-items:center;min-width:36px;height:36px;padding-inline:6px;border-radius:var(--sf-tp-radius-sm);border:var(--sf-tp-border-width-hairline) solid var(--sf-tp-color-border-subtle);color:var(--sf-tp-color-text-secondary);font-size:var(--sf-tp-font-size-sm);transition:all var(--sf-tp-duration-fast) var(--sf-tp-easing-standard)} +.sf-theme-trade-pro .sf-tp-page-num:hover,.sf-theme-trade-pro .sf-tp-page-arrow:hover:not(.is-disabled){border-color:var(--sf-tp-color-border-brand);color:var(--sf-tp-color-text-brand)} +.sf-theme-trade-pro .sf-tp-page-num.is-active{background:var(--sf-tp-color-brand-primary);border-color:var(--sf-tp-color-brand-primary);color:var(--sf-tp-color-text-on-brand);font-weight:var(--sf-tp-font-weight-semibold)} +.sf-theme-trade-pro .sf-tp-page-arrow.is-disabled{opacity:.35;cursor:default} +.sf-theme-trade-pro .sf-tp-page-dots{color:var(--sf-tp-color-text-muted);padding-inline:2px} + +/* 空列表友好空态(noindex 栏目占位页):居中提示,不渲染筛选/栅格/分页 */ +.sf-theme-trade-pro .sf-tp-listing-empty{text-align:center;padding:var(--sf-tp-space-12) var(--sf-tp-space-6);border:var(--sf-tp-border-width-hairline) dashed var(--sf-tp-color-border-default);border-radius:var(--sf-tp-card-border-radius);color:var(--sf-tp-color-text-muted)} +.sf-theme-trade-pro .sf-tp-listing-empty h2{color:var(--sf-tp-color-text-secondary);margin-block-end:var(--sf-tp-space-3)} + +/* ── listing 响应式(cms 断点:900 双栏堆叠 / 767 hero 竖版 / 620 紧凑)── */ +@media (max-width: 900px){ + .sf-theme-trade-pro .sf-tp-page-hero-copy{width:100%} + .sf-theme-trade-pro .sf-tp-catalogue-layout{grid-template-columns:1fr} + .sf-theme-trade-pro .sf-tp-side-panel{position:static;display:grid;grid-template-columns:repeat(2,1fr);gap:0 var(--sf-tp-space-4)} + .sf-theme-trade-pro .sf-tp-news-layout{grid-template-columns:1fr} + .sf-theme-trade-pro .sf-tp-news-row{grid-template-columns:240px 1fr} +} +@media (max-width: 767px){ + .sf-theme-trade-pro .sf-tp-page-hero{min-height:auto;padding-block:calc(var(--sf-tp-space-12) - var(--sf-tp-space-4)) calc(var(--sf-tp-space-8) + var(--sf-tp-space-2))} +} +@media (max-width: 620px){ + .sf-theme-trade-pro .sf-tp-page-hero{padding-block:calc(var(--sf-tp-space-10) + var(--sf-tp-space-4)) calc(var(--sf-tp-space-4) + var(--sf-tp-space-2));align-items:flex-start} + .sf-theme-trade-pro .sf-tp-page-hero-copy{width:62%} + .sf-theme-trade-pro .sf-tp-page-hero-copy h1{font-size:28px;line-height:1.05;margin-block-end:8px} + .sf-theme-trade-pro .sf-tp-page-hero-lead{font-size:11px;line-height:1.35;margin-block-end:12px} + .sf-theme-trade-pro .sf-tp-side-panel{display:block} + .sf-theme-trade-pro .sf-tp-news-row{grid-template-columns:1fr} + .sf-theme-trade-pro .sf-tp-news-row .sf-tp-card-media{aspect-ratio:16/9} + .sf-theme-trade-pro .sf-tp-news-body h3{font-size:var(--sf-tp-font-size-lg)} + .sf-theme-trade-pro .sf-tp-news-aside{margin-block-start:var(--sf-tp-space-8)} + .sf-theme-trade-pro .sf-tp-page-num,.sf-theme-trade-pro .sf-tp-page-arrow{min-width:32px;height:32px} +}