]> acesimba.cloud Git - StationBuilder.git/commitdiff
docs: 同步设计方案文档(刷新各页截图,新增菜单入口/AI对话框/新增领域弹窗截图)
authorroot <root@localhost.localdomain>
Wed, 9 Sep 2026 05:57:32 +0000 (13:57 +0800)
committerroot <root@localhost.localdomain>
Wed, 9 Sep 2026 05:57:32 +0000 (13:57 +0800)
19 files changed:
public/design-doc-assets/ai-dialog.png [new file with mode: 0644]
public/design-doc-assets/assets.png
public/design-doc-assets/basic.png
public/design-doc-assets/deviceTemplates.png [new file with mode: 0644]
public/design-doc-assets/dialog-add-domain.png [new file with mode: 0644]
public/design-doc-assets/domain-detail.png
public/design-doc-assets/domainSettings.png [new file with mode: 0644]
public/design-doc-assets/domains.png
public/design-doc-assets/functions.png
public/design-doc-assets/generate.png
public/design-doc-assets/menu-design-doc.png [new file with mode: 0644]
public/design-doc-assets/menu-full.png [new file with mode: 0644]
public/design-doc-assets/preview.png [new file with mode: 0644]
public/design-doc-assets/projects.png
public/design-doc-assets/result.png
public/design-doc-assets/styleLibrary.png [new file with mode: 0644]
public/design-doc-assets/toolSettings.png [new file with mode: 0644]
public/design-doc.html
scripts/capture-design-shots.mjs [new file with mode: 0644]

diff --git a/public/design-doc-assets/ai-dialog.png b/public/design-doc-assets/ai-dialog.png
new file mode 100644 (file)
index 0000000..71596a3
Binary files /dev/null and b/public/design-doc-assets/ai-dialog.png differ
index cfb723b229900cbe2a6c3f0a7aaeb3b0634b7f7e..29b3341ce97b7d61aee0b43c52088cbb60db1ba3 100644 (file)
Binary files a/public/design-doc-assets/assets.png and b/public/design-doc-assets/assets.png differ
index 2e66b40d73696583f065eb31a496adb0ffc87a20..bd4b5480ca97c1f42c814c713e03e702ae3fc712 100644 (file)
Binary files a/public/design-doc-assets/basic.png and b/public/design-doc-assets/basic.png differ
diff --git a/public/design-doc-assets/deviceTemplates.png b/public/design-doc-assets/deviceTemplates.png
new file mode 100644 (file)
index 0000000..ab64adb
Binary files /dev/null and b/public/design-doc-assets/deviceTemplates.png differ
diff --git a/public/design-doc-assets/dialog-add-domain.png b/public/design-doc-assets/dialog-add-domain.png
new file mode 100644 (file)
index 0000000..0cb0e7b
Binary files /dev/null and b/public/design-doc-assets/dialog-add-domain.png differ
index 1f392477faced8599fd129ed89592f1d73504ba9..5db1aa8f0e905407a6e07f6be8e9beb95887b65c 100644 (file)
Binary files a/public/design-doc-assets/domain-detail.png and b/public/design-doc-assets/domain-detail.png differ
diff --git a/public/design-doc-assets/domainSettings.png b/public/design-doc-assets/domainSettings.png
new file mode 100644 (file)
index 0000000..6a89427
Binary files /dev/null and b/public/design-doc-assets/domainSettings.png differ
index 1b6c83096131c3373c8e371650390462347c9d8d..5db1aa8f0e905407a6e07f6be8e9beb95887b65c 100644 (file)
Binary files a/public/design-doc-assets/domains.png and b/public/design-doc-assets/domains.png differ
index fa2d9e4d7bcc258272e8011ea1d5667641095e79..6a9ad465a10ce7935aaa64624f2647fcbe9f695f 100644 (file)
Binary files a/public/design-doc-assets/functions.png and b/public/design-doc-assets/functions.png differ
index 22acee758e850d2540c31ef3b5713ef20d17d5af..1fd3c4e201dc76a1945a2ba6f9e54dbd5e73b1f0 100644 (file)
Binary files a/public/design-doc-assets/generate.png and b/public/design-doc-assets/generate.png differ
diff --git a/public/design-doc-assets/menu-design-doc.png b/public/design-doc-assets/menu-design-doc.png
new file mode 100644 (file)
index 0000000..1eb6f22
Binary files /dev/null and b/public/design-doc-assets/menu-design-doc.png differ
diff --git a/public/design-doc-assets/menu-full.png b/public/design-doc-assets/menu-full.png
new file mode 100644 (file)
index 0000000..b8731c1
Binary files /dev/null and b/public/design-doc-assets/menu-full.png differ
diff --git a/public/design-doc-assets/preview.png b/public/design-doc-assets/preview.png
new file mode 100644 (file)
index 0000000..9593185
Binary files /dev/null and b/public/design-doc-assets/preview.png differ
index e58f6825d261874639172ed7632164eb944e0fa1..b8731c194eab6600e6bee3b069d8152a2747e5cf 100644 (file)
Binary files a/public/design-doc-assets/projects.png and b/public/design-doc-assets/projects.png differ
index f37f1c6ce82ffeb3ac23f97818f51bc023c9cd4d..0704dbe2877c63c6d9c91d5e8424dcafeb4bf177 100644 (file)
Binary files a/public/design-doc-assets/result.png and b/public/design-doc-assets/result.png differ
diff --git a/public/design-doc-assets/styleLibrary.png b/public/design-doc-assets/styleLibrary.png
new file mode 100644 (file)
index 0000000..4f3fabb
Binary files /dev/null and b/public/design-doc-assets/styleLibrary.png differ
diff --git a/public/design-doc-assets/toolSettings.png b/public/design-doc-assets/toolSettings.png
new file mode 100644 (file)
index 0000000..e5c74b2
Binary files /dev/null and b/public/design-doc-assets/toolSettings.png differ
index 1b95cfb1251f2da1cbbf2deca5c033c7b5b51e77..0c99f106207d49070e90cecb00dcdf12d8ad30fd 100644 (file)
@@ -2,7 +2,7 @@
 <!--
   维护说明:
   - 本文档是 StationBuilder 的产品/交互设计文档,单一 HTML 源,位于 public/design-doc.html,
