diff --git a/tests/test_theme_registry.py b/tests/test_theme_registry.py index 8ae6bae..3a5de92 100644 --- a/tests/test_theme_registry.py +++ b/tests/test_theme_registry.py @@ -401,6 +401,117 @@ def test_trade_pro_article_layout_structure(factory): assert not re.search(r'[←→↗↘⌕▾]', ''.join(a.get_text() for a in soup.find_all('a'))) +def test_trade_pro_about_layout_structure(factory): + # Task 10e:about.html 移植 cms 关于页设计(about.astro,像素基准 + # dist/hesupipe/en/about/)后的结构契约。 + # 区块序列:PageHero(面包屑 + 封面底图 + 页内锚点按钮)→ 公司介绍两栏 + # (文案 + 实景图 + 悬浮数据条)→ 富文本正文(仅当 body 存在)→ + # 优势特性网格 → 深色使命带(实景底图 + 玻璃卡)→ 厂区图集卡(仅当 + # gallery 存在)→ 资质特性网格。平台事实:about 数据 = 普通页面条目 + # (title/summary/body/cover + fields.gallery),cms 词典中的 stats/ + # 厂区文案无对应字段——stats 复用 10a 首页的派生计数(产品/分类/品牌/ + # 案例),厂区卡片由 page.cover+fields.gallery 驱动,均空则整段隐藏。 + # 双构建断言:数据齐备的 /about/(bootstrap 必备栏目路由)+ 空数据的 + # 自定义路由 about 页(meta.page_template 显式指定,不绑定特定路由)。 + import re + sid = site(factory) + assert factory.post(f'/api/factory/sites/{sid}/bootstrap').status_code == 200 + # 数据齐备的 about 条目:PATCH bootstrap 建立的 /about/(保留原路由) + entries = factory.get(f'/api/sites/{sid}/entries').json() + about_id = next(e['id'] for e in entries if e['route'] == '/about/') + cover = 'https://imgs.example/factory-floor.jpg' + shot1 = 'https://imgs.example/workshop-line.jpg' + shot2 = 'https://imgs.example/finished-goods.jpg' + patched = factory.patch(f'/api/sites/{sid}/entries/{about_id}', json={ + 'title': 'About Us', 'summary': 'Manufacturer and export partner since day one.', + 'body': '

Quality control runs from raw material to loaded container.

Our standards

Every batch is inspected before dispatch.

