diff --git a/NEXT-ROUND-PROMPT.md b/NEXT-ROUND-PROMPT.md index 1ee96d9..8d24ee3 100644 --- a/NEXT-ROUND-PROMPT.md +++ b/NEXT-ROUND-PROMPT.md @@ -75,7 +75,7 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){window.__cap.push(l+': '+[...a | 平台 | add 路由 | 店铺控件 | 类目控件 | store 家族 | |---|---|---|---|---| | Joom | `/web/joomProduct/add` | **复选框组** 85 个(全部+84 店) | 可搜索 `.ant-select` 平铺列表 | `smtChoice*` + `joom*`(已跑通) | - | aliChoice | `/web/smtChoice/add` | 下拉(实测 2 家:XYZ Store / UniStyle Bag Store) | 弹窗 + 自动识别分类 | `smtChoice*`,**但与 Joom 字段名不同**:标题在 `smtChoiceAddBasicStore.subject`(Joom 是 `.name`);图片在 `smtChoiceAddProductStore.mainImage[]`。侦察档见 `alichoice建品/RECIPE.md` | + | aliChoice | `/web/smtChoice/add` | 下拉(实测 2 家:XYZ Store / UniStyle Bag Store) | 弹窗 + 自动识别分类 | `smtChoice*`,**字段名与 Joom 不同**:标题在 `smtChoiceAddBasicStore.subject`(Joom 是 `.name`);描述在 `smtChoiceAddStore.describeInfoData.detail`。**已填到最后一步**,卡在「**请选择物流属性**」(无 label 的报错块 + `icon_edit2` 编辑入口,对应 SKU 行的 `logisticAttrListJson`)——下一轮从这里接着做,细节见 `alichoice建品/RECIPE.md` | | Lazada | `/web/lazada/add` | 下拉 | 弹窗 | 选完店铺才注册 `lazada*` store(懒加载) | | fruugo | `/web/fruugoProduct/add` | 下拉 | 弹窗 | `fruugoAdd*`(Common/Basic/Dxm/Product/Variation/Image/Describe/Form) | diff --git a/alichoice建品/RECIPE.md b/alichoice建品/RECIPE.md index 0c52082..feaaf4e 100644 --- a/alichoice建品/RECIPE.md +++ b/alichoice建品/RECIPE.md @@ -1,41 +1,45 @@ -# aliChoice(速卖通全托管)侦察 — 未跑通,下一轮从这里接着做 +# aliChoice(速卖通全托管)配方 — 差最后一步,下一轮从「物流属性」接着做 路由:`https://www.dianxiaomi.com/web/smtChoice/add` 列表:`/web/smtChoice/{draft=采集箱, offline=待发布}` -页面标题:`店小秘--添加速卖通全托管产品` -## 已勘明(`alc_probe1.mjs`) +## 已经打通的(`alc_try1.mjs`,一条完整填到最后一步) +1. **店铺**:`店铺名称` 下拉(实测 2 家:`XYZ Store` / `UniStyle Bag Store`),取第一家 → `shopId=4393191` +2. **备货类型**:下拉 `国内履约` / `海外备货` → 取第一个(→ `productType='1'`) +3. **类目**:`选择分类` 弹窗 → 用 `sh_build.selectCategory(c, cat_kw, cat_kw)`(与 SHEIN/Temu 同一套) +4. **标题** → `smtChoiceAddBasicStore.formState.subject`(⚠️ **不是 Joom 的 `name`**) +5. **主图** → `smtChoiceAddProductStore.formState.mainImage[]`(吃 `{url,imgUrl,name,width,height}`) +6. **PC端描述** → `smtChoiceAddStore.describeInfoData.detail`(DOM 里是个 `TEXTAREA`) +7. **SKU 行** → `smtChoiceSkuDataStore.formState.skuDataList[]` + 字段:`skuPrice / skuCode / scItemBarCode / packageWeight / packageLength / packageWidth / + packageHeight / logisticAttrListJson / logisticAttrList / imageList` +8. **必填类目属性**(品牌 / 高关注化学品 / 产地)→ `fillRequiredSelects()` 现已能自动填 + (产地会输「中国」过滤后选 `中国大陆`) -### 店铺 -`店铺名称` 是**下拉**,本例只有 **2 家**:`XYZ Store`、`UniStyle Bag Store`。 -(上一轮侦察写的「64 家」是平台维度统计,实际可用店铺以后续页面为准。) +## ⛔ 当前唯一卡点:「请选择物流属性」 +保存时钩子给 `MYJ.error: 校验失败`,DOM 里是一个**没有 label** 的报错块: -### 表单(可见,前 8 项) -`店铺名称 · 备货类型 · 产品标题 · 产品分类(+选择分类/自动识别分类) · 店小秘分类 · 来源URL · 产品属性` +```html +
+ +
请选择物流属性
+
+``` -### store 与字段落点(**注意:和 Joom 共用 `smtChoice*`,但字段名不同**) -| store | 关键字段 | -|---|---| -| `smtChoiceAddBasicStore` | `shopId`、`categoryId`、`productType`、**`subject`(产品标题在这里,不是 `name`)** | -| `smtChoiceAddProductStore` | `mainImage[]`(数组)、`whiteBgImg`、`sceneImg`、`productManual`、`installVideo`、`videoData` | -| `smtChoiceSkuDataStore` | `skuDataList[]`(默认已生成 1 行) | -| `smtChoiceSkuAttrStore` | `skuAttrs[]` | -| `smtChoiceAddAttrStore` | `customAttrs[]` + 一大票汽配字段(`motorIds` / `vehicleEuAfids` …) | -| `smtChoiceAddOtherStore` | `packageFormState` / `otherInfoFormState` / `otherBusinessInfoState` | -| `smtChoiceAddStore` | `productInfo` / `attrsInfo` / `describeInfoData`(**没有 formState**) | +- 它带一个 `icon_edit2 link` 的编辑入口 → 点开大概率是「物流属性」弹窗/抽屉 +- SKU 行里有 `logisticAttrListJson` / `logisticAttrList` 两个字段空着,应该就是它要填的 +- 下一步:点那个 `icon_edit2`,dump 弹窗结构(很可能要按类目选物流渠道/时效) -⚠️ 和 Joom 的差异:Joom 标题在 `smtChoiceAddBasicStore.name`,这里在 `.subject`。 -**别把 Joom 的填法直接抄过来。** +## 另一个未验证项:4 个规格组都是 0 选项 +`smtChoiceSkuAttrStore.formState.skuAttrs` 有 4 组: +`Ships From(200007763) / Color(14) / Number of Pcs(200116262) / Bulk Buy(200483263)`, +但 `options` 都是 0(值在 `_options` 或要异步加载),DOM 里能看到值(卡其色/粉红/6Pc/Set…)。 +**未验证变种值是否必须选**——先解物流属性,再看保存是否又冒出新的一层。 -## 下一轮的顺序建议 -1. 选店铺(下拉,取第一家 `XYZ Store`) -2. **备货类型**(必填,先探选项) -3. 类目:`选择分类` 弹窗(同 SHEIN/Temu 那套)+ 留意「自动识别分类」 -4. 标题 → `smtChoiceAddBasicStore.subject` -5. 图片 → `smtChoiceAddProductStore.mainImage[]` -6. SKU 行 / 属性 / 包装 → 对应 store -7. **挂钩子再保存**(大概率还有层层递进的必填:参考 Temu 的省份→变种→尺寸→净重→预览图) - -## 已知可复用的东西 -- `shein建品/sh_lib.mjs`(`newPage`/`closePage`/`installHook`/`readHook`) -- `shein建品/sh_build.mjs` 的 `selectCategory` / `fillRequiredSelects`(SHEIN/Temu/Joom 都已验证) -- `joom建品/jmbatch.mjs` 的骨架(换字段名和控件类型即可) +## 本轮顺手修好的共享 bug(`shein建品/sh_build.mjs` 的 `fillRequiredSelects`) +之前它在 aliChoice 上**一个必填属性都填不上**,三个原因,都已修: +1. select 分支的必填判断只看 `label.ant-form-item-required`,而速卖通系的属性是 + `.attr-label.required`(label 上还常带 `*` 前缀)→ 现在三种标记都认 +2. 容器项「产品属性」本身也带 `*`,会被当成一个字段去点 → 现在跳过**含嵌套 `.ant-form-item`** 的项 +3. 点开下拉后按 `.pop()` 取最后一个可见下拉,容易读到**上一个字段残留**的列表 + (曾把化学物列表当国家列表)→ 现在①每次点前先 `Esc` ②按**与点击点的距离**挑下拉 + ③产地/Origin 先 `Input.insertText('中国')` 过滤 diff --git a/alichoice建品/alc_probe2.mjs b/alichoice建品/alc_probe2.mjs new file mode 100644 index 0000000..8979052 --- /dev/null +++ b/alichoice建品/alc_probe2.mjs @@ -0,0 +1,66 @@ +import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs'; +import { selectCategory } 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 c = await newPage('https://www.dianxiaomi.com/web/smtChoice/add'); +await wait(12000); + +// 1) 店铺下拉 +const shop = 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?f.querySelector('.ant-select-selector'):null; if(!s) return 'no-sel'; 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)]);})()`); +const sp = JSON.parse(String(shop)); +if (sp.length){ + await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:sp[0],y:sp[1],buttons:0}); await wait(300); + for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:sp[0],y:sp[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();})()`))); +await wait(6000); + +// 2) 备货类型 +const bp = 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||'')); + if(!f) return '[]'; + const s=f.querySelector('.ant-select-selector'); + const ctrls=[...f.querySelectorAll('.ant-select,.ant-radio-group,.ant-btn-group')].map(x=>x.tagName+'.'+String(x.className).slice(0,26)); + if(!s) return JSON.stringify({ctrls, txt:(f.innerText||'').replace(/\\s+/g,' ').trim().slice(0,60)}); + s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect(); + return JSON.stringify({ctrls, txt:(f.innerText||'').replace(/\\s+/g,' ').trim().slice(0,60), x:Math.round(r.x+r.width/2), y:Math.round(r.y+r.height/2)});})()`))); +console.log('备货类型控件:', JSON.stringify(bp).slice(0,300)); +if (bp.x){ + await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:bp.x,y:bp.y,buttons:0}); await wait(300); + for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:bp.x,y:bp.y,button:'left',clickCount:1,buttons:1}); + await wait(2200); + console.log('备货类型选项:', S(String(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||'').trim()); + return JSON.stringify(items.slice(0,8));})()`))).slice(0,300)); + 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]; if(!op) return 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(3000); +} +// 3) 类目 +console.log('类目:', S(String(await selectCategory(c, it.cat_kw || '发夹', it.cat_kw))).slice(0,160)); +await wait(5000); +// 4) 表单项 + store +const items = await c.evalJS(`JSON.stringify([...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).slice(0,20).map(e=>{ + const l=e.querySelector('label'); const req=l?(/required/.test(String(l.className))||/required/.test(String((e.querySelector('.attr-label')||{}).className||''))?'*':''):''; + return req+((e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,44));}))`); +console.log('表单项:', S(String(items)).slice(0,1200)); +const st = await c.evalJS(`(()=>{const p=${P}; + const ids=(p._s instanceof Map?[...p._s.keys()]:Object.keys(p._s)).filter(x=>/smtChoice/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]?JSON.stringify(v[0]).slice(0,70):''); + 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>14){o['…']='';break;}} return o;} return String(v).slice(0,40); }; + for(const id of ids){ try{ const s=(p._s.get?p._s.get(id):p._s[id]).$state; out[id]= s.formState?fmt(s.formState):('keys='+Object.keys(s).slice(0,8).join(',')); }catch(e){out[id]='ERR';} } + return JSON.stringify(out);})()`); +console.log('stores:', JSON.stringify(JSON.parse(String(st)),null,1).slice(0,1800)); +try{ await c.shot('/tmp/alc2.jpg'); console.log('shot ok'); }catch(e){} +await closePage(c); process.exit(0); diff --git a/alichoice建品/alc_probe3.mjs b/alichoice建品/alc_probe3.mjs new file mode 100644 index 0000000..571d8dc --- /dev/null +++ b/alichoice建品/alc_probe3.mjs @@ -0,0 +1,42 @@ +import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs'; +import { selectCategory } from '/home/admin9webs/shein建品/sh_build.mjs'; +import { SAN as S } from '/tmp/cdp3.mjs'; +import { readFileSync } from 'fs'; +const pid='676236596821'; +const it = JSON.parse(readFileSync('/home/admin9webs/tiktok建品/tk_input.json','utf8'))[pid]; +const c = await newPage('https://www.dianxiaomi.com/web/smtChoice/add'); +await wait(12000); +// 店 + 备货类型(照抄 try1 的两步) +for (const [label, hint] of [['店铺名称','店铺'],['备货类型','备货']]) { + const p = JSON.parse(String(await c.evalJS(`(()=>{const f=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>new RegExp(${JSON.stringify(label)}).test((e.querySelector('label')||{}).innerText||'')); + const s=f?f.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(!p.length) { console.log(hint,'没找到'); continue; } + await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:p[0],y:p[1],buttons:0}); await wait(300); + for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:p[0],y:p[1],button:'left',clickCount:1,buttons:1}); + await wait(2000); + console.log(hint,'选中:', 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();})()`))); + await wait(label==='店铺名称'?6000:3000); +} +console.log('类目:', S(String(await selectCategory(c, it.cat_kw||'发夹', it.cat_kw))).slice(0,90)); +await wait(6000); +const d = await c.evalJS(`(()=>{ + const want=['品牌','高关注化学品','产地']; + const out=[]; + for (const e of document.querySelectorAll('.ant-form-item')) { + if (e.offsetParent===null) continue; + const l=e.querySelector('label'); const t=l?(l.innerText||'').replace(/\\s+/g,' ').trim():''; + if(!want.some(w=>t.startsWith(w)||t.startsWith('*'+w))) continue; + const sel=e.querySelector('.ant-select'); + out.push({ txt:t.slice(0,30), labelCls:l?String(l.className).slice(0,60):'', labelHTML:l?l.outerHTML.replace(/\\s+/g,' ').slice(0,150):'', + hasAttrLabel:!!e.querySelector('.attr-label'), attrCls:String((e.querySelector('.attr-label')||{}).className||''), + selTxt: sel?(sel.innerText||'').replace(/\\s+/g,' ').trim().slice(0,20):'no-sel', + nested: !!e.parentElement.closest('.ant-form-item') }); + } + return JSON.stringify(out);})()`); +console.log('必填属性项:', JSON.stringify(JSON.parse(String(d)), null, 1).slice(0,2000)); +await closePage(c); process.exit(0); diff --git a/alichoice建品/alc_try1.mjs b/alichoice建品/alc_try1.mjs new file mode 100644 index 0000000..69096e7 --- /dev/null +++ b/alichoice建品/alc_try1.mjs @@ -0,0 +1,88 @@ +import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs'; +import { selectCategory, 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/smtChoice/add'); +await wait(12000); await installHook(c); +// 店 +const sp = 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:sp[0],y:sp[1],buttons:0}); await wait(300); +for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:sp[0],y:sp[1],button:'left',clickCount:1,buttons:1}); +await wait(2000); +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 bp = 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:bp[0],y:bp[1],buttons:0}); await wait(300); +for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:bp[0],y:bp[1],button:'left',clickCount:1,buttons:1}); +await wait(2000); +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(3000); +console.log('类目:', S(String(await selectCategory(c, it.cat_kw || '发夹', it.cat_kw))).slice(0,120)); +await wait(6000); +// 结构 dump +const shape = await c.evalJS(`(()=>{const p=${P}; + const sd=p._s.get('smtChoiceSkuDataStore').$state.formState; + const sku=JSON.stringify(sd.skuDataList&&sd.skuDataList[0]||{}).slice(0,700); + const desc=p._s.get('smtChoiceAddStore').$state.describeInfoData; + const sa=p._s.get('smtChoiceSkuAttrStore').$state.formState.skuAttrs||[]; + return JSON.stringify({skuRow:sku, descKeys:Object.keys(desc||{}).slice(0,10), desc:JSON.stringify(desc||{}).slice(0,300), + attrs:sa.map(a=>a.attributeId+':'+a.attributeName+' opts='+((a.options||a._options||[]).length))});})()`); +console.log('结构:', S(String(shape)).slice(0,1200)); +// PC端描述 DOM +const dom = await c.evalJS(`(()=>{const f=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/PC端描述/.test((e.querySelector('label')||{}).innerText||'')); + if(!f) return 'no-item'; + return JSON.stringify({tags:[...f.querySelectorAll('input,textarea,[contenteditable],.ql-editor,.w-e-text')].map(x=>x.tagName+(x.className?'.'+String(x.className).slice(0,30):'')).slice(0,6), + txt:(f.innerText||'').replace(/\\s+/g,' ').trim().slice(0,60)});})()`); +console.log('PC端描述:', S(String(dom)).slice(0,400)); +// 填 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.subject=${JSON.stringify(it.title)}; + const pr=p._s.get('smtChoiceAddProductStore').$state.formState; + pr.mainImage=imgs.map(mk); + const sd=p._s.get('smtChoiceSkuDataStore').$state.formState; + (sd.skuDataList||[]).forEach((r,i)=>{ for(const k of ['skuPrice','price','skuStock','stock','inventory','skuSupplierCode','supplierCode','skuCode','code']) { if(k in r) { if(/price/i.test(k)) r[k]=String(${JSON.stringify(String(it.price_cny||'19.90'))}); else if(/stock|inventory/i.test(k)) r[k]='999'; else r[k]='AC-'+${JSON.stringify(pid)}+'-'+(i+1); } } }); + const desc=p._s.get('smtChoiceAddStore').$state.describeInfoData; + if(desc) desc.detail=(desc.detail||'')+${JSON.stringify((it.bullets||[]).join('
'))}; + (sd.skuDataList||[]).forEach(r=>{ r.skuPrice=String(${JSON.stringify(String(it.price_cny||'19.90'))}); r.skuCode='AC-'+${JSON.stringify(pid)}; + r.packageWeight='100'; r.packageLength='20'; r.packageWidth='15'; r.packageHeight='3'; }); + return JSON.stringify({subject:b.subject.slice(0,20), imgs:pr.mainImage.length, rows:(sd.skuDataList||[]).length, desc:(desc&&desc.detail||'').length});})()`); +console.log('填充:', S(String(fill)).slice(0,300)); +await wait(3500); +console.log('必填属性:', S(String(await fillRequiredSelects(c))).slice(0,300)); +await wait(2000); +// 保存 +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(14000); +const detail = await c.evalJS(`(()=>{const bad=[...document.querySelectorAll('.ant-form-item-has-error')].map(x=>({ + label:(x.querySelector('label')||{}).innerText||'(无)', + cls:String(x.className).slice(0,50), + html:x.outerHTML.replace(/\s+/g,' ').slice(0,420)})); + const sa=(()=>{const p=${P};return (p._s.get('smtChoiceSkuAttrStore').$state.formState.skuAttrs||[]).map(a=>({id:a.attributeId,name:a.attributeName,opts:(a.options||[]).length,sel:(a.options||[]).filter(o=>o.selected||o.checked).length}));})(); + const sd=(()=>{const p=${P};const r=(p._s.get('smtChoiceSkuDataStore').$state.formState.skuDataList||[])[0]||{};return {keys:Object.keys(r),skuPrice:r.skuPrice,skuCode:r.skuCode,w:r.packageWeight};})(); + return JSON.stringify({bad, sa, sd});})()`); +console.log('错误详情:', S(String(detail)).slice(0,1100)); +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)), + errItems:[...document.querySelectorAll('.ant-form-item-has-error')].map(x=>{const l=x.querySelector('label'); return l?(l.innerText||'').replace(/\\s+/g,' ').trim().slice(0,26):'?';}).slice(0,8)})`); +console.log('结果:', S(String(res)).slice(0,600)); +console.log('hook:', String(await readHook(c)).slice(-320)); +try{ await c.shot('/tmp/alc_try1.jpg'); console.log('shot ok'); }catch(e){} +await closePage(c); process.exit(0); diff --git a/shein建品/sh_build.mjs b/shein建品/sh_build.mjs index a3d366b..6a40920 100644 --- a/shein建品/sh_build.mjs +++ b/shein建品/sh_build.mjs @@ -202,7 +202,9 @@ export async function fillRequiredSelects(c, skip = ['店铺账号', '产品分 const r = await c.evalJS(`(()=>{ const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>{ if(e.offsetParent===null) return false; - const req=e.querySelector('.attr-label.required')||e.querySelector('label.ant-form-item-required'); + const lab=e.querySelector('label'); + const req=e.querySelector('.attr-label.required')||(lab&&/required/.test(String(lab.className))?lab:null) + ||(lab&&(lab.innerText||'').trim().startsWith('*')?lab:null); if(!req) return false; const name=(req.innerText||'').replace(/\s+/g,' ').trim(); if(${JSON.stringify(skip)}.some(s=>name.includes(s))) return false; @@ -229,8 +231,11 @@ export async function fillRequiredSelects(c, skip = ['店铺账号', '产品分 for (let round = 0; round < 8; round++) { const tgt = JSON.parse(String(await c.evalJS(`(()=>{ const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>{ - const l=e.querySelector('label'); if(!l||!/ant-form-item-required/.test(String(l.className))) return false; + const l=e.querySelector('label'); + const reqOk = l && (/required/.test(String(l.className)) || (l.innerText||'').trim().startsWith('*') || e.querySelector('.attr-label.required')); + if(!reqOk) return false; if(e.offsetParent===null) return false; + if(e.querySelector('.ant-form-item')) return false; // 容器项(如「产品属性」):只管叶子项 const name=(l.innerText||'').replace(/\\s+/g,' ').trim(); if(${JSON.stringify(skip)}.some(s=>name.includes(s))) return false; const sel=e.querySelector('.ant-select'); if(!sel) return false; @@ -241,13 +246,22 @@ export async function fillRequiredSelects(c, skip = ['店铺账号', '产品分 sel.scrollIntoView({block:'center'}); const r=sel.getBoundingClientRect(); return JSON.stringify({name:(l.innerText||'').replace(/\\s+/g,' ').trim().slice(0,30), x:Math.round(r.x+r.width/2), y:Math.round(r.y+r.height/2)});})()`))); if (!tgt.name) break; + await c.send('Input.dispatchKeyEvent', {type:'keyDown', key:'Escape', code:'Escape', windowsVirtualKeyCode:27}).catch(()=>{}); + await wait(400); await c.send('Input.dispatchMouseEvent', {type:'mouseMoved', x:tgt.x, y:tgt.y, buttons:0}); await wait(250); for (const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent', {type:t, x:tgt.x, y:tgt.y, button:'left', clickCount:1, buttons:1}); await wait(1800); - const pick = 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')].filter(e=>e.offsetParent!==null)[0]; - if(!op) return 'no-op'; + if (/产地|Origin/i.test(tgt.name)) { await c.send('Input.insertText', {text:'中国'}).catch(()=>{}); await wait(1500); } + const pick = await c.evalJS(`(()=>{const cy=${JSON.stringify(tgt.y)}; + const cands=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null); + if(!cands.length) return 'no-dd'; + const dd=cands.map(e=>({e,d:Math.abs(e.getBoundingClientRect().top-cy)})).sort((a,b)=>a.d-b.d)[0].e; + const ops=[...dd.querySelectorAll('.ant-select-item-option')].filter(e=>e.offsetParent!==null); + if(!ops.length) return 'no-op'; + const want=${JSON.stringify(tgt.name)}; + const clean=x=>(x.innerText||'').replace(/\s+/g,' ').trim(); + const op=(/产地|Origin/i.test(want) ? ops.find(o=>/中国|China/i.test(clean(o))) : null) + || ops.find(o=>!/^(无|none|NONE)/.test(clean(o))) || 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,18);})()`);