-    运行时通过顶栏「设计方案」链接(/design-doc.html)打开。
+    运行时通过左侧菜单底部的「设计方案」链接(/design-doc.html)在新标签打开(见 3.5)。
   - 每次功能改动后,请同步更新对应章节;新增/遗留的未实现能力用 <span class="todo">尚未实现</span> 标注。
   - 顶部三个按钮分别导出:PDF(调用浏览器打印另存为 PDF——真实文字、微软雅黑、纯黑、1.5 倍行距;打印对话框勾选「页眉和页脚」即得页码;截图限 300px 宽)、
     Word(.doc:微软雅黑、纯黑文字、黑色表格边框、1.5 倍行距、页脚页码域(第 X / 共 Y 页)、默认可见静态目录、截图 300px 宽)、
     <tr><td>步骤条 Steps(基本/资产/功能/生成)</td><td>向导进度指示</td><td>当前页 state</td><td>已完成步骤可点击回跳;当前步高亮、未达步骤置灰</td></tr>
   </table>
 
+  <h3>3.5 设计方案入口(左侧菜单底部)</h3>
+  <p>「设计方案」按钮固定在<b>左侧菜单最底部</b>(鲜明紫渐变、10 号字、圆角小按钮)。点击在<b>新标签页</b>打开本设计文档 <code>design-doc.html</code>,便于在任意页面随时对照产品与交互说明。按钮位置见图:</p>
+  <figure><img src="design-doc-assets/menu-design-doc.png" alt="设计方案入口·左侧菜单底部"/><figcaption>左侧菜单底部固定「设计方案」按钮:点击在新标签打开设计文档。</figcaption></figure>
+
   <h2>4. 页面详细说明</h2>
 
   <h3>4.2 站点项目 / 站点列表(projects)</h3>
     <tr><td>对话框(模型 · 消息流 / 输入框)</td><td>站点相关问答</td><td>POST /api/chat → ai-sidecar → AI 生成服务</td><td>流式显示回答;上下文隔离、禁用工具与写操作(仅问答)</td></tr>
     <tr><td>模型选择弹层</td><td>切换问答模型</td><td>前端 MODELS</td><td>仅免费模型可选</td></tr>
   </table>
+  <figure><img src="design-doc-assets/ai-dialog.png" alt="AI 助手对话框"/><figcaption>右下角悬浮按钮打开的 AI 助手对话框:模型信息、消息流与输入框。</figcaption></figure>
 
   <h2>7. 模态框(Modal)</h2>
   <table>
     <tr><td>版本历史(history)</td><td>查看版本</td><td>仅展示 V1 当前版本 <span class="todo">真实版本管理尚未实现</span></td></tr>
     <tr><td>生成提示词(prompt)</td><td>查看/复制组合提示词</td><td>展示发送给模型的完整提示词,可复制到剪贴板</td></tr>
   </table>
