feat 完善注释
This commit is contained in:
156
cdp.js
156
cdp.js
@@ -1,156 +0,0 @@
|
||||
// @ts-nocheck
|
||||
/**
|
||||
* 无依赖的 headless Chrome 截图 + DOM 测量工具(CDP over WebSocket)
|
||||
*
|
||||
* 用法:
|
||||
* node cdp.js <url> [width] [height] [outPng] [evalExpr]
|
||||
*
|
||||
* 环境变量:
|
||||
* CDP_FULL=1 截整页(captureBeyondViewport)
|
||||
* CDP_WAIT=2500 导航后等待毫秒
|
||||
* CDP_HASH= 路由 hash/附加查询串,直接拼在 url 后面
|
||||
*
|
||||
* 说明:本机 agent-browser 会反复 SIGTERM,故改为此脚本;仅用 Node 内置能力。
|
||||
*/
|
||||
import { spawn } from 'node:child_process'
|
||||
import { writeFileSync, mkdtempSync, existsSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const CANDIDATES = [
|
||||
'C:/Program Files/Google/Chrome/Application/chrome.exe',
|
||||
'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
|
||||
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
|
||||
'C:/Program Files/Microsoft/Edge/Application/msedge.exe',
|
||||
]
|
||||
const CHROME = CANDIDATES.find((p) => existsSync(p))
|
||||
if (!CHROME) {
|
||||
console.error('找不到 Chrome/Edge')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const [, , urlArg, wArg, hArg, outArg, exprArg] = process.argv
|
||||
const url = (urlArg || 'http://localhost:5173/') + (process.env.CDP_HASH || '')
|
||||
const width = Number(wArg || 1440)
|
||||
const height = Number(hArg || 900)
|
||||
const out = outArg || 'shot.png'
|
||||
const expr = exprArg || ''
|
||||
const full = process.env.CDP_FULL === '1'
|
||||
const waitMs = Number(process.env.CDP_WAIT || 2800)
|
||||
|
||||
const PORT = 9300 + Math.floor(Math.random() * 400)
|
||||
const userDataDir = mkdtempSync(join(tmpdir(), 'cdp-'))
|
||||
|
||||
const child = spawn(
|
||||
CHROME,
|
||||
[
|
||||
'--headless=new',
|
||||
`--remote-debugging-port=${PORT}`,
|
||||
`--user-data-dir=${userDataDir}`,
|
||||
`--window-size=${width},${height}`,
|
||||
'--no-first-run',
|
||||
'--no-default-browser-check',
|
||||
'--disable-gpu',
|
||||
'--force-device-scale-factor=1',
|
||||
'about:blank',
|
||||
],
|
||||
{ stdio: 'ignore' },
|
||||
)
|
||||
|
||||
async function findTarget() {
|
||||
for (let i = 0; i < 100; i += 1) {
|
||||
try {
|
||||
const res = await fetch(`http://127.0.0.1:${PORT}/json/list`)
|
||||
const list = await res.json()
|
||||
const page = list.find((t) => t.type === 'page' && t.webSocketDebuggerUrl)
|
||||
if (page) return page.webSocketDebuggerUrl
|
||||
}
|
||||
catch { /* 还没起来 */ }
|
||||
await new Promise((r) => setTimeout(r, 150))
|
||||
}
|
||||
throw new Error('Chrome 调试端口未就绪')
|
||||
}
|
||||
|
||||
function makeClient(wsUrl) {
|
||||
const ws = new WebSocket(wsUrl)
|
||||
let seq = 0
|
||||
const pending = new Map()
|
||||
const events = []
|
||||
const ready = new Promise((resolve, reject) => {
|
||||
ws.onopen = () => resolve()
|
||||
ws.onerror = (e) => reject(new Error(`ws error ${e.message || ''}`))
|
||||
})
|
||||
ws.onmessage = (ev) => {
|
||||
const msg = JSON.parse(ev.data)
|
||||
if (msg.id && pending.has(msg.id)) {
|
||||
const { resolve, reject } = pending.get(msg.id)
|
||||
pending.delete(msg.id)
|
||||
if (msg.error) reject(new Error(JSON.stringify(msg.error)))
|
||||
else resolve(msg.result)
|
||||
}
|
||||
else if (msg.method) events.push(msg.method)
|
||||
}
|
||||
const send = (method, params = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
seq += 1
|
||||
pending.set(seq, { resolve, reject })
|
||||
ws.send(JSON.stringify({ id: seq, method, params }))
|
||||
})
|
||||
return { ready, send, events, close: () => ws.close() }
|
||||
}
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
||||
|
||||
try {
|
||||
const wsUrl = await findTarget()
|
||||
const cdp = makeClient(wsUrl)
|
||||
await cdp.ready
|
||||
|
||||
await cdp.send('Page.enable')
|
||||
await cdp.send('Runtime.enable')
|
||||
await cdp.send('Emulation.setDeviceMetricsOverride', {
|
||||
width,
|
||||
height,
|
||||
deviceScaleFactor: 1,
|
||||
mobile: false,
|
||||
})
|
||||
|
||||
await cdp.send('Page.navigate', { url })
|
||||
await sleep(waitMs)
|
||||
|
||||
if (expr) {
|
||||
const res = await cdp.send('Runtime.evaluate', {
|
||||
expression: expr,
|
||||
returnByValue: true,
|
||||
awaitPromise: true,
|
||||
})
|
||||
if (res.exceptionDetails) {
|
||||
console.error('EVAL-ERROR:', JSON.stringify(res.exceptionDetails.exception?.description || res.exceptionDetails))
|
||||
}
|
||||
else {
|
||||
console.log(typeof res.result.value === 'string' ? res.result.value : JSON.stringify(res.result.value))
|
||||
}
|
||||
}
|
||||
|
||||
let clip
|
||||
if (full) {
|
||||
const metrics = await cdp.send('Page.getLayoutMetrics')
|
||||
const size = metrics.cssContentSize || metrics.contentSize
|
||||
clip = { x: 0, y: 0, width: size.width, height: size.height, scale: 1 }
|
||||
}
|
||||
const shot = await cdp.send('Page.captureScreenshot', {
|
||||
format: 'png',
|
||||
captureBeyondViewport: full,
|
||||
...(clip ? { clip } : {}),
|
||||
})
|
||||
writeFileSync(out, Buffer.from(shot.data, 'base64'))
|
||||
console.error(`saved ${out} (${width}x${height}${full ? ' fullpage' : ''})`)
|
||||
cdp.close()
|
||||
}
|
||||
catch (err) {
|
||||
console.error('FAIL:', err.message)
|
||||
process.exitCode = 1
|
||||
}
|
||||
finally {
|
||||
try { child.kill() } catch { /* ignore */ }
|
||||
}
|
||||
19
diag.js
19
diag.js
@@ -1,19 +0,0 @@
|
||||
// @ts-nocheck
|
||||
// 诊断:点页签后输出祖先链几何,定位额外间距来源
|
||||
const TAB = Number(process.argv[2] || 2)
|
||||
const TARGET = process.argv[3] || '.project-files__list'
|
||||
|
||||
const js = `(async function(){
|
||||
const tabs = document.querySelectorAll(".project-detail__tab");
|
||||
if (${TAB} > 0 && tabs[${TAB} - 1]) { tabs[${TAB} - 1].click(); await new Promise(r => setTimeout(r, 900)); }
|
||||
let el = document.querySelector(${JSON.stringify(TARGET)});
|
||||
const chain = [];
|
||||
while (el && chain.length < 6) {
|
||||
const r = el.getBoundingClientRect();
|
||||
const cs = getComputedStyle(el);
|
||||
chain.push({ cls: (el.className || '').toString().slice(0, 40), y: +r.y.toFixed(1), h: +r.height.toFixed(1), mt: cs.marginTop, pt: cs.paddingTop, pb: cs.paddingBottom, gap: cs.gap, rowGap: cs.rowGap });
|
||||
el = el.parentElement;
|
||||
}
|
||||
return JSON.stringify(chain, null, 0);
|
||||
})()`
|
||||
console.log(js)
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,22 +0,0 @@
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const process = require('node:process')
|
||||
|
||||
const filePath = process.argv[2]
|
||||
|
||||
if (!filePath) {
|
||||
console.error('Error: 请提供原型文件路径')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const fullPath = path.resolve(filePath)
|
||||
|
||||
if (fs.existsSync(fullPath)) {
|
||||
console.log('=== START PROTOTYPE CONTENT ===')
|
||||
console.log(fs.readFileSync(fullPath, 'utf-8'))
|
||||
console.log('=== END PROTOTYPE CONTENT ===')
|
||||
}
|
||||
else {
|
||||
console.error(`Error: 未找到原型文件 ${fullPath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 页面:成功案例库(Cases) -->
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 页面:合同中心(Contract) -->
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 页面:需求提报(Demand) -->
|
||||
<script setup lang="ts">
|
||||
// 需求提报(单页三段卡片 + 底部固定操作条,数据全部走真实接口)
|
||||
// 页面 → store → api;页面不直接 import api
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 页面:财务中心(Finance) -->
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 页面:首页(Home) -->
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 页面:登录(Login) -->
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 页面:消息中心(Message) -->
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 页面:个人中心(Profile) -->
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 页面:项目中心(Project) -->
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- 页面:在线客服(Service) -->
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
36
tabs.js
36
tabs.js
@@ -1,36 +0,0 @@
|
||||
// @ts-nocheck
|
||||
// 逐页签量测:node tabs.js 生成一段 JS,在页面内点第 N 个页签后量测
|
||||
const TAB = Number(process.argv[2] || 0)
|
||||
const SELS = process.argv[3]
|
||||
? process.argv[3].split(',')
|
||||
: [
|
||||
'.project-detail__tabs',
|
||||
'.project-files__list',
|
||||
'.project-files__item',
|
||||
'.project-finance__title',
|
||||
'.project-finance__plans',
|
||||
'.project-finance__plan',
|
||||
'.project-finance__files',
|
||||
'.project-finance__file',
|
||||
'.project-after-sales__banner',
|
||||
'.project-after-sales__title',
|
||||
'.project-after-sales__field',
|
||||
'.project-after-sales__readonly',
|
||||
'.project-after-sales__actions',
|
||||
'.project-after-sales__btn',
|
||||
]
|
||||
|
||||
const js = `(async function(){
|
||||
const tabs = document.querySelectorAll(".project-detail__tab");
|
||||
if (${TAB} > 0 && tabs[${TAB} - 1]) { tabs[${TAB} - 1].click(); await new Promise(r => setTimeout(r, 900)); }
|
||||
const o = {};
|
||||
for (const s of ${JSON.stringify(SELS)}) {
|
||||
const list = document.querySelectorAll(s);
|
||||
o[s] = list.length === 0 ? null : Array.from(list).slice(0, 3).map(e => {
|
||||
const r = e.getBoundingClientRect();
|
||||
return [+r.x.toFixed(1), +r.y.toFixed(1), +r.width.toFixed(1), +r.height.toFixed(1)];
|
||||
});
|
||||
}
|
||||
return JSON.stringify(o);
|
||||
})()`
|
||||
console.log(js)
|
||||
Reference in New Issue
Block a user