Greasy Fork

SKU填充

将剪切板内容拆分多行并快速填充到SKU当中哟~

// ==UserScript==
// @name         SKU填充
// @version      0.1.6
// @description  将剪切板内容拆分多行并快速填充到SKU当中哟~
// @author       鹿秋夏
// @include      https://sell.publish.tmall.com/tmall/publish.htm?*
// @namespace none
// ==/UserScript==

(() => {
    "use strict";
    const injectSkuButton = () => {
        const STYLE_PROPS = [
            'width', 'height', 'margin', 'padding', 'border', 'borderRadius',
            'font', 'color', 'background', 'cursor', 'boxSizing', 'lineHeight',
            'display', 'alignItems', 'justifyContent', 'flexDirection'
        ];

        const copyStyles = (s, t) => {
            const style = getComputedStyle(s);
            STYLE_PROPS.forEach(p => t.style[p] = style[p]);
            Object.assign(t.style, {
                display: 'inline-flex',
                alignItems: 'center',
                justifyContent: 'center',
                lineHeight: 'normal',
                verticalAlign: 'middle',
                whiteSpace: 'nowrap'
            });
        };

        const createActionHandlers = () => {
            const waiter = (x, t = 5e3) => new Promise((rs, rj) => {
                const check = (s = Date.now()) => setTimeout(() => {
                    const el = document.evaluate(x, document, null, 9).singleNodeValue;
                    el ? rs(el) : Date.now() - s > t ? rj(Error(`Element not found: ${x}`)) : check(s);
                }, 50);
                check();
            });

            const fillField = async (input, t) => {
                input.focus();
                input.select();
                document.execCommand('insertText', false, t);
                ['input', 'change', 'blur'].forEach(e => input.dispatchEvent(new Event(e, { bubbles: 1 })));
                Object.getOwnPropertyDescriptor(input.constructor.prototype, 'value')?.set?.call(input, t);
                await new Promise(r => setTimeout(r, 0)); 
            };

            return {
                fillHandler: async () => {
                    try {
                        const texts = (await navigator.clipboard.readText()).split('\n').map(l => l.trim()).filter(Boolean);
                        for (const text of texts) {
                            (await waiter('//div[@id="struct-p-1627207"]//button[contains(@class, "add")]')).click();
                            await fillField(await waiter('//input[@placeholder="主色(必选)" and @value=""]'), text);
                        }
                    } catch(e) {
                        console.error('填充操作失败:', e?.message || '未知错误');
                    }
                },
                replaceHandler: async () => {
                    try {
                        const texts = (await navigator.clipboard.readText()).split('\n').map(l => l.trim()).filter(Boolean);
                        const xpathResult = document.evaluate(
                            '//input[@placeholder="主色(必选)" and @value != ""]',
                            document,
                            null,
                            XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                            null
                        );

                        for (let i = 0; i < Math.min(texts.length, xpathResult.snapshotLength); i++) {
                            const input = xpathResult.snapshotItem(i);
                            await fillField(input, texts[i]); 
                            await new Promise(r => setTimeout(r, 50)); 
                        }
                    } catch(e) {
                        console.error('替换操作失败:', e?.message || '未知错误');
                    }
                }
            };
        };

        const createSkuButtons = (c, r) => {
            if (c.querySelectorAll('[data-sku]').length >= 2) return;

            const handlers = createActionHandlers();
            const baseMarginRight = getComputedStyle(r).marginRight;

            const createButton = (text, handler) => {
                const b = document.createElement('button');
                let pressed = false;

                b.textContent = text;
                b.dataset.sku = 'true';
                copyStyles(r, b);
                b.style.marginRight = baseMarginRight;

                const states = {
                    base: {
                        backgroundColor: '#FFEBF1',
                        color: '#FF335E',
                        transition: 'all 0.3s',
                        minWidth: `${r.offsetWidth}px`,
                        flexShrink: '0',
                        position: 'relative'
                    },
                    hover: { background: '#FFD1E0' },
                    active: { background: '#FFB5C7', boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.1)' }
                };

                Object.assign(b.style, states.base);

                const isInside = (el, { clientX: x, clientY: y }) => {
                    const rect = el.getBoundingClientRect();
                    return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
                };

                const handleUp = e => {
                    if (!pressed) return;
                    pressed = false;
                    const inside = isInside(b, e);
                    inside && handler().catch(e => console.error(`${text}失败:`, e?.message || '未知错误'));
                    Object.assign(b.style, inside ? states.hover : states.base);
                    b.style.boxShadow = '';
                };

                Object.entries({
                    mouseover: () => b.style.background = states.hover.background,
                    mouseout: () => !pressed && (b.style.background = states.base.backgroundColor),
                    mousedown: () => {
                        pressed = true;
                        Object.assign(b.style, states.active);
                        document.addEventListener('mouseup', handleUp, { once: true });
                    }
                }).forEach(([e, f]) => b.addEventListener(e, f));

                return b;
            };

            const buttonsContainer = r.parentNode;
            const fillBtn = createButton('SKU填充', handlers.fillHandler);
            const replaceBtn = createButton('SKU替换', handlers.replaceHandler);
            buttonsContainer.append(fillBtn, replaceBtn);

            console.log('SKU功能已加载');
        };

        const init = () => {
            const c = document.evaluate('//div[@class="front"]', document, null, 9).singleNodeValue;
            const r = c?.querySelector('button:not([data-sku])');
            c && r && createSkuButtons(c, r);
        };

        const start = () => {
            init();
            const timer = setInterval(() => {
                const existingButtons = document.querySelectorAll('[data-sku]');
                if (existingButtons.length < 2) init();
            }, 1e3);

            const c = document.evaluate('//div[@class="front"]', document, null, 9).singleNodeValue;

            c && (c._observer = new MutationObserver(() => {
                const existingButtons = c.querySelectorAll('[data-sku]');
                if (existingButtons.length < 2) init();
            })).observe(c, { childList: true, subtree: true });

            window.addEventListener('unload', () => {
                clearInterval(timer);
                c?._observer?.disconnect();
            });
        };

        (function checkContainer() {
            document.evaluate('//div[@class="front"]', document, null, 9).singleNodeValue
                ? start()
                : setTimeout(checkContainer, 100);
        })();
    };

    injectSkuButton();
})();