// ==UserScript== // @name temu 批量操作 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 提取 [备货单号] [方向] [货号] [SKC] [尺码] [SKU货号] [数量] 到表格,按方向分组,一键复制 // @author jbn // @match https://agentseller.temu.com/* // @grant GM_setClipboard // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== (function () { 'use strict'; // ---------- 提取函数 ---------- function extractTableData(table) { if (!table || table.tagName !== 'TABLE') { console.error('请传入有效的 HTMLTableElement'); return []; } const rows = table.querySelectorAll('tbody tr'); const result = []; let currentOrder = null, currentDirection = null, currentProductCode = null, currentSKC = null, currentAttr = null, currentSKU = null, cuurrentQuantity = null; // ---------- 主循环 ---------- for (const tr of rows) { currentSKU = null for (const td of tr.querySelectorAll('td')) { if (getActualColumnIndex(td) === 1) { currentOrder = td.querySelector('div[class*="orderSnInner"]').innerText currentDirection = td.querySelector('div[class*="sub-order-cell_meta"] span:last-child').innerText } if (getActualColumnIndex(td) === 2) { currentSKC = td.innerText.match(/SKC[::]\s*([^\s]+)/)?.[1] currentProductCode = td.innerText.match(/货号[::]\s*([^\s]+)/)?.[1] } if (getActualColumnIndex(td) === 4) { currentSKU = td.innerText.match(/SKU 货号[::]\s*([^\s]+)/)?.[1] currentAttr = td.innerText.match(/属性[::]\s*([^\s]+)/)?.[1] } if (getActualColumnIndex(td) === 6 && location.pathname.endsWith('order-manage-urgency')) { cuurrentQuantity = td.innerText } if (getActualColumnIndex(td) === 7 && location.pathname.endsWith('order-manage')) { cuurrentQuantity = td.innerText } } if (!currentSKU) continue result.push({ orderSn: currentOrder, direction: currentDirection, productCode: currentProductCode, attribute: currentAttr, skuCode: currentSKU, skcCode: currentSKC, quantity: cuurrentQuantity }); } return result; } // ---------- 生成 Excel 文本(按方向首次出现顺序分组) ---------- function generateExcel(data) { if (!data || !Array.isArray(data) || data.length === 0) return ''; const groups = {}; data.forEach(item => { const dir = item.direction || '未知方向'; if (!groups[dir]) groups[dir] = []; groups[dir].push(item); }); const dirOrder = []; const seen = new Set(); data.forEach(item => { const dir = item.direction || '未知方向'; if (!seen.has(dir)) { seen.add(dir); dirOrder.push(dir); } }); const sortedDirs = Object.keys(groups).sort((a, b) => { const idxA = dirOrder.indexOf(a); const idxB = dirOrder.indexOf(b); if (idxA === -1) return 1; if (idxB === -1) return -1; return idxA - idxB; }); const rows = []; rows.push(['单号', '方向', '货号', 'SKC', '尺码', 'sku', '数量'].join('\t')); sortedDirs.forEach(dir => { rows.push([]) groups[dir].forEach(item => { rows.push([ item.orderSn || '', item.direction || '', item.productCode || '', item.skcCode || '', item.attribute || '', item.skuCode || '', item.quantity || '' ].join('\t')); }); }); return rows.join('\n'); } // ---------- 复制 ---------- function copyToClipboard(text) { if (typeof GM_setClipboard !== 'undefined') { GM_setClipboard(text, 'text'); return true; } if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).catch(() => fallbackCopy(text)); return true; } fallbackCopy(text); return false; } function fallbackCopy(text) { const textarea = document.createElement('textarea'); textarea.value = text; document.body.appendChild(textarea); textarea.select(); try { document.execCommand('copy'); console.log('✅ 已复制到剪贴板(备用方法)'); } catch (e) { console.error('❌ 复制失败,请手动复制。'); } document.body.removeChild(textarea); } // 计算td所在列 function getActualColumnIndex(td, debug = false) { const table = td.closest('table'); if (!table) return -1; const rows = table.rows; let targetRowIndex = -1, targetCellIndex = -1; // 定位目标 td 所在的行和在该行 cells 中的索引 for (let r = 0; r < rows.length; r++) { const cells = rows[r].cells; for (let c = 0; c < cells.length; c++) { if (cells[c] === td) { targetRowIndex = r; targetCellIndex = c; break; } } if (targetRowIndex !== -1) break; } if (targetRowIndex === -1) return -1; // occupied[col] 表示该列当前被占用的剩余行数(包括当前行) const occupied = []; for (let r = 0; r <= targetRowIndex; r++) { // 每行开始时,所有占用计数减 1(消耗掉当前行) for (let col = 0; col < occupied.length; col++) { if (occupied[col] > 0) occupied[col]--; } if (debug) console.log(`行 ${r} 开始 occupied:`, occupied.slice()); const cells = rows[r].cells; let col = 0; // 当前行实际列指针 for (let c = 0; c < cells.length; c++) { // 跳过被占用的列 while (col < occupied.length && occupied[col] > 0) { if (debug) console.log(` 跳过列 ${col},剩余占用 ${occupied[col]}`); col++; } const cell = cells[c]; if (debug) console.log(` 处理单元格 ${c},当前列 ${col},内容:${cell.textContent.trim().slice(0, 20)}`); // 如果当前单元格就是目标,返回起始列索引 if (r === targetRowIndex && cell === td) { if (debug) console.log(`✅ 目标列索引 = ${col}`); return col; } const colspan = cell.colSpan || 1; const rowspan = cell.rowSpan || 1; // 如果单元格有 rowspan,标记占用 if (rowspan > 1) { for (let k = 0; k < colspan; k++) { const idx = col + k; while (occupied.length <= idx) occupied.push(0); // 占用行数 = rowspan(包括当前行) if (rowspan > occupied[idx]) { occupied[idx] = rowspan; if (debug) console.log(` 标记列 ${idx} 占用 ${rowspan} 行`); } } } col += colspan; } if (debug) console.log(`行 ${r} 结束 occupied:`, occupied.slice()); } return -1; // 未找到 } // ---------- 主执行 ---------- function run() { const table = document.querySelector('div[data-testid="beast-core-table-middle-body"] table'); if (!table) { alert('未找到表格,请确认页面已加载。'); return; } const data = extractTableData(table); if (data.length === 0) { alert('未提取到有效数据,请检查表格结构。'); return; } const text = generateExcel(data); copyToClipboard(text); console.log('✅ 已复制数据(共 ' + data.length + ' 条 SKU)'); } // ---------- 注册菜单 ---------- GM_registerMenuCommand("批量复制备货单SKU", run); })();