修正 aliChoice 失败根因:类目弹窗没关(遮罩挡住所有必填属性)+ 新增文本型必填兜底 fillRequiredTextInputs

This commit is contained in:
admin9webs committed 2026-09-20 22:51:05 +08:00
1 parent de9d988f4c
commit 14214862e6
3 files changed
+145 -5

No files matched your search

+59 -4
View File
@@ -75,17 +75,21 @@ export async function selectCategory(c, kw, preferName) {
await wait(2000);
}
await wait(1800);
// 3) 弹窗必须关掉,否则后面的点击全被挡(曾出现「类目已选但弹窗还开着 → 预览图挂不上」)
// 3) 弹窗必须关掉,否则后面的点击全被挡(SHEIN/Temu 上是「预览图挂不上」,
// aliChoice 上更狠:弹窗不关 → 所有必填属性都填不上 → 保存报一串牛头不对马嘴的错)
let left = Number(await c.evalJS(`[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).length`));
for (let k = 0; k < 3 && left; k++) {
for (let k = 0; k < 6 && left; k++) {
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
if(!m) return 'closed';
const b=[...m.querySelectorAll('button')].find(x=>/^\\s*(确定|选择)\\s*$/.test((x.innerText||'').trim()));
const bar=[...m.querySelectorAll('button')];
const b=bar.find(x=>/^\\s*(确定|选择)\\s*$/.test((x.innerText||'').trim()))||bar.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}));
const c2=[...m.querySelectorAll('.ant-modal-close')][0]; if(c2) c2.click();
return b?'ok':'still-open';})()`);
await wait(1500);
if (k >= 1) await c.send('Input.dispatchKeyEvent', {type:'keyDown', key:'Escape', code:'Escape', windowsVirtualKeyCode:27}).catch(()=>{});
await c.send('Input.dispatchKeyEvent', {type:'keyDown', key:'Escape', code:'Escape', windowsVirtualKeyCode:27}).catch(()=>{});
await wait(600);
if (k >= 3) await c.evalJS(`(()=>{const w=[...document.querySelectorAll('.ant-modal-wrap')].filter(e=>e.offsetParent!==null).pop(); if(w) w.click(); return 1;})()`);
left = Number(await c.evalJS(`[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).length`));
}
await wait(3000);
@@ -271,3 +275,54 @@ export async function fillRequiredSelects(c, skip = ['店铺账号', '产品分
}
return [filled.join(' | '), ticked].filter(x=>x&&x!=='none').join(' | ') || 'none';
}
/** 兜底填「必填但空着」的**文本输入**型类目属性(aliChoice 的「材质(Material)」就是 input,不是 select)。
* values:候选值,按顺序给每个空文本项发一个(不够就重复最后一个)。 */
export async function fillRequiredTextInputs(c, values = ['Other'], skip = ['店铺账号', '产品分类', '主规格', '产品标题', '店小秘分类', '尺码表', 'ParentSKU']) {
const filled = [];
for (let round = 0; round < 6; round++) {
const tgt = JSON.parse(String(await c.evalJS(`(()=>{
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>{
if(e.offsetParent===null) 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.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;
if(e.querySelector('.ant-select,.ant-checkbox-group,.ant-radio-group,.ant-cascader')) return false;
const inp=e.querySelector('input.ant-input, textarea.ant-input');
if(!inp || inp.offsetParent===null) return false;
if(String(inp.value||'').trim()) return false;
return true;});
const it=items[0]; if(!it) return '[]';
const l=it.querySelector('label');
return JSON.stringify({name:(l.innerText||'').replace(/\\s+/g,' ').trim().slice(0,30)});})()`)));
if (!tgt.name) break;
const v = values[Math.min(filled.length, values.length - 1)] || 'Other';
const ok = await c.evalJS(`(()=>{
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>{
if(e.offsetParent===null) 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.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;
if(e.querySelector('.ant-select,.ant-checkbox-group,.ant-radio-group,.ant-cascader')) return false;
const inp=e.querySelector('input.ant-input, textarea.ant-input');
return inp && inp.offsetParent!==null && !String(inp.value||'').trim();});
const it=items.find(e=>((e.querySelector('label')||{}).innerText||'').replace(/\\s+/g,' ').trim().slice(0,30)===${JSON.stringify(tgt.name)});
if(!it) return 'gone';
const inp=it.querySelector('input.ant-input, textarea.ant-input');
const proto = inp.tagName==='TEXTAREA' ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype;
Object.getOwnPropertyDescriptor(proto,'value').set.call(inp, ${JSON.stringify(v)});
inp.dispatchEvent(new Event('input',{bubbles:true})); inp.dispatchEvent(new Event('change',{bubbles:true}));
inp.dispatchEvent(new Event('blur',{bubbles:true}));
return 'ok';})()`);
filled.push(tgt.name + '=' + v);
await wait(1200);
if (ok === 'gone') break;
}
return filled.join(' | ') || 'none';
}