Joom 47 条进待发布:jmbatch 跑通(店铺复选框组/可搜索类目/smtChoice* store),新增 3 条坑(无星号必填、虚拟滚动下拉、哨兵串定位 store)与 4 平台侦察表
This commit is contained in:
1 parent
c69952bd59
commit
c275571317
17 files changed
+1156
-4
No files matched your search
@@ -0,0 +1,47 @@
|
||||
# Joom 建品配方(已跑通,批量进行中)
|
||||
|
||||
路由:`https://www.dianxiaomi.com/web/joomProduct/add` 列表:`/web/joomProduct/{draft=采集箱, offline=待发布}`
|
||||
|
||||
脚本:`jmbatch.mjs`(`node jmbatch.mjs` 全量 · `node jmbatch.mjs <pid>` 单条 · `--dry` 只构建)
|
||||
断点:`jm_done.json` 逐条日志:`jm_batch.log` 实际落到的类目:`joom_route.json`
|
||||
|
||||
## ⭐ 关键发现:Joom 的表单数据不在 `joom*` store 里
|
||||
商品主体数据落在 **`smtChoiceAddBasicStore`**(速卖通全托管的 store),
|
||||
Joom 与 **aliChoice 共用同一套 `smtChoice*` store**。定位方法:在页面上手输一个
|
||||
哨兵串(`PROBE-TITLE-XYZ`),再遍历所有 pinia store 的 `$state` 找它落在哪。
|
||||
|
||||
| 字段 | 落点 |
|
||||
|---|---|
|
||||
| 店铺 | `smtChoiceAddBasicStore.formState.shopIds[]` / `shopId`(**店铺是复选框组,不是下拉**) |
|
||||
| 类目 | `smtChoiceAddBasicStore.formState.categoryId`(复合串 `1473…-16-2-118-2600…`) |
|
||||
| 标题 / ParentSKU / 描述 / 标签 | `smtChoiceAddBasicStore.formState.{name, parentSku, description, tags[]}` |
|
||||
| 价格 / 库存 / 运费 / MSRP / 报关金额 | `joomAddPriceStore.formState.{price, inventory, shipping, msrp, declaredValue}` |
|
||||
| 主图 / 详情图 | `joomAddImageStore.formState.imageList[]` / `detailImageList[0].images[]`(吃 `{url,imgUrl,name,width,height}` 数组) |
|
||||
| SKU / 颜色 / 尺码 | `joomSkuDataStore.formState.skuDataList[]` / `joomSkuAttrStore.formState.{colorValues,sizeValues}` |
|
||||
| 自定义属性 | `smtChoiceAddAttrStore.formState.customAttrs[]` |
|
||||
| 类目属性(`attrsInfo.attrsList` 10 条) | `joomAddStore.attrsInfo`(`joomAddStore` **没有 formState**) |
|
||||
|
||||
## 单条顺序(不能换)
|
||||
1. **勾店铺**:`店铺名称` 是 `.d-checkbox-group-wrapper`,共 **85 个复选框** = `全部` + 84 家店。
|
||||
勾「第一个非全部」= `Dummer-Pets`。⚠️ **别碰 `全部`**(那是铺满 84 家店)。
|
||||
2. **选类目**:`产品分类` 是可搜索 `.ant-select`(不是 SHEIN/Temu 的弹窗)。点开 →
|
||||
`Input.insertText(cat_kw)` → 在 `.ant-select-item-option` 里选**去掉括注后完全相等**的,
|
||||
没有就退到第一个(Joom 类目比 1688 粗,实测「发圈」只能落到「发夹(Hairpins)」)。
|
||||
3. 填 store(标题/ParentSKU/描述/标签 + 价格/库存/运费/报关金额 + 主图/详情图)
|
||||
4. `fillRequiredSelects()`(复用 SHEIN 的兜底)→ **敏感货类型 = 普通货**
|
||||
5. **详情图语言** → 目标项**标签就叫「详情图」**(不含"语言"二字):
|
||||
下拉是虚拟滚动的,前 10 项没有「英语」→ 先 `Input.insertText('英')` 过滤,
|
||||
再点含「英」的项(否则会落到「阿拉伯语」)
|
||||
6. 保存 → `您的产品已保存到「待发布」`
|
||||
|
||||
## 钩子新抓出的两条「没标必填但必须填」
|
||||
| 被吞的报错 | 真相 |
|
||||
|---|---|
|
||||
| `MYJ.error: 运费不能为空` | 运费**没有** `*`,但必须填(给 `0` 即可) |
|
||||
| `MYJ.error: 请选择详情图语言且至少添加一张详情图` | 语言必选;图片 store 已赋值也要选语言 |
|
||||
|
||||
## 与其它平台的差异
|
||||
- 店铺:Joom 是**复选框组**(多选=铺多店),SHEIN/Temu/Lazada 是下拉
|
||||
- 类目:Joom 是**可搜索下拉平铺列表**,SHEIN/Temu 是「选择类目」弹窗(搜索→点结果→关弹窗)
|
||||
- 数据落点:Joom 用 `smtChoice*`(与 aliChoice 同构),SHEIN/Temu 各有自己的 `shein*` / `temu*`
|
||||
- **不消耗 EAN**(GTIN 非必填、`showGtin=false`)
|
||||
@@ -0,0 +1,145 @@
|
||||
{
|
||||
"548459586741": {
|
||||
"ok": true
|
||||
},
|
||||
"661190598139": {
|
||||
"ok": true
|
||||
},
|
||||
"764423265986": {
|
||||
"ok": true
|
||||
},
|
||||
"685456757807": {
|
||||
"ok": true
|
||||
},
|
||||
"735147938123": {
|
||||
"ok": true
|
||||
},
|
||||
"627879474686": {
|
||||
"ok": true
|
||||
},
|
||||
"610164857063": {
|
||||
"ok": true
|
||||
},
|
||||
"708420302844": {
|
||||
"ok": true
|
||||
},
|
||||
"866537106657": {
|
||||
"ok": true
|
||||
},
|
||||
"547183997437": {
|
||||
"ok": true
|
||||
},
|
||||
"657617635303": {
|
||||
"ok": true
|
||||
},
|
||||
"641673262387": {
|
||||
"ok": true
|
||||
},
|
||||
"953869908708": {
|
||||
"ok": true
|
||||
},
|
||||
"703822717997": {
|
||||
"ok": true
|
||||
},
|
||||
"863524134410": {
|
||||
"ok": true
|
||||
},
|
||||
"694637537727": {
|
||||
"ok": true
|
||||
},
|
||||
"623011262210": {
|
||||
"ok": true
|
||||
},
|
||||
"836512561982": {
|
||||
"ok": true
|
||||
},
|
||||
"714492799386": {
|
||||
"ok": true
|
||||
},
|
||||
"591994902720": {
|
||||
"ok": true
|
||||
},
|
||||
"853414558425": {
|
||||
"ok": true
|
||||
},
|
||||
"997450831819": {
|
||||
"ok": true
|
||||
},
|
||||
"639409307567": {
|
||||
"ok": true
|
||||
},
|
||||
"902440558099": {
|
||||
"ok": true
|
||||
},
|
||||
"823838532886": {
|
||||
"ok": true
|
||||
},
|
||||
"698782391248": {
|
||||
"ok": true
|
||||
},
|
||||
"707826905566": {
|
||||
"ok": true
|
||||
},
|
||||
"1027332215847": {
|
||||
"ok": true
|
||||
},
|
||||
"682195499823": {
|
||||
"ok": true
|
||||
},
|
||||
"823560564174": {
|
||||
"ok": true
|
||||
},
|
||||
"739147684759": {
|
||||
"ok": true
|
||||
},
|
||||
"621300323670": {
|
||||
"ok": true
|
||||
},
|
||||
"601964534137": {
|
||||
"ok": false,
|
||||
"skipped": true,
|
||||
"msg": "全平台统一跳过:18 张图全带水印、无干净图(imgs=0)"
|
||||
},
|
||||
"896469995553": {
|
||||
"ok": true
|
||||
},
|
||||
"944470567338": {
|
||||
"ok": true
|
||||
},
|
||||
"787261097583": {
|
||||
"ok": true
|
||||
},
|
||||
"833753630925": {
|
||||
"ok": true
|
||||
},
|
||||
"719391062058": {
|
||||
"ok": true
|
||||
},
|
||||
"857781294719": {
|
||||
"ok": true
|
||||
},
|
||||
"863384503032": {
|
||||
"ok": true
|
||||
},
|
||||
"774494945453": {
|
||||
"ok": true
|
||||
},
|
||||
"750271307455": {
|
||||
"ok": true
|
||||
},
|
||||
"726442972676": {
|
||||
"ok": true
|
||||
},
|
||||
"775057912118": {
|
||||
"ok": true
|
||||
},
|
||||
"893592264148": {
|
||||
"ok": true
|
||||
},
|
||||
"607330049419": {
|
||||
"ok": true
|
||||
},
|
||||
"854003999648": {
|
||||
"ok": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
// 1) 店铺选择控件长什么样
|
||||
const shop = await c.evalJS(`(()=>{
|
||||
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
if(!it) return 'no-item';
|
||||
const ctrls=[...it.querySelectorAll('.ant-select,.ant-checkbox-group,.ant-radio-group')].map(x=>x.tagName+'.'+String(x.className).slice(0,40));
|
||||
const txt=(it.innerText||'').replace(/\\s+/g,' ').trim().slice(0,120);
|
||||
return JSON.stringify({ctrls, txt, html:it.innerHTML.replace(/\\s+/g,' ').slice(0,500)});})()`);
|
||||
console.log('店铺:', S(String(shop)).slice(0,900));
|
||||
// 2) 点开它
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=it?it.querySelector('.ant-select-selector'):null; if(!s) return '[]'; s.scrollIntoView({block:'center'});
|
||||
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
console.log('坐标:', JSON.stringify(pos));
|
||||
if (pos.length){
|
||||
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(300);
|
||||
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(2500);
|
||||
}
|
||||
const opts = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!dd) return 'no-dd';
|
||||
const items=[...dd.querySelectorAll('.ant-select-item-option')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
|
||||
const multi=!!dd.querySelector('.ant-select-item-option-selected, .ant-checkbox');
|
||||
return JSON.stringify({n:items.length, items:items.slice(0,12), multi});})()`);
|
||||
console.log('店铺选项:', S(String(opts)).slice(0,800));
|
||||
try{ await c.shot('/tmp/joom_shop.jpg'); console.log('shot ok'); }catch(e){}
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,96 @@
|
||||
import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { fillRequiredSelects } from '/home/admin9webs/shein建品/sh_build.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
import { readFileSync } from 'fs';
|
||||
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||
const pid = process.argv[2] || '676236596821';
|
||||
const it = JSON.parse(readFileSync('/home/admin9webs/tiktok建品/tk_input.json','utf8'))[pid];
|
||||
const IMGS = (it.imgs||[]).slice(0,6);
|
||||
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
await installHook(c);
|
||||
// 店
|
||||
console.log('店:', String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return (w.innerText||'').trim();})()`)));
|
||||
await wait(6000);
|
||||
// 类目
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{const f=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=f.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||
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(1500);
|
||||
await c.send('Input.insertText',{text: it.cat_kw || '发夹'}).catch(()=>{});
|
||||
await wait(2500);
|
||||
console.log('类目:', String(await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!dd) return 'no-dd'; const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; if(!op) return 'no-op';
|
||||
const r=op.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||
return (op.innerText||'').trim().slice(0,30);})()`)));
|
||||
await wait(7000);
|
||||
// store 填
|
||||
const fill = await c.evalJS(`(()=>{const p=${P}; const imgs=${JSON.stringify(IMGS)};
|
||||
const mk=(u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800});
|
||||
const b=p._s.get('smtChoiceAddBasicStore').$state.formState;
|
||||
b.name=${JSON.stringify(it.title)}; b.parentSku='JM-'+${JSON.stringify(pid)}; b.description=(${JSON.stringify((it.bullets||[]).join('. '))}||b.name); b.tags=(${JSON.stringify(it.keywords||'')}.split(', ').slice(0,10));
|
||||
const pr=p._s.get('joomAddPriceStore').$state.formState;
|
||||
pr.price=String(${JSON.stringify(String(it.price_cny||'19.90'))}); pr.inventory='999'; pr.msrp=''; pr.shipping='0'; pr.declaredValue=String(${JSON.stringify(String(it.cost||'10'))});
|
||||
const im=p._s.get('joomAddImageStore').$state.formState;
|
||||
im.imageList=imgs.map(mk);
|
||||
if(Array.isArray(im.detailImageList)&&im.detailImageList[0]) im.detailImageList[0].images=imgs.map(mk);
|
||||
return JSON.stringify({name:b.name.slice(0,20), price:pr.price, imgs:im.imageList.length, cat:b.categoryId});})()`);
|
||||
console.log('填充:', S(String(fill)).slice(0,400));
|
||||
await wait(4000);
|
||||
// 列出所有带 select 的表单项,找「详情图语言」
|
||||
const pairs = await c.evalJS(`(()=>{ try {
|
||||
const out = [];
|
||||
for (const e of document.querySelectorAll('.ant-form-item')) {
|
||||
if (e.offsetParent === null) continue;
|
||||
const sel = e.querySelector('.ant-select');
|
||||
if (!sel) continue;
|
||||
const l = e.querySelector('label');
|
||||
out.push(((l ? l.innerText : '') || '?').replace(/\\s+/g, ' ').trim().slice(0, 26) + ' => ' + (sel.innerText || '').replace(/\\s+/g, ' ').trim().slice(0, 24));
|
||||
}
|
||||
return JSON.stringify(out.slice(0, 20));
|
||||
} catch (e) { return 'ERR ' + e.message; } })()`);
|
||||
console.log('带下拉的表项:', S(String(pairs)).slice(0, 1200));
|
||||
|
||||
// 点「详情图语言」那个 select,选第一项
|
||||
const lang = JSON.parse(String(await c.evalJS(`(()=>{ try {
|
||||
let target = null;
|
||||
for (const e of document.querySelectorAll('.ant-form-item')) {
|
||||
if (e.offsetParent === null) continue;
|
||||
const l = e.querySelector('label'); const t = l ? (l.innerText || '') : '';
|
||||
if (t.indexOf('详情图') >= 0 && e.querySelector('.ant-select')) { target = e; break; }
|
||||
}
|
||||
if (!target) return '[]';
|
||||
const s = target.querySelector('.ant-select-selector'); s.scrollIntoView({ block: 'center' });
|
||||
const r = s.getBoundingClientRect();
|
||||
return JSON.stringify([Math.round(r.x + r.width / 2), Math.round(r.y + r.height / 2)]);
|
||||
} catch (e) { return '[]'; } })()`)));
|
||||
console.log('语言控件坐标:', JSON.stringify(lang));
|
||||
if (lang.length) {
|
||||
await c.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: lang[0], y: lang[1], buttons: 0 }); await wait(300);
|
||||
for (const t of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type: t, x: lang[0], y: lang[1], button: 'left', clickCount: 1, buttons: 1 });
|
||||
await wait(2000);
|
||||
console.log('选了:', String(await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!dd) return 'no-dd'; const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; if(!op) return 'no-op';
|
||||
const r=op.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||
return (op.innerText||'').trim().slice(0,20);})()`)));
|
||||
await wait(3000);
|
||||
}
|
||||
// 保存
|
||||
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
|
||||
await c.evalJS(`(()=>{const xs=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null);
|
||||
const b=xs.find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()))||xs.find(e=>/保存/.test(e.innerText||''));
|
||||
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
|
||||
await wait(13000);
|
||||
console.log('hook:', String(await readHook(c)).slice(-260));
|
||||
const res = await c.evalJS(`JSON.stringify({mods:[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,120))})`);
|
||||
console.log('结果:', S(String(res)).slice(0,400));
|
||||
try { await c.shot('/tmp/joom_lang.jpg'); console.log('shot ok'); } catch (e) {}
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,54 @@
|
||||
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
// 全部店铺标签
|
||||
const labels = await c.evalJS(`(()=>{
|
||||
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const ls=[...it.querySelectorAll('.ant-checkbox-wrapper')].map(w=>(w.innerText||'').replace(/\\s+/g,' ').trim());
|
||||
return JSON.stringify({n:ls.length, first:ls.slice(0,10), last:ls.slice(-3)});})()`);
|
||||
console.log('店铺:', S(String(labels)).slice(0,600));
|
||||
// 勾第一个真实店铺(跳过「全部」)
|
||||
const tick = await c.evalJS(`(()=>{
|
||||
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const ws=[...it.querySelectorAll('.ant-checkbox-wrapper')];
|
||||
const target=ws.find(w=>{const t=(w.innerText||'').trim(); return t && !/^全部/.test(t);});
|
||||
if(!target) return 'nf';
|
||||
for(const ev of ['mousedown','mouseup','click']) target.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||
const inp=target.querySelector('input[type=checkbox]'); if(inp&&!inp.checked) inp.click();
|
||||
return 'ticked:'+(target.innerText||'').trim();})()`);
|
||||
console.log('勾选:', String(tick));
|
||||
await wait(6000);
|
||||
// 选类目
|
||||
const cat = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类|分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
if(!it) return 'no-item';
|
||||
const b=[...it.querySelectorAll('*')].find(e=>(e.innerText||'').trim()==='选择分类'&&e.children.length===0);
|
||||
if(!b) return 'no-btn';
|
||||
for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'opened';})()`);
|
||||
console.log('类目按钮:', String(cat));
|
||||
await wait(3500);
|
||||
const modal = await c.evalJS(`(()=>{
|
||||
const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!m) return 'no-modal';
|
||||
const inp=[...m.querySelectorAll('input')].map(e=>e.placeholder||'').slice(0,4);
|
||||
const txt=(m.innerText||'').replace(/\\s+/g,' ').trim().slice(0,220);
|
||||
return JSON.stringify({inp, txt});})()`);
|
||||
console.log('类目弹窗:', S(String(modal)).slice(0,700));
|
||||
// 搜一个关键词
|
||||
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
|
||||
const inp=m.querySelector('input[placeholder="搜索分类名称"]')||m.querySelector('input'); if(!inp) return 'no-input';
|
||||
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
|
||||
setter.call(inp,'发圈'); inp.dispatchEvent(new Event('input',{bubbles:true})); return 1;})()`);
|
||||
await wait(600);
|
||||
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).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(4000);
|
||||
const res = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!m) return 'closed';
|
||||
const items=[...m.querySelectorAll('.search-result-item')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,70));
|
||||
return JSON.stringify({n:items.length, items:items.slice(0,5)});})()`);
|
||||
console.log('类目结果:', S(String(res)).slice(0,600));
|
||||
try{ await c.shot('/tmp/joom_cat.jpg'); console.log('shot ok'); }catch(e){}
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,25 @@
|
||||
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return 1;})()`);
|
||||
await wait(6000);
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=it?it.querySelector('.ant-select-selector'):null; if(!s) return '[]'; s.scrollIntoView({block:'center'});
|
||||
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
console.log('分类select坐标:', JSON.stringify(pos));
|
||||
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(300);
|
||||
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(3000);
|
||||
const d = await c.evalJS(`(()=>{
|
||||
const dd=[...document.querySelectorAll('.ant-select-dropdown,.ant-cascader-menus,.ant-cascader-dropdown')].filter(e=>e.offsetParent!==null);
|
||||
const info=dd.map(e=>({cls:String(e.className).slice(0,60), txt:(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,300)}));
|
||||
const menus=[...document.querySelectorAll('.ant-cascader-menu')].length;
|
||||
const inputs=[...document.querySelectorAll('.ant-select-dropdown input,.ant-cascader-menus input')].filter(e=>e.offsetParent!==null).map(e=>e.placeholder||'');
|
||||
return JSON.stringify({n:info.length, info, menus, inputs});})()`);
|
||||
console.log('下拉:', S(String(d)).slice(0,1200));
|
||||
try{ await c.shot('/tmp/joom_cat3.jpg'); console.log('shot ok'); }catch(e){}
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,48 @@
|
||||
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
// 勾店
|
||||
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return 1;})()`);
|
||||
await wait(6000);
|
||||
// 点分类 select 并输入关键词
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=it?it.querySelector('.ant-select-selector'):null; if(!s) return '[]'; s.scrollIntoView({block:'center'});
|
||||
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||
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(1500);
|
||||
await c.send('Input.insertText',{text:'发圈'}).catch(()=>{});
|
||||
await wait(2500);
|
||||
const pick = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!dd) return 'no-dd';
|
||||
const ops=[...dd.querySelectorAll('.ant-select-item-option')].filter(e=>e.offsetParent!==null);
|
||||
if(!ops.length) return 'no-op';
|
||||
const t=ops[0]; const r=t.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) t.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||
return 'picked:'+(t.innerText||'').trim().slice(0,30)+' (共'+ops.length+'项)';})()`);
|
||||
console.log('类目:', String(pick));
|
||||
await wait(6000);
|
||||
// dump store 状态
|
||||
const st = await c.evalJS(`(()=>{const p=${P};
|
||||
const ids=(p._s instanceof Map?[...p._s.keys()]:Object.keys(p._s)).filter(x=>/joom/i.test(x));
|
||||
const out={};
|
||||
for(const id of ids){ try{ const s=(p._s.get?p._s.get(id):p._s[id]).$state; const f=s.formState||{};
|
||||
const keep={}; for(const k of Object.keys(f)){ const v=f[k]; if(v===null||v===undefined||v===''||(Array.isArray(v)&&!v.length)) continue;
|
||||
keep[k]=Array.isArray(v)?('['+v.length+'] '+(v[0]?JSON.stringify(v[0]).slice(0,80):'')):(typeof v==='object'?JSON.stringify(v).slice(0,80):String(v).slice(0,50));
|
||||
if(Object.keys(keep).length>12) break; }
|
||||
const extra=Object.keys(s).filter(k=>k!=='formState').slice(0,4);
|
||||
out[id]={...keep, _keys:extra}; }catch(e){ out[id]='ERR'; } }
|
||||
return JSON.stringify(out);})()`);
|
||||
console.log('stores:', S(String(st)).slice(0,1800));
|
||||
// 可见表单项(带必填标记)
|
||||
const items = await c.evalJS(`JSON.stringify([...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).slice(0,22).map(e=>{
|
||||
const l=e.querySelector('label'); const req=l?/required/.test(String(l.className)):'';
|
||||
return (req?'*':'')+((e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,46));}))`);
|
||||
console.log('表单项:', S(String(items)).slice(0,1400));
|
||||
try{ await c.shot('/tmp/joom_form.jpg'); console.log('shot ok'); }catch(e){}
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,43 @@
|
||||
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return 1;})()`);
|
||||
await wait(6000);
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||
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(1500);
|
||||
await c.send('Input.insertText',{text:'发夹'}).catch(()=>{});
|
||||
await wait(2500);
|
||||
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; const r=op.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2})); return 1;})()`);
|
||||
await wait(7000);
|
||||
// 1) joomAddStore 全量结构
|
||||
const st = await c.evalJS(`(()=>{const p=${P};
|
||||
const s=p._s.get('joomAddStore').$state;
|
||||
const keys=Object.keys(s);
|
||||
const pi=s.productInfo||{};
|
||||
const pick=(o,d=0)=>{ if(d>2||!o||typeof o!=='object') return o; const out={}; let n=0;
|
||||
for(const k of Object.keys(o)){ const v=o[k]; out[k]=(v&&typeof v==='object')?(Array.isArray(v)?'['+v.length+']':pick(v,d+1)):String(v).slice(0,40); if(++n>14) break; } return out; };
|
||||
return JSON.stringify({keys, productInfo:pick(pi), attrsInfo:pick(s.attrsInfo), sharedData:pick(s.sharedData)});})()`);
|
||||
console.log('joomAddStore:', S(String(st)).slice(0,1500));
|
||||
// 2) 价格/库存/SKU/图片 控件
|
||||
const ctrls = await c.evalJS(`(()=>{const out={};
|
||||
const bag=(re)=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>{const l=e.querySelector('label'); return l&&new RegExp(re).test(l.innerText||'');});
|
||||
if(!it) return 'no-item';
|
||||
return {txt:(it.innerText||'').replace(/\\s+/g,' ').trim().slice(0,60),
|
||||
inputs:[...it.querySelectorAll('input')].filter(i=>i.offsetParent!==null).map(i=>({ph:i.placeholder||'', name:i.name||'', cls:String(i.className).slice(0,34)})).slice(0,5),
|
||||
selects:[...it.querySelectorAll('.ant-select')].length,};};
|
||||
out['价格']=bag('^\\*?价格'); out['库存']=bag('库存'); out['ParentSKU']=bag('ParentSKU'); out['产品图']=bag('产品图'); out['敏感货类型']=bag('敏感货类型');
|
||||
const sku=p=>0;
|
||||
return JSON.stringify(out);})()`);
|
||||
console.log('控件:', S(String(ctrls)).slice(0,1600));
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,45 @@
|
||||
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return 1;})()`);
|
||||
await wait(6000);
|
||||
// 类目
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||
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(1500);
|
||||
await c.send('Input.insertText',{text:'发夹'}).catch(()=>{});
|
||||
await wait(2500);
|
||||
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; const r=op.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2})); return 1;})()`);
|
||||
await wait(6000);
|
||||
// 在标题输入框里打字(走真实键盘)
|
||||
const tpos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/^\\s*\\*?产品标题/.test((e.innerText||'').trim()));
|
||||
const inp=it?it.querySelector('input,textarea'):null; if(!inp) return '[]'; inp.scrollIntoView({block:'center'});
|
||||
inp.setAttribute('data-probe','1'); const r=inp.getBoundingClientRect();
|
||||
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
console.log('标题框坐标:', JSON.stringify(tpos));
|
||||
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:tpos[0],y:tpos[1],button:'left',clickCount:1,buttons:1});
|
||||
await wait(400);
|
||||
await c.send('Input.insertText',{text:'PROBE-TITLE-XYZ'}).catch(()=>{});
|
||||
await wait(2500);
|
||||
// 全 store 搜索这个字符串
|
||||
const found = await c.evalJS(`(()=>{const p=${P}; const needle='PROBE-TITLE-XYZ'; const hits=[];
|
||||
const ids=(p._s instanceof Map?[...p._s.keys()]:Object.keys(p._s));
|
||||
for(const id of ids){ try{ const s=(p._s.get?p._s.get(id):p._s[id]).$state;
|
||||
const js=JSON.stringify(s); if(js&&js.includes(needle)) hits.push(id+':'+js.slice(Math.max(0,js.indexOf(needle)-70), js.indexOf(needle)+40).replace(/\\\\/g,'')); }catch(e){} }
|
||||
const domVal=(document.querySelector('input[data-probe="1"]')||{}).value||'';
|
||||
// joomAddStore 的非 $state 属性
|
||||
let extra=[];
|
||||
try{ const st=p._s.get('joomAddStore'); extra=Object.keys(st).filter(k=>k!=='$state'&&k!=='_s'&&!/^\\$/.test(k)).slice(0,18); }catch(e){}
|
||||
return JSON.stringify({hits, domVal, joomAddStoreOwnKeys:extra});})()`);
|
||||
console.log('命中:', S(String(found)).slice(0,1600));
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,32 @@
|
||||
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return 1;})()`);
|
||||
await wait(6000);
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||
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(1500);
|
||||
await c.send('Input.insertText',{text:'发夹'}).catch(()=>{});
|
||||
await wait(2500);
|
||||
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; const r=op.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2})); return 1;})()`);
|
||||
await wait(7000);
|
||||
const d = await c.evalJS(`(()=>{const p=${P};
|
||||
const ids=(p._s instanceof Map?[...p._s.keys()]:Object.keys(p._s)).filter(x=>/smtChoice|joom|image|Image/i.test(x));
|
||||
const out={};
|
||||
const fmt=(v,d=0)=>{ if(v===null||v===undefined) return String(v); if(Array.isArray(v)) return '['+v.length+'] '+(v[0]?fmt(v[0],d+1):'');
|
||||
if(typeof v==='object'){ if(d>1) return '{...}'; const o={}; let n=0; for(const k of Object.keys(v)){ o[k]=fmt(v[k],d+1); if(++n>16){o['…']='';break;} } return o; } return String(v).slice(0,46); };
|
||||
for(const id of ids){ try{ const s=(p._s.get?p._s.get(id):p._s[id]).$state; const f=s.formState;
|
||||
out[id]= f ? fmt(f) : '(no formState) keys='+Object.keys(s).slice(0,10).join(','); }catch(e){ out[id]='ERR'; } }
|
||||
return JSON.stringify(out);})()`);
|
||||
console.log('stores:', JSON.stringify(JSON.parse(String(d)), null, 1).slice(0,3000));
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,69 @@
|
||||
import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { fillRequiredSelects } from '/home/admin9webs/shein建品/sh_build.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
import { readFileSync } from 'fs';
|
||||
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||
const pid = process.argv[2] || '676236596821';
|
||||
const it = JSON.parse(readFileSync('/home/admin9webs/tiktok建品/tk_input.json','utf8'))[pid];
|
||||
const IMGS = (it.imgs||[]).slice(0,6);
|
||||
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
await installHook(c);
|
||||
// 店
|
||||
console.log('店:', String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return (w.innerText||'').trim();})()`)));
|
||||
await wait(6000);
|
||||
// 类目
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{const f=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=f.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||
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(1500);
|
||||
await c.send('Input.insertText',{text: it.cat_kw || '发夹'}).catch(()=>{});
|
||||
await wait(2500);
|
||||
console.log('类目:', String(await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!dd) return 'no-dd'; const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; if(!op) return 'no-op';
|
||||
const r=op.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||
return (op.innerText||'').trim().slice(0,30);})()`)));
|
||||
await wait(7000);
|
||||
// store 填
|
||||
const fill = await c.evalJS(`(()=>{const p=${P}; const imgs=${JSON.stringify(IMGS)};
|
||||
const mk=(u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800});
|
||||
const b=p._s.get('smtChoiceAddBasicStore').$state.formState;
|
||||
b.name=${JSON.stringify(it.title)}; b.parentSku='JM-'+${JSON.stringify(pid)}; b.description=(${JSON.stringify((it.bullets||[]).join('. '))}||b.name); b.tags=(${JSON.stringify(it.keywords||'')}.split(', ').slice(0,10));
|
||||
const pr=p._s.get('joomAddPriceStore').$state.formState;
|
||||
pr.price=String(${JSON.stringify(String(it.price_cny||'19.90'))}); pr.inventory='999'; pr.msrp=''; pr.shipping='0'; pr.declaredValue=String(${JSON.stringify(String(it.cost||'10'))});
|
||||
const im=p._s.get('joomAddImageStore').$state.formState;
|
||||
im.imageList=imgs.map(mk);
|
||||
if(Array.isArray(im.detailImageList)&&im.detailImageList[0]) im.detailImageList[0].images=imgs.map(mk);
|
||||
return JSON.stringify({name:b.name.slice(0,20), price:pr.price, imgs:im.imageList.length, cat:b.categoryId});})()`);
|
||||
console.log('填充:', S(String(fill)).slice(0,400));
|
||||
await wait(4000);
|
||||
const attrs = await fillRequiredSelects(c);
|
||||
console.log('必填属性:', S(String(attrs)).slice(0,300));
|
||||
await wait(2000);
|
||||
// dump(简化版,避免页面里复杂表达式报错)
|
||||
const d = await c.evalJS(`(()=>{ try {
|
||||
const p = document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
|
||||
const im = p._s.get('joomAddImageStore').$state;
|
||||
const labels = [];
|
||||
const all = document.querySelectorAll('.ant-form-item');
|
||||
for (const e of all) {
|
||||
if (e.offsetParent === null) continue;
|
||||
const l = e.querySelector('label');
|
||||
const t = l ? (l.innerText || '') : '';
|
||||
if (t.indexOf('图') >= 0 || t.indexOf('语言') >= 0) {
|
||||
labels.push(t.replace(/\\s+/g, ' ').trim().slice(0, 30) + ' | selects=' + e.querySelectorAll('.ant-select').length + ' imgs=' + e.querySelectorAll('img').length);
|
||||
}
|
||||
}
|
||||
return JSON.stringify({ keys: Object.keys(im), imageList: JSON.stringify(im.imageList).slice(0, 160),
|
||||
detail: JSON.stringify(im.detailImageList).slice(0, 260), labels: labels.slice(0, 12) });
|
||||
} catch (err) { return 'INNER-ERR ' + err.message; } })()`);
|
||||
console.log('图片区:', S(String(d)).slice(0, 1400));
|
||||
try { await c.shot('/tmp/joom_img.jpg'); console.log('shot ok'); } catch (e) {}
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,64 @@
|
||||
import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { fillRequiredSelects } from '/home/admin9webs/shein建品/sh_build.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
import { readFileSync } from 'fs';
|
||||
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||
const pid = process.argv[2] || '676236596821';
|
||||
const it = JSON.parse(readFileSync('/home/admin9webs/tiktok建品/tk_input.json','utf8'))[pid];
|
||||
const IMGS = (it.imgs||[]).slice(0,6);
|
||||
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
await installHook(c);
|
||||
// 店
|
||||
console.log('店:', String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return (w.innerText||'').trim();})()`)));
|
||||
await wait(6000);
|
||||
// 类目
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{const f=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=f.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||
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(1500);
|
||||
await c.send('Input.insertText',{text: it.cat_kw || '发夹'}).catch(()=>{});
|
||||
await wait(2500);
|
||||
console.log('类目:', String(await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!dd) return 'no-dd'; const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; if(!op) return 'no-op';
|
||||
const r=op.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||
return (op.innerText||'').trim().slice(0,30);})()`)));
|
||||
await wait(7000);
|
||||
// store 填
|
||||
const fill = await c.evalJS(`(()=>{const p=${P}; const imgs=${JSON.stringify(IMGS)};
|
||||
const mk=(u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800});
|
||||
const b=p._s.get('smtChoiceAddBasicStore').$state.formState;
|
||||
b.name=${JSON.stringify(it.title)}; b.parentSku='JM-'+${JSON.stringify(pid)}; b.description=(${JSON.stringify((it.bullets||[]).join('. '))}||b.name); b.tags=(${JSON.stringify(it.keywords||'')}.split(', ').slice(0,10));
|
||||
const pr=p._s.get('joomAddPriceStore').$state.formState;
|
||||
pr.price=String(${JSON.stringify(String(it.price_cny||'19.90'))}); pr.inventory='999'; pr.msrp=''; pr.shipping='0'; pr.declaredValue=String(${JSON.stringify(String(it.cost||'10'))});
|
||||
const im=p._s.get('joomAddImageStore').$state.formState;
|
||||
im.imageList=imgs.map(mk);
|
||||
if(Array.isArray(im.detailImageList)&&im.detailImageList[0]) im.detailImageList[0].images=imgs.map(mk);
|
||||
return JSON.stringify({name:b.name.slice(0,20), price:pr.price, imgs:im.imageList.length, cat:b.categoryId});})()`);
|
||||
console.log('填充:', S(String(fill)).slice(0,400));
|
||||
await wait(4000);
|
||||
const dumpImgs = async (tag) => {
|
||||
const r = await c.evalJS(`(()=>{ try {
|
||||
const p = document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
|
||||
const st = p._s.get('joomAddImageStore').$state;
|
||||
const f = st.formState || {};
|
||||
return JSON.stringify({ keys: Object.keys(f), imageList: Array.isArray(f.imageList) ? f.imageList.length : 'undef',
|
||||
detail: Array.isArray(f.detailImageList) && f.detailImageList[0] && Array.isArray(f.detailImageList[0].images) ? f.detailImageList[0].images.length : 'undef',
|
||||
domImgs: document.querySelectorAll('img').length });
|
||||
} catch (e) { return 'ERR ' + e.message; } })()`);
|
||||
console.log(tag, S(String(r)).slice(0, 300));
|
||||
};
|
||||
await wait(800); await dumpImgs('填完立刻:');
|
||||
await wait(6000); await dumpImgs('6 秒后:');
|
||||
const attrs = await fillRequiredSelects(c);
|
||||
console.log('必填属性:', S(String(attrs)).slice(0, 200));
|
||||
await wait(2500); await dumpImgs('填必填属性后:');
|
||||
try { await c.shot('/tmp/joom_img2.jpg'); console.log('shot ok'); } catch (e) {}
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,13 @@
|
||||
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/offline');
|
||||
await wait(13000);
|
||||
const o = await c.evalJS(`(()=>{
|
||||
const body=(document.body.innerText||'').replace(/\s+/g,' ');
|
||||
const m=body.match(/共\s*\d+\s*条/);
|
||||
const rows=[...document.querySelectorAll('.vxe-body--row')].slice(0,3).map(r=>(r.innerText||'').replace(/\s+/g,' ').trim().slice(0,90));
|
||||
const n=document.querySelectorAll('.vxe-body--row').length;
|
||||
return JSON.stringify({total:m?m[0]:'', n, rows});})()`);
|
||||
console.log(S(String(o)).slice(0,700));
|
||||
try{ await c.shot('/tmp/joom_offline.jpg'); console.log('shot ok'); }catch(e){}
|
||||
await closePage(c); process.exit(0);
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Joom 批量建品(84 家店取第一家;与 aliChoice 共用 smtChoice* store)
|
||||
*
|
||||
* 用法: node jmbatch.mjs [--dry] [pid...]
|
||||
*
|
||||
* 单条顺序:勾店铺(复选框) → 选类目(可搜索 ant-select) → 标题/ParentSKU/描述/标签
|
||||
* → 价格/库存/运费/报关金额 → 图片(主图+详情图) → 敏感货类型 → 详情图语言 → 保存
|
||||
*
|
||||
* 被钩子抓出来的两条「没标必填但必须填」:运费 shipping、详情图语言(标签就叫「详情图」)
|
||||
*/
|
||||
import { readFileSync, writeFileSync, appendFileSync } from 'fs';
|
||||
import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||
import { fillRequiredSelects } from '/home/admin9webs/shein建品/sh_build.mjs';
|
||||
|
||||
const DIR = '/home/admin9webs/joom建品';
|
||||
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||
const INPUT = JSON.parse(readFileSync('/home/admin9webs/tiktok建品/tk_input.json', 'utf8'));
|
||||
const DONE_F = `${DIR}/jm_done.json`;
|
||||
const LOG_F = `${DIR}/jm_batch.log`;
|
||||
const SKIP = new Set([
|
||||
'676236596821', // 首条手工测试已进待发布(详情图语言选了阿拉伯语),不再重复建
|
||||
'601964534137', // 遥控器保护套:18 张图全带水印、无干净图 → 所有平台统一跳过
|
||||
]);
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
const dry = argv.includes('--dry');
|
||||
const only = argv.filter(a => !a.startsWith('--'));
|
||||
let tasks = Object.keys(INPUT)
|
||||
.filter(pid => (INPUT[pid].imgs || []).length > 0) // 没有干净图的直接不参与(全平台统一规则)
|
||||
.map(pid => ({ pid }));
|
||||
if (only.length) tasks = tasks.filter(t => only.includes(t.pid)); else tasks = tasks.filter(t => !SKIP.has(t.pid));
|
||||
let done = {}; try { done = JSON.parse(readFileSync(DONE_F, 'utf8')); } catch {}
|
||||
let ROUTE = {}; try { ROUTE = JSON.parse(readFileSync(`${DIR}/joom_route.json`, 'utf8')); } catch {}
|
||||
const log = m => { const l = `[${new Date().toISOString().slice(11, 19)}] ${m}`; console.log(l); appendFileSync(LOG_F, l + '\n'); };
|
||||
|
||||
/** 勾第一家真实店铺(Joom 店名没有圈号,取 DOM 第一个非「全部」,即 Dummer-Pets) */
|
||||
async function tickShop(c, want = null) {
|
||||
return c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
|
||||
.find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||
if(!it) return 'no-item';
|
||||
const ws=[...it.querySelectorAll('.ant-checkbox-wrapper')];
|
||||
const want=${JSON.stringify(want)};
|
||||
const w=want ? ws.find(x=>(x.innerText||'').trim()===want) : ws.find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||
if(!w) return 'no-shop';
|
||||
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||
const i=w.querySelector('input'); if(i&&!i.checked) i.click();
|
||||
return 'ticked:'+(w.innerText||'').trim();})()`);
|
||||
}
|
||||
|
||||
/** 类目:点开可搜索下拉 → 输关键词 → 选名字匹配的项(优先去掉括注后完全相等) */
|
||||
async function pickCategory(c, kw) {
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
|
||||
.find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=it?it.querySelector('.ant-select-selector'):null; if(!s) return '[]';
|
||||
s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||
if (!pos.length) return 'no-select';
|
||||
await c.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: pos[0], y: pos[1], buttons: 0 }); await wait(250);
|
||||
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(1500);
|
||||
await c.send('Input.insertText', { text: kw }).catch(() => {});
|
||||
await wait(2600);
|
||||
return c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!dd) return 'no-dd';
|
||||
const ops=[...dd.querySelectorAll('.ant-select-item-option')].filter(e=>e.offsetParent!==null);
|
||||
if(!ops.length) return 'no-option';
|
||||
const clean=s=>(s||'').replace(/\\s+/g,'').split('(')[0];
|
||||
const op=ops.find(e=>clean(e.innerText)===clean(${JSON.stringify(kw)}))||ops[0];
|
||||
const r=op.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||
return 'picked:'+(op.innerText||'').trim().slice(0,26)+'/'+ops.length;})()`);
|
||||
}
|
||||
|
||||
/** 详情图语言:标签就是「详情图」,优先英语 */
|
||||
async function pickDetailLang(c) {
|
||||
const pos = JSON.parse(String(await c.evalJS(`(()=>{
|
||||
for(const e of document.querySelectorAll('.ant-form-item')){
|
||||
if(e.offsetParent===null) continue;
|
||||
const l=e.querySelector('label'); const t=l?(l.innerText||''):'';
|
||||
if(t.indexOf('详情图')>=0&&e.querySelector('.ant-select')){
|
||||
const s=e.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'});
|
||||
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]); } }
|
||||
return '[]';})()`)));
|
||||
if (!pos.length) return 'no-lang';
|
||||
await c.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: pos[0], y: pos[1], buttons: 0 }); await wait(250);
|
||||
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(2000);
|
||||
// 列表是虚拟滚动的,前 10 项里没有「英语」→ 先试着输「英」过滤,不行就滚到底
|
||||
await c.send('Input.insertText', { text: '英' }).catch(() => {});
|
||||
await wait(1500);
|
||||
let hit = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!dd) return 'no-dd';
|
||||
const ops=[...dd.querySelectorAll('.ant-select-item-option')].filter(e=>e.offsetParent!==null);
|
||||
const names=ops.map(e=>(e.innerText||'').trim()).filter(Boolean);
|
||||
const op=ops.find(e=>/英|English/i.test(e.innerText||''));
|
||||
if(!op) return 'none|'+names.slice(0,6).join('/');
|
||||
const r=op.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||
return 'picked:'+(op.innerText||'').trim();})()`);
|
||||
if (String(hit).startsWith('none')) {
|
||||
hit = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||
if(!dd) return 'no-dd';
|
||||
const sc=dd.querySelector('.rc-virtual-list-holder')||dd; sc.scrollTop=sc.scrollHeight;
|
||||
const ops=[...dd.querySelectorAll('.ant-select-item-option')].filter(e=>e.offsetParent!==null);
|
||||
const op=ops.find(e=>/英|English/i.test(e.innerText||''));
|
||||
if(!op) return 'still-none|'+ops.map(e=>(e.innerText||'').trim()).slice(-6).join('/');
|
||||
const r=op.getBoundingClientRect();
|
||||
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||
return 'picked(滚动):'+(op.innerText||'').trim();})()`);
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
log(`Joom 批量开始:任务 ${tasks.length},dry=${dry}`);
|
||||
for (const t of tasks) {
|
||||
if (!dry && done[t.pid] && done[t.pid].ok) { log(`跳过 ${t.pid}(已建)`); continue; }
|
||||
const it = INPUT[t.pid]; if (!it) { log(`✗ ${t.pid} 无输入`); continue; }
|
||||
log(`── ${t.pid} kw=${it.cat_kw || '?'} :: ${String(it.title).slice(0, 40)}`);
|
||||
let c;
|
||||
try {
|
||||
c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||
await wait(12000);
|
||||
await installHook(c);
|
||||
const shop = await tickShop(c, process.env.JOOM_SHOP || null);
|
||||
if (!String(shop).startsWith('ticked:')) throw new Error('选店铺失败:' + shop);
|
||||
await wait(6000);
|
||||
const cat = await pickCategory(c, it.cat_kw || '发夹');
|
||||
log(` 店=${shop.slice(7)} 类目=${cat}`);
|
||||
if (!String(cat).startsWith('picked:')) throw new Error('选类目失败:' + cat);
|
||||
// 记录实际落到的类目名(Joom 类目比 1688 粗,kw 常常只能近似匹配,事后要人工抽查)
|
||||
const catName = String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
|
||||
.find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||
const s=it?it.querySelector('.ant-select'):null; return s?(s.innerText||'').replace(/\\s+/g,' ').trim().slice(0,40):'';})()`));
|
||||
ROUTE[t.pid] = { shop: shop.slice(7), kw: it.cat_kw || '', cat: catName };
|
||||
writeFileSync(`${DIR}/joom_route.json`, JSON.stringify(ROUTE, null, 1));
|
||||
await wait(7000);
|
||||
const fill = await c.evalJS(`(()=>{const p=${P}; const imgs=${JSON.stringify((it.imgs || []).slice(0, 6))};
|
||||
const mk=(u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800});
|
||||
const b=p._s.get('smtChoiceAddBasicStore').$state.formState;
|
||||
b.name=${JSON.stringify(it.title)}; b.parentSku='JM-'+${JSON.stringify(t.pid)};
|
||||
b.description=(${JSON.stringify((it.bullets || []).join('. '))}||b.name);
|
||||
b.tags=String(${JSON.stringify(it.keywords || '')}).split(', ').slice(0,10);
|
||||
const pr=p._s.get('joomAddPriceStore').$state.formState;
|
||||
pr.price=String(${JSON.stringify(String(it.price_cny || '19.90'))}); pr.inventory='999';
|
||||
pr.shipping='0'; pr.msrp=''; pr.declaredValue=String(${JSON.stringify(String(it.cost || '10'))});
|
||||
const im=p._s.get('joomAddImageStore').$state.formState;
|
||||
im.imageList=imgs.map(mk);
|
||||
if(Array.isArray(im.detailImageList)&&im.detailImageList[0]) im.detailImageList[0].images=imgs.map(mk);
|
||||
return JSON.stringify({name:b.name.slice(0,18), price:pr.price, imgs:im.imageList.length, cat:b.categoryId});})()`);
|
||||
log(` 填充: ${fill}`);
|
||||
await wait(3500);
|
||||
const attrs = await fillRequiredSelects(c);
|
||||
if (attrs !== 'none') log(` 必填属性: ${attrs}`);
|
||||
await wait(1500);
|
||||
log(` 详情图语言: ${await pickDetailLang(c)}`);
|
||||
await wait(2500);
|
||||
if (dry) { delete done[t.pid]; log(' ✓ dry 构建完成(未保存)'); await closePage(c); continue; }
|
||||
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
|
||||
await c.evalJS(`(()=>{const xs=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null);
|
||||
const b=xs.find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()))||xs.find(e=>/保存/.test(e.innerText||''));
|
||||
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
|
||||
await wait(13000);
|
||||
const mods = String(await c.evalJS(`JSON.stringify([...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,110)))`));
|
||||
const cap = await readHook(c);
|
||||
const ok = /已保存|保存成功|待发布/.test(mods);
|
||||
done[t.pid] = ok ? { ok: true } : { ok: false, mods: mods.slice(0, 160), hook: String(cap).slice(-220) };
|
||||
log(ok ? ' ✓ 已保存到待发布' : ` ✗ 失败 ${String(cap).slice(-180)}`);
|
||||
} catch (e) {
|
||||
done[t.pid] = { ok: false, msg: String(e).slice(0, 240) };
|
||||
log(` ✗ 异常: ${String(e).slice(0, 220)}`);
|
||||
} finally {
|
||||
if (!dry) writeFileSync(DONE_F, JSON.stringify(done, null, 1));
|
||||
if (c) await closePage(c);
|
||||
await wait(2500);
|
||||
}
|
||||
}
|
||||
log(`Joom 批量结束:成功 ${Object.values(done).filter(x => x.ok).length}/${tasks.length}`);
|
||||
@@ -0,0 +1,242 @@
|
||||
{
|
||||
"676236596821": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发夹",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"548459586741": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "口哨",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"661190598139": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发圈",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"764423265986": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "温度计",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"685456757807": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "毛线",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"735147938123": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发箍",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"627879474686": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "袜子",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"610164857063": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发带",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"708420302844": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "钥匙扣",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"866537106657": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "纽扣",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"547183997437": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "项链",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"657617635303": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "收纳盒",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"641673262387": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "首饰袋",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"953869908708": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "窗贴",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"703822717997": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "笔夹",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"863524134410": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "贴纸",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"694637537727": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "保温袋",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"623011262210": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "窗贴",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"836512561982": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "化妆包",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"714492799386": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "化妆包",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"591994902720": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "笔袋",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"853414558425": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "手机挂绳",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"997450831819": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "贴纸",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"639409307567": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "墙贴",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"902440558099": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发圈",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"823838532886": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "卡套",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"698782391248": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "贴纸",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"707826905566": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "卡包",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"1027332215847": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "收纳盒",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"682195499823": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "笔袋",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"823560564174": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发圈",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"739147684759": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "纽扣",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"621300323670": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "胶带座",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"601964534137": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "遥控器保护套",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"896469995553": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "钥匙扣",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"944470567338": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发夹",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"787261097583": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发夹",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"833753630925": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发箍",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"719391062058": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "收纳盒",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"857781294719": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发夹",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"863384503032": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发夹",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"774494945453": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "钥匙扣",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"750271307455": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "钥匙扣",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"726442972676": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发夹",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"775057912118": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "钥匙扣",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"893592264148": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发夹",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"607330049419": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发圈",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
},
|
||||
"854003999648": {
|
||||
"shop": "Dummer-Pets",
|
||||
"kw": "发圈",
|
||||
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user