Greasy Fork

Greasy Fork is available in English.

店小蜜价格助手

5倍价格

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         店小蜜价格助手
// @namespace    http://tampermonkey.net/
// @version      1.6.5
// @description  5倍价格
// @author       Rayu
// @match        https://www.dianxiaomi.com/web/shopeeSite/edit*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 配置项 ---
    const HIGHLIGHT_COLOR = 'yellow';     // 高亮颜色
    const MULTIPLE_THRESHOLD = 5;         // 倍数阈值 (例如,5代表5倍)
    const INCLUDE_EQUAL = true;           // 如果价格等于阈值倍数,是否也高亮 (true为高亮,false为严格大于)
    const DEBOUNCE_DELAY = 300;           // 防抖延迟,单位毫秒 (避免频繁更新)

    let debounceTimer; // 用于防抖的定时器
    // 存储 SKU 名称到其对应元素(价格输入、复选框)的映射,提高查找效率和准确性
    let skuElementsMap = new Map(); // Key: SKU Name (as string), Value: { priceInput: HTMLElement, checkboxInput: HTMLElement }

    /**
     * 清理 SKU 名称,去除不必要的空格或特殊字符,使其更适合匹配
     * @param {string} name - 原始 SKU 名称
     * @returns {string} - 清理后的 SKU 名称
     */
    function cleanSkuName(name) {
        // 使用正则表达式替换所有空白字符(包括空格、制表符、换行符等)为一个空格,并去除首尾空格
        return name ? name.replace(/\s+/g, ' ').trim() : '';
    }

    /**
     * 获取所有有效的 SKU 价格输入框,并建立 SKU 名称与元素(价格输入、复选框)的映射
     * @returns {Array<Object>} 包含 { name, price, priceInput, checkboxInput } 的数组
     */
    function getSkuData() {
        console.log('[Shopee Price Highlighter & Filter] Collecting SKU data...');
        const priceInputs = document.querySelectorAll('#skuDataInfo tbody tr td:nth-child(4) input.g-form-component');
        const skuData = [];
        skuElementsMap.clear(); // 每次收集前清空,确保最新状态

        priceInputs.forEach((priceInput, index) => {
            const row = priceInput.closest('tr');
            if (!row) return;

            let rawSkuName = '';
            // 尝试从行中获取 SKU 名称
            // 优先级:
            // 1. data-sku-name属性
            // 2. 表格第二个td的直接文本内容 (根据最新反馈)
            // 3. 表格第一个td中的特定子元素 (span[title], input, .sku-name-text, .break-all)

            rawSkuName = row.dataset.skuName; // 尝试从自定义属性获取

            if (!rawSkuName) { // 如果没有自定义属性,则从DOM解析
                const secondTd = row.querySelector('td:nth-child(2)'); // 尝试获取第二个td
                if (secondTd && secondTd.textContent.trim()) {
                    rawSkuName = secondTd.textContent.trim(); // 获取其直接文本内容
                } else {
                    // 如果第二个td没有内容,或者不存在,再尝试第一个td的子元素 (兼容旧版或其他布局)
                    const firstTd = row.querySelector('td:first-child');
                    if (firstTd) {
                        const skuNameEl = firstTd.querySelector('span[title]') ||
                                          firstTd.querySelector('.ant-input[data-v-9bbdc5ab]') ||
                                          firstTd.querySelector('.sku-name-text') ||
                                          firstTd.querySelector('span.break-all');
                        rawSkuName = skuNameEl ? skuNameEl.title || skuNameEl.value || skuNameEl.textContent : '';
                    }
                }
            }
            const skuName = cleanSkuName(rawSkuName);

            // 如果SKU名称仍然为空,记录详细日志
            if (!skuName) {
                console.warn(`[Shopee Price Highlighter & Filter] Failed to extract SKU name for row #${index}. Defaulting to "Unknown SKU". Row HTML:`, row.outerHTML);
                // 确保即使名称为空,也有一个可识别的placeholder
                const tempSkuName = 'Unknown SKU for Row ' + index;
                rawSkuName = tempSkuName; // 确保后面的 findCheckboxForSkuName 不会因为空字符串而崩溃
            }

            // 查找对应的复选框
            // 只有当 skuName 不是 "Unknown SKU" 并且有实际内容时才尝试匹配复选框
            let checkboxInput = null;
            if (skuName && !skuName.startsWith('Unknown SKU')) {
                checkboxInput = findCheckboxForSkuName(skuName, index);
            }
            if (!checkboxInput && skuName && !skuName.startsWith('Unknown SKU')) { // 仅当未找到且 SKU 名称有效时记录警告
                console.warn(`[Shopee Price Highlighter & Filter] No checkbox found for SKU (from price table) #${index}: "${skuName}"`);
            }

            const price = parseFloat(priceInput.value);
            if (!isNaN(price) && price > 0) {
                const data = {
                    name: skuName, // 使用清理后的SKU名称
                    price: price,
                    priceInput: priceInput,
                    checkboxInput: checkboxInput // 可能为 null
                };
                skuData.push(data);
                // 存储到 map 中,key 使用清理后的 SKU 名称
                skuElementsMap.set(skuName, { priceInput: priceInput, checkboxInput: checkboxInput });
            }
        });
        console.log(`[Shopee Price Highlighter & Filter] Found ${skuData.length} valid SKUs in price table. ${skuData.filter(s => s.checkboxInput).length} SKUs successfully linked to checkboxes.`);
        return skuData;
    }

    /**
     * 根据 SKU 名称查找对应的复选框
     * 优化查找逻辑,使其更健壮
     */
    function findCheckboxForSkuName(skuName, skuIndexForLog = -1) {
        if (!skuName || skuName.startsWith('Unknown SKU')) { // 修正:如果skuName是空的或者'Unknown SKU',直接返回null
            console.warn(`[Shopee Price Highlighter & Filter] findCheckboxForSkuName called with empty or generic skuName ("${skuName}") for index #${skuIndexForLog}. Cannot match checkbox.`);
            return null;
        }

        let foundCheckbox = null;

        // 1. 尝试精确匹配 input[value] 属性 (最理想情况)
        foundCheckbox = document.querySelector(`.checkbox-group input[type="checkbox"][value="${skuName}"]`);
        if (foundCheckbox) {
            console.log(`[Shopee Price Highlighter & Filter] SKU #${skuIndexForLog} "${skuName}" matched checkbox by exact 'value' attribute.`);
            return foundCheckbox;
        }

        // 2. 尝试遍历所有 checkbox-item,精确匹配其内部 theme-value-text 的 title 属性
        const allCheckboxItems = document.querySelectorAll('.checkbox-group .checkbox-item');
        for (let i = 0; i < allCheckboxItems.length; i++) {
            const item = allCheckboxItems[i];
            const cb = item.querySelector('input[type="checkbox"]');
            const textEl = item.querySelector('.theme-value-edit .theme-value-text');
            if (cb && textEl) {
                // 清理并比较 title 属性
                if (cleanSkuName(textEl.title) === skuName) {
                    console.log(`[Shopee Price Highlighter & Filter] SKU #${skuIndexForLog} "${skuName}" matched checkbox by exact 'title' attribute: "${textEl.title}"`);
                    return cb;
                }
            }
        }

        // 3. 尝试遍历所有 checkbox-item,模糊匹配其内部 theme-value-text 的 textContent (最宽松,但也最容易误判)
        // 只有当精确匹配都失败时才尝试
        for (let i = 0; i < allCheckboxItems.length; i++) {
            const item = allCheckboxItems[i];
            const cb = item.querySelector('input[type="checkbox"]');
            const textEl = item.querySelector('.theme-value-edit .theme-value-text');
            if (cb && textEl) {
                const cleanedTextContent = cleanSkuName(textEl.textContent);
                // 检查 SKU 名称是否包含在文本内容中,或者文本内容是否包含在 SKU 名称中
                // 避免 'Unknown SKU' 与实际内容模糊匹配,因为 'Unknown SKU' 本身就是个错误
                if (cleanedTextContent && skuName && (cleanedTextContent.includes(skuName) || skuName.includes(cleanedTextContent))) {
                    console.warn(`[Shopee Price Highlighter & Filter] SKU #${skuIndexForLog} "${skuName}" fuzzy matched checkbox by textContent: "${cleanedTextContent}"`);
                    return cb;
                }
            }
        }

        return null;
    }


    /**
     * 查找并高亮价格 (V1.4 版本的高亮逻辑)
     */
    function highlightPrices() {
        console.log('[Shopee Price Highlighter & Filter] Executing price highlight (V1.4 Logic)...');
        const skuData = getSkuData(); // 重新获取最新 SKU 数据

        if (skuData.length === 0) {
            console.log('[Shopee Price Highlighter & Filter] No SKU data for highlighting.');
            return;
        }

        const validPrices = skuData.map(s => s.price).filter(price => !isNaN(price) && price > 0);

        if (validPrices.length === 0) {
            console.log('[Shopee Price Highlighter & Filter] No valid prices for comparison. Clearing highlights.');
            // 清除所有高亮
            skuData.forEach(sku => { sku.priceInput.style.backgroundColor = ''; });
            return;
        }

        const minPrice = Math.min(...validPrices);
        const thresholdPrice = minPrice * MULTIPLE_THRESHOLD;

        console.log(`[Shopee Price Highlighter & Filter] Global Min Price (from all valid SKUs): ${minPrice}, Threshold: ${thresholdPrice} (${MULTIPLE_THRESHOLD}x Min)`);

        skuData.forEach(sku => {
            sku.priceInput.style.backgroundColor = ''; // 清除之前的高亮
            if (!isNaN(sku.price)) {
                if (INCLUDE_EQUAL) {
                    if (sku.price >= thresholdPrice) {
                        sku.priceInput.style.backgroundColor = HIGHLIGHT_COLOR;
                    }
                } else {
                    if (sku.price > thresholdPrice) {
                        sku.priceInput.style.backgroundColor = HIGHLIGHT_COLOR;
                    }
                }
            }
        });
    }

    /**
     * 模拟点击复选框以改变其状态,并触发 change 事件
     * @param {HTMLElement} checkboxInput - 复选框元素
     * @param {boolean} checked - 期望的状态 (true 为勾选,false 为取消勾选)
     */
    function setCheckboxState(checkboxInput, checked) {
        if (!checkboxInput) {
            console.warn('[Shopee Price Highlighter & Filter] Attempted to set state for null checkboxInput.');
            return;
        }
        if (checkboxInput.checked === checked) {
            return; // 已经处于期望状态
        }
        checkboxInput.checked = checked;
        // 模拟触发 change 事件,让店小秘的内部逻辑响应
        const event = new Event('change', { bubbles: true });
        checkboxInput.dispatchEvent(event);

        // 获取对应的SKU名称用于日志
        let skuNameForLog = cleanSkuName(checkboxInput.value); // 优先使用 checkbox 的 value
        const item = checkboxInput.closest('.checkbox-item');
        if (item) {
            const titleSpan = item.querySelector('.theme-value-text');
            if (titleSpan) skuNameForLog = cleanSkuName(titleSpan.title || titleSpan.textContent);
        }
        console.log(`[Shopee Price Highlighter & Filter] Checkbox for "${skuNameForLog}" set to ${checked}`);
    }

    /**
     * 去掉最低价 SKU
     */
    function removeLowestPriceSku() {
        // 这里仍然只考虑当前勾选的 SKU 进行操作,因为我们只希望操作用户当前可见且激活的 SKU
        const skuData = getSkuData().filter(s => s.checkboxInput && s.checkboxInput.checked);
        if (skuData.length <= 1) {
            alert('至少需要保留一个 SKU。');
            return;
        }

        const minPrice = Math.min(...skuData.map(s => s.price));
        // 找到所有最低价的SKU(可能不止一个),取第一个
        const lowestSku = skuData.find(s => s.price === minPrice);

        if (lowestSku) {
            setCheckboxState(lowestSku.checkboxInput, false);
            console.log(`[Shopee Price Highlighter & Filter] Removed lowest price SKU: ${lowestSku.name} (${lowestSku.price})`);
            // 等待 DOM 更新,然后重新高亮
            setTimeout(highlightPrices, DEBOUNCE_DELAY);
        } else {
            console.log('[Shopee Price Highlighter & Filter] No lowest price active SKU found to remove.');
        }
    }

    /**
     * 去掉最高价 SKU
     */
    function removeHighestPriceSku() {
        const skuData = getSkuData().filter(s => s.checkboxInput && s.checkboxInput.checked); // 只考虑当前勾选的
        if (skuData.length <= 1) {
            alert('至少需要保留一个 SKU。');
            return;
        }

        const maxPrice = Math.max(...skuData.map(s => s.price));
        // 找到所有最高价的SKU(可能不止一个),取第一个
        const highestSku = skuData.find(s => s.price === maxPrice);

        if (highestSku) {
            setCheckboxState(highestSku.checkboxInput, false);
            console.log(`[Shopee Price Highlighter & Filter] Removed highest price SKU: ${highestSku.name} (${highestSku.price})`);
            // 等待 DOM 更新,然后重新高亮
            setTimeout(highlightPrices, DEBOUNCE_DELAY);
        } else {
            console.log('[Shopee Price Highlighter & Filter] No highest price active SKU found to remove.');
        }
    }


    // --- 启动 MutationObserver 实时监听DOM变化 ---
    const observerCallback = function(mutationsList, observer) {
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => {
            highlightPrices();
        }, DEBOUNCE_DELAY);
    };

    let observerRetryCount = 0;
    const maxObserverRetries = 10;
    const observerRetryInterval = 500; // 500ms

    function initializeObserver() {
        const targetNode = document.getElementById('skuDataInfo');

        if (targetNode) {
            const config = {
                childList: true,    // 观察子节点的增加或删除 (添加/删除 SKU 行)
                subtree: true,      // 观察所有后代节点 (价格输入框的 value 变化)
                attributes: true,   // 观察属性变化
                attributeFilter: ['value'] // 只监听value属性的变化,优化性能
            };

            const observer = new MutationObserver(observerCallback);
            observer.observe(targetNode, config);

            console.log('[Shopee Price Highlighter & Filter] MutationObserver started, observing #skuDataInfo.');
            highlightPrices(); // 首次加载时执行一次高亮
        } else if (observerRetryCount < maxObserverRetries) {
            observerRetryCount++;
            console.warn(`[Shopee Price Highlighter & Filter] #skuDataInfo element not found on initial load. Retrying (${observerRetryCount}/${maxObserverRetries})...`);
            setTimeout(initializeObserver, observerRetryInterval);
        } else {
            console.error('[Shopee Price Highlighter & Filter] Failed to find #skuDataInfo after multiple retries. Script may not function correctly.');
        }
    }


    /**
     * 添加功能按钮
     */
    let addButtonsRetryCount = 0;
    const maxAddButtonsRetries = 10;
    const addButtonsRetryInterval = 500; // 500ms

    function addFilterButtons() {
        if (document.getElementById('skuFilterButtons')) {
            // 按钮已经存在,不再重复添加
            console.log('[Shopee Price Highlighter & Filter] Filter buttons already exist. Skipping addFilterButtons.');
            return;
        }

        // 精确瞄准用户指定的 div 元素
        const targetElementForButtons = document.querySelector("#skuDataInfo > div.form-card-content > div > div > div.mb-20.flex-justify-between");

        if (targetElementForButtons) {
            const buttonContainer = document.createElement('div');
            buttonContainer.id = 'skuFilterButtons';
            // 利用父元素的 flex-justify-between 样式,将按钮推到右侧
            buttonContainer.style.cssText = 'display: flex; gap: 10px; align-items: center;';

            const createButton = (text, onClickHandler) => {
                const button = document.createElement('button');
                button.className = 'ant-btn ant-btn-default css-1oz1bg8'; // 使用店小秘的按钮样式
                button.textContent = text;
                button.style.backgroundColor = '#e6f7ff'; // 蓝色系,与店小秘主题更搭
                button.style.borderColor = '#91d5ff';
                button.style.color = '#1890ff';
                button.style.minWidth = 'unset'; // 取消最小宽度限制,让按钮更紧凑
                button.style.padding = '4px 12px'; // 调整内边距
                button.style.height = '32px'; // 调整高度
                button.addEventListener('click', onClickHandler);
                return button;
            };

            buttonContainer.appendChild(createButton('移除最低价', removeLowestPriceSku));
            buttonContainer.appendChild(createButton('移除最高价', removeHighestPriceSku));

            // 直接追加到目标 div 内部
            targetElementForButtons.appendChild(buttonContainer);

            console.log('[Shopee Price Highlighter & Filter] Filter buttons successfully added to variant count section.');
            addButtonsRetryCount = 0; // 重置重试计数器
        } else if (addButtonsRetryCount < maxAddButtonsRetries) {
            addButtonsRetryCount++;
            console.warn(`[Shopee Price Highlighter & Filter] Target element for filter buttons not found yet. Retrying (${addButtonsRetryCount}/${maxAddButtonsRetries})...`);
            setTimeout(addFilterButtons, addButtonsRetryInterval);
        } else {
            console.error('[Shopee Price Highlighter & Filter] Failed to find target element for filter buttons after multiple retries.');
        }
    }


    // --- 启动脚本 ---
    function init() {
        initializeObserver();
        addFilterButtons(); // 首次尝试添加按钮
        // 额外的延迟执行,以防内容是异步加载的,确保按钮和高亮
        setTimeout(addFilterButtons, 1500); // 1.5秒后再次尝试添加按钮
        setTimeout(highlightPrices, 1500); // 1.5秒后再次尝试执行一次高亮
        setTimeout(addFilterButtons, 4000); // 4秒后再次尝试添加按钮
        setTimeout(highlightPrices, 4000); // 4秒后再次尝试执行一次高亮
    }

    // 等待 DOMContentLoaded 事件确保页面基本结构加载完成
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();