diff --git a/NEXT-ROUND-PROMPT.md b/NEXT-ROUND-PROMPT.md index 380c155..db3490e 100644 --- a/NEXT-ROUND-PROMPT.md +++ b/NEXT-ROUND-PROMPT.md @@ -59,6 +59,9 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){window.__cap.push(l+': '+[...a - **SHEIN 40/40**(`shein建品/shbatch.mjs`,按 `shein_route.json` 分 4 家店;1 条因 ①Shein-武汉九个网-箱包 报「商家无可用的发品额度」改挂 ②Shein-易世通达-箱包) - **Temu 全托管 47**(`temu建品/tmbatch.mjs` 批量 46/46 + 手工首条),逐条结果在 `tm_done.json` +- **Joom 47**(`joom建品/jmbatch.mjs` 批量 46/47 + 手工首条;唯一没建的是全平台统一跳过的 + 无水印图商品 `601964534137`),逐条结果 `jm_done.json`、实际类目 `joom_route.json`; + 关键发现是 **Joom 的商品数据落在 `smtChoiceAddBasicStore`**(与 aliChoice 共用 `smtChoice*` store) ## 5. 这轮要做的(按优先级) @@ -67,9 +70,16 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){window.__cap.push(l+': '+[...a (pc 3447~3551,游标 `ean_cursor.json` → **3552**)。KVM-2 够不到 S03, **需要在 S03 上把这份台账并进 `/root/ean_*.txt`**,之后从 3552 续号。 -1. **继续铺剩余店小秘平台**:Joom(84 家)、Lazada(24 家)、aliChoice(64 家)、 - pddkj(15 家)、fruugo(3 家)。店铺用**字母序/圈号自然序第一家** - (除 SHEIN 那种锁品类的)。这 5 个平台的字段落点和第一家店规则都还没勘,属于新探路。 +1. **继续铺剩余店小秘平台**(已侦察,直接用): + + | 平台 | add 路由 | 店铺控件 | 类目控件 | store 家族 | + |---|---|---|---|---| + | Joom | `/web/joomProduct/add` | **复选框组** 85 个(全部+84 店) | 可搜索 `.ant-select` 平铺列表 | `smtChoice*` + `joom*`(已跑通) | + | aliChoice | `/web/smtChoice/add` | 下拉 | 弹窗 + 自动识别分类 | `smtChoice*`(和 Joom 同一套,**可望直接复用**) | + | Lazada | `/web/lazada/add` | 下拉 | 弹窗 | 选完店铺才注册 `lazada*` store(懒加载) | + | fruugo | `/web/fruugoProduct/add` | 下拉 | 弹窗 | `fruugoAdd*`(Common/Basic/Dxm/Product/Variation/Image/Describe/Form) | + + (pddkj = Temu 全托管,已完成。)店铺一律取**自然序第一家**,SHEIN 那种锁品类的除外。 2. **妙手各平台**(店小秘全部完成后) diff --git a/README.md b/README.md index 96f5c9f..7737287 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ | Ozon | Yejet | **47** | 已建;改价 40/47 | | SHEIN | 4 家分品类 | **40** | 完成(`shbatch.mjs` 自动批量;1 条因 ①箱包店发品额度用尽改挂 ②店) | | Temu 全托管 | ①Alimingpro | **47** | 完成(`tmbatch.mjs` 批量 46 + 手工首条;预览图已解) | +| Joom | Dummer-Pets(84 家里的第一家) | **47** | 完成(`jmbatch.mjs` 批量 46 + 手工首条;与 aliChoice 共用 `smtChoice*` store) | > `601964534137`(遥控器保护套)18 张图全带水印、无干净图,所有平台统一跳过。 @@ -58,6 +59,7 @@ function wrap(o, n, l) { | 速卖通 | `速卖通建品/` | `check_box` 属性值**必须是数组**;`idStr ≠ id` | | SHEIN | `shein建品/` | 店铺**锁品类**;**一个产品最多 3 个规格**(加满就停);**加规格会重建 SKU 行**;类目动态必填属性(`.attr-label.required`)不填只报「产品信息中有错误」 | | Temu | `temu建品/` | 类目 1038 个从 store 直读;规格是 `.theme-value-add`(**必须手动输值再点「添加」**,与 SHEIN 不同构);主图**必须满 5 张**;**预览图逐行点单元格** → 引用产品轮播图 → 全部 → 选择 | +| Joom | `joom建品/` | 店铺是**复选框组**(85 个 = 全部+84 店,别碰「全部」);类目是**可搜索下拉平铺列表**;商品数据落在 **`smtChoiceAddBasicStore`**(与 aliChoice 同构,不在 `joom*` 里) | ## 通用技术事实 @@ -74,7 +76,7 @@ function wrap(o, n, l) { 批量脚本请用 `shein建品/sh_lib.mjs` 的 `newPage(url)`:按 target 直连,不会抢错 - VNC:`http://192.168.9.122:6097/vnc.html` 或 `:6100`(免密) -## 十五条会重复踩的坑 +## 十八条会重复踩的坑 1. **保存静默拦截** → 挂钩子,别猜 2. **`idStr ≠ id`** → 大整数精度丢失,服务端操作一律用 `idStr` @@ -94,6 +96,13 @@ function wrap(o, n, l) { 14. **类目动态必填属性**(`.attr-label.required`,如下拉、勾选组)不填不给具体提示, 只报「产品信息中有错误,请检查」→ 兜底:空的下拉选第一项、勾选组勾第一个 15. **EAN 游标必须落盘** → 计数器只存内存,换进程重跑就会从起点重发,必撞号 + +16. **没标 `*` 的字段也可能是必填** → Joom 的「运费」和「详情图语言」都没星号, + 不填只给 `MYJ.error: 运费不能为空` / `请选择详情图语言且至少添加一张详情图` +17. **虚拟滚动的下拉,前几项里可能没有你要的值** → 先 `Input.insertText('英')` 过滤再点, + 否则只能拿到列表第一项(Joom 详情图语言会落到「阿拉伯语」) +18. **找不到字段落点?打一个哨兵串** → 在页面手输 `PROBE-TITLE-XYZ`,再遍历所有 pinia store + 的 `$state` 找这个串,立刻知道数据落在哪个 store(Joom 就是这么发现数据在 `smtChoice*` 的) (Temu 第一轮就撞了一次,靠编辑页改号救回来) ## EAN-13 台账(湖北易千亿彩 · 69803093 段) diff --git a/joom建品/RECIPE.md b/joom建品/RECIPE.md new file mode 100644 index 0000000..de01fbe --- /dev/null +++ b/joom建品/RECIPE.md @@ -0,0 +1,47 @@ +# Joom 建品配方(已跑通,批量进行中) + +路由:`https://www.dianxiaomi.com/web/joomProduct/add` 列表:`/web/joomProduct/{draft=采集箱, offline=待发布}` + +脚本:`jmbatch.mjs`(`node jmbatch.mjs` 全量 · `node jmbatch.mjs ` 单条 · `--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`) diff --git a/joom建品/jm_done.json b/joom建品/jm_done.json new file mode 100644 index 0000000..92003da --- /dev/null +++ b/joom建品/jm_done.json @@ -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 + } +} \ No newline at end of file diff --git a/joom建品/jm_probe1.mjs b/joom建品/jm_probe1.mjs new file mode 100644 index 0000000..d91d447 --- /dev/null +++ b/joom建品/jm_probe1.mjs @@ -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); diff --git a/joom建品/jm_probe10.mjs b/joom建品/jm_probe10.mjs new file mode 100644 index 0000000..74998eb --- /dev/null +++ b/joom建品/jm_probe10.mjs @@ -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); diff --git a/joom建品/jm_probe2.mjs b/joom建品/jm_probe2.mjs new file mode 100644 index 0000000..e7a2b05 --- /dev/null +++ b/joom建品/jm_probe2.mjs @@ -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); diff --git a/joom建品/jm_probe3.mjs b/joom建品/jm_probe3.mjs new file mode 100644 index 0000000..b1ebc30 --- /dev/null +++ b/joom建品/jm_probe3.mjs @@ -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); diff --git a/joom建品/jm_probe4.mjs b/joom建品/jm_probe4.mjs new file mode 100644 index 0000000..5fd714e --- /dev/null +++ b/joom建品/jm_probe4.mjs @@ -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); diff --git a/joom建品/jm_probe5.mjs b/joom建品/jm_probe5.mjs new file mode 100644 index 0000000..f534475 --- /dev/null +++ b/joom建品/jm_probe5.mjs @@ -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); diff --git a/joom建品/jm_probe6.mjs b/joom建品/jm_probe6.mjs new file mode 100644 index 0000000..be5843f --- /dev/null +++ b/joom建品/jm_probe6.mjs @@ -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); diff --git a/joom建品/jm_probe7.mjs b/joom建品/jm_probe7.mjs new file mode 100644 index 0000000..9447a32 --- /dev/null +++ b/joom建品/jm_probe7.mjs @@ -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); diff --git a/joom建品/jm_probe8.mjs b/joom建品/jm_probe8.mjs new file mode 100644 index 0000000..d2d8e9e --- /dev/null +++ b/joom建品/jm_probe8.mjs @@ -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); diff --git a/joom建品/jm_probe9.mjs b/joom建品/jm_probe9.mjs new file mode 100644 index 0000000..2f54901 --- /dev/null +++ b/joom建品/jm_probe9.mjs @@ -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); diff --git a/joom建品/jm_verify.mjs b/joom建品/jm_verify.mjs new file mode 100644 index 0000000..8c84ad5 --- /dev/null +++ b/joom建品/jm_verify.mjs @@ -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); diff --git a/joom建品/jmbatch.mjs b/joom建品/jmbatch.mjs new file mode 100644 index 0000000..19b4696 --- /dev/null +++ b/joom建品/jmbatch.mjs @@ -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}`); diff --git a/joom建品/joom_route.json b/joom建品/joom_route.json new file mode 100644 index 0000000..a5e4f06 --- /dev/null +++ b/joom建品/joom_route.json @@ -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)" + } +} \ No newline at end of file