Temu 全托管:解「预览图」静默拦截(逐行点单元格→引用产品轮播图),首条已进待发布;补 tmpv.mjs 配方与 /draft=采集箱 /offline=待发布 路由事实
This commit is contained in:
1 parent
719ad2b609
commit
cdde45ed9a
4 files changed
+135
-3
No files matched your search
@@ -56,3 +56,33 @@ Temu 的规格机制和 SHEIN **不同构**——没有 `theme-select-box`,
|
||||
`预览图 | 颜色 | 尺码 | SKU货号 | EAN UPC ISBN | 申报价格(CNY) | 尺寸(cm) | 重量(g) | 净重(g) | 建议售价 | SKU分类`
|
||||
|
||||
⚠️ EAN 用公司台账下一段:pc 3447 起(Walmart 已用 3400~3446)
|
||||
|
||||
---
|
||||
|
||||
## 更新3:预览图已解 → **首条已保存进待发布(2026-09-20)**
|
||||
|
||||
### 解法(点单元格,不是点表头)
|
||||
表头那个「批量」下拉是**通用图片工具**(批量传图/压缩/加水印…),跟预览图无关,别点它。
|
||||
真正的入口是**单元格本身**:
|
||||
|
||||
1. 用**真实鼠标事件**点 `.sku-image` 单元格(`mouseMoved` → `mousePressed` → `mouseReleased`;
|
||||
DOM click 打不开)
|
||||
2. 弹出下拉 `.myj-image-drop`:`本地图片 / 空间图片 / 网络图片 / **引用产品轮播图** / 引用采集图片`
|
||||
3. 点「**引用产品轮播图**」→ 弹窗「**引用产品图片**」(列出产品轮播图,每个带「选择图片」)
|
||||
4. 勾「**全部**」→ 点「**选择**」
|
||||
5. 行数据出现 `image: {url, imgUrl, name, width, height}`,单元格变成真图
|
||||
|
||||
**关键坑:这个弹窗是「单格」的**——勾全部只作用**当前那一行**。N 行就要走 N 遍。
|
||||
(`全部` = 把那 5 张轮播图都塞给这一格,单元格只显示第一张。)
|
||||
|
||||
现成脚本:`tmpv.mjs`(`node tmpv.mjs` 给所有 SKU 行挂图,`node tmpv.mjs 1 3` 只挂指定行)
|
||||
|
||||
### 结果
|
||||
`/web/temu/choiceTemuList/offline`(**待发布**)搜标题命中:
|
||||
`Hair Clips Set for Women Girls Fashion Hair Accessories 「①Alimingpro」 ... TM-TEST-1 TM-TEST-2 CNY 12.90`
|
||||
保存成功弹「您的产品已保存到「待发布」,请在待发布页完成发布!」
|
||||
|
||||
### ⚠️ 路由纠正
|
||||
店小秘的列表路由是 **`/draft` = 采集箱、`/offline` = 待发布**(之前搜 `/draft` 找不到东西就是这个原因)。
|
||||
Temu 全托管列表 = `/web/temu/choiceTemuList/{draft|offline|online}`;
|
||||
Temu 半托管 POP = `/web/popTemu/pageList/*`。保存接口是 `/api/pddkjProduct/add*.json`(拼多多跨境=Temu 全托管)。
|
||||
@@ -0,0 +1,24 @@
|
||||
import { newTarget, attach, SAN } from '/tmp/cdp3.mjs';
|
||||
const wait=ms=>new Promise(z=>setTimeout(z,ms));
|
||||
const path='/web/temu/choiceTemuList/offline';
|
||||
await newTarget('https://www.dianxiaomi.com'+path);
|
||||
await wait(11000);
|
||||
const c = await attach('/choiceTemuList/offline');
|
||||
if(!c){console.log('attach fail');process.exit(1);}
|
||||
const set = await c.evalJS(`(()=>{
|
||||
const el=[...document.querySelectorAll('input.ant-input')].filter(e=>e.offsetParent!==null).find(e=>!e.placeholder||e.placeholder===' ');
|
||||
if(!el) return 'no-input';
|
||||
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
|
||||
setter.call(el,'Hair Clips Set for Women'); el.dispatchEvent(new Event('input',{bubbles:true})); return 'set';})()`);
|
||||
console.log('输入:', String(set));
|
||||
await wait(700);
|
||||
await c.evalJS(`(()=>{const b=[...document.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(9000);
|
||||
const o = await c.evalJS(`(()=>{
|
||||
const rows=[...document.querySelectorAll('.vxe-body--row')].map(r=>(r.innerText||'').replace(/\\s+/g,' ').trim());
|
||||
const body=(document.body.innerText||'').replace(/\\s+/g,' '); const m=body.match(/共\\s*\\d+\\s*条/);
|
||||
return JSON.stringify({total:m?m[0]:'', n:rows.length, first:rows.slice(0,3).map(t=>t.slice(0,150))});})()`);
|
||||
console.log('待发布搜索:', SAN(String(o)).slice(0,900));
|
||||
try{ await c.shot('/tmp/temu_offline.jpg'); console.log('shot ok'); }catch(e){ console.log('shot fail'); }
|
||||
process.exit(0);
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
Reference in new issue
Block a user