Greasy Fork

Greasy Fork is available in English.

店小蜜价格助手

5倍价格

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

(function() {
    'use strict';

    // ========================================
    // 配置项
    // ========================================
    const CONFIG = {
        // 价格高亮配置
        HIGHLIGHT_COLOR: 'yellow',
        MULTIPLE_THRESHOLD: 5,
        INCLUDE_EQUAL: true,
        DEBOUNCE_DELAY: 300,
    };

    let debounceTimer;

    // ========================================
    // 公共函数:等待元素加载
    // ========================================
    function waitForElement(selector, callback, maxAttempts = 50) {
        let attempts = 0;
        const interval = setInterval(() => {
            const element = document.querySelector(selector);
            if (element || attempts >= maxAttempts) {
                clearInterval(interval);
                if (element) callback(element);
            }
            attempts++;
        }, 200);
    }

    // ========================================
    // 公共函数:智能查找价格列
    // ========================================
    function findPriceColumnIndex() {
        const table = document.querySelector('#skuDataInfo table.myj-table');
        if (!table) {
            console.warn('[价格工具] 未找到表格');
            return -1;
        }

        const headers = table.querySelectorAll('thead th');
        let priceColumnIndex = -1;

        headers.forEach((th, index) => {
            const text = th.textContent.trim();
            if ((text.includes('价格') && (text.includes('TWD') || text.includes('$')))
                && !text.includes('促销')) {
                priceColumnIndex = index + 1;
                console.log(`[价格工具] 找到"价格"列,索引为 ${priceColumnIndex},表头文本: "${text}"`);
            }
        });

        return priceColumnIndex;
    }

    // ========================================
    // 公共函数:获取价格输入框
    // ========================================
    function findPriceInputs() {
        const priceColumnIndex = findPriceColumnIndex();

        if (priceColumnIndex !== -1) {
            const selector = `#skuDataInfo tbody tr td:nth-child(${priceColumnIndex}) .sku-valid-item > div > input.g-form-component:not([disabled])`;
            const inputs = document.querySelectorAll(selector);
            if (inputs.length > 0) {
                console.log(`[价格工具] 通过表头匹配找到 ${inputs.length} 个价格输入框`);
                return Array.from(inputs);
            }
        }

        // 回退策略
        const fallbackSelectors = [
            '#skuDataInfo tbody tr td:nth-child(4) input.g-form-component:not([disabled])',
            '#skuDataInfo tbody tr td:nth-child(5) input.g-form-component:not([disabled])',
        ];

        for (let selector of fallbackSelectors) {
            const inputs = document.querySelectorAll(selector);
            if (inputs.length > 0) {
                console.log(`[价格工具] 使用回退选择器找到 ${inputs.length} 个价格输入框: ${selector}`);
                return Array.from(inputs);
            }
        }

        console.warn('[价格工具] 无法找到任何价格输入框');
        return [];
    }

    // ========================================
    // 功能1: 价格高亮显示
    // ========================================
    function highlightPrices() {
        console.log('[价格高亮] 执行价格高亮...');

        const priceInputs = findPriceInputs();

        if (priceInputs.length === 0) {
            console.log('[价格高亮] 未找到价格输入框');
            return;
        }

        const validPrices = [];
        const inputElements = [];

        priceInputs.forEach(input => {
            input.style.backgroundColor = '';

            const priceStr = input.value.trim();
            const price = parseFloat(priceStr);

            if (!isNaN(price) && price > 0) {
                validPrices.push(price);
                inputElements.push(input);
            }
        });

        if (validPrices.length === 0) {
            console.log('[价格高亮] 没有有效价格可供比较');
            return;
        }

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

        console.log(`[价格高亮] 最低价: ${minPrice}, 阈值: ${thresholdPrice} (${CONFIG.MULTIPLE_THRESHOLD}倍)`);

        let highlightCount = 0;
        inputElements.forEach(input => {
            const currentPrice = parseFloat(input.value);
            if (!isNaN(currentPrice)) {
                if (CONFIG.INCLUDE_EQUAL) {
                    if (currentPrice >= thresholdPrice) {
                        input.style.backgroundColor = CONFIG.HIGHLIGHT_COLOR;
                        highlightCount++;
                    }
                } else {
                    if (currentPrice > thresholdPrice) {
                        input.style.backgroundColor = CONFIG.HIGHLIGHT_COLOR;
                        highlightCount++;
                    }
                }
            }
        });

        console.log(`[价格高亮] 共高亮 ${highlightCount} 个价格,总计 ${inputElements.length} 个`);
    }

    // ========================================
    // 功能2: 移除极值价格商品
    // ========================================

    // 提取价格数据
    function extractPriceData() {
        const priceInputs = findPriceInputs();
        if (priceInputs.length === 0) return null;

        const priceData = [];

        // 获取所有复选框以便进行映射检查
        const allCheckboxes = getAllCheckboxes();
        console.log(`[DEBUG extractPriceData] 总共 ${allCheckboxes.length} 个复选框, ${priceInputs.length} 个价格输入框`);

        priceInputs.forEach((input, inputIndex) => {
            const value = input.value.trim();
            const price = parseFloat(value);

            if (!isNaN(price) && price > 0) {
                // 找到输入框所在的 tr 元素
                const tr = input.closest('tr');
                // 获取这个 tr 在 tbody 中的真实索引
                const tbody = tr.parentElement;
                const trIndexInTbody = Array.from(tbody.children).indexOf(tr);

                console.log(`[DEBUG extractPriceData] 价格输入框序号: ${inputIndex}, 价格: ${price}, tr在tbody中的索引: ${trIndexInTbody}`);

                // 使用输入框在有效输入框列表中的索引作为rowIndex
                priceData.push({
                    price: price,
                    rowIndex: inputIndex,  // 改用inputIndex而不是trIndexInTbody
                    input: input,
                    trIndexInTbody: trIndexInTbody  // 保留用于调试
                });
            }
        });

        console.log(`[价格移除] 成功提取 ${priceData.length} 个有效价格数据:`, priceData.map(d => `价格${d.price}@行${d.rowIndex}`).join(', '));
        return priceData.length > 0 ? priceData : null;
    }

    // 找到所有最低价和最高价的商品
    function findPriceExtremes(priceData) {
        if (!priceData || priceData.length === 0) return null;

        let minPriceValue = Math.min(...priceData.map(item => item.price));
        let maxPriceValue = Math.max(...priceData.map(item => item.price));

        let minPriceItems = priceData.filter(item => item.price === minPriceValue);
        let maxPriceItems = priceData.filter(item => item.price === maxPriceValue);

        return { minPriceItems, maxPriceItems };
    }

    // 获取分组的复选框信息
    function getCheckboxGroups() {
        const container1 = document.querySelector('#skuAttrInfo > div.form-card-content > div > form > div.sku-attrs-container > div:nth-child(1)');
        const container2 = document.querySelector('#skuAttrInfo > div.form-card-content > div > form > div.sku-attrs-container > div:nth-child(2)');

        const group1 = container1 ? Array.from(container1.querySelectorAll('input[type="checkbox"]:checked')) : [];
        const group2 = container2 ? Array.from(container2.querySelectorAll('input[type="checkbox"]:checked')) : [];

        console.log(`[DEBUG] 变种主题1: ${group1.length}个复选框, 变种主题2: ${group2.length}个复选框`);

        return { group1, group2, container1, container2 };
    }

    // 获取所有已选中的复选框(用于兼容性)
    function getAllCheckboxes() {
        const { group1, group2 } = getCheckboxGroups();
        return [...group1, ...group2];
    }

    // 根据行索引找到对应的复选框并取消勾选(笛卡尔积组合智能移除)
    function uncheckByRowIndex(rowIndex) {
        const { group1, group2 } = getCheckboxGroups();
        const totalCheckboxes = group1.length + group2.length;
        const cartesianProductCount = group1.length * group2.length;

        console.log(`[DEBUG] 主题1: ${group1.length}个复选框, 主题2: ${group2.length}个复选框`);
        console.log(`[DEBUG] 笛卡尔积组合数: ${group1.length}×${group2.length}=${cartesianProductCount}`);

        // 判断哪个变种主题的复选框数量更多(大主题)和更少(小主题)
        const largerGroup = group1.length > group2.length ? group1 : group2;
        const smallerGroup = group1.length > group2.length ? group2 : group1;
        const largerGroupName = group1.length > group2.length ? '变种主题1' : '变种主题2';

        console.log(`[DEBUG] 大主题:${largerGroupName} (${largerGroup.length}个复选框),小主题:${smallerGroup.length}个复选框`);

        let targetIndex;

        // 如果小主题复选框数量为0,直接使用rowIndex
        if (smallerGroup.length === 0) {
            targetIndex = rowIndex;
            console.log(`[DEBUG] 小主题复选框数量为0,直接使用行索引${rowIndex}`);
        } else {
            // 使用用户提供的公式计算大主题中要移除的复选框索引
            // 公式:CEILING((行索引+1)/小主题复选框数量, 1) - 1(减1是因为数组索引从0开始)
            targetIndex = Math.ceil((rowIndex + 1) / smallerGroup.length) - 1;
            console.log(`[DEBUG] 价格行索引${rowIndex} → 使用公式CEILING((${rowIndex}+1)/${smallerGroup.length},1)-1 = ${targetIndex}`);
        }

        console.log(`[DEBUG] 映射到${largerGroupName}的第${targetIndex}个复选框`);

        if (targetIndex >= 0 && targetIndex < largerGroup.length) {
            const checkbox = largerGroup[targetIndex];
            if (checkbox && checkbox.checked) {
                console.log(`[DEBUG] 点击${largerGroupName}的复选框${targetIndex}`);
                checkbox.click();
                return true;
            } else {
                console.log(`[DEBUG] ${largerGroupName}的复选框${targetIndex}不存在或未选中`);
                return false;
            }
        } else {
            console.log(`[DEBUG] 计算出的目标索引${targetIndex}超出${largerGroupName}范围(0-${largerGroup.length-1})`);
            return false;
        }
    }

    // 移除最低价商品(只移除第一个)
    async function removeMinPrice() {
        const priceData = extractPriceData();
        if (!priceData || priceData.length === 0) {
            alert('无法提取价格数据');
            return;
        }

        const extremes = findPriceExtremes(priceData);
        if (!extremes || !extremes.minPriceItems || extremes.minPriceItems.length === 0) {
            alert('无法找到最低价');
            return;
        }

        const { minPriceItems } = extremes;
        // 只移除第一个最低价
        const firstItem = minPriceItems[0];

        if (uncheckByRowIndex(firstItem.rowIndex)) {
            console.log(`已移除最低价商品: ¥${firstItem.price}`);
            alert(`成功移除最低价商品\n价格: ¥${firstItem.price}`);
            // 移除后重新高亮
            setTimeout(highlightPrices, 500);
        } else {
            alert('移除失败,请检查页面结构');
        }
    }

    // 移除最高价商品(只移除第一个)
    async function removeMaxPrice() {
        const priceData = extractPriceData();
        if (!priceData || priceData.length === 0) {
            alert('无法提取价格数据');
            return;
        }

        const extremes = findPriceExtremes(priceData);
        if (!extremes || !extremes.maxPriceItems || extremes.maxPriceItems.length === 0) {
            alert('无法找到最高价');
            return;
        }

        const { maxPriceItems } = extremes;
        // 只移除第一个最高价
        const firstItem = maxPriceItems[0];

        if (uncheckByRowIndex(firstItem.rowIndex)) {
            console.log(`已移除最高价商品: ¥${firstItem.price}`);
            alert(`成功移除最高价商品\n价格: ¥${firstItem.price}`);
            // 移除后重新高亮
            setTimeout(highlightPrices, 500);
        } else {
            alert('移除失败,请检查页面结构');
        }
    }

    // ========================================
    // UI: 创建操作按钮
    // ========================================
    function createButtons() {
        const container = document.createElement('div');
        container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 10000;
            display: flex;
            flex-direction: column;
            gap: 10px;
        `;

        const minButton = document.createElement('button');
        minButton.textContent = '移除最低价';
        minButton.style.cssText = `
            padding: 10px 20px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            transition: background-color 0.3s;
        `;

        minButton.addEventListener('mouseenter', () => {
            minButton.style.backgroundColor = '#45a049';
        });

        minButton.addEventListener('mouseleave', () => {
            minButton.style.backgroundColor = '#4CAF50';
        });

        minButton.addEventListener('click', removeMinPrice);

        const maxButton = document.createElement('button');
        maxButton.textContent = '移除最高价';
        maxButton.style.cssText = `
            padding: 10px 20px;
            background-color: #ff4444;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            transition: background-color 0.3s;
        `;

        maxButton.addEventListener('mouseenter', () => {
            maxButton.style.backgroundColor = '#cc0000';
        });

        maxButton.addEventListener('mouseleave', () => {
            maxButton.style.backgroundColor = '#ff4444';
        });

        maxButton.addEventListener('click', removeMaxPrice);

        container.appendChild(minButton);
        container.appendChild(maxButton);

        document.body.appendChild(container);
    }

    // ========================================
    // 监听器: MutationObserver
    // ========================================
    const observerCallback = function(mutationsList, observer) {
        console.log('[价格工具] DOM变化检测');

        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => {
            highlightPrices();
        }, CONFIG.DEBOUNCE_DELAY);
    };

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

        if (targetNode) {
            const config = {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['value']
            };

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

            console.log('[价格工具] MutationObserver 已启动');

            highlightPrices();
        } else {
            console.warn('[价格工具] #skuDataInfo 未找到,重试中...');

            let retryCount = 0;
            const maxRetries = 10;
            const retryInterval = 500;

            const findAndObserve = () => {
                const dynamicTargetNode = document.getElementById('skuDataInfo');
                if (dynamicTargetNode) {
                    const config = {
                        childList: true,
                        subtree: true,
                        attributes: true,
                        attributeFilter: ['value']
                    };
                    const observer = new MutationObserver(observerCallback);
                    observer.observe(dynamicTargetNode, config);
                    highlightPrices();
                    console.log('[价格工具] #skuDataInfo 找到并启动观察器');
                } else if (retryCount < maxRetries) {
                    retryCount++;
                    console.warn(`[价格工具] 重试中 (${retryCount}/${maxRetries})...`);
                    setTimeout(findAndObserve, retryInterval);
                } else {
                    console.error('[价格工具] 多次重试后仍未找到 #skuDataInfo');
                }
            };

            setTimeout(findAndObserve, 1000);
        }
    }

    // 监听价格输入框的 input 事件
    function attachInputListeners() {
        document.addEventListener('input', function(e) {
            if (e.target && e.target.classList && e.target.classList.contains('g-form-component')) {
                const parentTd = e.target.closest('td');
                if (parentTd) {
                    const table = parentTd.closest('table');
                    const skuDataInfo = parentTd.closest('#skuDataInfo');
                    if (table && skuDataInfo && !e.target.disabled) {
                        console.log('[价格工具] 检测到价格输入,重新计算...');
                        clearTimeout(debounceTimer);
                        debounceTimer = setTimeout(() => {
                            highlightPrices();
                        }, CONFIG.DEBOUNCE_DELAY);
                    }
                }
            }
        }, true);
        console.log('[价格工具] Input 事件监听器已附加');
    }

    // ========================================
    // 初始化
    // ========================================
    function init() {
        waitForElement('#skuDataInfo', () => {
            console.log('[价格工具] 检测到SKU数据表格,初始化功能...');
            createButtons();
            startObserver();
            attachInputListeners();
        });
    }

    // 启动脚本
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // 延迟执行,确保异步加载的内容也能被处理
    setTimeout(() => {
        console.log('[价格工具] 延迟执行 (1.5s)');
        highlightPrices();
    }, 1500);

    setTimeout(() => {
        console.log('[价格工具] 延迟执行 (4s)');
        highlightPrices();
    }, 4000);

    // 监听页面路由变化(单页应用)
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            console.log('[价格工具] URL变化,重新初始化...');
            setTimeout(() => {
                startObserver();
                highlightPrices();
            }, 2000);
        }
    }).observe(document, {subtree: true, childList: true});

})();