// ==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 = `
`;
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();
}
});
})();