Yv3.3

一键成品面板:输入“型号 商品名称”后自动拆分填写.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Yv3.3
// @namespace    http://tampermonkey.net/
// @version      28.1.486486
// @description  一键成品面板:输入“型号 商品名称”后自动拆分填写.
// @author       Assistant
// @match        *://agentseller.temu.com/goods/create*
// @match        *://agentseller.temu.com/goods/edit*
// @grant        none
// @license      y
// ==/UserScript==

(function () {
    'use strict';

    /* ─── 配置区 ────────────────────────────────────────────────────── */
    const CONFIG = {
        // 1. 商品属性
        queue: [ // 商品属性总配置:[字段名, 默认值];留空会自动跳过
            ['主题', '无'],
            ['材料', '涤纶'],
            ['商品产地', '中国大陆'],
            ['图案', '伪装'],
            ['关闭', '拉链'],
            ['包含的组件', '斜挎包'],
            ['衬里说明', '涤纶'],
            ['油边', '否'],
            ['货源产地', '广州产区'],
            ['护理说明', '手洗或干洗'],
            ['颜色', '黑色'],
            ['点缀功能', '无'],
            ['特征', '可调节肩带'],
            ['图案样式', '其他印花'],
            ['印花类型', '定位印花'],
            ['风格', '休闲'],
            ['是否印花样板', '否'],
            ['品牌名', ''],
            ['系列线', ''],
            ['边油', ''],
            ['供电方式', ''],
            ['图案风格', ''],
            ['款式', ''],
            ['包袋大小', ''],
            ['适用场景', ''],
            ['适用季节', ''],
            ['适用性别', ''],
            ['是否可定制', ''],
            ['是否防水', ''],
            ['颜色分类', ''],
            ['容量', ''],
            ['硬度', ''],
            ['开口方式', ''],
            ['内部结构', ''],
            ['肩带样式', ''],
            ['箱包形状', ''],
            ['货号属性', '']
        ],
        categorySelection: { // 新建商品第一页的商品分类配置
            keyword: '女士斜挎包', // 分类关键词;例如 女士斜挎包 / 女包套装
            recommendedTexts: [ // 常用推荐真实可点击文本;按顺序优先匹配,留空则跳过
                '服装、鞋靴和珠宝饰品>...>女士斜挎包',
                '服装、鞋靴和珠宝饰品>...>女包套装',
                '服装、鞋靴和珠宝饰品>...>女士单肩包',
                '服装、鞋靴和珠宝饰品>...>女士托特包'
            ],
            nextButtonText: '下一步' // 分类页下一步按钮文案
        },
        province: '广东省', // 货源省份
        sensitiveAttrValue: '否', // 敏感属性默认选项;留空则跳过

        // 2. 规格
        parentSpec1: { // 父规格1:既支持 specValue,也支持 specValues[]
            typeValueId: 1001, // 规格类型ID
            specValue: '黑色', // 单值模板
            specValues: [] // 多值模板,例如 ['黄色', '白色']
        },
        parentSpec2: { // 父规格2:既支持 specValue,也支持 specValues[]
            typeValueId: 45114199, // 规格类型ID
            specValue: '', // 单值模板;为空时默认回退到输入框里的型号
            specValues: [] // 多值模板,例如 ['套装A', '套装B', '套装C']
        },

        // 3. 尺码与体积重量
        dimensions: { // 体积重量总配置:支持 default/byModel,也兼容旧 marker/value 数组
            default: ['', '', '', ''], // 顺序:最长边 / 次长边 / 最短边 / 重量
            byModel: {
                // '套装A': ['32', '20', '3', '260'],
            },
            markers: ['最长边', '次长边', '最短边', 'g'] // 没有按型号行时的字段顺序
        },
        sizeChart: [ // 尺码表区间;留空项自动跳过
            { label: '长度', min: '29', max: '31' },
            { label: '宽度', min: '20', max: '22' },
            { label: '高度', min: '13', max: '15' }
        ],
        // 4. SKU 与包装清单
        sku: { // SKU 总配置:简单模板和多套装模板都放这里,不用的留空
            declarePrice: '31', // 简单模板申报价格;留空则跳过
            declarePriceByModel: {
                // '套装A': '',
            },
            declarePriceCurrency: '', // 申报价格币种占位;当前脚本未用,留作总模板配置
            skuTypeId: 1, // SKU 类型ID;留空则跳过
            skuTypeLabel: '', // SKU 类型名称占位;当前脚本未用,留作总模板配置
            skuClassLabel: '', // 多套装模板的 SKU分类,例如 混合套装;留空则跳过
            singleItemCount: '', // 简单模板单品数量;留空则跳过
            singleItemCountByModel: {
                // '套装A': '',
            },
            packMode: '', // 包装模式,例如 不是独立包装;留空则跳过
            currency: 'CNY', // 建议零售价币种;留空则跳过
            suggestPrice: '199', // 建议零售价;留空则跳过
            suggestPriceByModel: {
                // '套装A': '',
            },
            packageItemCount: '', // 包装清单单项数量;留空则跳过
            packageList: [], // 简单模板包装清单;单 SKU 或不分套装时可直接写 ['托特包', '帽子']
            packageListByModel: {
                // '套装A': [],
            },
            packageNameOptions: [], // 包装清单候选项占位;当前脚本未用,留作总模板配置
            cargoNo: '', // 货号;为空时默认回退到输入型号
            customsCode: '', // 海关编码占位;当前脚本未用,留作总模板配置
            barcode: '', // 条码占位;当前脚本未用,留作总模板配置
            remark: '' // SKU 备注占位;当前脚本未用,留作总模板配置
        },
        // 5. 图片上传规则
        imageUpload: { // 图片上传总配置:单入口、多入口、颜色/型号都从这里配
            carousel: {
                mode: 'custom', // 轮播图排序模式:sequential=按原顺序;custom=按下面 customOrderTop10 自定义
                customOrderTop10: [ // 自定义轮播图前10张顺序;写法示例:1st=第1张,6st=第6张,10st=第10张;留空跳过
                    '6', // 图1
                    '1', // 图2
                    '3', // 图3
                    '4', // 图4
                    '2', // 图5
                    '5', // 图6
                    '7', // 图7
                    '', // 图8
                    '', // 图9
                    '' // 图10
                ]
            },
            preview: {
                singleEntryImage: { mode: 'fromEnd', index: 1 }, // 单个预览图入口时用哪张:fromEnd=倒数,fromStart=正数;index 从 1 开始
                multiEntryFallbackByOrder: [ // 多个入口但没有命中规则时,按行顺序兜底
                    { mode: 'fromEnd', index: 1 },
                    { mode: 'fromEnd', index: 2 },
                    { mode: 'fromEnd', index: 3 }
                ],
                rules: [ // 多入口匹配规则:可按型号、颜色或任何行里能看到的文字匹配
                    { matchTexts: ['套装A'], image: { mode: 'fromEnd', index: 1 } },
                    { matchTexts: ['套装B'], image: { mode: 'fromEnd', index: 2 } },
                    { matchTexts: ['套装C'], image: { mode: 'fromEnd', index: 3 } },
                    { matchTexts: ['黑色'], image: { mode: 'fromEnd', index: 1 } },
                    { matchTexts: ['白色'], image: { mode: 'fromEnd', index: 2 } },
                    { matchTexts: ['红色'], image: { mode: 'fromEnd', index: 3 } },
                    { matchTexts: [''], image: { mode: 'fromEnd', index: 1 } } // 留空规则自动跳过
                ]
            }
        }
    };

    const STATE = {
        modelText: '', // 输入框里拆出来的型号
        productNameText: '' // 输入框里拆出来的商品名称
    };

    const EDITABLE_CONFIG = { // 统一可改配置区
        UI: { // 悬浮面板 UI 尺寸
            panelLeft: 10, // 面板距左侧
            panelTop: 10, // 面板距顶部
            panelWidth: 88, // 面板宽度
            panelPadding: '8px 8px 9px', // 面板内边距
            dragbarWidth: 30, // 拖拽条宽度
            dragbarHeight: 4, // 拖拽条高度
            inputHeight: 25, // 输入框高度
            inputPadding: '5px 8px', // 输入框内边距
            buttonPadding: '8px 8px', // 按钮内边距
            borderRadius: 15, // 面板圆角
            controlRadius: 8 // 输入框/按钮圆角
        },
        FEATURES: { // 工作流总开关:true=开启,false=跳过
            selectCategory: true, // 商品分类选择
            randomWaitBeforeCategoryNext: true, // 分类选完后,点下一步前随机等待
            uploadPrepare: true, // 预授权图片目录
            fillAttributes: true, // 商品属性下拉
            fillProvince: true, // 货源省份
            fillParentSpecs: true, // 父规格
            fillProductName: true, // 商品名称
            waitSpecGeneration: true, // 等待规格生成
            fillSizeChart: true, // 尺码表
            fillDimensions: true, // 体积重量
            fillSensitiveAttr: true, // 敏感属性
            clickBatchFill: true, // 批量填写按钮
            fillSkuBlock: true, // SKU 填写
            complianceAgreement: true, // 勾选合规声明
            extraSkuFill: true, // 附加 SKU 小脚本
            imageUpload: true, // 图片上传脚本
            randomWaitBeforeCreate: true, // 点创建前随机等待
            clickCreate: true, // 点击创建
            continueCreate: true // 点击继续新建商品
        },
        T: { // 主流程时间常量(毫秒)
            MICRO: 80, // 极短等待
            SHORT: 180, // 短等待
            MED: 300, // 中等待
            SPEC: 1800 // 规格生成等待
        },
        CATEGORY_TIMING: { // 商品分类页等待
            afterCategoryClickMs: 80, // 点完推荐分类后的极短缓冲
            nextPagePollMs: 120, // 点下一步后轮询新页面的步长
            nextPageStableMs: 500 // 新页面出现后额外稳定多久再继续
        },
        RANDOM_WAIT_TIMING: { // 随机等待配置(毫秒)
            categoryNextMinMs: 500, // 分类选完后,点下一步前随机等待最小值
            categoryNextMaxMs: 10000, // 分类选完后,点下一步前随机等待最大值
            createMinMs: 1000, // 点创建前随机等待最小值
            createMaxMs: 30000 // 点创建前随机等待最大值
        },
        SIZE_CHART_TIMING: { // 尺码表等待
            openDialogMs: 240, // 打开尺码表弹层后的等待
            checkboxMs: 80, // 勾选基础选项后的等待
            rangeCheckboxMs: 80, // 勾选范围区间后的等待
            inputMs: 80, // 每个尺码输入框填写后的等待
            closeDialogMs: 400, // 确认后等待弹层关闭
            pollMs: 80 // 尺码表内部轮询步长
        },
        SKU_TIMING: { // 规格 / 体积重量 / SKU / 包装清单等待
            specPollMs: 120, // 子规格新增输入框轮询步长
            dimensionRowMs: 60, // 体积重量逐行填写后的等待
            dimensionFallbackMs: 100, // 体积重量兜底填写后的等待
            packageAddMs: 260, // 点包装清单“添加”后的等待
            packageConfirmMs: 260, // 包装清单选择后点确认的等待
            packagePollMs: 100, // 包装清单新增项轮询步长
            packageCountMs: 45, // 包装清单数量输入后的等待
            rowDeclareMs: 45, // SKU行申报价填写后的等待
            rowSkuClassMs: 70, // SKU分类选择后的等待
            rowCountMs: 45, // 单品数量填写后的等待
            rowPackModeMs: 70, // 包装模式选择后的等待
            rowSuggestMs: 70, // 建议零售价填写后的等待
            rowCurrencyMs: 45, // 建议零售价币种选择后的等待
            cargoMs: 100, // 货号填写后的等待
            sensitiveAfterMs: 200 // 敏感属性选择完成后的等待
        },
        IMAGE_TIMING: { // 图片脚本等待参数(毫秒)
            listStableMs: 1000, // “在列表中查看”稳定出现多久后再点
            previewStableMs: 1200, // 预览图比轮播图额外更稳的缓冲时间
            pollStepMs: 40, // 轮询步长
            stableStepMs: 60, // 稳定性检测步长
            confirmStableMs: 420, // “确认”按钮稳定出现多久后再点
            confirmPostMs: 60, // 点完“确认”后额外收尾等待
            genericTimeoutMs: 10000, // 通用等待超时
            dialogTimeoutMs: 8000, // 素材中心弹层等待超时
            shortPollMs: 120, // 慢轮询步长
            fastPollMs: 40, // 快轮询步长
            listReadyTimeoutMs: 5000, // 上传列表就绪超时
            outerUploadInputTimeoutMs: 1000, // 外包装上传输入框超时
            retryOpenMs: 240, // 素材中心首次没打开时的重试等待
            cardToggleMs: 240, // 取消已选素材卡片后的等待
            cardSelectMs: 180, // 重新选择素材卡片后的等待
            uploadInputPollMs: 100 // 上传 input 轮询步长
        },
        SKU_FILL: { // 附加 SKU 自动填写脚本默认值
            declaredPrice: '27', // 申报价格
            retailPrice: '159', // 建议零售价
            retailCurrency: 'CNY', // 建议零售价币种
            skuClass: '单品', // SKU 分类
            maxEdge: '31', // 最长边
            midEdge: '18', // 次长边
            minEdge: '1', // 最短边
            weight: '120' // 重量
        },
        SKU_FILL_TIMING: { // 附加 SKU 小脚本等待
            selectOpenMs: 250, // 下拉选择器展开后的等待
            selectDoneMs: 100, // 选中下拉项后的等待
            sensitiveSwitchMs: 350 // 小脚本里敏感属性开关切换后的等待
        }
    };

    /* ─── 时间常量 ──────────────────────────────────────────────────── */
    const T = { ...(EDITABLE_CONFIG.T || {}) };
    const FEATURES = { ...(EDITABLE_CONFIG.FEATURES || {}) };
    const CATEGORY_TIMING = { ...(EDITABLE_CONFIG.CATEGORY_TIMING || {}) };
    const RANDOM_WAIT_TIMING = { ...(EDITABLE_CONFIG.RANDOM_WAIT_TIMING || {}) };
    const SIZE_CHART_TIMING = { ...(EDITABLE_CONFIG.SIZE_CHART_TIMING || {}) };
    const SKU_TIMING = { ...(EDITABLE_CONFIG.SKU_TIMING || {}) };

    const wait = (ms) => new Promise((r) => setTimeout(r, ms));
    const clean = (t) => (t || '').replace(/\s+/g, ' ').trim();

    function isBlankValue(value) {
        if (value == null) return true;
        if (typeof value === 'string') return clean(value) === '';
        if (Array.isArray(value)) return value.length === 0 || value.every(isBlankValue);
        return false;
    }

    function isFeatureOn(key) {
        return FEATURES[key] !== false;
    }

    function randomIntBetween(min, max) {
        const safeMin = Math.max(0, Number(min) || 0);
        const safeMax = Math.max(safeMin, Number(max) || safeMin);
        return Math.floor(Math.random() * (safeMax - safeMin + 1)) + safeMin;
    }

    async function waitRandomBetween(min, max) {
        const ms = randomIntBetween(min, max);
        await wait(ms);
        return ms;
    }

    function activeItems(list) {
        return (Array.isArray(list) ? list : []).filter((item) => {
            if (!item || typeof item !== 'object') return false;
            return Object.values(item).some((v) => !isBlankValue(v));
        });
    }

    function normalizeSpecValues(specConfig, fallbackValue = '') {
        if (!specConfig || isBlankValue(specConfig.typeValueId)) return [];
        const values = [];
        if (Array.isArray(specConfig.specValues)) {
            values.push(...specConfig.specValues.filter((v) => !isBlankValue(v)).map((v) => clean(v)));
        }
        if (values.length) return values;
        if (!isBlankValue(specConfig.specValue)) return [clean(specConfig.specValue)];
        if (!isBlankValue(fallbackValue)) return [clean(fallbackValue)];
        return [];
    }

    function getParentSpecTypeIds() {
        return [CONFIG.parentSpec1?.typeValueId, CONFIG.parentSpec2?.typeValueId]
            .filter((v) => !isBlankValue(v))
            .map((v) => String(v));
    }

    function getParentSpec2ModelNames() {
        const values = normalizeSpecValues(CONFIG.parentSpec2, STATE.modelText);
        return values.length ? values : (isBlankValue(STATE.modelText) ? [] : [clean(STATE.modelText)]);
    }

    function getDimensionConfig() {
        return CONFIG.dimensions || {};
    }

    function getDimensionDefaultValues() {
        const dimensions = getDimensionConfig();
        if (Array.isArray(dimensions)) {
            const ordered = ['最长边', '次长边', '最短边', 'g'].map((marker) =>
                dimensions.find((item) => clean(item?.marker) === marker)?.value || ''
            );
            return ordered;
        }
        return Array.isArray(dimensions.default) ? dimensions.default : [];
    }

    function getDimensionValuesByModel(modelName = '') {
        const dimensions = getDimensionConfig();
        if (Array.isArray(dimensions)) {
            return getDimensionDefaultValues();
        }
        const direct = dimensions.byModel?.[modelName];
        if (Array.isArray(direct) && direct.some((v) => !isBlankValue(v))) return direct;
        return getDimensionDefaultValues();
    }

    function hasAdvancedSkuConfig() {
        const sku = CONFIG.sku || {};
        return !isBlankValue(sku.skuClassLabel)
            || Object.keys(sku.declarePriceByModel || {}).length > 0
            || Object.keys(sku.singleItemCountByModel || {}).length > 0
            || Object.keys(sku.packageListByModel || {}).length > 0
            || Object.keys(sku.suggestPriceByModel || {}).length > 0
            || !isBlankValue(sku.packMode);
    }

    function getEditableConfig() {
        return EDITABLE_CONFIG;
    }

    function getUiConfig() {
        return EDITABLE_CONFIG.UI || {};
    }

    window.__TEMU_GET_UNIFIED_CONFIG__ = getEditableConfig;
    window.__TEMU_GET_MAIN_SCRIPT_CONFIG__ = () => CONFIG;

    function parseInputLine(raw) {
        const text = String(raw || '').replace(/\u00A0/g, ' ').trim();
        if (!text) {
            return null;
        }
        const m = text.match(/^(\S+)\s+(.+)$/);
        if (!m) {
            return null;
        }
        return {
            model: clean(m[1]),
            name: clean(m[2])
        };
    }

    function isCategoryCreatePage() {
        return location.pathname.includes('/goods/create/category');
    }

    async function selectConfiguredCategory() {
        const keyword = clean(CONFIG.categorySelection?.keyword || '');
        const nextText = clean(CONFIG.categorySelection?.nextButtonText || '下一步');
        if (!isCategoryCreatePage() || isBlankValue(keyword)) return false;

        const categoryEl = await waitForStableElement(() => {
            return [...document.querySelectorAll('div, span, p, a, button, label')]
                .filter((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return text === keyword
                        && style.display !== 'none'
                        && style.visibility !== 'hidden'
                        && rect.width > 0
                        && rect.height > 0;
                })
                .sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top)[0]
                || null;
        }, 15000, 120, 360);
        if (!categoryEl) throw new Error(`未找到商品分类:${keyword}`);

        const categoryTarget = categoryEl.closest('button, a, label') || categoryEl;
        if (typeof categoryTarget.click === 'function') categoryTarget.click();
        else click(categoryTarget);
        await wait(T.MED);

        const nextBtn = await waitForStableElement(() => {
            return [...document.querySelectorAll('button, [role="button"], a, span, div')]
                .filter((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return text === nextText
                        && style.display !== 'none'
                        && style.visibility !== 'hidden'
                        && rect.width > 0
                        && rect.height > 0;
                })
                .map((el) => el.closest('button, a') || el)
                .at(0) || null;
        }, 15000, 120, 360);
        if (!nextBtn) throw new Error(`未找到按钮:${nextText}`);

        if (typeof nextBtn.click === 'function') nextBtn.click();
        else click(nextBtn);
        await wait(T.MED);
        return true;
    }

    function readPanelData() {
        const input = document.getElementById('temu-onekey-source');
        const parsed = parseInputLine(input?.value || '');
        if (!parsed) {
            throw new Error('请输入“型号 商品名称”,中间至少一个空格或 Tab');
        }
        STATE.modelText = parsed.model;
        STATE.productNameText = parsed.name;
        if (!normalizeSpecValues(CONFIG.parentSpec2).length) {
            CONFIG.parentSpec2.specValue = parsed.model;
        }
        if (isBlankValue(CONFIG.sku.cargoNo)) {
            CONFIG.sku.cargoNo = parsed.model;
        }
        return parsed;
    }

    async function selectConfiguredCategory2() {
        const keyword = clean(CONFIG.categorySelection?.keyword || '');
        const nextText = clean(CONFIG.categorySelection?.nextButtonText || '下一步');
        if (!isCategoryCreatePage() || isBlankValue(keyword)) return false;

        const categoryEl = await waitForStableElement(() => {
            const isVisible = (el) => {
                const rect = el.getBoundingClientRect();
                const style = window.getComputedStyle(el);
                return style.display !== 'none'
                    && style.visibility !== 'hidden'
                    && rect.width > 0
                    && rect.height > 0;
            };

            const title = [...document.querySelectorAll('div, span, p, label')]
                .find((el) => isVisible(el) && clean(el.innerText || el.textContent || '').startsWith('常用推荐'));

            const scopes = [];
            if (title?.parentElement) scopes.push(title.parentElement);
            if (title?.parentElement?.parentElement) scopes.push(title.parentElement.parentElement);
            scopes.push(document);

            for (const scope of scopes) {
                const hit = [...scope.querySelectorAll('div, span, p, a, button, label')]
                    .filter((el) => isVisible(el) && clean(el.innerText || el.textContent || '') === keyword)
                    .sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top)[0];
                if (hit) return hit;
            }
            return null;
        }, 15000, 120, 360);
        if (!categoryEl) throw new Error(`未找到商品分类:${keyword}`);

        const categoryTarget = categoryEl.closest('button, a, label') || categoryEl;
        if (typeof categoryTarget.click === 'function') categoryTarget.click();
        else click(categoryTarget);
        await wait(T.MED);

        const nextBtn = await waitForStableElement(() => {
            return [...document.querySelectorAll('button, [role="button"], a, span, div')]
                .filter((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return text === nextText
                        && style.display !== 'none'
                        && style.visibility !== 'hidden'
                        && rect.width > 0
                        && rect.height > 0;
                })
                .map((el) => el.closest('button, a') || el)
                .at(0) || null;
        }, 15000, 120, 360);
        if (!nextBtn) throw new Error(`未找到按钮:${nextText}`);

        if (typeof nextBtn.click === 'function') nextBtn.click();
        else click(nextBtn);

        await waitForStableElement(() => !isCategoryCreatePage() ? document.body : null, 20000, 120, 360);
        return true;
    }

    /* ════════════════════════════════════════════════════════════════
       原点击/输入工具
    ════════════════════════════════════════════════════════════════ */

    function isVisibleNodeForCategory(el) {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const style = window.getComputedStyle(el);
        return style.display !== 'none'
            && style.visibility !== 'hidden'
            && rect.width > 0
            && rect.height > 0;
    }

    function hasPostCategoryReadyMarkers() {
        const bodyText = clean(document.body?.innerText || '');
        if (!bodyText) return false;
        return [
            '商品名称',
            '商品属性',
            '尺码表',
            '商品素材图',
            '货号',
            '敏感属性'
        ].some((text) => bodyText.includes(text));
    }

    async function selectConfiguredCategory3() {
        const keyword = clean(CONFIG.categorySelection?.keyword || '');
        const nextText = clean(CONFIG.categorySelection?.nextButtonText || '下一步');
        if (!isCategoryCreatePage() || isBlankValue(keyword)) return false;

        const selectedBlock = await waitForStableElement(() => {
            return [...document.querySelectorAll('div, span, p, label')]
                .filter((el) => isVisibleNodeForCategory(el))
                .find((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    return text.includes('已选分类') && text.includes(keyword);
                }) || null;
        }, 1200, 80, 180);

        if (!selectedBlock) {
            const categoryEl = await waitForStableElement(() => {
                const title = [...document.querySelectorAll('div, span, p, label')]
                    .find((el) => isVisibleNodeForCategory(el) && clean(el.innerText || el.textContent || '').includes('常用推荐'));

                const scopes = [];
                if (title?.parentElement) scopes.push(title.parentElement);
                if (title?.parentElement?.parentElement) scopes.push(title.parentElement.parentElement);
                scopes.push(document);

                for (const scope of scopes) {
                    const hits = [...scope.querySelectorAll('div, span, p, a, button, label')]
                        .filter((el) => {
                            if (!isVisibleNodeForCategory(el)) return false;
                            const text = clean(el.innerText || el.textContent || '');
                            if (!text || text.includes('已选分类')) return false;
                            return text === keyword || text.includes(keyword);
                        })
                        .sort((a, b) => {
                            const at = a.getBoundingClientRect();
                            const bt = b.getBoundingClientRect();
                            return (at.top - bt.top) || (at.width - bt.width);
                        });
                    if (hits.length) return hits[0];
                }
                return null;
            }, 15000, 100, 260);
            if (!categoryEl) throw new Error(`未找到商品分类:${keyword}`);

            const categoryTarget = categoryEl.closest('button, a, label') || categoryEl;
            if (typeof categoryTarget.click === 'function') categoryTarget.click();
            else click(categoryTarget);

            await waitForStableElement(() => {
                return [...document.querySelectorAll('div, span, p, label')]
                    .filter((el) => isVisibleNodeForCategory(el))
                    .find((el) => {
                        const text = clean(el.innerText || el.textContent || '');
                        return text.includes('已选分类') && text.includes(keyword);
                    }) || null;
            }, 10000, 100, 220);
        }

        const nextBtn = await waitForStableElement(() => {
            return [...document.querySelectorAll('button, [role="button"], a, span, div')]
                .filter((el) => {
                    if (!isVisibleNodeForCategory(el)) return false;
                    const text = clean(el.innerText || el.textContent || '');
                    return text === nextText || text.includes(nextText);
                })
                .map((el) => el.closest('button, a') || el)
                .at(0) || null;
        }, 15000, 100, 260);
        if (!nextBtn) throw new Error(`未找到按钮:${nextText}`);

        const beforeUrl = location.href;
        if (typeof nextBtn.click === 'function') nextBtn.click();
        else click(nextBtn);

        const readyBody = await waitForStableElement(() => {
            const urlChanged = location.href !== beforeUrl && !isCategoryCreatePage();
            if (urlChanged && hasPostCategoryReadyMarkers()) return document.body;
            if (!isCategoryCreatePage() && hasPostCategoryReadyMarkers()) return document.body;
            return null;
        }, 30000, 120, 500);
        if (!readyBody) {
            throw new Error('点击下一步后,未等到商品发布页面加载完成');
        }
        return true;
    }

    function isPublishFormPageReady2() {
        const path = location.pathname || '';
        if (path.includes('/goods/create/category')) return false;
        const categoryPanel = document.querySelector('[class*="product-category-select_panelContainer__"]')
            || document.querySelector('[class*="product-category-select_productPublishContainer__"]');
        if (categoryPanel && isVisibleNodeForCategory(categoryPanel)) return false;
        const productNameControl = typeof findProductNameInput === 'function' ? findProductNameInput() : null;
        if (productNameControl) return true;
        if (path.includes('/goods/edit') || path.includes('/goods/add')) {
            const bodyText = clean(document.body?.innerText || '');
            return bodyText.includes('商品名称')
                || bodyText.includes('商品属性')
                || bodyText.includes('尺码表')
                || bodyText.includes('商品素材图')
                || bodyText.includes('货号');
        }
        const bodyText = clean(document.body?.innerText || '');
        return bodyText.includes('商品名称')
            || bodyText.includes('商品属性')
            || bodyText.includes('尺码表')
            || bodyText.includes('商品素材图')
            || bodyText.includes('货号');
    }

    async function selectConfiguredCategory4() {
        const keyword = clean(CONFIG.categorySelection?.keyword || '');
        const nextText = clean(CONFIG.categorySelection?.nextButtonText || '下一步');
        const preferredTexts = (Array.isArray(CONFIG.categorySelection?.recommendedTexts) ? CONFIG.categorySelection.recommendedTexts : [])
            .map((text) => clean(text))
            .filter(Boolean);
        if (!isCategoryCreatePage() || (!keyword && !preferredTexts.length)) return false;

        const publishRoot = document.querySelector('[class*="product-category-select_productPublishContainer__"]')
            || document.querySelector('[class*="product-category-select_productCreateContainer__"]')
            || document;
        const panelRoot = publishRoot.querySelector('[class*="product-category-select_panelContainer__"]') || publishRoot;

        const isSelected = () => {
            const text = clean(panelRoot.innerText || '');
            return text.includes('已选分类') && ((keyword && text.includes(keyword)) || preferredTexts.some((item) => text.includes(item)));
        };

        if (!isSelected()) {
            const categoryEl = await waitForStableElement(() => {
                const candidates = [...panelRoot.querySelectorAll('div, span, p, a, button, label')]
                    .filter((el) => isVisibleNodeForCategory(el))
                    .filter((el) => {
                        const text = clean(el.innerText || el.textContent || '');
                        if (!text || text.includes('常用推荐') || text.includes('已选分类') || text.includes('全部分类')) return false;
                        const rect = el.getBoundingClientRect();
                        if (rect.y < 340 || rect.y > 430 || rect.width > 280) return false;
                        return preferredTexts.includes(text)
                            || preferredTexts.some((item) => text.includes(item))
                            || (keyword && text.includes(keyword));
                    })
                    .sort((a, b) => {
                        const at = a.getBoundingClientRect();
                        const bt = b.getBoundingClientRect();
                        return (at.y - bt.y) || (at.x - bt.x) || (at.width - bt.width);
                    });
                return candidates[0] || null;
            }, 15000, 100, 260);
            if (!categoryEl) throw new Error(`未找到商品分类:${keyword || preferredTexts[0] || ''}`);

            const categoryTarget = categoryEl.closest('button, a, label') || categoryEl;
            if (typeof categoryTarget.click === 'function') categoryTarget.click();
            else click(categoryTarget);
            await wait(CATEGORY_TIMING.afterCategoryClickMs ?? T.MICRO);
        }

        const nextBtn = await waitForStableElement(() => {
            return [...publishRoot.querySelectorAll('button, [role="button"], a, span, div')]
                .filter((el) => {
                    if (!isVisibleNodeForCategory(el)) return false;
                    const text = clean(el.innerText || el.textContent || '');
                    return text === nextText || text.includes(nextText);
                })
                .map((el) => el.closest('button, a') || el)
                .at(-1) || null;
        }, 15000, 100, 260);
        if (!nextBtn) throw new Error(`未找到按钮:${nextText}`);

        if (isFeatureOn('randomWaitBeforeCategoryNext')) {
            await waitRandomBetween(
                RANDOM_WAIT_TIMING.categoryNextMinMs,
                RANDOM_WAIT_TIMING.categoryNextMaxMs
            );
        }

        const beforeUrl = location.href;
        if (typeof nextBtn.click === 'function') nextBtn.click();
        else click(nextBtn);

        const readyBody = await waitForStableElement(() => {
            const urlChanged = location.href !== beforeUrl;
            const categoryPanelStillVisible = document.querySelector('[class*="product-category-select_panelContainer__"]')
                || document.querySelector('[class*="product-category-select_productPublishContainer__"]');
            if (categoryPanelStillVisible && isVisibleNodeForCategory(categoryPanelStillVisible)) return null;
            if (urlChanged && isPublishFormPageReady2()) return document.body;
            if (isPublishFormPageReady2()) return document.body;
            return null;
        }, 30000, CATEGORY_TIMING.nextPagePollMs ?? T.SHORT, CATEGORY_TIMING.nextPageStableMs ?? T.MED);
        if (!readyBody) {
            throw new Error('点击下一步后,未等到商品发布页面加载完成');
        }
        return true;
    }

    async function superClick(el) {
        if (!el) return;
        el.scrollIntoView({ block: 'center' });
        await wait(0);
        const rect = el.getBoundingClientRect();
        const clientX = rect.left + rect.width / 2;
        const clientY = rect.top + rect.height / 2;
        [
            new PointerEvent('pointerdown', { bubbles: true, cancelable: true, pointerType: 'mouse', clientX, clientY }),
            new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window, clientX, clientY }),
            new PointerEvent('pointerup', { bubbles: true, cancelable: true, pointerType: 'mouse', clientX, clientY }),
            new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window, clientX, clientY }),
            new MouseEvent('click', { bubbles: true, cancelable: true, view: window, clientX, clientY })
        ].forEach((ev) => el.dispatchEvent(ev));
    }

    function fastClick(el) {
        if (!el) return;
        const opts = { bubbles: true, view: window };
        el.dispatchEvent(new MouseEvent('mousedown', opts));
        el.dispatchEvent(new MouseEvent('click', opts));
        el.dispatchEvent(new MouseEvent('mouseup', opts));
    }

    function setReactValue(el, val) {
        if (!el) return;
        const prev = el.value;
        el.value = val;
        const t = el._valueTracker;
        if (t) t.setValue(prev);
        el.dispatchEvent(new Event('input', { bubbles: true }));
        el.dispatchEvent(new Event('change', { bubbles: true }));
    }

    function setVal(el, val) {
        if (!el) return;
        const prev = el.value;
        el.value = val;
        const tracker = el._valueTracker;
        if (tracker) tracker.setValue(prev);
        el.dispatchEvent(new Event('input', { bubbles: true }));
        el.dispatchEvent(new Event('change', { bubbles: true }));
    }

    function click(el) {
        if (!el) return;
        el.scrollIntoView({ block: 'center' });
        const r = el.getBoundingClientRect();
        const cx = r.left + r.width / 2;
        const cy = r.top + r.height / 2;
        const s = { bubbles: true, cancelable: true, view: window, clientX: cx, clientY: cy };
        ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'].forEach((type) =>
            el.dispatchEvent(type.startsWith('pointer')
                ? new PointerEvent(type, { ...s, pointerType: 'mouse' })
                : new MouseEvent(type, s))
        );
    }

    async function pickOptionExact(val) {
        await wait(0);
        const popups = Array.from(document.querySelectorAll(
            '.beast-select-dropdown:not([style*="display: none"]), .ST_popupWrapper_5-120-1, [class*="ST_popup"]'
        )).filter((el) => window.getComputedStyle(el).display !== 'none');
        const lastPopup = popups[popups.length - 1] || document;
        const opts = Array.from(lastPopup.querySelectorAll(
            '.ST_item_5-120-1, .beast-select-item-option-content, [role="option"], li'
        ));
        const hit = opts.find((o) => o.innerText.trim() === val)
            || opts.find((o) => o.innerText.trim().includes(val));
        if (hit) {
            fastClick(hit);
            return true;
        }
        return false;
    }

    function findItemV26(labelText) {
        for (const el of document.querySelectorAll('.Form_itemLabelContent_5-120-1')) {
            const txt = el.innerText.trim();
            if (txt === labelText || txt.startsWith(labelText)) {
                return el.closest('.Form_item_5-120-1');
            }
        }
        return null;
    }

    async function selectDropdown(labelText, val) {
        if (isBlankValue(labelText) || isBlankValue(val)) return;
        if (Array.isArray(val)) {
            for (const one of val.filter((x) => !isBlankValue(x))) {
                await selectDropdown(labelText, one);
                await wait(T.SHORT);
            }
            return;
        }
        const item = findItemV26(labelText);
        if (!item) return;
        const trigger = item.querySelector('.ST_outerWrapper_5-120-1, .beast-select-selector, input');
        document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 27, bubbles: true }));
        await wait(0);
        fastClick(trigger);
        const input = item.querySelector('input');
        if (input) {
            setReactValue(input, val);
            await wait(0);
        }
        await pickOptionExact(val);
    }

    async function selectProvince() {
        if (isBlankValue(CONFIG.province)) return;
        const provInp = document.querySelector('input[placeholder="请选择省份"]');
        if (!provInp) return;
        fastClick(provInp.closest('.ST_outerWrapper_5-120-1') || provInp);
        setReactValue(provInp, CONFIG.province);
        await pickOptionExact(CONFIG.province);
    }

    async function clickAddSpec2() {
        const target = Array.from(document.querySelectorAll('div, span, button'))
            .filter((el) => (el.innerText || '').includes('添加父规格 2') && el.offsetWidth > 0)
            .reverse()[0];
        if (target) {
            await superClick(target);
        } else {
            const fallback = Array.from(document.querySelectorAll('button, div, span'))
                .find((el) => el.innerText.trim() === '添加父规格' && el.offsetWidth > 0);
            if (fallback) await superClick(fallback);
        }
    }

    function getCtrl(el) {
        if (!el) return null;
        const key = Object.keys(el).find(
            (k) => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')
        );
        let node = key ? el[key] : null;
        let d = 0;
        while (node && d < 20) {
            const p = node.memoizedProps || {};
            if (Array.isArray(p.options) && typeof p.onChange === 'function') return p;
            node = node.return;
            d++;
        }
        return null;
    }

    function pickById(ctrl, valueId) {
        if (!ctrl) return false;
        const opt = ctrl.options.find((o) => String(o.value) === String(valueId));
        if (!opt) return false;
        ctrl.onChange(opt.value, opt);
        return true;
    }

    function pickByLabel(ctrl, label) {
        if (!ctrl) return false;
        const opt = ctrl.options.find((o) => clean(o.label) === label)
            || ctrl.options.find((o) => clean(o.label).includes(label));
        if (!opt) return false;
        ctrl.onChange(opt.value, opt);
        return true;
    }

    function findFormItem(labelText) {
        for (const el of document.querySelectorAll('[class*="Form_itemLabelContent"]')) {
            const t = clean(el.innerText);
            if (t === labelText || t.startsWith(labelText)) {
                return el.closest('[class*="Form_item_"]');
            }
        }
        return null;
    }

    function findInput(labelText, { last = true } = {}) {
        const item = findFormItem(labelText);
        if (!item) return null;
        const inputs = [...item.querySelectorAll(
            'input:not([readonly]):not([type="radio"]):not([type="checkbox"]):not([type="file"]), textarea'
        )];
        return inputs.length ? (last ? inputs[inputs.length - 1] : inputs[0]) : null;
    }

    function findParentSpecTypeInputs() {
        return [...document.querySelectorAll('input')]
            .filter((el) => {
                if (!el.readOnly) return false;
                const ctrl = getCtrl(el);
                const ids = getParentSpecTypeIds();
                return ctrl?.options?.some((o) => ids.includes(String(o.value)));
            });
    }

    function findSpecBlockRoot(specTypeEl) {
        let root = specTypeEl.closest(
            '[class*="Form_item_"], [class*="specRow"], [class*="SpecRow"], tr, [class*="row_"], [class*="Row_"]'
        ) || specTypeEl.parentElement;

        let node = root;
        let depth = 0;
        while (node && depth < 8) {
            const text = clean(node.innerText || '');
            if (text.includes('继续添加子规格') || text.includes('添加子规格')) {
                root = node;
            }
            node = node.parentElement;
            depth++;
        }
        return root || specTypeEl.parentElement;
    }

    function listEditableSpecInputs(root) {
        return [...(root || document).querySelectorAll('input, textarea')]
            .filter((el) => !el.readOnly && !el.disabled)
            .filter((el) => !['file', 'radio', 'checkbox', 'hidden'].includes((el.type || '').toLowerCase()));
    }

    function findSpecificSpecTableRoot(labelText) {
        const normalize = (t) => clean(t).replace(/^\+\s*/, '');
        const tables = [...document.querySelectorAll('table.TB_tableWrapper_5-120-1')];
        return tables.find((table) => {
            const text = clean(table.innerText || '');
            const hasContinue = [...table.querySelectorAll('button, div, span, a')]
                .some((el) => normalize(el.innerText) === '继续添加子规格');
            return hasContinue && text.includes(`*${labelText}`);
        }) || null;
    }

    async function fillOneParentSpec(specTypeEl, typeValueId, specTextValue) {
        const ctrl = getCtrl(specTypeEl);
        if (!pickById(ctrl, typeValueId)) {
            pickByLabel(ctrl, String(typeValueId));
        }
        await wait(T.MED);

        const container = specTypeEl.closest(
            '[class*="specRow"], [class*="SpecRow"], tr, [class*="row_"], [class*="Row_"]'
        ) || specTypeEl.parentElement?.parentElement;

        let valueEl = container
            ? container.querySelector('input:not([readonly]):not([disabled]):not([type="file"]):not([type="radio"]):not([type="checkbox"])')
            : null;

        if (!valueEl) {
            const all = [...document.querySelectorAll('input')];
            const selfIdx = all.indexOf(specTypeEl);
            valueEl = all.slice(selfIdx + 1, selfIdx + 6)
                .find((el) => !el.readOnly && !el.disabled && !['file', 'radio', 'checkbox'].includes(el.type));
        }

        if (valueEl) {
            valueEl.focus();
            setVal(valueEl, specTextValue);
            await wait(T.SHORT);
        } else {
            console.warn('[Temu助手] 找不到规格值输入框');
        }
        return { blockRoot: findSpecBlockRoot(specTypeEl), valueEl };
    }

    async function clickContinueAddChildSpec(root = document) {
        const normalize = (t) => clean(t).replace(/^\+\s*/, '');
        const btn = [...root.querySelectorAll('button')]
            .find((el) => normalize(el.innerText) === '继续添加子规格')
            || [...root.querySelectorAll('div, span, a')]
                .find((el) => normalize(el.innerText) === '继续添加子规格');
        if (!btn) throw new Error('未找到“继续添加子规格”按钮');
        click(btn.closest('button') || btn);
        await wait(T.MED);
    }

    async function fillParentSpecs() {
        const spec1Values = normalizeSpecValues(CONFIG.parentSpec1);
        const spec2Values = normalizeSpecValues(CONFIG.parentSpec2, STATE.modelText);
        const hasParent1 = !isBlankValue(CONFIG.parentSpec1?.typeValueId) && spec1Values.length > 0;
        const hasParent2 = !isBlankValue(CONFIG.parentSpec2?.typeValueId) && spec2Values.length > 0;
        if (!hasParent1 && !hasParent2) return;

        const appendChildSpecValues = async (values, root) => {
            let inputs = listEditableSpecInputs(root);
            for (let i = 1; i < values.length; i++) {
                const beforeCount = inputs.length;
                await clickContinueAddChildSpec(root);
                const start = Date.now();
                while (true) {
                    inputs = listEditableSpecInputs(root);
                    if (inputs.length > beforeCount) break;
                    if (Date.now() - start > 5000) throw new Error('新增子规格输入框超时');
                    await wait(SKU_TIMING.specPollMs ?? T.SHORT);
                }

                const input = inputs[beforeCount];
                if (!input) throw new Error(`未找到子规格输入框 ${i + 1}`);
                input.focus();
                setVal(input, values[i]);
                await wait(T.SHORT);
            }
        };

        let specInputs = findParentSpecTypeInputs();
        if (hasParent1 && !specInputs.length) {
            console.warn('[Temu助手] 未找到父规格1');
            return;
        }
        if (hasParent1) {
            const parent1 = await fillOneParentSpec(specInputs[0], CONFIG.parentSpec1.typeValueId, spec1Values[0]);
            const root1 = findSpecificSpecTableRoot('颜色') || parent1.blockRoot;
            if (spec1Values.length > 1) {
                await appendChildSpecValues(spec1Values, root1);
            }
        }

        if (!hasParent2) return;

        await clickAddSpec2();

        specInputs = findParentSpecTypeInputs();
        if (specInputs.length < 2) {
            console.warn('[Temu助手] 未找到父规格2');
            return;
        }
        const parent2 = await fillOneParentSpec(specInputs[1], CONFIG.parentSpec2.typeValueId, spec2Values[0]);
        const root2 = findSpecificSpecTableRoot('型号') || parent2.blockRoot;
        if (spec2Values.length > 1) {
            await appendChildSpecValues(spec2Values, root2);
        }
    }

    function isEditableTextControl(el) {
        if (!el) return false;
        if (el.disabled || el.readOnly) return false;
        if (el.matches('textarea')) return true;
        if (el.matches('input')) {
            const t = (el.type || '').toLowerCase();
            return !['file', 'radio', 'checkbox', 'hidden'].includes(t);
        }
        return false;
    }

    function findProductNameInput() {
        const exactLabelEls = [...document.querySelectorAll('div, span, label, p')]
            .filter((el) => clean(el.innerText) === '商品名称' && el.offsetParent !== null);

        for (const labelEl of exactLabelEls) {
            let cur = labelEl;
            for (let i = 0; i < 6 && cur; i++) {
                const scope = cur.parentElement || cur;
                const controls = [...scope.querySelectorAll('textarea, input')]
                    .filter(isEditableTextControl)
                    .filter((el) => {
                        const text = clean(el.closest('[class*="Form_item_"], div, section, form')?.innerText || '');
                        return !text.includes('英文名称');
                    });
                if (controls.length) return controls[0];
                cur = cur.parentElement;
            }

            let next = labelEl.nextElementSibling;
            let guard = 0;
            while (next && guard < 8) {
                const controls = [...next.querySelectorAll('textarea, input')]
                    .filter(isEditableTextControl)
                    .filter((el) => {
                        const text = clean(el.closest('[class*="Form_item_"], div, section, form')?.innerText || '');
                        return !text.includes('英文名称');
                    });
                if (controls.length) return controls[0];
                next = next.nextElementSibling;
                guard++;
            }
        }

        const candidates = [...document.querySelectorAll('textarea[placeholder="请输入"], input[placeholder="请输入"]')]
            .filter(isEditableTextControl)
            .filter((el) => {
                const areaText = clean(el.closest('[class*="Form_item_"], form, section, div')?.innerText || '');
                return areaText.includes('商品名称') && !areaText.includes('英文名称');
            });

        if (candidates.length) return candidates[0];

        return null;
    }

    async function fillProductName() {
        if (isBlankValue(STATE.productNameText)) return;
        const input = findProductNameInput();
        if (!input) {
            console.warn('[Temu助手] 未找到商品名称输入框');
            return;
        }
        input.focus();
        setVal(input, STATE.productNameText);
        await wait(T.SHORT);
    }

    async function fillDimensions() {
        const dimensions = getDimensionConfig();
        const modelValues = getParentSpec2ModelNames();

        const volumeTable = document.querySelector('.product-sku_skuTableContainer__sX1e0 table.performance-table_performanceTable__dwfgW')
            || document.querySelector('table.performance-table_performanceTable__dwfgW');
        if (!Array.isArray(dimensions) && volumeTable && modelValues.length) {
            const rows = [...volumeTable.querySelectorAll('tbody tr')].filter((row) => row.querySelector('input[placeholder="请输入"]'));
            for (const row of rows) {
                const rowText = clean(row.innerText || '');
                const modelName = modelValues.find((name) => rowText.includes(name)) || '';
                const rowValues = getDimensionValuesByModel(modelName);
                const inputs = [...row.querySelectorAll('input[placeholder="请输入"]')]
                    .filter((el) => !el.disabled && !el.readOnly)
                    .slice(0, 4);
                for (let i = 0; i < Math.min(inputs.length, rowValues.length); i++) {
                    if (isBlankValue(rowValues[i])) continue;
                    inputs[i].focus();
                    setVal(inputs[i], rowValues[i]);
                    await wait(SKU_TIMING.dimensionRowMs ?? T.MICRO);
                }
            }
            return;
        }

        const fallbackItems = Array.isArray(dimensions)
            ? activeItems(dimensions)
            : (dimensions.markers || ['最长边', '次长边', '最短边', 'g']).map((marker, index) => ({
                marker,
                value: getDimensionDefaultValues()[index] || ''
            }));

        for (const { marker, value } of fallbackItems) {
            if (isBlankValue(marker) || isBlankValue(value)) continue;
            const target = [...document.querySelectorAll('input, textarea')]
                .filter((el) => !el.disabled && !el.readOnly && clean(el.placeholder) === '请输入')
                .find((el) => clean(el.closest('div')?.innerText || '').includes(marker));
            if (!target) {
                console.warn(`[Temu助手] 找不到尺寸输入: ${marker}`);
                continue;
            }
            target.focus();
            setVal(target, value);
            await wait(SKU_TIMING.dimensionFallbackMs ?? T.SHORT);
        }
    }

    function modalBody() {
        return document.querySelector('[data-testid="beast-core-modal-body"]');
    }

    async function openSizeChartDialog() {
        const trigger = [...document.querySelectorAll('button, span, div')]
            .find((el) => {
                const txt = clean(el.innerText);
                return txt === '添加尺码表' || txt === '编辑尺码表';
            });
        if (!trigger) throw new Error('未找到“添加尺码表”按钮');

        click(trigger.closest('button') || trigger);
        await wait(SIZE_CHART_TIMING.openDialogMs ?? T.MED);

        const start = Date.now();
        while (!modalBody()) {
            if (Date.now() - start > 5000) throw new Error('尺码表弹层未打开');
            await wait(SIZE_CHART_TIMING.pollMs ?? T.SHORT);
        }
    }

    function getBaseCheckBoxes() {
        const modal = modalBody();
        if (!modal) return [];
        const targets = new Set(activeItems(CONFIG.sizeChart).map((item) => `${clean(item.label)}(cm)`));
        return [...modal.querySelectorAll('label[data-testid="beast-core-checkbox"]')]
            .filter((label) => targets.has(clean(label.innerText)));
    }

    async function waitUntilBaseMetricsReady() {
        const expected = activeItems(CONFIG.sizeChart).length;
        if (!expected) return [];
        const start = Date.now();
        while (true) {
            const boxes = getBaseCheckBoxes();
            if (boxes.length === expected) return boxes;
            if (Date.now() - start > 5000) throw new Error('尺码表基础选项未就绪');
            await wait(SIZE_CHART_TIMING.pollMs ?? T.SHORT);
        }
    }

    async function clickBaseMetrics() {
        const boxes = await waitUntilBaseMetricsReady();
        for (const label of boxes) {
            const checkbox = label.querySelector('input[type="checkbox"]');
            if (checkbox && !checkbox.checked) {
                click(checkbox);
                await wait(SIZE_CHART_TIMING.checkboxMs ?? T.SHORT);
            }
        }
    }

    async function ensureRangeCheckboxes() {
        const modal = modalBody();
        if (!modal) throw new Error('未找到尺码表弹层');
        const labels = activeItems(CONFIG.sizeChart).map((item) => `${clean(item.label)} 范围区间`);
        if (!labels.length) return [];

        const start = Date.now();
        while (true) {
            const text = clean(modal.innerText);
            if (labels.every((label) => text.includes(label))) {
                const rangeBoxes = [...modal.querySelectorAll('input[type="checkbox"]')].slice(-labels.length);
                if (rangeBoxes.length === labels.length) return rangeBoxes;
            }

            if (Date.now() - start > 5000) throw new Error('范围区间复选框未就绪');
            await wait(SIZE_CHART_TIMING.pollMs ?? T.SHORT);
        }
    }

    function getRangeInputs() {
        const modal = modalBody();
        if (!modal) return [];
        return [...modal.querySelectorAll('input[type="text"], textarea')]
            .filter((el) => !el.disabled && !el.readOnly)
            .filter((el) => clean(el.placeholder) === '请输入');
    }

    async function fillSizeChart() {
        const charts = activeItems(CONFIG.sizeChart).filter((item) => !isBlankValue(item.label));
        if (!charts.length) return;

        await openSizeChartDialog();
        await clickBaseMetrics();

        const rangeBoxes = await ensureRangeCheckboxes();
        for (const box of rangeBoxes) {
            if (!box.checked) {
                click(box);
                await wait(SIZE_CHART_TIMING.rangeCheckboxMs ?? T.SHORT);
            }
        }

        const start = Date.now();
        const expectedCount = charts.length * 2;
        while (getRangeInputs().length < expectedCount) {
            if (Date.now() - start > 5000) throw new Error('范围输入框未出现');
            await wait(SIZE_CHART_TIMING.pollMs ?? T.SHORT);
        }

        const inputs = getRangeInputs();
        const values = charts.flatMap((item) => [item.min, item.max]).filter((v) => !isBlankValue(v));
        for (let i = 0; i < values.length; i++) {
            setVal(inputs[i], values[i]);
            await wait(SIZE_CHART_TIMING.inputMs ?? T.SHORT);
        }

        const confirm = [...document.querySelectorAll('button, span, div')]
            .find((el) => clean(el.innerText) === '确认');
        if (!confirm) throw new Error('未找到尺码表确认按钮');
        click(confirm.closest('button') || confirm);

        const closeStart = Date.now();
        while (modalBody()) {
            if (Date.now() - closeStart > 5000) break;
            await wait(SIZE_CHART_TIMING.pollMs ?? T.SHORT);
        }
        await wait(SIZE_CHART_TIMING.closeDialogMs ?? T.MED);
    }

    async function selectSensitiveNo() {
        const targetLabel = clean(CONFIG.sensitiveAttrValue || '否');
        if (isBlankValue(targetLabel)) return;
        const input = [...document.querySelectorAll('input')].find((el) => clean(el.placeholder) === '敏感属性');
        const trigger = input
            ? input.closest('[class*="ST_outerWrapper"], [class*="IPT_inputWrapper"], [class*="IPT_outerWrapper"], div')
            : [...document.querySelectorAll('div, span, input')].find((el) => {
                const txt = clean(el.innerText || el.value || el.placeholder || '');
                return txt.includes('敏感属性') && !txt.includes('说明');
            });
        if (!trigger) throw new Error('未找到敏感属性触发器');

        click(trigger);
        await wait(T.MED);

        const noLabel = [...document.querySelectorAll('label, span, div')]
            .find((el) => clean(el.innerText) === targetLabel);
        if (!noLabel) throw new Error('未找到“否”选项');

        click(noLabel.closest('label') || noLabel);
        await wait(T.MED);
    }

    async function clickBatchFill() {
        const start = Date.now();
        while (Date.now() - start < 5000) {
            const btn = [...document.querySelectorAll('button, span, div')]
                .find((el) => clean(el.innerText) === '批量填写');
            if (btn) {
                const target = btn.closest('button') || btn;
                if (typeof target.click === 'function') {
                    target.click();
                } else {
                    click(target);
                }
                await wait(T.MED);
                return true;
            }
            await wait(T.SHORT);
        }
        throw new Error('未找到“批量填写”按钮');
    }

    async function clickComplianceAgreement() {
        const label = [...document.querySelectorAll('label[data-testid="beast-core-checkbox"], label')]
            .find((el) => clean(el.innerText || el.textContent || '').includes('我已阅读并同意')
                && clean(el.innerText || el.textContent || '').includes('商品合规声明'));
        if (!label) {
            console.warn('[Temu助手] 未找到商品合规声明勾选框');
            return;
        }
        const checkbox = label.querySelector('input[type="checkbox"]');
        if (checkbox?.checked) return;

        if (typeof label.click === 'function') label.click();
        else click(label);
        await wait(T.SHORT);
    }

    async function clickCreateButton() {
        const agreementLabel = [...document.querySelectorAll('label[data-testid="beast-core-checkbox"], label')]
            .find((el) => clean(el.innerText || el.textContent || '').includes('我已阅读并同意')
                && clean(el.innerText || el.textContent || '').includes('商品合规声明'));
        const scopedButtons = agreementLabel?.parentElement
            ? [...agreementLabel.parentElement.querySelectorAll('button, [role="button"], a, span, div')]
            : [];
        const btn = scopedButtons
            .filter((el) => {
                const text = clean(el.innerText || el.textContent || '');
                if (text !== '创建') return false;
                const rect = el.getBoundingClientRect();
                const style = window.getComputedStyle(el);
                return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
            })
            .map((el) => el.closest('button') || el)
            .at(0)
            || [...document.querySelectorAll('button, [role="button"], span, div')]
                .filter((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    if (text !== '创建') return false;
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
                })
                .map((el) => el.closest('button') || el)
                .at(0)
            || null;
        if (!btn) {
            console.warn('[Temu助手] 未找到创建按钮');
            return;
        }
        if (typeof btn.click === 'function') btn.click();
        else click(btn);
        await wait(T.MED);
    }

    async function clickContinueCreateButton() {
        const start = Date.now();
        while (Date.now() - start < 15000) {
            const btn = [...document.querySelectorAll('button, [role="button"], a, span, div')]
                .filter((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    if (text !== '继续新建商品' && text !== '继续新建') return false;
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
                })
                .map((el) => el.closest('button, a') || el)
                .at(0) || null;
            if (btn) {
                if (typeof btn.click === 'function') btn.click();
                else click(btn);
                await wait(T.MED);
                return true;
            }
            await wait(SKU_TIMING.specPollMs ?? T.SHORT);
        }
        console.warn('[Temu助手] 未找到继续新建商品按钮');
        return false;
    }

    async function clickComplianceAgreement() {
        const textTarget = '我已阅读并同意《商品合规声明》';
        const label = [...document.querySelectorAll('label, div, span, p')]
            .find((el) => clean(el.innerText || el.textContent || '') === textTarget);
        if (!label) {
            console.warn('[Temu助手] 未找到商品合规声明区域');
            return;
        }

        const wrapper = label.closest('label, div, span')?.parentElement || label.parentElement || label;
        const checkbox = wrapper.querySelector('input[type="checkbox"]')
            || label.closest('label')?.querySelector('input[type="checkbox"]')
            || null;

        if (checkbox) {
            if (!checkbox.checked) {
                click(checkbox);
                await wait(T.SHORT);
            }
            return;
        }

        const clickable = [...(wrapper || document).querySelectorAll('label, span, div, i')]
            .find((el) => {
                const text = clean(el.innerText || el.textContent || '');
                if (text && text !== textTarget) return false;
                const rect = el.getBoundingClientRect();
                return rect.width > 0 && rect.height > 0;
            })
            || label;
        click(clickable);
        await wait(T.SHORT);
    }

    async function waitForStableElement(fn, timeoutMs = 15000, stepMs = 120, stableMs = 360) {
        const start = Date.now();
        let stableStart = 0;
        let lastKey = '';
        let lastEl = null;
        while (Date.now() - start < timeoutMs) {
            const el = fn();
            if (!el) {
                stableStart = 0;
                lastKey = '';
                lastEl = null;
                await wait(stepMs);
                continue;
            }
            const rect = el.getBoundingClientRect();
            const key = `${clean(el.innerText || el.textContent || '')}|${Math.round(rect.left)}|${Math.round(rect.top)}|${Math.round(rect.width)}|${Math.round(rect.height)}`;
            if (key !== lastKey) {
                lastKey = key;
                lastEl = el;
                stableStart = Date.now();
            } else if (Date.now() - stableStart >= stableMs) {
                return lastEl;
            }
            await wait(stepMs);
        }
        return null;
    }

    async function clickCreateButtonStable() {
        const btn = await waitForStableElement(() => {
            const agreementLabel = [...document.querySelectorAll('label[data-testid="beast-core-checkbox"], label')]
                .find((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    return text.includes('我已阅读并同意') && text.includes('商品合规声明');
                });
            const scope = agreementLabel?.parentElement || agreementLabel?.closest('div, section, article') || document;
            return [...scope.querySelectorAll('button, [role="button"], a, span, div')]
                .filter((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    if (text !== '创建') return false;
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
                })
                .map((el) => el.closest('button, a') || el)
                .at(0) || null;
        }, 15000, 120, 360);
        if (!btn) {
            console.warn('[Temu助手] 未找到创建按钮');
            return false;
        }

        if (isFeatureOn('randomWaitBeforeCreate')) {
            await waitRandomBetween(
                RANDOM_WAIT_TIMING.createMinMs,
                RANDOM_WAIT_TIMING.createMaxMs
            );
        }

        if (typeof btn.click === 'function') btn.click();
        else click(btn);
        await wait(T.MED);
        return true;
    }

    async function clickCreateButtonStable2() {
        const btn = await waitForStableElement(() => {
            return [...document.querySelectorAll('button, [role="button"]')]
                .filter((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    if (text !== '创建') return false;
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
                })
                .sort((a, b) => {
                    const ar = a.getBoundingClientRect();
                    const br = b.getBoundingClientRect();
                    return (br.top - ar.top) || (ar.left - br.left);
                })
                .at(0) || null;
        }, 15000, 120, 360);
        if (!btn) {
            console.warn('[Temu助手] 未找到创建按钮');
            return false;
        }
        if (typeof btn.click === 'function') btn.click();
        else click(btn);
        await wait(T.MED);
        return true;
    }

    async function clickContinueCreateButtonStable() {
        const successDialog = await waitForStableElement(() => {
            return [...document.querySelectorAll('div, section, article')]
                .find((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return style.display !== 'none'
                        && style.visibility !== 'hidden'
                        && rect.width > 0
                        && rect.height > 0
                        && text.includes('创建成功')
                        && text.includes('继续新建商品')
                        && text.includes('查看商品列表');
                }) || null;
        }, 20000, 120, 420);
        if (!successDialog) {
            console.warn('[Temu助手] 未找到创建成功弹层');
            return false;
        }
        const btn = await waitForStableElement(() => {
            return [...successDialog.querySelectorAll('button, [role="button"], a, span, div')]
                .filter((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    if (text !== '继续新建商品' && text !== '继续新建') return false;
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
                })
                .map((el) => el.closest('button, a') || el)
                .at(0) || null;
        }, 10000, 120, 420);
        if (!btn) {
            console.warn('[Temu助手] 未找到继续新建商品按钮');
            return false;
        }
        if (typeof btn.click === 'function') btn.click();
        else click(btn);
        await wait(T.MED);

        if (isCategoryCreatePage()) {
            await selectConfiguredCategory();
        } else {
            const start = Date.now();
            while (Date.now() - start < 20000) {
                if (isCategoryCreatePage()) {
                    await selectConfiguredCategory();
                    break;
                }
                await wait(CATEGORY_TIMING.nextPagePollMs ?? T.SHORT);
            }
        }
        return true;
    }

    async function clickContinueCreateButtonStable2() {
        const successDialog = await waitForStableElement(() => {
            return [...document.querySelectorAll('div, section, article')]
                .find((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return style.display !== 'none'
                        && style.visibility !== 'hidden'
                        && rect.width > 0
                        && rect.height > 0
                        && text.includes('创建成功')
                        && text.includes('继续新建商品')
                        && text.includes('查看商品列表');
                }) || null;
        }, 20000, 120, 420);
        if (!successDialog) {
            console.warn('[Temu助手] 未找到创建成功弹层');
            return false;
        }

        const btn = await waitForStableElement(() => {
            return [...successDialog.querySelectorAll('button, [role="button"], a, span, div')]
                .filter((el) => {
                    const text = clean(el.innerText || el.textContent || '');
                    if (text !== '继续新建商品' && text !== '继续新建') return false;
                    const rect = el.getBoundingClientRect();
                    const style = window.getComputedStyle(el);
                    return style.display !== 'none'
                        && style.visibility !== 'hidden'
                        && rect.width > 0
                        && rect.height > 0;
                })
                .map((el) => el.closest('button, a') || el)
                .at(0) || null;
        }, 10000, 120, 420);
        if (!btn) {
            console.warn('[Temu助手] 未找到继续新建商品按钮');
            return false;
        }

        if (typeof btn.click === 'function') btn.click();
        else click(btn);
        await wait(T.MED);
        return true;
    }

    function findSkuBatchSectionRoot() {
        return document.querySelector('.product-sku_skuTableContainer__sX1e0')
            || [...document.querySelectorAll('div, section, form, article')]
                .find((el) => {
                    const text = clean(el.innerText || '').replace(/\s+/g, '');
                    return text.includes('SKU信息') && text.includes('批量填写');
                }) || null;
    }

    function findSkuBatchTable() {
        return document.querySelector('.product-sku_skuTableContainer__sX1e0 table.performance-table_performanceTable__dwfgW')
            || findSkuBatchSectionRoot()?.querySelector('table.performance-table_performanceTable__dwfgW')
            || null;
    }

    function findSelectInputIn(root) {
        return root?.querySelector('input[data-testid="beast-core-select-htmlInput"]') || null;
    }

    async function pickSelectValueIn(root, label) {
        if (isBlankValue(label)) return false;
        const input = findSelectInputIn(root);
        const ctrl = getCtrl(input);
        if (ctrl && pickByLabel(ctrl, label)) {
            await wait(T.SHORT);
            return true;
        }

        const trigger = root?.querySelector('[class*="ST_outerWrapper"], [data-testid="beast-core-select"]') || root;
        if (!trigger) return false;
        click(trigger);
        await wait(T.SHORT);
        return await pickOptionExact(label);
    }

    async function pickPackModeValueIn(root, label) {
        if (isBlankValue(label)) return false;
        const trigger = root?.querySelector('[class*="ST_outerWrapper"], [data-testid="beast-core-select"]') || root;
        if (!trigger) return false;
        click(trigger);
        await wait(T.SHORT);

        const popup = [...document.querySelectorAll('[role="listbox"], .ST_dropdownPanel_5-120-1, .beast-select-dropdown, .ST_popupWrapper_5-120-1, [class*="ST_popup"]')]
            .filter((el) => {
                const style = window.getComputedStyle(el);
                const rect = el.getBoundingClientRect();
                return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
            })
            .reverse()
            .find((el) => {
                const text = clean(el.innerText || '');
                return text.includes('是独立包装') || text.includes('不是独立包装');
            });
        if (!popup) return false;

        const option = [...popup.querySelectorAll('[role="option"], li, .beast-select-item-option-content, .ST_item_5-120-1')]
            .find((el) => clean(el.innerText) === label || clean(el.innerText).includes(label));
        if (!option) return false;
        click(option);
        await wait(T.SHORT);
        return true;
    }

    function findPackModeRootInRow(row) {
        const direct = row.querySelector('[id*=".productSkuMultiPack.packIncludeInfo"]');
        if (direct) return direct;

        const categoryItems = [...row.querySelectorAll('.sku-category_formItem__iqG7r [data-testid="beast-core-form-item"]')];
        if (categoryItems.length >= 3) return categoryItems[2];

        return [...row.querySelectorAll('[data-testid="beast-core-form-item"]')].find((el) => {
            const text = clean(el.innerText || '');
            return text.includes('请选择独立包装') || text.includes('不是独立包装') || text.includes('独立包装');
        }) || null;
    }

    function findModelNameInRow(row) {
        const rowText = clean(row?.innerText || '');
        return getParentSpec2ModelNames().find((name) => rowText.includes(name)) || '';
    }

    function listPackageRootsInRow(row) {
        const supplierRoot = row.querySelector('[id$=".supplierPrice"]');
        const baseId = (supplierRoot?.id || '').replace(/\.supplierPrice$/, '');
        const selector = baseId ? `[id^="${baseId}.packageInventoryList["]` : '[id*=".packageInventoryList["]';
        return [...document.querySelectorAll(selector)]
            .sort((a, b) => {
                const ai = Number((a.id.match(/\[(\d+)\]/) || [])[1] || 0);
                const bi = Number((b.id.match(/\[(\d+)\]/) || [])[1] || 0);
                return ai - bi;
            });
    }

    async function clickPackageAddInRow(row) {
        const roots = listPackageRootsInRow(row);
        const anchorRoot = roots[roots.length - 1] || row;
        const packageCell = anchorRoot.closest('td') || anchorRoot.parentElement || row;
        const btn = [...packageCell.querySelectorAll('button, a, span, div')]
            .find((el) => {
                const text = clean(el.innerText || el.textContent || '');
                return text === '+ 添加' || text.includes('+ 添加') || text.includes('添加');
            })
            || [...packageCell.querySelectorAll('a[class*="BTN_outerWrapperLink"], button[class*="BTN_textPrimary"], a[class*="BTN_textPrimary"]')].at(-1)
            || null;
        if (!btn) return false;

        const target = btn.closest('button, a') || btn;
        if (typeof target.click === 'function') target.click();
        else click(target);
        await wait(SKU_TIMING.packageAddMs ?? T.MED);
        return true;
    }

    async function clickConfirmIfVisible() {
        const confirm = [...document.querySelectorAll('button, [role="button"]')]
            .filter((el) => {
                const text = clean(el.innerText || el.textContent || '');
                if (text !== '确认') return false;
                const rect = el.getBoundingClientRect();
                const style = window.getComputedStyle(el);
                return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
            })
            .sort((a, b) => {
                const ar = a.getBoundingClientRect();
                const br = b.getBoundingClientRect();
                return (br.top - ar.top) || (br.left - ar.left);
            })[0];
        if (!confirm) return false;
        const target = confirm.closest('button') || confirm;
        if (typeof target.click === 'function') target.click();
        click(target);
        await wait(SKU_TIMING.packageConfirmMs ?? T.MED);
        return true;
    }

    async function ensurePackageRootsCount(row, expectedCount) {
        let roots = listPackageRootsInRow(row);
        const addTimes = Math.max(0, expectedCount - 1);
        for (let i = 0; i < addTimes; i++) {
            const beforeCount = roots.length;
            const ok = await clickPackageAddInRow(row);
            if (!ok) break;

            const start = Date.now();
            while (Date.now() - start < 3000) {
                roots = listPackageRootsInRow(row);
                if (roots.length > beforeCount) break;
                await wait(SKU_TIMING.packagePollMs ?? T.SHORT);
            }
            if (roots.length <= beforeCount) break;
        }
        return listPackageRootsInRow(row);
    }

    async function fillPackageInventoryInRow(row, modelName) {
        const items = CONFIG.sku.packageListByModel?.[modelName]
            || (Array.isArray(CONFIG.sku.packageList) ? CONFIG.sku.packageList : []);
        if (!items.length) return;

        const roots = await ensurePackageRootsCount(row, items.length);
        for (let i = 0; i < items.length; i++) {
            const root = roots[i];
            if (!root) continue;

            const selectWrapper = root.querySelector('[class*="ST_outerWrapper"], [data-testid="beast-core-select"]') || root;
            const selectInput = root.querySelector('input[data-testid="beast-core-select-htmlInput"]');
            let ok = false;

            if (selectWrapper) {
                click(selectWrapper);
                await wait(T.SHORT);
            }
            if (selectInput) {
                setReactValue(selectInput, items[i]);
                await wait(T.SHORT);
                ok = await pickOptionExact(items[i]);
            }
            if (!ok) ok = await pickSelectValueIn(root, items[i]);
            await clickConfirmIfVisible();

            const countInput = root.querySelector('input[data-testid="beast-core-inputNumber-htmlInput"], input[placeholder="数量"], input[placeholder="请输入"]');
            if (countInput && !countInput.disabled && !countInput.readOnly && !isBlankValue(CONFIG.sku.packageItemCount)) {
                setVal(countInput, CONFIG.sku.packageItemCount);
                await wait(SKU_TIMING.packageCountMs ?? T.MICRO);
            }
        }
    }

    async function fillSkuBatchTable() {
        const table = findSkuBatchTable();
        if (!table) return false;

        const rows = [...table.querySelectorAll('tbody tr')].filter((row) => row.querySelector('[id$=".supplierPrice"]'));
        if (!rows.length) return false;

        for (const row of rows) {
            const modelName = findModelNameInRow(row);
            const declarePrice = CONFIG.sku.declarePriceByModel?.[modelName] || CONFIG.sku.declarePrice || '';
            const singleItemCount = CONFIG.sku.singleItemCountByModel?.[modelName] || CONFIG.sku.singleItemCount || '';
            const suggestPrice = CONFIG.sku.suggestPriceByModel?.[modelName] || CONFIG.sku.suggestPrice || '';

            const declareRoot = row.querySelector('[id$=".supplierPrice"]');
            const declareInput = declareRoot?.querySelector('input[placeholder="请输入"]');
            if (declareInput && !declareInput.disabled && !declareInput.readOnly && !isBlankValue(declarePrice)) {
                setVal(declareInput, declarePrice);
                await wait(SKU_TIMING.rowDeclareMs ?? T.MICRO);
            }

            const skuClassRoot = row.querySelector('[id*=".productSkuMultiPack.skuClassification"]');
            if (skuClassRoot && !isBlankValue(CONFIG.sku.skuClassLabel)) {
                await pickSelectValueIn(skuClassRoot, CONFIG.sku.skuClassLabel);
                await wait(SKU_TIMING.rowSkuClassMs ?? T.SHORT);
            }

            const countRoot = row.querySelector('[id*=".productSkuMultiPack.numberOfInfo"]');
            const countInput = countRoot?.querySelector('input[data-testid="beast-core-inputNumber-htmlInput"], input[placeholder="请输入"]');
            if (countInput && !countInput.disabled && !countInput.readOnly && !isBlankValue(singleItemCount)) {
                setVal(countInput, singleItemCount);
                await wait(SKU_TIMING.rowCountMs ?? T.MICRO);
            }

            const packModeRoot = findPackModeRootInRow(row);
            if (packModeRoot && !isBlankValue(CONFIG.sku.packMode)) {
                await pickPackModeValueIn(packModeRoot, CONFIG.sku.packMode)
                    || await pickSelectValueIn(packModeRoot, CONFIG.sku.packMode);
                await wait(SKU_TIMING.rowPackModeMs ?? T.SHORT);
            }

            await fillPackageInventoryInRow(row, modelName);

            const suggestRoot = row.querySelector('[id$=".suggestSalesPrice"]');
            const suggestInput = suggestRoot?.querySelector('input[data-testid="beast-core-input-htmlInput"], input[data-testid="beast-core-inputNumber-htmlInput"], input[placeholder="请输入"]');
            if (suggestInput && !suggestInput.disabled && !suggestInput.readOnly && !isBlankValue(suggestPrice)) {
                setVal(suggestInput, suggestPrice);
                await wait(SKU_TIMING.rowSuggestMs ?? T.SHORT);
            }

            const selectWrappers = suggestRoot ? [...suggestRoot.querySelectorAll('[class*="ST_outerWrapper"], [data-testid="beast-core-select"]')] : [];
            const currencyRoot = selectWrappers[selectWrappers.length - 1] || null;
            if (currencyRoot && !isBlankValue(CONFIG.sku.currency)) {
                await pickSelectValueIn(currencyRoot, CONFIG.sku.currency);
                await wait(SKU_TIMING.rowCurrencyMs ?? T.MICRO);
            }
        }

        return true;
    }

    async function fillSkuBlock() {
        if (isBlankValue(CONFIG.sku?.declarePrice)
            && isBlankValue(CONFIG.sku?.skuTypeId)
            && isBlankValue(CONFIG.sku?.currency)
            && isBlankValue(CONFIG.sku?.suggestPrice)
            && isBlankValue(CONFIG.sku?.cargoNo)
            && !hasAdvancedSkuConfig()) {
            return;
        }

        if (hasAdvancedSkuConfig()) {
            const skuBatchFilled = await fillSkuBatchTable();
            if (skuBatchFilled) {
                const cargoEl = findInput('货号');
                const cargoNo = isBlankValue(CONFIG.sku.cargoNo) ? STATE.modelText : CONFIG.sku.cargoNo;
                if (cargoEl && !isBlankValue(cargoNo)) {
                    setVal(cargoEl, cargoNo);
                    await wait(SKU_TIMING.cargoMs ?? T.SHORT);
                }
                return;
            }
        }

        const allInputs = [...document.querySelectorAll('input, textarea')];
        const headerEl = allInputs.find((el) => el.placeholder === '申报价格' && el.readOnly);

        let rowInputs = [];
        if (headerEl) {
            const headerRow = headerEl.closest('tr, [class*="tableRow"], [class*="TableRow"]');
            const table = headerEl.closest('table, [class*="Table"]');
            if (headerRow && table) {
                const dataRow = [...table.querySelectorAll('tr, [class*="tableRow"], [class*="TableRow"]')]
                    .find((r) => r !== headerRow && !headerRow.contains(r));
                if (dataRow) {
                    rowInputs = [...dataRow.querySelectorAll(
                        'input:not([type="file"]):not([type="radio"]):not([type="checkbox"]), textarea'
                    )];
                }
            }
        }

        if (!rowInputs.length && headerEl) {
            const idx = allInputs.indexOf(headerEl);
            rowInputs = allInputs.slice(idx + 1, idx + 20)
                .filter((el) => !['file', 'radio', 'checkbox'].includes(el.type));
        }

        if (!rowInputs.length) {
            console.warn('[Temu助手] 未找到 SKU 数据行输入框');
        }

        const declareEl = rowInputs.find(
            (el) => !el.readOnly && !el.disabled
                && ['text', ''].includes(el.type)
                && !getCtrl(el)?.options?.length
        );
        if (declareEl && !isBlankValue(CONFIG.sku.declarePrice)) {
            setVal(declareEl, CONFIG.sku.declarePrice);
            await wait(SKU_TIMING.cargoMs ?? T.SHORT);
        } else if (!isBlankValue(CONFIG.sku.declarePrice)) {
            console.warn('[Temu助手] 未找到申报价格输入');
        }

        const skuTypeEl = rowInputs.find((el) => {
            const ctrl = getCtrl(el);
            return ctrl?.options?.some((o) => o.value === 1 || o.value === '1');
        });
        if (skuTypeEl && !isBlankValue(CONFIG.sku.skuTypeId)) {
            pickById(getCtrl(skuTypeEl), CONFIG.sku.skuTypeId);
            await wait(T.SHORT);
        } else if (!isBlankValue(CONFIG.sku.skuTypeId)) {
            console.warn('[Temu助手] 未找到 SKU 分类选择器');
        }

        const currencyEl = rowInputs.find((el) => {
            const ctrl = getCtrl(el);
            return ctrl?.options?.length >= 50;
        });
        if (currencyEl && !isBlankValue(CONFIG.sku.currency)) {
            const ctrl = getCtrl(currencyEl);
            const cnOpt = ctrl.options.find((o) => String(o.value) === CONFIG.sku.currency);
            if (cnOpt) {
                ctrl.onChange(cnOpt.value, cnOpt);
                await wait(T.SHORT);
            }
        } else if (!isBlankValue(CONFIG.sku.currency)) {
            console.warn('[Temu助手] 未找到货币选择器');
        }

        const priceInputs = rowInputs.filter(
            (el) => !el.readOnly && !el.disabled
                && ['text', ''].includes(el.type)
                && !getCtrl(el)?.options?.length
        );
        const suggestEl = priceInputs[1] || null;
        if (suggestEl && !isBlankValue(CONFIG.sku.suggestPrice)) {
            setVal(suggestEl, CONFIG.sku.suggestPrice);
            await wait(SKU_TIMING.cargoMs ?? T.SHORT);
        } else if (!isBlankValue(CONFIG.sku.suggestPrice)) {
            console.warn('[Temu助手] 未找到建议零售价输入');
        }

        const cargoEl = findInput('货号');
        const cargoNo = isBlankValue(CONFIG.sku.cargoNo) ? STATE.modelText : CONFIG.sku.cargoNo;
        if (cargoEl && !isBlankValue(cargoNo)) {
            setVal(cargoEl, cargoNo);
            await wait(SKU_TIMING.cargoMs ?? T.SHORT);
        } else if (!isBlankValue(cargoNo)) {
            console.warn('[Temu助手] 未找到货号输入');
        }
    }

    const STEPS = [
        {
            key: 'fillAttributes',
            name: '属性下拉',
            fn: async () => {
                for (const [name, val] of (Array.isArray(CONFIG.queue) ? CONFIG.queue : [])) {
                    if (isBlankValue(name) || isBlankValue(val)) continue;
                    await selectDropdown(name, val);
                }
            }
        },
        { key: 'fillProvince', name: '货源省份', fn: () => selectProvince() },
        { key: 'fillParentSpecs', name: '父规格', fn: () => fillParentSpecs() },
        { key: 'fillProductName', name: '商品名称', fn: () => fillProductName() },
        { key: 'waitSpecGeneration', name: '等待规格生成', fn: () => wait(T.SPEC) },
        { key: 'fillSizeChart', name: '尺码表', fn: () => fillSizeChart() },
        { key: 'fillDimensions', name: '尺寸重量', fn: () => fillDimensions() },
        { key: 'fillSensitiveAttr', name: '敏感属性', fn: async () => { await selectSensitiveNo(); await wait(SKU_TIMING.sensitiveAfterMs ?? T.SHORT); } },
        { key: 'clickBatchFill', name: '批量填写', fn: async () => { await clickBatchFill(); } },
        { key: 'fillSkuBlock', name: 'SKU 填写', fn: () => fillSkuBlock() }
    ];

    function getActiveSteps() {
        return STEPS.filter((step) => isFeatureOn(step.key));
    }

    async function run(btn) {
        if (btn.dataset.running === 'true') return;

        btn.dataset.running = 'true';
        const label = btn.querySelector('.temu-label');
        const orig = label.textContent;

        try {
            if (isFeatureOn('selectCategory') && isCategoryCreatePage()) {
                label.textContent = '选择商品分类';
                await selectConfiguredCategory4();
            }

            readPanelData();

            if (isFeatureOn('uploadPrepare') && typeof window.__TEMU_UPLOAD_PREPARE__ === 'function') {
                label.textContent = '预授权图片目录';
                await window.__TEMU_UPLOAD_PREPARE__();
            }

            const activeSteps = getActiveSteps();
            for (const [i, step] of activeSteps.entries()) {
                label.textContent = `${i + 1}/${activeSteps.length} ${step.name}`;
                await step.fn();
            }

            if (isFeatureOn('extraSkuFill') && typeof window.__TEMU_SKU_RUNFILL__ === 'function') {
                label.textContent = `附加 1/2 SKU脚本`;
                await window.__TEMU_SKU_RUNFILL__();
            }

            if (isFeatureOn('imageUpload') && typeof window.__TEMU_UPLOAD_RUN__ === 'function') {
                label.textContent = `附加 2/2 图片脚本`;
                await window.__TEMU_UPLOAD_RUN__(STATE.modelText);
            }

            if (isFeatureOn('complianceAgreement')) {
                label.textContent = '勾选合规声明';
                await clickComplianceAgreement();
            }

            if (isFeatureOn('clickCreate')) {
                label.textContent = '点击创建';
                await clickCreateButtonStable2();
            }

            if (isFeatureOn('continueCreate')) {
                label.textContent = '继续新建商品';
                await clickContinueCreateButtonStable2();
            }

            btn.style.setProperty('--bg', '#4caf50');
            label.textContent = '完成 ✓';
        } catch (e) {
            console.error('[Temu助手]', e);
            btn.style.setProperty('--bg', '#f44336');
            label.textContent = '出错 ✗';
            alert(e.message || '执行失败');
        }

        setTimeout(() => {
            btn.dataset.running = 'false';
            btn.style.setProperty('--bg', 'rgba(255,87,34,.9)');
            label.textContent = orig;
        }, 2200);
    }

    function injectUI() {
        if (document.getElementById('temu-v28-panel')) return;
        const ui = getUiConfig();

        const style = document.createElement('style');
        style.textContent = `
            #temu-v28-panel {
                position: fixed;
                left: ${ui.panelLeft}px;
                top: ${ui.panelTop}px;
                z-index: 100000;
                width: ${ui.panelWidth}px;
                background: rgba(255,255,255,.96);
                border: 1px solid rgba(0,0,0,.12);
                border-radius: ${ui.borderRadius}px;
                box-shadow: 0 8px 22px rgba(0,0,0,.15);
                padding: ${ui.panelPadding};
                font: 12px/1.3 sans-serif;
                color: #222;
                user-select: none;
            }
            #temu-v28-dragbar {
                width: ${ui.dragbarWidth}px;
                height: ${ui.dragbarHeight}px;
                margin: 0 auto 8px;
                border-radius: 999px;
                background: rgba(0,0,0,.18);
                cursor: move;
            }
            #temu-onekey-source {
                width: 100%;
                box-sizing: border-box;
                height: ${ui.inputHeight}px;
                border: 1px solid #d9d9d9;
                border-radius: ${ui.controlRadius}px;
                padding: ${ui.inputPadding};
                outline: none;
                font: 12px/1.1 monospace;
                color: #222;
                background: #fff;
            }
            #temu-onekey-source:focus {
                border-color: #ff5722;
                box-shadow: 0 0 0 2px rgba(255,87,34,.12);
            }
            #temu-v28-btn {
                margin-top: 8px;
                width: 100%;
                padding: ${ui.buttonPadding};
                border: 0;
                border-radius: ${ui.controlRadius}px;
                background: rgba(255,87,34,.95);
                color: #fff;
                font: bold 13px/1 sans-serif;
                cursor: pointer;
                user-select: none;
                transition: opacity .2s, background .3s;
            }
            #temu-v28-btn:hover { opacity: .88; }
            #temu-v28-btn[data-running="true"] { opacity: .92; cursor: wait; }
        `;
        document.head.appendChild(style);

        const panel = document.createElement('div');
        panel.id = 'temu-v28-panel';
        panel.innerHTML = `
            <div id="temu-v28-dragbar"></div>
            <input id="temu-onekey-source" type="text" placeholder="型号 商品名称" value="${STATE.modelText} ${STATE.productNameText}" />
            <button id="temu-v28-btn" type="button" data-running="false"><span class="temu-label">= V =</span></button>
        `;

        document.body.appendChild(panel);

        const dragbar = panel.querySelector('#temu-v28-dragbar');
        let dragging = false;
        let startX = 0;
        let startY = 0;
        let startLeft = 0;
        let startTop = 0;

        dragbar.addEventListener('pointerdown', (e) => {
            dragging = true;
            panel.style.right = 'auto';
            panel.style.bottom = 'auto';
            const rect = panel.getBoundingClientRect();
            startX = e.clientX;
            startY = e.clientY;
            startLeft = rect.left;
            startTop = rect.top;
            dragbar.setPointerCapture(e.pointerId);
            e.preventDefault();
        });

        dragbar.addEventListener('pointermove', (e) => {
            if (!dragging) return;
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            panel.style.left = `${Math.max(0, startLeft + dx)}px`;
            panel.style.top = `${Math.max(0, startTop + dy)}px`;
        });

        const stopDrag = () => { dragging = false; };
        dragbar.addEventListener('pointerup', stopDrag);
        dragbar.addEventListener('pointercancel', stopDrag);
        window.addEventListener('pointerup', stopDrag);

        const btn = panel.querySelector('#temu-v28-btn');
        btn.addEventListener('click', () => run(btn));
    }

    injectUI();
    new MutationObserver(injectUI).observe(document.body, { childList: true });

    /* ════════════════════════════════════════════════════════════════
       第二脚本逻辑保留,仅不单独注入按钮
    ════════════════════════════════════════════════════════════════ */

    (function () {
        'use strict';

        const wait = ms => new Promise(r => setTimeout(r, ms));

        function fastClick(el) {
            if (!el) return;
            ['mousedown', 'click', 'mouseup'].forEach(n =>
                el.dispatchEvent(new MouseEvent(n, { bubbles: true, view: window }))
            );
        }

        function setReactValue(el, val) {
            if (!el) return;
            const lastValue = el.value;
            el.value = val;
            const event = new Event('input', { bubbles: true });
            const tracker = el._valueTracker;
            if (tracker) tracker.setValue(lastValue);
            el.dispatchEvent(event);
            el.dispatchEvent(new Event('change', { bubbles: true }));
        }

        const byPh = ph => {
            const inputs = Array.from(document.querySelectorAll(`input[placeholder*="${ph}"]`));
            return inputs.find(inp => {
                const wrapper = inp.closest('.beast-input-inner-wrapper');
                const hasCurrencySymbol = wrapper?.innerText.includes('¥') ||
                    wrapper?.parentElement?.innerText.includes('¥');
                return hasCurrencySymbol && !inp.closest('[class*="batch"], [class*="Batch"]');
            }) || inputs.find(inp => !inp.closest('[class*="batch"]'));
        };

        function findInputNear(labelText) {
            const labels = Array.from(document.querySelectorAll('*')).filter(el =>
                el.childElementCount === 0 && el.innerText?.trim() === labelText && !el.closest('[class*="batch"]')
            );
            for (const el of labels) {
                let p = el.parentElement;
                for (let i = 0; i < 5; i++) {
                    if (!p) break;
                    const inp = p.querySelector('input:not([readonly])');
                    if (inp) return inp;
                    p = p.parentElement;
                }
            }
            return null;
        }

        async function selectOption(trigger, optionText) {
            if (!trigger) return false;
            const TIMING = { ...((window.__TEMU_GET_UNIFIED_CONFIG__?.().SKU_FILL_TIMING) || {}) };
            fastClick(trigger);
            await wait(TIMING.selectOpenMs ?? 250);
            const opts = Array.from(document.querySelectorAll('.beast-select-item-option-content, [role="option"], li'));
            const hit = opts.find(o => o.innerText?.trim() === optionText) || opts.find(o => o.innerText?.includes(optionText));
            if (hit) {
                fastClick(hit);
                await wait(TIMING.selectDoneMs ?? 100);
                return true;
            }
            return false;
        }

        async function runFill() {
            try {
                const CONFIG = {
                    ...((window.__TEMU_GET_UNIFIED_CONFIG__?.().SKU_FILL) || {})
                };

                const swLabel = Array.from(document.querySelectorAll('span')).find(el => el.innerText?.includes('敏感属性') && !el.closest('[class*="batch"]'));
                if (swLabel) {
                    const TIMING = {
                        ...((window.__TEMU_GET_UNIFIED_CONFIG__?.().SKU_FILL_TIMING) || {})
                    };
                    const sw = swLabel.closest('.Form_item_container, .Form_item_5-120-1')?.querySelector('.beast-switch');
                    if (sw && !sw.classList.contains('beast-switch-checked')) {
                        fastClick(sw);
                        await wait(TIMING.sensitiveSwitchMs ?? 350);
                    }
                    const radioNo = Array.from(document.querySelectorAll('.beast-radio-group label')).find(r => r.innerText?.trim() === '否');
                    if (radioNo) fastClick(radioNo);
                }

                const declaredInp = byPh('申报价格');
                if (declaredInp) setReactValue(declaredInp, CONFIG.declaredPrice);

                const retailInp = byPh('建议零售价') || byPh('零售价');
                if (retailInp) setReactValue(retailInp, CONFIG.retailPrice);

                const currTrigger = Array.from(document.querySelectorAll('[class*="ST_outerWrapper"], [class*="appendCell"]'))
                    .find(el => !el.closest('[class*="batch"], [class*="Batch"]') && ['USD', 'CNY', 'JPY'].includes(el.innerText?.trim()));
                if (currTrigger) await selectOption(currTrigger, CONFIG.retailCurrency);

                const skuInp = byPh('SKU分类');
                if (skuInp) await selectOption(skuInp.closest('[class*="ST_outerWrapper"]') || skuInp, CONFIG.skuClass);

                const dims = { '最长边': CONFIG.maxEdge, '次长边': CONFIG.midEdge, '最短边': CONFIG.minEdge };
                for (const [k, v] of Object.entries(dims)) {
                    const inp = byPh(k) || findInputNear(k);
                    if (inp) setReactValue(inp, v);
                }
                const wtInp = findInputNear('g')?.parentElement?.querySelector('input') || findInputNear('重量');
                if (wtInp) setReactValue(wtInp, CONFIG.weight);
            } catch (e) {
                console.error('[Temu SKU信息自动填写]', e);
                throw e;
            }
        }

        window.__TEMU_SKU_RUNFILL__ = runFill;
    })();

})();


