京东商品参数对比工具

该脚本可用于对比不限数量的同类型商品(如:手机、笔记本)的详细参数

目前為 2025-06-15 提交的版本,檢視 最新版本

// ==UserScript==
// @name         京东商品参数对比工具
// @namespace    http://tampermonkey.net/
// @version      20250615
// @description  该脚本可用于对比不限数量的同类型商品(如:手机、笔记本)的详细参数
// @author       Yihang Wang <[email protected]>
// @match        https://item.jd.com/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==
(function () {
    'use strict';

    var version = '20250615';
    var itemIDs = GM_getValue("jd-price-compare-item-ids", []);
    var relatedItemIDs = getRelatedItemIDs(document);
    var userID = getUserID();

    // 从localStorage获取商品数据
    function getItemFromStorage(itemID) {
        const items = JSON.parse(localStorage.getItem('jd-compare-items') || '{}');
        return items[itemID];
    }

    // 保存商品数据到localStorage
    function saveItemToStorage(itemID, data) {
        const items = JSON.parse(localStorage.getItem('jd-compare-items') || '{}');
        items[itemID] = {
            user_id: userID,
            created_at: new Date().toISOString(),
            updated_at: new Date().toISOString(),
            version: version,
            data: data
        };
        localStorage.setItem('jd-compare-items', JSON.stringify(items));
    }

    // 保存对比列表到localStorage
    function saveListToStorage(itemIDs) {
        const lists = JSON.parse(localStorage.getItem('jd-compare-lists') || '[]');
        const listId = generateListId(itemIDs);
        const list = {
            id: listId,
            user_id: userID,
            created_at: new Date().toISOString(),
            updated_at: new Date().toISOString(),
            version: version,
            items: itemIDs
        };
        lists.push(list);
        localStorage.setItem('jd-compare-lists', JSON.stringify(lists));
        return listId;
    }

    // 生成列表ID
    function generateListId(itemIDs) {
        const timestamp = new Date().toISOString();
        const source = `${userID}-${timestamp}-${JSON.stringify(itemIDs)}`;
        return btoa(source).replace(/[^a-zA-Z0-9]/g, '').substring(0, 24);
    }

    // 扁平化对象
    function flattenObject(obj, parentKey = '', separator = '_') {
        return Object.keys(obj).reduce((acc, key) => {
            const newKey = parentKey ? `${parentKey}${separator}${key}` : key;
            if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
                Object.assign(acc, flattenObject(obj[key], newKey, separator));
            } else {
                acc[newKey] = obj[key];
            }
            return acc;
        }, {});
    }

    // 自定义排序顺序
    function customSortOrder(key) {
        const order = ['商品编号', '图片', '价格'];
        for (let i = 0; i < order.length; i++) {
            if (key === order[i]) {
                return i;
            }
        }
        return order.length;
    }

    // 创建对比页面
    function createComparePage(listId, itemIDs) {
        const items = itemIDs.map(id => getItemFromStorage(id)).filter(Boolean);
        if (items.length === 0) {
            alert('没有找到可对比的商品数据');
            return;
        }

        // 获取所有列
        const columns = new Set();
        items.forEach(item => {
            const flatData = flattenObject(item.data);
            Object.keys(flatData).forEach(key => columns.add(key));
        });

        // 排序列
        const sortedColumns = Array.from(columns).sort((a, b) => {
            const orderA = customSortOrder(a);
            const orderB = customSortOrder(b);
            if (orderA !== orderB) {
                return orderA - orderB;
            }
            return a.localeCompare(b);
        });

        // 生成表格数据
        const tableData = items.map(item => {
            const flatData = flattenObject(item.data);
            return sortedColumns.map(column => {
                if (column === '图片') {
                    return `<div class="text-center">
                        <img src="${flatData[column] || ''}" alt="${flatData['基本信息_商品名称'] || ''}" 
                            class="img-fluid product-image" style="max-height: 150px; object-fit: contain;">
                    </div>`;
                } else if (column === '商品编号') {
                    return `<a href="https://item.jd.com/${item.data.商品编号}.html" target="_blank" 
                        class="text-primary text-decoration-none">${flatData[column] || ''}</a>`;
                } else if (column === '价格') {
                    const price = flatData[column];
                    return price === 'N/A' ? 
                        '<span class="text-muted">N/A</span>' : 
                        `<span class="text-danger fw-bold">¥${price}</span>`;
                }
                return flatData[column] || '';
            });
        });

        // 创建HTML
        const html = `
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title>商品对比 - 京东商品参数对比工具</title>
                <!-- Bootstrap CSS -->
                <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
                <!-- DataTables CSS -->
                <link href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css" rel="stylesheet">
                <!-- Font Awesome -->
                <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
                <style>
                    :root {
                        --primary-color: #4a90e2;
                        --secondary-color: #357abd;
                        --success-color: #28a745;
                        --danger-color: #dc3545;
                        --warning-color: #ffc107;
                        --info-color: #17a2b8;
                        --light-color: #f8f9fa;
                        --dark-color: #343a40;
                    }

                    body {
                        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                        background-color: #f8f9fa;
                        color: #333;
                        line-height: 1.6;
                    }

                    .container {
                        max-width: 1400px;
                        padding: 2rem;
                    }

                    .header {
                        background: white;
                        padding: 2rem;
                        border-radius: 10px;
                        box-shadow: 0 2px 15px rgba(0,0,0,0.1);
                        margin-bottom: 2rem;
                    }

                    .metadata {
                        background: white;
                        padding: 1.5rem;
                        border-radius: 10px;
                        box-shadow: 0 2px 15px rgba(0,0,0,0.1);
                        margin-bottom: 2rem;
                    }

                    .metadata table {
                        margin-bottom: 0;
                    }

                    .metadata th {
                        background-color: #f8f9fa;
                        font-weight: 600;
                    }

                    .table-container {
                        background: white;
                        padding: 1.5rem;
                        border-radius: 10px;
                        box-shadow: 0 2px 15px rgba(0,0,0,0.1);
                        margin-bottom: 2rem;
                    }

                    .dataTables_wrapper {
                        padding: 1rem 0;
                    }

                    .dataTables_filter input {
                        border: 1px solid #dee2e6;
                        border-radius: 4px;
                        padding: 0.375rem 0.75rem;
                    }

                    .dataTables_length select {
                        border: 1px solid #dee2e6;
                        border-radius: 4px;
                        padding: 0.375rem 2rem 0.375rem 0.75rem;
                    }

                    .table {
                        margin-bottom: 0;
                    }

                    .table th {
                        background-color: #f8f9fa;
                        font-weight: 600;
                        white-space: nowrap;
                        position: sticky;
                        top: 0;
                        z-index: 1;
                    }

                    .table td {
                        vertical-align: middle;
                    }

                    .product-image {
                        transition: transform 0.3s ease;
                    }

                    .product-image:hover {
                        transform: scale(1.1);
                    }

                    .btn-export {
                        background-color: var(--primary-color);
                        color: white;
                        border: none;
                        padding: 0.5rem 1rem;
                        border-radius: 5px;
                        transition: background-color 0.3s ease;
                    }

                    .btn-export:hover {
                        background-color: var(--secondary-color);
                        color: white;
                    }

                    .faq {
                        background: white;
                        padding: 2rem;
                        border-radius: 10px;
                        box-shadow: 0 2px 15px rgba(0,0,0,0.1);
                    }

                    .faq h3 {
                        color: var(--dark-color);
                        margin-bottom: 1.5rem;
                        font-weight: 600;
                    }

                    .faq .question {
                        color: var(--primary-color);
                        font-weight: 600;
                        margin: 1rem 0 0.5rem;
                    }

                    .faq .answer {
                        color: #666;
                        margin-bottom: 1rem;
                    }

                    .faq .solution {
                        background-color: #f8f9fa;
                        padding: 1rem;
                        border-radius: 5px;
                        margin-top: 1rem;
                    }

                    .faq .solution li {
                        margin-bottom: 0.5rem;
                    }

                    .badge {
                        font-size: 0.875rem;
                        padding: 0.5em 0.75em;
                    }

                    .badge-version {
                        background-color: var(--info-color);
                        color: white;
                    }

                    .badge-count {
                        background-color: var(--success-color);
                        color: white;
                    }

                    @media (max-width: 768px) {
                        .container {
                            padding: 1rem;
                        }
                        
                        .header, .metadata, .table-container, .faq {
                            padding: 1rem;
                        }
                    }
                </style>
            </head>
            <body>
                <div class="container">
                    <div class="header">
                        <div class="d-flex justify-content-between align-items-center">
                            <h1 class="mb-0">京东商品参数对比</h1>
                            <div>
                                <span class="badge badge-version me-2">v${version}</span>
                                <span class="badge badge-count">${items.length} 个商品</span>
                            </div>
                        </div>
                    </div>
                    
                    <div class="metadata">
                        <h2 class="h4 mb-3">列表信息</h2>
                        <div class="table-responsive">
                            <table class="table table-bordered">
                                <thead>
                                    <tr>
                                        <th>列表 ID</th>
                                        <th>商品数量</th>
                                        <th>生成时间</th>
                                        <th>当前版本</th>
                                        <th>操作</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr>
                                        <td><code>${listId}</code></td>
                                        <td>${items.length}</td>
                                        <td>${new Date().toLocaleString()}</td>
                                        <td>v${version}</td>
                                        <td>
                                            <button class="btn btn-export" id="exportCsvBtn">
                                                <i class="fas fa-download me-1"></i>导出为 CSV
                                            </button>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                    </div>

                    <div class="table-container">
                        <h2 class="h4 mb-3">商品参数对比结果</h2>
                        <div class="alert alert-info">
                            <i class="fas fa-info-circle me-2"></i>
                            您可以点击列名来对当前列的商品信息进行排序,使用搜索框对商品参数进行搜索,精准定位商品信息。
                        </div>

                        <div class="table-responsive">
                            <table id="compare-result" class="table table-striped table-hover">
                                <thead>
                                    <tr>
                                        ${sortedColumns.map(col => `<th>${col}</th>`).join('')}
                                    </tr>
                                </thead>
                                <tbody>
                                    ${tableData.map(row => `
                                        <tr>
                                            ${row.map(cell => `<td>${cell}</td>`).join('')}
                                        </tr>
                                    `).join('')}
                                </tbody>
                            </table>
                        </div>
                    </div>

                    <div class="faq">
                        <h3><i class="fas fa-question-circle me-2"></i>常见问题</h3>
                        <h4 class="h5">关于价格显示"N/A"的说明</h4>
                        <p class="question">为什么价格显示为 N/A?</p>
                        <p class="answer">这是因为京东页面的限制,本脚本仅能在您点击"添加本商品"按钮时,获取到当前页面的商品价格。因此,当您使用"添加所有 XX 个型号"按钮时,只有当前页面显示的商品价格可以被成功获取。</p>
                        
                        <div class="solution">
                            <h5 class="h6 mb-3"><i class="fas fa-lightbulb me-2"></i>解决方案</h5>
                            <ul class="mb-0">
                                <li>大部分商家都会在商品的主图上标记价格,您可以通过仔细查看"图片"列来获取商品价格。</li>
                                <li>为了获取更准确的价格信息,建议您不要使用"添加所有 XX 个型号"按钮,而是逐个点击商品详情页,并使用"添加本商品"按钮来获取价格。</li>
                            </ul>
                        </div>
                    </div>
                </div>

                <!-- jQuery -->
                <script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
                <!-- Bootstrap Bundle with Popper -->
                <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
                <!-- DataTables -->
                <script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
                <script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>

                <script>
                    $(document).ready(function() {
                        $('#compare-result').DataTable({
                            language: {
                                url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/zh.json'
                            },
                            pageLength: 25,
                            order: [[0, 'asc']],
                            responsive: true,
                            scrollX: true,
                            scrollY: '60vh',
                            scrollCollapse: true,
                            fixedHeader: true,
                            dom: '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>' +
                                 '<"row"<"col-sm-12"tr>>' +
                                 '<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
                            initComplete: function() {
                                $('.dataTables_length select').addClass('form-select');
                                $('.dataTables_filter input').addClass('form-control');
                            }
                        });

                        // 导出CSV功能
                        document.getElementById('exportCsvBtn').addEventListener('click', function() {
                            const table = document.getElementById('compare-result');
                            const rows = table.querySelectorAll('tr');
                            let csv = [];
                            
                            // 添加 BOM 标记
                            const BOM = '\\uFEFF';
                            
                            for (let i = 0; i < rows.length; i++) {
                                const row = [], cols = rows[i].querySelectorAll('td, th');
                                
                                for (let j = 0; j < cols.length; j++) {
                                    let text = cols[j].innerText;
                                    // 处理包含逗号、引号或换行符的文本
                                    if (text.includes(',') || text.includes('"') || text.includes('\\n')) {
                                        text = '"' + text.replace(/"/g, '""') + '"';
                                    }
                                    row.push(text);
                                }
                                
                                csv.push(row.join(','));
                            }
                            
                            // 下载CSV文件
                            const csvContent = BOM + csv.join('\\n');
                            const blob = new Blob([csvContent], { 
                                type: 'text/csv;charset=utf-8'
                            });
                            
                            // 使用 URL.createObjectURL 创建下载链接
                            const link = document.createElement('a');
                            if (window.navigator.msSaveOrOpenBlob) {
                                // 兼容 IE
                                window.navigator.msSaveOrOpenBlob(blob, '${listId}.csv');
                            } else {
                                link.href = URL.createObjectURL(blob);
                                link.download = '${listId}.csv';
                                link.style.display = 'none';
                                document.body.appendChild(link);
                                link.click();
                                document.body.removeChild(link);
                            }
                        });
                    });
                </script>
            </body>
            </html>
        `;

        // 创建新窗口显示对比结果
        const win = window.open('', '_blank');
        win.document.write(html);
        win.document.close();
    }

    function pollUntil(conditionFn, interval = 1000, maxAttempts = 10) {
        return new Promise((resolve, reject) => {
            let attempts = 0;
            function checkCondition() {
                if (conditionFn()) {
                    resolve();
                } else if (attempts < maxAttempts) {
                    attempts++;
                    setTimeout(checkCondition, interval);
                } else {
                    reject(new Error('Polling timed out'));
                }
            }
            checkCondition();
        });
    }

    function getItemID(doc) {
        // 尝试多种可能的选择器
        let selectors = [
            'a.follow.J-follow[data-id]',
            'div.sku-name[data-sku]',
            'div.item[data-sku]',
            'div.sku-name'
        ];
        
        for (let selector of selectors) {
            let element = doc.querySelector(selector);
            if (element) {
                let itemID = element.getAttribute('data-id') || element.getAttribute('data-sku');
                if (itemID) {
                    console.log(`[商品ID] 使用选择器 ${selector} 获取到商品ID: ${itemID}`);
                    return itemID;
                }
            }
        }

        // 如果上述方法都失败,尝试从URL中获取
        let url = window.location.href;
        let match = url.match(/\/(\d+)\.html/);
        if (match && match[1]) {
            console.log(`[商品ID] 从URL获取到商品ID: ${match[1]}`);
            return match[1];
        }

        console.error('[商品ID] 无法获取商品ID,请检查页面结构');
        return "unknown";
    }

    function getRelatedItemIDs(doc) {
        console.log('[相关商品] 开始获取相关商品ID列表');
        var dataSkuValues = [getItemID(doc)];
        if (Object.keys(pageConfig.product.colorSize).length > 0) {
            console.log(`[相关商品] 发现 ${pageConfig.product.colorSize.length} 个颜色/规格选项`);
            pageConfig.product.colorSize.forEach(function (item) {
                dataSkuValues.push(item.skuId.toString());
            });
        }
        let itemIdSet = new Set(dataSkuValues);
        const result = Array.from(itemIdSet.values());
        console.log(`[相关商品] 共找到 ${result.length} 个相关商品ID`);
        return result;
    }

    function getPrice(doc) {
        const itemID = getItemID(doc);
        console.log(`[价格] 开始获取商品 ${itemID} 的价格`);
        
        // 尝试多个可能的价格选择器
        const priceSelectors = [
            `.price.J-p-${itemID}`,
            '.price',
            '.jd-price',
            '.p-price .price',
            '.sku-price',
            '.price-box .price'
        ];

        for (let selector of priceSelectors) {
            const targetNode = doc.querySelector(selector);
            if (targetNode) {
                const priceText = targetNode.innerText.trim();
                // 尝试提取数字
                const priceMatch = priceText.match(/[\d.]+/);
                if (priceMatch) {
                    const price = parseFloat(priceMatch[0]);
                    console.log(`[价格] 使用选择器 ${selector} 获取到价格: ${price}`);
                    return price;
                }
            }
        }

        // 如果上述方法都失败,尝试从其他元素获取价格
        const possiblePriceElements = doc.querySelectorAll('*');
        for (let element of possiblePriceElements) {
            const text = element.innerText.trim();
            if (text.includes('¥') || text.includes('¥')) {
                const priceMatch = text.match(/[\d.]+/);
                if (priceMatch) {
                    const price = parseFloat(priceMatch[0]);
                    console.log(`[价格] 从文本内容获取到价格: ${price}`);
                    return price;
                }
            }
        }

        console.error('[价格] 无法获取商品价格');
        return 0; // 返回0而不是NaN,避免后续处理出错
    }

    function getBasicInfo(doc) {
        console.log('[基本信息] 开始解析商品基本信息');
        const basicInfoElement = doc.querySelector('#detail > div.tab-con > div:nth-child(3) > div.goods-base');
        const basicInfo = {};

        basicInfoElement.querySelectorAll('.item').forEach(item => {
            const nameElement = item.querySelector('.name');
            const textElement = item.querySelector('.text');
            
            if (!nameElement || !textElement) {
                return;
            }

            const key = nameElement.textContent.trim();
            const value = textElement.textContent.trim();
            
            if (key && value) {
                basicInfo[key] = value;
                console.log(`[基本信息] ${key}: ${value}`);
            }
        });

        console.log(`[基本信息] 共解析出 ${Object.keys(basicInfo).length} 条基本信息`);
        return basicInfo;
    }

    function getMainInfo(doc) {
        console.log('[主体信息] 开始解析商品主体信息');
        const mainInfoElements = doc.querySelectorAll('.Ptable-item');
        const mainInfo = {};

        mainInfoElements.forEach(item => {
            const key = item.querySelector('h3').textContent.trim();
            console.log(`[主体信息] 正在解析 ${key} 部分`);
            const values = {};

            item.querySelectorAll('dl').forEach(dl => {
                const detailKey = dl.querySelector('dt').textContent.trim();
                const hintElement = dl.querySelector('dd.Ptable-tips');
                var tableHint = "";
                if (hintElement != null) {
                    const tableHint = hintElement.textContent.trim();
                }
                const detailValue = dl.querySelector('dd:not(.Ptable-tips)').textContent.trim();
                const key = detailKey;
                values[key] = detailValue;
                console.log(`[主体信息] ${key}: ${detailValue}`);
            });

            mainInfo[key] = values;
        });

        console.log(`[主体信息] 共解析出 ${Object.keys(mainInfo).length} 个主体信息分类`);
        return mainInfo;
    }

    function getPackageList(doc) {
        console.log('[包装信息] 开始获取商品包装清单');
        
        // 尝试多个可能的包装清单选择器
        const packageSelectors = [
            '.package-list p',
            '.goods-base .exclusive-row.item .text',
            '.goods-base .item:last-child .text',
            '.goods-base .item:contains("包装清单") .text',
            '.goods-base .item:contains("包装") .text'
        ];

        for (let selector of packageSelectors) {
            const element = doc.querySelector(selector);
            if (element) {
                const packageList = element.textContent.trim();
                console.log(`[包装信息] 使用选择器 ${selector} 获取到包装清单: ${packageList}`);
                return packageList;
            }
        }

        // 如果上述方法都失败,尝试从基本信息中获取包装清单
        const basicInfo = getBasicInfo(doc);
        if (basicInfo['包装清单']) {
            console.log('[包装信息] 从基本信息中获取到包装清单');
            return basicInfo['包装清单'];
        }

        console.log('[包装信息] 未找到包装清单信息');
        return '暂无包装信息';
    }

    function getImageUrl(doc) {
        console.log('[图片] 开始获取商品主图URL');
        const imageElement = doc.querySelector("#spec-list > ul > li:nth-child(1) > img");
        let items = imageElement.src.split("/");
        items[3] = 'n0'
        items[4] = 'jfs'
        items[5] = 't1'
        const imageUrl = items.join("/");
        console.log(`[图片] 商品主图URL: ${imageUrl}`);
        return imageUrl;
    }

    function parseItemWithDocument(doc) {
        console.log('[解析] 开始解析当前页面商品信息');
        try {
            let basicInfo = getBasicInfo(doc);
            let mainInfo = getMainInfo(doc);
            let packageList = getPackageList(doc);
            let imageUrl = getImageUrl(doc);
            let itemID = getItemID(doc);
            let data = {
                "商品编号": itemID,
                "基本信息": basicInfo,
                "主体信息": mainInfo,
                "包装信息": packageList,
                "价格": 'N/A',
                "图片": imageUrl,
            };
            console.log('[解析] 商品信息解析完成');
            return data;
        } catch (error) {
            console.error('[解析] 解析商品信息时出错:', error);
            // 返回一个包含错误信息的基本数据结构
            return {
                "商品编号": getItemID(doc) || "unknown",
                "基本信息": {},
                "主体信息": {},
                "包装信息": "解析出错",
                "价格": 'N/A',
                "图片": "",
                "错误信息": error.message
            };
        }
    }

    // 添加随机延迟函数
    function randomDelay(min = 10, max = 50) {
        const delay = Math.floor(Math.random() * (max - min + 1)) + min;
        console.log(`[延迟] 等待 ${delay}ms 后继续执行`);
        return new Promise(resolve => setTimeout(resolve, delay));
    }

    // 创建进度条
    function createProgressBar() {
        const progressContainer = document.createElement('div');
        progressContainer.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            z-index: 9999;
            min-width: 300px;
        `;

        const progressTitle = document.createElement('div');
        progressTitle.style.cssText = `
            margin-bottom: 10px;
            font-weight: bold;
            color: #333;
        `;
        progressTitle.textContent = '正在添加商品...';

        const progressBar = document.createElement('div');
        progressBar.style.cssText = `
            width: 100%;
            height: 20px;
            background: #f0f0f0;
            border-radius: 10px;
            overflow: hidden;
        `;

        const progressFill = document.createElement('div');
        progressFill.style.cssText = `
            width: 0%;
            height: 100%;
            background: #4a90e2;
            transition: width 0.3s ease;
        `;

        const progressText = document.createElement('div');
        progressText.style.cssText = `
            margin-top: 5px;
            text-align: center;
            color: #666;
            font-size: 12px;
        `;

        progressBar.appendChild(progressFill);
        progressContainer.appendChild(progressTitle);
        progressContainer.appendChild(progressBar);
        progressContainer.appendChild(progressText);

        document.body.appendChild(progressContainer);

        return {
            container: progressContainer,
            fill: progressFill,
            text: progressText,
            title: progressTitle
        };
    }

    // 更新进度条
    function updateProgress(progress, current, total, status) {
        const percentage = (current / total) * 100;
        progress.fill.style.width = `${percentage}%`;
        progress.text.textContent = `${current}/${total} - ${status}`;
    }

    async function parseItemByID(itemID) {
        return new Promise(async function (resolve, reject) {
            // 添加随机延迟
            await randomDelay(10, 30);
            
            let endpoint = `https://item.jd.com/${itemID}.html`;
            fetch(endpoint)
                .then(response => {
                    if (!response.ok) {
                        throw new Error(`HTTP error! Status: ${response.status}`);
                    }
                    resolve(response.text());
                })
                .catch(error => {
                    console.error('Error:', error);
                    reject(error);
                });
        });
    }

    async function parseItem(itemID) {
        console.log(`[解析] 开始解析商品 ${itemID} 的信息`);
        if (itemID == getItemID(document)) {
            console.log('[解析] 正在解析当前页面商品');
            try {
                await pollUntil(() => {
                    const price = getPrice(document);
                    return price > 0;
                });
                let price = getPrice(document);
                let data = parseItemWithDocument(document);
                data["价格"] = price;
                console.log(`[解析] 当前页面商品解析完成,价格: ${price}`);
                return data;
            } catch (error) {
                console.error('[解析] 等待价格超时,继续处理其他信息');
                let data = parseItemWithDocument(document);
                data["价格"] = 0;
                return data;
            }
        } else {
            console.log(`[解析] 正在获取商品 ${itemID} 的页面内容`);
            try {
                // 添加随机延迟
                await randomDelay(10, 30);
                
                let html = await parseItemByID(itemID);
                let parser = new DOMParser();
                let doc = parser.parseFromString(html, 'text/html');
                let item = parseItemWithDocument(doc);
                console.log(`[解析] 商品 ${itemID} 解析完成`);
                return item;
            } catch (error) {
                console.error(`[解析] 解析商品 ${itemID} 失败:`, error);
                throw error;
            }
        }
    }

    async function appendList(itemID) {
        console.log(`[添加] 开始添加商品 ${itemID} 到对比列表`);
        try {
            // 添加随机延迟
            await randomDelay(20, 40);
            
            let item = await parseItem(itemID);
            saveItemToStorage(itemID, item);
            
            let itemIdList = GM_getValue("jd-price-compare-item-ids", []);
            let itemIdSet = new Set(itemIdList);
            itemIdSet.add(itemID);
            GM_setValue("jd-price-compare-item-ids", Array.from(itemIdSet.values()));
            
            let statusLine = document.getElementById('jd-price-compare-status-line');
            statusLine.textContent = `已成功添加 ${itemIdSet.size} 个待对比商品`;
            console.log(`[添加] 商品 ${itemID} 已添加到对比列表,当前共 ${itemIdSet.size} 个商品`);
        } catch (error) {
            console.error('[添加] 添加商品失败:', error);
            let statusLine = document.getElementById('jd-price-compare-status-line');
            statusLine.textContent = `添加商品失败,请稍后重试`;
            throw error;
        }
    }

    function createList() {
        console.log('[创建] 开始创建商品对比列表');
        let itemIdList = GM_getValue("jd-price-compare-item-ids", []);
        let statusLine = document.getElementById('jd-price-compare-status-line');
        if (itemIdList.length == 0) {
            console.log('[创建] 对比列表为空,无法创建');
            statusLine.textContent = `当前待对比商品列表为空,请先点击添加商品按钮。`;
            return;
        }
        console.log(`[创建] 准备创建包含 ${itemIdList.length} 个商品的对比列表`);
        statusLine.textContent = `正在创建 ${itemIdList.length} 个商品的对比列表...`;
        
        const listId = saveListToStorage(itemIdList);
        createComparePage(listId, itemIdList);
        
        GM_setValue("jd-price-compare-item-ids", []);
        statusLine.textContent = `对比完成,已打开商品对比结果页面。`;
    }

    // 修改添加所有商品的函数,添加进度条
    async function addAllItems() {
        const progress = createProgressBar();
        let statusLine = document.getElementById('jd-price-compare-status-line');
        
        try {
            for (let i = 0; i < relatedItemIDs.length; i++) {
                const itemID = relatedItemIDs[i];
                updateProgress(progress, i + 1, relatedItemIDs.length, `正在获取商品 ${itemID} 的详情`);
                
                try {
                    await appendList(itemID);
                    // 在添加每个商品之间添加随机延迟
                    if (i < relatedItemIDs.length - 1) {
                        await randomDelay(20, 40);
                    }
                } catch (error) {
                    console.error(`[添加] 添加商品 ${itemID} 失败:`, error);
                    // 如果添加失败,等待更长时间后继续
                    await randomDelay(30, 50);
                }
            }
            
            progress.title.textContent = '添加完成!';
            updateProgress(progress, relatedItemIDs.length, relatedItemIDs.length, '所有商品已添加完成');
            
            // 3秒后移除进度条
            setTimeout(() => {
                progress.container.remove();
            }, 3000);
            
        } catch (error) {
            progress.title.textContent = '添加失败';
            progress.title.style.color = '#ff4444';
            updateProgress(progress, i, relatedItemIDs.length, '发生错误,请重试');
            
            // 5秒后移除进度条
            setTimeout(() => {
                progress.container.remove();
            }, 5000);
        }
    }

    function createCompareButtons() {
        console.log('[界面] 开始创建对比功能按钮组');
        
        // 创建按钮组容器
        const buttonGroup = document.createElement('div');
        buttonGroup.className = 'compare-buttons';
        buttonGroup.style.display = 'flex';
        buttonGroup.style.justifyContent = 'center';
        buttonGroup.style.gap = '10px'; // 按钮之间的间距

        // 创建按钮样式
        const buttonStyle = {
            fontSize: '16px', // 字体稍小
            padding: '8px 16px',
            borderRadius: '4px',
            cursor: 'pointer',
            border: 'none',
            color: '#fff',
            backgroundColor: '#4a90e2', // 使用不同的背景色
            transition: 'background-color 0.3s',
            textDecoration: 'none',
            display: 'inline-block',
            textAlign: 'center',
            minWidth: '120px' // 最小宽度
        };

        // 创建按钮
        const buttons = [
            {
                id: 'jd-price-compare-add-single-button',
                text: '添加本商品',
                onClick: async function(event) {
                    event.preventDefault();
                    let itemID = getItemID(document);
                    let statusLine = document.getElementById('jd-price-compare-status-line');
                    statusLine.textContent = `正在获取商品(ID:${itemID})详情...`;
                    await appendList(itemID);
                    updateCompareButton();
                }
            },
            {
                id: 'jd-price-compare-add-all-button',
                text: `添加所有 ${relatedItemIDs.length} 个型号`,
                onClick: async function(event) {
                    event.preventDefault();
                    await addAllItems();
                }
            },
            {
                id: 'jd-price-compare-start-button',
                text: `开始对比 (${itemIDs.length})`,
                onClick: function(event) {
                    event.preventDefault();
                    createList();
                }
            },
            {
                id: 'jd-price-add-feedback-button',
                text: '意见反馈',
                onClick: function(event) {
                    event.preventDefault();
                    const url = "https://gf.qytechs.cn/zh-CN/scripts/486915-%E4%BA%AC%E4%B8%9C%E5%95%86%E5%93%81%E5%8F%82%E6%95%B0%E5%AF%B9%E6%AF%94%E5%B7%A5%E5%85%B7/feedback";
                    window.open(url);
                }
            }
        ];

        // 添加按钮到按钮组
        buttons.forEach(item => {
            const button = document.createElement('a');
            button.href = 'javascript:void(0);';
            button.id = item.id;
            button.textContent = item.text;
            
            // 应用样式
            Object.assign(button.style, buttonStyle);
            
            // 添加悬停效果
            button.addEventListener('mouseover', () => {
                button.style.backgroundColor = '#357abd'; // 深一点的蓝色
            });
            button.addEventListener('mouseout', () => {
                button.style.backgroundColor = '#4a90e2';
            });
            
            // 添加点击事件
            button.addEventListener('click', item.onClick);
            
            buttonGroup.appendChild(button);
        });

        return buttonGroup;
    }

    function getUserID() {
        let userID = GM_getValue("jd-price-compare-user-id", "");
        if (userID == "") {
            userID = Math.random().toString(36).substring(2);
            GM_setValue("jd-price-compare-user-id", userID);
        }
        return userID;
    }

    function addStatusLine() {
        console.log('[界面] 开始添加状态栏');
        let statusLine = document.createElement('p');
        statusLine.id = 'jd-price-compare-status-line';
        statusLine.style.color = '#000000';
        statusLine.style.padding = '3px';
        statusLine.textContent = `京东商品参数对比工具 v${version}`;

        let statusLineDiv = document.createElement('div');
        statusLineDiv.id = 'jd-price-compare-status-line-div';
        statusLineDiv.style.textAlign = 'center';
        statusLineDiv.appendChild(statusLine);

        // 尝试多个可能的目标位置
        let targetSelectors = [
            '#preview > div.preview-info',
            '#detail > div.tab-con',
            '#detail',
            '.goods-base'
        ];

        let targetElement = null;
        for (let selector of targetSelectors) {
            targetElement = document.querySelector(selector);
            if (targetElement) {
                console.log(`[界面] 找到目标元素: ${selector}`);
                break;
            }
        }

        if (!targetElement) {
            console.error('[界面] 无法找到合适的位置添加状态栏');
            // 如果找不到合适的位置,添加到body末尾
            document.body.appendChild(statusLineDiv);
            return;
        }

        targetElement.parentNode.insertBefore(statusLineDiv, targetElement.nextSibling);
        console.log('[界面] 状态栏添加成功');
    }

    function updateCompareButton() {
        let element = document.getElementById('jd-price-compare-start-button');
        let itemIdList = GM_getValue("jd-price-compare-item-ids", []);
        element.textContent = `开始对比 (${itemIdList.length})`;
    }

    function main() {
        addStatusLine();
        
        // 创建并添加对比按钮组
        const buttonGroup = createCompareButtons();
        const targetElement = document.querySelector('#choose-btns > div');
        if (targetElement) {
            // 创建一个新的容器来放置对比按钮组
            const compareContainer = document.createElement('div');
            compareContainer.style.marginBottom = '16px'; // 添加底部间距
            compareContainer.style.textAlign = 'center'; // 居中对齐
            compareContainer.appendChild(buttonGroup);
            
            // 将对比按钮组插入到购物车按钮之前
            targetElement.insertBefore(compareContainer, targetElement.firstChild);
            console.log('[界面] 对比功能按钮组添加成功');
        } else {
            console.error('[界面] 无法找到合适的位置添加对比按钮组');
        }

        setInterval(updateCompareButton, 512);
    }

    window.addEventListener('load', main);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址