From 882b8cb1ec082d2b556a183988738c951cdbeebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Fri, 23 Oct 2026 16:34:28 +0800 Subject: [PATCH] feat: port cms contact layout into trade-pro (info/form/no-fake-submit) Task 10f. contact.html rebuilt from contact.astro + LeadForm.astro: PageHero (breadcrumb/cover bg) -> 0.85/1.25 info+form-card grid -> rich body section (body only). No-fake-submit dual form: configured HTTPS form_endpoint renders external form (name/email/message + pro-honeypot, 10b contract); unconfigured renders no
at all, guidance card with real mailto when footer_email exists. Contact items render only with real fields (email mailto; no fabricated phone/address), cms map band dropped (no address data source). CSS appended to pro.css with own 900/620 media blocks; new test test_trade_pro_contact_layout_structure covers both shapes. Co-Authored-By: Claude Code --- tests/test_theme_registry.py | 91 ++++++++++++++++++++++++++ themes/trade-pro/pages/contact.html | 99 +++++++++++++++++++++++++++-- themes/trade-pro/pro.css | 40 ++++++++++++ 3 files changed, 225 insertions(+), 5 deletions(-) diff --git a/tests/test_theme_registry.py b/tests/test_theme_registry.py index 3a5de92..c61ea22 100644 --- a/tests/test_theme_registry.py +++ b/tests/test_theme_registry.py @@ -512,6 +512,97 @@ def test_trade_pro_about_layout_structure(factory): assert len(bsoup.select('.sf-tp-about-certs .sf-tp-feature')) == 4 +def test_trade_pro_contact_layout_structure(factory): + # Task 10f:contact.html 移植 cms 联系页设计(contact.astro + LeadForm.astro, + # 像素基准 dist/hesupipe/en/contact/)后的结构契约。 + # 区块序列:PageHero(面包屑 + 封面底图 + 眉题/h1/导语)→ 联系信息/ + # 表单双栏(0.85fr 信息列 + 1.25fr 灰底表单卡)→ 富文本正文(body + # 存在才渲染)。cms 的地图带(Google 嵌入 = 公司真实地址数据)不移植 + # ——平台无地址字段,虚构地址即假数据;结尾 Cta 由 pro.html 全局 + # pro-end-cta 覆盖。 + # 无假提交双形态:配置 form_endpoint(HTTPS 双层校验——API 400 拒绝 + # 非 HTTPS + make_context 渲染层回退)→ 外部表单(name/email/message + # + pro-honeypot 蜜罐,10b 同款契约,隐藏规则随构建分发);未配置 → + # 不渲染任何 ,引导卡 CTA 兜底。联系字段有真实数据才渲染: + # 邮箱 mailto;电话/地址无数据源绝不虚构。 + import re + sid = site(factory) + assert factory.post(f'/api/factory/sites/{sid}/bootstrap').status_code == 200 + entries = factory.get(f'/api/sites/{sid}/entries').json() + contact_id = next(e['id'] for e in entries if e['route'] == '/contact/') + patched = factory.patch(f'/api/sites/{sid}/entries/{contact_id}', json={ + 'title': 'Contact Us', 'summary': 'Product enquiries, export logistics and partnership requests.', + 'body': '

Reach the trade desk for specifications, pricing and delivery schedules.

