Greasy Fork

【抖店】价格监控

根据指定规则高亮显示价格

// ==UserScript==
// @name         【抖店】价格监控
// @namespace    http://tampermonkey.net/
// @version      0.9
// @description  根据指定规则高亮显示价格
// @author       [email protected]
// @match        https://fxg.jinritemai.com/ffa/morder/order/list?btm_ppre=*
// @grant        GM_getValue
// @grant        GM_setValue
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 注入CSS样式
    const styleSheet = document.createElement('style');
    styleSheet.textContent = `
        .price-monitor-container {
            position: fixed;
            top: calc(5vh + 20px); /* 向下移动3%的视窗高度 */
            right: 20px;
            background: rgba(255, 255, 255, 0.95);
            padding: 20px;
            border-radius: 12px;
            box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
            backdrop-filter: blur(10px);
            z-index: 9999;
            max-height: 80vh;
            overflow-y: auto;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            min-width: 320px;
            transition: all 0.3s ease;
        }

        .price-monitor-container.collapsed {
            min-width: auto;
            padding: 12px;
            background: rgba(144, 238, 144, 0.3); /* 浅绿色背景,带透明度 */
            backdrop-filter: blur(10px);
            border: 1px solid rgba(144, 238, 144, 0.5); /* 浅绿色边框 */
            box-shadow: 0 2px 12px rgba(144, 238, 144, 0.2); /* 浅绿色阴影 */
        }

        .price-monitor-container.collapsed:hover {
            background: rgba(144, 238, 144, 0.4); /* 悬停时稍微加深 */
        }

        .price-monitor-container.collapsed .price-monitor-content {
            display: none;
        }

        .price-monitor-container.collapsed .price-monitor-status {
            color: #2e8b57; /* 折叠时的文字颜色为深绿色 */
        }

        .price-monitor-toggle {
            position: absolute;
            top: 12px;
            right: 12px;
            width: 24px;
            height: 24px;
            border: none;
            background: transparent;
            cursor: pointer;
            padding: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #1d1d1f;
            opacity: 0.6;
            transition: all 0.2s ease;
        }

        .price-monitor-toggle:hover {
            opacity: 1;
        }

        .price-monitor-toggle svg {
            width: 16px;
            height: 16px;
            transition: transform 0.3s ease;
        }

        .collapsed .price-monitor-toggle svg {
            transform: rotate(180deg);
        }

        .price-monitor-status {
            display: flex;
            align-items: center;
            font-size: 14px;
            color: #1d1d1f;
            margin-right: 24px;
        }

        .collapsed .price-monitor-status {
            margin: 0;
        }

        .price-monitor-title {
            margin: 0 0 20px 0;
            font-size: 18px;
            font-weight: 600;
            color: #1d1d1f;
        }

        .price-monitor-item {
            margin-bottom: 15px;
            padding: 12px;
            background: rgba(0, 0, 0, 0.02);
            border-radius: 8px;
            transition: all 0.3s ease;
        }

        .price-monitor-item:hover {
            background: rgba(0, 0, 0, 0.04);
        }

        .price-monitor-input {
            width: 100%;
            padding: 8px 12px;
            margin: 4px 0;
            border: 1px solid #d2d2d7;
            border-radius: 6px;
            font-size: 14px;
            transition: all 0.3s ease;
        }

        .price-monitor-input:focus {
            outline: none;
            border-color: #0071e3;
            box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.1);
        }

        .price-monitor-button {
            background: #0071e3;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 6px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.3s ease;
        }

        .price-monitor-button:hover {
            background: #0077ED;
        }

        .price-monitor-button.secondary {
            background: #f5f5f7;
            color: #1d1d1f;
            margin-right: 8px;
        }

        .price-monitor-button.secondary:hover {
            background: #e8e8ed;
        }

        .price-monitor-button.delete {
            background: #ff3b30;
            padding: 4px 8px;
            font-size: 12px;
        }

        .price-monitor-button.delete:hover {
            background: #ff453a;
        }

        .price-monitor-actions {
            display: flex;
            justify-content: flex-end;
            margin-top: 15px;
            padding-top: 15px;
            border-top: 1px solid #d2d2d7;
        }
    `;
    document.head.appendChild(styleSheet);

    // 默认配置
    const defaultConfig = {
        items: [
            { name: '乌黑芝麻230克*8瓶x1', expectedPrice: 119.8 }
        ]
    };

    // 从存储中获取配置
    let config = GM_getValue('priceConfig', defaultConfig);

    // 创建配置界面
    function createConfigUI() {
        const container = document.createElement('div');
        container.className = 'price-monitor-container';

        // 创建内容容器
        const content = document.createElement('div');
        content.className = 'price-monitor-content';

        // 创建状态指示器
        const status = document.createElement('div');
        status.className = 'price-monitor-status';
        status.textContent = '价格监控已启用';

        // 创建折叠按钮
        const toggleButton = document.createElement('button');
        toggleButton.className = 'price-monitor-toggle';
        toggleButton.innerHTML = `
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <path d="M18 15l-6-6-6 6"/>
            </svg>
        `;

        // 添加折叠/展开功能
        let isCollapsed = GM_getValue('uiCollapsed', false);
        if (isCollapsed) {
            container.classList.add('collapsed');
        }

        toggleButton.onclick = () => {
            isCollapsed = !isCollapsed;
            container.classList.toggle('collapsed');
            GM_setValue('uiCollapsed', isCollapsed);
        };

        // 创建标题
        const title = document.createElement('h3');
        title.className = 'price-monitor-title';
        title.textContent = '价格监控配置';
        content.appendChild(title);

        // 创建配置列表
        const configList = document.createElement('div');
        configList.id = 'configList';
        content.appendChild(configList);

        // 创建操作按钮容器
        const actions = document.createElement('div');
        actions.className = 'price-monitor-actions';

        // 添加按钮
        const addButton = document.createElement('button');
        addButton.textContent = '添加商品';
        addButton.className = 'price-monitor-button secondary';
        addButton.onclick = addConfigItem;
        actions.appendChild(addButton);

        // 保存按钮
        const saveButton = document.createElement('button');
        saveButton.textContent = '保存配置';
        saveButton.className = 'price-monitor-button';
        saveButton.onclick = saveConfig;
        actions.appendChild(saveButton);

        content.appendChild(actions);

        // 组装界面
        container.appendChild(status);
        container.appendChild(toggleButton);
        container.appendChild(content);
        document.body.appendChild(container);

        // 渲染现有配置
        renderConfigItems();

        // 更新状态显示
        function updateStatus() {
            const activeItems = config.items.length;
            status.textContent = `监控中: ${activeItems}个商品`;
        }
        updateStatus();
    }

    // 渲染配置项
    function renderConfigItems() {
        const configList = document.getElementById('configList');
        configList.innerHTML = '';

        config.items.forEach((item, index) => {
            const itemDiv = document.createElement('div');
            itemDiv.className = 'price-monitor-item';
            itemDiv.innerHTML = `
                <div>
                    <input type="text" placeholder="商品名称" value="${item.name}"
                           class="price-monitor-input item-name" data-index="${index}">
                    <div style="display: flex; gap: 8px; margin-top: 8px;">
                        <input type="number" placeholder="预期价格" value="${item.expectedPrice}"
                               class="price-monitor-input item-price" data-index="${index}" style="flex: 1;">
                        <button class="price-monitor-button delete" onclick="this.parentElement.parentElement.parentElement.remove()">删除</button>
                    </div>
                </div>
            `;
            configList.appendChild(itemDiv);
        });
    }

    // 添加配置项
    function addConfigItem() {
        config.items.push({ name: '', expectedPrice: 0 });
        renderConfigItems();
    }

    // 保存配置
    function saveConfig() {
        const newConfig = { items: [] };
        const names = document.querySelectorAll('.item-name');
        const prices = document.querySelectorAll('.item-price');

        for (let i = 0; i < names.length; i++) {
            newConfig.items.push({
                name: names[i].value,
                expectedPrice: parseFloat(prices[i].value)
            });
        }

        config = newConfig;
        GM_setValue('priceConfig', config);
        alert('配置已保存');
    }

    // 获取价格样式
    function getPriceStyle(currentPrice, expectedPrice) {
        if (currentPrice === expectedPrice) {
            return { backgroundColor: '#90EE90', color: '#006400' }; // 绿色底色
        } else {
            return { backgroundColor: '#FFB6C1', color: '#8B0000' }; // 深红色底色
        }
    }

    // 检查元素
    function checkElements() {
        const containers = document.querySelectorAll('tr');
        const results = [];

        containers.forEach(container => {
            const relativeElement = container.querySelector('.index_ellipsis__MJ7fR');
            const priceElement = container.querySelector('.index_amountWrap___vXdu.index_marked__FC_BX');

            if (relativeElement && priceElement) {
                const relativeText = relativeElement.textContent.trim();
                const priceText = priceElement.textContent.trim();
                const priceMatch = priceText.match(/([\d,.]+)/);

                if (priceMatch && priceMatch[1]) {
                    const currentPrice = parseFloat(priceMatch[1].replace(/,/g, ''));

                    // 检查是否匹配配置的商品
                    const matchedItem = config.items.find(item => relativeText.includes(item.name));
                    if (matchedItem) {
                        const style = getPriceStyle(currentPrice, matchedItem.expectedPrice);
                        priceElement.style.backgroundColor = style.backgroundColor;
                        priceElement.style.color = style.color;

                        results.push({
                            name: relativeText,
                            price: currentPrice,
                            expectedPrice: matchedItem.expectedPrice,
                            isExpected: currentPrice === matchedItem.expectedPrice
                        });
                    }
                }
            }
        });

        // 输出结果
        if (results.length > 0) {
            console.group('价格监控结果');
            results.forEach(result => {
                const priceInfo = `当前价格: ¥${result.price.toFixed(2)} / 预期价格: ¥${result.expectedPrice.toFixed(2)}`;
                console.log(
                    `%c${result.name}%c${priceInfo}`,
                    'color: #333; font-weight: normal;',
                    `color: ${result.isExpected ? '#006400' : '#8B0000'}; font-weight: bold;`
                );
            });
            console.groupEnd();
        }
    }

    // 创建配置界面
    createConfigUI();

    // 定时检查
    setInterval(checkElements, 5000);
})();