Files
dianxiaomi-crossborder-play…/walmart建品/wmbatch.mjs
T
admin9webs f5720ad613 店小秘多平台铺货配方:7 平台建品脚本 + 静默拦截诊断法 + 项目状态
- TikTok/Shopee/速卖通/Walmart 各 46-47 条已在待发布
- Ozon 47 条已建、改价 40/47
- SHEIN 配方跑通第一条;Temu 差「预览图」一步
- 核心产出:MYJ.message.error 钩子诊断法(解开了三个平台的保存静默拦截)
- 含下一轮提示词 NEXT-ROUND-PROMPT.md
2026-09-20 17:29:38 +08:00

141 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
import fs from 'fs';
/* ── Walmart (Snowmountain, shopId 7661631) 建品配方 ──
node wmbatch.mjs <oid>
选店铺 → 选类目(索引) → Product Name/SKU/UPC(EAN)/Brand → 价格+库存+运输重量
→ 图片 → 描述+key feature → 保存 → 回读 */
const OID = process.argv[2];
if (!OID) { console.log('用法: node wmbatch.mjs <oid>'); process.exit(1); }
const IN = JSON.parse(fs.readFileSync('/tmp/wm_input.json', 'utf8'))[OID];
if (!IN) { console.log('无输入数据', OID); process.exit(1); }
const CATS = JSON.parse(fs.readFileSync('/tmp/wm_cats.json', 'utf8'));
const CAT = CATS[IN.cat_index];
const SHOP = 'Snowmountain';
const SKU = 'WM-' + OID;
const BRAND = process.env.WM_BRAND || 'Generic';
const wait = ms => new Promise(z => setTimeout(z, ms));
const log = (...a) => console.log(' ', ...a);
const tl = await (await fetch('http://127.0.0.1:9223/json/list')).json();
let k = 0;
for (const t of tl.filter(x => x.type === 'page' && /walmart/i.test(x.url))) { await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(() => {}); k++; }
if (k) { log(`清理 walmart 页 ${k} 个`); await wait(1200); }
const t = await newTarget('about:blank');
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable');
await c.send('Page.navigate', { url: 'https://www.dianxiaomi.com/web/walmartProduct/add' });
await wait(24000);
// 1. 店铺
const spos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/店铺/.test(l.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)]);})()`)));
for (const type of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type, x: spos[0], y: spos[1], button: 'left', clickCount: 1 });
await wait(2500);
const pk = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>new RegExp('^\\\\s*${SHOP}\\\\s*$','i').test(o.innerText||''));
if(!hit)return'no-hit'; for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'ok';})()`);
log('1) 店铺:', pk);
await wait(9000);
// 2. 类目
await c.evalJS(`document.body.dispatchEvent(new MouseEvent('mousedown',{bubbles:true}));document.body.click();`);
await wait(1200);
const cpos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&/产品分类/.test(l.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)]);})()`)));
for (const type of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type, x: cpos[0], y: cpos[1], button: 'left', clickCount: 1 });
await wait(3000);
let catRes = 'nf';
for (let i = 0; i < 70; i++) {
const r = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return'no-dd'; const target=${JSON.stringify(CAT)};
const hit=[...dd.querySelectorAll('.ant-select-item-option')].find(o=>(o.innerText||'').trim()===target);
if(!hit)return'scroll';
for(const ev of ['mousedown','mouseup','click']) hit.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
return 'picked';})()`);
if (r === 'picked') { catRes = 'picked:' + CAT; break; }
if (r === 'no-dd') { catRes = 'no-dd'; break; }
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
if(!dd)return 0; const rc=dd.querySelector('.rc-virtual-list-holder')||dd; rc.scrollTop=rc.scrollTop+340; return rc.scrollTop;})()`);
await wait(330);
}
log('2) 类目:', catRes);
if (catRes === 'nf' || catRes === 'no-dd') { log('❌ 类目未选中,中止'); process.exit(2); }
await wait(16000);
// 3. 全部字段直填 store
const wr = await c.evalJS(`(()=>{const p=document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
const g=n=>p._s.get(n).$state;
const basic=g('walmartBasicStore').formState;
basic.hasVariant=false;
const pr=g('walmartProductStore').formState;
pr.productName=${JSON.stringify(IN.title)};
pr.productIdType='UPC';
pr.productId=${JSON.stringify(IN.ean)};
pr.sku=${JSON.stringify(SKU)};
const at=g('walmartAttrStore').formState;
at.brand=${JSON.stringify(BRAND)};
const ps=g('walmartPriceShippingStore').formState;
ps.price=${JSON.stringify(String(IN.price_usd))};
ps.inventory='999';
ps.shippingWeight=${JSON.stringify(String(IN.weight_lbs))};
const ds=g('walmartDescStore').formState;
ds.description=${JSON.stringify(IN.bullets.map(b => '• ' + b).join('<br>'))};
ds.keyFeatures=${JSON.stringify(IN.bullets.slice(0, 3).map(b => b.slice(0, 100)))};
const im=g('walmartImgStore').formState;
const urls=${JSON.stringify(IN.imgs)};
im.mainImage=urls.map((u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800}));
return JSON.stringify({name:pr.productName.length, upc:pr.productId, price:ps.price, w:ps.shippingWeight, imgs:urls.length, kf:ds.keyFeatures.length});})()`);
log('3) 字段:', wr);
await wait(2500);
// 3b. DOM 兜底:把关键文本字段再按真实事件写一遍(Brand/价格/库存/重量等 store 不绑 DOM)
const setByLabel = (rx, val) => c.evalJS(`(()=>{
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
.find(x=>{const l=x.querySelector('label'); return l&&new RegExp(${JSON.stringify(rx)}).test(l.innerText||'');});
if(!it)return'nf';
const el=it.querySelector('textarea')||it.querySelector('input:not([type=hidden])');
if(!el)return'no-el';
const proto=el.tagName==='TEXTAREA'?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;
Object.getOwnPropertyDescriptor(proto,'value').set.call(el, ${JSON.stringify(String(val))});
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true}));
return 'ok';})()`);
const fills = [
['Product Name|产品名称', IN.title],
['^\\s*SKU', SKU],
['^\\s*Brand|品牌', BRAND],
['^\\s*价格', String(IN.price_usd)],
['^\\s*库存', '999'],
['运输重量', String(IN.weight_lbs)],
['产品描述', IN.bullets.map(b => '• ' + b).join('\n')],
['key feature', IN.bullets.slice(0, 3).join('\n')],
];
const fillRes = [];
for (const [rx, v] of fills) fillRes.push(rx.split('|')[0] + '=' + (await setByLabel(rx, v)));
log('3b) DOM兜底:', fillRes.join(' '));
await wait(2000);
// 4. 保存
const sv = await c.evalJS(`(()=>{const b=[...document.querySelectorAll('button,a,span')].filter(e=>e.offsetParent!==null&&/^\\s*保存\\s*$/.test(e.innerText||''));
if(!b.length)return'nf'; for(const ev of ['mousedown','mouseup','click']) b[b.length-1].dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'clicked';})()`);
log('4) 保存:', sv);
const seen = [];
for (let i = 0; i < 18; i++) {
await wait(700);
const m = await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-message-notice,.ant-notification-notice,.ant-modal-confirm')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim()).filter(Boolean); return JSON.stringify(ms.slice(0,3));})()`);
JSON.parse(String(m)).forEach(x => seen.push(x));
}
log('5) 提示:', SAN([...new Set(seen)].join(' || ')).slice(0, 420) || '(无)');
const er = await c.evalJS(`(()=>{const e=[...document.querySelectorAll('.ant-form-item-has-error')].map(x=>{const l=x.querySelector('label'); return (l?l.innerText.trim():'?');});
return JSON.stringify([...new Set(e)].slice(0,8));})()`);
log('6) 仍报错的字段:', SAN(String(er)).slice(0, 200));
await c.shot('/tmp/wm_pilot.jpeg').catch(() => {});
process.exit(0);