', + 'cover': 'https://imgs.example/contact-hall.jpg'}) + assert patched.status_code == 200, patched.text + # HTTPS 双层校验第一层:非 HTTPS 端点被 API 拒绝 + bad = factory.put(f'/api/sites/{sid}/contact-settings', json={ + 'email': 'sales@example.org', 'form_endpoint': 'http://unsecure.example/lead'}) + assert bad.status_code == 400 + ok = factory.put(f'/api/sites/{sid}/contact-settings', json={ + 'email': 'sales@example.org', 'form_endpoint': 'https://forms.example.org/leads'}) + assert ok.status_code == 200, ok.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'] + + # ── 形态一(已配置外部表单):hero + 双栏 + 表单 + 蜜罐 ── + html = factory.get(f'/preview/{sid}/{bid}/contact/').text + soup = BeautifulSoup(html, 'html.parser') + assert len(soup.find_all('h1')) == 1 + hero = soup.select_one('section.sf-tp-page-hero.sf-tp-contact-hero') + assert hero is not None + bg = hero.select_one('.sf-tp-page-hero-bg') + assert bg is not None and 'contact-hall.jpg' in bg.get('src', '') + current = hero.select_one('.sf-tp-breadcrumb-current') + assert current is not None and current.get_text(strip=True) == 'Contact Us' + assert 'Product enquiries' in hero.select_one('.sf-tp-page-hero-lead').get_text() + # 左栏:联系信息(眉题 + h2 + 真实邮箱 mailto + SVG 图标条目) + grid = soup.select_one('.sf-tp-contact-grid') + assert grid is not None + info = grid.select_one('.sf-tp-contact-info[data-reveal]') + assert info is not None and info.select_one('.eyebrow') and info.find('h2') + assert info.select_one('.sf-tp-contact-item a[href="mailto:sales@example.org"]') is not None + items = info.select('.sf-tp-contact-item') + assert len(items) >= 2 and all(i.select_one('.icon-circle svg') for i in items) + # 电话/地址无数据源 → 不渲染(绝不虚构) + assert 'tel:' not in html + # 右栏:外部表单(全页唯一 form)name/email/message + 隐藏蜜罐 + card = grid.select_one('.sf-tp-contact-form[data-reveal]') + assert card is not None + forms = soup.find_all('form') + assert len(forms) == 1 + form = forms[0] + assert form.get('action') == 'https://forms.example.org/leads' and form.get('method') == 'POST' + for name in ('name', 'email', 'message'): + assert form.select_one(f'[name={name}]') is not None, name + honeypot = form.select_one('input.pro-honeypot') + assert honeypot is not None and honeypot.get('aria-hidden') == 'true' and honeypot.get('tabindex') == '-1' + # 蜜罐隐藏规则随构建分发(10b 修复轮的 .pro-honeypot CSS) + pro_css = factory.get(f'/preview/{sid}/{bid}/assets/pro.css').text + assert '.sf-theme-trade-pro .pro-honeypot' in pro_css and 'left:-9999px' in pro_css + # 正文:body 存在才渲染(10d 排版层双挂) + body = soup.select_one('.sf-tp-contact-body .pro-rich') + assert body is not None and 'trade desk' in body.get_text() + # 交互箭头一律 SVG(Task 9 契约) + assert not re.search(r'[←→↗↘⌕▾]', ''.join(a.get_text() for a in soup.find_all('a'))) + + # ── 形态二(未配置):无任何 ,引导卡兜底 + 无虚构联系方式 ── + sid2 = site(factory) + assert factory.post(f'/api/factory/sites/{sid2}/bootstrap').status_code == 200 + assert factory.put(f'/api/factory/sites/{sid2}/theme', json={'theme': 'trade-pro', 'version': '1.0.0'}).status_code == 200 + bid2 = factory.post(f'/api/sites/{sid2}/builds', json={}).json()['version'] + bare = factory.get(f'/preview/{sid2}/{bid2}/contact/') + assert bare.status_code == 200, bare.text[:300] + bsoup = BeautifulSoup(bare.text, 'html.parser') + assert len(bsoup.find_all('form')) == 0 # 无假提交:未配置即不渲染任何表单 + assert 'mailto:' not in bare.text # 无邮箱数据 → 无 mailto(含页脚) + assert 'tel:' not in bare.text # 绝不虚构电话 + card2 = bsoup.select_one('.sf-tp-contact-form') + assert card2 is not None # 引导卡仍在(CTA 引导) + assert card2.select_one('a.sf-tp-btn[href$="/products/"]') is not None + assert len(bsoup.find_all('h1')) == 1 + + def test_frontend_entries_include_trade_pro(): from pathlib import Path base = Path(__file__).resolve().parent.parent diff --git a/themes/trade-pro/pages/contact.html b/themes/trade-pro/pages/contact.html index 07cf572..3ece6be 100644 --- a/themes/trade-pro/pages/contact.html +++ b/themes/trade-pro/pages/contact.html @@ -1,6 +1,95 @@ -
GET IN TOUCH

{{page.title}}

{{page.summary}}

-
CONNECT / CONTACT