', + 'cover': cover, + 'meta': {'fields': {'gallery': f'{shot1}; {shot2}'}}}) + assert patched.status_code == 200, patched.text + # 3 个产品(1 品牌 1 分类)+ 1 篇文章:驱动派生 stats 与 /news/ 链接 + batch = [{'type': 'product', 'title': f'Valve Series {i}', 'summary': f'Control valve {i}.', + 'meta': {'fields': {'brand': 'Quanta', 'category': 'Valves'}}} for i in range(3)] + batch.append({'type': 'post', 'title': 'Plant Expansion Update', 'summary': 'New extrusion lines.'}) + import io + payload = json.dumps(batch).encode('utf-8') + imported = factory.post(f'/api/sites/{sid}/import', + files={'file': ('about-batch.json', io.BytesIO(payload), 'application/json')}) + assert imported.status_code == 200, imported.text[:300] + 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'] + + # ── 数据齐备 /about/:hero + 介绍两栏 + 正文 + 四个设计区块 ── + html = factory.get(f'/preview/{sid}/{bid}/about/').text + soup = BeautifulSoup(html, 'html.parser') + assert len(soup.find_all('h1')) == 1 + hero = soup.select_one('section.sf-tp-page-hero.sf-tp-about-hero') + assert hero is not None + bg = hero.select_one('.sf-tp-page-hero-bg') + assert bg is not None and 'factory-floor.jpg' in bg.get('src', '') + # 面包屑:Home + 当前页标题(非链接) + assert hero.select_one('.sf-tp-breadcrumb a') + current = hero.select_one('.sf-tp-breadcrumb-current') + assert current is not None and current.get_text(strip=True) == 'About Us' + # hero 锚点按钮(cms PageHero button → #main 的页内对应物) + assert hero.select_one('.sf-tp-page-hero-copy a.sf-tp-btn[href="#tp-about-intro"]') + assert 'Manufacturer and export partner' in hero.select_one('.sf-tp-page-hero-lead').get_text() + # 介绍两栏:文案列(摘要 + 有文章时的 /news/ 链接)+ 实景图 + 派生数据条 + intro = soup.select_one('section.sf-tp-about-intro .sf-tp-two-col') + assert intro is not None + assert intro.select_one('.sf-tp-copy h2') + assert 'Manufacturer and export partner' in intro.select_one('.sf-tp-copy p').get_text() + assert intro.select_one('.sf-tp-copy a.sf-tp-btn[href$="/news/"]') + photo = intro.select_one('.sf-tp-photo img, .sf-tp-photo .sf-tp-photo-plain') + assert photo is not None + stats = intro.select('.sf-tp-stats .sf-tp-stat') + assert len(stats) >= 2 and 'Products' in intro.select_one('.sf-tp-stats').get_text() + # 富文本正文:body 存在才渲染(cms isRichAbout 分支的平台化) + body = soup.select_one('.sf-tp-about-body .pro-rich') + assert body is not None and 'Quality control' in body.get_text() and 'Our standards' in body.get_text() + # 优势 / 使命 / 资质:静态设计内容(cms 词典区块),特性卡带 SVG 图标 + assert len(soup.select('.sf-tp-about-advantages .sf-tp-feature')) == 4 + assert all(f.select_one('.icon-circle svg') for f in soup.select('.sf-tp-about-advantages .sf-tp-feature')) + mission = soup.select_one('section.section--dark.sf-tp-about-mission') + assert mission is not None + assert len(mission.select('.sf-tp-feature')) == 4 + assert mission.select_one('.sf-tp-intro') + assert len(soup.select('.sf-tp-about-certs .sf-tp-feature')) == 4 + # 厂区图集卡:cover + fields.gallery 去重共 3 图 + facilities = soup.select_one('.sf-tp-about-facilities') + assert facilities is not None + cards = facilities.select('.sf-tp-about-facility') + assert len(cards) == 3 + srcs = ' '.join(i.get('src', '') for i in facilities.select('img')) + assert 'workshop-line.jpg' in srcs and 'finished-goods.jpg' in srcs + # 交互箭头一律 SVG(Task 9 契约) + assert not re.search(r'[←→↗↘⌕▾]', ''.join(a.get_text() for a in soup.find_all('a'))) + + # ── 空数据 about(自定义路由 + meta.page_template 显式指定)── + # 无 cover/body/gallery/产品:hero 无底图、正文与图集整段隐藏、stats + # 整段隐藏、/news/ 链接不渲染;静态设计区块保留(完整页面而非两栏摘要)。 + sid2 = site(factory) + made = factory.post(f'/api/sites/{sid2}/entries', json={ + 'type': 'page', 'title': 'Our Story', 'route': '/our-story/', + 'meta': {'page_template': 'about'}}) + assert made.status_code in (200, 201), made.text + 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}/our-story/') + assert bare.status_code == 200, bare.text[:300] + bsoup = BeautifulSoup(bare.text, 'html.parser') + assert len(bsoup.find_all('h1')) == 1 + assert bsoup.select_one('section.sf-tp-about-hero') is not None + assert bsoup.select_one('.sf-tp-page-hero-bg') is None + assert bsoup.select_one('.sf-tp-about-body') is None + assert bsoup.select_one('.sf-tp-about-facilities') is None + assert bsoup.select_one('.sf-tp-stats') is None + assert bsoup.select_one('.sf-tp-about-intro .sf-tp-copy a.sf-tp-btn[href$="/news/"]') is None + assert bsoup.select_one('.sf-tp-copy p').get_text(strip=True) # 英文兜底文案 + assert len(bsoup.select('.sf-tp-about-advantages .sf-tp-feature')) == 4 + assert bsoup.select_one('section.section--dark.sf-tp-about-mission') is not None + assert len(bsoup.select('.sf-tp-about-certs .sf-tp-feature')) == 4 + + def test_frontend_entries_include_trade_pro(): from pathlib import Path base = Path(__file__).resolve().parent.parent diff --git a/themes/trade-pro/pages/about.html b/themes/trade-pro/pages/about.html index a08a6b0..4502433 100644 --- a/themes/trade-pro/pages/about.html +++ b/themes/trade-pro/pages/about.html @@ -1,2 +1,175 @@ -
OUR STORY