+  <figure><img src="design-doc-assets/dialog-add-domain.png" alt="新增领域对话框"/><figcaption>弹窗示例:系统设定页「+ 新增领域」对话框(输入领域名称,Enter 确认 / Esc 取消)。</figcaption></figure>
 
   <h2>8. 数据与存储</h2>
   <table>
   <h3>10.1 版本变更记录(Changelog)</h3>
   <table class="changelog">
     <tr><th>版本</th><th>日期</th><th>修改人</th><th>说明</th></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>
     <tr><td>v0.30</td><td>2026-08-21</td><td>wang.yu</td><td>修复领域方案「生成保存」页面预览全部空白:根因为 <code>public/dashboard-renderer.js</code> 的 <code>renderDashboard</code> 每次渲染开头执行 <code>renderDashboard._instances.forEach(i=&gt;i.dispose())</code> 把<b>全部全局 echarts 实例销毁</b>——同一方案 11 张页面缩略图顺序渲染时,后一张的渲染把前一张的图表实例 dispose 掉,仅最后一张卡片保留图表、其余均为空白(DOM 有内容但 0 个 <code>&lt;canvas&gt;</code>)。修复:废弃全局实例数组,改为<b>按容器隔离</b>——每个容器记录自身 <code>container.__dashInst</code>,渲染前仅销毁本容器旧实例并清空 <code>innerHTML</code>,渲染后回挂本容器实例;新增模块级 <code>_curInst</code> 收集本次渲染的实例。现已验证污水厂(橙色风格)方案 11 张卡片全部渲染出图表(共 43 个 canvas,每卡 ≥0)。附带增强:① 生成确认页「领域方案页面」列表卡片改为整宽单列大图(<code>.domain-review-pages</code> 由两列改 1 列,<code>.dashboard-thumb-live</code> 最小高度 115px→240px),并整体可点击——点击卡片经新增 <code>onPreviewPage</code> 入参打开 <code>DomainPagePreviewModal</code> 全屏预览(含图表),卡片右下角加「⛶ 点击放大」提示。② globals.css 增补 <code>.domain-review-page</code> hover 描边与 <code>.review-zoom</code> 提示样式。Playwright E2E:11 卡全部有 canvas、点开预览 modal 含 2 个 canvas + 361 字正文,0 控制台报错。</td></tr>
