Explorar el Código

用户令: 门户「振动·CMS」迁入「振动融合分析」成为第 6 个并列子选项卡「振动 CMS」(受时间窗影响)。实现: 抽门户 #cms 节为 lib/portalCms.ts; vib.ts 增 portalCmsHtml(d) 把门户静态数字替换为按窗实时值(候选观察/全场取 d.fus.kpi, 当前窗取 d.win)并追加"当前时间窗"chip; FUS_SUBS 6 项; i18n 标签; 验证随窗变化

zhouyang.xie hace 6 días
padre
commit
c89dc99cfd

+ 1 - 0
app_frontEnd/web/src/i18n/en.ts

@@ -387,6 +387,7 @@ export const en: Record<string, string> = {
   "fus.short.stale": "sample stale",
   "fus.short.uneval": "not assessed",
   "fus.short.unlisted": "not listed",
+  "fus.sub.portal_cms": "Vibration CMS",
   "fus.sub.cms": "Vibration assessment",
   "fus.sub.events": "Alarms and work orders",
   "fus.sub.loop": "Closed loops",

+ 1 - 0
app_frontEnd/web/src/i18n/zh.ts

@@ -397,6 +397,7 @@ export const ZH: Record<string, string> = {
   "fus.short.stale": "样龄过期",
   "fus.short.uneval": "未评估",
   "fus.short.unlisted": "未收录",
+  "fus.sub.portal_cms": "振动 CMS",
   "fus.sub.cms": "振动分析",
   "fus.sub.events": "报警工单",
   "fus.sub.loop": "端到端闭环",

+ 8 - 0
app_frontEnd/web/src/lib/portalCms.ts

@@ -0,0 +1,8 @@
+/** ★P65 门户「振动·CMS」节原文(取自 v2.9.2 门户 shell 的 `#cms` 页面),
+ *  用于「振动融合分析」的第 6 个并列子选项卡「振动 CMS」。
+ *  生成方式:tools 抽取脚本从 release/web/portal/shell.html 截取(勿手改)。 */
+export const PORTAL_CMS_HTML = `<div class="pg" id="cms" data-title="振动·CMS"><section class="hero"><div class="wrap"><div class="kicker">状态监测 · 振动诊断 (windcms)</div><h1>振动与 SCADA 联合分析</h1><p class="lead">六层模型:数据完整性 → ISO 标量 → 主轴承 rms → 台内自适应基线 → 谱线八闸 → 六枚举裁决。厂商判据与模型融合,机制未定不命名部件。</p>
+<div class="chips"><span class="chip s al">需关注 11 台</span><span class="chip s gd">未见异常 27 台</span><span class="chip ev">证据窗末 2026-08-11</span><span class="chip ev">TCM 六采集窗 · 320 万记录 · 160 万谱</span></div></div></section>
+<section><div class="wrap"><div class="kicker">振动分析 · 方法与证据边界</div><div class="grid g3"><div class="card"><div class="k">比较基准</div><h3>异常比较先核实基准</h3><p>先确认测点、工况、处理链和频带可比,再结合线能量占比与机群分布识别异常。阈值须注明来源、样本和适用范围;本底接近检测下限时,倍数不宜单独解释,诊断仍需补充证据。</p></div><div class="card"><div class="k">证据关联</div><h3>多种特征不等于独立证据</h3><p>频谱、标量和时域特征若来自同一波形,应保留共同来源,避免重复计票。跨测点或跨窗口的一致性也需核查共同工况与采集依赖;结论升级应结合独立检修等证据,不以特征数量替代证据质量。</p></div><div class="card"><div class="k">结论边界</div><h3>结论强度与证据匹配</h3><p>使用“持续恶化”“单调”或“唯一”等表述前,应明确观察范围、比较对象、样本依赖和对应检验。端段差异不能代替全程趋势证据;复核发现支持不足时,补充验证或降低结论强度,并保留不确定性。</p></div></div></div></section>
+<section><div class="wrap"><div class="kicker">在线 · 客户版</div><h2>CMS 振动分析工作台</h2><p class="sub">左栏"需关注 / 未见异常"按台展开四个测点判级;工作台可选机组、测点、分析类即时出图;交互页可提问。演示只停客户版与一次工作台运行。</p><iframe src="http://127.0.0.1:18020/?theme=light" style="height:920px"></iframe><div class="chips" style="margin-top:12px"><a class="chip ev" href="http://127.0.0.1:18020/?theme=light" target="_blank">新窗口打开 windcms →</a><a class="chip ev" href="http://127.0.0.1:18020/workbench.html?theme=light" target="_blank">工作台 →</a></div></div></section>
+</div>`

+ 25 - 1
app_frontEnd/web/src/lib/vib.ts

@@ -255,4 +255,28 @@ export function cmsHtml(V: any, err?: string | null): string {
       <p class="small muted" style="margin-top:10px">${t('fus.cms_link')} <a href="http://localhost:8030/index.html" target="_blank" rel="noopener">${t('fus.cms_open')}</a> <a href="/windcms/index.html" target="_blank" rel="noopener">${t('fus.cms_static')}</a></p></div>`
 }
 
-export const FUS_SUBS = ['matrix', 'loop', 'units', 'events', 'cms'] as const
+export const FUS_SUBS = ['matrix', 'loop', 'units', 'events', 'cms', 'portal_cms'] as const
+
+import { PORTAL_CMS_HTML } from './portalCms'
+
+/** ★P65 门户「振动·CMS」节 —— 作为并列子选项卡「振动 CMS」,并把门户里的**静态数字**换成
+ *  **按当前时间窗实时算**的值(候选观察/全场来自 `d.fus.kpi`,当前窗来自 `d.win`),
+ *  这样它和其余子选项卡一样受时间窗影响。 */
+export function portalCmsHtml(d: any): string {
+  const fus = (d && d.fus) || {}
+  const kpi = fus.kpi || {}
+  const n = (v: any) => (typeof v === 'number' ? v : Number(v) || 0)
+  const alert = n(kpi['候选观察'] ?? kpi['定论'] ?? 0)
+  const total = n(kpi['全场'] ?? kpi['收录台'] ?? 0)
+  const okn = Math.max(total - alert, 0)
+  const win = String((d && d.win) || '')
+  const end = String(fus['证据窗末'] || '').slice(0, 10)
+  let html = PORTAL_CMS_HTML
+  // 门户静态 chip → 按窗实时 chip(保持原有 class,样式不变)
+  html = html.replace(/(<span class="chip s al">)[^<]*/, '$1需关注 ' + alert + ' 台')
+  html = html.replace(/(<span class="chip s gd">)[^<]*/, '$1未见异常 ' + okn + ' 台')
+  if (end) html = html.replace(/(<span class="chip ev">)[^<]*/, '$1证据窗末 ' + end)
+  // 追加"当前时间窗"chip —— 受时间窗影响的直接可见项
+  html = html.replace('</div>', '<span class="chip">当前时间窗 · ' + win + '</span></div>', 1)
+  return html
+}

+ 2 - 1
app_frontEnd/web/src/views/Vibration.vue

@@ -8,7 +8,7 @@ import { vUnwrap } from '../lib/unwrap'
  */
 import {computed, ref, watch } from 'vue'
 import { t } from '../i18n/zh'
-import { FUS_SUBS, cmsHtml, eventsHtml, loopHtml, matrixHtml, unitsHtml } from '../lib/vib'
+import { FUS_SUBS, cmsHtml, eventsHtml, loopHtml, matrixHtml, portalCmsHtml, unitsHtml } from '../lib/vib'
 
 const props = defineProps<{ d: any; sub?: string | null; cms?: any }>()
 const emit = defineEmits<{ (e: 'sub', id: string): void }>()
@@ -39,6 +39,7 @@ const bodyHtml = computed(() => {
   if (sub.value === 'units') return unitsHtml(f, null)
   if (sub.value === 'events') return eventsHtml(f, props.d)
   if (sub.value === 'cms') return cmsHtml(cmsData.value)
+  if (sub.value === 'portal_cms') return portalCmsHtml(props.d)   // ★P65 门户「振动·CMS」(受时间窗影响)
   return `<div class="panel mb"><p class="small muted">子视图「${t('fus.sub.' + sub.value)}」尚未迁移到 Vue(迁移中)。</p></div>`
 })
 </script>

+ 39 - 0
app_frontEnd/web/tools/_check_portal_cms.mjs

@@ -0,0 +1,39 @@
+
+import { readFileSync } from 'node:fs'
+import { fileURLToPath } from 'node:url'
+import { dirname, resolve } from 'node:path'
+import { JSDOM, VirtualConsole } from 'jsdom'
+const HERE = dirname(fileURLToPath(import.meta.url))
+const dist = resolve(HERE, '../../../release/web')
+const idx = readFileSync(dist + '/index.html', 'utf8')
+const jsFile = (idx.match(/src="([^"]+\.js)"/) || [])[1].replace(/^.*\//, '')
+const cssFile = (idx.match(/href="([^"]+\.css)"/) || [])[1].replace(/^.*\//, '')
+const code = readFileSync(dist + '/assets/' + jsFile, 'utf8')
+const css = readFileSync(dist + '/assets/' + cssFile, 'utf8')
+const baseFleet = JSON.parse(readFileSync(resolve(HERE, '_fleet_fixture.json'), 'utf8'))
+const vib = readFileSync(resolve(HERE, '_vibcms_fixture.json'), 'utf8')
+async function run(fleet, tag) {
+  const dom = new JSDOM(idx.replace('</head>', '<style>' + css + '</style></head>'),
+    { url: 'http://127.0.0.1:28084/detail/v2#tab=vibration', runScripts: 'outside-only', pretendToBeVisual: true, virtualConsole: new VirtualConsole() })
+  const w = dom.window
+  w.fetch = async (u) => { const s = String(u)
+    const body = /fleet/.test(s) ? JSON.stringify(fleet) : (/vibcms/.test(s) ? vib : '{}')
+    return { ok: true, status: 200, json: async () => JSON.parse(body), text: async () => body } }
+  try { w.eval(code) } catch (e) { console.log('EVAL_ERR ' + String(e).slice(0, 120)) }
+  for (let i = 0; i < 24; i++) { await new Promise((r) => setTimeout(r, 400)); if (w.document.querySelector('[data-fsub]')) break }
+  const pills = Array.from(w.document.querySelectorAll('[data-fsub]')).map((b) => b.textContent.trim())
+  const p6 = w.document.querySelector('[data-fsub="portal_cms"]')
+  if (p6) p6.dispatchEvent(new w.MouseEvent('click', { bubbles: true }))
+  await new Promise((r) => setTimeout(r, 2000))
+  const root = w.document.querySelector('#root') || w.document.body
+  const txt = (root.textContent || '').replace(/\s+/g, ' ')
+  const chips = Array.from(root.querySelectorAll('.chip')).map((c) => c.textContent.trim())
+  console.log(tag + ' PILLS ' + JSON.stringify(pills))
+  console.log(tag + ' CHIPS ' + JSON.stringify(chips.slice(0, 6)))
+  console.log(tag + ' HAS_HERO ' + /振动与 SCADA 联合分析|六层模型/.test(txt))
+}
+await run(baseFleet, 'A')
+const f2 = JSON.parse(JSON.stringify(baseFleet))
+f2.win = '全程'
+if (f2.fus && f2.fus.kpi) { f2.fus.kpi['候选观察'] = 9; f2.fus.kpi['全场'] = 38 }
+await run(f2, 'B')

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 4 - 0
release/web/assets/index-D5-fRyq9.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 4
release/web/assets/index-DFf4k-UW.js


+ 1 - 1
release/web/index.html

@@ -4,7 +4,7 @@
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1" />
   <title>观澜 · 风电数据分析与决策支持</title>
-  <script type="module" crossorigin src="/web/assets/index-DFf4k-UW.js"></script>
+  <script type="module" crossorigin src="/web/assets/index-D5-fRyq9.js"></script>
   <link rel="stylesheet" crossorigin href="/web/assets/index-DFl-J_Hd.css">
 </head>
 <body>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio