您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
无
// ==UserScript== // @name 店小秘助手 // @namespace http://tampermonkey.net/ // @version 1.6 // @description 无 // @license MIT // @author Rayu // @match https://www.dianxiaomi.com/web/shopeeSite/* // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; function waitForContainer(selector, timeout = 5000) { return new Promise((resolve, reject) => { const intervalTime = 100; let totalTime = 0; const interval = setInterval(() => { const el = document.querySelector(selector); if (el) { clearInterval(interval); resolve(el); } totalTime += intervalTime; if (totalTime >= timeout) { clearInterval(interval); reject(new Error('未找到目标容器:' + selector)); } }, intervalTime); }); } waitForContainer('.d-grid-pager--header-left.min-w-0') .then(container => { if (container.querySelector('#custom-filter-box')) return; const filterDiv = document.createElement('div'); filterDiv.id = 'custom-filter-box'; filterDiv.style.padding = '6px 12px'; filterDiv.style.backgroundColor = '#f9f9f9'; filterDiv.style.border = '1px solid #ccc'; filterDiv.style.borderRadius = '4px'; filterDiv.style.display = 'flex'; filterDiv.style.alignItems = 'center'; filterDiv.style.gap = '8px'; filterDiv.style.minWidth = '450px'; const input = document.createElement('input'); input.type = 'text'; input.placeholder = '输入筛选关键词(如: another in your shop)'; input.style.flex = '1'; input.style.height = '28px'; input.style.padding = '0 8px'; input.style.border = '1px solid #ccc'; input.style.borderRadius = '4px'; const button = document.createElement('button'); button.textContent = '筛选'; button.style.height = '28px'; button.style.padding = '0 12px'; button.style.cursor = 'pointer'; button.style.border = '1px solid #1890ff'; button.style.backgroundColor = '#1890ff'; button.style.color = '#fff'; button.style.borderRadius = '4px'; button.style.fontSize = '14px'; const selectAllBtn = document.createElement('button'); selectAllBtn.textContent = '全选'; selectAllBtn.style.height = '28px'; selectAllBtn.style.padding = '0 12px'; selectAllBtn.style.cursor = 'pointer'; selectAllBtn.style.border = '1px solid #52c41a'; selectAllBtn.style.backgroundColor = '#52c41a'; selectAllBtn.style.color = '#fff'; selectAllBtn.style.borderRadius = '4px'; selectAllBtn.style.fontSize = '14px'; const unselectAllBtn = document.createElement('button'); unselectAllBtn.textContent = '取消全选'; unselectAllBtn.style.height = '28px'; unselectAllBtn.style.padding = '0 12px'; unselectAllBtn.style.cursor = 'pointer'; unselectAllBtn.style.border = '1px solid #f5222d'; unselectAllBtn.style.backgroundColor = '#f5222d'; unselectAllBtn.style.color = '#fff'; unselectAllBtn.style.borderRadius = '4px'; unselectAllBtn.style.fontSize = '14px'; const invertSelectBtn = document.createElement('button'); invertSelectBtn.textContent = '反选'; invertSelectBtn.style.height = '28px'; invertSelectBtn.style.padding = '0 12px'; invertSelectBtn.style.cursor = 'pointer'; invertSelectBtn.style.border = '1px solid #faad14'; invertSelectBtn.style.backgroundColor = '#faad14'; invertSelectBtn.style.color = '#fff'; invertSelectBtn.style.borderRadius = '4px'; invertSelectBtn.style.fontSize = '14px'; // 新增“打开编辑页”按钮 const openSelectedBtn = document.createElement('button'); openSelectedBtn.textContent = '打开编辑页'; openSelectedBtn.style.height = '28px'; openSelectedBtn.style.padding = '0 12px'; openSelectedBtn.style.cursor = 'pointer'; openSelectedBtn.style.border = '1px solid #13c2c2'; openSelectedBtn.style.backgroundColor = '#13c2c2'; openSelectedBtn.style.color = '#fff'; openSelectedBtn.style.borderRadius = '4px'; openSelectedBtn.style.fontSize = '14px'; filterDiv.appendChild(input); filterDiv.appendChild(button); filterDiv.appendChild(selectAllBtn); filterDiv.appendChild(unselectAllBtn); filterDiv.appendChild(invertSelectBtn); filterDiv.appendChild(openSelectedBtn); container.appendChild(filterDiv); // 筛选按钮逻辑:关键词匹配失败原因中span文本 button.addEventListener('click', () => { const keyword = input.value.trim(); if (!keyword) { document.querySelectorAll('tr.vxe-body--row').forEach(row => { row.style.display = ''; }); return; } document.querySelectorAll('tr.vxe-body--row').forEach(row => { const errorMsgElem = row.querySelector('.product-list-error-msg span'); if (errorMsgElem && errorMsgElem.textContent.includes(keyword)) { row.style.display = ''; } else { row.style.display = 'none'; } }); }); // 全选:显示的行选中(如果没选中才点checkbox) selectAllBtn.addEventListener('click', () => { document.querySelectorAll('tr.vxe-body--row').forEach(row => { if (row.style.display !== 'none') { const checkbox = row.querySelector('input.ant-checkbox-input[type="checkbox"]'); if (checkbox && !checkbox.checked) { checkbox.click(); } } }); }); // 取消全选:全部复选框取消选中 unselectAllBtn.addEventListener('click', () => { document.querySelectorAll('tr.vxe-body--row').forEach(row => { const checkbox = row.querySelector('input.ant-checkbox-input[type="checkbox"]'); if (checkbox && checkbox.checked) { checkbox.click(); } }); }); // 反选:显示的行复选框状态取反 invertSelectBtn.addEventListener('click', () => { document.querySelectorAll('tr.vxe-body--row').forEach(row => { const checkbox = row.querySelector('input.ant-checkbox-input[type="checkbox"]'); if (checkbox && row.style.display !== 'none') { checkbox.click(); } }); }); // 打开编辑页按钮:批量打开所有选中行的“编辑”链接,带延迟避免浏览器拦截 openSelectedBtn.addEventListener('click', () => { const rows = Array.from(document.querySelectorAll('tr.vxe-body--row')); const selectedRows = rows.filter(row => { const checkbox = row.querySelector('input.ant-checkbox-input[type="checkbox"]'); return checkbox && checkbox.checked; }); if (selectedRows.length === 0) { alert('未选中任何商品或未找到可打开的编辑链接'); return; } let openedCount = 0; selectedRows.forEach((row, index) => { setTimeout(() => { const editLinkElem = row.querySelector('td[colid="col_13"] a[href*="/edit?id="]'); if (editLinkElem && editLinkElem.href) { const url = new URL(editLinkElem.getAttribute('href'), location.origin); window.open(url.href, '_blank'); openedCount++; } else { console.warn('未找到编辑链接,无法打开', row); } if (index === selectedRows.length -1) { setTimeout(() => { alert(`已尝试打开${openedCount}个编辑页,请检查弹窗拦截。`); }, 300); } }, 300 * index); }); }); }) .catch(err => { console.error(err); }); // ====================== 核心配置:两种结构的表头关键词(无需修改) ====================== const HEADER_CONFIG = { // 结构A:顏色+尺寸(无款式) colorSize: { colorKey: '顏色', // 顏色表头(繁体) sizeKey: '尺寸' // 尺寸表头 }, // 结构B:仅款式(无顏色/尺寸) styleOnly: { styleKey: '款式' // 款式表头 } }; console.log("📌 双结构配置:支持「顏色+尺寸」和「仅款式」两种表格", HEADER_CONFIG); // ====================== 1. 等待表格加载+识别表头结构 ====================== function waitAndDetectTableStructure(timeout = 20000) { return new Promise((resolve) => { let waitTime = 0; const checkInterval = 1000; const checkTimer = setInterval(() => { waitTime += checkInterval; console.log(`等待表格:已等待${waitTime/1000}秒,正在检测表头结构`); // 步骤1:找核心容器#skuDataInfo const skuContainer = document.querySelector('#skuDataInfo'); if (!skuContainer) { if (waitTime >= timeout) { clearInterval(checkTimer); resolve(null); } return; } // 步骤2:找表格(需有表头和产品行) const productTable = skuContainer.querySelector('table'); if (!productTable || !productTable.querySelector('thead') || !productTable.querySelector('tbody')) { if (waitTime >= timeout) { clearInterval(checkTimer); resolve(null); } return; } // 步骤3:确认有产品行(至少1行) const productRows = productTable.querySelectorAll('tbody tr'); if (productRows.length === 0) { if (waitTime >= timeout) { clearInterval(checkTimer); resolve(null); } return; } // 步骤4:关键!检测表格属于哪种结构 const tableStructure = detectHeaderStructure(productTable); if (tableStructure.type !== 'unknown') { // 识别到有效结构 clearInterval(checkTimer); console.log(`✅ 表格就绪:共${productRows.length}行,检测到表头结构→${tableStructure.type}`); resolve({ skuContainer, productTable, productRows, tableStructure // 携带结构信息(类型+列索引) }); } // 超时容错:即使未识别结构,也返回当前状态 if (waitTime >= timeout) { clearInterval(checkTimer); const tableStructure = detectHeaderStructure(productTable); resolve({ skuContainer, productTable, productRows, tableStructure }); } }, checkInterval); }); } // ====================== 2. 核心函数:检测表格属于哪种结构 ====================== function detectHeaderStructure(table) { const headers = Array.from(table.querySelectorAll('thead th, thead td')); const structure = { type: 'unknown', indexes: {} }; // unknown=未识别 // 先检测是否为「结构A:顏色+尺寸」(优先级:先找颜色+尺寸,再找款式) const colorIdx = findHeaderIndex(headers, HEADER_CONFIG.colorSize.colorKey); const sizeIdx = findHeaderIndex(headers, HEADER_CONFIG.colorSize.sizeKey); if (colorIdx !== -1 && sizeIdx !== -1) { structure.type = 'colorSize'; // 结构A标识 structure.indexes = { colorIdx, sizeIdx }; // 颜色/尺寸列索引 console.log(`🔍 检测到结构A(顏色+尺寸):顏色列第${colorIdx+1}列,尺寸列第${sizeIdx+1}列`); return structure; } // 再检测是否为「结构B:仅款式」 const styleIdx = findHeaderIndex(headers, HEADER_CONFIG.styleOnly.styleKey); if (styleIdx !== -1) { structure.type = 'styleOnly'; // 结构B标识 structure.indexes = { styleIdx }; // 款式列索引 console.log(`🔍 检测到结构B(仅款式):款式列第${styleIdx+1}列`); return structure; } // 两种结构都未识别 console.error("❌ 未识别表头结构:既无「顏色+尺寸」,也无「款式」表头"); return structure; } // ====================== 3. 辅助函数:根据关键词找表头索引(兼容空格) ====================== function findHeaderIndex(headers, targetKey) { for (let i = 0; i < headers.length; i++) { const headerText = headers[i].textContent.trim().replace(/\s+/g, ''); // 去所有空格 if (headerText === targetKey) { return i; // 返回列索引(0开始) } } return -1; // 未找到返回-1 } // ====================== 4. 按表格结构提取数据(分两种情况) ====================== function extractDataByStructure(productRows, tableStructure) { const priceList = []; const { type, indexes } = tableStructure; // 情况1:结构A(顏色+尺寸) if (type === 'colorSize') { const { colorIdx, sizeIdx } = indexes; productRows.forEach((row, idx) => { try { const tds = row.querySelectorAll('td'); if (tds.length === 0) return; // 提取顏色 const color = (colorIdx < tds.length) ? tds[colorIdx].textContent.trim().replace(/\s+/g, ' ') : ''; // 提取尺寸 const size = (sizeIdx < tds.length) ? tds[sizeIdx].textContent.trim().replace(/\s+/g, ' ') : ''; // 提取价格 const priceEl = row.querySelector('.font-size-10\\!.text-left.ml-10'); if (!priceEl) return; const priceText = priceEl.textContent.trim(); const priceMatch = priceText.match(/≈\s*(\d+\.\d+)\s*USD/); if (!priceMatch) return; // 规格:顏色-尺寸(无尺寸时只显顏色) let spec = color; if (size) spec = `${color} - ${size}`; spec = spec || '未识别规格'; priceList.push({ value: parseFloat(priceMatch[1]), text: priceText, spec }); console.log(`✅ 第${idx+1}行(结构A):价格=${priceText} | 规格=${spec}`); } catch (e) { console.warn(`第${idx+1}行(结构A):提取出错→${e.message}`); } }); } // 情况2:结构B(仅款式) else if (type === 'styleOnly') { const { styleIdx } = indexes; productRows.forEach((row, idx) => { try { const tds = row.querySelectorAll('td'); if (tds.length === 0) return; // 提取款式 const style = (styleIdx < tds.length) ? tds[styleIdx].textContent.trim().replace(/\s+/g, ' ') : ''; // 提取价格 const priceEl = row.querySelector('.font-size-10\\!.text-left.ml-10'); if (!priceEl) return; const priceText = priceEl.textContent.trim(); const priceMatch = priceText.match(/≈\s*(\d+\.\d+)\s*USD/); if (!priceMatch) return; // 规格:款式 const spec = style || '未识别款式'; priceList.push({ value: parseFloat(priceMatch[1]), text: priceText, spec }); console.log(`✅ 第${idx+1}行(结构B):价格=${priceText} | 规格=款式:${spec}`); } catch (e) { console.warn(`第${idx+1}行(结构B):提取出错→${e.message}`); } }); } return priceList.length > 0 ? priceList : null; } // ====================== 5. 计算最值+插入容器(按结构显示规格) ====================== function getMinMax(priceList) { const sorted = [...priceList].sort((a, b) => a.value - b.value); const min = sorted[0]; const maxVal = sorted.at(-1).value; const maxSpecs = priceList.filter(item => item.value.toFixed(2) === maxVal.toFixed(2)).map(item => item.spec); return { min: { text: min.text, spec: min.spec }, max: { text: sorted.at(-1).text, specs: maxSpecs } }; } function insertContainer(priceMinMax, skuContainer, tableStructure) { const oldContainer = document.getElementById('price-range-container'); if (oldContainer) oldContainer.remove(); const container = document.createElement('div'); container.id = "price-range-container"; container.style.cssText = ` margin: 15px; padding: 12px 15px; background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 6px; font-size: 12px; color: #333; z-index: 9999; position: relative; `; // 按结构显示规格标题(结构A显“顏色-尺寸”,结构B显“款式”) const specTitle = tableStructure.type === 'colorSize' ? '规格(顏色-尺寸)' : '规格(款式)'; container.innerHTML = ` <div style="font-size:14px; font-weight:bold; margin-bottom:8px; color:#1890ff;">#skuDataInfo 价格范围(USD)</div> <div style="margin-bottom:10px; line-height:1.6;"> <div style="color:#28a745;">🔻 最小值:${priceMinMax.min.text}</div> <div style="padding-left:18px; margin-top:3px; color:#666; font-size:11px;">${specTitle}:${priceMinMax.min.spec}</div> </div> <div style="line-height:1.6;"> <div style="color:#dc3545;">🔺 最大值:${priceMinMax.max.text}</div> <div style="padding-left:18px; margin-top:3px; color:#666; font-size:11px;"> ${priceMinMax.max.specs.map(s => `- ${specTitle}:${s}`).join('<br>')} </div> </div> `; const formContent = skuContainer.querySelector('.form-card-content') || skuContainer; formContent.prepend(container); console.log(`✅ 价格容器已插入(适配结构:${tableStructure.type})`); } // ====================== 6. 主流程:自动识别→提取→显示 ====================== async function main() { try { // 步骤1:等待表格+识别结构 const data = await waitAndDetectTableStructure(); if (!data || !data.productTable || data.productRows.length === 0 || data.tableStructure.type === 'unknown') { console.error("❌ 无有效表格/产品行,或未识别表头结构,脚本终止"); return; } const { skuContainer, productRows, tableStructure } = data; // 步骤2:按结构提取数据 const priceList = extractDataByStructure(productRows, tableStructure); if (!priceList) { console.error("❌ 未提取到有效价格数据"); return; } // 步骤3:显示价格范围 const priceMinMax = getMinMax(priceList); insertContainer(priceMinMax, skuContainer, tableStructure); } catch (e) { console.error("❌ 脚本主流程出错:", e); } } // 启动 main(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址