Prechádzať zdrojové kódy

修 TDZ(又一次): #tab=<tab>:<sub> 的 sub 赋值写在 const sub 之前 => setup 抛错, 整个工作台渲染不出(用户报"振动 CMS 展示不出"); 移到声明之后; 核验 blob iframe 渲染 + 挂载门禁通过

zhouyang.xie 6 dní pred
rodič
commit
0000e92a4e

+ 4 - 0
app_frontEnd/web/src/App.vue

@@ -21,6 +21,8 @@ type Mode = 'portal' | 'workbench' | 'smoke' | 'about'
 /** 入口哈希:`#workbench` 或 `#tab=<页签>` ⇒ 工作台(后者直达该页签);否则门户。 */
 const _h = location.hash.replace('#', '')
 const _tabInHash = (_h.match(/^tab=([a-z_]+)/) || [])[1] || ''
+/** ★P72 子视图深链:`#tab=vibration:portal_cms` ⇒ 直接进该子页(也便于真机验证) */
+const _subInHash = (_h.match(/^tab=[a-z_]+:([a-z_]+)/) || [])[1] || ''
 /** 旧 SPA 地址 `/detail/v2` 一律进工作台(用户令:登录后要进工作台) */
 const _isDetail = /\/detail\/v2\/?$/.test(location.pathname)
 const mode = ref<Mode>((_isDetail || _h === 'workbench' || _tabInHash) ? 'workbench' : 'portal')
@@ -29,6 +31,8 @@ const tab = ref<string>('overview')
 /** ★P53:`#tab=<id>` 直达页签(必须放在 tab 声明之后,否则 TDZ 报错导致整页空白) */
 if (_tabInHash) tab.value = _tabInHash
 const sub = ref<string | null>(null)
+/** ★P72 子视图深链(必须放在 `sub` 声明之后,否则 TDZ 报错导致整页空白) */
+if (_subInHash) sub.value = _subInHash
 const d = ref<any>(null)
 const win = ref<string>('')
 const WIN_PRESETS: [string, string][] = [['近30日', 'win.30d'], ['近90日', 'win.90d'], ['2026年', 'win.2026'], ['2026H1', 'win.2026H1'], ['2025H2', 'win.2025H2'], ['全程', 'win.all']]

+ 9 - 3
app_frontEnd/web/src/views/System.vue

@@ -5,7 +5,7 @@
  * 数据:`/api/maint_survey` → `{rows: [{层, 存在, 名, 覆盖, 条数, 末次更新, 频率, 责任, 位置, 摄入, 说明}], error?}`。
  * 三种分支:未取到("正在巡检…")· 出错 · 两层表(数据 / 机理)。
  */
-import { computed } from 'vue'
+import { computed, ref, watch } from 'vue'
 import { PORTAL_RECALC_HTML } from '../lib/portalRecalc'
 import { PORTAL_CSS } from '../lib/portalSections'
 import { t } from '../i18n/zh'
@@ -18,9 +18,15 @@ const props = defineProps<{ apis?: Record<string, any> | null }>()
 const publicMeta = (x: unknown): string => String(x == null ? '' : x)
 
 /** ★P68 用**门户 CSS + 该节原文**装进同源 iframe(srcdoc),与门户四节同一手法 ⇒ 渲染与 v2.9.2 一致 */
