20260821173417

This commit is contained in:
oneao committed 2026-08-21 17:34:18 +08:00
1 parent 45854b0570
commit 2e5d532f23
19 files changed
+1495 -93

No files matched your search

@@ -0,0 +1,97 @@
import { chromium } from 'playwright-core'
import { PNG } from 'pngjs'
const BASE = 'http://localhost:5082'
const browser = await chromium.launch({
headless: true,
executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
})
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 })
await page.addInitScript(() => {
sessionStorage.setItem('fms-login', JSON.stringify({ loginInfo: { token: 'dev-token', user: { id: 1, name: 'dev', account: 'g3soft' } } }))
localStorage.setItem('fms-user', JSON.stringify({ userInfo: { orgId: 'G3HD', account: 'g3soft' } }))
})
await page.route('**/api/**', async (route) => {
const req = route.request()
const url = req.url()
let body = {}
try { body = req.postDataJSON() || {} } catch {}
if (url.includes('/data/loaddata')) {
const rows = body.view_name === 's_module' ? [{ b_id: 1, b_code: 'b_port', b_name: '港口管理' }] :
body.view_name === 'bf_files_cateid' ? [{ b_id: '10', b_name: '合同文件' }] : []
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: rows }) })
}
if (url.includes('/data/page')) {
const files = Array.from({ length: 4 }, (_, i) => ({
subid: String(1000 + i), father: '1001', mx_moduleid: '1', mx_cate_id: '10',
mx_filename: `文件-${i + 1}.pdf`, mx_filesize: (i + 1) * 1024 * 1024,
mx_fileext: 'pdf', mx_mapfilename: '',
}))
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: { rows: files, total: files.length } }) })
}
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: null }) })
})
await page.goto(`${BASE}/file-demo`, { waitUntil: 'networkidle', timeout: 30000 })
await page.waitForTimeout(2000)
await page.locator('button:has-text("声明式弹窗")').click()
await page.waitForTimeout(1500)
const buf = await page.screenshot({ fullPage: false })
const png = PNG.sync.read(buf)
// Scan for "line-like" pixels: contiguous vertical runs of a gray border color in the splitter region.
// Detect distinct vertical line clusters (x positions with many pixels of border color).
const { width, height, data } = png
const isBorderColor = (x, y) => {
const i = (y * width + x) * 4
const r = data[i], g = data[i + 1], b = data[i + 2]
// fms-border rgb(229,230,235) — treat near-gray light lines
return Math.abs(r - g) <= 6 && Math.abs(g - b) <= 6 && r >= 200 && r <= 245
}
const colCounts = new Array(width).fill(0)
for (let x = 0; x < width; x++) {
for (let y = 200; y < 850; y++) {
if (isBorderColor(x, y)) colCounts[x]++
}
}
// Find clusters of consecutive columns with meaningful counts
const clusters = []
let start = -1
for (let x = 0; x < width; x++) {
if (colCounts[x] > 300) {
if (start === -1) start = x
} else if (start !== -1) {
clusters.push({ from: start, to: x - 1, count: colCounts[start] })
start = -1
}
}
if (start !== -1) clusters.push({ from: start, to: width - 1, count: colCounts[start] })
console.log('vertical line clusters (border-colored, >300px tall):')
console.log(JSON.stringify(clusters, null, 2))
// Also check the modal resize handles positions
const handles = await page.evaluate(() => {
const hs = Array.from(document.querySelectorAll('.fms-modal-resizer')).map((el) => {
const r = el.getBoundingClientRect()
const cs = getComputedStyle(el)
const before = getComputedStyle(el, '::before')
return {
cls: el.className,
x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height),
beforeW: before.width, beforeH: before.height, beforeOpacity: before.opacity,
}
})
const bars = Array.from(document.querySelectorAll('.fms-splitter-bar')).map((el) => {
const r = el.getBoundingClientRect()
return { cls: el.className, x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }
})
return { handles, bars }
})
console.log('modal resize handles + splitter bars:')
console.log(JSON.stringify(handles, null, 2))
// Save the screenshot for reference
require('fs').writeFileSync('.playwright-cli/dialog-geo.png', buf)
await browser.close()
Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

