probe-legend-diff.mjs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. /**
  2. * 诊断:详情卡状态标签 与 一张图顶部状态栏 的 DOM 结构逐一对比(维护者反馈"多出一个废弃设计")。
  3. * 只读探针:打开点位详情 → dump 两侧 outerHTML + 计算样式。
  4. */
  5. import { mkdir, writeFile } from 'node:fs/promises'
  6. import path from 'node:path'
  7. import { cdpLaunch } from '../../../../cockpit-shell-panel-layout/verify/cdp-driver.mjs'
  8. const CHROME = process.env.CHROME_PATH || 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
  9. const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
  10. const POINT = 'CBWS202610150001'
  11. const browser = await cdpLaunch({ executablePath: CHROME, headless: true, width: 1680, height: 950 })
  12. const page = await browser.newPage()
  13. await page.goto('http://127.0.0.1:8000/#/cockpit', { timeout: 60000 })
  14. await sleep(2500)
  15. await page.click('[data-cockpit-intro]').catch(() => {})
  16. await page.waitForFunction(() => ['ready', 'failed', 'disabled'].includes(document.querySelector('.cockpit-shell__map .map-canvas')?.dataset.mapEngine || ''), { timeout: 60000 })
  17. // 报警筛选 + 飞近 + 真鼠标点标记(与走查同一进入路径)
  18. await page.click('[data-legend-key="ALARM"]').catch(() => {})
  19. await sleep(4000)
  20. const coord = await page.evaluate(async (code) => {
  21. const r = await fetch('http://192.168.110.96:8301/api/cockpit/v1/map-snapshot?page=1&pageSize=200&pointStatus=ALARM', { cache: 'no-store' })
  22. const j = await r.json()
  23. const found = ((j.data || j).monitoringPoints || []).find((p) => (p.pointCode || p.code) === code)
  24. return found ? { lon: found.longitude, lat: found.latitude } : null
  25. }, POINT)
  26. await page.evaluate((c) => globalThis.__cockpitMap?.setCameraFocus({ lon: c.lon, lat: c.lat, height: 900 }), coord)
  27. await sleep(4000)
  28. const box = await page.evaluate((code) => {
  29. const node = document.querySelector(`[data-marker-overlay-code="${code}"]`)
  30. const inner = node?.querySelector('svg')
  31. if (!inner) return null
  32. const r = inner.getBoundingClientRect()
  33. return { cx: r.x + r.width / 2, cy: r.y + r.height / 2 }
  34. }, POINT)
  35. if (box) {
  36. await page._dispatchMouse('mouseMoved', box.cx, box.cy)
  37. await page._dispatchMouse('mousePressed', box.cx, box.cy, { button: 'left', buttons: 1, clickCount: 1 })
  38. await page._dispatchMouse('mouseReleased', box.cx, box.cy, { button: 'left', buttons: 0, clickCount: 1 })
  39. }
  40. await sleep(3000)
  41. const dump = await page.evaluate(() => {
  42. const strip = (html) => (html || '').replace(/\s+/g, ' ').slice(0, 900)
  43. const topAlarm = document.querySelector('[data-map-legend] [data-legend-key="ALARM"]')
  44. const topEmpty = document.querySelector('[data-map-legend] [data-legend-key="EMPTY"]')
  45. const cardStatus = document.querySelector('[data-point-status]')
  46. const deviceCardTag = document.querySelector('[data-device-card] .status-tag')
  47. const shapeOf = (el) => {
  48. const svg = el?.querySelector('svg.status-tag__shape')
  49. if (!svg) return { shape: false }
  50. const r = svg.getBoundingClientRect()
  51. return { shape: true, w: Math.round(r.width), h: Math.round(r.height), display: getComputedStyle(svg).display }
  52. }
  53. const iconOf = (el) => {
  54. const icon = el?.querySelector('.map-holo__legend-icon, .status-tag__icon')
  55. if (!icon) return { icon: false }
  56. const r = icon.getBoundingClientRect()
  57. return { icon: true, w: Math.round(r.width), h: Math.round(r.height), cls: icon.className.baseVal !== undefined ? String(icon.getAttribute('class')) : icon.className }
  58. }
  59. return {
  60. topAlarmHTML: strip(topAlarm?.outerHTML),
  61. topEmptyHTML: strip(topEmpty?.outerHTML),
  62. cardStatusHTML: strip(cardStatus?.outerHTML),
  63. deviceCardHTML: strip(deviceCardTag?.outerHTML),
  64. topAlarmShape: shapeOf(topAlarm?.querySelector('.status-tag')),
  65. topAlarmIcon: iconOf(topAlarm),
  66. cardShape: shapeOf(cardStatus),
  67. cardIcon: iconOf(cardStatus),
  68. }
  69. })
  70. console.log(JSON.stringify(dump, null, 2))
  71. // 放大截图:状态卡区域 + 顶部图例区域
  72. const clipDump = await page.evaluate(() => {
  73. const pick = (el) => { const r = el.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height } }
  74. return {
  75. card: pick(document.querySelector('[data-point-status-card]')),
  76. legend: pick(document.querySelector('[data-map-legend]')),
  77. }
  78. })
  79. const OUT = path.resolve('.scratch/cockpit-real-data-wayfinding/verify/artifacts/t46')
  80. await mkdir(OUT, { recursive: true })
  81. for (const [tag, clip] of Object.entries(clipDump)) {
  82. const b64 = await page.screenshot({ clip: { ...clip, scale: 3 } })
  83. await writeFile(path.join(OUT, `zoom-${tag}.png`), Buffer.from(b64, 'base64'))
  84. }
  85. console.log('zoom shots written')
  86. await browser.close()