Files
cms-SiteForge/app/templates/editor.html
T
浅念 601451f22b upgrade: adopt V8.21.3 (V8.13→8.21.3: P5 SFTP/S3, P6 multilingual, editor overhaul, enterprise hardening)
Overlay ~150 upstream items onto our V8.12 base, preserving trade-pro
theme, registry tests, and design docs. Re-applied our patch set
(discovery/env_for/blocks.css/type-mapping/theme_color/frontend entries,
test_theme_pro_v3 mods) — all anchors survived upstream evolution.

New platform enhancement (fixes upgrade-introduced regression):
multilingual.site_locale now honors settings_json.locale, plus
PUT /api/i18n/sites/{id}/locale — standalone English sites get
<html lang='en'> under the V8.15 locale-rewrite pipeline.

compose: adopt V8.21.3 (image 8.21.3-dev.1, /api/ready probes, MySQL/S3
env) with project name pinned to siteforge-v8-10 (data volumes).

Tests: 356 passed / 11 failed — all 11 confirmed pre-existing in
pristine V8.21.3 on this host (10 upstream Windows env + our build test
now green after locale fix). Patch registry updated in plan doc.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-23 12:31:03 +08:00

121 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>SiteForge Studio · 可视化建站</title><link rel="stylesheet" href="/ui/editor.css"><script defer src="/ui/editor-helpers.js"></script><script defer src="/ui/editor.js"></script><link rel="stylesheet" href="/ui/professional-ui.css"><script defer src="/ui/professional-ui.js"></script></head>
<body data-sf-page="editor">
<div class="studio">
<header class="studio-top sf-easy-top">
<div class="brand"><a href="/" title="返回网站管理">◈</a><div><b>SiteForge</b><small>页面编辑</small></div></div>
<div class="file-name sf-easy-file"><small id="siteName">加载中</small><strong id="pageName">页面编辑器</strong></div>
<div class="sf-pane-controls"><button type="button" onclick="Studio.togglePane('left')">☰ 内容</button><button type="button" onclick="Studio.togglePane('right')">⚙ 编辑</button></div>
<div class="top-center sf-easy-device" role="group" aria-label="预览设备">
<button id="deviceDesktop" class="icon-btn on" onclick="Studio.device('desktop')" title="电脑预览" aria-label="电脑预览">电脑</button>
<button id="deviceTablet" class="icon-btn" onclick="Studio.device('tablet')" title="平板预览" aria-label="平板预览">平板</button>
<button id="devicePhone" class="icon-btn" onclick="Studio.device('phone')" title="手机预览" aria-label="手机预览">手机</button>
</div>
<span id="saveStatus" class="save-status sf-easy-save-status" role="status" aria-live="polite">读取中…</span>
<button id="saveDraftButton" type="button" class="ui-btn ghost sf-save-btn" onclick="Studio.save()">保存草稿</button>
<button id="publishDesignButton" type="button" class="ui-btn primary sf-publish-btn" onclick="Studio.publishDesign()">发布设计</button>
<details class="studio-actions"><summary title="其他功能">更多 ▾</summary><div class="studio-actions-menu">
<button id="studioUndo" class="ui-btn ghost" onclick="Studio.undo()" disabled>↶ 撤销</button>
<button id="studioRedo" class="ui-btn ghost" onclick="Studio.redo()" disabled>↷ 重做</button>
<button class="ui-btn ghost" onclick="Studio.showHistory()">版本历史</button>
<button class="ui-btn ghost" onclick="Studio.showSiteDesign()">全站页眉与页脚</button>
<button class="ui-btn ghost" onclick="Studio.showTokens()">全站设计规范</button>
<button class="ui-btn ghost" onclick="Studio.refreshPreview()">刷新预览</button>
<button class="ui-btn ghost" onclick="Studio.buildSite()">构建静态网站</button>
<button class="ui-btn ghost" onclick="Studio.discardDraft()">放弃草稿</button>
</div></details>
</header>
<div class="sf-steps" aria-label="编辑操作指引"><div class="sf-steps-label">只需 3 步</div><span class="sf-step" id="sfStep1">① 选择或添加区块</span><span class="sf-step-separator">→</span><span class="sf-step" id="sfStep2">② 修改右侧内容</span><span class="sf-step-separator">→</span><span class="sf-step" id="sfStep3">③ 保存并发布</span><button type="button" class="sf-guide-link" onclick="Studio.showHelp()">新手使用指南 ?</button></div>
<div class="studio-body">
<aside class="left-pane" aria-label="页面内容和区块">
<div class="pane-heading"><div><span class="overline">页面内容</span><h3>编辑页面</h3></div><span class="chip" id="blockCount">0 个区块</span></div>
<div class="field sf-page-picker"><label for="pageSelect">当前编辑的页面</label><select id="pageSelect" onchange="Studio.choosePage(this.value)"></select></div>
<div class="sf-add-area"><button type="button" id="sfAddBlock" class="sf-add-button" aria-expanded="false" aria-controls="sfLibraryPanel" onclick="Studio.toggleLibrary()">+ 添加内容区块</button><p>例如图片横幅、公司简介、产品列表等</p></div>
<section id="sfLibraryPanel" class="sf-library-panel" hidden aria-label="添加区块"><div class="sf-panel-caption">点击类型即可添加到当前页面 <button type="button" onclick="Studio.toggleLibrary(false)" aria-label="收起添加面板">收起 ×</button></div><div class="palette" id="palette"></div><details class="sf-subsection"><summary>从我的组件库添加</summary><div class="palette" id="reusablePalette"></div></details></section>
<div class="section-label">当前页面的内容 <span class="section-caption">点击一项进行编辑</span></div>
<div class="structure-filter"><label for="blockSearch" class="sr-only">搜索页面内容</label><input id="blockSearch" type="search" placeholder="查找横幅、文字、产品…" autocomplete="off" oninput="Studio.filter(this.value)"><button type="button" onclick="Studio.clearFilter()" aria-label="清除搜索">×</button></div>
<div id="blockList" class="block-list" role="list" aria-label="当前页面区块"></div>
<p class="structure-hint">点击区块进入右侧编辑;拖动 ⠿ 或使用 ↑ ↓ 调整顺序。</p>
<details class="sf-advanced"><summary>高级页面设置 <span>模板、布局和组件保存</span></summary>
<div class="field"><label for="pageTemplate">页面模板</label><select id="pageTemplate" onchange="Studio.changeTemplateKind(this.value)"></select></div>
<div class="field"><label for="layoutMode">排版方式</label><select id="layoutMode" onchange="Studio.setLayoutMode(this.value)"><option value="append">保留原页面,追加区块</option><option value="replace">用这些区块重新排版页面</option></select></div>
<div class="sf-advanced-buttons"><button type="button" class="ui-btn ghost" onclick="Studio.resetPreset()">使用模板预设布局</button><button type="button" class="ui-btn ghost" onclick="Studio.saveReusable()">将所选区块存为组件</button></div>
<p class="sf-advanced-note">更换模板预设可能替换当前草稿,请先保存;不会直接改变在线网站。</p>
</details>
</aside>
<section class="preview-workspace"><div class="preview-head"><span class="live-dot" id="previewDot"></span><span id="previewState" role="status" aria-live="polite">实时预览</span><code id="routeDisplay">/</code><span class="preview-note">修改后自动预览 · 不会直接改变线上网站</span></div><div class="stage"><div class="device-frame" id="deviceFrame"><iframe id="sitePreview" title="页面预览" sandbox="allow-scripts" referrerpolicy="no-referrer"></iframe></div></div>
<div class="stage-footer"><span id="deviceDetails">桌面端 · 自适应宽度</span><span>当前是草稿预览,不是线上网站</span></div></section>
<aside class="right-pane"><div class="pane-heading"><div><span class="overline">步骤 2 · 修改内容</span><h3>编辑选中的区块</h3></div><span class="chip" id="blockTypeLabel">页面</span></div>
<div class="inspector-nav" role="tablist" aria-label="属性编辑分类"><button type="button" role="tab" id="tabContent" aria-selected="true" onclick="Studio.changeInspectorTab('content')">内容</button><button type="button" role="tab" id="tabStyle" aria-selected="false" onclick="Studio.changeInspectorTab('style')">样式</button><button type="button" role="tab" id="tabResponsive" aria-selected="false" onclick="Studio.changeInspectorTab('responsive')">手机/平板</button></div>
<div class="inspector" id="inspector"><div class="no-selection">先从左侧点击一个内容区块,或者点击“添加内容区块”。</div></div>
<details class="right-foot sf-advanced-foot"><summary>高级:动态数据变量</summary><p>支持 <code>{{site.name}}</code>、<code>{{page.title}}</code> 等变量;动态内容列表可以按网站内容类型绑定。</p></details>
</aside>
</div>
<dialog id="sfGuideDialog" class="sf-help-dialog" aria-labelledby="sfGuideTitle">
<div class="sf-help-head"><div><span class="overline">新手指南</span><h2 id="sfGuideTitle">3 分钟学会编辑页面</h2></div><button type="button" class="ui-btn ghost" onclick="document.querySelector('#sfGuideDialog').close()">关闭 ×</button></div>
<ol class="sf-help-steps"><li><b>先选页面</b><p>左侧顶部切换要编辑的页面;选择现有内容区块,或点击橙色的“添加内容区块”。</p></li><li><b>修改内容</b><p>在右侧“内容”里改标题、文案和图片;切换“样式”或“手机/平板”调整外观。中间自动展示效果。</p></li><li><b>保存草稿,再发布</b><p>点击右上角“保存草稿”可以保留工作。核对后点击“发布设计”,它会更新内容库;正式网站还需要执行静态构建和部署。</p></li></ol>
<div class="sf-guide-note">撤销、版本历史、全站页眉、静态构建等低频功能在右上角「更多」。</div>
<button type="button" class="ui-btn primary sf-guide-done" onclick="document.querySelector('#sfGuideDialog').close()">知道了,开始编辑</button>
</dialog>
<dialog id="designModal" style="border:1px solid #e2e8f0;border-radius:14px;padding:24px;width:min(460px,90vw);box-shadow:0 16px 52px #142a492f"><h2>全站设计规范</h2><p>调整后保存并刷新预览,发布构建自动采用该规范。</p><form id="designForm" method="dialog" style="display:grid;gap:12px"><label>标题缩放 <input name="heading_scale" type="number" min="0.8" max="1.5" step="0.1"></label><label>圆角 <input name="radius" type="number" min="0" max="32"></label><label>版心宽度 <input name="container_width" type="number" min="880" max="1600"></label><label>板块间距 <input name="section_spacing" type="number" min="20" max="140"></label><label>字体 <select name="font"><option value="system">系统字体</option><option value="serif">衬线字体</option><option value="modern">现代字体</option></select></label><label>按钮风格 <select name="button_style"><option value="rounded">圆角</option><option value="square">直角</option><option value="pill">胶囊</option></select></label><label>导航风格 <select name="header_style"><option value="standard">标准</option><option value="compact">紧凑</option></select></label><div style="display:flex;gap:12px"><button class="ui-btn primary" type="button" onclick="Studio.saveTokens()">保存全站规范</button><button class="ui-btn ghost" value="cancel">取消</button></div></form></dialog>
<dialog id="siteDesignModal" style="border:1px solid #e2e8f0;border-radius:14px;padding:22px;width:min(750px,96vw);max-height:88vh;overflow:auto;box-shadow:0 18px 56px #142a492f">
<h2>全站设计 · 页眉、导航、页脚与响应式</h2>
<p style="color:#64748b">先保存独立草稿;预览只显示在编辑器内。发布后,所有页面的下次静态构建才会使用新设计。</p>
<div id="siteDesignStatus" role="status" style="font-weight:600;margin-bottom:12px">读取中...</div>
<form id="siteDesignForm" method="dialog" style="display:grid;gap:12px">
<fieldset style="display:grid;gap:9px;border:1px solid #e2e8f0;border-radius:8px"><legend>页眉与导航</legend>
<label><input type="checkbox" name="show_announcement"> 显示顶部提示栏</label>
<label>提示栏文字 <input name="announcement" maxlength="140" style="width:100%"></label>
<label><input type="checkbox" name="show_search"> 显示站内搜索</label>
<label><input type="checkbox" name="show_cta"> 显示联系按钮</label>
<label>按钮文字 <input name="cta_text" maxlength="40"></label>
<label>按钮路径 <input name="cta_href" placeholder="/contact/"></label>
<label>导航结构(JSON,每项含 title、route、可选 children)
<textarea name="nav" rows="6" style="display:block;width:100%;font-family:monospace" spellcheck="false"></textarea>
</label>
</fieldset>
<fieldset style="display:grid;gap:9px;border:1px solid #e2e8f0;border-radius:8px"><legend>全站页脚</legend>
<label>企业名称或页脚标题 <input name="footer_title" maxlength="120" style="width:100%"></label>
<label>页脚简介 <textarea name="footer_description" rows="2" maxlength="650" style="display:block;width:100%"></textarea></label>
<label>联系邮箱 <input name="footer_email" type="email" maxlength="150"></label>
<label><input name="footer_show_nav" type="checkbox"> 页脚显示导航链接</label>
</fieldset>
<fieldset style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;border:1px solid #e2e8f0;border-radius:8px"><legend>不同设备布局</legend>
<label>平板板块间距 <input name="tablet_gap" type="number" min="20" max="120"></label>
<label>手机板块间距 <input name="mobile_gap" type="number" min="16" max="100"></label>
<label>手机内容边距 <input name="mobile_container_padding" type="number" min="8" max="48"></label>
<label><input name="mobile_hide_announcement" type="checkbox"> 手机隐藏提示栏</label>
<label><input name="mobile_hide_footer_links" type="checkbox"> 手机隐藏页脚导航</label>
</fieldset>
<fieldset style="border:1px solid #e2e8f0;border-radius:10px;padding:15px;display:grid;gap:12px"><legend>手机底部快捷导航 Tabs(可选)</legend>
<p style="margin:0;color:#64748b;font-size:13px">默认关闭。需要时开启后添加 1–5 个按钮。它们只显示在手机上,平板和电脑不显示。</p>
<label><input name="mobile_tabs_enabled" type="checkbox" onchange="Studio.toggleMobileTabsForm()"> 开启手机底部 Tabs</label>
<div id="mobileTabsEditor" hidden style="display:grid;gap:10px">
<div id="mobileTabsRows" style="display:grid;gap:9px"></div>
<button class="ui-btn ghost" type="button" onclick="Studio.addMobileTab()">+ 添加一个 Tab(最多 5 个)</button>
<small style="color:#64748b">标题建议不超过 4 个字;链接使用站内路径(如 /products/)。菜单按钮会打开右侧导航抽屉。</small>
</div>
</fieldset>
<div style="display:flex;flex-wrap:wrap;gap:8px">
<button class="ui-btn ghost" type="button" onclick="Studio.previewSiteDesign()">预览草稿</button>
<button class="ui-btn ghost" type="button" onclick="Studio.discardSiteDesign()">放弃全站草稿</button>
<button class="ui-btn ghost" type="button" onclick="Studio.saveSiteDesign()">保存全站草稿</button>
<button class="ui-btn primary" type="button" onclick="Studio.publishSiteDesign()">发布全站设计</button>
<button class="ui-btn ghost" value="cancel">关闭</button>
</div>
</form>
</dialog>
<dialog id="siteMediaModal" style="border:1px solid #dce5f1;border-radius:14px;padding:24px;width:min(720px,94vw);max-height:85vh;overflow:auto">
<h2>本站图片素材库</h2><p style="font-size:13px;color:#66758a">选择图片后更新当前组件;保存草稿、发布设计后才会参与静态构建。</p>
<div id="siteMediaGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(135px,1fr));gap:12px"></div>
<button type="button" class="ui-btn ghost" onclick="document.querySelector('#siteMediaModal').close()" style="margin-top:15px">关闭</button>
</dialog>
<dialog id="studioHistoryModal" aria-labelledby="studioHistoryTitle" style="border:1px solid #dce5f1;border-radius:14px;padding:24px;width:min(680px,95vw);max-height:82vh;overflow:auto">
<h2 id="studioHistoryTitle">页面设计版本历史</h2>
<p style="color:#64748b">保存草稿、发布设计都会创建不可变快照。恢复只写入新草稿,不会直接修改已发布站点。</p>
<div id="studioHistoryItems" aria-live="polite"></div>
<button class="ui-btn ghost" type="button" onclick="document.querySelector('#studioHistoryModal').close()">关闭</button>
</dialog>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
</div></body></html>