您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
该脚本可用于对比不限数量的同类型商品(如:手机、笔记本)的详细参数
当前为
// ==UserScript== // @name 京东商品参数对比工具 // @namespace http://tampermonkey.net/ // @version 20250616 // @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 = '20250616'; 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, index) => { 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: 200px; object-fit: contain;"> </div>`; } else if (column === '基本信息_商品编号') { return `<div class="d-flex align-items-center"> <a href="https://item.jd.com/${flatData[column]}.html" target="_blank" class="text-primary text-decoration-none me-2">${flatData[column] || ''}</a> <div class="btn-group btn-group-sm"> <button class="btn btn-outline-secondary btn-hide" data-index="${index}"> <i class="fas fa-eye-slash"></i> </button> <button class="btn btn-outline-info btn-alternative" data-index="${index}"> <i class="fas fa-star"></i> </button> </div> </div>`; } 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>`; } else if (column === '备注') { return `<div class="notes-container"> <textarea class="form-control notes-input" data-index="${index}" placeholder="添加备注...">${flatData[column] || ''}</textarea> </div>`; } else if (column === '状态') { const status = flatData[column] || '正常'; let badgeClass = 'bg-success'; if (status === '隐藏') badgeClass = 'bg-secondary'; if (status === '备选') badgeClass = 'bg-info'; return `<span class="badge ${badgeClass}">${status}</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; } } .notes-container { position: relative; } .notes-input { width: 100%; min-height: 60px; resize: vertical; } .btn-hide, .btn-alternative { padding: 0.25rem 0.5rem; } .btn-hide:hover { background-color: #6c757d; color: white; } .btn-alternative:hover { background-color: #0dcaf0; color: white; } .btn-alternative.active { background-color: #0dcaf0; color: white; } .btn-hide.active { background-color: #6c757d; color: white; } </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() { const table = $('#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, columnDefs: [ { targets: '图片', orderable: false }, { targets: '备注', orderable: false } ], 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'); } }); // 处理隐藏按钮点击 $(document).on('click', '.btn-hide', function() { const index = $(this).data('index'); const row = table.row(index); const statusCell = row.cell(function(idx, data, node) { return $(node).find('.badge').length > 0; }); const currentStatus = statusCell.data(); const newStatus = currentStatus === '隐藏' ? '正常' : '隐藏'; // 更新状态 statusCell.data(newStatus); statusCell.html('<span class="badge ' + (newStatus === '隐藏' ? 'bg-secondary' : 'bg-success') + '">' + newStatus + '</span>'); // 更新按钮状态 $(this).toggleClass('active'); // 保存状态到localStorage const items = JSON.parse(localStorage.getItem('jd-compare-items') || '{}'); const itemId = row.data()[0].match(/href=".*\/(\d+)\.html"/)[1]; if (items[itemId]) { items[itemId].data.状态 = newStatus; localStorage.setItem('jd-compare-items', JSON.stringify(items)); } }); // 处理备选按钮点击 $(document).on('click', '.btn-alternative', function() { const index = $(this).data('index'); const row = table.row(index); const statusCell = row.cell(function(idx, data, node) { return $(node).find('.badge').length > 0; }); const currentStatus = statusCell.data(); const newStatus = currentStatus === '备选' ? '正常' : '备选'; // 更新状态 statusCell.data(newStatus); statusCell.html('<span class="badge ' + (newStatus === '备选' ? 'bg-info' : 'bg-success') + '">' + newStatus + '</span>'); // 更新按钮状态 $(this).toggleClass('active'); // 保存状态到localStorage const items = JSON.parse(localStorage.getItem('jd-compare-items') || '{}'); const itemId = row.data()[0].match(/href=".*\/(\d+)\.html"/)[1]; if (items[itemId]) { items[itemId].data.状态 = newStatus; localStorage.setItem('jd-compare-items', JSON.stringify(items)); } }); // 处理备注输入 $(document).on('change', '.notes-input', function() { const index = $(this).data('index'); const row = table.row(index); const note = $(this).val(); // 保存备注到localStorage const items = JSON.parse(localStorage.getItem('jd-compare-items') || '{}'); const itemId = row.data()[0].match(/href=".*\/(\d+)\.html"/)[1]; if (items[itemId]) { items[itemId].data.备注 = note; localStorage.setItem('jd-compare-items', JSON.stringify(items)); } }); // 导出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) { // 从URL中获取商品ID 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] 无法从URL获取商品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或关注我们的公众号极客氢云获取最新地址