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>
This commit is contained in:
浅念
2026-10-23 12:31:03 +08:00
parent 257c89e159
commit 601451f22b
218 changed files with 10441 additions and 628 deletions
+67 -18
View File
@@ -1,27 +1,61 @@
<!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.js"></script><link rel="stylesheet" href="/ui/professional-ui.css"><script defer src="/ui/professional-ui.js"></script></head>
<!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">
<div class="brand"><a href="/" title="返回网站管理">◈</a><div><b>SiteForge</b><small>VISUAL STUDIO</small></div></div>
<span class="divide"></span><div class="file-name"><span id="siteName">加载中</span><small> / </small><strong id="pageName">页面编辑器</strong></div>
<div class="top-center"><button id="deviceDesktop" class="icon-btn on" onclick="Studio.device('desktop')" title="PC预览">▣</button><button id="deviceTablet" class="icon-btn" onclick="Studio.device('tablet')" title="平板预览">▤</button><button id="devicePhone" class="icon-btn" onclick="Studio.device('phone')" title="手机预览">▯</button></div>
<button class="ui-btn ghost" onclick="Studio.undo()" title="撤销 Ctrl+Z">↶ 撤销</button><button class="ui-btn ghost" onclick="Studio.redo()" title="重做 Ctrl+Y">↷ 重做</button><button class="ui-btn ghost" onclick="Studio.showTokens()">设计规范</button><button class="ui-btn ghost" onclick="Studio.showSiteDesign()">全站页眉与页脚</button><span id="saveStatus" class="save-status">未修改</span><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><button class="ui-btn ghost" onclick="Studio.save()">保存草稿</button><button class="ui-btn primary" onclick="Studio.publishDesign()">发布设计 ↗</button>
<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"><div class="pane-heading"><div><span class="overline">STRUCTURE</span><h3>页面结构</h3></div><span class="chip" id="blockCount">0 区块</span></div>
<div class="field"><label>页面</label><select id="pageSelect" onchange="Studio.choosePage(this.value)"></select></div>
<div class="field"><label>页面模板</label><select id="pageTemplate" onchange="Studio.changeTemplateKind(this.value)"></select></div>
<div style="padding:0 19px 7px"><button class="ui-btn ghost" style="width:100%" onclick="Studio.resetPreset()">◈ 应用此页面模板的预设区块</button></div>
<div class="field"><label>页面布局模式</label><select id="layoutMode" onchange="Studio.setLayoutMode(this.value)"><option value="append">保留主题原页面 + 追加区块</option><option value="replace">全页自由排版(保留导航与页脚)</option></select></div><div class="section-label">内容区块 · 拖拽可调整排序</div><div id="blockList" class="block-list"></div>
<div class="section-label">添加功能区块</div><div class="palette" id="palette"></div><div class="section-label">可复用组件库</div><button class="ui-btn ghost" style="margin:10px" onclick="Studio.saveReusable()">将当前区块存为组件</button><div class="palette" id="reusablePalette"></div>
<div class="sidebar-foot">页面由区块驱动,所见预览使用服务端与正式构建相同的数据绑定引擎。</div>
<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"></span><span>实时预览</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></section>
<aside class="right-pane"><div class="pane-heading"><div><span class="overline">PROPERTIES</span><h3>属性设置</h3></div><span class="chip" id="blockTypeLabel">页面</span></div>
<div class="inspector" id="inspector"><div class="no-selection">在左侧选择一个区块,在这里编辑它的文字、图片、布局及数据来源。</div></div>
<div class="right-foot"><div>动态数据绑定</div><p>支持 <code>{{site.name}}</code>、<code>{{page.title}}</code> 等变量;“动态内容列表”按类型自动查询当前网站的数据。</p></div>
<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">
@@ -53,6 +87,15 @@
<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>
@@ -67,5 +110,11 @@
<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>
<div id="toast" class="toast"></div>
<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>
+2 -2
View File
@@ -1,5 +1,5 @@
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>登录 · SiteForge 网站工厂</title><style>
:root{font-family:Inter,system-ui,-apple-system,"Microsoft YaHei",sans-serif;color:#263952}*{box-sizing:border-box}body{margin:0;background:#f5f7fa;min-height:100vh;display:grid;place-items:center;padding:24px}main{width:min(430px,100%);background:#fff;border:1px solid #e1e8ef;border-radius:20px;box-shadow:0 25px 70px #19345512;padding:42px}.symbol{width:56px;height:56px;border-radius:15px;background:#173250;color:#fff;display:grid;place-items:center;font-size:26px;font-weight:700;margin-bottom:24px}h1{font-size:27px;margin:0 0 6px}p{font-size:13px;color:#7c8ca0;line-height:1.8;margin:0 0 29px}label{display:block;font-size:13px;font-weight:600;margin-bottom:9px}input{width:100%;height:48px;border:1px solid #d8e0e9;border-radius:10px;padding:0 14px;font-size:15px;outline:0}input:focus{border-color:#386aab;box-shadow:0 0 0 3px #417cbc1c}button{width:100%;margin-top:22px;background:#19446f;color:#fff;height:48px;border:0;border-radius:10px;font-size:14px;font-weight:650;cursor:pointer}button:disabled{opacity:.65}small{display:block;text-align:center;margin-top:23px;color:#96a4b6}#error{margin:12px 0 0;color:#b63d3d;min-height:18px;font-size:13px}
</style></head><body><main><div class="symbol">S✳</div><h1>欢迎使用 SiteForge</h1><p>登录网站生产工作台,管理网站、内容、主题和静态构建版本。</p><form id="auth"><label for="pass">账号密码</label><input id="username" type="text" value="admin" required autocomplete="username" placeholder="管理员或团队用户名"><label for="pass">管理员密码 / 团队密码</label><input id="pass" type="password" required autofocus autocomplete="current-password" placeholder="请输入部署时设置的密码"><div id="error" role="alert"></div><button id="submit" type="submit">登录工作台 →</button></form><small>SiteForge Web · Factory 7.1.0</small></main><script>
document.getElementById('auth').addEventListener('submit',async e=>{e.preventDefault();const b=document.getElementById('submit'),err=document.getElementById('error');b.disabled=true;err.textContent='';try{const res=await fetch('/api/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:document.getElementById('username').value,password:document.getElementById('pass').value})});if(!res.ok){const d=await res.json();throw new Error(d.detail||'登录失败')}location.replace('/')}catch(e){err.textContent=e.message;b.disabled=false}});
</style></head><body><main><div class="symbol">S✳</div><h1>欢迎使用 SiteForge</h1><p>登录网站生产工作台,管理网站、内容、主题和静态构建版本。</p><form id="auth"><label for="pass">账号密码</label><input id="username" type="text" value="admin" required autocomplete="username" placeholder="管理员或团队用户名"><label for="pass">管理员密码 / 团队密码</label><input id="pass" type="password" required autofocus autocomplete="current-password" placeholder="请输入部署时设置的密码"><div id="error" role="alert"></div><button id="submit" type="submit">登录工作台 →</button></form><small>SiteForge Web · Enterprise 8.19</small></main><script>
document.getElementById('auth').addEventListener('submit',async e=>{e.preventDefault();const b=document.getElementById('submit'),err=document.getElementById('error');b.disabled=true;err.textContent='';try{const res=await fetch('/api/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:document.getElementById('username').value,password:document.getElementById('pass').value})});if(!res.ok){const d=await res.json();throw new Error(d.detail||'登录失败')}const next=new URLSearchParams(location.search).get('next')||'/';const safe=next==='/'||next.startsWith('/siteforge/')&&!next.startsWith('//');location.replace(safe?next:'/')}catch(e){err.textContent=e.message;b.disabled=false}});
</script></body></html>
+1 -1
View File
@@ -5,6 +5,6 @@
<div class="two"><section><div class="section-head"><h2>01 · CMS 数据迁移</h2><small>先预演,再写入网站数据库</small></div><div class="fields"><label>来源系统<select id="platform"><option value="wordpress-rest">WordPress REST JSON</option><option value="woocommerce">WooCommerce 产品 JSON</option><option value="drupal-jsonapi">Drupal JSON:API</option><option value="shopify">Shopify 商品 JSON</option></select></label><label>导出 JSON<input id="file" type="file" accept=".json,application/json"></label></div><div class="actions"><button id="previewImport">预演与查错</button><button class="secondary" id="commitImport" disabled>确认导入</button></div><div id="report" class="report">选择文件后,预演将显示内容数量、URL 错误和复杂字段待复核事项。</div></section>
<section><div class="section-head"><h2>02 · 导入风险复核</h2><small>处理插件字段与内容关系;迁移站上线前必须清零</small></div><div id="reviews" class="job-list">暂无待复核事项</div><p class="muted">复核人必须填写处理说明;仅更改记录状态不会自动修复旧站内容,请人工检查预览。</p></section></div>
<div class="two"><section><div class="section-head"><h2>03 · 生产构建任务</h2><small>由独立 Worker 处理</small></div><p class="muted">保存内容后提交任务。相同的构建请求会去重,构建可取消、重试或在超时后恢复。</p><div class="actions"><button id="build">提交异步构建</button><a id="edit" class="secondary link" href="#">打开网站编辑器 ↗</a></div><div id="jobList" class="job-list">加载中…</div></section></div>
<div class="two"><section><div class="section-head"><h2>04 · 构建质量与审批</h2><small>分权审核发布</small></div><div id="buildList" class="job-list"></div><h3>发布审批记录</h3><div id="approvals" class="job-list"></div></section><section><div class="section-head"><h2>05 · 远程发布</h2><small>HTTPS 签名发布 Agent</small></div><p class="muted">仅能部署已审批、质量检查通过的构建;目标服务器地址由管理员提前设置,不能在这里随意填写。</p><div class="fields"><label>目标环境<select id="target"><option value="staging">测试环境 staging</option><option value="production">生产环境 production</option></select></label><label>已构建版本<select id="releaseBuild"></select></label></div><div class="actions"><button id="deploy">发布所选版本</button><button class="secondary" id="refreshRelease">刷新部署历史</button></div><h3>已发布版本</h3><div id="releases" class="job-list">暂无部署记录</div></section></div>
<div class="two"><section><div class="section-head"><h2>04 · 构建质量与审批</h2><small>分权审核发布</small></div><div id="buildList" class="job-list"></div><h3>发布审批记录</h3><div id="approvals" class="job-list"></div></section><section><div class="section-head"><h2>05 · 远程发布</h2><small>Agent / SFTP / S3</small></div><p class="muted">只能部署已审批且质量检查通过的构建;发布方式由管理员预先配置,S3 需指针感知的 HTTPS 网关。</p><div class="fields"><label>目标环境<select id="target"><option value="staging">测试环境 staging</option><option value="production">生产环境 production</option></select></label><label>已构建版本<select id="releaseBuild"></select></label></div><div class="actions"><button id="deploy">发布所选版本</button><button class="secondary" id="refreshRelease">刷新部署历史</button></div><p class="muted" id="releaseBackend" aria-live="polite">读取发布目标配置…</p><h3>已发布版本与回滚</h3><div id="releases" class="job-list">暂无部署记录</div></section></div>
<footer>企业运行与迁移中心 · 第三方 CMS 复杂插件、PostgreSQL/Redis 和公网部署应分别完成真实环境验收。</footer>
</main></div></body></html>
+7 -1
View File
@@ -4,7 +4,7 @@
[hidden]{display:none!important}.tool-switch{display:flex;flex-wrap:wrap;gap:9px;background:#fff;padding:12px;margin:18px 0 0;border-radius:12px;border:1px solid #e1e8f1}.tool-btn{background:#f6f8fc;color:#50627c;border-color:transparent;padding:11px 14px}.tool-btn.active{background:#e8f0ff;color:#174ea7;border:1px solid #a5c5f5}.tool-help{font-size:12px;color:#64748b;margin:12px 5px 16px}.advanced-layout{grid-template-columns:minmax(0,1fr)}.advanced-layout>.panel{width:100%}.nav a:first-child{border:1px solid #7793bd;padding:9px 11px;border-radius:7px;background:#fff1}@media(max-width:720px){.tool-switch{gap:5px}.tool-btn{padding:9px 11px}}
</style><link rel="stylesheet" href="/ui/professional-ui.css"><script defer src="/ui/professional-ui.js"></script></head><body data-sf-page="production">
<header><div><h1>SiteForge · 网站生产控制台 <span style="font-size:11px;color:#acd5ff">V8.11 · 高级工具</span></h1><small>整站模板 · 数据批次导入 · 可审计回滚 · 静态构建</small></div><div class="nav"><a href="/">返回网站工作台</a><a href="/factory-studio">页面编辑器</a><a href="/theme-gallery">主题中心</a><a href="/operations">任务中心</a></div></header>
<header><div><h1>SiteForge · 网站生产控制台 <span style="font-size:11px;color:#acd5ff">V8.21 · 高级工具</span></h1><small>整站模板 · 数据批次导入 · 可审计回滚 · 静态构建</small></div><div class="nav"><a href="/">返回网站工作台</a><a href="/factory-studio">页面编辑器</a><a href="/theme-gallery">主题中心</a><a href="/operations">任务中心</a></div></header>
<main>
<div class="panel"><div class="row" style="justify-content:space-between"><div><h2>选择要操作的网站</h2><div class="muted">所有批次、模板和构建记录均按网站隔离;新网站可先在网站工作台创建。</div></div><select id="site" style="max-width:410px" aria-label="选择网站"></select></div></div>
<section class="kpi"><article><span class="muted">当前网站</span><strong id="siteName">—</strong></article><article><span class="muted">内容记录</span><strong id="entryCount">—</strong></article><article><span class="muted">导入批次</span><strong id="batchCount">—</strong></article></section>
@@ -51,6 +51,11 @@
<button id="wpCancel" class="danger">取消迁移</button>
<button id="wpReload">刷新状态</button><button id="wpAudit">对比最新构建的 URL/SEO</button>
</div>
<div class="row" style="margin-top:12px;align-items:flex-end">
<div style="flex:2;min-width:250px"><label for="wpPreflightFile">V8.20 离线 WXR 兼容预检(不会导入或修改内容)</label><input id="wpPreflightFile" type="file" accept=".xml,application/xml"></div>
<button id="wpPreflight" class="primary">检查旧 URL / Elementor / ACF / 动态功能</button>
</div>
<pre id="wpPreflightResult" class="message">上传有权迁移的 WordPress WXR 文件,可检查路径冲突、复杂字段和动态功能。预检不等于迁移完成。</pre>
<pre id="wpResult" class="message">选择站点并新建迁移会话。每页请求独立保存检查点;采集完成后可明确审核 URL、SEO、缺失内容。</pre>
</section>
@@ -128,6 +133,7 @@ $('preflight').onclick=()=>applyTemplate(false);$('apply').onclick=()=>applyTemp
$('build').onclick=async()=>{try{show('buildResult',await api(`/api/sites/${current()}/builds`,'POST',{mirror_images:false}))}catch(e){show('buildResult','错误:'+e.message)}};
async function loadWpSessions(){try{if(!current())return;const result=await api(`/api/migrations/sites/${current()}/wordpress/sessions`);const select=$('wpSession');const chosen=select.value;select.replaceChildren(...result.items.map(s=>opt(s.id,`${s.origin} · ${s.state} · ${s.fetched} 条`)));if(result.items.some(s=>s.id===chosen))select.value=chosen;if(select.value){const item=await api(`/api/migrations/sites/${current()}/wordpress/sessions/${select.value}`);show('wpResult',item)}}catch(e){show('wpResult','查询错误:'+e.message)}}
async function wpAction(action){const sid=$('wpSession').value;if(!sid)throw Error('请先创建迁移会话');return api(`/api/migrations/sites/${current()}/wordpress/sessions/${sid}/${action}`,'POST',{})}
$('wpPreflight').onclick=async()=>{try{if(!current())throw Error('请先选择网站');const file=$('wpPreflightFile').files[0];if(!file)throw Error('请选择 WordPress WXR XML 文件');const form=new FormData();form.append('file',file);const result=await api(`/api/migrations/sites/${current()}/wordpress/wxr-preflight`,'POST',form);show('wpPreflightResult',result)}catch(e){show('wpPreflightResult','兼容性预检失败:'+e.message)}};
$('wpCreate').onclick=async()=>{try{const data=await api(`/api/migrations/sites/${current()}/wordpress/sessions`,'POST',{origin:$('wpOrigin').value,max_items:Number($('wpLimit').value)});await loadWpSessions();$('wpSession').value=data.id;show('wpResult',data)}catch(e){show('wpResult','错误:'+e.message)}};
let wpAutoRunning=false;
$('wpAuto').onclick=async()=>{if(wpAutoRunning)return;wpAutoRunning=true;let count=0;try{while(wpAutoRunning&&count<200){const r=await wpAction('advance');show('wpResult',r);count++;if(['ready','completed','cancelled','limit_reached'].includes(r.state))break;await new Promise(done=>setTimeout(done,350))}await loadWpSessions()}catch(e){show('wpResult','暂停:'+e.message)}finally{wpAutoRunning=false}};
+1
View File
@@ -16,6 +16,7 @@
<button data-view="design" class="nav-item"><span class="nav-ico">◈</span><span>外观与设计</span></button>
<button data-view="import" class="nav-item"><span class="nav-ico">⇩</span><span>导入与迁移</span></button>
<button data-view="build" class="nav-item"><span class="nav-ico">↗</span><span>构建与交付</span></button>
<button data-view="languages" class="nav-item"><span class="nav-ico">文</span><span>多语言管理</span></button>
<button data-view="settings" class="nav-item"><span class="nav-ico">⚙</span><span>网站设置</span></button>
</div>
<details class="advanced"><summary><span class="nav-ico">⌘</span>高级工具 <span class="chevron">⌄</span></summary>