]> acesimba.cloud Git - StationBuilder.git/commitdiff
feat: 新增仪表盘示意页(左侧菜单扩展页面·仪表盘,仅展示压缩示意图)
authorroot <root@localhost.localdomain>
Wed, 9 Sep 2026 06:36:15 +0000 (14:36 +0800)
committerroot <root@localhost.localdomain>
Wed, 9 Sep 2026 06:36:15 +0000 (14:36 +0800)
app/globals.css
app/page.tsx
data/db/StationBuilder.sqlite
public/dashboard-preview.jpg [new file with mode: 0644]
public/design-doc-assets/dashboard.png [new file with mode: 0644]
public/design-doc.html
scripts/shot-dashboard.mjs [new file with mode: 0644]

index cc13220393499409a42415650e1fc1974593485f..11133dd95c28f3265fc0ec4ca88ceaafd0d94bbd 100644 (file)
@@ -355,6 +355,15 @@ html,body{font-family:Roboto,"Noto Sans SC","Microsoft YaHei",Arial,sans-serif;f
 .badge.src-upload{display:inline-flex;align-items:center;gap:2px;background:#e7f5fc;color:#007cc3;border:1px solid #b9e0f4;border-radius:10px;padding:2px 9px;font-size:10px}
 .badge.src-ai{display:inline-flex;align-items:center;gap:2px;background:#f3e8ff;color:#7a3cc0;border:1px solid #dcc4f3;border-radius:10px;padding:2px 9px;font-size:10px}
 .badge.src-manual{display:inline-flex;align-items:center;gap:2px;background:#f1f2f3;color:#6b7177;border:1px solid #dfe2e4;border-radius:10px;padding:2px 9px;font-size:10px}
+
+/* 扩展菜单可点击项 */
+.extension-menu-section .extension-menu-item.clickable{cursor:pointer}
+.extension-menu-section .extension-menu-item.clickable:hover{background:#e8f4fb;color:var(--company-primary)}
+.extension-menu-section .extension-menu-item.clickable.active{background:#d7eef9;color:var(--company-primary);font-weight:500}
+
+/* 仪表盘页面 */
+.dashboard-page{min-height:calc(100vh - 56px);padding:20px;background:#f7f8f9;display:flex;align-items:flex-start;justify-content:center}
+.dashboard-page img{display:block;max-width:100%;height:auto;background:#fff;box-shadow:0 4px 12px #00000012;border:1px solid #e3e6e8;border-radius:4px}
 .domain-preview-canvas{background:#1e2630;height:64vh;border-radius:6px;overflow:auto}
 .domain-preview-canvas .dashboard-thumb-live{min-height:0}
 .domain-preview-feedback{margin-top:14px}
index d9e4bf5fd1d62ac3db95982d5b382590623d69fe..f8f80cf702963cad162e595958ddd08ed0f38dba 100644 (file)
@@ -4,7 +4,7 @@ import { useEffect, useMemo, useRef, useState, type PointerEvent as ReactPointer
 import { Button as AntButton, Drawer, Dropdown, QRCode, Tag } from "antd";
 import { AimOutlined, ApartmentOutlined, BarsOutlined, BlockOutlined, CaretDownOutlined, CaretRightOutlined, ControlOutlined, CopyOutlined, DeleteOutlined, DownloadOutlined, EditOutlined, EllipsisOutlined, EyeOutlined, LinkOutlined, PlusOutlined, PlusSquareOutlined, RobotOutlined, SearchOutlined, SnippetsOutlined, UploadOutlined, ZoomInOutlined, ZoomOutOutlined } from "@ant-design/icons";
 
-type PageKey = "login" | "projects" | "siteEdit" | "basic" | "assets" | "pageConfig" | "functions" | "generate" | "result" | "preview" | "domains" | "domainDetail" | "styleLibrary" | "deviceTemplates" | "toolSettings" | "domainSettings";
+type PageKey = "login" | "projects" | "siteEdit" | "basic" | "assets" | "pageConfig" | "functions" | "generate" | "result" | "preview" | "domains" | "domainDetail" | "styleLibrary" | "deviceTemplates" | "toolSettings" | "domainSettings" | "dashboard";
 type ModalKey = "" | "feedback" | "download" | "history" | "prompt" | "completePrompt";
 
 const meta: Record<PageKey, { label: string; icon: string; subtitle: string }> = {
@@ -24,6 +24,7 @@ const meta: Record<PageKey, { label: string; icon: string; subtitle: string }> =
   styleLibrary:{label:"页面样式",icon:"❖",subtitle:"管理可复用的 Cube 页面视觉样式与预览资源"},
   toolSettings:{label:"内部工具配置",icon:"",subtitle:"配置 StationBuilder 使用的内部能力、AI 服务和默认模型"},
   domainSettings:{label:"领域配置",icon:"",subtitle:"维护领域分类,供领域方案与站点项目统一使用"},
+  dashboard:{label:"仪表盘",icon:"◈",subtitle:"仅展示仪表盘示意图片"},
 };
 
 
@@ -532,6 +533,7 @@ export default function Home(){
   const projectArea = (["projects","siteEdit","basic","assets","pageConfig","functions","generate","result","preview"] as PageKey[]).includes(page);
   const resourceArea = (["domains","domainDetail","deviceTemplates","styleLibrary"] as PageKey[]).includes(page);
   const systemArea = (["toolSettings","domainSettings"] as PageKey[]).includes(page);
+  const extensionArea = (["dashboard"] as PageKey[]).includes(page);
   const siteEditMeta = meta[siteEditSection];
   const manualAssetActive = assetConfigMode==="manual" && (page==="assets" || (page==="siteEdit"&&siteEditSection==="assets"));
 
@@ -564,10 +566,11 @@ export default function Home(){
             <button className={page==="siteEdit"?"active":""} onClick={()=>go("siteEdit",sites[0]?.id)}><span>站点编辑</span></button>
           </div>}
         </section>
-        <section className="menu-section extension-menu-section" aria-label="扩展页面菜单">
+        <section className={extensionArea?"menu-section extension-menu-section current":"menu-section extension-menu-section"} aria-label="扩展页面菜单">
           <button className="menu-section-title" aria-expanded={extensionNavOpen} onClick={()=>setExtensionNavOpen(v=>!v)}><span>扩展页面</span>{extensionNavOpen?<CaretDownOutlined/>:<CaretRightOutlined/>}</button>
           {extensionNavOpen&&<div className="menu-section-items extension-menu-items">
-            <div className="extension-menu-item">仪表盘</div>
+            <button className={page==="dashboard"?"extension-menu-item clickable active":"extension-menu-item clickable"} onClick={()=>go("dashboard")}>仪表盘</button>
+            <div className="extension-menu-item">数据源</div>
             <div className="extension-submenu">
               <button className="extension-menu-item extension-submenu-title" aria-expanded={reportNavOpen} onClick={()=>setReportNavOpen(v=>!v)}><span>报表</span>{reportNavOpen?<CaretDownOutlined/>:<CaretRightOutlined/>}</button>
               {reportNavOpen&&<div className="extension-third-menu">
@@ -635,6 +638,7 @@ export default function Home(){
     </div></aside>
     <main className="workspace"><div className="crumb">iStation StationBuilder / <b>{["domains","domainDetail","styleLibrary","deviceTemplates","toolSettings","domainSettings"].includes(page)?meta[page].label:page==="projects"?"站点列表":page==="siteEdit"?`站点管理 / 站点编辑 / ${siteEditMeta.label}`:`站点详情 / ${meta[page].label}`}</b></div><div className="page-heading"><div><h1>{page==="siteEdit"?siteEditMeta.label:meta[page].label}</h1><p>{page==="siteEdit"?siteEditMeta.subtitle:meta[page].subtitle}</p></div><div>{page==="projects"&&<Button onClick={()=>go("basic","new")}>+ 新建站点</Button>}{page==="siteEdit"&&<label className="site-edit-switcher"><span>当前站点</span><select value={siteId??""} onChange={async e=>{const id=Number(e.target.value);await loadSite(id);window.history.replaceState({},"",`?page=siteEdit&id=${id}`);}}>{sites.map(item=><option key={item.id} value={item.id}>{item.name||"未命名站点"}</option>)}</select></label>}{page==="domains"&&openDomainTemplateId==null&&<Button onClick={()=>setOpenDomainTemplateId("create")}>+ 新增领域方案</Button>}{page==="domains"&&openDomainTemplateId!=null&&<Button secondary onClick={()=>setOpenDomainTemplateId(null)}>← 返回方案列表</Button>}{domainFilter!=null&&page==="domains"&&openDomainTemplateId==null&&<Button secondary onClick={()=>{setDomainFilter(null);}}>清除领域筛选:{domainOptions.find(d=>d.id===domainFilter)?.name||""}</Button>}{page==="domainSettings"&&<Button onClick={()=>{setNewDomainName("");setAddingDomain(true);}}>+ 新增领域</Button>}</div></div>{page==="siteEdit"&&<SiteEditTabs section={siteEditSection} onChange={setSiteEditSection}/>} {SITE_EDIT_STEPS.includes(page as SiteEditSection)&&<Steps page={page} go={go}/>}<div className="content">
       {page==="projects"&&<Projects view={view} setView={setView} sites={sites} loading={loading} go={go} loadSite={loadSite} notify={notify} onDelete={deleteSites}/>}
+      {page==="dashboard"&&<Dashboard/>}
       {(page==="domains"||page==="domainDetail")&&(openDomainTemplateId!=null
         ? <DomainDetail id={openDomainTemplateId==="create"?null:openDomainTemplateId} go={go} notify={notify} onBack={()=>setOpenDomainTemplateId(null)} onCreated={setOpenDomainTemplateId} viewCompletePrompt={viewDtCompletePrompt} viewCompleteAssetsPrompt={viewDtCompleteAssetsPrompt} viewCompleteCubesPrompt={viewDtCompleteCubesPrompt} pageStyles={pageStyles}/>
         : <CompanyDomainLibrary go={go} notify={notify} onOpen={(id:number)=>setOpenDomainTemplateId(id)} domainFilter={domainFilter} domainOptions={domainOptions}/>)}
@@ -2502,6 +2506,10 @@ function CompanyPageStyleLibrary({pageStyles,loadPageStyles,notify}:{pageStyles:
   </>;
 }
 
+function Dashboard(){
+  return <div className="content dashboard-page"><img src="/t/stationbuilder/dashboard-preview.jpg" alt="仪表盘示意" loading="lazy"/></div>;
+}
+
 function Modal({type,close,notify,promptData,feedbackCube,refineCube,refining,refineLog}:{type:Exclude<ModalKey,"">;close:()=>void;notify:(m:string)=>void;promptData?:{name:string;text:string}|null;feedbackCube?:Cube|null;refineCube?:(cube:Cube, feedback:string)=>void;refining?:boolean;refineLog?:string;}){
   const [feedbackText,setFeedbackText]=useState("");
   const refineConsoleRef=useRef<HTMLPreElement>(null);
index d87700d82d7f68d6529db3b8ed2be124d5caf449..0d8130d093faeae991940835bd9f9bac6e9086b8 100644 (file)
Binary files a/data/db/StationBuilder.sqlite and b/data/db/StationBuilder.sqlite differ
diff --git a/public/dashboard-preview.jpg b/public/dashboard-preview.jpg
new file mode 100644 (file)
index 0000000..938c4d0
Binary files /dev/null and b/public/dashboard-preview.jpg differ
diff --git a/public/design-doc-assets/dashboard.png b/public/design-doc-assets/dashboard.png
new file mode 100644 (file)
index 0000000..1e666d0
Binary files /dev/null and b/public/design-doc-assets/dashboard.png differ
index 0c99f106207d49070e90cecb00dcdf12d8ad30fd..5caac39ba5e517f2df1532cfe997d668220c3e43 100644 (file)
   <figure><img src="design-doc-assets/device-detail.png" alt="设备模版详情"/><figcaption>设备模版详情:设备基本信息、图片、采集点表格编辑。</figcaption></figure>
   <figure><img src="design-doc-assets/style-library.png" alt="页面样式库"/><figcaption>页面样式库:样式缩略图网格与新增/编辑入口。</figcaption></figure>
 
+  <h3>4.14 仪表盘(dashboard)</h3>
+  <p>位于左侧「扩展页面」分组下的「仪表盘」菜单项(点击进入,当前页高亮)。该页定位为<b>示意展示页</b>:打开后仅居中展示一张仪表盘示意图片(已压缩至约 66KB,宽 1440,避免影响加载速度),不含其它交互元素。图片随页面懒加载(<code>loading="lazy"</code>)。</p>
+  <figure><img src="design-doc-assets/dashboard.png" alt="仪表盘示意页"/><figcaption>仪表盘页:仅居中展示仪表盘示意图片。</figcaption></figure>
+
   <h2>5. 生成流程(端到端)</h2>
   <div class="flow">
     <ol>
   <h3>10.1 版本变更记录(Changelog)</h3>
   <table class="changelog">
     <tr><th>版本</th><th>日期</th><th>修改人</th><th>说明</th></tr>
+    <tr><td>v0.34</td><td>2026-09-09</td><td>wang.yu</td><td>新增「仪表盘」页面:① 左侧「扩展页面」分组下的「仪表盘」由占位文本改为可点击菜单项(<code>.extension-menu-item.clickable</code>),点击进入新页 <code>dashboard</code>(当前页高亮、分组自动展开)。② 页面定位为<b>示意展示页</b>:<code>Dashboard</code> 组件仅居中展示一张仪表盘示意图片(<code>public/dashboard-preview.jpg</code>,源图经 sharp 压至 1440×729 / 66KB、渐进式 JPEG,<code>loading="lazy"</code> 懒加载,避免影响打开速度),不含其它交互。③ 图片存放于 <code>public/</code> 并随 Vite public 资源在 basePath <code>/t/stationbuilder/</code> 下提供。④ 设计文档新增 4.14 节并附截图(<code>dashboard.png</code>),Changelog 记 v0.34。</td></tr>
     <tr><td>v0.33</td><td>2026-09-09</td><td>wang.yu</td><td>「设计方案」入口与导航栏对齐:① <b>入口位置变更</b>:由早期顶栏/页脚链接改为固定在<b>左侧菜单最底部</b>(`.company-side-nav` 最后一个 flex 子项,<code>margin-top:auto</code> 推至底),成为常驻的菜单项而非浮层,避免遮挡正文。② <b>按钮样式</b>:鲜明紫渐变(<code>#7e57c2 → #ab47bc</code>)、10 号字、圆角小按钮(<code>.design-doc-link</code>),点击 <code>target="_blank"</code> 新标签打开 <code>https://aidemo.istation.cloud/t/stationbuilder/design-doc.html</code>。③ <b>设计文档同步</b>:第 3 章新增 3.5「设计方案入口(左侧菜单底部)」并附菜单截图(<code>menu-design-doc.png</code>);第 6 章「AI 问答」附 AI 助手对话框截图(<code>ai-dialog.png</code>);第 7 章「模态框」附「新增领域」对话框截图(<code>dialog-add-domain.png</code>);第 4 章各页面说明截图全部重新抓取(projects/basic/assets/functions/generate/result/preview/domains/domain-detail/styleLibrary/deviceTemplates/toolSettings/domainSettings),并对页面全量截图做样式加载校验(0 控制台 404)。④ 维护说明注释同步更新入口位置。</td></tr>
     <tr><td>v0.32</td><td>2026-08-21</td><td>wang.yu</td><td>领域方案「生成保存」步骤重构为「方案打包」:① <b>步骤改名</b>:<code>DOMAIN_STEPS</code> 末步标签「生成保存」→「方案打包」;该步骤不再提供「下一步」,仅保留「保存草稿」与本步专属「📦 打包下载」按钮(末步另保留「提交确认」以提交专家确认)。② <b>「生成范围确认」→「方案概览」多标签总览</b>:<code>DomainGenerateReview</code> 重写为单 Panel「方案概览」+ 六标签(标签上带数量徽标):<b>设备模板 N</b>(方案去重用到的设备模板列表,点击行内展开详情——调 <code>GET /api/db/device-templates/:id</code> 显示说明与采集点清单)、<b>资产结构 N</b>(按设备模板聚合的默认资产组树状预览,使用 <code>formatInstances(rule,count)</code> 展开实例)、<b>MU页面 N</b>(复用功能与菜单的 MU 树,禁用态勾选框展示已选页面)、<b>扩展页面 N</b>(已生成 Cube 页面卡片,点击整卡经 <code>onPreviewPage</code> 打开全屏预览)、<b>领域知识 N</b>(列举本方案上传的知识文档与 URL 站点)、<b>运维流程</b>(占位「待开发」+ 固定文案「运维流程编辑功能待开发,后续版本将支持在方案中配置标准运维 SOP 与告警处理流程。」)。概览头部固定展示所属领域 / 方案名称 / 方案描述 / 补充描述。③ <b>修复扩展页面预览「只能看到上半部分」</b>:<code>DomainPageThumb</code> 新增 <code>fit</code> 模式——由「仅按宽度缩放」改为「按宽高较小比例缩放」<code>s = min(w/pw, availH/rh)</code>(availH 取预览 canvas 父容器高度),canvas 高度由固定 430px 改为 <code>height:64vh; overflow:auto</code>,整页一次可见不再裁切。④ <b>「📦 打包下载」示意弹窗</b>:新增 <code>PackageModal</code>——SVG 立体包装箱(堆叠文档矩形,带投影)+ 5 张打包组成卡片(设备模板 / 资产结构 / 扩展页面文件 / 默认菜单 / 领域知识),「确认打包下载」提示「方案已打包(示意),可交付给实施团队」后关闭(本期为示意,真实文件导出待后续)。<code>globals.css</code> 增补 <code>.gen-overview / .overview-info / .overview-tabs / .dev-list / .dev-detail / .asset-tree-preview / .mu-tree / .doc-list / .ops-placeholder / .pkg-modal / .pkg-svg / .pkg-items</code> 等样式。Playwright E2E(污水厂方案,34 设备 / 11 页面):步骤标签=方案打包、Panel=方案概览、6 标签徽标正确、设备行展开显示 12 个采集点、资产树 / MU 树 / 页面卡片渲染、预览 canvas 渲染高度 576px = 缩放后整页高度(证明整页可见,不再上半部裁切)、知识 / 运维标签文案正确、本步无「下一步」、打包弹窗含 SVG + 5 组成项,0 控制台报错;另以数字机房方案验证领域知识列表确实展示已上传文档。</td></tr>
     <tr><td>v0.31</td><td>2026-08-21</td><td>wang.yu</td><td>UI 风格对齐 KB Insight 门户(Ant Design):参照 https://portal-kbinsight-wiseiot-ensaas.practice.cloud.advantech.com.cn/kb/homepage 调整 ① <b>字体</b>:由 <code>Arial,"Microsoft YaHei"</code> 改为 AntD 系统字体栈 <code>-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Microsoft YaHei"</code>,与门户一致(中文保留雅黑 / Noto)。② <b>顶部 Header</b>:高度 42px→56px,去除 4px 实色顶边,改为 3px 蓝白渐变顶线(模拟门户 <code>.bd-linear</code>,用 <code>background-clip:padding-box,border-box</code> 双层背景实现);同步 <code>.app-shell</code> 顶部内边距与 <code>.side-nav</code> 顶部偏移 42px→56px,避免布局错位。③ <b>左侧菜单选中/悬停</b>对齐 AntD:选中背景由 <code>#dff1fa</code>/<code>#e9f6fc</code> 改为 <code>rgba(0,140,214,.1)</code>(对应门户 <code>--color-blue-tree</code>/<code>--color-menu-bk</code>),选中文字 <code>#008cd6</code>,悬停浅灰 <code>rgba(0,0,0,.04)</code>(对应 <code>--color-hover-gray</code>);覆盖 <code>.side-nav&gt;button</code>、<code>.module-switch&gt;button</code>、<code>.domain-nav-row</code> 三处,菜单字号提到 14px;修复 <code>.domain-nav-row&gt;button:not()</code> 高优先级规则覆盖选中态的问题(给选中规则补 <code>:not(.domain-nav-del):not(.domain-nav-ok)</code> 提权)。主蓝 token 维持 <code>#009fe3</code>(与目标 <code>#008CD6</code> 肉眼无差,避免大范围改色不一致)。Playwright 断言:body 字体栈含 Segoe UI/Roboto、header 高度 56 且 backgroundImage 为双层渐变、三类菜单选中背景均为 <code>rgba(0,140,214,.1)</code>、0 控制台报错。</td></tr>
diff --git a/scripts/shot-dashboard.mjs b/scripts/shot-dashboard.mjs
new file mode 100644 (file)
index 0000000..77ace3f
--- /dev/null
@@ -0,0 +1,13 @@
+import { chromium } from 'playwright';
+const BASE='https://aidemo.istation.cloud/t/stationbuilder';
+const b=await chromium.launch();
+const p=await b.newPage({viewport:{width:1440,height:900}});
+const errs=[];
+p.on('console',m=>{if(m.type()==='error')errs.push(m.text());});
+await p.goto(`${BASE}/?page=dashboard`,{waitUntil:'networkidle'});
+await p.waitForSelector('.dashboard-page img',{timeout:15000});
+await p.waitForTimeout(1000);
+await p.screenshot({path:'public/design-doc-assets/dashboard.png',fullPage:true});
+console.log('saved dashboard.png');
+console.log('ERRORS',errs.slice(0,5));
+await b.close();