// ==UserScript==
// @name         Temu 商品素材图上传 3.1x
// @namespace    http://tampermonkey.net/
// @version      0.4.1
// @description  上传商品素材图和外包装图片:输入货号,自动把该货号文件夹内全部图片上传到“商品素材图 -> 素材中心 -> 本地上传”,并把最后一张图上传到外包装图片。
// @author       Assistant
// @match        *://agentseller.temu.com/goods/edit*
// @match        *://agentseller.temu.com/goods/add*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp']);
    const ROOT_KEY = 'temu_material_root_dir_v1';
    const ROOT_HINT = 'C:\\Users\\1\\Desktop\\新建文件夹 (2)\\NYZX包\\11111';
    const wait = (ms) => new Promise((r) => setTimeout(r, ms));
    const clean = (t) => (t || '').replace(/\s+/g, ' ').trim();

        function getImageTimingConfig() {
            return {
                ...((window.__TEMU_GET_UNIFIED_CONFIG__?.().IMAGE_TIMING) || {})
            };
        }

        function getPreviewUploadConfig() {
            return window.__TEMU_GET_MAIN_SCRIPT_CONFIG__?.().imageUpload?.preview || {};
        }

        function getCarouselUploadConfig() {
            return window.__TEMU_GET_MAIN_SCRIPT_CONFIG__?.().imageUpload?.carousel || {};
        }

    function isVisible(el) {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        const style = window.getComputedStyle(el);
        return rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden';
    }

    function click(el) {
        if (!el) return;
        el.scrollIntoView({ block: 'center', inline: 'center' });
        const rect = el.getBoundingClientRect();
        const shared = {
            bubbles: true,
            cancelable: true,
            view: window,
            clientX: rect.left + rect.width / 2,
            clientY: rect.top + rect.height / 2,
        };
        ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'].forEach((type) => {
            el.dispatchEvent(type.startsWith('pointer')
                ? new PointerEvent(type, { ...shared, pointerType: 'mouse' })
                : new MouseEvent(type, shared));
        });
    }

    function setFiles(input, files) {
        const dt = new DataTransfer();
        for (const file of files) dt.items.add(file);
        input.files = dt.files;
        input.dispatchEvent(new Event('change', { bubbles: true }));
    }

    function compareFileNames(a, b) {
        return a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' });
    }

    async function pickImagesFromDir(dirHandle) {
        const files = [];
        for await (const [name, handle] of dirHandle.entries()) {
            if (handle.kind !== 'file') continue;
            const dot = name.lastIndexOf('.');
            const ext = dot >= 0 ? name.slice(dot).toLowerCase() : '';
            if (!IMAGE_EXTS.has(ext)) continue;
            files.push(await handle.getFile());
        }
        files.sort(compareFileNames);
        return files;
    }

    async function openDb() {
        return await new Promise((resolve, reject) => {
            const req = indexedDB.open('temu-material-root-db', 1);
            req.onupgradeneeded = () => {
                req.result.createObjectStore('kv');
            };
            req.onsuccess = () => resolve(req.result);
            req.onerror = () => reject(req.error);
        });
    }

    async function saveRootHandle(handle) {
        try {
            const db = await openDb();
            await new Promise((resolve, reject) => {
                const tx = db.transaction('kv', 'readwrite');
                tx.objectStore('kv').put(handle, ROOT_KEY);
                tx.oncomplete = () => resolve();
                tx.onerror = () => reject(tx.error);
            });
            db.close();
        } catch (e) {
            console.warn('[Temu素材图] 保存根目录句柄失败', e);
        }
    }

    async function loadRootHandle() {
        try {
            const db = await openDb();
            const handle = await new Promise((resolve, reject) => {
                const tx = db.transaction('kv', 'readonly');
                const req = tx.objectStore('kv').get(ROOT_KEY);
                req.onsuccess = () => resolve(req.result || null);
                req.onerror = () => reject(req.error);
            });
            db.close();
            if (!handle) return null;
            if (handle.queryPermission && (await handle.queryPermission({ mode: 'read' })) !== 'granted') {
                return null;
            }
            return handle;
        } catch {
            return null;
        }
    }

    async function ensureRootHandle() {
        const stored = await loadRootHandle();
        if (stored) return stored;
        const handle = await window.showDirectoryPicker({ mode: 'read', startIn: 'documents' });
        await saveRootHandle(handle);
        return handle;
    }

    async function prepareRootHandle() {
        await ensureRootHandle();
        return true;
    }

    async function getSkuImages(sku) {
        if (!sku) throw new Error('请输入货号');
        const rootHandle = await ensureRootHandle();
        const skuDir = await rootHandle.getDirectoryHandle(sku, { create: false });
        const images = await pickImagesFromDir(skuDir);
        if (!images.length) throw new Error(`货号文件夹里没有图片:${sku}`);
        return images;
    }

    function findFormItem(labelText) {
        return [...document.querySelectorAll('[class*="Form_itemLabelContent"]')]
            .find((el) => {
                const t = clean(el.innerText);
                return t === labelText || t.startsWith(labelText);
            })?.closest('[class*="Form_item_"]') || null;
    }

    function findMaterialItem() {
        const exact = findFormItem('商品素材图');
        if (exact) return exact;
        return [...document.querySelectorAll('div, section, form, article')]
            .find((el) => {
                const t = clean(el.innerText);
                return t.includes('商品素材图') && (t.includes('素材中心') || t.includes('上传列表') || t.includes('本地上传'));
            }) || null;
    }

    function findCarouselItem() {
        const exact = findFormItem('商品轮播图');
        if (exact) return exact;
        return [...document.querySelectorAll('div, section, form, article')]
            .find((el) => clean(el.innerText).includes('商品轮播图')) || null;
    }

    function findOuterPackagingItem() {
        const exact = findFormItem('商品包装信息');
        if (exact) return exact;
        return [...document.querySelectorAll('div, section, form, article')]
            .find((el) => {
                const t = clean(el.innerText);
                return t.includes('外包装图片') && t.includes('批量上传');
            }) || null;
    }

    function findPreviewItem() {
        const exact = document.querySelector('[id$=".previewImgsI18n.common"]');
        if (exact) return exact;
        const skuPreviewWrap = document.querySelector('div[class*="sku-preview-img_wrap"]');
        if (skuPreviewWrap) return skuPreviewWrap.closest('[class*="Form_item_"], td, div') || skuPreviewWrap;
        return [...document.querySelectorAll('div, section, form, article')]
            .find((el) => {
                const t = clean(el.innerText);
                return t.includes('素材中心') && t.includes('AI 制图');
            }) || null;
    }

    function findPreviewEntries() {
        const table = document.querySelector('.product-sku_skuTableContainer__sX1e0 table.performance-table_performanceTable__dwfgW')
            || document.querySelector('table.performance-table_performanceTable__dwfgW');
        if (!table) return [];
        const rows = [...table.querySelectorAll('tbody tr')];
        return rows.map((row) => {
            const previewItem = row.querySelector('[id$=".previewImgsI18n.common"]');
            if (!previewItem) return null;
            const cells = [...row.querySelectorAll('td')].map((td) => clean(td.innerText)).filter(Boolean);
            return {
                item: previewItem,
                rowText: clean(row.innerText || ''),
                cells
            };
        }).filter(Boolean);
    }

    function resolveImageByPick(images, pick) {
        if (!Array.isArray(images) || !images.length || !pick) return null;
        const mode = clean(pick.mode || 'fromEnd');
        const rawIndex = Number(pick.index);
        const index = Number.isFinite(rawIndex) && rawIndex > 0 ? Math.floor(rawIndex) : 1;
        if (mode === 'fromStart') {
            return images[index - 1] || null;
        }
        return images[images.length - index] || null;
    }

    function resolvePreviewRuleForEntry(entry) {
        const previewConfig = getPreviewUploadConfig();
        const rules = Array.isArray(previewConfig.rules) ? previewConfig.rules : [];
        const haystack = [clean(entry?.rowText || ''), ...(entry?.cells || []).map(clean)].filter(Boolean);
        return rules.find((rule) => {
            const matchTexts = (Array.isArray(rule?.matchTexts) ? rule.matchTexts : [rule?.matchText])
                .filter((text) => !isBlankValue(text))
                .map((text) => clean(text));
            if (!matchTexts.length) return false;
            return matchTexts.some((needle) => haystack.some((text) => text.includes(needle)));
        }) || null;
    }

    function findUploadTrigger(root) {
        if (!root) return null;
        return root.querySelector('.upload-trigger_wrapProduct__caAk7, .upload-trigger_wrap__kMsdx')
            || [...root.querySelectorAll('button, div, span, label, a')].find((el) => {
                if (!isVisible(el)) return false;
                const t = clean(el.innerText);
                return t.includes('素材中心');
            })
            || null;
    }

    function findClickableText(root, texts) {
        if (!root) return null;
        const terms = Array.isArray(texts) ? texts : [texts];
        return [...root.querySelectorAll('button, div, span, label, a')]
            .find((el) => {
                if (!isVisible(el)) return false;
                const t = clean(el.innerText);
                return terms.some((term) => t === term || t.includes(term));
            }) || null;
    }

    function findTopDialogWithText(text) {
        const dialogs = [...document.querySelectorAll('div, section, form, article')].filter((el) => {
            const t = clean(el.innerText);
            return t.includes(text) && isVisible(el);
        });
        if (!dialogs.length) return null;
        return dialogs.sort((a, b) => {
            const ar = a.getBoundingClientRect();
            const br = b.getBoundingClientRect();
            return (br.width * br.height) - (ar.width * ar.height);
        })[0];
    }

    async function waitFor(fn, timeoutMs = 10000, stepMs = 120) {
        const imageTiming = getImageTimingConfig();
        timeoutMs = timeoutMs || imageTiming.genericTimeoutMs;
        stepMs = stepMs || imageTiming.shortPollMs;
        const start = Date.now();
        while (Date.now() - start < timeoutMs) {
            const result = fn();
            if (result) return result;
            await wait(stepMs);
        }
        return null;
    }

    async function waitForStableResult(fn, timeoutMs = 10000, stepMs = 60, stableMs = 220) {
        const imageTiming = getImageTimingConfig();
        timeoutMs = timeoutMs || imageTiming.genericTimeoutMs;
        stepMs = stepMs || imageTiming.stableStepMs;
        stableMs = stableMs || imageTiming.listStableMs;
        const start = Date.now();
        let stableStart = 0;
        let lastKey = '';
        let lastResult = null;
        while (Date.now() - start < timeoutMs) {
            const result = fn();
            if (!result) {
                stableStart = 0;
                lastKey = '';
                lastResult = null;
                await wait(stepMs);
                continue;
            }

            const rect = typeof result.getBoundingClientRect === 'function' ? result.getBoundingClientRect() : null;
            const key = rect
                ? `${clean(result.innerText || result.textContent || '')}|${Math.round(rect.left)}|${Math.round(rect.top)}|${Math.round(rect.width)}|${Math.round(rect.height)}`
                : clean(result.innerText || result.textContent || '');

            if (key !== lastKey) {
                lastKey = key;
                lastResult = result;
                stableStart = Date.now();
            } else if (Date.now() - stableStart >= stableMs) {
                return lastResult;
            }

            await wait(stepMs);
        }
        return null;
    }

    async function ensureMaterialCenterDialog() {
        const dialog = findTopDialogWithText('素材中心') || findTopDialogWithText('上传列表') || findTopDialogWithText('本地上传');
        if (!dialog) throw new Error('未找到素材中心弹层');
        return dialog;
    }

    async function openMaterialCenterFromGoodsMaterial() {
        const item = findCarouselItem() || findMaterialItem();
        if (!item) throw new Error('未找到商品轮播图区域');

        const trigger = findUploadTrigger(item) || findClickableText(item, ['素材中心', '进入素材中心']);
        if (!trigger) throw new Error('未找到商品轮播图入口');

        click(trigger.closest('button') || trigger);
        let dialog = await waitFor(() => ensureMaterialCenterDialog().catch(() => null), 4000);
        if (!dialog) {
            click(item);
            await wait(getImageTimingConfig().retryOpenMs ?? 120);
            click(trigger.closest('button') || trigger);
            dialog = await waitFor(() => ensureMaterialCenterDialog().catch(() => null), 8000);
        }
        if (!dialog) throw new Error('商品素材图素材中心没有打开');
        return dialog;
    }

    async function handleUploadResultDialog(stableMs = getImageTimingConfig().listStableMs) {
        const imageTiming = getImageTimingConfig();
        const listBtn = await waitForStableResult(() => {
            const candidates = [...document.querySelectorAll('button, div, span, label, a')];
            return candidates.find((el) => isVisible(el) && clean(el.innerText) === '在列表中查看')
                || candidates.find((el) => clean(el.innerText) === '在列表中查看')
                || null;
        }, imageTiming.dialogTimeoutMs, imageTiming.pollStepMs, stableMs);
        if (listBtn) {
            click(listBtn.closest('button') || listBtn);
            await waitFor(() => {
                const dialog = findTopDialogWithText('上传列表') || findTopDialogWithText('素材中心');
                if (!dialog) return null;
                const cards = dialog.querySelectorAll('div[class*="cardContainer"]');
                const confirm = [...dialog.querySelectorAll('button, [role="button"]')]
                    .find((el) => isVisible(el) && clean(el.innerText || el.textContent || '') === '确认');
                return cards.length || confirm ? dialog : null;
            }, imageTiming.listReadyTimeoutMs, imageTiming.pollStepMs);
        }
    }

    async function waitForPreviewListViewReady(timeoutMs = 8000) {
        const imageTiming = getImageTimingConfig();
        return await waitFor(() => {
            const dialog = findTopDialogWithText('素材中心') || findTopDialogWithText('上传列表');
            if (!dialog) return null;
            const candidates = [...dialog.querySelectorAll('button, div, span, label, a')];
            return candidates.find((el) => isVisible(el) && clean(el.innerText) === '在列表中查看')
                || candidates.find((el) => clean(el.innerText) === '在列表中查看')
                || null;
        }, timeoutMs || imageTiming.dialogTimeoutMs, imageTiming.fastPollMs);
    }

    async function clickConfirmInMaterialCenter(stableMs = getImageTimingConfig().confirmStableMs) {
        const imageTiming = getImageTimingConfig();
        const confirm = await waitForStableResult(() => {
            return [...document.querySelectorAll('button, [role="button"]')]
                .filter((el) => isVisible(el))
                .map((el) => ({
                    el,
                    r: el.getBoundingClientRect(),
                    text: clean(el.innerText || el.textContent || ''),
                    disabled: el.disabled || el.getAttribute('aria-disabled') === 'true' || el.classList.contains('disabled')
                }))
                .filter(({ text, r, disabled }) => text === '确认' && r.width > 60 && r.height > 24 && !disabled)
                .sort((a, b) => b.r.top - a.r.top)
                .at(0)?.el
                || null;
        }, imageTiming.dialogTimeoutMs, imageTiming.stableStepMs, stableMs);

        if (confirm) {
            const target = confirm.closest('button') || confirm;
            if (typeof target.click === 'function') target.click();
            else click(target);
            await wait(imageTiming.confirmPostMs);
            await waitFor(() => !findTopDialogWithText('素材中心') && !findTopDialogWithText('上传列表'), imageTiming.listReadyTimeoutMs, imageTiming.shortPollMs);
            return true;
        }
        return false;
    }

    function baseName(file) {
        return file.name.replace(/\.[^.]+$/, '');
    }

    function parseCarouselOrderToken(token) {
        const text = clean(String(token || '')).toLowerCase();
        if (!text) return null;
        const match = text.match(/^(\d+)(?:st|nd|rd|th)?$/);
        if (!match) return null;
        const index = Number(match[1]);
        if (!Number.isFinite(index) || index <= 0) return null;
        return index - 1;
    }

    function isCarouselCustomMode() {
        const carouselConfig = getCarouselUploadConfig();
        return clean(carouselConfig.mode || 'sequential').toLowerCase() === 'custom';
    }

    function getCarouselCustomIndexes() {
        const carouselConfig = getCarouselUploadConfig();
        const customTokens = Array.isArray(carouselConfig.customOrderTop10) ? carouselConfig.customOrderTop10 : [];
        const indexes = [];
        const used = new Set();
        for (const token of customTokens) {
            const idx = parseCarouselOrderToken(token);
            if (idx == null || used.has(idx)) continue;
            used.add(idx);
            indexes.push(idx);
        }
        return indexes;
    }

    function buildCarouselOrderFiles(files) {
        if (!isCarouselCustomMode()) return files;

        const customIndexes = getCarouselCustomIndexes();
        const picked = [];
        const used = new Set();
        for (const idx of customIndexes) {
            const file = files[idx];
            if (!file) continue;
            const key = `${file.name}|${file.size}|${file.lastModified}`;
            if (used.has(key)) continue;
            used.add(key);
            picked.push(file);
        }

        return picked.length ? picked : files;
    }

    function findMaterialCardsInDialog(dialog) {
        return [...dialog.querySelectorAll('div[class*="cardContainer"]')]
            .filter(isVisible)
            .map((card) => {
                const text = clean(card.textContent);
                const nameEl = [...card.querySelectorAll('div,span,a')].find((el) => {
                    const t = clean(el.textContent);
                    return t && !t.includes('裁剪') && !t.includes('美化') && !t.includes('翻译') && /^[A-Za-z0-9_-]+$/.test(t);
                });
                return {
                    card,
                    text,
                    name: nameEl ? clean(nameEl.textContent) : ''
                };
            })
            .filter((item) => item.name);
    }

    function findMatchedMaterialCardsByIndex(cards) {
        return getCarouselCustomIndexes()
            .map((idx) => cards[idx] || null)
            .filter(Boolean);
    }

    function findMatchedMaterialCards(cards, files) {
        const targetNames = files.map(baseName);
        return targetNames
            .map((name) => cards.find((card) => card.name === name || card.text.includes(name)))
            .filter(Boolean);
    }

    async function clearSelectedMaterialCards(cards) {
        const checkedCards = cards.filter((item) => item.card.className.includes('checked'));
        for (const item of checkedCards) {
            click(item.card);
            await wait(getImageTimingConfig().cardToggleMs ?? 120);
        }
    }

    async function selectMaterialCards(cards) {
        for (const item of cards) {
            click(item.card);
            await wait(getImageTimingConfig().cardSelectMs ?? 150);
        }
    }

    async function reorderMaterialCards(files) {
        const dialog = await waitFor(() => {
            const root = findTopDialogWithText('上传列表') || findTopDialogWithText('素材中心');
            if (!root) return null;
            const cards = root.querySelectorAll('div[class*="cardContainer"]');
            return cards.length ? root : null;
        }, getImageTimingConfig().listReadyTimeoutMs, getImageTimingConfig().shortPollMs) || await ensureMaterialCenterDialog();
        let cards = findMaterialCardsInDialog(dialog);
        if (!cards.length) return;

        const orderedFiles = buildCarouselOrderFiles(files);
        const expectedCount = isCarouselCustomMode() ? orderedFiles.length : files.length;
        const settledCards = await waitFor(() => {
            const currentCards = findMaterialCardsInDialog(dialog);
            if (!currentCards.length) return null;
            if (expectedCount > 0 && currentCards.length < expectedCount) return null;
            return currentCards;
        }, getImageTimingConfig().listReadyTimeoutMs, getImageTimingConfig().shortPollMs);
        cards = settledCards || cards;
        if (!cards.length) return;

        let matchedCards = findMatchedMaterialCards(cards, orderedFiles);
        if (isCarouselCustomMode() && (!matchedCards.length || matchedCards.length < orderedFiles.length)) {
            matchedCards = findMatchedMaterialCardsByIndex(cards);
        }

        if (!matchedCards.length) return;

        await clearSelectedMaterialCards(cards);
        await selectMaterialCards(matchedCards);
    }

    async function waitForMaterialUploadInput(timeoutMs = 10000) {
        const start = Date.now();
        while (Date.now() - start < timeoutMs) {
            const input = [...document.querySelectorAll('input[type="file"]')].find((el) => {
                const accept = (el.getAttribute('accept') || '').toLowerCase();
                const parentText = clean(el.parentElement?.textContent || el.closest('div,section,article,form')?.textContent || '');
                return parentText.includes('本地上传') && accept.includes('.png') && accept.includes('.jpg');
            });
            if (input) return input;
            await wait(getImageTimingConfig().uploadInputPollMs ?? 100);
        }
        return null;
    }

    async function waitForOuterPackagingUploadInput(timeoutMs = 10000) {
        const start = Date.now();
        while (Date.now() - start < timeoutMs) {
            const input = [...document.querySelectorAll('input[type="file"]')].find((el) => {
                const accept = (el.getAttribute('accept') || '').toLowerCase();
                const parentText = clean(el.parentElement?.textContent || el.closest('div,section,article,form')?.textContent || '');
                return parentText.includes('批量上传') && accept.includes('.png') && accept.includes('.jpg');
            });
            if (input) return input;
            await wait(getImageTimingConfig().uploadInputPollMs ?? 100);
        }
        return null;
    }

    async function uploadCarouselImages(images) {
        await openMaterialCenterFromGoodsMaterial();
        const fileInput = await waitForMaterialUploadInput(10000);
        if (!fileInput) throw new Error('未找到素材中心里的本地上传输入框');

        setFiles(fileInput, images);
        await handleUploadResultDialog();
        await reorderMaterialCards(images);
        await clickConfirmInMaterialCenter();
    }

    async function uploadOuterPackagingLastImage(images) {
        const lastImage = images[images.length - 1];
        const item = findOuterPackagingItem();
        if (!item) throw new Error('未找到外包装图片区');

        const trigger = findClickableText(item, ['批量上传']);
        if (!trigger) throw new Error('未找到外包装图片上传按钮');

        click(trigger.closest('button') || trigger);
        const fileInput = await waitForOuterPackagingUploadInput(getImageTimingConfig().outerUploadInputTimeoutMs);
        if (!fileInput) throw new Error('未找到外包装图片的本地上传输入框');

        setFiles(fileInput, [lastImage]);
    }

    async function uploadPreviewLastImage(images) {
        const previewConfig = getPreviewUploadConfig();
        const previewEntries = findPreviewEntries();
        if (!previewEntries.length) {
            const lastImage = images[images.length - 1];
            const item = findPreviewItem();
            if (!item) throw new Error('未找到预览图区域');

            const trigger = findUploadTrigger(item) || findClickableText(item, ['素材中心', '预览图']);
            if (!trigger) throw new Error('未找到预览图素材中心入口');

            click(trigger.closest('button') || trigger);
            const dialog = await waitFor(() => ensureMaterialCenterDialog().catch(() => null), getImageTimingConfig().dialogTimeoutMs, getImageTimingConfig().shortPollMs);
            if (!dialog) throw new Error('预览图素材中心没有打开');

            const fileInput = await waitForMaterialUploadInput(getImageTimingConfig().genericTimeoutMs);
            if (!fileInput) throw new Error('未找到预览图素材中心里的本地上传输入框');

            setFiles(fileInput, [lastImage]);
            await handleUploadResultDialog(getImageTimingConfig().previewStableMs);
            await clickConfirmInMaterialCenter(getImageTimingConfig().previewStableMs);
            return;
        }

        if (previewEntries.length === 1) {
            const onlyImage = resolveImageByPick(images, previewConfig.singleEntryImage) || images[images.length - 1];
            const onlyItem = previewEntries[0].item;
            const trigger = findUploadTrigger(onlyItem) || findClickableText(onlyItem, ['素材中心', '预览图']);
            if (!trigger) throw new Error('未找到预览图素材中心入口');

            click(trigger.closest('button') || trigger);
            const dialog = await waitFor(() => ensureMaterialCenterDialog().catch(() => null), getImageTimingConfig().dialogTimeoutMs, getImageTimingConfig().shortPollMs);
            if (!dialog) throw new Error('预览图素材中心没有打开');

            const fileInput = await waitForMaterialUploadInput(getImageTimingConfig().genericTimeoutMs);
            if (!fileInput) throw new Error('未找到预览图素材中心里的本地上传输入框');

            setFiles(fileInput, [onlyImage]);
            await handleUploadResultDialog(getImageTimingConfig().previewStableMs);
            await clickConfirmInMaterialCenter(getImageTimingConfig().previewStableMs);
            return;
        }

        const fallbackByOrder = Array.isArray(previewConfig.multiEntryFallbackByOrder)
            ? previewConfig.multiEntryFallbackByOrder
            : [];

        for (let i = 0; i < previewEntries.length; i++) {
            const entry = previewEntries[i];
            const rule = resolvePreviewRuleForEntry(entry);
            const fallbackPick = fallbackByOrder[i] || previewConfig.singleEntryImage;
            const image = resolveImageByPick(images, rule?.image || fallbackPick);
            if (!image) continue;

            const trigger = findUploadTrigger(entry.item) || findClickableText(entry.item, ['素材中心', '预览图']);
            if (!trigger) {
                console.warn(`[Temu助手] 未找到预览图素材中心入口:${entry.rowText}`);
                continue;
            }

            click(trigger.closest('button') || trigger);
            const dialog = await waitFor(() => ensureMaterialCenterDialog().catch(() => null), getImageTimingConfig().dialogTimeoutMs, getImageTimingConfig().shortPollMs);
            if (!dialog) throw new Error(`预览图素材中心没有打开:${entry.rowText}`);

            const fileInput = await waitForMaterialUploadInput(getImageTimingConfig().genericTimeoutMs);
            if (!fileInput) throw new Error(`未找到预览图素材中心里的本地上传输入框:${entry.rowText}`);

            setFiles(fileInput, [image]);
            await handleUploadResultDialog(getImageTimingConfig().previewStableMs);
            await clickConfirmInMaterialCenter(getImageTimingConfig().previewStableMs);
            await waitFor(() => !findTopDialogWithText('素材中心') && !findTopDialogWithText('上传列表'), getImageTimingConfig().listReadyTimeoutMs, getImageTimingConfig().shortPollMs);
        }
    }

    async function uploadMaterialAndOuterPackaging(sku) {
        const images = await getSkuImages(sku);
        await uploadCarouselImages(images);
        await Promise.all([
            uploadOuterPackagingLastImage(images),
            uploadPreviewLastImage(images)
        ]);
    }

    function readSharedSku() {
        const source = document.getElementById('temu-onekey-source');
        const raw = clean(source?.value || '');
        if (!raw) return '';
        return clean(raw.split(/\s+/)[0] || '');
    }

    window.__TEMU_UPLOAD_RUN__ = async function (sku) {
        const finalSku = clean(sku || readSharedSku());
        if (!finalSku) throw new Error('未找到可用于上传图片的货号');
        await uploadMaterialAndOuterPackaging(finalSku);
    };
    window.__TEMU_UPLOAD_PREPARE__ = prepareRootHandle;
})();