feat 完善注释

This commit is contained in:
Ironsp
2026-10-01 16:58:22 +08:00
parent 0d2389e49a
commit 2b59f68fd7
24 changed files with 10 additions and 9010 deletions

156
cdp.js
View File

@@ -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
View File

@@ -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

View File

@@ -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)
}

View File

@@ -1,3 +1,4 @@
<!-- 页面:成功案例库(Cases) -->
<script setup lang="ts">
import { onMounted } from 'vue'
import { storeToRefs } from 'pinia'

View File

@@ -1,3 +1,4 @@
<!-- 页面:合同中心(Contract) -->
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { storeToRefs } from 'pinia'

View File

@@ -1,3 +1,4 @@
<!-- 页面:需求提报(Demand) -->
<script setup lang="ts">
// 需求提报(单页三段卡片 + 底部固定操作条,数据全部走真实接口)
// 页面 → store → api;页面不直接 import api

View File

@@ -1,3 +1,4 @@
<!-- 页面:财务中心(Finance) -->
<script setup lang="ts">
import { onMounted } from 'vue'
import { storeToRefs } from 'pinia'

View File

@@ -1,3 +1,4 @@
<!-- 页面:首页(Home) -->
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { storeToRefs } from 'pinia'

View File

@@ -1,3 +1,4 @@
<!-- 页面:登录(Login) -->
<script setup lang="ts">
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'

View File

@@ -1,3 +1,4 @@
<!-- 页面:消息中心(Message) -->
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { storeToRefs } from 'pinia'

View File

@@ -1,3 +1,4 @@
<!-- 页面:个人中心(Profile) -->
<script setup lang="ts">
import { onMounted } from 'vue'
import { storeToRefs } from 'pinia'

View File

@@ -1,3 +1,4 @@
<!-- 页面:项目中心(Project) -->
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { storeToRefs } from 'pinia'

View File

@@ -1,3 +1,4 @@
<!-- 页面:在线客服(Service) -->
<script setup lang="ts">
import { onMounted } from 'vue'
import { storeToRefs } from 'pinia'

36
tabs.js
View File

@@ -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)