// ==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);
})();