diff --git a/scripts/capture-design-shots.mjs b/scripts/capture-design-shots.mjs
new file mode 100644 (file)
index 0000000..4fdb18f
--- /dev/null
@@ -0,0 +1,132 @@
+import { chromium } from 'playwright';
+import fs from 'fs';
+
+const BASE = 'https://aidemo.istation.cloud/t/stationbuilder';
+const OUT = '/home/StationBuilder/public/design-doc-assets';
+fs.mkdirSync(OUT, { recursive: true });
+
+const shot = async (page, name, opts = {}) => {
+  const p = `${OUT}/${name}.png`;
+  await page.screenshot({ path: p, fullPage: !!opts.full, ...opts });
+  console.log('saved', name);
+};
+
+const waitPage = async (page, key) => {
+  await page.waitForSelector('.company-side-nav', { timeout: 15000 });
+  if (key !== 'projects') {
+    await page.waitForSelector('.page-heading h1', { timeout: 15000 });
+  }
+  await page.waitForTimeout(800);
+};
+
+const browser = await chromium.launch();
+const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
+const errors = [];
+page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
+
+// resolve a valid site id
+await page.goto(`${BASE}/?page=projects`, { waitUntil: 'networkidle' });
+await waitPage(page, 'projects');
+const siteId = await page.evaluate(async () => {
+  try { const r = await fetch('/api/db/sites'); const j = await r.json();
+    const arr = Array.isArray(j) ? j : (j.data || j.rows || j.sites || []);
+    return arr[0]?.id || null;
+  } catch (e) { return null; }
+});
+console.log('siteId =', siteId);
+const SID = siteId || 1;
+
+const pageShots = [
+  ['projects', '?page=projects'],
+  ['basic', `?page=basic&id=${SID}`],
+  ['assets', `?page=assets&id=${SID}`],
+  ['functions', `?page=functions&id=${SID}`],
+  ['generate', `?page=generate&id=${SID}`],
+  ['result', `?page=result&id=${SID}`],
+  ['preview', `?page=preview&id=${SID}`],
+  ['domains', '?page=domains'],
+  ['styleLibrary', '?page=styleLibrary'],
+  ['deviceTemplates', '?page=deviceTemplates'],
+  ['toolSettings', '?page=toolSettings'],
+  ['domainSettings', '?page=domainSettings'],
+];
+
+for (const [key, qs] of pageShots) {
+  try {
+    await page.goto(`${BASE}/${qs}`, { waitUntil: 'networkidle' });
+    await waitPage(page, key);
+    await shot(page, key, { fullPage: true });
+  } catch (e) { console.log('PAGE FAIL', key, e.message); }
+}
+
+// domain detail (click first card)
+try {
+  await page.goto(`${BASE}/?page=domains`, { waitUntil: 'networkidle' });
+  await waitPage(page, 'domains');
+  const card = await page.$('.domain-grid article, .domain-list .domain-row, [class*="domain"] button');
+  // click first clickable domain item
+  const clicked = await page.evaluate(() => {
+    const el = document.querySelector('.domain-grid article') || document.querySelector('[class*="domain"]');
+    if (el) { el.click(); return true; } return false;
+  });
+  await page.waitForTimeout(1200);
+  await shot(page, 'domain-detail', { fullPage: true });
+  console.log('domain-detail captured, clicked=', clicked);
+} catch (e) { console.log('DOMAIN DETAIL FAIL', e.message); }
+
+// left menu element (shows 设计方案 at bottom)
+try {
+  await page.goto(`${BASE}/?page=projects`, { waitUntil: 'networkidle' });
+  await waitPage(page, 'projects');
+  await page.waitForSelector('.company-side-nav .design-doc-link', { timeout: 8000 });
+  const nav = await page.$('.company-side-nav');
+  await nav.screenshot({ path: `${OUT}/menu-design-doc.png` });
+  console.log('saved menu-design-doc (element)');
+  // full page too
+  await shot(page, 'menu-full', { fullPage: true });
+} catch (e) { console.log('MENU FAIL', e.message); }
+
+// AI dialog (floating button)
+try {
+  await page.goto(`${BASE}/?page=projects`, { waitUntil: 'networkidle' });
+  await waitPage(page, 'projects');
+  await page.click('.ai-fab');
+  await page.waitForSelector('.ai-dialog', { timeout: 8000 });
+  await page.waitForTimeout(600);
+  await shot(page, 'ai-dialog', { fullPage: false });
+  await page.keyboard.press('Escape').catch(() => {});
+} catch (e) { console.log('AI DIALOG FAIL', e.message); }
+
+// model popover (Wordbuddy AI model selector in left menu)
+try {
+  await page.goto(`${BASE}/?page=projects`, { waitUntil: 'networkidle' });
+  await waitPage(page, 'projects');
+  await page.click('.company-side-nav .model-trigger, .model-trigger');
+  await page.waitForSelector('.model-popover', { timeout: 8000 });
+  await page.waitForTimeout(400);
+  await shot(page, 'model-popover', { fullPage: false });
+} catch (e) { console.log('MODEL POPOVER FAIL', e.message); }
+
+// 新增领域 dialog (domainSettings)
+try {
+  await page.goto(`${BASE}/?page=domainSettings`, { waitUntil: 'networkidle' });
+  await waitPage(page, 'domainSettings');
+  await page.click('text=新增领域');
+  await page.waitForSelector('.domain-create-dialog', { timeout: 8000 });
+  await page.waitForTimeout(400);
+  await shot(page, 'dialog-add-domain', { fullPage: false });
+} catch (e) { console.log('ADD DOMAIN DIALOG FAIL', e.message); }
+
+// 新增领域方案 modal (domains create)
+try {
+  await page.goto(`${BASE}/?page=domains`, { waitUntil: 'networkidle' });
+  await waitPage(page, 'domains');
+  await page.click('text=新增领域方案');
+  await page.waitForSelector('.dlg-box', { timeout: 8000 });
+  await page.waitForTimeout(400);
+  await shot(page, 'modal-create-domain', { fullPage: false });
+} catch (e) { console.log('CREATE DOMAIN MODAL FAIL', e.message); }
+
+console.log('ERRORS:', errors.slice(0, 5));
+await browser.close();
+console.log('DONE');