20260821173417
This commit is contained in:
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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user