// ==UserScript== // @name Temu 商品图片批量上传工具6.0 // @namespace http://tampermonkey.net/ // @version 5.1 // @description 货号不补0,上传最多6张,中文标题≤250字,英文≤500字,错误红色提示+禁止上传 // @author You // @match https://agentseller.temu.com/* // @icon https://www.temu.com/favicon.ico // @grant none // ==/UserScript== (function() { 'use strict'; // 1. 样式:完全保留你提供的“高度修复对齐版”样式 + 新增错误提示样式 const style = document.createElement('style'); style.textContent = ` .upload-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 9999; display: flex; justify-content: center; align-items: start; padding-top:10px } .upload-container { background: white; padding: 25px; border-radius: 8px; box-shadow: 0 2px 20px rgba(0,0,0,0.3); max-height: 85vh; overflow-y: auto; width: 95%; } .upload-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid #eee; } .upload-title { margin: 0; font-size: 20px; color: #333; } .close-btn { background: none; border: none; font-size: 24px; color: #999; cursor: pointer; padding: 5px 10px; } .batch-spu-container { display: flex; gap: 15px; align-items: center; margin-bottom: 20px; padding: 15px; background-color: #f5fafe; border-radius: 6px; } .batch-spu-input { width: 100%; padding: 8px 10px; border: 1px solid #ddd; border-radius: 4px; } /* 复选框本身放大 */ .color-checkbox-item input[type="checkbox"] { width: 14px; /* 复选框宽度 */ height: 14px; /* 复选框高度 */ transform: scale(1.2); /* 额外放大 */ } /* --- 修复单元格高度一致性的关键 CSS --- */ .data-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; table-layout: fixed; } .data-table th, .data-table td { border: 1px solid #ddd; padding: 12px; vertical-align: top; height: 100%; } .input-field { width: 100%; padding: 8px 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } /* 新增:输入框错误样式 */ .input-field.error { border: 2px solid #ff4444; background-color: #fff5f5; } /* 错误提示文字样式 */ .error-tip { font-size: 12px; color: #ff4444; margin-top: 4px; display: flex; align-items: center; gap: 4px; } .error-tip::before { content: "❗"; font-size: 14px; } .color-select-cell { font-size:19px; align-content: flex-start; min-height: 40px; } .color-checkbox-item { display: flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 4px; background-color: #f5f5f5; font-size: 16px; white-space: nowrap; } .color-image-group { border: 1px solid #eee; border-radius: 6px; padding: 10px; margin-bottom: 10px; } .color-group-header { display: flex; justify-content:start; align-items: center; margin-bottom: 8px; } .color-label { font-size: 13px; font-weight: bold; color: #333; } .image-upload-btn { padding: 10px 10px; background-color: #4299e1; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; margin-left:20px} .image-preview-container { display: flex; flex-wrap: wrap; gap: 8px; min-height: 55px; padding: 8px; background: #fafafa; border-radius: 4px; } .image-preview-remove { position: relative; display: inline-block; transition: all 0.2s; border: 2px solid transparent; border-radius: 4px; cursor: grab; } .image-preview { width: 80px; height: 80px; object-fit: cover; border-radius: 4px; border: 1px solid #ddd; display: block; background: white; } /* 拖放提速核心:取消延迟 + 缩小动画幅度 */ .image-preview-remove { transition: transform 0.1s ease, opacity 0.1s ease; /* 超级快 */ } .image-preview-remove.dragging { opacity: 0.5; transform: scale(0.95); /* 只缩一点点,不卡 */ border: 2px dashed #4299e1; } .image-preview-remove.drag-over { border: 2px solid #48bb78; transform: scale(1); /* 不放大,零延迟 */ } .image-preview-remove::after { content: '×'; position: absolute; top: -6px; right: -6px; background: #e53935; color: white; width: 16px; height: 16px; border-radius: 50%; font-size: 10px; text-align: center; line-height: 16px; cursor: pointer; } .button-group { display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px; } .action-btn { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; } .add-row-btn { background-color: #48bb78; color: white; } .submit-btn { background-color: #2563eb; color: white; } .trigger-btn { position: fixed; top: 20px; left: 20px; z-index: 9998; padding: 12px 20px; background-color: #e53935; color: white; border: none; border-radius: 4px; font-weight: 600; cursor: pointer; } `; document.head.appendChild(style); let isUIOpen = false; const colorList = ['黑色','白色', '棕色', '灰色', '蓝色', '绿色' , '紫色','粉色']; let draggedElement = null; // --- 字数限制配置 --- const WORD_LIMIT = { CN: 250, // 中文标题最大字数 EN: 500 // 英文标题最大字数 }; // --- 逻辑1:获取签名 (来自旧版) --- async function getMD8Signature() { const urlSig = 'https://agentseller.temu.com/general_auth/get_signature?sdk_version=js-0.0.40&tag_name=product-material-tag&scene_id=agent-seller'; const antiContent = document.querySelector('meta[name="anti-content"]')?.content || ''; const resSig = await fetch(urlSig, { method: 'POST', headers: { 'Content-Type': 'application/json', 'anti-content': antiContent }, body: JSON.stringify({ bucket_tag: 'product-material-tag' }) }); const sigData = await resSig.json(); return sigData.signature; } // --- 逻辑2:上传图片 (来自旧版) --- async function uploadSingleImage(file) { const signature = await getMD8Signature(); const formData = new FormData(); formData.append('url_width_height', 'true'); formData.append('upload_sign', signature); formData.append('image', file); const resUp = await fetch('https://agentseller.temu.com/api/galerie/v3/store_image', { method: 'POST', body: formData, headers: { 'anti-content': document.querySelector('meta[name="anti-content"]')?.content || '' } }); const data = await resUp.json(); return data.image_url || data.url || data.data?.image_url; } // --- 逻辑3:提交到本地服务器生成Excel (来自旧版) --- function generateAndDownloadExcelByForm(newsList, apiUrl = 'http://127.0.0.1:8000/generate-excel') { if (!Array.isArray(newsList) || newsList.length === 0) return; const form = document.createElement('form'); form.method = 'POST'; form.action = apiUrl; form.style.display = 'none'; const input = document.createElement('input'); input.type = 'hidden'; input.name = 'newsData'; input.value = JSON.stringify(newsList); form.appendChild(input); document.body.appendChild(form); form.submit(); document.body.removeChild(form); } // ==================== 新增:校验输入框字数 ==================== function validateInput(input, type) { const value = input.value.trim(); const max = type === 'cn' ? WORD_LIMIT.CN : WORD_LIMIT.EN; const parent = input.parentElement; let errorTip = parent.querySelector('.error-tip'); // 移除旧提示 if (errorTip) errorTip.remove(); if (value.length > max) { // 超过字数:添加红色边框 + 错误提示 input.classList.add('error'); const tip = document.createElement('div'); tip.className = 'error-tip'; tip.textContent = `已超过 ${max} 字限制(当前${value.length}字)`; parent.appendChild(tip); return false; } else { // 正常:清除错误样式 input.classList.remove('error'); return true; } } // ==================== 新增:批量校验所有行 ==================== function validateAllRows() { let isValid = true; const rows = document.querySelectorAll('.data-row'); rows.forEach(row => { const cnInput = row.querySelector('.product-name'); const enInput = row.querySelector('.english-name'); const cnOk = validateInput(cnInput, 'cn'); const enOk = validateInput(enInput, 'en'); if (!cnOk || !enOk) isValid = false; }); return isValid; } // --- UI 相关逻辑 --- function createTriggerBtn() { const btn = document.createElement('button'); btn.className = 'trigger-btn'; btn.textContent = '📤 商品批量上传工具'; btn.onclick = toggleUploadUI; document.body.appendChild(btn); } function toggleUploadUI() { if (isUIOpen) { const mask = document.querySelector('.upload-mask'); if (mask) document.body.removeChild(mask); isUIOpen = false; return; } createUploadUI(); isUIOpen = true; bindPasteEvents(); } function createUploadUI() { const mask = document.createElement('div'); mask.className = 'upload-mask'; const container = document.createElement('div'); container.className = 'upload-container'; container.innerHTML = `

