Greasy Fork

Greasy Fork is available in English.

SKU填充

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

当前为 2025-03-12 提交的版本,查看 最新版本

// ==UserScript==
// @name         SKU填充
// @version      0.1.5
// @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 createActionHandler = () => {
            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 t => {
                const input = await waiter('//input[@placeholder="主色(必选)" and @value=""]');
                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);
            };

            return 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(text);
                    }
                } catch(e) {
                    console.error('操作失败:', e?.message || '未知错误');
                }
            };
        };

        const createSkuButton = (c, r) => {
            if (c.querySelector('[data-sku]')) return;

            const b = document.createElement('button');
            let pressed = false;
            const handler = createActionHandler();

            b.textContent = 'SKU填充';
            b.dataset.sku = 'true';
            copyStyles(r, b);

            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('SKU填充失败:', 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));

            c.append(b);
            console.log('SKU功能已加载');
            return b;
        };

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

        const start = () => {
            init();
            const timer = setInterval(() => !document.querySelector('[data-sku]') && init(), 1e3);
            const c = document.evaluate('//div[@class="front"]', document, null, 9).singleNodeValue;

            c && (c._observer = new MutationObserver(() =>
                !c.querySelector('[data-sku]') && 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();
})();