Files
dianxiaomi-crossborder-play…/temu建品/tmpv.mjs
T

74 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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);
}