feat: port cms product detail layout into trade-pro (gallery/specs/inquiry)
Rewrite pages/product.html from the 3-row skeleton to the cms [slug].astro design: breadcrumb -> gallery (main + scrolling thumbs, reusing the platform pro.js data-gallery-image hooks, zero new JS) + summary column (eyebrow/h1/lead/brand+model/actions) -> flattened Product details panel -> Specifications table from fields (hidden when empty) -> related products on the home card grid -> inquiry section (left contact info, right external form only when form_endpoint is configured, CTA to /contact/ otherwise). Component CSS lands in pro.css under .sf-theme-trade-pro with 900/620 breakpoints; stale .pro-specs tint rules removed (other themes keep their pro-base skeleton); cross-theme skeleton assertion in test_theme_pro_v3.py now excludes trade-pro; new test_trade_pro_product_layout_structure covers the build output. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,10 @@ def test_complete_theme_families_have_distinct_markup(factory):
|
||||
assert soup.find('footer',class_='pro-footer')
|
||||
assert len(soup.find_all('h1'))==1,(theme,page_type,len(soup.find_all('h1')))
|
||||
assert 'pro-base.css' in r.text and 'pro.js' in r.text
|
||||
if page_type=='product':assert soup.select_one('.pro-product-layout .pro-specs')
|
||||
# trade-pro 的产品详情自 Task 10b 起为 cms 移植布局(sf-tp-product-*),
|
||||
# 骨架断言只覆盖其余沿用 pro-base 骨架的主题(test_theme_registry.py
|
||||
# 的 test_trade_pro_product_layout_structure 覆盖 trade-pro 侧)。
|
||||
if page_type=='product' and theme!='trade-pro':assert soup.select_one('.pro-product-layout .pro-specs')
|
||||
if page_type=='listing':assert soup.select_one('[data-pro-filter-search]')
|
||||
if page_type=='article':assert soup.select_one('.pro-article-hero')
|
||||
main.append(factory.get(f'/api/factory/themes/{theme}/preview?page_type=home').text)
|
||||
|
||||
@@ -178,6 +178,56 @@ def test_trade_pro_home_layout_structure(factory):
|
||||
assert len(soup.find_all('h1')) == 1
|
||||
|
||||
|
||||
def test_trade_pro_product_layout_structure(factory):
|
||||
# Task 10b:product.html 移植 cms 产品详情设计([slug].astro)后的结构契约。
|
||||
# 构建流(/preview/ HTTP)+ 带 fields/gallery 的产品条目驱动断言:
|
||||
# 画廊(主图封面 + data-gallery-image 缩略图,复用平台 pro.js 增强钩子)、
|
||||
# 标题区、参数表渲染 fields 键值(品牌等管理字段不进表)、询盘 CTA、
|
||||
# 相关产品复用首页卡片网格;旧骨架 .pro-product-layout/.pro-specs 无残留。
|
||||
sid = site(factory)
|
||||
assert factory.post(f'/api/factory/sites/{sid}/bootstrap').status_code == 200
|
||||
made = factory.post(f'/api/sites/{sid}/entries', json={
|
||||
'type': 'product', 'title': 'Hydraulic Pump HP-200', 'route': '/product/hp-200/',
|
||||
'cover': 'https://imgs.example/hp-200.jpg', 'summary': 'High-pressure hydraulic pump for export.',
|
||||
'meta': {'fields': {'brand': 'Quanta', 'range': '500 km', 'pressure': '210 bar',
|
||||
'gallery': ['https://imgs.example/hp-200.jpg', 'https://imgs.example/hp-200-b.jpg']}}})
|
||||
assert made.status_code in (200, 201), made.text
|
||||
other = factory.post(f'/api/sites/{sid}/entries', json={
|
||||
'type': 'product', 'title': 'Companion Valve', 'route': '/product/valve/',
|
||||
'summary': 'Matching control valve.'})
|
||||
assert other.status_code in (200, 201), other.text
|
||||
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']
|
||||
r = factory.get(f'/preview/{sid}/{bid}/product/hp-200/')
|
||||
assert r.status_code == 200, r.text[:400]
|
||||
html = r.text
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
# 画廊:主图为封面;多图时缩略图带 data-gallery-image(pro.js 切换/灯箱钩子)
|
||||
media = soup.select_one('.sf-tp-product-grid .sf-tp-product-media img')
|
||||
assert media is not None and 'hp-200.jpg' in media.get('src', '')
|
||||
thumbs = soup.select('.sf-tp-product-thumbs [data-gallery-image]')
|
||||
assert len(thumbs) == 2
|
||||
# 标题区(唯一 h1)+ 询盘 CTA 指向联系页(/preview/ 响应会把根相对
|
||||
# href 重写为 /preview/{sid}/{bid}/contact/,故用后缀匹配)
|
||||
assert soup.select_one('.sf-tp-product-summary h1.sf-tp-product-title')
|
||||
assert soup.select_one('.sf-tp-product-actions a.sf-tp-btn[href$="/contact/"]')
|
||||
# 参数表:fields 键值渲染(下划线转空格 + 首字母大写),空表整段隐藏
|
||||
spec_table = soup.select_one('table.sf-tp-spec-table')
|
||||
assert spec_table is not None
|
||||
spec_text = spec_table.get_text()
|
||||
assert 'Pressure' in spec_text and '210 bar' in spec_text and '500 km' in spec_text
|
||||
assert 'Quanta' not in spec_text # brand 等管理字段不进参数表
|
||||
# 相关产品:复用 sf-tp-card-grid;自身不出现在相关列表
|
||||
cards = soup.select('.sf-tp-card-grid .sf-tp-card')
|
||||
assert cards and any('Companion Valve' in c.get_text() for c in cards)
|
||||
assert all('Hydraulic Pump' not in c.get_text() for c in cards)
|
||||
# 询盘板块存在(左联系信息 + 右 CTA/表单位)
|
||||
assert soup.select_one('.sf-tp-product-quote') is not None
|
||||
# 旧骨架清理:无 .pro-product-layout/.pro-specs 残留类依赖
|
||||
assert 'pro-product-layout' not in html and 'pro-specs' not in html
|
||||
assert len(soup.find_all('h1')) == 1
|
||||
|
||||
|
||||
def test_frontend_entries_include_trade_pro():
|
||||
from pathlib import Path
|
||||
base = Path(__file__).resolve().parent.parent
|
||||
|
||||
@@ -1,3 +1,142 @@
|
||||
<section class="pro-section pro-product-layout"><div class="pro-wrap"><div class="pro-breadcrumb"><a href="/">Home</a><span>/</span><a href="/products/">Products</a><span>/</span><strong>{{page.title}}</strong></div>
|
||||
<div class="pro-product-hero"><div class="pro-product-gallery"><div class="pro-product-media">{{cover(page,'product')}}</div>{% if gallery|length>1 %}<div class="pro-gallery-thumbs" aria-label="Product image gallery">{% for photo in gallery %}<button type="button" data-gallery-image="{{photo}}" aria-label="View image {{loop.index}}"><img src="{{photo}}" alt="{{page.title}} · image {{loop.index}}" loading="lazy"></button>{% endfor %}</div>{% endif %}</div><div class="pro-product-summary"><span class="pro-eyebrow">PRODUCT DETAILS / {{fields.get('brand') or 'CATALOGUE'}}</span><h1>{{page.title}}</h1><p>{{page.summary or 'Detailed product information and specifications.'}}</p>{% if fields.get('brand') %}<p class="pro-product-brand">Brand · {{fields.get('brand')}}</p>{% endif %}<a class="sf-tp-btn" href="/contact/">Inquire about this product <span>{{ icon('arrow-right') }}</span></a></div></div>
|
||||
<div class="pro-detail-layout"><div><span class="pro-eyebrow">OVERVIEW</span><h2>Product overview</h2>{{body_copy(content,page)}}</div><div><span class="pro-eyebrow">SPECIFICATIONS</span><h2>Specifications</h2><div class="pro-specs">{% for row in specs %}<div><span>{{row.label}}</span><strong>{{row.value}}</strong></div>{% else %}<p class="pro-muted">Specifications have not been filled in yet.</p>{% endfor %}</div></div></div></div></section>
|
||||
{# 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>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/* trade-pro pro.css — component layer.
|
||||
Phase 2 shipped a skeleton tint layer; Phase 3 Task 10a prepends the ported
|
||||
cms home components. Sources (scoped CSS + dist pixel baseline
|
||||
D:/project/cms/runtimes/astro-site-runtime/dist/hesupipe/en/index.html):
|
||||
cms home components; Task 10b adds the product detail components. Sources
|
||||
(scoped CSS + dist pixel baseline
|
||||
D:/project/cms/runtimes/astro-site-runtime/dist/hesupipe/en/):
|
||||
- src/pages/[locale]/index.astro (hero / trustbar / two-col about /
|
||||
applications band / section-head / card / feature / cta-band)
|
||||
- src/pages/[locale]/products/[slug].astro (product hero gallery /
|
||||
summary / panel titles / spec table / quote grid)
|
||||
- src/components/{PageHero,FeatureGrid,CardGrid,Cta,SectionHead}.astro
|
||||
Layer split with theme.css (= tokens + element baseline + shared utilities):
|
||||
pro.css owns the sf-tp-* component classes; markup composes on the
|
||||
@@ -101,10 +104,64 @@
|
||||
.sf-theme-trade-pro .sf-tp-cta h2{margin-block-end:var(--sf-tp-space-2);color:var(--sf-tp-color-text-inverse)}
|
||||
.sf-theme-trade-pro .sf-tp-cta p{color:color-mix(in srgb,var(--sf-tp-color-text-inverse) 83%,transparent);margin:0}
|
||||
|
||||
/* ── 产品详情(Task 10b,[slug].astro 移植):画廊 + 标题区 + 参数表 + 询盘 ── */
|
||||
.sf-theme-trade-pro .sf-tp-product-hero{padding-block:calc(var(--sf-tp-space-8) + var(--sf-tp-space-4)) var(--sf-tp-space-8)}
|
||||
.sf-theme-trade-pro .sf-tp-breadcrumb{display:flex;flex-wrap:wrap;align-items:center;color:var(--sf-tp-color-text-muted);font-size:var(--sf-tp-font-size-sm);margin-block-end:var(--sf-tp-space-6)}
|
||||
.sf-theme-trade-pro .sf-tp-breadcrumb>*+*::before{content:"›";margin-inline:var(--sf-tp-space-3) calc(var(--sf-tp-space-3) - var(--sf-tp-space-1));color:var(--sf-tp-color-neutral-400)}
|
||||
.sf-theme-trade-pro .sf-tp-breadcrumb a:hover{color:var(--sf-tp-color-text-link)}
|
||||
.sf-theme-trade-pro .sf-tp-breadcrumb-current{color:var(--sf-tp-color-text-secondary);font-weight:var(--sf-tp-font-weight-semibold)}
|
||||
.sf-theme-trade-pro .sf-tp-product-grid{display:grid;grid-template-columns:1.08fr 0.92fr;gap:calc(var(--sf-tp-space-12) + var(--sf-tp-space-4));align-items:center}
|
||||
/* grid 列最小宽归零:缩略图横滚依赖列宽被约束(cms 同款注释) */
|
||||
.sf-theme-trade-pro .sf-tp-product-gallery,.sf-theme-trade-pro .sf-tp-product-summary{min-width:0}
|
||||
/* 主图:cms product-main-image(4:3 + 圆角 + 浅底);覆盖 pro-product-media 的骨架固定高。
|
||||
.pro-product-media 类仅作 pro.js 画廊钩子(querySelector 定位主图)保留。 */
|
||||
.sf-theme-trade-pro .sf-tp-product-media{height:auto;aspect-ratio:var(--sf-tp-ratio-product-main);overflow:hidden;border-radius:var(--sf-tp-radius-sm);background:var(--sf-tp-color-surface-muted)}
|
||||
.sf-theme-trade-pro .sf-tp-product-media img{width:100%;height:100%;object-fit:cover;display:block}
|
||||
/* 缩略图:单行横滚 + scroll-snap(cms product-thumbs);激活态沿用 pro.js
|
||||
维护的 aria-pressed,品牌描边 + 焦点环 */
|
||||
.sf-theme-trade-pro .sf-tp-product-thumbs{display:flex;gap:var(--sf-tp-space-3);margin-block-start:var(--sf-tp-space-3);overflow-x:auto;scroll-behavior:smooth;scrollbar-width:thin;scroll-snap-type:x proximity;padding-block-end:var(--sf-tp-space-1)}
|
||||
.sf-theme-trade-pro .sf-tp-product-thumb{flex:0 0 120px;aspect-ratio:var(--sf-tp-ratio-landscape);padding:0;overflow:hidden;border:var(--sf-tp-border-width-medium) solid transparent;border-radius:var(--sf-tp-radius-xs);background:var(--sf-tp-color-surface-muted);cursor:pointer;scroll-snap-align:center}
|
||||
.sf-theme-trade-pro .sf-tp-product-thumb[aria-pressed="true"],.sf-theme-trade-pro .sf-tp-product-thumb:focus-visible{border-color:var(--sf-tp-color-border-brand);box-shadow:var(--sf-tp-shadow-focus-ring)}
|
||||
.sf-theme-trade-pro .sf-tp-product-thumb img{width:100%;height:100%;object-fit:cover;display:block}
|
||||
/* 标题区:产品名 h1 用 cms 量级(2xl,非巨型 display),导语 + 管理字段行 + CTA 对 */
|
||||
.sf-theme-trade-pro .sf-tp-product-title{font-size:var(--sf-tp-font-size-2xl);line-height:var(--sf-tp-line-height-snug);letter-spacing:var(--sf-tp-letter-spacing-normal);margin-block-end:var(--sf-tp-space-3)}
|
||||
.sf-theme-trade-pro .sf-tp-product-lead{color:var(--sf-tp-color-text-muted);line-height:var(--sf-tp-line-height-relaxed);margin-block-end:var(--sf-tp-space-4)}
|
||||
.sf-theme-trade-pro .sf-tp-product-meta{display:flex;flex-wrap:wrap;gap:var(--sf-tp-space-2) var(--sf-tp-space-5);color:var(--sf-tp-color-text-secondary);font-size:var(--sf-tp-font-size-sm);margin-block-end:var(--sf-tp-space-6)}
|
||||
.sf-theme-trade-pro .sf-tp-product-actions{display:flex;gap:var(--sf-tp-space-4);flex-wrap:wrap}
|
||||
.sf-theme-trade-pro .sf-tp-product-actions .sf-tp-btn{border-radius:var(--sf-tp-radius-full)}
|
||||
/* 详情面板标题:品牌色下划短线(cms .detail-section h2 层次) */
|
||||
.sf-theme-trade-pro .sf-tp-panel-title{display:inline-block;margin-block-end:var(--sf-tp-space-6);padding-block-end:var(--sf-tp-space-3);border-block-end:var(--sf-tp-border-width-strong) solid var(--sf-tp-color-border-brand)}
|
||||
/* 正文:迁移富文本(.pro-rich 基线之上补表格形态——WP 迁移正文常带规格表) */
|
||||
.sf-theme-trade-pro .sf-tp-product-body table{width:100%;border-collapse:collapse;table-layout:auto;font-size:var(--sf-tp-font-size-sm);margin-block:var(--sf-tp-space-6)}
|
||||
.sf-theme-trade-pro .sf-tp-product-body th,.sf-theme-trade-pro .sf-tp-product-body td{border:var(--sf-tp-border-width-hairline) solid var(--sf-tp-color-border-default);padding:var(--sf-tp-space-3) var(--sf-tp-space-4);text-align:start;vertical-align:middle;word-break:break-word}
|
||||
.sf-theme-trade-pro .sf-tp-product-body tbody tr:nth-child(even) td{background:var(--sf-tp-color-neutral-50)}
|
||||
/* 参数表(cms spec-table):键列灰底,横向可滚(宽表不溢出容器) */
|
||||
.sf-theme-trade-pro .sf-tp-table-scroll{overflow-x:auto}
|
||||
.sf-theme-trade-pro .sf-tp-spec-table{width:100%;border-collapse:collapse;font-size:var(--sf-tp-font-size-sm)}
|
||||
.sf-theme-trade-pro .sf-tp-spec-table th,.sf-theme-trade-pro .sf-tp-spec-table td{border:var(--sf-tp-border-width-hairline) solid var(--sf-tp-color-border-default);padding:var(--sf-tp-space-3) var(--sf-tp-space-4);text-align:start}
|
||||
.sf-theme-trade-pro .sf-tp-spec-table th{background:var(--sf-tp-color-surface-muted);font-weight:var(--sf-tp-font-weight-semibold);white-space:nowrap}
|
||||
/* 询盘板块(cms quote-grid 同构):左联系信息 + 右表单卡 */
|
||||
.sf-theme-trade-pro .sf-tp-quote-grid{display:grid;grid-template-columns:0.85fr 1.15fr;gap:calc(var(--sf-tp-space-12) + var(--sf-tp-space-4));align-items:start}
|
||||
.sf-theme-trade-pro .sf-tp-quote-info>p{color:var(--sf-tp-color-text-muted)}
|
||||
.sf-theme-trade-pro .sf-tp-quote-list{display:grid;gap:var(--sf-tp-space-5);margin-block-start:var(--sf-tp-space-6)}
|
||||
.sf-theme-trade-pro .sf-tp-quote-item{display:flex;align-items:center;gap:var(--sf-tp-space-4)}
|
||||
.sf-theme-trade-pro .sf-tp-quote-item .icon-circle{margin:0;flex-shrink:0}
|
||||
.sf-theme-trade-pro .sf-tp-quote-item strong{display:block;margin-block-end:var(--sf-tp-space-1)}
|
||||
.sf-theme-trade-pro .sf-tp-quote-item p{margin:0;color:var(--sf-tp-color-text-muted);font-size:var(--sf-tp-font-size-sm)}
|
||||
.sf-theme-trade-pro .sf-tp-quote-item a:hover{color:var(--sf-tp-color-text-link)}
|
||||
.sf-theme-trade-pro .sf-tp-quote-form{background:var(--sf-tp-color-surface-muted);padding:calc(var(--sf-tp-space-8) + var(--sf-tp-space-1));border-radius:var(--sf-tp-radius-sm)}
|
||||
.sf-theme-trade-pro .sf-tp-quote-form form{display:grid;gap:var(--sf-tp-space-4)}
|
||||
.sf-theme-trade-pro .sf-tp-quote-form .sf-tp-btn{justify-self:start}
|
||||
.sf-theme-trade-pro .sf-tp-quote-form textarea{min-height:110px}
|
||||
.sf-theme-trade-pro .sf-tp-quote-note{font-size:var(--sf-tp-font-size-xs);color:var(--sf-tp-color-text-muted);margin:0}
|
||||
|
||||
/* ── 响应式(断点随 cms 设计稿:900 栅格堆叠 / 767 hero 竖版 / 620 紧凑)── */
|
||||
@media (max-width: 900px){
|
||||
.sf-theme-trade-pro .sf-tp-applications-grid{grid-template-columns:1fr;gap:calc(var(--sf-tp-space-8) + var(--sf-tp-space-1))}
|
||||
.sf-theme-trade-pro .sf-tp-two-col{grid-template-columns:1fr}
|
||||
/* 产品详情:画廊/标题区上下堆叠;询盘左右并排收窄间距(cms 900 同款) */
|
||||
.sf-theme-trade-pro .sf-tp-product-grid{grid-template-columns:1fr;gap:var(--sf-tp-space-8)}
|
||||
.sf-theme-trade-pro .sf-tp-quote-grid{grid-template-columns:1fr;gap:var(--sf-tp-space-8)}
|
||||
.sf-theme-trade-pro .sf-tp-card-grid,.sf-theme-trade-pro .sf-tp-card-grid--news{grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||
.sf-theme-trade-pro .sf-tp-feature-grid,.sf-theme-trade-pro .sf-tp-trust-grid{grid-template-columns:1fr 1fr}
|
||||
.sf-theme-trade-pro .sf-tp-trust-item{border-inline-end:0;border-block-end:var(--sf-tp-border-width-hairline) solid var(--sf-tp-color-border-subtle)}
|
||||
@@ -166,6 +223,20 @@
|
||||
.sf-theme-trade-pro .sf-tp-cta h2{font-size:22px}
|
||||
.sf-theme-trade-pro .sf-tp-cta p{font-size:11px}
|
||||
.sf-theme-trade-pro .sf-tp-cta .sf-tp-btn{margin-block-start:12px}
|
||||
/* 产品详情 620 紧凑(cms 移动端设计稿):面包屑收紧、缩略图缩格、
|
||||
CTA 双列铺满、表单卡与参数表留白降档 */
|
||||
.sf-theme-trade-pro .sf-tp-product-hero{padding-block:14px 21px}
|
||||
.sf-theme-trade-pro .sf-tp-breadcrumb{margin-block-end:8px;font-size:10px}
|
||||
.sf-theme-trade-pro .sf-tp-product-grid{gap:14px}
|
||||
.sf-theme-trade-pro .sf-tp-product-media{aspect-ratio:16/9}
|
||||
.sf-theme-trade-pro .sf-tp-product-thumbs{gap:8px;margin-block-start:7px}
|
||||
.sf-theme-trade-pro .sf-tp-product-thumb{flex-basis:86px}
|
||||
.sf-theme-trade-pro .sf-tp-product-lead{font-size:11px;line-height:1.35}
|
||||
.sf-theme-trade-pro .sf-tp-product-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||||
.sf-theme-trade-pro .sf-tp-product-actions .sf-tp-btn{min-height:37px;font-size:11px}
|
||||
.sf-theme-trade-pro .sf-tp-quote-list{gap:12px;margin-block-start:16px}
|
||||
.sf-theme-trade-pro .sf-tp-quote-form{padding:16px}
|
||||
.sf-theme-trade-pro .sf-tp-spec-table th,.sf-theme-trade-pro .sf-tp-spec-table td{padding:9px 12px}
|
||||
}
|
||||
|
||||
/* ── Phase 2 skeleton tint layer(chrome 着色,供其余页面与 pro-base 骨架)── */
|
||||
@@ -183,8 +254,6 @@
|
||||
.sf-theme-trade-pro .pro-card h3{color:var(--sf-tp-text,#1a2332)}
|
||||
.sf-theme-trade-pro .pro-card:hover{border-color:var(--sf-tp-primary,#0b3d91)}
|
||||
.sf-theme-trade-pro .pro-card-link{color:var(--sf-tp-primary,#0b3d91)}
|
||||
.sf-theme-trade-pro .pro-specs div{border-bottom:1px solid #e3e9f2}
|
||||
.sf-theme-trade-pro .pro-specs span{color:var(--sf-tp-muted,#5a6a7e)}
|
||||
.sf-theme-trade-pro .pro-end-cta{background:var(--sf-tp-primary,#0b3d91);color:#fff}
|
||||
.sf-theme-trade-pro .pro-end-cta .pro-eyebrow{color:var(--sf-tp-accent,#f5a623)}
|
||||
.sf-theme-trade-pro .pro-end-cta .sf-tp-btn{background:#fff;color:var(--sf-tp-primary,#0b3d91);border-color:#fff}
|
||||
|
||||
Reference in New Issue
Block a user