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

87 lines
5.2 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 = [], failed = [];
for (const i of idxs) {
let reason = 'unknown';
for (let attempt = 0; attempt < 3; attempt++) {
reason = await fillOneRow(c, i);
if (reason === 'ok') break;
await c.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Escape', code: 'Escape', windowsVirtualKeyCode: 27 }).catch(() => {});
await wait(1500);
}
if (reason === 'ok') done.push(i); else failed.push(i + ':' + reason);
}
if (failed.length) console.log(' 预览图未挂上:', failed.join(', '));
return done;
}
async function fillOneRow(c, i) {
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) return 'no-cell';
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') return li;
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()) return 'no-ref-modal';
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);
const has = await c.evalJS(`(()=>{const p=${P};const r=p._s.get('temuAddSkuDataStore').$state.formState.skuDataList[${i}]; return r&&r.image?1:0;})()`);
return Number(has) ? 'ok' : 'no-image-set';
}
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);
}