/** * Temu 全托管:给 SKU 表「预览图」列批量挂图(已跑通,保存成功) * * 用法: node tmpv.mjs # 给所有 SKU 行挂图 * node tmpv.mjs 1 3 # 只给第 1、3 行(0 基)挂图 * * 交互链(每一行都要走一遍,弹窗是「单格」的,选一次只作用当前行): * 点 .sku-image 单元格 * → 下拉 .myj-image-drop 里点「引用产品轮播图」 * → 弹窗「引用产品图片」勾「全部」→ 点「选择」 * 结果:行数据里出现 image: {url, imgUrl},单元格 img.src 变成 alicdn 真图。 * * 注意:必须用真实鼠标事件点单元格(先 mouseMoved 再 press/release), * DOM click 打不开这个下拉。 */ import { attach } from '/tmp/cdp3.mjs'; const wait = ms => new Promise(z => setTimeout(z, ms)); const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`; export async function fillPreviewImages(c, rowIdxs = null) { const n = Number(await c.evalJS(`(()=>{const p=${P}; return p._s.get('temuAddSkuDataStore').$state.formState.skuDataList.length;})()`)); const idxs = rowIdxs && rowIdxs.length ? rowIdxs : [...Array(n).keys()]; const done = []; for (const i of idxs) { await c.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Escape', code: 'Escape', windowsVirtualKeyCode: 27 }).catch(() => {}); await wait(400); const pos = JSON.parse(String(await c.evalJS(`(()=>{const cell=[...document.querySelectorAll('.sku-image')][${i}]; if(!cell) return '[]'; cell.scrollIntoView({block:'center'}); const r=cell.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`))); if (!pos.length) { console.log(' 行' + i + ': 找不到单元格'); continue; } await c.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: pos[0], y: pos[1], buttons: 0 }); await wait(400); for (const t of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type: t, x: pos[0], y: pos[1], button: 'left', clickCount: 1, buttons: 1 }); await wait(1800); const li = await c.evalJS(`(()=>{ const it=[...document.querySelectorAll('.myj-image-drop .ant-dropdown-menu-item')].filter(e=>e.offsetParent!==null).find(e=>/引用产品轮播图/.test(e.innerText||'')); if(!it) return 'no-menu'; const r=it.getBoundingClientRect(); for(const ev of ['mousedown','mouseup','click']) it.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2})); return 'ok';})()`); if (li !== 'ok') { console.log(' 行' + i + ': 下拉没出(' + li + ')'); continue; } await wait(2000); const hasModal = async () => Number(await c.evalJS( `[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null&&/引用产品图片/.test(e.innerText||'')).length`)); if (!await hasModal()) await wait(1600); if (!await hasModal()) { console.log(' 行' + i + ': 引用产品图片弹窗没开'); continue; } await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null&&/引用产品图片/.test(e.innerText||'')).pop(); const all=[...m.querySelectorAll('*')].find(e=>(e.innerText||'').trim()==='全部'&&e.children.length===0); const t=all?(all.closest('label')||all.parentElement):null; if(t){const cb=t.querySelector('.ant-checkbox')||t; for(const ev of ['mousedown','mouseup','click']) cb.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));} return 1;})()`); await wait(1200); await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null&&/引用产品图片/.test(e.innerText||'')).pop(); const b=[...m.querySelectorAll('button')].find(x=>/^\\s*选择\\s*$/.test((x.innerText||'').trim())); if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`); await wait(2500); done.push(i); } return done; } if (import.meta.url === `file://${process.argv[1]}`) { const c = await attach('/web/temu/add'); if (!c) { console.log('没有 Temu 建品页'); process.exit(1); } const idxs = process.argv.slice(2).map(Number).filter(n => !Number.isNaN(n)); const done = await fillPreviewImages(c, idxs); const chk = await c.evalJS(`(()=>{const p=${P}; return JSON.stringify(p._s.get('temuAddSkuDataStore').$state.formState.skuDataList.map(r=>({id:r._id, img:r.image?(r.image.url||'').slice(-38):'NONE'})));})()`); console.log('已挂图行:', JSON.stringify(done)); console.log('核对:', String(chk).slice(0, 500)); process.exit(0); }