@@ -0,0 +1,92 @@
import { chromium } from 'playwright-core'
const BASE = 'http://localhost:5082'
const browser = await chromium.launch({
headless: true,
executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
})
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } })
await page.addInitScript(() => {
sessionStorage.setItem('fms-login', JSON.stringify({ loginInfo: { token: 'dev-token', user: { id: 1, name: 'dev', account: 'g3soft' } } }))
localStorage.setItem('fms-user', JSON.stringify({ userInfo: { orgId: 'G3HD', account: 'g3soft' } }))
})
await page.route('**/api/**', async (route) => {
const req = route.request()
const url = req.url()
let body = {}
try { body = req.postDataJSON() || {} } catch {}
if (url.includes('/data/loaddata')) {
const rows = body.view_name === 's_module' ? [{ b_id: 1, b_code: 'b_port', b_name: '港口管理' }] :
body.view_name === 'bf_files_cateid' ? [{ b_id: '10', b_name: '合同文件' }] : []
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: rows }) })
}
if (url.includes('/data/page')) {
const files = Array.from({ length: 4 }, (_, i) => ({
subid: String(1000 + i), father: '1001', mx_moduleid: '1', mx_cate_id: '10',
mx_filename: `文件-${i + 1}.pdf`, mx_filesize: (i + 1) * 1024 * 1024,
mx_fileext: 'pdf', mx_mapfilename: '',
}))
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: { rows: files, total: files.length } }) })
}
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: null }) })
})
await page.goto(`${BASE}/file-demo`, { waitUntil: 'networkidle', timeout: 30000 })
await page.waitForTimeout(2000)
await page.locator('button:has-text("声明式弹窗")').click()
await page.waitForTimeout(1500)
const result = await page.evaluate(() => {
const ff = document.querySelector('.ff-splitter')
if (!ff) return { error: 'no .ff-splitter' }
const all = ff.querySelectorAll(':scope > *')
const items = Array.from(all).map((el, i) => {
const cls = el.className
const rect = el.getBoundingClientRect()
const cs = getComputedStyle(el)
const barInfo = cls.includes('fms-splitter-bar')
? (() => {
const style = getComputedStyle(el, '::before')
const grip = el.querySelector('.fms-splitter-bar-grip')
const gripStyle = grip ? getComputedStyle(grip) : null
return {
w: Math.round(rect.width),
beforeW: style.width,
beforeH: style.height,
beforeBg: style.background,
gripW: gripStyle ? gripStyle.width : null,
gripH: gripStyle ? gripStyle.height : null,
gripOpacity: gripStyle ? gripStyle.opacity : null,
}
})()
: null
return {
index: i,
class: cls,
left: Math.round(rect.left),
top: Math.round(rect.top),
width: Math.round(rect.width),
height: Math.round(rect.height),
csWidth: cs.width,
csMargin: cs.margin,
csFlex: cs.flex,
csFlexBasis: cs.flexBasis,
barInfo,
}
})
const container = ff.closest('.ff-container')
const containerRect = container ? container.getBoundingClientRect() : null
const containerCS = container ? getComputedStyle(container) : null
const modalBody = document.querySelector('.fms-modal-body')
const modalBodyRect = modalBody ? modalBody.getBoundingClientRect() : null
const panels = Array.from(ff.querySelectorAll(':scope > .fms-splitter-panel')).map((el) => {
const r = el.getBoundingClientRect()
const cs = getComputedStyle(el)
return { w: Math.round(r.width), h: Math.round(r.height), flex: cs.flex, flexBasis: cs.flexBasis, padding: cs.padding, border: cs.border }
})
return { items, containerRect, containerCS: containerCS ? { border: containerCS.border, padding: containerCS.padding } : null, modalBodyRect, panels }
})
console.log(JSON.stringify(result, null, 2))
await browser.close()
@@ -0,0 +1,57 @@
import { chromium } from 'playwright-core'
const BASE = 'http://localhost:5082'
const browser = await chromium.launch({
headless: true,
executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
})
const page = await browser.newPage({ viewport: { width: 1600, height: 1000 } })
const logs = []
page.on('console', (m) => {
if (m.type() === 'error') logs.push(`[console.error] ${m.text()}`)
})
page.on('pageerror', (e) => logs.push(`[pageerror] ${e.message}`))
await page.addInitScript(() => {
sessionStorage.setItem(
'fms-login',
JSON.stringify({ loginInfo: { token: 'dev-token', user: { id: 1, name: 'dev' } } }),
)
localStorage.setItem('fms-user', JSON.stringify({ userInfo: { orgId: 'G3HD', account: 'g3soft' } }))
})
// mock all API calls so the fake token never gets 401'd
await page.route('**/api/**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ code: 0, data: null, message: '' }),
}),
)
try {
await page.goto(`${BASE}/file-demo`, { waitUntil: 'networkidle', timeout: 30000 })
await page.waitForTimeout(1500)
console.log('url after load:', page.url())
const clickInfo = await page.evaluate(() => {
const els = Array.from(document.querySelectorAll('.button, button'))
const el = els.find((b) => (b.textContent || '').includes('声明式弹窗'))
if (!el) return { found: false }
el.click()
return { found: true }
})
console.log('click:', JSON.stringify(clickInfo))
await page.waitForTimeout(1200)
const state = await page.evaluate(() => ({
url: location.href,
modalCount: document.querySelectorAll('.fms-modal').length,
ffCount: document.querySelectorAll('.ff-container').length,
ffSplitter: !!document.querySelector('.ff-splitter'),
}))
console.log('state:', JSON.stringify(state))
} catch (e) {
console.log('ERROR:', String(e))
}
console.log('logs:', JSON.stringify(logs.slice(0, 10), null, 2))
await browser.close()
+29
View File
@@ -0,0 +1,29 @@
import { chromium } from 'playwright-core'
const BASE = 'http://localhost:5082'
const browser = await chromium.launch({
headless: true,
executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
})
const page = await browser.newPage({ viewport: { width: 1600, height: 1000 } })
page.on('framenavigated', (f) => {
if (f === page.mainFrame()) console.log('nav ->', f.url())
})
await page.addInitScript(() => {
sessionStorage.setItem(
'fms-login',
JSON.stringify({ loginInfo: { token: 'dev-token', user: { id: 1, name: 'dev' } } }),
)
localStorage.setItem('fms-user', JSON.stringify({ userInfo: { orgId: 'G3HD', account: 'g3soft' } }))
})
await page.goto(`${BASE}/file-demo`, { waitUntil: 'domcontentloaded', timeout: 30000 })
await page.waitForTimeout(2500)
console.log('final url:', page.url())
console.log('session:', await page.evaluate(() => sessionStorage.getItem('fms-login')))
console.log(
'body snippet:',
await page.evaluate(() => (document.body.textContent || '').slice(0, 120).replace(/\s+/g, ' ')),
)
await browser.close()
@@ -0,0 +1,97 @@
import { chromium } from 'playwright-core'
const BASE = 'http://localhost:5082'
const browser = await chromium.launch({
headless: true,
executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
})
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 })
await page.addInitScript(() => {
sessionStorage.setItem('fms-login', JSON.stringify({ loginInfo: { token: 'dev-token', user: { id: 1, name: 'dev', account: 'g3soft' } } }))
localStorage.setItem('fms-user', JSON.stringify({ userInfo: { orgId: 'G3HD', account: 'g3soft' } }))
})
await page.route('**/api/**', async (route) => {
const req = route.request()
const url = req.url()
let body = {}
try { body = req.postDataJSON() || {} } catch {}
if (url.includes('/data/loaddata')) {
const rows = body.view_name === 's_module' ? [{ b_id: 1, b_code: 'b_port', b_name: '港口管理' }] :
body.view_name === 'bf_files_cateid' ? [{ b_id: '10', b_name: '合同文件' }] : []
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: rows }) })
}
if (url.includes('/data/page')) {
const files = Array.from({ length: 4 }, (_, i) => ({
subid: String(1000 + i), father: '1001', mx_moduleid: '1', mx_cate_id: '10',
mx_filename: `文件-${i + 1}.pdf`, mx_filesize: (i + 1) * 1024 * 1024,
mx_fileext: 'pdf', mx_mapfilename: '',
}))
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: { rows: files, total: files.length } }) })
}
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: null }) })
})
await page.goto(`${BASE}/file-demo`, { waitUntil: 'networkidle', timeout: 30000 })
await page.waitForTimeout(2000)
await page.locator('button:has-text("声明式弹窗")').click()
await page.waitForTimeout(1500)
// Take screenshot as base64, load into canvas inside the page, and sample pixel columns
const b64 = await page.screenshot({ encoding: 'base64' })
const analysis = await page.evaluate(async (b64) => {
const img = new Image()
img.src = 'data:image/png;base64,' + b64
await img.decode()
const canvas = document.createElement('canvas')
canvas.width = img.width
canvas.height = img.height
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
const { width, height } = canvas
// get color at pixel
const px = (x, y) => {
const d = ctx.getImageData(x, y, 1, 1).data
return `${d[0]},${d[1]},${d[2]}`
}
// Scan a horizontal strip at y = 500 (mid of dialog) from x=570 to x=1410
// and report runs of distinct colors
const y = 500
const runs = []
let cur = null
for (let x = 570; x < 1410; x++) {
const c = px(x, y)
if (cur && cur.color === c) {
cur.to = x
} else {
if (cur) runs.push(cur)
cur = { color: c, from: x, to: x }
}
}
if (cur) runs.push(cur)
// Vertical line detection: for a given x, count how many y in [215, 850] have the same color
const lineAt = (x) => {
const counts = new Map()
for (let yy = 215; yy < 850; yy += 2) {
const c = px(x, yy)
counts.set(c, (counts.get(c) || 0) + 1)
}
const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3)
return sorted.map(([c, n]) => ({ color: c, count: n }))
}
// Check the two bar regions: every x in the bar area, list dominant colors
const bars = []
for (const [x0, x1] of [[795, 806], [1301, 1312]]) {
const cols = []
for (let x = x0; x <= x1; x++) cols.push({ x, colors: lineAt(x) })
bars.push(cols)
}
return { runs, bars, imgW: width, imgH: height }
}, b64)
console.log(JSON.stringify(analysis, null, 2))
await browser.close()
+72
View File
@@ -0,0 +1,72 @@
import { chromium } from 'playwright-core'
const BASE = 'http://localhost:5082'
const browser = await chromium.launch({
headless: true,
executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
})
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 })
await page.addInitScript(() => {
sessionStorage.setItem('fms-login', JSON.stringify({ loginInfo: { token: 'dev-token', user: { id: 1, name: 'dev', account: 'g3soft' } } }))
localStorage.setItem('fms-user', JSON.stringify({ userInfo: { orgId: 'G3HD', account: 'g3soft' } }))
})
await page.route('**/api/**', async (route) => {
const req = route.request()
const url = req.url()
let body = {}
try { body = req.postDataJSON() || {} } catch {}
if (url.includes('/data/loaddata')) {
const rows = body.view_name === 's_module' ? [{ b_id: 1, b_code: 'b_port', b_name: '港口管理' }] :
body.view_name === 'bf_files_cateid' ? [{ b_id: '10', b_name: '合同文件' }] : []
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: rows }) })
}
if (url.includes('/data/page')) {
const files = Array.from({ length: 4 }, (_, i) => ({
subid: String(1000 + i), father: '1001', mx_moduleid: '1', mx_cate_id: '10',
mx_filename: `文件-${i + 1}.pdf`, mx_filesize: (i + 1) * 1024 * 1024,
mx_fileext: 'pdf', mx_mapfilename: '',
}))
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: { rows: files, total: files.length } }) })
}
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: null }) })
})
await page.goto(`${BASE}/file-demo`, { waitUntil: 'networkidle', timeout: 30000 })
await page.waitForTimeout(2000)
await page.locator('button:has-text("声明式弹窗")').click()
await page.waitForTimeout(1500)
const b64 = await page.screenshot({ encoding: 'base64' })
const analysis = await page.evaluate(async (b64) => {
const bin = atob(b64)
const bytes = new Uint8Array(bin.length)
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)
const blob = new Blob([bytes], { type: 'image/png' })
const bmp = await createImageBitmap(blob)
const canvas = document.createElement('canvas')
canvas.width = bmp.width
canvas.height = bmp.height
const ctx = canvas.getContext('2d')
ctx.drawImage(bmp, 0, 0)
const { width, height } = canvas
const px = (x, y) => {
const d = ctx.getImageData(x, y, 1, 1).data
return `${d[0]},${d[1]},${d[2]}`
}
// For each x in the splitter region, count how many sample rows are the border-gray color
// and report a compact "row" profile at several y positions
const yPositions = [220, 300, 400, 500, 600, 700]
const profile = []
for (let x = 370; x <= 930; x++) {
const row = {}
for (const y of yPositions) row[`y${y}`] = px(x, y)
profile.push({ x, ...row })
}
return { imgW: width, imgH: height, profile }
}, b64)
console.log(JSON.stringify(analysis, null, 2))
await browser.close()
+123
View File
@@ -0,0 +1,123 @@
import { chromium } from 'playwright-core'
import zlib from 'node:zlib'
const BASE = 'http://localhost:5082'
const browser = await chromium.launch({
headless: true,
executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
})
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 })
await page.addInitScript(() => {
sessionStorage.setItem('fms-login', JSON.stringify({ loginInfo: { token: 'dev-token', user: { id: 1, name: 'dev', account: 'g3soft' } } }))
localStorage.setItem('fms-user', JSON.stringify({ userInfo: { orgId: 'G3HD', account: 'g3soft' } }))
})
await page.route('**/api/**', async (route) => {
const req = route.request()
const url = req.url()
let body = {}
try { body = req.postDataJSON() || {} } catch {}
if (url.includes('/data/loaddata')) {
const rows = body.view_name === 's_module' ? [{ b_id: 1, b_code: 'b_port', b_name: '港口管理' }] :
body.view_name === 'bf_files_cateid' ? [{ b_id: '10', b_name: '合同文件' }] : []
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: rows }) })
}
if (url.includes('/data/page')) {
const files = Array.from({ length: 4 }, (_, i) => ({
subid: String(1000 + i), father: '1001', mx_moduleid: '1', mx_cate_id: '10',
mx_filename: `文件-${i + 1}.pdf`, mx_filesize: (i + 1) * 1024 * 1024,
mx_fileext: 'pdf', mx_mapfilename: '',
}))
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: { rows: files, total: files.length } }) })
}
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: null }) })
})
await page.goto(`${BASE}/file-demo`, { waitUntil: 'networkidle', timeout: 30000 })
await page.waitForTimeout(2000)
await page.locator('button:has-text("声明式弹窗")').click()
await page.waitForTimeout(1500)
const buf = await page.screenshot()
// ---- minimal PNG decoder ----
function decodePNG(buf) {
if (buf.readUInt32BE(0) !== 0x89504e47) throw new Error('not png')
let pos = 8
let width = 0, height = 0, bitDepth = 0, colorType = 0
const idat = []
while (pos < buf.length) {
const len = buf.readUInt32BE(pos)
const type = buf.toString('ascii', pos + 4, pos + 8)
const data = buf.subarray(pos + 8, pos + 8 + len)
if (type === 'IHDR') {
width = data.readUInt32BE(0)
height = data.readUInt32BE(4)
bitDepth = data[8]
colorType = data[9]
} else if (type === 'IDAT') {
idat.push(data)
} else if (type === 'IEND') break
pos += 12 + len
}
const channels = colorType === 6 ? 4 : colorType === 2 ? 3 : colorType === 0 ? 1 : 4
const bpp = Math.max(1, (channels * bitDepth) / 8)
const raw = zlib.inflateSync(Buffer.concat(idat))
const stride = width * channels
const out = Buffer.alloc(height * stride)
const paeth = (a, b, c) => {
const p = a + b - c
const pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c)
if (pa <= pb && pa <= pc) return a
if (pb <= pc) return b
return c
}
for (let y = 0; y < height; y++) {
const filter = raw[y * (stride + 1)]
const row = raw.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1))
const prev = y > 0 ? out.subarray((y - 1) * stride, y * stride) : null
const cur = out.subarray(y * stride, (y + 1) * stride)
for (let x = 0; x < stride; x++) {
const a = x >= bpp ? cur[x - bpp] : 0
const b = prev ? prev[x] : 0
const c = x >= bpp && prev ? prev[x - bpp] : 0
let v = row[x]
if (filter === 1) v = (v + a) & 0xff
else if (filter === 2) v = (v + b) & 0xff
else if (filter === 3) v = (v + Math.floor((a + b) / 2)) & 0xff
else if (filter === 4) v = (v + paeth(a, b, c)) & 0xff
cur[x] = v
}
}
return { width, height, channels, data: out }
}
const png = decodePNG(buf)
const { width, height, channels, data } = png
const px = (x, y) => {
const i = (y * width + x) * channels
return channels === 4
? `${data[i]},${data[i + 1]},${data[i + 2]}`
: `${data[i]},${data[i + 1]},${data[i + 2]}`
}
// Profile horizontal strip at several y's around the two bars (x 380..940)
const yPositions = [220, 300, 400, 500, 600, 700, 800]
const compact = []
for (let x = 385; x <= 940; x++) {
const row = { x }
for (const y of yPositions) row[`y${y}`] = px(x, y)
compact.push(row)
}
// Also sample a vertical strip through both bars at mid height to show line thickness
const vertical = []
for (let y = 215; y <= 720; y += 10) {
vertical.push({ y, bar1: px(400, y), bar2: px(906, y), gap1: px(397, y), gap2: px(403, y) })
}
console.log('HORIZONTAL PROFILE (x around both bars):')
for (const r of compact) console.log(JSON.stringify(r))
console.log('\nVERTICAL PROFILE:')
for (const r of vertical) console.log(JSON.stringify(r))
await browser.close()
@@ -0,0 +1,68 @@
import { chromium } from 'playwright-core'
const BASE = 'http://localhost:5082'
const browser = await chromium.launch({
headless: true,
executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
})
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 })
await page.addInitScript(() => {
sessionStorage.setItem('fms-login', JSON.stringify({ loginInfo: { token: 'dev-token', user: { id: 1, name: 'dev', account: 'g3soft' } } }))
localStorage.setItem('fms-user', JSON.stringify({ userInfo: { orgId: 'G3HD', account: 'g3soft' } }))
})
await page.route('**/api/**', async (route) => {
const req = route.request()
const url = req.url()
let body = {}
try { body = req.postDataJSON() || {} } catch {}
if (url.includes('/data/loaddata')) {
const rows = body.view_name === 's_module' ? [{ b_id: 1, b_code: 'b_port', b_name: '港口管理' }] :
body.view_name === 'bf_files_cateid' ? [{ b_id: '10', b_name: '合同文件' }] : []
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: rows }) })
}
if (url.includes('/data/page')) {
const files = Array.from({ length: 4 }, (_, i) => ({
subid: String(1000 + i), father: '1001', mx_moduleid: '1', mx_cate_id: '10',
mx_filename: `文件-${i + 1}.pdf`, mx_filesize: (i + 1) * 1024 * 1024,
mx_fileext: 'pdf', mx_mapfilename: '',
}))
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: { rows: files, total: files.length } }) })
}
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, data: null }) })
})
await page.goto(`${BASE}/file-demo`, { waitUntil: 'networkidle', timeout: 30000 })
await page.waitForTimeout(2000)
await page.locator('button:has-text("声明式弹窗")').click()
await page.waitForTimeout(1500)
const result = await page.evaluate(() => {
const lines = []
function info(el) {
if (!el) return null
const r = el.getBoundingClientRect()
const cs = getComputedStyle(el)
return {
tag: el.tagName.toLowerCase(),
cls: String(el.className),
x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height),
display: cs.display,
padding: cs.padding,
border: cs.border,
overflow: cs.overflow,
}
}
const walk = (el, depth) => {
if (!el || depth > 12) return
lines.push(' '.repeat(depth * 2) + JSON.stringify(info(el)))
for (const child of el.children) walk(child, depth + 1)
}
const modal = document.querySelector('.fms-modal')
if (!modal) return { error: 'no modal' }
walk(modal, 0)
return lines
})
console.log('RESULT:', JSON.stringify(result))
await browser.close()