Files
浅念 8cea7dee20 chore: baseline V8.10.0-dev.1 with v8_workflows type-mapping fix and trade-pro spec
- SiteForge Enterprise V8.10.0-dev.1 upstream source (210 tests)
- Fix: v8_workflows._mapped_input now reads both 类型/分类 columns so WXR
  imports keep post/product types instead of collapsing to page
- Docs: trade-pro theme port spec (R3) and implementation plan

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 16:49:25 +08:00

57 lines
4.6 KiB
Python

"""Trusted shared site shell for the four visual theme designs and installed themes.
Client-side search/filter works without a server after static export.
"""
from html import escape as e
import json
from pathlib import Path
from . import db
ASSETS=Path(__file__).resolve().parent/'static'
def shell(html,site,page,records,nav):
theme=site.get('theme','corporate')
label=lambda key: e(str(key or ''),quote=True)
links=[]
if not nav:
nav=[{'title':'首页','route':'/'},{'title':'产品中心','route':'/product/'},{'title':'品牌','route':'/brand/'},
{'title':'新闻资讯','route':'/post/'},{'title':'成功案例','route':'/case/'},{'title':'关于我们','route':'/about/'},{'title':'联系我们','route':'/contact/'}]
seen=set()
for n in nav[:25]:
route=n.get('route','/')
if route in seen:continue
seen.add(route)
links.append(f'<a href="{label(route.lstrip("/") or "./")}">{label(n.get("title"))}</a>')
# Themes can vary greatly in typography, but share an accessible responsive site shell.
name=label(site['name']); tag=label(site.get('tagline',''))
panels=''.join(f'<a href="{label(x["route"].lstrip("/") or "./")}"><span>{label(x["title"])}</span><small>{label(x["summary"][:86])}</small></a>' for x in records if x.get('type') in ('brand','service') and x.get('status')=='published')[:12000]
header=f'''<div class="sf-site-head" id="sf-site-head"><div class="sf-head-inner"><a class="sf-logo" href="./"><span class="sf-logo-mark">S</span><strong>{name}</strong></a>
<button type="button" class="sf-menu-toggle" aria-controls="sf-primary-links" aria-expanded="false" aria-label="展开网站菜单">☰</button>
<nav class="sf-primary-links" id="sf-primary-links" aria-label="主导航">{''.join(links)}<a href="search/" aria-label="站内搜索">⌕ 搜索</a></nav>
<a class="sf-head-cta" href="contact/">合作咨询 ↗</a></div>{('<div class="sf-mega">'+panels+'</div>') if panels else ''}</div>'''
foot=f'''<div class="sf-site-foot"><div class="sf-foot-grid"><div><h2>{name}</h2><p>{tag}</p></div><div><h4>快速导航</h4>{''.join(links[:6])}</div><div><h4>联系我们</h4><p>{label(site.get('email') or '请在管理后台填写联系邮箱')}</p><a href="contact/">提交咨询 →</a></div></div><div class="sf-foot-lower"><span>© {name}</span><span>GLOBAL SOURCING · GLOBAL DELIVERY</span></div></div>'''
# Every generated landing/detail page needs exactly one visible page heading.
# Blueprints for information pages start with h2 blocks by default.
if page.get('route') != '/' and '<main class="sf-canvas">' in html and '<h1' not in html:
intro=f'<section class="sf-page-intro"><div><p>EXPLORE / {label(page.get("type","PAGE")).upper()}</p><h1>{label(page.get("title", ""))}</h1><span>{label(page.get("summary", ""))}</span></div></section>'
html=html.replace('<main class="sf-canvas">',intro+'<main class="sf-canvas">',1)
html=html.replace('<body>','<body class="sf-theme-'+label(theme)+'">'+header,1)
html=html.replace('</body>',foot+'<script defer src="assets/site-runtime.js"></script></body>',1)
html=html.replace('</head>','<link rel="stylesheet" href="assets/factory.css"></head>',1)
return html
def write_assets(dist,records):
from shutil import copy2
copy2(ASSETS/'factory.css',dist/'assets'/'factory.css')
copy2(ASSETS/'site-runtime.js',dist/'assets'/'site-runtime.js')
search=[{'title':r['title'],'url':r['route'],'summary':(r.get('summary') or '')[:360],
'type':r['type'], 'brand':str((json.loads(r.get('meta_json') or '{}').get('fields') or {}).get('brand',''))}
for r in records if r.get('route') and r['route']!='/']
(dist/'search-index.json').write_text(json.dumps(search,ensure_ascii=False),encoding='utf-8')
def search_page(site):
from html import escape
return f'''<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><base href="/"><title>站内搜索 - {escape(site['name'])}</title><meta name="robots" content="noindex,follow"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/factory.css"></head>
<body class="sf-theme-{escape(site['theme'])}"><main class="sf-search-page"><a href="./">← 返回首页</a><h1>搜索网站内容</h1><label for="sf-search">关键词</label><input id="sf-search" type="search" autofocus placeholder="搜索产品、新闻、品牌、案例……"><div id="sf-search-count" aria-live="polite"></div><div id="sf-search-results"></div></main><script defer src="assets/site-runtime.js"></script></body></html>'''