// ==UserScript== // @name 店小蜜5倍价格筛选 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 5倍价格筛选 // @author Rayu // @match https://www.dianxiaomi.com/web/shopeeSite/* // @exclude https://www.dianxiaomi.com/web/shopeeSite/edit* // @grant none // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/554912/%E5%BA%97%E5%B0%8F%E8%9C%9C5%E5%80%8D%E4%BB%B7%E6%A0%BC%E7%AD%9B%E9%80%89.user.js // @updateURL https://update.greasyfork.icu/scripts/554912/%E5%BA%97%E5%B0%8F%E8%9C%9C5%E5%80%8D%E4%BB%B7%E6%A0%BC%E7%AD%9B%E9%80%89.meta.js // ==/UserScript== (function() { 'use strict'; // 等待页面加载完成 function waitForElement(selector, callback, maxWait = 10000) { const startTime = Date.now(); const interval = setInterval(() => { const element = document.querySelector(selector); if (element) { clearInterval(interval); callback(); } else if (Date.now() - startTime > maxWait) { clearInterval(interval); console.log('等待元素超时'); } }, 500); } // ================== 数据持久化缓存模块 ================== const CACHE_KEY = 'dianxiaomi_product_cache'; const CACHE_EXPIRE_DAYS = 7; // 缓存过期天数 // 缓存管理器 const CacheManager = { // 保存数据到localStorage save(data) { try { const cacheData = { data: data, timestamp: Date.now(), version: '1.0.0' }; localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData)); console.log('💾 数据已保存到localStorage,当前数量:', data.length); } catch (e) { console.error('❌ 保存缓存失败:', e); } }, // 从localStorage加载数据 load() { try { const cached = localStorage.getItem(CACHE_KEY); if (!cached) { console.log('📂 未找到缓存数据,初始化为空数组'); return []; } const cacheData = JSON.parse(cached); const age = Date.now() - cacheData.timestamp; const ageDays = Math.floor(age / (1000 * 60 * 60 * 24)); console.log(`📂 加载缓存数据: ${cacheData.data.length} 条记录`); console.log(`⏰ 缓存时间: ${ageDays} 天前`); return cacheData.data || []; } catch (e) { console.error('❌ 加载缓存失败:', e); return []; } }, // 清除所有缓存 clear() { try { localStorage.removeItem(CACHE_KEY); console.log('🗑️ 已清除localStorage中的所有缓存'); } catch (e) { console.error('❌ 清除缓存失败:', e); } }, // 清理过期缓存(超过7天) cleanExpired() { try { const cached = localStorage.getItem(CACHE_KEY); if (!cached) return; const cacheData = JSON.parse(cached); const age = Date.now() - cacheData.timestamp; const ageDays = age / (1000 * 60 * 60 * 24); if (ageDays > CACHE_EXPIRE_DAYS) { this.clear(); console.log(`🧹 自动清理 ${Math.floor(ageDays)} 天前的过期缓存`); return true; } return false; } catch (e) { console.error('❌ 清理过期缓存失败:', e); return false; } }, // 获取缓存信息 getInfo() { try { const cached = localStorage.getItem(CACHE_KEY); if (!cached) { return { exists: false, count: 0, ageDays: 0 }; } const cacheData = JSON.parse(cached); const age = Date.now() - cacheData.timestamp; const ageDays = Math.floor(age / (1000 * 60 * 60 * 24)); return { exists: true, count: cacheData.data?.length || 0, ageDays: ageDays, timestamp: cacheData.timestamp }; } catch (e) { console.error('❌ 获取缓存信息失败:', e); return { exists: false, count: 0, ageDays: 0 }; } } }; // 页面加载时自动清理过期缓存 CacheManager.cleanExpired(); // 从localStorage加载已缓存的数据 let capturedApiData = CacheManager.load(); // 将capturedApiData和缓存管理器暴露到全局作用域 window.capturedApiData = capturedApiData; window.CacheManager = CacheManager; console.log('🚀 API拦截器已启动'); console.log('💡 提示: 可以在控制台使用 window.capturedApiData 或 capturedApiData 访问捕获的数据'); // 公共函数:添加悬停效果 function addHoverEffect(element, hoverColor, normalColor) { element.addEventListener('mouseenter', function() { this.style.backgroundColor = hoverColor; }); element.addEventListener('mouseleave', function() { this.style.backgroundColor = normalColor; }); } // 拦截XMLHttpRequest const originalXHROpen = XMLHttpRequest.prototype.open; const originalXHRSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url) { this._url = url; this._method = method; return originalXHROpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function(body) { const xhr = this; const requestBody = body; this.addEventListener('load', function() { try { if (this._url && this.responseText) { // 检查是否是目标API const isTargetApi = this._url.includes('shopeeProduct') || this._url.includes('getProductById'); if (isTargetApi) { console.log('🎯 捕获到目标API (XHR):', this._url); } // 尝试解析JSON响应 const data = JSON.parse(this.responseText); console.log('🔍 XHR请求:', this._url); console.log('📦 响应数据:', data); capturedApiData.push({ url: this._url, method: 'XHR', data: data, timestamp: new Date().toISOString() }); // 自动保存到localStorage CacheManager.save(capturedApiData); if (isTargetApi) { console.log('✅ 已保存到 capturedApiData,当前总数:', capturedApiData.length); } } } catch (e) { // 忽略非JSON响应 if (this._url && (this._url.includes('shopeeProduct') || this._url.includes('getProductById'))) { console.error('❌ 解析目标API响应失败:', this._url, e); } } }); this.addEventListener('error', function() { console.error('❌ XHR请求失败:', this._url); }); return originalXHRSend.apply(this, arguments); }; // 拦截fetch const originalFetch = window.fetch; window.fetch = function() { const url = arguments[0]; const isTargetApi = typeof url === 'string' && (url.includes('shopeeProduct') || url.includes('getProductById')); if (isTargetApi) { console.log('🎯 准备发起目标API请求 (Fetch):', url); } return originalFetch.apply(this, arguments).then(response => { // 克隆响应以便读取 const clonedResponse = response.clone(); clonedResponse.json().then(data => { console.log('🔍 Fetch请求:', url); console.log('📦 响应数据:', data); capturedApiData.push({ url: url, method: 'Fetch', data: data, timestamp: new Date().toISOString() }); // 自动保存到localStorage CacheManager.save(capturedApiData); if (isTargetApi) { console.log('✅ 已保存到 capturedApiData,当前总数:', capturedApiData.length); } }).catch((e) => { // 忽略非JSON响应 if (isTargetApi) { console.error('❌ 解析目标API响应失败:', url, e); } }); return response; }).catch(error => { console.error('❌ Fetch请求失败:', url, error); throw error; }); }; // 从API数据中提取商品信息(新格式) function extractProductInfo() { const products = []; console.log('开始从API数据中提取商品信息...'); console.log(`已捕获 ${capturedApiData.length} 个API请求`); // 遍历所有捕获的API数据 capturedApiData.forEach((apiCall, index) => { console.log(`\n处理API请求 ${index + 1}:`, apiCall.url); // 判断API类型 const isGetProductById = apiCall.url.includes('getProductById'); console.log(`🔖 API类型: ${isGetProductById ? 'getProductById (规格列表)' : 'pageList (商品列表)'}`); // 如果是getProductById API,直接提取variations数组 if (isGetProductById && apiCall.data && apiCall.data.data && Array.isArray(apiCall.data.data)) { console.log(`📦 发现 getProductById 数据,包含 ${apiCall.data.data.length} 个规格`); // 从第一个variation中获取商品基本信息 const firstVariation = apiCall.data.data[0]; if (firstVariation) { const product = { name: firstVariation.dxmProductName || '(从规格数据获取)', id: firstVariation.dxmProductId || firstVariation.id, // 保留原始ID用于数据合并 idStr: firstVariation.idStr || firstVariation.id, // 用于UI显示 dxmProductId: firstVariation.dxmProductId || firstVariation.id, specifications: [] }; // 提取所有规格 apiCall.data.data.forEach((variation, vIndex) => { // 组合 option1 和 option2 const optionText = [variation.option1, variation.option2].filter(Boolean).join(' - '); console.log(` [${vIndex + 1}] ${optionText}: ¥${variation.price} (idStr: ${variation.idStr || '无'})`); if ((variation.option1 !== undefined || variation.option2 !== undefined) && variation.price !== undefined) { product.specifications.push({ option: optionText, price: variation.price, idStr: variation.idStr || variation.id }); } }); if (product.specifications.length > 0) { console.log(`✅ 从 getProductById 提取商品: ${product.name} (DXM ID: ${product.dxmProductId})`); console.log(` └─ 规格数量: ${product.specifications.length}`); products.push(product); } } // getProductById API处理完毕,跳过递归查找 return; } // 对于pageList API,使用递归查找 // 递归查找数据中的商品信息 function findProducts(obj, path = '') { if (!obj || typeof obj !== 'object') return; // 检查当前对象是否包含商品信息(必须有name和id) if (obj.name && obj.id) { console.log(`\n🔍 发现潜在商品对象: ${obj.name} (ID: ${obj.id})`); console.log(` ├─ 路径: ${path}`); console.log(` ├─ idStr: ${obj.idStr || '无'}`); console.log(` ├─ dxmProductId: ${obj.dxmProductId || '无'}`); console.log(` ├─ variations 类型: ${Array.isArray(obj.variations) ? '数组' : typeof obj.variations}`); console.log(` └─ variations 长度: ${Array.isArray(obj.variations) ? obj.variations.length : 'N/A'}`); const product = { name: obj.name, id: obj.id, // 保留原始ID用于数据合并 idStr: obj.idStr || obj.id, // 用于UI显示 dxmProductId: obj.idStr || obj.dxmProductId || obj.id, // 优先使用idStr specifications: [] }; // 提取variations数组 if (Array.isArray(obj.variations)) { console.log(` 📦 开始处理 ${obj.variations.length} 个 variations...`); obj.variations.forEach((variation, vIndex) => { // 组合 option1 和 option2 const optionText = [variation.option1, variation.option2].filter(Boolean).join(' - '); console.log(` [${vIndex + 1}] option1=${variation.option1}, option2=${variation.option2 || '无'}, price=${variation.price}, dxmProductId=${variation.dxmProductId || '无'}`); if ((variation.option1 !== undefined || variation.option2 !== undefined) && variation.price !== undefined) { product.specifications.push({ option: optionText, price: variation.price }); } }); console.log(` ✅ 成功提取 ${product.specifications.length} 个规格`); } else { console.log(` ⚠️ variations 不是数组或不存在`); // 输出整个对象的键,帮助调试 console.log(` 📋 对象的所有键:`, Object.keys(obj).join(', ')); } // dxmProductId已在创建product时设置(优先级:idStr > dxmProductId > id) console.log(` ✓ 使用的 dxmProductId: ${product.dxmProductId}`); // 只有当有规格数据时才添加商品 if (product.specifications.length > 0) { console.log(`✓ 找到商品: ${product.name} (ID: ${product.id}, DXM ID: ${product.dxmProductId || '无'})`); console.log(` └─ 规格数量: ${product.specifications.length}`); console.log(` └─ 路径: ${path}`); products.push(product); console.log(` ⚙️ 调试模式: 继续递归查找所有嵌套对象`); } else { console.log(` ❌ 该商品没有有效的规格数据,跳过`); } } // 递归遍历对象和数组 if (Array.isArray(obj)) { obj.forEach((item, idx) => findProducts(item, `${path}[${idx}]`)); } else { // 遍历对象的所有属性 for (let key in obj) { if (obj.hasOwnProperty(key)) { findProducts(obj[key], path ? `${path}.${key}` : key); } } } } findProducts(apiCall.data); }); console.log(`\n共找到 ${products.length} 个商品(去重前)`); // 去重:合并相同ID的商品数据 const productMap = new Map(); products.forEach(product => { const key = product.id; // 使用原始ID作为唯一标识符 if (productMap.has(key)) { // 如果已存在该商品,合并规格数据 const existing = productMap.get(key); console.log(`🔄 发现重复商品: ${product.name} (ID: ${key})`); console.log(` 合并前规格数量: ${existing.specifications.length}`); console.log(` 待合并规格数量: ${product.specifications.length}`); // 合并规格数组(去重相同option的规格) const specMap = new Map(); existing.specifications.forEach(spec => { specMap.set(spec.option, spec); }); product.specifications.forEach(spec => { if (!specMap.has(spec.option)) { specMap.set(spec.option, spec); } }); existing.specifications = Array.from(specMap.values()); console.log(` 合并后规格数量: ${existing.specifications.length}`); } else { // 首次出现该商品,直接添加 productMap.set(key, product); } }); const mergedProducts = Array.from(productMap.values()); console.log(`\n✅ 去重后共 ${mergedProducts.length} 个商品`); return mergedProducts; } // 显示商品数据 function displayProductData(products) { console.log('\n=== 商品信息数据 ==='); console.log(`共找到 ${products.length} 个商品`); console.log('\n详细列表:'); products.forEach((product, index) => { console.log(`${index + 1}. 商品: ${product.name}`); console.log(` ID: ${product.id}`); console.log(` DXM ID: ${product.dxmProductId}`); console.log(` 规格数量: ${product.specifications.length} 个`); product.specifications.forEach((spec, sIndex) => { console.log(` [${sIndex + 1}] ${spec.option}: ${spec.price}`); }); console.log('---'); }); return products; } // ⭐ [新增] 自动滚动并勾选高价差商品的函数 async function selectHighPriceProducts() { console.log('🔘 一键勾选按钮被点击'); const btn = document.getElementById('dianxiaomi-select-high-price-btn'); if (!btn) { console.error('❌ 未找到一键勾选按钮元素'); return; } // 检查是否存在商品数据 if (!window.dianxiaomiProductData || window.dianxiaomiProductData.length === 0) { console.warn('⚠️ 商品数据不存在'); alert('请先点击"手动获取"按钮加载商品数据!'); return; } console.log(`📊 当前商品数据数量: ${window.dianxiaomiProductData.length}`); btn.disabled = true; btn.textContent = '🔄 正在勾选...'; // 筛选出所有高价差商品 const highPriceProducts = window.dianxiaomiProductData.filter(product => { if (!product.specifications || product.specifications.length <= 1) return false; const prices = product.specifications.map(s => parseFloat(s.price)).filter(p => p > 0); if (prices.length <= 1) return false; const minPrice = Math.min(...prices); const maxPrice = Math.max(...prices); return (maxPrice / minPrice) > 5; }); if (highPriceProducts.length === 0) { alert('未在当前列表中找到高价差商品。'); btn.disabled = false; btn.textContent = `✔️ 一键勾选 (0)`; return; } console.log(`准备勾选 ${highPriceProducts.length} 个高价差商品...`); // 寻找页面上的表格容器 const tableWrapper = document.querySelector('.vxe-table--body-wrapper'); if (!tableWrapper) { alert('错误:无法在页面上找到商品表格,无法执行勾选操作。'); btn.disabled = false; btn.textContent = `✔️ 一键勾选 (${highPriceProducts.length})`; return; } // 滚动回页面顶部 window.scrollTo({ top: 0, behavior: 'auto' }); await new Promise(resolve => setTimeout(resolve, 300)); console.log('🔝 已滚动回页面顶部,开始一次性滚动查找所有商品...'); // 创建商品名称集合用于快速查找 const productNames = new Set(highPriceProducts.map(p => p.name)); const foundRows = new Map(); // 存储找到的商品行:商品名称 -> 行元素 // 滚动参数配置 const scrollSpeed = 200; // 每次滚动像素数 const scrollInterval = 5; // 每次滚动间隔(毫秒) const maxScrolls = 500; // 最大滚动次数 let scrollCount = 0; btn.textContent = `🔄 滚动查找中...`; // 一次性从头到尾滚动页面,收集所有目标商品行 while (scrollCount < maxScrolls && foundRows.size < highPriceProducts.length) { // 获取当前页面中所有的商品行 const allRows = document.querySelectorAll('.vxe-table--body-wrapper table tbody tr'); // 在当前可视区域查找包含目标商品名称的行 for (const row of allRows) { const rect = row.getBoundingClientRect(); const windowHeight = window.innerHeight; // 判断行是否在浏览器窗口可视区域内 const isVisible = rect.top >= 0 && rect.top <= windowHeight && rect.bottom >= 0 && rect.bottom <= windowHeight; if (isVisible) { const rowText = row.textContent; // 检查是否包含任何目标商品名称 for (const productName of productNames) { if (rowText.includes(productName) && !foundRows.has(productName)) { foundRows.set(productName, row); console.log(`✅ 找到目标商品(第 ${scrollCount + 1} 次滚动): ${productName}`); btn.textContent = `🔄 已找到 ${foundRows.size}/${highPriceProducts.length}`; break; } } } } // 如果已找到所有商品,停止滚动 if (foundRows.size >= highPriceProducts.length) { console.log('✅ 已找到所有目标商品,停止滚动'); break; } // 使用 window.scrollBy 进行真实页面滚动 window.scrollBy({ top: scrollSpeed, behavior: 'auto' }); await new Promise(resolve => setTimeout(resolve, scrollInterval)); scrollCount++; // 检查是否已滚动到页面底部 const scrollHeight = document.documentElement.scrollHeight; const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const clientHeight = window.innerHeight; if (scrollTop + clientHeight >= scrollHeight - 10) { console.log('⚠️ 已滚动到页面底部'); break; } } console.log(`\n📋 滚动完成,共找到 ${foundRows.size}/${highPriceProducts.length} 个商品,开始勾选...`); // 统一勾选所有找到的商品 let successCount = 0; let notFoundCount = highPriceProducts.length - foundRows.size; for (let i = 0; i < highPriceProducts.length; i++) { const product = highPriceProducts[i]; const targetRow = foundRows.get(product.name); btn.textContent = `🔄 勾选中(${i + 1}/${highPriceProducts.length})`; if (targetRow) { try { // 查找并点击复选框 const checkbox = targetRow.querySelector('.ant-checkbox-input'); if (checkbox) { checkbox.click(); successCount++; console.log(`✅ 已勾选 (${successCount}/${foundRows.size}): ${product.name}`); await new Promise(resolve => setTimeout(resolve, 50)); } else { console.warn(`❌ 在行内未找到复选框: ${product.name}`); notFoundCount++; successCount--; } } catch (e) { console.error(`❌ 勾选商品时发生错误: ${product.name}`, e); notFoundCount++; } } else { console.warn(`⚠️ 未找到商品: ${product.name}`); } } alert(`勾选完成!\n\n成功勾选: ${successCount} 个\n未找到或失败: ${notFoundCount} 个`); console.log(`勾选操作完成。成功: ${successCount}, 失败/未找到: ${notFoundCount}`); btn.disabled = false; btn.textContent = `✅ 已完成`; setTimeout(() => { btn.textContent = `✔️ 一键勾选 (${highPriceProducts.length})`; }, 5000); } // 创建浮动窗口UI function createFloatingPanel() { // 创建容器 const panel = document.createElement('div'); panel.id = 'dianxiaomi-price-panel'; panel.style.cssText = ` position: fixed; top: 10px; right: 10px; width: 400px; max-height: 600px; background: white; border: 2px solid #1890ff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 999999; font-family: Arial, sans-serif; overflow: hidden; display: flex; flex-direction: column; `; // 创建标题栏 const header = document.createElement('div'); header.style.cssText = ` background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 15px; font-weight: bold; font-size: 16px; display: flex; justify-content: space-between; align-items: center; cursor: move; `; header.innerHTML = ` 📊 商品价格数据 `; // 创建工具栏 const toolbar = document.createElement('div'); toolbar.style.cssText = ` padding: 10px 15px; background: #f5f5f5; border-bottom: 1px solid #e0e0e0; display: flex; gap: 10px; align-items: center; `; toolbar.innerHTML = ` 准备就绪 `; // 创建内容区域 const content = document.createElement('div'); content.id = 'dianxiaomi-content'; content.style.cssText = ` flex: 1; overflow-y: auto; padding: 15px; max-height: 500px; `; content.innerHTML = '