Temu 商品批量上传 (Excel 增强版)

货号前缀:
起始:
批量增行:
SPU货号 商品名称
≤250字
英文名称
≤500字
颜色选择 轮播图管理 (6张/色) 操作
`; mask.appendChild(container); document.body.appendChild(mask); document.getElementById('closeBtn').onclick = toggleUploadUI; document.getElementById('applySpuBtn').onclick = applyBatchSpu; document.getElementById('singleAddRowBtn').onclick = () => addNewRow(document.getElementById('tableBody')); document.getElementById('startUploadBtn').onclick = handleBatchUpload; // 批量增行功能 document.getElementById('batchAddRowBtn').onclick = () => { const count = parseInt(document.getElementById('batchAddCount').value) || 0; const tbody = document.getElementById('tableBody'); for(let i=0; i row.colorImageData[color] = { files: [] }); row.innerHTML = ` `; const colorCell = row.querySelector('.color-select-cell'); const imageCell = row.querySelector('.image-upload-cell'); // ==================== 绑定输入监听:实时校验字数 ==================== const cnInput = row.querySelector('.product-name'); const enInput = row.querySelector('.english-name'); cnInput.addEventListener('input', () => validateInput(cnInput, 'cn')); enInput.addEventListener('input', () => validateInput(enInput, 'en')); colorList.forEach(color => { const wrap = document.createElement('div'); wrap.className = 'color-checkbox-item'; // 默认勾选黑色时,也要把黑色加入顺序数组 const isDefault = (color === '黑色'); if(isDefault) row.checkedOrder.push(color); wrap.innerHTML = ` `; const checkbox = wrap.querySelector('input'); checkbox.onchange = (e) => { const colorVal = e.target.dataset.color; if (e.target.checked) { // 勾选时,追加到顺序末尾 if (!row.checkedOrder.includes(colorVal)) row.checkedOrder.push(colorVal); } else { // 取消勾选时,从顺序中移除 row.checkedOrder = row.checkedOrder.filter(c => c !== colorVal); } updateColorImageUploadArea(row, imageCell); }; colorCell.appendChild(wrap); }); tbody.appendChild(row); updateColorImageUploadArea(row, imageCell); } function updateColorImageUploadArea(row, imageCell) { imageCell.innerHTML = ''; // 关键点:这里改用 row.checkedOrder 进行遍历 const order = row.checkedOrder || []; order.forEach(color => { const group = document.createElement('div'); group.className = 'color-image-group'; group.innerHTML = `
${color} 轮播图 (0/6)
`; const btn = group.querySelector('.image-upload-btn'); const previewContainer = group.querySelector('.image-preview-container'); const hint = group.querySelector('.hint-count'); const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.multiple = true; fileInput.accept = 'image/*'; fileInput.style.display = 'none'; btn.onclick = () => fileInput.click(); fileInput.onchange = (e) => { const files = Array.from(e.target.files); const limit = 6 - row.colorImageData[color].files.length; files.slice(0, limit).forEach(file => row.colorImageData[color].files.push(file)); renderPreviews(row, color, previewContainer, hint); }; renderPreviews(row, color, previewContainer, hint); imageCell.appendChild(group); }); } function renderPreviews(row, color, container, hint) { container.innerHTML = ''; const files = row.colorImageData[color].files; hint.textContent = `${files.length}/6`; hint.style.color = files.length >= 6 ? 'green' : '#666'; files.forEach((file, idx) => { const wrap = document.createElement('div'); wrap.className = 'image-preview-remove'; wrap.draggable = true; const img = document.createElement('img'); img.className = 'image-preview'; const reader = new FileReader(); reader.onload = (e) => img.src = e.target.result; reader.readAsDataURL(file); wrap.onclick = (e) => { if (e.offsetX > 40 && e.offsetY < 15) { row.colorImageData[color].files.splice(idx, 1); renderPreviews(row, color, container, hint); } }; wrap.ondragstart = () => { draggedElement = { row, color, idx }; wrap.classList.add('dragging'); }; wrap.ondragenter = () => { if(draggedElement.color === color) wrap.classList.add('drag-over'); }; wrap.ondragover = (e) => e.preventDefault(); wrap.ondragleave = () => wrap.classList.remove('drag-over'); wrap.ondragend = () => wrap.classList.remove('dragging'); wrap.ondrop = () => { if (draggedElement.color === color) { const temp = row.colorImageData[color].files.splice(draggedElement.idx, 1)[0]; row.colorImageData[color].files.splice(idx, 0, temp); renderPreviews(row, color, container, hint); } }; wrap.appendChild(img); container.appendChild(wrap); }); } async function handleBatchUpload() { // ==================== 核心:先校验字数,不通过则阻止上传 ==================== const pass = validateAllRows(); if (!pass) { alert('存在标题字数超限,请修正红色错误后再上传!'); return; } const rows = document.querySelectorAll('.data-row'); const validData = []; rows.forEach((row) => { const spu = row.querySelector('.spu-code').value.trim(); const name = row.querySelector('.product-name').value.trim(); const colors = Array.from(row.querySelectorAll('.color-checkbox:checked')).map(c => c.dataset.color); if(spu && name && colors.length > 0) validData.push({ row, spu, name, en: row.querySelector('.english-name').value, colors }); }); if(validData.length === 0) return alert('请完善信息'); showLoading('上传图片并生成Excel中...'); const finalResults = []; for (const item of validData) { const colorImageList = []; for (const color of item.colors) { const files = item.row.colorImageData[color].files; const imageUrls = []; for(let k=0; k < files.length; k++) { try { const url = await uploadSingleImage(files[k]); imageUrls.push(url); } catch(e) { imageUrls.push('上传失败'); } } // 补齐6张图数据格式 while(imageUrls.length < 6) imageUrls.push(''); colorImageList.push({ '颜色': color, '商品轮播图': { '商品轮播图1': imageUrls[0], '商品轮播图2': imageUrls[1], '商品轮播图3': imageUrls[2], '商品轮播图4': imageUrls[3], '商品轮播图5': imageUrls[4], '商品轮播图6': imageUrls[5] } }); } finalResults.push({ 'SPU货号': item.spu, '商品名称': item.name, '英文名称': item.en, '颜色轮播图': colorImageList }); } hideLoading(); // 提交到你的 FastAPI/Flask 后端生成 Excel generateAndDownloadExcelByForm(finalResults); console.log('处理完成,已尝试唤起后端表格下载'); } function applyBatchSpu() { const prefix = document.getElementById('batchSpuPrefix').value; const start = parseInt(document.getElementById('batchSpuStart').value) || 1; document.querySelectorAll('.data-row').forEach((row, i) => { // 修改点:直接数字累加,不补0 row.querySelector('.spu-code').value = `${prefix}-${start + i}`; }); } function bindPasteEvents() { document.addEventListener('paste', function(e) { const active = document.activeElement; // 确保是在我们插件的输入框内粘贴 if (!active || !active.classList.contains('input-field')) return; // 阻止默认粘贴行为,由我们手动控制多行分配 e.preventDefault(); // 获取剪贴板文本并拆分成行 const text = (e.clipboardData || window.clipboardData).getData('text'); // 过滤空行并去除两端空格 const lines = text.split(/\r\n|\n|\r/).map(l => l.trim()).filter(l => l); const rows = document.querySelectorAll('.data-row'); const startRow = active.closest('.data-row'); let idx = Array.from(rows).indexOf(startRow); const isName = active.classList.contains('product-name'); // 遍历行进行填充 for (let i = 0; i < lines.length; i++) { const row = rows[idx + i]; if (!row) break; // 如果下方没有更多行了,停止填充 // 根据当前是在“商品名称”还是“英文名称”列,决定填充目标 const input = isName ? row.querySelector('.product-name') : row.querySelector('.english-name'); if (input) { input.value = lines[i]; // 填充内容 // 填充后立即触发字数校验提示(变红或变绿) validateInput(input, isName ? 'cn' : 'en'); } } }); } function showLoading(txt) { const div = document.createElement('div'); div.className = 'loading-overlay'; div.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(255,255,255,0.8);z-index:10000;display:flex;justify-content:center;align-items:center;flex-direction:column;"; div.innerHTML = `
${txt}
`; document.body.appendChild(div); } function hideLoading() { const el = document.querySelector('.loading-overlay'); if(el) el.remove(); } createTriggerBtn(); })();