-const recalcSrc = computed<string>(() => '<!doctype html><html lang="zh" data-theme="light"><head>'
+const recalcBlobUrl = ref<string>('')
+const _recalcSrc = computed<string>(() => '<!doctype html><html lang="zh" data-theme="light"><head>'
   + '<meta charset="utf-8"><base target="_top"><style>' + PORTAL_CSS + '</style></head><body>'
   + PORTAL_RECALC_HTML + '</body></html>')
+watch(_recalcSrc, (html) => {
+  const old = recalcBlobUrl.value
+  recalcBlobUrl.value = URL.createObjectURL(new Blob([html], { type: 'text/html;charset=utf-8' }))
+  if (old) { try { URL.revokeObjectURL(old) } catch { /* ignore */ } }
+}, { immediate: true })
 
 const md = computed<any>(() => props.apis?.['/detail/api/maint_survey'] ?? props.apis?.['/api/maint_survey'] ?? null)
 
@@ -74,6 +80,6 @@ const groups = computed(() => LAYERS
     <div class="eyebrow" style="margin-top:18px">{{ t('tab.recalc') }}</div>
   <Recalc />
   <iframe data-added="recalc" class="portal-recalc-frame" title="数据重算与产物"
-            :srcdoc="recalcSrc" style="width:100%;height:1420px;border:0;display:block;margin-top:18px"></iframe>
+            :src="recalcBlobUrl" style="width:100%;height:1420px;border:0;display:block;margin-top:18px"></iframe>
 </section>
 </template>

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

@@ -34,6 +34,13 @@ const F = computed<any>(() => props.d?.fus)
 
 /** ★P71 门户「振动·CMS」整页文档(同源 iframe)—— 用门户 CSS 渲染,chip 为按窗实时值 */
 const portalCmsSrc = computed<string>(() => portalCmsDoc(props.d, cmsData.value, PORTAL_CSS))
+/** ★P72 用 **Blob URL** 装进 iframe(与门户整页同一手法;真机上比 srcdoc 可靠)。 */
+const cmsBlobUrl = ref<string>('')
+watch(portalCmsSrc, (html) => {
+  const old = cmsBlobUrl.value
+  cmsBlobUrl.value = URL.createObjectURL(new Blob([html], { type: 'text/html;charset=utf-8' }))
+  if (old) { try { URL.revokeObjectURL(old) } catch { /* ignore */ } }
+}, { immediate: true })
 
 const bodyHtml = computed(() => {
   const f = F.value
@@ -56,7 +63,7 @@ const bodyHtml = computed(() => {
               @click="emit('sub', s)">{{ t('fus.sub.' + s) }}</button>
     </div>
     <iframe v-if="sub === 'portal_cms'" class="portal-cms-frame" title="振动 · CMS"
-            :srcdoc="portalCmsSrc" style="width:100%;height:1080px;border:0;display:block"></iframe>
+            :src="cmsBlobUrl" style="width:100%;height:1080px;border:0;display:block"></iframe>
     <div v-else class="unwrap-host" v-html="bodyHtml"></div>
   </section>
 </template>

+ 28 - 0
app_frontEnd/web/tools/_check_cms_blob.mjs

@@ -0,0 +1,28 @@
+
+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 fleet = readFileSync(resolve(HERE, '_fleet_fixture.json'), 'utf8')
+const vib = readFileSync(resolve(HERE, '_vibcms_fixture.json'), 'utf8')
+const survey = readFileSync(resolve(HERE, '_survey_fixture.json'), 'utf8')
+const dom = new JSDOM(idx.replace('</head>', '<style>' + css + '</style></head>'),
+  { url: 'http://127.0.0.1:28084/detail/v2#tab=vibration:portal_cms', runScripts: 'outside-only', pretendToBeVisual: true, virtualConsole: new VirtualConsole() })
+const w = dom.window
+const blobs = []
+const origCreate = w.URL.createObjectURL
+w.URL.createObjectURL = (b) => { blobs.push(b); return origCreate ? origCreate(b) : 'blob:stub/' + blobs.length }
+w.fetch = async (u) => { const s = String(u); const body = /fleet/.test(s) ? fleet : (/vibcms/.test(s) ? vib : (/maint_survey/.test(s) ? survey : '{}'))
+  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 < 20; i++) { await new Promise((r) => setTimeout(r, 400)); if (w.document.querySelector('.portal-cms-frame')) break }
+const f = w.document.querySelector('.portal-cms-frame')
+console.log('FRAME ' + (f ? 'Y' : 'N') + ' SRC ' + (f ? String(f.getAttribute('src')).slice(0, 22) : '-'))
+console.log('BLOBS ' + blobs.length)

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
release/web/assets/index-CivycO6G.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-C6A6HZLY.js"></script>
+  <script type="module" crossorigin src="/web/assets/index-CivycO6G.js"></script>
   <link rel="stylesheet" crossorigin href="/web/assets/index-DFl-J_Hd.css">
 </head>
 <body>

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov