74 lines
4.7 KiB
JavaScript
74 lines
4.7 KiB
JavaScript
/**
|
||
* 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);
|
||
}
|