// ==UserScript== // @name Temu商品批量上架工具(菲律宾) // @namespace http://tampermonkey.net/ // @version 2.0 // @description 批量生成Temu商品草稿、自动上传图片、自动保存草稿 // @author binning // @match https://agentseller.temu.com/* // @require https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js // @require https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; // ============================================== // 1. 自动获取当前页面的 cookie、anti-content、mallId // ============================================== // 全局图片压缩函数(1340x1785) function resizeImageFile(file, targetWidth, targetHeight) { return new Promise((resolve) => { const img = new Image(); img.src = URL.createObjectURL(file); img.onload = function () { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); const scale = Math.max(targetWidth / img.width, targetHeight / img.height); const w = img.width * scale; const h = img.height * scale; const x = (targetWidth - w) / 2; const y = (targetHeight - h) / 2; canvas.width = targetWidth; canvas.height = targetHeight; ctx.drawImage(img, x, y, w, h); canvas.toBlob((blob) => { const newFile = new File([blob], file.name, { type: "image/jpeg", quality: 0.85 }); resolve(newFile); }, "image/jpeg", 0.85); }; }); } function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); } function getAntiContent() { return `0aqAfxnYgiSY89d2J2goDWND5PwGbLPP2y1UqwKU51ttJhQyIMeD_jr_nAU8zin-1anOjLM6cEWa0ipLKxpcwj-P9VcXGxPbHBtPHC-sTmsTAUcScZVP45RRzybN45hNP68AQ4RDxundpczuyR9p7wJCkkAyj89hvjFLq_y2FACTFx6py9FFcsfquTHp7jgHNoyEVvz-nhgrJ4Xz_7RV67-tWg8FfU_XvipE_IDW-B1jJ3CpORC1vio6VzP8j9BGwgJoafkf7L9evTUhSz61gwN9dvdPpL4FLVt7Ps7kB1lKcr9BZSTsR9yzZGlowcY_Kxs0vTUqU9eFECsAPmjZvWVQkS_GYXspujU1dHUkEsbUpVJZLElDgdJHYVpLMKoIaSAuVormbq5SCUi3zwYoi0pDUm8HRLEe7PfxiPxMt3V3rZuOIwHqBFlwRnlVRk7egzT2vgwKgQaOB7LJay8bEa0pAi7zahqArLZi51Qv3wU7cziQFFzAPiLvs9dVwud6xJ3vtUM6bCYySe1SanbEKptzdsIgVOBFQlLAuEvaZMnBHRo87jHoGh32KEYet-2qmF_QwcoDdXQLYlujlBNScPzeuDvjub7r0I4B5ZKHjyLGBMvHY803g4ugxpsqzNvBbIBSjMNuxvJc4YNkP74l29rmuPpgCAOKPMv8SXOEoXiSVgkab-2qcLHZWH0T5foqVzaqw605CaswFOdoRbD_pzuYStbhqdHKkp-74DKA4eiXugtpIhRbr6kyImziqzecz_hfjvMlxxWYtOLzIy5lf0oRRJt0R53ab7cb2Iy2jolL1ZWGOGBJ` } function getMallId() { return "634418227088667" } const currentCookie = document.cookie; const currentAnti = getAntiContent(); const currentMallId = getMallId(); // ============================================== // 官方Temu图片上传函数 // ============================================== // ============================================== // 官方Temu图片上传函数 + 自动压缩成 1340x1785 // ============================================== async function temuUploadImage(imageFile) { // 目标尺寸(Temu要求) const TARGET_WIDTH = 1340; const TARGET_HEIGHT = 1785; // 图片压缩 + 尺寸修正函数 function resizeImage(file, maxWidth, maxHeight) { return new Promise((resolve) => { const img = new Image(); img.src = URL.createObjectURL(file); img.onload = function () { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); // 计算居中裁剪比例 const scale = Math.max(maxWidth / img.width, maxHeight / img.height); const w = img.width * scale; const h = img.height * scale; const x = (maxWidth - w) / 2; const y = (maxHeight - h) / 2; // 绘制 canvas.width = maxWidth; canvas.height = maxHeight; ctx.drawImage(img, x, y, w, h); // 转成 Blob canvas.toBlob( (blob) => { const newFile = new File([blob], file.name, { type: "image/jpeg", quality: 0.85, }); resolve(newFile); }, "image/jpeg", 0.85 ); }; }); } // 先压缩尺寸 const compressedFile = await resizeImage(imageFile, TARGET_WIDTH, TARGET_HEIGHT); // 1. 获取上传签名 const signRes = await fetch('https://agentseller.temu.com/general_auth/get_signature?sdk_version=js-0.0.40&tag_name=product-material-tag&scene_id=agent-seller', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ bucket_tag: 'product-material-tag' }) }); const signData = await signRes.json(); const signature = signData.signature; // 2. 上传图片(使用压缩后的图片) const formData = new FormData(); formData.append('url_width_height', 'true'); formData.append('upload_sign', signature); formData.append('image', compressedFile); // 这里用压缩后的图 const upRes = await fetch('https://agentseller.temu.com/api/galerie/v3/store_image', { method: 'POST', credentials: 'include', body: formData }); const res = await upRes.json(); return res.image_url || res.url || res.data?.image_url; } // ============================================== // 2. 创建浮动按钮(打开/关闭工具) // ============================================== const floatBtn = document.createElement('div'); floatBtn.innerHTML = '打开上架工具'; floatBtn.style.cssText = ` position: fixed; bottom: 30px; right: 30px; z-index: 9999999; padding: 12px 20px; background: #409eff; color: #fff; border-radius: 50px; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.2); font-size: 14px; `; document.body.appendChild(floatBtn); // ============================================== // 3. 动态加载样式 // ============================================== const style = document.createElement('style'); style.textContent = `/* 你的完整 CSS 样式,我已全部保留 */ html,body { width: 100%; height: 100%; margin: 0; padding: 0; } #temu-tool-app { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.5); z-index: 9999999; display: none; } #app { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; } .content { width: 95%; height: 98%; background: #fff; padding: 40px 20px 20px; box-sizing: border-box; display: flex; flex-direction: column; gap: 10px; position: relative; } /* 右上角关闭按钮 */ .content .close-panel-btn { position: absolute; right: 20px; top: 10px; width: 36px; height: 36px; background: #ff4d4f; color: #fff; border: none; border-radius: 50%; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 10; } .content .close-panel-btn:hover { background: #e64547; } /* 进度条样式 */ .upload-progress-container { width: 100%; height: 8px; background: #e5e7eb; border-radius: 4px; margin: 8px 0; overflow: hidden; } .upload-progress-bar { height: 100%; background: #409eff; border-radius: 4px; transition: width 0.3s ease; } .defalut { display: flex; gap: 25px; align-items: center; flex-wrap: wrap; } .setClothing { display: flex; align-items: center; gap: 10px; background: #f7f8fa; padding: 10px 14px; border-radius: 10px; box-shadow: 0 2px 6px #0001; } .selectBox { border: 1px solid #e1e5e9; border-radius: 6px; padding: 6px 10px; outline: none; background: #fff; cursor: pointer; } .selectBox:focus { border-color: #409eff; box-shadow: 0 0 0 2px #409eff1a; } .tools { display: flex; gap: 30px; align-items: center; flex-wrap: wrap; } .tool { display: flex; align-items: center; gap: 12px; background: #f7f8fa; padding: 10px 16px; border-radius: 10px; box-shadow: 0 2px 6px #0001; } .tool input[type="text"],.tool input[type="number"] { border: 1px solid #e1e5e9; border-radius: 6px; padding: 6px 10px; outline: none; width: 100px; } .tool input:focus { border-color: #409eff; box-shadow: 0 0 0 2px #409eff1a; } .tool button { padding: 6px 14px; background: #409eff; color: #fff; border: none; border-radius: 6px; cursor: pointer; } .tool button:hover { background: #338eef; } table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 2px 10px #0001; table-layout: fixed; } td { height: 90px; } thead td { height: 30px; } thead td:nth-child(1) { width: 120px; } thead td:nth-child(2) { width: 220px; } thead td:nth-child(3) { width: 240px; } thead td:nth-child(4) { width: 240px; } thead td:nth-child(5) { width: 280px; } thead td:nth-child(6) { width: 420px; } thead td:nth-child(7) { width: 100px; } thead { color: white; background: #5a709d; height: 40px; } thead td { padding: 14px 10px; font-weight: 600; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } tbody tr { border-bottom: 1px solid #f2f2f2; } tbody tr:hover { background: #fafbfc; } table td { padding: 12px 8px; vertical-align: top; text-align: center; overflow: hidden; } table input[type="text"] { width: 100% !important; box-sizing: border-box; padding: 6px 10px; border: 1px solid #b8bcc0; border-radius: 6px; outline: none; height: 40px; } table input:focus { border-color: #409eff; box-shadow: 0 0 0 2px #409eff1a; } .color-checkbox-group { display: grid; grid-template-columns: repeat(4,1fr); gap: 6px 4px; text-align: left; width: 100%; box-sizing: border-box; } .color-checkbox-group label { display: flex; align-items: center; gap: 4px; padding: 4px 6px; border-radius: 4px; cursor: pointer; white-space: nowrap; overflow: hidden; } .color-checkbox-group label:has(input:checked[value="黑色"]) { background: #383838; color: #fff; } .color-checkbox-group label:has(input:checked[value="白色"]) { background: #fafafa; } .color-checkbox-group label:has(input:checked[value="灰色"]) { background: #c2c2c2; } .color-checkbox-group label:has(input:checked[value="蓝色"]) { background: #4176c5; color: #fff; } .color-checkbox-group label:has(input:checked[value="粉色"]) { background: #e7397f; color: #fff; } .color-checkbox-group label:has(input:checked[value="紫色"]) { background: #ce3cdb; color: #fff; } .color-checkbox-group label:has(input:checked[value="棕色"]) { background: #885431; color: #fff; } .color-checkbox-group label:has(input:checked[value="绿色"]) { background: #268b9d; color: #fff; } .RowImageList { display: flex; flex-direction: column; gap: 10px; max-height: 300px; overflow-y: auto; width: 100%; box-sizing: border-box; } .ImageByColor { background: #fafbfc; border-radius: 8px; padding: 10px; border: 1px solid #c6c6c0; width: 100%; box-sizing: border-box; } .ImageByColor>div:first-child { font-size: 13px; font-weight: 500; margin-bottom: 8px; display: flex; justify-content: space-between; } .ImageByColor>div:first-child div:last-child { padding: 3px 8px; border: 1px solid #409eff; border-radius: 4px; color: #409eff; cursor: pointer; } .ImageByColor>div:first-child div:last-child:hover { background: #409eff; color: #fff; } .colorImages { display: flex; justify-content: flex-start; gap: 6px; flex-wrap: wrap; width: 100%; } .imgs { width: 62px; height: 62px; border: 1px solid #e1e5e9; border-radius: 6px; background: #fff; display: flex; align-items: center; justify-content: center; cursor: move; position: relative; flex-shrink: 0; } .imgs img { width: 100%; height: 100%; object-fit: cover; } .del-img { position: absolute; top: -4px; right: -4px; width: 18px; height: 18px; background: #ff4d4f; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; cursor: pointer; line-height: 1; } .del-img:hover { background: #ff3333; } .delete-btn { cursor: pointer; padding: 5px 10px; border-radius: 5px; background-color: #ff4d4f; color: white; } .delete-btn:hover { background-color: rgb(130,7,7); } .batch-color-tool { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } .batch-color-label { padding: 2px 6px; cursor: pointer; display: flex; align-items: center; gap: 4px; } .batch-color-tool { display: flex; align-items: center; gap: 10px; flex-wrap: nowrap; overflow-x: auto; padding: 8px 12px; } .batch-color-label { display: flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 4px; cursor: pointer; white-space: nowrap; transition: all 0.2s; } .batch-color-label input { cursor: pointer; } .batch-color-label.checked { background: #383838; color: #fff; } .batch-color-label.checked:has([value="白色"]) { background: #fafafa; color: #000; } .batch-color-label.checked:has([value="灰色"]) { background: #c2c2c2; color: #000; } .batch-color-label.checked:has([value="蓝色"]) { background: #4176c5; color: #fff; } .batch-color-label.checked:has([value="粉色"]) { background: #e7397f; color: #fff; } .batch-color-label.checked:has([value="紫色"]) { background: #ce3cdb; color: #fff; } .batch-color-label.checked:has([value="棕色"]) { background: #885431; color: #fff; } .batch-color-label.checked:has([value="绿色"]) { background: #268b9d; color: #fff; } .style-manager-btn { margin-left: 10px; padding: 6px 12px; background: #677ab7; color: #fff; border: none; border-radius: 6px; cursor: pointer; } .style-manager-btn:hover { background: #5a6ca8; } .style-manager-modal { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 9999; } .modal-content { background: #fff; width: 90%; height: 90vh; border-radius: 12px; display: flex; flex-direction: column; overflow: hidden; } .modal-header { padding: 16px 20px; background: #5a709d; color: #fff; display: flex; justify-content: space-between; align-items: center; } .modal-header h2 { margin: 0; font-size: 18px; } .close-btn { background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; } .modal-body { padding: 20px; overflow-y: auto; flex: 1; } .modal-footer { padding: 16px 20px; background: #f7f8fa; display: flex; justify-content: flex-end; gap: 12px; } .form-section { margin-bottom: 24px; padding-bottom: 16px; border-bottom: 1px solid #eee; } .form-section h3 { margin: 0 0 12px; font-size: 16px; color: #333; } .form-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; } .form-row label { width: 90px; font-weight: 500; } .form-row input { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; width: 120px; } .add-btn { padding: 6px 12px; background: #409eff; color: #fff; border: none; border-radius: 4px; cursor: pointer; margin-bottom: 8px; } .del-btn { padding: 4px 8px; background: #ff4d4f; color: #fff; border: none; border-radius: 4px; cursor: pointer; } .inner-table { width: 100%; border-collapse: collapse; margin-top: 8px; } .inner-table th,.inner-table td { border: 1px solid #ddd; padding: 8px; text-align: center; } .inner-table th { background: #f7f8fa; } .inner-table input { width: 100%; box-sizing: border-box; padding: 4px; border: 1px solid #ddd; border-radius: 4px; } .key-value-list { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; } .kv-item { display: flex; align-items: center; gap: 6px; padding: 6px; border: 1px solid #eee; border-radius: 4px; } .kv-item input { width: 120px; padding: 4px; border: 1px solid #ddd; border-radius: 4px; } .del-col { color: #ff4d4f; cursor: pointer; margin-left: 4px; } .save-btn { padding: 8px 16px; background: #409eff; color: #fff; border: none; border-radius: 6px; cursor: pointer; } .cancel-btn { padding: 8px 16px; background: #999; color: #fff; border: none; border-radius: 6px; cursor: pointer; } .size-header { display: flex; gap: 10px; margin-bottom: 8px; } .size-header-input { width: 100%; box-sizing: border-box; padding: 4px; border: 1px solid #ddd; border-radius: 4px; text-align: center; font-weight: normal; } .size-header-input:focus { border-color: #409eff; outline: none; } .custom-alert { position: fixed; top: 30px; left: 50%; transform: translateX(-50%); z-index: 999999; padding: 12px 28px; background: #fff; color: #f56c6c; border-radius: 8px; font-size: 15px; font-weight: 500; box-shadow: 0 4px 15px rgba(0,0,0,0.15); border-left: 4px solid #f56c6c; animation: alertPopup 0.3s ease forwards; } @keyframes alertPopup { 0% { opacity: 0; top: 0; } 100% { opacity: 1; top: 30px; } } `; document.head.appendChild(style); // ============================================== // 4. 插入 Vue 容器 // ============================================== const appContainer = document.createElement('div'); appContainer.id = 'temu-tool-app'; appContainer.innerHTML = `
款式:
填充货号:前缀:
起始值:
批量添加行:
商品数量:{{ goods.length }}
批量勾选颜色:
货号 标题 英文标题 菲律宾标题 颜色 图片 操作
{{ color }}
导入图片
×
删除
{{ alertText }}
`; document.body.appendChild(appContainer); // ============================================== // 5. 启动 Vue 实例 // ============================================== const vm = new Vue({ el: '#app', data() { return { showAlert: false, alertText: "", alertTimer: null, computerId: "", batchSelectedColors: [], selectClthingData: "", currentColors: [], currentStyleColorImg: {}, prefix: "", startNum: 1, addRowCount: 1, showStyleManager: false, editStyleKey: "", styleForm: { code: "", colors: [{ name: "", img8: "", img9: "", img10: "" }] }, defaultClothings: { XS001model: {} }, Clothings: {}, goods: [{ id: "", title: "", titleEn: "", titlePh: "", selectedColors: [], Color: {} }], dragData: null, // 进度条 uploadProgress: 0, showProgress: false } }, created() { let cid = localStorage.getItem('my_computer_id'); if (!cid) { cid = 'PC-' + crypto.randomUUID().split('-')[0]; localStorage.setItem('my_computer_id', cid); } this.computerId = cid; this.loadLocalData(); }, methods: { loadLocalData() { const localClothings = localStorage.getItem('app_clothings'); if (localClothings) { const parsed = JSON.parse(localClothings); this.Clothings = (parsed && Object.keys(parsed).length > 0) ? parsed : JSON.parse(JSON.stringify(this.defaultClothings)); } else { this.Clothings = JSON.parse(JSON.stringify(this.defaultClothings)); } }, saveClothingsToLocal() { localStorage.setItem('app_clothings', JSON.stringify(this.Clothings)); }, openStyleManager() { this.showStyleManager = true; this.editStyleKey = ""; this.resetStyleForm(); this.loadLocalData(); // 关键:每次打开都重新读取本地存储 }, closeStyleManager() { this.showStyleManager = false; }, resetStyleForm() { this.styleForm = { code: "", colors: [] }; // 空数组,不写死一条 }, addColorRow() { this.styleForm.colors.push({ name: "", img8: "", img9: "", img10: "" }); }, delColorRow(idx) { this.styleForm.colors.splice(idx, 1); }, saveStyle() { const { code, colors } = this.styleForm; if (!code) { alert("请输入款式编码!"); return; } const colorObj = {}; colors.forEach(c => { if (c.name) { colorObj[c.name] = { img8: c.img8, img9: c.img9, img10: c.img10 }; } }); this.$set(this.Clothings, code, { color: colorObj }); this.saveClothingsToLocal(); this.closeStyleManager(); alert("保存款式成功!"); }, deleteStyle() { const key = this.editStyleKey; if (!key) { alert("请选择要删除的款式!"); return; } if (!confirm(`确定要删除款式【${key}】吗?`)) return; this.$delete(this.Clothings, key); this.saveClothingsToLocal(); this.resetStyleForm(); }, clearAllRows() { this.goods = [{ id: "", title: "", titleEn: "", titlePh: "", selectedColors: [], Color: {} }]; }, getFullColorImgList(userImgList, color) { const detailUrls = []; const colorCfg = this.currentStyleColorImg[color] || {}; if (colorCfg.img8) detailUrls.push(colorCfg.img8); if (colorCfg.img9) detailUrls.push(colorCfg.img9); if (colorCfg.img10) detailUrls.push(colorCfg.img10); return [...userImgList, ...detailUrls]; }, hidePanel() { appContainer.style.display = 'none'; floatBtn.textContent = '打开上架工具'; show = false; }, // 统一上传所有图片并输出数据(核心修改) async upload() { this.showCustomAlert("⏳ 正在上传所有图片,请稍候..."); console.log("开始统一上传所有图片"); // 统计总图片数 let totalFiles = 0; let uploaded = 0; this.uploadProgress = 0; this.showProgress = true; // 统计所有需要上传的图片 for (let item of this.goods) { for (let color in item.Color) { const fileList = item.Color[color] || []; fileList.forEach(f => { if (f.file) totalFiles++; }); } } if (totalFiles === 0) { this.showProgress = false; this.showCustomAlert("✅ 没有需要上传的图片"); } // 遍历所有商品,上传所有图片 for (let item of this.goods) { for (let color in item.Color) { const fileList = item.Color[color] || []; const urlList = []; for (let f of fileList) { if (f.file) { try { const url = await temuUploadImage(f.file); urlList.push(url); uploaded++; this.uploadProgress = Math.round((uploaded / totalFiles) * 100); console.log(`✅ ${color} 图片上传完成:${url}`); } catch (err) { console.error("❌ 图片上传失败", err); } } else if (f.url) { urlList.push(f.url); } } // 替换为真实URL item.Color[color] = urlList; } } this.showCustomAlert("✅ 所有图片上传完成,正在输出数据"); setTimeout(() => { this.showProgress = false; }, 800); // 生成最终数据 const exportData = this.goods.map(item => { const newColorData = {}; for (const color in item.Color) { newColorData[color] = this.getFullColorImgList(item.Color[color] || [], color); } return { id: item.id, title: item.title, titleEn: item.titleEn, titlePh: item.titlePh, selectedColors: item.selectedColors, Color: newColorData }; }); console.log("【完整商品数据】", exportData); const templateData = []; for (const rows of exportData) { const rowData = await this.buildProductPayload(rows); const spuid = await this.createTemuProductDraft(); rowData.productDraftId = spuid console.log(spuid) if (spuid) { console.log("创建草稿成功:", spuid); await this.saveTemuProductDraft(rowData); } templateData.push(rowData); } console.log("【最终提交模板】", templateData); this.showCustomAlert("🎉 全部处理完成!"); }, onColorChange(item, color) { if (!item.Color[color]) this.$set(item.Color, color, []); }, selectClothing(code) { const data = this.Clothings[code]; if (!data) return; this.currentStyleColorImg = data.color || {}; this.currentColors = Object.keys(data.color || {}); this.goods.forEach(item => { item.selectedColors = []; item.Color = {}; }); }, // 选择图片,只存文件+预览,不立即上传 // 选择图片,自动压缩 + 预览 uploadImgs(item, color) { const input = document.createElement("input"); input.type = "file"; input.multiple = true; input.accept = "image/*"; input.onchange = async () => { const files = Array.from(input.files); if (!item.Color[color]) this.$set(item.Color, color, []); for (let file of files) { // 自动压缩成 1340×1785 const compressedFile = await resizeImageFile(file, 1340, 1785); const preview = URL.createObjectURL(compressedFile); item.Color[color].push({ file: compressedFile, preview: preview }); } this.$forceUpdate(); }; input.click(); }, delSingleImg(item, color, imgIdx) { item.Color[color].splice(imgIdx, 1); this.$forceUpdate(); }, dragStart(e, idx, color, imgIdx) { this.dragData = { idx, color, imgIdx }; e.dataTransfer.effectAllowed = "move"; }, drop(e, idx, color, targetIdx) { if (!this.dragData) return; const { idx: sIdx, color: sColor, imgIdx: sImgIdx } = this.dragData; if (idx !== sIdx || color !== sColor) return; const arr = this.goods[idx].Color[color]; const movedItem = arr[sImgIdx]; arr.splice(sImgIdx, 1); arr.splice(targetIdx, 0, movedItem); this.$forceUpdate(); this.dragData = null; }, fillIds() { let num = parseInt(this.startNum); this.goods.forEach(item => { item.id = this.prefix + "-" + num; num++; }); }, addRows() { const count = parseInt(this.addRowCount) || 1; for (let i = 0; i < count; i++) { this.goods.push({ id: "", title: "", titleEn: "", titlePh: "", selectedColors: [], Color: {} }); } }, delRow(idx) { this.goods.splice(idx, 1); }, batchApplyColors() { if (this.currentColors.length === 0) { alert("请先选择款式!"); return; } this.goods.forEach(item => { item.selectedColors = [...this.batchSelectedColors]; item.selectedColors.forEach(color => { if (!item.Color[color]) this.$set(item.Color, color, []); }); }); }, batchCheckAllColor() { if (this.currentColors.length === 0) { alert("请先选择款式!"); return; } this.goods.forEach(item => { item.selectedColors = [...this.currentColors]; this.currentColors.forEach(color => { if (!item.Color[color]) this.$set(item.Color, color, []); }); }); }, batchClearColor() { this.goods.forEach(item => { item.selectedColors = []; item.Color = {}; }); }, pasteColumn(e, startIndex, field) { const text = e.clipboardData.getData('text').trim(); if (!text) return; const lines = text.split(/\r\n|\n|\r|\t/).map(s => s.trim()).filter(Boolean); const lastNeedIndex = startIndex + lines.length - 1; const needAddCount = lastNeedIndex - this.goods.length + 1; if (needAddCount > 0) { for (let i = 0; i < needAddCount; i++) { this.goods.push({ id: "", title: "", titleEn: "", titlePh: "", selectedColors: [], Color: {} }); } } lines.forEach((val, i) => { const rowIndex = startIndex + i; if (rowIndex >= this.goods.length) return; this.goods[rowIndex][field] = val; }); }, loadEditStyle() { const key = this.editStyleKey; if (!key) { this.resetStyleForm(); return; } const data = this.Clothings[key]; if (!data) return; this.styleForm.code = key; const colorArr = []; const colorObj = data.color || {}; for (const name in colorObj) { const img = colorObj[name]; colorArr.push({ name, img8: img.img8 || "", img9: img.img9 || "", img10: img.img10 || "" }); } this.styleForm.colors = colorArr; // 正确回填数组 }, showCustomAlert(text) { this.alertText = text; this.showAlert = true; clearTimeout(this.alertTimer); this.alertTimer = setTimeout(() => { this.showAlert = false; }, 2800); }, // ====================== 【核心修复】完全按你给的结构生成SKU ====================== buildProductPayload(row) { const originalPayload = { "copyFromProductId": 8703919697, "cat1Id": 27011, "cat2Id": 30327, "cat3Id": 30328, "cat4Id": 30477, "cat5Id": 30478, productName: row.title, productI18nReqs: [{ language: "en", productName: row.titleEn }], goodsI18nReqs: [{ language: "fil", productName: row.titlePh }], productPropertyReqs: [{ "templatePid": 1140677, "pid": 1, "refPid": 12, "propName": "材质", "vid": 49, "propValue": "棉", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140678, "pid": 2, "refPid": 15, "propName": "成分", "vid": 78, "propValue": "棉", "valueUnit": "%", "valueExtendInfo": "", "numberInputValue": "100.00" }, { "templatePid": 1140683, "pid": 3, "refPid": 19, "propName": "风格", "vid": 145, "propValue": "休闲", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140684, "pid": 4, "refPid": 20, "propName": "护理说明", "vid": 26001, "propValue": "可机洗且不可干洗", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140682, "pid": 5, "refPid": 21, "propName": "领型", "vid": 182, "propValue": "小圆领", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140685, "pid": 6, "refPid": 22, "propName": "面料", "vid": 207, "propValue": "微弹", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140688, "pid": 7, "refPid": 24, "propName": "是否透明", "vid": 210, "propValue": "否", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140680, "pid": 10, "refPid": 26, "propName": "图案", "vid": 230, "propValue": "卡通", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140689, "pid": 11, "refPid": 27, "propName": "袖型", "vid": 289, "propValue": "落肩袖", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140691, "pid": 12, "refPid": 28, "propName": "长度", "vid": 293, "propValue": "常规款", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140690, "pid": 16, "refPid": 29, "propName": "袖长", "vid": 296, "propValue": "短袖", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140699, "pid": 19, "refPid": 74, "propName": "腰带", "vid": 551, "propValue": "否", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140687, "pid": 24, "refPid": 76, "propName": "季节", "vid": 25850, "propValue": "夏天", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140679, "pid": 22, "refPid": 77, "propName": "次要材质", "vid": 653, "propValue": "棉", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140681, "pid": 21, "refPid": 83, "propName": "细节", "vid": 29210, "propValue": "无", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140697, "pid": 39, "refPid": 86, "propName": "门襟类型", "vid": 873, "propValue": "套头衫", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140694, "pid": 37, "refPid": 90, "propName": "场景", "vid": 893, "propValue": "日常和休闲", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140693, "pid": 26, "refPid": 96, "propName": "场合", "vid": 724, "propValue": "休闲外出", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140695, "pid": 20, "refPid": 113, "propName": "廓形", "vid": 1829, "propValue": "常规款", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140692, "pid": 36, "refPid": 114, "propName": "版型", "vid": 35189, "propValue": "常规", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140686, "pid": 67, "refPid": 115, "propName": "适用人群", "vid": 30062, "propValue": "男士", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140703, "pid": 1224, "refPid": 1192, "propName": "织造方式", "vid": 54654, "propValue": "针织(含钩织、毛织面料)", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1196179, "pid": 1437, "refPid": 1919, "propName": "印花类型", "vid": 36893, "propValue": "定位印花", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1140702, "pid": 1467, "refPid": 1960, "propName": "品牌名", "vid": 71208, "propValue": "DISNEY", "valueUnit": "", "valueExtendInfo": "", "controlType": 1 }, { "templatePid": 1225371, "pid": 1926, "refPid": 6491, "propName": "面料克重", "vid": 91475, "propValue": "180", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1301462, "pid": 2054, "refPid": 6926, "propName": "面料纹理1", "vid": 161198, "propValue": "光面", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1301882, "pid": 2054, "refPid": 6927, "propName": "面料纹理2", "vid": 161198, "propValue": "光面", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1337001, "pid": 2050, "refPid": 6928, "propName": "里料纹理", "vid": 161112, "propValue": "无里料/无内衬", "valueUnit": "", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1302302, "pid": 2052, "refPid": 6930, "propName": "面料克重1(g/m²)", "vid": 0, "propValue": "180", "valueUnit": "g/㎡", "valueExtendInfo": "", "numberInputValue": "" }, { "templatePid": 1302724, "pid": 2052, "refPid": 6931, "propName": "面料克重2(g/m²)", "vid": 0, "propValue": "180", "valueUnit": "g/㎡", "valueExtendInfo": "", "numberInputValue": "" }], productSkcReqs: [], productSpecPropertyReqs: [ { "parentSpecId": 1001, "parentSpecName": "颜色", "specId": 2001, "specName": "白色", "groupId": 1, "vid": 376, "refPid": 63, "pid": 13, "templatePid": 1140675, "propName": "颜色", "propValue": "白色", "valueUnit": "", "valueGroupId": 1, "valueGroupName": "白色系", "valueExtendInfo": "(255,255,255,1)" }, { "parentSpecId": 1001, "parentSpecName": "颜色", "specId": 3002, "specName": "黑色", "groupId": 3, "vid": 378, "refPid": 63, "pid": 13, "templatePid": 1140675, "propName": "颜色", "propValue": "黑色", "valueUnit": "", "valueGroupId": 3, "valueGroupName": "黑色系", "valueExtendInfo": "(0,0,0,1)" }, { "parentSpecId": 3001, "parentSpecName": "尺码", "specId": 10004, "specName": "S", "groupId": 2, "vid": 315, "refPid": 65, "pid": 14, "templatePid": 1140676, "propName": "尺码", "propValue": "S", "valueUnit": "", "valueGroupId": 2, "valueGroupName": "中国码", "valueExtendInfo": "" }, { "parentSpecId": 3001, "parentSpecName": "尺码", "specId": 9005, "specName": "M", "groupId": 2, "vid": 317, "refPid": 65, "pid": 14, "templatePid": 1140676, "propName": "尺码", "propValue": "M", "valueUnit": "", "valueGroupId": 2, "valueGroupName": "中国码", "valueExtendInfo": "" }, { "parentSpecId": 3001, "parentSpecName": "尺码", "specId": 11002, "specName": "L", "groupId": 2, "vid": 319, "refPid": 65, "pid": 14, "templatePid": 1140676, "propName": "尺码", "propValue": "L", "valueUnit": "", "valueGroupId": 2, "valueGroupName": "中国码", "valueExtendInfo": "" }, { "parentSpecId": 3001, "parentSpecName": "尺码", "specId": 12003, "specName": "XL", "groupId": 2, "vid": 320, "refPid": 65, "pid": 14, "templatePid": 1140676, "propName": "尺码", "propValue": "XL", "valueUnit": "", "valueGroupId": 2, "valueGroupName": "中国码", "valueExtendInfo": "" }, { "parentSpecId": 3001, "parentSpecName": "尺码", "specId": 8002, "specName": "XXL", "groupId": 2, "vid": 321, "refPid": 65, "pid": 14, "templatePid": 1140676, "propName": "尺码", "propValue": "XXL", "valueUnit": "", "valueGroupId": 2, "valueGroupName": "中国码", "valueExtendInfo": "" } ], carouselImageUrls: [], sizeTemplateIds: [17595329043547], showSizeTemplateIds: [17595329043547], productWhExtAttrReq: { outerGoodsUrl: "", productOrigin: { countryShortName: "CN", region2Id: 43000000000006 } }, productShipmentReq: { freightTemplateId: "HFT-18140051757711563355", shipmentLimitSecond: 172800 }, productWarehouseRouteReq: { targetRouteList: [{ warehouseId: "WH-01805626921113355", siteIdList: [127] }] }, productSemiManagedReq: { bindSiteIds: [127], semiLanguageStrategy: 2 }, personalizationSwitch: 0, productDraftId: 0, goodsModelReqs: [ { "id": 2604220096493, "source": 3, "headPortrait": "https://img.kwcdn.com/product/76942db695580e3e1f56feb1ed9665b4.jpg", "modelName": "Malik", "height": 177.5, "bust": 98, "waist": 82, "hipline": 98, "supplierId": 634418227088667, "footLength": null, "footWidth": null, "modelType": 1, "operatorId": 0, "operatorName": "", "operateAt": 1776843307946, "createOperatorId": 0, "createOperatorName": null, "createAt": 1776843307946, "canModelEdit": null, "specId": null, "fit": null, "recommendSpecId": 9005, "canSelect": true, "fromSystemGen": true, "modelId": 2604220096493, "modelProfileUrl": "https://img.kwcdn.com/product/76942db695580e3e1f56feb1ed9665b4.jpg", "modelFootLength": "", "modelFootWidth": "", "modelHeight": "177.5", "modelBust": "98", "modelWaist": "82", "modelHip": "98", "sizeSpecId": 9005, "sizeSpecName": "M", "tryOnResult": 1, "modelFeature": 1 } ] }; const payload = JSON.parse(JSON.stringify(originalPayload)); payload.productName = row.title; if (payload.productI18nReqs?.length > 0) payload.productI18nReqs[0].productName = row.titleEn; if (payload.goodsI18nReqs?.length > 0) payload.goodsI18nReqs[0].productName = row.titlePh; payload.productSkcReqs = []; // 颜色映射 const colorMap = { 白色: { specId: 2001, vid: 376, groupId: 1 }, 黑色: { specId: 3002, vid: 378, groupId: 3 } }; // 尺码配置(完全按你给的标准) const sizeList = [ { specId: 10004, name: "S", vid: 315, weight: 170000 }, { specId: 9005, name: "M", vid: 317, weight: 180000 }, { specId: 11002, name: "L", vid: 319, weight: 190000 }, { specId: 12003, name: "XL", vid: 320, weight: 200000 }, { specId: 8002, name: "XXL", vid: 321, weight: 210000 } ]; // 遍历颜色生成SKC row.selectedColors.forEach(colorName => { const cfg = colorMap[colorName]; if (!cfg) return; const imgs = row.Color[colorName] || []; const firstImg = imgs[0] || ""; // 取第一张图作为thumbUrl // ============== 生成标准SKU列表 ============== const skuList = sizeList.map(size => { return { "thumbUrl": firstImg, "productSkuThumbUrlI18nReqs": [], "extCode": "", "siteSupplierPrices": [{ "siteId": 127, "supplierPrice": 3600 }], "currencyType": "CNY", "productSkuSpecReqs": [ { "parentSpecId": 1001, "parentSpecName": "颜色", "specId": cfg.specId, "specName": colorName, "groupId": cfg.groupId, "vid": cfg.vid }, { "parentSpecId": 3001, "parentSpecName": "尺码", "specId": size.specId, "specName": size.name, "groupId": 2, "vid": size.vid } ], "productSkuId": 0, "productSkuSuggestedPriceReq": { "suggestedPrice": 8000, "suggestedPriceCurrencyType": "CNY" }, "productSkuWhExtAttrReq": { "productSkuVolumeReq": { "len": 300, "width": 250, "height": 10 }, "productSkuWeightReq": { "value": size.weight }, "productSkuBarCodeReqs": [], "productSkuSensitiveAttrReq": { "isSensitive": 0, "sensitiveList": [] }, "productSkuSensitiveLimitReq": {} }, "productSkuMultiPackReq": { "skuClassification": 1, "numberOfPieces": 1, "pieceUnitCode": 1, "productSkuNetContentReq": {}, "totalNetContent": {} }, "productSkuStockQuantityReq": { "warehouseStockQuantityReqs": [] }, "productSkuAccessoriesReq": { "productSkuAccessories": [] }, "productSkuNonAuditExtAttrReq": {} }; }); // 组装SKC const skc = { previewImgUrls: imgs, extCode: row.id, mainProductSkuSpecReqs: [{ parentSpecId: 1001, parentSpecName: "颜色", specId: cfg.specId, specName: colorName, groupId: cfg.groupId, vid: cfg.vid }], productSkuReqs: skuList, colorImageUrl: colorName === "黑色" ? "https://img.kwcdn.com/product/fancy/cb516eae-2176-4fa4-a1e0-f548b81f848f.jpg" : colorName === "白色" ? "https://img.kwcdn.com/product/fancy/38db2fa3-997c-40db-8b6c-58f398730a71.jpg" : firstImg, isBasePlate: 0 }; payload.productSkcReqs.push(skc); }); // 轮播图 const allImgs = []; row.selectedColors.forEach(c => allImgs.push(...(row.Color[c] || []))); payload.carouselImageUrls = allImgs; return payload; }, // 创建草稿 async createTemuProductDraft() { try { const { data } = await axios.post('https://agentseller.temu.com/visage-agent-seller/product/draft/add', { catId: 30478, productSemiManagedReq: { bindSiteIds: [127] }, productWarehouseRouteReq: { targetRouteList: [{ warehouseId: "WH-01805626921113355", siteIdList: [127] }] } }, { headers: { "accept": "*/*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", "anti-content": currentAnti, "cache-control": "no-cache", "content-type": "application/json", "mallid": currentMallId, "pragma": "no-cache" }, withCredentials: true }); if (data.success) return data.result.productDraftId; console.error('创建草稿失败', data); return null; } catch (e) { console.error('创建草稿异常', e); return null; } }, // 保存草稿 async saveTemuProductDraft(payload) { try { const { data } = await axios.post('https://agentseller.temu.com/visage-agent-seller/product/draft/save', payload, { headers: { "accept": "*/*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", "anti-content": currentAnti, "cache-control": "no-cache", "content-type": "application/json", "mallid": currentMallId, "pragma": "no-cache" }, withCredentials: true }); if (data.success) { console.log('✅ 保存草稿成功', payload.productDraftId); return true; } console.error('❌ 保存草稿失败', data); return false; } catch (e) { console.error('❌ 保存草稿异常', e); return false; } } } }); // ============================================== // 6. 点击浮动按钮显示/隐藏工具 // ============================================== let show = false; floatBtn.onclick = () => { show = !show; appContainer.style.display = show ? 'block' : 'none'; floatBtn.textContent = show ? '关闭上架工具' : '打开上架工具'; }; // 点击灰色区域关闭面板 appContainer.addEventListener('click', (e) => { if (e.target === appContainer) { vm.hidePanel(); } }); })();