From: root Date: Fri, 21 Aug 2026 05:04:41 +0000 (+0800) Subject: v0.30: 修复领域方案页面预览空白(dashboard-renderer 按容器隔离 echarts 实例)+ 预览卡片点击放大 + 设计文档同步 X-Git-Url: http://acesimba.cloud/gitweb/?a=commitdiff_plain;h=31dd2af6ed316643bf3612942e594f46f25d0e17;p=StationBuilder.git v0.30: 修复领域方案页面预览空白(dashboard-renderer 按容器隔离 echarts 实例)+ 预览卡片点击放大 + 设计文档同步 --- diff --git a/app/globals.css b/app/globals.css index 0818ed2..b3d4a65 100644 --- a/app/globals.css +++ b/app/globals.css @@ -236,11 +236,13 @@ .domain-preview-canvas .dashboard-thumb-live{min-height:430px} .domain-preview-feedback{margin-top:14px} .domain-preview-feedback textarea{width:100%;border:1px solid #cfd3d7;border-radius:3px;padding:9px;resize:vertical;line-height:1.6;margin-top:6px} -.domain-review-pages{display:grid;grid-template-columns:repeat(2,1fr);gap:12px} -.domain-review-page{border:1px solid #e0e3e5;background:#fff;overflow:hidden} -.domain-review-page .dashboard-thumb-live{border-bottom:0} -.domain-review-page>div{padding:9px 11px;display:flex;align-items:center;justify-content:space-between} +.domain-review-pages{display:grid;grid-template-columns:1fr;gap:12px} +.domain-review-page{border:1px solid #e0e3e5;background:#fff;overflow:hidden;transition:border-color .15s,box-shadow .15s} +.domain-review-page:hover{border-color:var(--blue);box-shadow:0 2px 8px #008fd21f} +.domain-review-page .dashboard-thumb-live{border-bottom:0;min-height:240px} +.domain-review-page>div{padding:9px 11px;display:flex;align-items:center;justify-content:space-between;gap:8px} .domain-review-page h4{margin:0;font-size:13px} +.domain-review-page .review-zoom{font-size:10px;color:#008ed2;white-space:nowrap} .domain-card .card-actions{display:flex;gap:14px;margin-top:2px;padding-top:8px;border-top:1px solid #f0f2f3} /* ===== 设备模版 ===== */ diff --git a/app/page.tsx b/app/page.tsx index c0c411d..4c8d998 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1742,7 +1742,7 @@ function DomainWizard({id,doms,go,notify,onBack,viewCompletePrompt,viewCompleteA pageStyles={pageStyles} styleId={m.style_id??null} onSelectStyle={selectStyle} />} {step==="opsflow" && } - {step==="generate" && goStep("functions")} onKnowledge={()=>goStep("knowledge")} onConfirm={()=>notify("方案已生成并保存,可返回上一步继续调整")}/>} + {step==="generate" && goStep("functions")} onKnowledge={()=>goStep("knowledge")} onConfirm={()=>notify("方案已生成并保存,可返回上一步继续调整")} onPreviewPage={setPreviewPage}/>} {step==="expert" && }
{curIdxgoStep(DOMAIN_STEPS[curIdx+1].key)}>下一步}{curIdx===DOMAIN_STEPS.length-1 && }
@@ -1821,7 +1821,7 @@ function FunctionsStep({muTree,grouped,effSelected,onToggleMu,pages,onAddPage,on ; } -function DomainGenerateReview({tName,assets,effSelected,grouped,pages,docs,onBack,onKnowledge,onConfirm}:{tName:string;assets:DomainAsset[];effSelected:string[];grouped:any[];pages:DomainPage[];docs:any[];onBack:()=>void;onKnowledge?:()=>void;onConfirm:()=>void}){ +function DomainGenerateReview({tName,assets,effSelected,grouped,pages,docs,onBack,onKnowledge,onConfirm,onPreviewPage}:{tName:string;assets:DomainAsset[];effSelected:string[];grouped:any[];pages:DomainPage[];docs:any[];onBack:()=>void;onKnowledge?:()=>void;onConfirm:()=>void;onPreviewPage?:(p:DomainPage)=>void}){ const reviewRows:[string,string,string,string?,(()=>void)?][]=[ ["方案",tName,"","返回修改",onBack], ["资产",`${assets.filter(a=>a.device_template_id).length} 类 / ${assets.reduce((n,a)=>n+Number(a.count||0),0)} 个设备`,"3 层资产结构",""], @@ -1841,7 +1841,7 @@ function DomainGenerateReview({tName,assets,effSelected,grouped,pages,docs,onBac

以下为方案中已配置的 Cube 扩展页面预览(点击可放大查看)。如需调整,请返回「功能与菜单」。

-
{pages.length===0?
暂无 Cube 页面
: pages.map(p=>
{p.status==="generated"?"AI已生成":p.status==="uploaded"?"已上传":"未生成"}{p.source?` · ${p.source==="ai"?"AI":p.source==="upload"?"上传":"手动"}`:""}

{p.name}

)}
+
{pages.length===0?
暂无 Cube 页面
: pages.map(p=>
onPreviewPage&&onPreviewPage(p)}>
{p.status==="generated"?"AI已生成":p.status==="uploaded"?"已上传":"未生成"}{p.source?` · ${p.source==="ai"?"AI":p.source==="upload"?"上传":"手动"}`:""}

{p.name}

⛶ 点击放大
)}
; diff --git a/data/db/StationBuilder.sqlite b/data/db/StationBuilder.sqlite index bd413b6..d6e9dcc 100644 Binary files a/data/db/StationBuilder.sqlite and b/data/db/StationBuilder.sqlite differ diff --git a/public/dashboard-renderer.js b/public/dashboard-renderer.js index 4e3ec6b..8c88fa3 100644 --- a/public/dashboard-renderer.js +++ b/public/dashboard-renderer.js @@ -4,6 +4,7 @@ (function () { var COLS = 24; var ROWS = 25; // 24 列网格,整页 25 行贴合 1920×1080 + var _curInst = []; // 当前这一次渲染产生的 echarts 实例(按容器维度管理,避免误杀其它缩略图) function el(tag, style, text) { var e = document.createElement(tag); @@ -230,8 +231,7 @@ content.appendChild(div); var inst = echarts.init(div, undefined, { renderer: "canvas" }); inst.setOption(option); - if (!renderDashboard._instances) renderDashboard._instances = []; - renderDashboard._instances.push(inst); + _curInst.push(inst); } function renderPanel(cell, p, ctx) { @@ -266,8 +266,9 @@ function renderDashboard(json, container, options) { options = options || {}; - if (renderDashboard._instances) renderDashboard._instances.forEach(function (i) { try { i.dispose(); } catch (e) {} }); - renderDashboard._instances = []; + // 仅销毁「本容器」上一次渲染产生的 echarts 实例;禁止全局销毁,否则同一页面内的多张缩略图会互相误杀导致图表空白。 + if (container.__dashInst) { container.__dashInst.forEach(function (i) { try { i.dispose(); } catch (e) {} }); container.__dashInst = null; } + _curInst = []; container.innerHTML = ""; if (!json || !json.panels || !json.panels.length) { container.innerHTML = '
(看板无内容,可能尚未生成)
'; @@ -315,6 +316,7 @@ root.appendChild(card); }); container.appendChild(root); + container.__dashInst = _curInst; if (options.fit) { // 按宽度铺满(与真实 Cube 一致:填满屏宽、垂直滚动),避免整体被等比缩小显得“压缩”。 diff --git a/public/design-doc.html b/public/design-doc.html index b6b3647..75e6da7 100644 --- a/public/design-doc.html +++ b/public/design-doc.html @@ -111,7 +111,7 @@
-

文档版本:v0.29 最后更新:2026-08-21 修改人:wang.yu 状态:开发中(演示数据已标注)

+

文档版本:v0.30 最后更新:2026-08-21 修改人:wang.yu 状态:开发中(演示数据已标注)

StationBuilder 产品与交互设计方案

1. 文档说明

@@ -759,6 +759,7 @@

10.1 版本变更记录(Changelog)

+
版本日期修改人说明
v0.302026-08-21wang.yu修复领域方案「生成保存」页面预览全部空白:根因为 public/dashboard-renderer.js 的 renderDashboard 每次渲染开头执行 renderDashboard._instances.forEach(i=>i.dispose()) 把全部全局 echarts 实例销毁——同一方案 11 张页面缩略图顺序渲染时,后一张的渲染把前一张的图表实例 dispose 掉,仅最后一张卡片保留图表、其余均为空白(DOM 有内容但 0 个 <canvas>)。修复:废弃全局实例数组,改为按容器隔离——每个容器记录自身 container.__dashInst,渲染前仅销毁本容器旧实例并清空 innerHTML,渲染后回挂本容器实例;新增模块级 _curInst 收集本次渲染的实例。现已验证污水厂(橙色风格)方案 11 张卡片全部渲染出图表(共 43 个 canvas,每卡 ≥0)。附带增强:① 生成确认页「领域方案页面」列表卡片改为整宽单列大图(.domain-review-pages 由两列改 1 列,.dashboard-thumb-live 最小高度 115px→240px),并整体可点击——点击卡片经新增 onPreviewPage 入参打开 DomainPagePreviewModal 全屏预览(含图表),卡片右下角加「⛶ 点击放大」提示。② globals.css 增补 .domain-review-page hover 描边与 .review-zoom 提示样式。Playwright E2E:11 卡全部有 canvas、点开预览 modal 含 2 个 canvas + 361 字正文,0 控制台报错。
v0.292026-08-21wang.yu领域方案库 / 站点「功能与菜单 → Cube 扩展页面」遗留问题修复与体验提升:① 页签改名与排序:站点与领域两处「Cube 扩展页面」页签统一更名为「领域扩展页面」,并调整顺序置于「MU 标准页面」之前(默认进入该页签);文档同步。② 生成按钮醒目化:两处扩展页面表格的「生成」按钮文案升级为「AI生成」、生成中显示「AI生成中」,并采用醒目渐变蓝紫样式(.btn-ai,覆盖 .cube-actions button 白底默认态)。③ 统一生成:领域方案「领域扩展页面」表格上方新增「⚡ AI 统一生成全部(N)」按钮(N = 未生成页面数),一键对所有「未生成」且未在生成中的页面并发触发 generatePage;后端按每页独立锁 + AI_CONCURRENCY=2 信号量排队,各自页面按钮同步显示「AI生成中」,免去逐个点击(仅保留单页生成)。④ 图片加载提速:后端 db-sidecar 的 handlePageStyleImage / handleDeviceTemplateImage 新增 ?w= 宽度参数,经 sharp 生成 WebP 缩略图并落盘缓存(mtime 校验失效重生成),原图约 1.4MB → 缩略图约 9KB(提速约 150 倍);前端样式选择器(?systemctl restart stationbuilder-db 生效(新增 sharp 依赖,Node 原生模块已就绪)。Playwright E2E:领域方案进入「领域扩展页面」页签确认置首、10 个页面按钮显示「AI生成」、统一按钮计数正确;新增待生成页面后点「AI 统一生成全部」其按钮即时翻为「AI生成中」、其余已生成页保持「AI生成」,0 控制台报错;页面样式库 5 张缩略图均带 ?w=、缩略图接口实测 1355KB→8.9KB。
v0.282026-08-21wang.yu设备模板「采集点」AI 智能补全(两处入口):① 领域方案「AI 资产补全」自动创建设备模板后,后端立即以设备名称为其生成 6~12 个合适采集点并写库(resolveDeviceTemplate 返回 created:true 时触发;追加模式,单个失败不影响整体,推导过程回流到资产补全控制台;已存在模板不重复补全)。② 设备模板详情(DeviceTemplateCard)新增「AI 智能补全 · 采集点」面板:「开始补全」调用新增接口 POST /api/db/device-templates/:id/complete-collection-points(流式 NDJSON,codebuddy 生成 + 写库,前端补全后重新拉取详情刷新表格),「查看提示词」GET /api/db/device-templates/:id/complete-collection-points-prompt 弹窗展示完整组合提示词。后端新增 buildCompleteCollectionPointsPrompt / extractCollectionPoints / generateDeviceTemplatePoints(按 name 去重追加,不覆盖用户已录入采集点;数值范围优先取预设集合);前端新增 DeviceTemplateCard 的 cpAi=useStreamComplete() 流式面板,并透传 onViewCpPrompt 复用 App 级提示词弹窗;资产补全前端超时守卫由 6 分钟放宽到 10 分钟(因额外补全采集点)。Playwright E2E:临时设备点「开始补全」→ 实际运行 codebuddy 生成 12 个采集点并持久化(GET 详情 collection_points.length=12),控制台显示「✓ 已补全 12 个采集点」,0 控制台报错,临时设备已清理。设计文档同步更新 4.12 / 新增 4.12.2、版本 v0.28。
v0.272026-08-21wang.yu领域编辑迁移至左侧导航栏:① 移除「领域方案库」首页的「管理领域」面板,首页方案列表相应上移(直接进入列表,不再被管理面板挤占首屏)。② 领域增删改入口迁至左侧「领域方案库模块 → 领域分类」分组,UI 与现有领域分类导航项一致:每个领域行尾加 × 删除按钮(仅非「其他」领域,点击经 window.confirm 二次确认后 DELETE /api/domains/:id,其下方案归入「其他」,随后 reloadDomains 刷新导航);行末新增「+ 新增领域」按钮,点击展开行内输入框(最多 6 字,Enter 确认 / Esc 取消),POST /api/domains 后刷新导航。③ 修复 addDomain 原只更新前端 domains 字符串数组、未刷新导航用 domainOptions 导致新增后导航不出现的问题——现改由 reloadDomains 统一刷新两个 state。④ globals.css 新增 .domain-nav-row / .domain-nav-del / .domain-nav-ok / .domain-nav-add / .domain-nav-input 等样式,保持与导航按钮一致的 41px 行高与激活态。Playwright 验证:面板已移除、导航显示 6 领域行(5 个 × + 1 个 +)、新增/删除临时领域均生效、「其他」无删除按钮、0 控制台报错。后续微调:领域名按钮与 ×/+ 按钮原共用 `.domain-nav-row>button` 的 `flex:1` 导致 × 也等分给到约 95px、过滤按钮被压窄至 95px、4 字以内领域名在用户浏览器中换行;改为 `flex:1` 仅作用于过滤按钮(`:not(.domain-nav-del):not(.domain-nav-ok)`),×/✓ 固定 30px,领域名加 `white-space:nowrap` 并包入 `.domain-nav-name` 超出省略号,4 字以内领域名稳定单行显示。