Let's start the conversation.

{% if footer_email %}{{footer_email}} {{ icon('arrow-right') }}{% else %}

Set the official business email in the site admin console first.

{% endif %}
{{body_copy(content,page)}}{% if page.cover %}{{page.title}}{% endif %}
-{% if form_endpoint %} -
CONTACT FORM

Send a business enquiry

Submissions go directly to the external form service configured by the site administrator.

-

Form submissions are handled by the external service; review its privacy and spam-filtering settings.

+{# trade-pro contact — cms 联系页设计移植(Task 10f)。 + 区块序列对照 D:/project/cms/runtimes/astro-site-runtime/src/pages/[locale]/contact.astro + + src/components/LeadForm.astro(像素基准 dist/hesupipe/en/contact/): + PageHero(面包屑 + 封面底图 + 眉题/h1/导语)→ 联系信息/表单双栏 + (0.85fr 信息列 + 1.25fr 灰底表单卡)→ 富文本正文(body 存在才渲染)。 + 与 cms 的结构决策: + 1. 无假提交纪律(核心差异):cms LeadForm 提交到自家 control-api; + 平台表单必须由管理员配置外部 HTTPS 服务(contact-settings API + 双层校验 + make_context 渲染层回退)。form_endpoint 存在 → 渲染 + 外部表单(name/email/message + pro-honeypot 蜜罐,10b 产品页 + 询盘区同款契约);不存在 → 不渲染任何
,表单位换引导卡 + (真实邮箱 mailto 优先;无邮箱给通用引导 + /products/ CTA)。 + 2. 联系信息条目有真实字段才渲染:平台上下文只有 footer_email + (邮箱 mailto + mail 图标)。cms 词典的电话/地址/营业时间是 + HESU 真实数据,平台无对应字段——绝不虚构,以两条通用服务承诺 + (响应时效/采购对接,全主题既有口径)补足列表节奏。地图带同理 + 不移植:Google 嵌入指向公司真实地址,无地址数据源即无地图。 + 结尾 Cta 带不重复渲染:pro.html 全局 pro-end-cta 对 contact 页 + 本就输出(cms Cta 也是「回联系页」自指按钮,无增量信息)。 + 3. 正文:cms 无独立正文区(infoText 为单段配置文案,平台由静态 + 引导语对应);WP 迁移的 contact 页常带富文本 body,渲染为正文 + 区(10d sf-tp-article-body 排版层双挂,无 body 整段隐藏)。 #} +
+ {% if safe_image(page.cover) %}{% endif %} + +
+ +
+ CONTACT US +

{{ page.title }}

+ {% if page.summary %}

{{ page.summary }}

{% endif %} +
+
+
+
+
+
+ CONTACT INFORMATION +

Let's start the conversation

+

For enquiries about products, specifications or export orders, reach the team through the channels below.

+
+ {% if footer_email %} +
+ + +
+ {% endif %} +
+ +
Response time

Replies within one business day.

+
+
+ +
Sourcing desk

One contact from inquiry to container loading.

+
+
+
+
+ {% if form_endpoint %} + {# 已配置外部表单服务(HTTPS 双层校验通过):cms LeadForm 形态 —— + 标题 + 引导语 + 纵向字段栅格;蜜罐隐藏(.pro-honeypot,10b 同款) #} +

Send us a message

+

Fill out the form below and we will get back to you shortly.

+ + + + + + +

Submissions are handled by the external form service configured by the site administrator; review its privacy and spam-filtering settings.

+ + {% else %} + {# 无假提交:未配置外部表单服务 → 不渲染任何
。有真实邮箱给 + mailto 直达;无邮箱给通用引导 + 产品目录 CTA(不虚构任何联系方式) #} +

Send us a message

+ {% if footer_email %} +

The online form is not activated on this site yet. Email the team directly — replies within one business day.

+ Email {{ footer_email }} {{ icon('arrow-right') }} + {% else %} +

The online enquiry form is not activated on this site yet. Published specifications are available on the catalogue pages while the enquiry channel is being set up.

+ Browse products {{ icon('arrow-right') }} + {% endif %} + {% endif %} +
+
+
+{% if content %} +
+
+
{{ body_copy(content, page) }}
+
+
{% endif %} diff --git a/themes/trade-pro/pro.css b/themes/trade-pro/pro.css index 265304d..2843f7f 100644 --- a/themes/trade-pro/pro.css +++ b/themes/trade-pro/pro.css @@ -491,3 +491,43 @@ .sf-theme-trade-pro .sf-tp-about-facility figcaption{padding:9px 10px} .sf-theme-trade-pro .sf-tp-about-facility .sf-tp-tag{font-size:10px} } + +/* ── Task 10f:contact 联系页(contact.astro + LeadForm.astro 移植)── + 源 src/pages/[locale]/contact.astro:联系信息/表单双栏(0.85fr 信息列 + + 1.25fr 灰底 form-card)+ LeadForm(引导语 + 纵向字段栅格 + 提交按钮)。 + 共享组件复用:sf-tp-page-hero/breadcrumb(10c 内页 hero)、 + sf-tp-article-body(10d 富文本排版层双挂);表单卡与 10b 询盘表单卡 + 同构(cms 同一 LeadForm 组件),字段样式走 theme.css 元素基线, + 本块只补 contact 专属布局(双栏比、信息列表、图标实底圆)。 */ + +.sf-theme-trade-pro .sf-tp-contact-grid{display:grid;grid-template-columns:0.85fr 1.25fr;gap:calc(var(--sf-tp-space-16) + var(--sf-tp-space-6));align-items:start} +.sf-theme-trade-pro .sf-tp-contact-info>p{color:var(--sf-tp-color-text-muted)} +/* 信息列表(cms contact-list):图标圆 + 标题/值两列行 */ +.sf-theme-trade-pro .sf-tp-contact-list{display:grid;gap:var(--sf-tp-space-6);margin-block-start:var(--sf-tp-space-8)} +.sf-theme-trade-pro .sf-tp-contact-item{display:grid;grid-template-columns:var(--sf-tp-space-12) 1fr;gap:var(--sf-tp-space-4);align-items:center} +/* cms icon-solid:品牌实底圆覆写默认 brand-soft 形态(居中 auto 边距归零) */ +.sf-theme-trade-pro .sf-tp-contact-item .icon-circle{margin:0;background:var(--sf-tp-color-brand-primary);color:var(--sf-tp-color-text-on-brand)} +.sf-theme-trade-pro .sf-tp-contact-item strong{display:block;margin-block-end:var(--sf-tp-space-1)} +.sf-theme-trade-pro .sf-tp-contact-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-contact-item a{color:inherit;word-break:break-all} +.sf-theme-trade-pro .sf-tp-contact-item a:hover{color:var(--sf-tp-color-text-link)} +/* 表单卡(cms form-card + lead-text):灰底圆角;两形态共用(外部表单/ + 无表单引导卡),卡内引导语 muted(cms .lead-text 间距) */ +.sf-theme-trade-pro .sf-tp-contact-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-contact-form>p{color:var(--sf-tp-color-text-muted);margin-block-end:var(--sf-tp-space-6)} +.sf-theme-trade-pro .sf-tp-contact-form form{display:grid;gap:var(--sf-tp-space-4)} +.sf-theme-trade-pro .sf-tp-contact-form .sf-tp-btn{justify-self:start} +.sf-theme-trade-pro .sf-tp-contact-form textarea{min-height:110px} +.sf-theme-trade-pro .sf-tp-contact-note{font-size:var(--sf-tp-font-size-xs);color:var(--sf-tp-color-text-muted);margin:0} + +/* ── contact 响应式(cms 断点 900/620;767 无 contact 专属规则——内页 + hero 的 767 竖版由 10c 既有块覆盖。折叠规则收在本任务媒体块内 + (10c 教训:同特异性按源顺序取胜)── */ +@media (max-width: 900px){ + .sf-theme-trade-pro .sf-tp-contact-grid{grid-template-columns:1fr} +} +@media (max-width: 620px){ + .sf-theme-trade-pro .sf-tp-contact-list{gap:16px} + .sf-theme-trade-pro .sf-tp-contact-item{grid-template-columns:40px 1fr;gap:10px} + .sf-theme-trade-pro .sf-tp-contact-form{padding:20px} +}