{{page.title}}

{{page.summary}}

-
ABOUT / DISCOVER

Who we are.

Company profile, capabilities and export experience.

{{body_copy(content,page)}}{% if page.cover %}{{page.title}}{% endif %}
+{# trade-pro about — cms 关于页设计移植(Task 10e)。 + 区块序列对照 D:/project/cms/runtimes/astro-site-runtime/src/pages/[locale]/about.astro + (像素基准 dist/hesupipe/en/about/): + PageHero(面包屑 + 封面底图 + 页内锚点按钮)→ 公司介绍两栏(文案 + + 实景图 + 悬浮数据条)→ 富文本正文(cms 仅富文本渲染,平台 body 存在 + 即渲染)→ 优势特性网格(浅底)→ 使命深色带(实景底图 + 玻璃卡)→ + 厂区图集卡(图集驱动)→ 资质特性网格(浅底)。 + 与 cms 的结构决策: + 1. about 数据 = 一个普通页面条目(title/summary/body/cover + fields)。 + cms 词典中的 stats(26+/19+/47/10)与厂区卡文案平台无对应字段: + stats 复用 10a 首页关于两栏的派生计数(产品/分类/品牌/案例,全空 + 整段隐藏);厂区卡由 page.cover + fields.gallery(make_context 的 + gallery 变量,去重含封面)驱动,无图整段隐藏——与首页 about 两栏 + 不同,这是完整页面,静态设计区块(优势/使命/资质)保留 cms 形态。 + 2. cms markFloatImages 构建期给正文图片打浮动 class:平台 sanitize 剥 + 离 class 属性(10d 同结论),不移植;正文统一 body_copy(.pro-rich + 基线)+ 双挂 sf-tp-article-body 复用 10d 富文本排版层(about.astro + 的 about-body 与全站 rich-body 同构)。 + 3. hero 的「Our History」按钮 cms 锚到 #main(hero 正下方的内容区), + 平台模板不在 main 内,锚到介绍两栏的 #tp-about-intro(构建器豁免 + # 锚点链接);文案列的 history 链接指向 /news/(cms 同款),无文章 + 时隐藏以免空栏目断链。 + 4. cms 使命深色带底图固定 factory.webp:平台用 gallery[0](即封面优先) + 作实景底图,无图回退 theme.css .section--dark 纯渐变(10a CTA 同策)。 + 5. cms 的 certKicker 等词典文案是设计内容(同首页 trustbar/features + 处理):移植为英文静态文案,去除 HESU 专属表述(厂名/年份)。 #} +
+ {% if safe_image(page.cover) %}{% endif %} + +
+ +
+ ABOUT US +

{{ page.title }}

+ {% if page.summary %}

{{ page.summary }}

{% endif %} + Our history {{ icon('arrow-right') }} +
+
+
+
+
+
+ COMPANY INTRODUCTION +

Who we are

+

{{ page.summary or site.tagline or 'Company profile, capabilities and export experience.' }}

+ {% if stories %}Company history {{ icon('arrow-right') }}{% endif %} +
+
+
+ {% if gallery %}{{ site.name }} facility + {% else %}{% endif %} +
+ {% if groups['product'] or groups['vehicle'] or categories or brands or cases %} +
+ {% if groups['product'] or groups['vehicle'] %}
{{ groups['product']|length + groups['vehicle']|length }}Products
{% endif %} + {% if categories %}
{{ categories|length }}Categories
{% endif %} + {% if brands %}
{{ brands|length }}Brands
{% endif %} + {% if cases %}
{{ cases|length }}Case studies
{% endif %} +
+ {% endif %} +
+
+
+{% if content %} +
+
+
+
+ COMPANY INTRODUCTION +

{{ page.title }}

+
+
+
{{ body_copy(content, page) }}
+
+
+{% endif %} +
+
+
+
+ OUR ADVANTAGES +

Why buyers choose us

+
+
+
+ {% set tp_advantages = [ + ('check', 'Reliable quality', 'Strict quality control from raw material to loaded container.'), + ('search', 'Advanced equipment', 'Modern production and testing facilities.'), + ('phone', 'Professional team', 'Experienced technical and export service teams.'), + ('location', 'Global service', 'Products delivered to worldwide markets.'), + ] %} + {% for tp_icon, tp_title, tp_desc in tp_advantages %} +
+ +

{{ tp_title }}

+

{{ tp_desc }}

+
+ {% endfor %} +
+
+
+
+ {% if gallery %}{% endif %} +
+
+
+ OUR MISSION +

Reliable solutions for a better tomorrow

+
+

Our mission is to deliver reliable products and dependable service for global development.

+
+
+ {% set tp_mission = [ + ('quote', 'Our mission', 'Reliable solutions for global development.'), + ('location', 'Our vision', 'A trusted global partner in our field.'), + ('check', 'Our values', 'Quality, innovation and sustainability.'), + ('phone', 'Our promise', 'Long-term value for customers.'), + ] %} + {% for tp_icon, tp_title, tp_desc in tp_mission %} +
+ +

{{ tp_title }}

+

{{ tp_desc }}

+
+ {% endfor %} +
+
+
+{% if gallery %} +
+
+
+
+ FACTORY DISPLAY +

Our manufacturing facilities

+
+
+
+ {% for tp_shot in gallery[:4] %} +
+
{{ site.name }} facility {{ loop.index }}
+
Facility {{ '%02d'|format(loop.index) }}
+
+ {% endfor %} +
+
+
+{% endif %} +
+
+
+
+ CERTIFICATES +

International certifications

+
+
+
+ {% set tp_certs = [ + ('check', 'ISO 9001', 'Quality management system.'), + ('location', 'ISO 14001', 'Environmental management system.'), + ('search', 'ISO 45001', 'Occupational health and safety.'), + ('download', 'CE', 'European conformity marking.'), + ] %} + {% for tp_icon, tp_title, tp_desc in tp_certs %} +
+ +

{{ tp_title }}

+

{{ tp_desc }}

+
+ {% endfor %} +
+
+
diff --git a/themes/trade-pro/pro.css b/themes/trade-pro/pro.css index 8a96942..265304d 100644 --- a/themes/trade-pro/pro.css +++ b/themes/trade-pro/pro.css @@ -445,3 +445,49 @@ .sf-theme-trade-pro .sf-tp-article-side-item{grid-template-columns:72px 1fr} .sf-theme-trade-pro .sf-tp-article-side-cta{padding:16px} } + +/* ── Task 10e:about 关于页(about.astro 移植)── + 源 src/pages/[locale]/about.astro:PageHero → 公司介绍两栏(实景图 + + 悬浮数据条)→ 富文本正文 → 优势网格 → 使命深色带(实景底图 + 玻璃 + 特性卡,cms dark-photo/.dark .feature)→ 厂区图集卡(CardGrid cols-4, + 平台 gallery 驱动)→ 资质网格。共享组件复用 10a/10b/10c 移植成果: + sf-tp-two-col/copy/photo/stats(介绍两栏 = 首页关于两栏同构,cms 源码 + 本就同款)、sf-tp-feature-grid/card-grid/section-head(cms 同组件)、 + sf-tp-page-hero/breadcrumb(10c 内页 hero)、sf-tp-article-body(10d + 富文本排版层,about-body 与全站 rich-body 同构,markup 双挂类名)。 + 本块只补 about 专属形态:使命带底图叠层与玻璃卡、厂区图集卡。 */ + +/* 使命深色带:cms dark-photo = section--dark 渐变 + 实景图(object-position + center 55%)。叠层级序(同 page-hero):图(z-1)→ ::before 渐变罩 + (z-base,复刻 section--dark 渐变盖住图保证文字对比)→ .wrap(z-raised)。 + 无图时罩层叠在原渐变上,视觉与 .section--dark 纯渐变一致。 */ +.sf-theme-trade-pro .sf-tp-about-mission{position:relative;overflow:hidden} +.sf-theme-trade-pro .sf-tp-about-mission-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center 55%;z-index:calc(var(--sf-tp-z-base) - 1)} +.sf-theme-trade-pro .sf-tp-about-mission::before{content:"";position:absolute;inset:0;z-index:var(--sf-tp-z-base);background:linear-gradient(90deg,color-mix(in srgb,var(--sf-tp-color-surface-inverse) 94%,transparent),color-mix(in srgb,var(--sf-tp-color-brand-primary) 60%,transparent))} +.sf-theme-trade-pro .sf-tp-about-mission .wrap{position:relative;z-index:var(--sf-tp-z-raised)} +/* 玻璃特性卡(cms .dark .feature):半透明白 + 反色字。0,3,0 无条件压过 + 10a 的 .sf-tp-feature 底色(0,2,0),620 横排布局仍由 10a 折叠规则提供 */ +.sf-theme-trade-pro .sf-tp-about-mission .sf-tp-feature{background:color-mix(in srgb,var(--sf-tp-color-neutral-0) 10%,transparent);border-color:var(--sf-tp-color-border-inverse)} +.sf-theme-trade-pro .sf-tp-about-mission .sf-tp-feature h3{color:var(--sf-tp-color-text-inverse)} +.sf-theme-trade-pro .sf-tp-about-mission .sf-tp-feature p{color:color-mix(in srgb,var(--sf-tp-color-text-inverse) 82%,transparent)} +.sf-theme-trade-pro .sf-tp-about-mission .sf-tp-intro{color:color-mix(in srgb,var(--sf-tp-color-text-inverse) 82%,transparent)} +/* 厂区图集卡(cms CardGrid 卡片形态,数据驱动无悬停语义故不复用 .sf-tp-card + 的链接上抬):媒体复用 sf-tp-card-media(4:3 + 裁剪),编号 tag 补位 + cms 卡片标题行 */ +.sf-theme-trade-pro .sf-tp-about-facility{margin:0;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);overflow:hidden;box-shadow:var(--sf-tp-shadow-xs)} +.sf-theme-trade-pro .sf-tp-about-facility figcaption{padding:var(--sf-tp-space-3) var(--sf-tp-space-4)} + +/* ── about 响应式(cms 断点 900/620;767 无 about 专属规则——内页 hero + 的 767 竖版由 10c 既有块覆盖。介绍两栏/数据条/特性网格/图集栅格的折叠 + 全部由 10a 既有块提供(复用同一批类名);10d 的 620 富文本字号经 + sf-tp-article-body 双挂类名作用于本页正文。折叠规则收在本任务媒体块 + 内的原则只针对本任务新类名(10c 教训:同特异性按源顺序取胜)── */ +@media (max-width: 900px){ + .sf-theme-trade-pro .sf-tp-about-mission .sf-tp-intro{max-width:100%} +} +@media (max-width: 620px){ + .sf-theme-trade-pro .sf-tp-about-mission h2{font-size:22px} + .sf-theme-trade-pro .sf-tp-about-mission .sf-tp-intro{font-size:11px} + .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} +}