Files
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

101 lines
6.0 KiB
JavaScript

import { newTarget, CDP, SAN } from '/tmp/cdp3.mjs';
import fs from 'fs';
/* ── Ozon 改价(≤500g 档,售价 = 合计成本 ÷ (1 − 20% − 利润率))──
node ozprice_set.mjs <pid> <priceCNY> [dry]
dry = 只填不保存 */
const PID = process.argv[2];
const PRICE = process.argv[3];
const DRY = process.argv[4] === 'dry';
if (!PID || !PRICE) { console.log('用法: node ozprice_set.mjs <pid> <priceCNY> [dry]'); process.exit(1); }
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' && x.url.includes('ozonProduct/edit'))) { await fetch(`http://127.0.0.1:9223/json/close/${t.id}`).catch(() => {}); k++; }
if (k) { log(`清理残留编辑页 ${k} 个`); await wait(1100); }
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/ozonProduct/edit?id=${PID}` });
await wait(32000);
for (let i = 0; i < 8; i++) {
const x = await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null);
if(!ms.length)return'n'; ms.forEach(m=>{const cb=m.querySelector('.ant-modal-close'); if(cb&&cb.offsetParent!==null)cb.click();}); return 'c';})()`);
if (x === 'n') break; await wait(1000);
}
await wait(2000);
// 1. 找 SKU 表 + 价格列
const map = await c.evalJS(`(()=>{
const tbls=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null);
for(let ti=0;ti<tbls.length;ti++){
const ths=[...tbls[ti].querySelectorAll('th')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
if(!ths.some(h=>/价格|售价|₽|RUB/i.test(h))) continue;
const rows=[...tbls[ti].querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length);
if(!rows.length) continue;
return JSON.stringify({ti, ths, rowCount: rows.length,
vals: rows.slice(0,3).map(tr=>[...tr.querySelectorAll('input')].map(i=>(i.value||'').slice(0,12)))});
}
return 'no-table';})()`);
log('SKU 表:', SAN(String(map)).slice(0, 500));
if (map === 'no-table') { log('❌ 找不到价格表'); process.exit(1); }
const M = JSON.parse(String(map));
const IDX_PRICE = M.ths.findIndex(h => /价格|售价|₽|RUB/i.test(h) && !/划线|原价|市场价|最低/i.test(h));
const IDX_OLD = M.ths.findIndex(h => /划线|原价|市场价/i.test(h));
const OLD_PRICE = (Math.ceil(Number(PRICE) * 1.2 * 100) / 100).toFixed(2);
log('价格列:', IDX_PRICE, JSON.stringify(M.ths[IDX_PRICE]), '| 原价列:', IDX_OLD, IDX_OLD >= 0 ? JSON.stringify(M.ths[IDX_OLD]) : '(无)', '→ 原价设为', OLD_PRICE);
// 2. 填价
const r = await c.evalJS(`(()=>{
const setV=(el,v)=>{ Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set.call(el,String(v));
el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); el.dispatchEvent(new Event('blur',{bubbles:true})); };
const tbls=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null);
const tbl=tbls[${M.ti}]; if(!tbl)return'no-table';
const rows=[...tbl.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length);
let n=0;
rows.forEach(tr=>{ const tds=[...tr.querySelectorAll('td')]; const td=tds[${IDX_PRICE}]; if(!td)return;
const inp=td.querySelector('input'); if(!inp)return; setV(inp,${JSON.stringify(PRICE)}); n++;
const od=tds[${IDX_OLD}]; if(od){ const oi=od.querySelector('input'); if(oi) setV(oi, ${JSON.stringify(OLD_PRICE)}); } });
return 'set:'+n+'/'+rows.length;})()`);
log('填价:', r);
await wait(2500);
// 3. 回读
const back = await c.evalJS(`(()=>{const tbls=[...document.querySelectorAll('table')].filter(t=>t.offsetParent!==null);
const tbl=tbls[${M.ti}]; const rows=[...tbl.querySelectorAll('tbody tr')].filter(x=>x.querySelectorAll('input').length);
return JSON.stringify(rows.slice(0,5).map(tr=>[...tr.querySelectorAll('td')].map(td=>{const i=td.querySelector('input'); return i?i.value:'';})));})()`);
log('回读:', SAN(String(back)).slice(0, 300));
if (DRY) { log('dry-run,不保存'); process.exit(0); }
// 4. 保存(优先「保存并移入待发布」,与 fixone3 一致)
await c.evalJS(`window.scrollTo(0,0)`); await wait(700);
const reqs = [];
c.on(m => { if (m.method === 'Network.requestWillBeSent') { const u = m.params.request.url; if (/ozonProduct\/add\.json/.test(u)) reqs.push(m.params.request.method); } });
await c.send('Network.enable').catch(()=>{});
const sv = await c.evalJS(`(()=>{const xs=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null);
let x=xs.find(e=>/保存并移入待发布/.test((e.innerText||'').trim())); let tag='保存并移入待发布';
if(!x){ x=xs.find(e=>/^保\\s*存$/.test((e.innerText||'').trim())); tag='保存'; }
if(!x) return 'nf';
for(const ev of ['mousedown','mouseup','click']) x.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return tag;})()`);
log('保存按钮:', sv);
await wait(13000);
await c.evalJS(`(()=>{const ms=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null);
for(const m of ms){const b=[...m.querySelectorAll('button')].find(x=>/确定|确认/.test(x.innerText||'')); if(b)b.click();} return 1;})()`);
await wait(5000);
log('保存请求:', JSON.stringify(reqs));
// 5. API 回读(权威)
const back2 = await c.evalJS(`(async()=>{const rr=await fetch('/api/ozonProduct/add.json?id=${PID}&_t='+Date.now(),{headers:{'X-Requested-With':'XMLHttpRequest','Cache-Control':'no-cache'}});
const tx=await rr.text(); if(!tx.startsWith('{'))return'(err)';
const p=(JSON.parse(tx).data||{}).product||{}; const vl=p.variantList||[];
return JSON.stringify({n:vl.length, prices:vl.slice(0,3).map(v=>v.price), sales:vl.slice(0,3).map(v=>v.salePrice)});})()`, true);
log('★ API 回读:', SAN(String(back2)).slice(0, 260));
process.exit(0);