aliChoice:SKU分类是原生 select(新增 fillNativeSelects,失败 6/10→8/10);文本必填改为按字段名给值,修正颜色被填成材质词

This commit is contained in:
admin9webs committed 2026-09-20 23:09:39 +08:00
1 parent 14214862e6
commit ad9b955d95
2 files changed
+46 -5

No files matched your search

+17 -4
View File
@@ -9,7 +9,7 @@
*/ */
import { readFileSync, writeFileSync, appendFileSync } from 'fs'; import { readFileSync, writeFileSync, appendFileSync } from 'fs';
import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs'; import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs';
import { selectCategory, fillRequiredSelects, fillRequiredTextInputs } from '/home/admin9webs/shein建品/sh_build.mjs'; import { selectCategory, fillRequiredSelects, fillRequiredTextInputs, fillNativeSelects } from '/home/admin9webs/shein建品/sh_build.mjs';
const DIR = '/home/admin9webs/alichoice建品'; const DIR = '/home/admin9webs/alichoice建品';
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`; const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
@@ -140,11 +140,24 @@ for (const t of tasks) {
if (attrs !== 'none') log(` 必填属性: ${String(attrs).slice(0, 150)}`); if (attrs !== 'none') log(` 必填属性: ${String(attrs).slice(0, 150)}`);
await wait(1200); await wait(1200);
// 文本型必填(aliChoice 的「材质(Material)」是 input,不是 select):按标题里的材质词兜底 // 文本型必填(aliChoice 的「材质(Material)」是 input,不是 select):按标题里的材质词兜底
const matHit = String(it.title || '').match(/plastic|cotton|polyester|stainless steel|metal|silicone|wood|resin|aluminum|alloy|fabric|nylon|glass|ceramic|leather/i); const t = String(it.title || '');
const matVals = [...new Set([matHit ? matHit[0] : null, 'Polyester', 'Other'].filter(Boolean))]; const matHit = t.match(/plastic|cotton|polyester|stainless steel|metal|silicone|wood|resin|aluminum|alloy|fabric|nylon|glass|ceramic|leather/i);
const txtAttrs = await fillRequiredTextInputs(c, matVals); const colorHit = t.match(/black|white|pink|red|blue|green|beige|grey|gray|purple|yellow|brown|khaki|ivory|apricot|silver|gold|multicolou?r/i);
// 按字段名给值:颜色别串成材质(曾把「颜色」填成 Resin)
const valueFor = (name) => {
if (/颜色|Color/i.test(name)) return colorHit ? colorHit[0] : 'Multicolor';
if (/材质|Material/i.test(name)) return matHit ? matHit[0] : 'Polyester';
if (/图案|Pattern/i.test(name)) return 'Solid';
if (/尺寸|Size/i.test(name)) return 'Medium';
return 'Other';
};
const txtAttrs = await fillRequiredTextInputs(c, valueFor);
if (txtAttrs !== 'none') log(` 文本必填: ${String(txtAttrs).slice(0, 120)}`); if (txtAttrs !== 'none') log(` 文本必填: ${String(txtAttrs).slice(0, 120)}`);
await wait(1200); await wait(1200);
// 原生 <select>(SKU分类=单品单件、单位=件/个)——antd 选择器够不到
const nat = await fillNativeSelects(c, ['单品单件', '件/个']);
if (nat !== 'none') log(` 原生下拉: ${String(nat).slice(0, 120)}`);
await wait(1200);
log(` PC端描述: ${await fillDescription(c, (it.bullets || []).join('<br/>') || it.title)}`); log(` PC端描述: ${await fillDescription(c, (it.bullets || []).join('<br/>') || it.title)}`);
await wait(1500); await wait(1500);
if (dry) { delete done[t.pid]; log(' ✓ dry 构建完成(未保存)'); await closePage(c); continue; } if (dry) { delete done[t.pid]; log(' ✓ dry 构建完成(未保存)'); await closePage(c); continue; }
+29 -1
View File
@@ -279,6 +279,7 @@ export async function fillRequiredSelects(c, skip = ['店铺账号', '产品分
/** 兜底填「必填但空着」的**文本输入**型类目属性(aliChoice 的「材质(Material)」就是 input,不是 select)。 /** 兜底填「必填但空着」的**文本输入**型类目属性(aliChoice 的「材质(Material)」就是 input,不是 select)。
* values:候选值,按顺序给每个空文本项发一个(不够就重复最后一个)。 */ * values:候选值,按顺序给每个空文本项发一个(不够就重复最后一个)。 */
export async function fillRequiredTextInputs(c, values = ['Other'], skip = ['店铺账号', '产品分类', '主规格', '产品标题', '店小秘分类', '尺码表', 'ParentSKU']) { export async function fillRequiredTextInputs(c, values = ['Other'], skip = ['店铺账号', '产品分类', '主规格', '产品标题', '店小秘分类', '尺码表', 'ParentSKU']) {
// values 可以是数组(按顺序发),也可以是 (字段名)=>值的函数(推荐:不同字段给对的值)
const filled = []; const filled = [];
for (let round = 0; round < 6; round++) { for (let round = 0; round < 6; round++) {
const tgt = JSON.parse(String(await c.evalJS(`(()=>{ const tgt = JSON.parse(String(await c.evalJS(`(()=>{
@@ -299,7 +300,9 @@ export async function fillRequiredTextInputs(c, values = ['Other'], skip = ['店
const l=it.querySelector('label'); const l=it.querySelector('label');
return JSON.stringify({name:(l.innerText||'').replace(/\\s+/g,' ').trim().slice(0,30)});})()`))); return JSON.stringify({name:(l.innerText||'').replace(/\\s+/g,' ').trim().slice(0,30)});})()`)));
if (!tgt.name) break; if (!tgt.name) break;
const v = values[Math.min(filled.length, values.length - 1)] || 'Other'; const v = (typeof values === 'function')
? String(values(tgt.name) || 'Other')
: (values[Math.min(filled.length, values.length - 1)] || 'Other');
const ok = await c.evalJS(`(()=>{ const ok = await c.evalJS(`(()=>{
const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>{ const items=[...document.querySelectorAll('.ant-form-item')].filter(e=>{
if(e.offsetParent===null) return false; if(e.offsetParent===null) return false;
@@ -326,3 +329,28 @@ export async function fillRequiredTextInputs(c, values = ['Other'], skip = ['店
} }
return filled.join(' | ') || 'none'; return filled.join(' | ') || 'none';
} }
/** 兜底填**原生 <select>**(aliChoice 的「SKU分类」= 单品单件/同款多件/混合套装,
* 以及「件/个 / 双」这类单位选择)。antd 选择器一个都匹配不到,只能按原生元素处理。 */
export async function fillNativeSelects(c, preferred = ['单品单件', '件/个']) {
return c.evalJS(`(()=>{const pref=${JSON.stringify(preferred)};
const done=[];
for (const sel of document.querySelectorAll('select')) {
if (sel.offsetParent === null) continue;
if (sel.multiple) continue;
const cur = sel.options[sel.selectedIndex];
const empty = !sel.value || (cur && cur.disabled) || sel.selectedIndex <= 0;
if (!empty) continue;
const opts = [...sel.options].filter(o => !o.disabled && o.value !== '');
if (!opts.length) continue;
let pick = null;
for (const p of pref) { pick = opts.find(o => (o.textContent || '').includes(p)); if (pick) break; }
if (!pick) pick = opts[0];
sel.value = pick.value;
sel.dispatchEvent(new Event('input', { bubbles: true }));
sel.dispatchEvent(new Event('change', { bubbles: true }));
const label = (sel.closest('.ant-form-item') || {}).innerText || '';
done.push((label.replace(/\\s+/g, ' ').trim().slice(0, 12) || 'select') + '=' + (pick.textContent || '').trim().slice(0, 12));
}
return done.join(' | ') || 'none';})()`);
}