// 验证「单向联动」v2:全部用 JS 设置 scrollTop 触发 scroll 事件(避免 mouseWheel SIGTERM) const list = await fetch('http://localhost:9223/json').then((r) => r.json()); const page = list.find((t) => t.type === 'page' && t.url.includes('#/rewrites')); if (!page) { console.log('ERR: no #/rewrites page'); process.exit(1); } const ws = new WebSocket(page.webSocketDebuggerUrl); let id = 0; const pending = new Map(); const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; await new Promise((r) => (ws.onopen = r)); const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); await send('Page.enable'); await send('Runtime.enable'); await send('Page.reload', { ignoreCache: true }); await sleep(3000); await ev(`document.querySelectorAll('.modal-mask').forEach((m) => m.remove()); 'cleaned'`); await ev(`(() => { const i = document.querySelector('#rwSearch'); if (i) { i.value = '快递'; i.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); } return !!i; })()`); await sleep(2000); const clickRes = await ev(`(() => { const btns = [...document.querySelectorAll('[data-detail]')]; if (!btns.length) return { err: 'no data-detail' }; btns[0].click(); return { clicked: btns[0].dataset.detail }; })()`); console.log('打开弹窗:', JSON.stringify(clickRes)); await sleep(2500); const st = async () => ev(`(() => { const secs = [...document.querySelectorAll('.rw-stack > .rw-sec')]; if (!secs.length) return { err: 'no rw-stack' }; const ai = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent.trim() === 'AI脚本'); const sb = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent.trim() === 'AI分镜提示词'); if (!ai || !sb) return { err: 'missing col', heads: secs.map((s) => s.querySelector('.rw-sec-head')?.textContent.trim()) }; const aiBody = ai.querySelector('.rw-sec-body'); const sbBody = sb.querySelector('.rw-sec-body'); const vis = [...sbBody.querySelectorAll('.sb-block')].find((b) => b.getBoundingClientRect().top >= sbBody.getBoundingClientRect().top - 4); return { aiScrollTop: Math.round(aiBody.scrollTop), sbScrollTop: Math.round(sbBody.scrollTop), aiHeads: aiBody.querySelectorAll('strong').length, sbBlocks: sbBody.querySelectorAll('.sb-block').length, sbVis: vis ? (vis.querySelector('.sb-head')?.textContent.trim() || '').slice(0, 30) : null, }; })()`); const init = await st(); console.log('初始:', JSON.stringify(init)); // 翻到有分镜的 AI 脚本版本(rewrite 22) if ((init.sbBlocks || 0) === 0) { for (let i = 0; i < 5; i++) { const hasNext = await ev(`(() => { const b = document.querySelector('[data-vnext]'); if (!b || b.disabled) return false; b.click(); return true; })()`); if (!hasNext) break; await sleep(900); const s = await st(); console.log('翻版本后:', JSON.stringify(s)); if ((s.sbBlocks || 0) > 0) break; } } const ready = await st(); console.log('就绪:', JSON.stringify(ready)); if (!(ready.sbBlocks > 0)) { console.log('ERR: 无分镜版本可验证'); process.exit(0); } // ===== 链路①:AI脚本列滚动 → 分镜列应对齐(设置scrollTop+补发scroll事件模拟滚动) ===== const setAi = async (delta, tag) => { const r = await ev(`(() => { const secs = [...document.querySelectorAll('.rw-stack > .rw-sec')]; const aiBody = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent.trim() === 'AI脚本').querySelector('.rw-sec-body'); aiBody.scrollTop += ${delta}; aiBody.dispatchEvent(new Event('scroll')); // CDP下scrollTop赋值不触发事件,补发 return Math.round(aiBody.scrollTop); })()`); await sleep(350); const s = await st(); console.log(`${tag}: aiScrollTop=${r} → sbScrollTop=${s.sbScrollTop} sbVis=${s.sbVis}`); return s; }; let cur = await setAi(400, '链路① 第1步滚400'); cur = await setAi(400, '链路① 第2步滚400'); cur = await setAi(400, '链路① 第3步滚400'); const link1 = { aiMoved: Math.abs(cur.aiScrollTop - ready.aiScrollTop) > 50, sbMoved: Math.abs(cur.sbScrollTop - ready.sbScrollTop) > 50, }; console.log('链路①判定:', JSON.stringify(link1), link1.aiMoved && link1.sbMoved ? '✅ AI脚本主导联动生效' : '❌ 联动异常'); // ===== 链路②:分镜列滚动 → AI脚本列独立 ===== const beforeSb = await st(); const r2 = await ev(`(() => { const secs = [...document.querySelectorAll('.rw-stack > .rw-sec')]; const sbBody = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent.trim() === 'AI分镜提示词').querySelector('.rw-sec-body'); sbBody.scrollTop += 600; sbBody.dispatchEvent(new Event('scroll')); return Math.round(sbBody.scrollTop); })()`); await sleep(350); const afterSb = await st(); console.log(`链路② 分镜列滚600: sbScrollTop=${r2} → aiScrollTop=${afterSb.aiScrollTop} sbVis=${afterSb.sbVis}`); const link2 = { sbMoved: Math.abs(afterSb.sbScrollTop - beforeSb.sbScrollTop) > 50, aiMovedBack: Math.abs(afterSb.aiScrollTop - beforeSb.aiScrollTop) > 5, }; console.log('链路②判定:', JSON.stringify(link2), link2.sbMoved && !link2.aiMovedBack ? '✅ 分镜列独立滚动,AI脚本列未动' : (link2.sbMoved ? '❌ AI脚本列被反向联动' : '❌ 分镜列未滚动')); // ===== 链路①b:AI脚本列跳底部 → 分镜列跟随最后场 ===== const jump = await ev(`(() => { const secs = [...document.querySelectorAll('.rw-stack > .rw-sec')]; const aiBody = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent.trim() === 'AI脚本').querySelector('.rw-sec-body'); aiBody.scrollTop = aiBody.scrollHeight; aiBody.dispatchEvent(new Event('scroll')); return { set: Math.round(aiBody.scrollTop) }; })()`); await sleep(400); const afterJump = await st(); const detail = await ev(`(() => { const secs = [...document.querySelectorAll('.rw-stack > .rw-sec')]; const aiBody = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent.trim() === 'AI脚本').querySelector('.rw-sec-body'); const heads = [...aiBody.querySelectorAll('strong, h1, h2, h3, h4')].filter((el) => /场\\s*\\d/.test(el.textContent)); const sbBody = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent.trim() === 'AI分镜提示词').querySelector('.rw-sec-body'); const vis = [...sbBody.querySelectorAll('.sb-block')].find((b) => b.getBoundingClientRect().top >= sbBody.getBoundingClientRect().top - 4); return { lastAiHead: heads.length ? heads[heads.length - 1].textContent.trim().slice(0, 24) : null, sbVis: vis ? (vis.querySelector('.sb-head')?.textContent.trim() || '').slice(0, 30) : null }; })()`); console.log('链路①b 跳底部:', JSON.stringify({ jump, sbScrollTop: afterJump.sbScrollTop, detail })); try { ws.close(); } catch (e) {} process.exit(0);