// ==UserScript==
// @name SHEIN工具合集
// @namespace http://tampermonkey.net/
// @version 6.0
// @description 资质失败重绑定 + SKC批量上传实拍图 + 批量同意议价 + 商品榜单SKC自动注入 + 批量上传运单
// @author 工具脚本
// @match https://sso.geiwohuo.com/*
// @grant none
// @require https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js
// @run-at document-start
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ====================== 本地存储:监控开关状态 ======================
const MONITOR_KEY = 'shein_rank_monitor_enabled';
function getMonitorStatus() {
return localStorage.getItem(MONITOR_KEY) === 'true';
}
function setMonitorStatus(enabled) {
localStorage.setItem(MONITOR_KEY, enabled);
}
// ====================== 全局悬浮球 ======================
function createGlobalFloatBall() {
const old = document.getElementById('global-float-ball');
if (old) old.remove();
const wrap = document.createElement('div');
wrap.id = 'global-float-ball';
wrap.style.cssText = `
position: fixed; top: 20px; left: 20px; z-index: 999999;
width: 48px; height: 48px;
`;
const ball = document.createElement('div');
ball.innerText = 'SK';
ball.style.cssText = `
width: 48px; height: 48px; border-radius: 50%;
background: #1677ff; color: #fff;
display: flex; align-items: center; justify-content: center;
font-size: 16px; font-weight: bold; cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
`;
const menuPanel = document.createElement('div');
menuPanel.id = 'global-menu';
menuPanel.style.cssText = `
display: none; position: absolute; top: 0; left: 58px;
width: 220px; background: #fff; border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15); padding: 12px;
`;
// 读取监控状态,设置按钮文字和颜色
const monitorEnabled = getMonitorStatus();
const monitorBtnBg = monitorEnabled ? '#52c41a' : '#999';
const monitorBtnText = monitorEnabled ? '✅ 榜单SKC监控(开)' : '⏸️ 榜单SKC监控(关)';
menuPanel.innerHTML = `
`;
wrap.appendChild(ball);
wrap.appendChild(menuPanel);
document.body.appendChild(wrap);
let showMenu = false;
ball.onclick = () => {
showMenu = !showMenu;
menuPanel.style.display = showMenu ? 'block' : 'none';
ball.style.background = showMenu ? '#ff4d4f' : '#1677ff';
if (!showMenu) hideAllToolPanels();
};
document.getElementById('showTool1').onclick = () => { hideAllToolPanels(); initTool1(); };
document.getElementById('showTool2').onclick = () => { hideAllToolPanels(); initTool2(); };
document.getElementById('showTool3').onclick = () => { hideAllToolPanels(); initTool3(); };
document.getElementById('showTool4').onclick = () => { hideAllToolPanels(); initTool4(); };
document.getElementById('showTool5').onclick = () => { hideAllToolPanels(); initTool5(); };
// 监控开关点击事件
document.getElementById('toggleRankMonitor').onclick = () => {
const newStatus = !getMonitorStatus();
setMonitorStatus(newStatus);
const btn = document.getElementById('toggleRankMonitor');
if (newStatus) {
btn.textContent = '✅ 榜单SKC监控(开)';
btn.style.background = '#52c41a';
console.log("✅ 榜单监控已开启");
} else {
btn.textContent = '⏸️ 榜单SKC监控(关)';
btn.style.background = '#999';
document.querySelectorAll('.injected-skc').forEach(el => el.remove());
console.log("⏸️ 榜单监控已关闭,清空SKC");
}
};
}
function hideAllToolPanels() {
const panel1 = document.getElementById('tool1-panel');
const panel2 = document.getElementById('float-skctool');
const panel3 = document.getElementById('bargain-tool-panel');
const panel4 = document.getElementById('upload-shipment-panel');
const panel5 = document.getElementById('auto-reserve-panel');
if (panel1) panel1.remove();
if (panel2) panel2.remove();
if (panel3) panel3.remove();
if (panel4) panel4.remove();
if (panel5) panel5.remove();
}
// ====================== 工具1:资质失败重绑定 ======================
function initTool1() {
const API = {
getFailSkc: 'https://sso.geiwohuo.com/grcp-api-prefix/pces/skc_compliance_item/list?page_size=100&page_num=1',
queryBind: 'https://sso.geiwohuo.com/grcp-api-prefix/pces/certificate_pool/query/list?page_num=1&page_size=20',
operate: 'https://sso.geiwohuo.com/grcp-api-prefix/pces/certificate_pool/batch/bind/skc'
};
const panel = document.createElement('div');
panel.id = 'tool1-panel';
panel.style.cssText = `
position: fixed; top: 20px; left: 300px; z-index: 999998;
background: #fff; padding: 16px; border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.15); width: 260px;
font-family: system-ui, -apple-system, sans-serif;
`;
panel.innerHTML = `
=== 工具加载成功 ===\n点击按钮开始全自动重新绑定\n
`;
document.body.appendChild(panel);
// ✅ 修复:工具1独立 log,不使用 store
function log(text) {
const el = document.getElementById('logBox');
el.textContent = `[${new Date().toLocaleTimeString()}] ${text}\n` + el.textContent;
}
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function getFailSkcList() {
const includeReview4 = document.getElementById('includeReview4').checked;
log('🔍 正在拉取资质失败的SKC...');
try {
const res = await fetch(API.getFailSkc, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"optional_aggregation_status_list": [0, 3, 5],
"optional_shelf_status_list": [0, 1, 2, 3],
"shelf_status_list": [0, 1, 2, 3],
"target_is_required_or_required": 1,
"verify_status_list": [],
})
});
const data = await res.json();
if (data.code !== '0') throw new Error(data.msg || '拉取失败');
const skcList = (data.info?.data || [])
.filter(item => includeReview4 ? true : item.review_state !== 4)
.map(item => item.skc)
.filter(Boolean);
if (skcList.length === 0) { log('✅ 未查询到符合条件的SKC'); return []; }
log(`✅ 拉取成功,共 ${skcList.length} 个待处理SKC`);
return skcList;
} catch (err) { log(`❌ 拉取SKC失败:${err.message}`); return []; }
}
async function queryBind(skc) {
log(`\n[${skc}] 查询绑定信息...`);
try {
const res = await fetch(API.queryBind, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
certificate_type_code_list: ['childgarment'],
spu_name_list: [],
pool_sn_list: [],
skc_name_list: [skc],
sort_field: 'bind_skc_flag_status_last_update_time',
sort_type: 'desc'
})
});
const data = await res.json();
const bindList = (data.info?.data || []).filter(item => item.bind_flag === 1);
if (bindList.length === 0) { log(`[${skc}] ⚠️ 未查询到绑定资质`); return null; }
log(`[${skc}] ✅ 获取pool_sn:${bindList[0].pool_sn}`);
return bindList[0].pool_sn;
} catch (err) { log(`[${skc}] ❌ 查询失败:${err.message}`); return null; }
}
async function doUnbind(poolSn, skc) {
log(`[${skc}] 执行解绑...`);
try {
const res = await fetch(API.operate, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
bind_certificate_pool_req_list: [{
pool_sn: poolSn,
un_bind_skc_name_list: [skc],
un_bind_spu_name_list: []
}]
})
});
const data = await res.json();
if (data.code === '0') log(`[${skc}] ✅ 解绑成功`);
else log(`[${skc}] ❌ 解绑失败:${data.msg}`);
} catch (err) { log(`[${skc}] ❌ 解绑异常:${err.message}`); }
}
async function doBind(poolSn, skc) {
log(`[${skc}] 执行绑定...`);
try {
const res = await fetch(API.operate, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
bind_certificate_pool_req_list: [{
pool_sn: poolSn,
bind_skc_name_list: [skc],
bind_spu_name_list: []
}]
})
});
const data = await res.json();
if (data.code === '0') log(`[${skc}] ✅ 绑定成功`);
else log(`[${skc}] ❌ 绑定失败:${data.msg}`);
} catch (err) { log(`[${skc}] ❌ 绑定异常:${err.message}`); }
}
async function processSingle(skc) {
const poolSn = await queryBind(skc);
if (!poolSn) return;
await doUnbind(poolSn, skc);
await delay(1000);
await doBind(poolSn, skc);
}
async function startAutoFix() {
const btn = document.getElementById('autoFixBtn');
btn.disabled = true;
btn.textContent = '⚙️ 执行中...请勿重复点击';
document.getElementById('logBox').textContent = '';
const skcList = await getFailSkcList();
if (skcList.length === 0) {
btn.disabled = false;
btn.textContent = '一键执行:自动绑定失败资质';
return;
}
log(`\n=== 开始批量绑定,共 ${skcList.length} 个SKC ==`);
for (let i = 0; i < skcList.length; i++) {
log(`\n==============================================`);
log(`处理第 ${i + 1}/${skcList.length} 个:${skcList[i]}`);
await processSingle(skcList[i]);
}
btn.disabled = false;
btn.textContent = '一键执行:自动绑定失败资质';
log(`\n==============================================`);
log(`全部绑定任务执行完成!`);
}
document.getElementById('autoFixBtn').onclick = startAutoFix;
}
// ====================== 工具2:XLSX + 文件夹自动上传实拍图(缓存版·不重复上传)+ 手动SKC上传 ======================
function initTool2() {
const API = {
upload: 'https://sso.geiwohuo.com/grcp-api-prefix/grcp/upload_private_attachment_to_pqms',
getSkcInfo: 'https://sso.geiwohuo.com/grcp-api-prefix/grcp/gpc/skc/details/v2',
algorithm: 'https://sso.geiwohuo.com/grcp-api-prefix/pces/skc/label/query_algorithm_result',
save: 'https://sso.geiwohuo.com/grcp-api-prefix/pces/skc/label/batch_save_spspt'
};
const FIXED_LABELS = [
{ certificate_type_id: 690, group: 2, industries: [106], label_code: "TAGA0075", label_id: 75 },
{ certificate_type_id: 612, group: 2, industries: [106], label_code: "TAGA0009", label_id: 9 },
{ certificate_type_id: 613, group: 2, industries: [106], label_code: "TAGA0008", label_id: 8 },
{ certificate_type_id: 645, group: 2, industries: [106], label_code: "TAGA0042", label_id: 42 },
{ certificate_type_id: 700, group: 1, industries: [106], label_code: "TAGA0080", label_id: 80 },
{ certificate_type_id: 701, group: 1, industries: [106], label_code: "TAGA0081", label_id: 81 },
{ certificate_type_id: 702, group: 1, industries: [106], label_code: "TAGA0082", label_id: 82 },
{ certificate_type_id: 703, group: 1, industries: [106], label_code: "TAGA0083", label_id: 83 }
];
const store = {
taskList: [],
currentIndex: 0,
isRunning: false,
isPaused: false,
success: 0, fail: 0,
fileMap: new Map(),
fileCache: new Map(),
algCache: new Map(),
manualFiles: { g1: [], g2: [] }
};
function createUI() {
const old = document.getElementById('float-skctool');
if (old) old.remove();
const wrap = document.createElement('div');
wrap.id = 'float-skctool';
wrap.style.cssText = `position:fixed; top:20px; left:300px; z-index:999998;`;
const panel = document.createElement('div');
panel.style.cssText = `
display:block; width:480px; background:#fff; border-radius:12px;
box-shadow:0 4px 20px rgba(0,0,0,0.15); padding:16px;
`;
panel.innerHTML = `
XLSX+文件夹自动上传实拍图
手动批量上传(单文件夹)
`;
wrap.appendChild(panel);
document.body.appendChild(wrap);
document.getElementById('folder-select').addEventListener('change', handleFolder);
document.getElementById('xlsx-file').addEventListener('change', handleXlsx);
document.getElementById('start').onclick = startRun;
document.getElementById('pause').onclick = () => { store.isPaused = true; updateBtn(); };
document.getElementById('resume').onclick = () => { store.isPaused = false; loop(); };
document.getElementById('clearCache').onclick = () => {
store.fileCache.clear();
store.algCache.clear();
log("🧹 已清空图片上传&识别缓存");
};
document.getElementById('manual-folder').addEventListener('change', handleManualFolder);
document.getElementById('start-manual').onclick = startManualRun;
}
function log(text) {
const el = document.getElementById('log');
const pre = store.isRunning && store.taskList.length ? `[${store.currentIndex + 1}/${store.taskList.length}] ` : '';
el.textContent = `[${new Date().toLocaleTimeString()}] ${pre}${text}\n` + el.textContent;
}
function updateBtn() {
const $ = s => document.getElementById(s);
if (!store.isRunning) {
$('start').style.display = 'block'; $('pause').style.display = 'none'; $('resume').style.display = 'none';
} else {
$('start').style.display = 'none';
$('pause').style.display = store.isPaused ? 'none' : 'block';
$('resume').style.display = store.isPaused ? 'block' : 'none';
}
}
function handleFolder(e) {
const files = Array.from(e.target.files);
store.fileMap.clear();
const map = new Map();
for (const f of files) {
const path = f.webkitRelativePath;
const parts = path.split('/');
if (parts.length < 2) continue;
const folder = parts[parts.length - 2];
const name = f.name.toLowerCase();
if (!map.has(folder)) map.set(folder, { g1: [], g2: [] });
if (name.includes('实体图')) map.get(folder).g1.push(f);
if (name.includes('外包装图')) map.get(folder).g2.push(f);
}
store.fileMap = map;
log(`✅ 已加载 ${map.size} 个文件夹的图片`);
}
function handleXlsx(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
const wb = XLSX.read(e.target.result, { type: 'binary' });
const ws = wb.Sheets[wb.SheetNames[0]];
const json = XLSX.utils.sheet_to_json(ws);
const tasks = json.map(row => ({
skc: (row['SKC'] || '').toString().trim(),
folder: (row['款式文件夹'] || '').toString().trim()
})).filter(t => t.skc && t.folder);
store.taskList = tasks;
log(`✅ 读取表格成功:共 ${tasks.length} 个SKC`);
};
reader.readAsBinaryString(file);
}
function handleManualFolder(e) {
const files = Array.from(e.target.files);
let g1 = [], g2 = [];
for (const f of files) {
const name = f.name.toLowerCase();
if (name.includes('实体图')) g1.push(f);
if (name.includes('外包装图')) g2.push(f);
}
store.manualFiles = { g1, g2 };
log(`📁 手动文件夹加载完成:实体图${g1.length}张 | 外包装图${g2.length}张`);
}
function getImages(folderStr) {
if (folderStr === '__MANUAL__') {
return store.manualFiles;
}
const folders = folderStr.split('+').map(s => s.trim());
let g1 = [], g2 = [];
for (const f of folders) {
const item = store.fileMap.get(f);
if (item) {
g1 = g1.concat(item.g1);
g2 = g2.concat(item.g2);
}
}
return { g1, g2 };
}
async function upload(file) {
const key = `${file.name}_${file.size}_${file.lastModified}`;
if (store.fileCache.has(key)) {
return store.fileCache.get(key);
}
const fd = new FormData(); fd.append('file', file);
try {
const r = await fetch(API.upload, { method: 'POST', body: fd });
const j = await r.json();
const res = j.code === '0' ? j.info : null;
store.fileCache.set(key, res);
return res;
} catch (e) {
store.fileCache.set(key, null);
return null;
}
}
async function processOne(task) {
const { skc, folder } = task;
const { g1: files1, g2: files2 } = getImages(folder);
if (files1.length === 0 && files2.length === 0) {
log(`❌ 未找到图片`);
return false;
}
const g1 = [], g2 = [];
for (const f of files1) {
const u = await upload(f);
if (u) g1.push(u);
await new Promise(r => setTimeout(r, 50));
}
for (const f of files2) {
const u = await upload(f);
if (u) g2.push(u);
await new Promise(r => setTimeout(r, 50));
}
log(`开始处理:${skc} 实体图${g1.length}张 | 外包装图${g2.length}张`);
const info = await getSkcInfo(skc);
if (!info) return false;
const urls1 = g1.map(x => x.url).sort().join('|');
const urls2 = g2.map(x => x.url).sort().join('|');
const algKey = `${skc}_${urls1}_${urls2}`;
let alg = null;
if (store.algCache.has(algKey)) {
alg = store.algCache.get(algKey);
log("⚡ 使用缓存识别结果,跳过算法请求");
} else {
alg = await algSubmit(skc, info.product_name, g1, g2);
if (alg) store.algCache.set(algKey, alg);
}
if (!alg) return false;
const ok = await save(skc, g1, g2, alg);
if (ok) store.success++; else store.fail++;
return ok;
}
async function getSkcInfo(skc) {
try {
const r = await fetch(API.getSkcInfo, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query_list: [{ skc }] })
});
const j = await r.json();
return j.code === '0' ? j.info[0] : null;
} catch (e) { log('❌ 获取SKC信息失败'); return null; }
}
async function algSubmit(skc, pname, g1, g2) {
const imgs = [
...g1.map(x => ({ group: 1, url: x.url })),
...g2.map(x => ({ group: 2, url: x.url }))
];
try {
const r = await fetch(API.algorithm, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
label_images: imgs, labels: FIXED_LABELS,
product_name: pname, skc, task_id: crypto.randomUUID()
})
});
const j = await r.json();
return j.code === '0' ? j.info.data[0] : null;
} catch (e) { log('❌ 识别失败'); return null; }
}
async function save(skc, g1, g2, alg) {
const body = g1.map(x => ({
file_url: x.url,
file_md5: x.md5,
file_name: x.name || 'image.png',
upload_dimension: 0,
part_code: ""
}));
const pkg = g2.map(x => ({
file_name: x.name || 'package.png',
file_url: x.url,
signed_file_url: x.url,
file_md5: x.md5,
file_group: 2,
lang: 'en',
source_type: 0,
upload_dimension: 0,
part_code: ""
}));
try {
const r = await fetch(API.save, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
algorithm_raw_result: alg.algorithm_raw_result,
body_file_list: body,
package_file_list: pkg,
paper_file_list: [],
response_duration_ms: alg.response_duration_ms || 200,
response_status: alg.response_status || 0,
skc_list: [skc]
})
});
const j = await r.json();
const ok = j.code === '0';
log(ok ? '✅ 上传保存成功' : '❌ 保存失败');
return ok;
} catch (e) {
log('❌ 保存异常');
return false;
}
}
async function loop() {
if (!store.isRunning || store.isPaused) return;
if (store.currentIndex >= store.taskList.length) {
store.isRunning = false; updateBtn();
log(`全部完成 | 总数:${store.taskList.length} | 成功:${store.success} | 失败:${store.fail}`);
return;
}
const task = store.taskList[store.currentIndex];
await processOne(task);
store.currentIndex++;
setTimeout(loop, 600);
}
function startRun() {
if (store.fileMap.size === 0) return log('请先选择图片文件夹');
if (store.taskList.length === 0) return log('请先选择XLSX表格');
store.currentIndex = 0; store.success = 0; store.fail = 0; store.isRunning = true; store.isPaused = false;
updateBtn();
loop();
}
async function startManualRun() {
const g1 = store.manualFiles.g1;
const g2 = store.manualFiles.g2;
if (g1.length === 0 && g2.length === 0) {
return log('❌ 请先选择【单个款式文件夹】');
}
const skcText = document.getElementById('manual-skc').value.trim();
if (!skcText) {
return log('❌ 请输入SKC(一行一个)');
}
const skcList = skcText.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0);
if (skcList.length === 0) return log('❌ 未识别到有效SKC');
log(`====================================`);
log(`📌 手动任务开始:共 ${skcList.length} 个SKC`);
store.success = 0;
store.fail = 0;
for (let i = 0; i < skcList.length; i++) {
const skc = skcList[i];
log(`\n【手动任务 ${i + 1}/${skcList.length}】${skc}`);
await processOne({ skc, folder: '__MANUAL__' });
await new Promise(r => setTimeout(r, 600));
}
log(`\n====================================`);
log(`✅ 手动任务全部完成!成功:${store.success} | 失败:${store.fail}`);
}
createUI();
}
// ====================== 工具3:批量同意/拒绝议价 ======================
async function initTool3() {
const now = new Date();
const endTime = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, '0') + "-" + String(now.getDate()).padStart(2, '0') + " 23:59:59";
let startMonth = now.getMonth() - 2;
let startYear = now.getFullYear();
if (startMonth < 0) { startMonth += 12; startYear -= 1; }
const startTime = startYear + "-" + String(startMonth + 1).padStart(2, '0') + "-" + String(now.getDate()).padStart(2, '0') + " 00:00:00";
const API_QUERY = 'https://sso.geiwohuo.com/dpas-api-prefix/dpas/discuss/bargain_page?page_num=1&page_size=100';
const API_AGREE = 'https://sso.geiwohuo.com/dpas-api-prefix/dpas/dpas/nonClothReceiptApi/batch_re_quote';
const QUERY_BODY = { bargain_status: 1, start_time: startTime, end_time: endTime };
async function request(url, opt) {
const def = { headers: { 'Content-Type': 'application/json', Cookie: document.cookie }, credentials: 'include', ...opt };
const r = await fetch(url, def);
return await r.json();
}
const panel = document.createElement('div');
panel.id = 'bargain-tool-panel';
panel.style.cssText = `position:fixed;top:20px;left:300px;z-index:999998;background:#fff;padding:20px;border-radius:8px;box-shadow:0 2px 12px rgba(0,0,0,0.15);width:800px;font-family:system-ui,-apple-system,sans-serif;`;
panel.innerHTML = `🔍 加载中...
`;
document.body.appendChild(panel);
const qr = await request(API_QUERY, { method: 'POST', body: JSON.stringify(QUERY_BODY) });
if (qr.code !== '0' || !qr.info?.data?.length) { panel.innerHTML = `✅ 暂无待处理议价
`; return; }
const list = qr.info.data.map((item, i) => ({
index: i, skc: item.skc_name || '',
sp: item.sku_cost_prices?.[0]?.cost_price_histories?.[0]?.cost_price || 0,
sy: item.sku_cost_prices?.[0]?.suggest_cost_price || 0,
bsn: item.bargain_sn, dsn: item.document_sn
}));
// 界面新增拒绝按钮,红色区分
panel.innerHTML = `
✅ 批量同意/拒绝议价
`;
const tb = document.getElementById('bt');
list.forEach(item => {
const tr = document.createElement('tr');
tr.innerHTML = `
|
${item.skc} |
${item.sp} |
${item.sy} | `;
tb.appendChild(tr);
});
const cbs = document.querySelectorAll('.bcb');
document.getElementById('ba').onclick = () => cbs.forEach(c => c.checked = true);
document.getElementById('bu').onclick = () => cbs.forEach(c => c.checked = false);
document.getElementById('bs').onclick = () => {
const v = +document.getElementById('bp').value;
if (isNaN(v)) return alert('请输入数字');
cbs.forEach((c, i) => c.checked = list[i].sy > v);
};
// -------- 原:同意议价 --------
document.getElementById('bsub').onclick = async () => {
if (!confirm('确定【同意】选中的议价?')) return;
const sel = Array.from(cbs).map((c, i) => c.checked ? list[i] : null).filter(Boolean);
if (sel.length === 0) return alert('未选中任何项');
const btn = document.getElementById('bsub');
btn.disabled = true; btn.textContent = '处理中...';
let ok = 0, ng = 0;
for (const item of sel) {
try {
const r = await request(API_AGREE, {
method: 'POST', body: JSON.stringify({
confirm_infos: [{ discuss_audit_type: 1, discuss_sn: item.bsn, document_sn: item.dsn }]
})
});
if (r.code === '0') ok++; else ng++;
} catch (e) { ng++; }
await new Promise(r => setTimeout(r, 300));
}
btn.disabled = false; btn.textContent = '✅ 提交同意议价';
alert(`处理完成\n同意成功:${ok}\n失败:${ng}`);
};
// -------- 新增:拒绝议价 --------
document.getElementById('bsubRefuse').onclick = async () => {
if (!confirm('确定【拒绝】选中的议价?')) return;
const sel = Array.from(cbs).map((c, i) => c.checked ? list[i] : null).filter(Boolean);
if (sel.length === 0) return alert('未选中任何项');
const btn = document.getElementById('bsubRefuse');
btn.disabled = true; btn.textContent = '拒绝中...';
let ok = 0, ng = 0;
for (const item of sel) {
try {
const r = await request(API_AGREE, {
method: 'POST', body: JSON.stringify({
confirm_infos: [{ discuss_audit_type: 2, discuss_sn: item.bsn, document_sn: item.dsn }]
})
});
if (r.code === '0') ok++; else ng++;
} catch (e) { ng++; }
await new Promise(r => setTimeout(r, 300));
}
btn.disabled = false; btn.textContent = '❌ 提交拒绝议价';
alert(`处理完成\n拒绝成功:${ok}\n失败:${ng}`);
};
}
// ====================== 工具4:批量上传运单(整合完成 + 自动获取待上传面单订单) ======================
function initTool4() {
const oldPanel = document.getElementById('upload-shipment-panel');
if (oldPanel) oldPanel.remove();
const style = document.createElement('style');
style.innerHTML = `
#upload-shipment-panel {
position: fixed; top: 70px; left: 300px;
z-index: 999998;
width: 380px; background: #fff;
border: 1px solid #ccc; border-radius: 10px;
padding: 15px; box-shadow: 0 0 8px rgba(0,0,0,0.2);
font-family: system-ui;
}
#upload-shipment-panel select,
#upload-shipment-panel input {
width:100%; padding:6px; margin:4px 0; box-sizing:border-box; border:1px solid #ccc; border-radius:4px;
}
.ship-btn { width:100%; padding:9px; border:none; border-radius:6px; cursor:pointer; color:white; margin:6px 0; font-size:14px; }
.ship-btn-file { background:#2385ee; }
.ship-btn-start { background:#19be6b; }
.ship-btn-pause { background:#ff5722; }
.ship-btn-resume { background:#ff9800; }
.ship-btn-auto { background:#722ED1; }
#shipFileInput { display:none; }
#shipStatus { font-size:13px; margin-top:10px; color:#333; line-height:1.5; }
#shipLogContainer {
margin-top:12px; padding:10px; height:220px; overflow-y:auto;
background:#f7f7f7; border:1px solid #ddd; border-radius:6px;
font-size:12px; line-height:1.4; white-space:pre-wrap;
}
.ship-log-success { color:#0d7f2d; font-weight:bold; }
.ship-log-error { color:#d70000; font-weight:bold; }
.ship-log-warn { color:#ff8c00; font-weight:bold; }
.ship-log-skip { color:#1750d1; font-weight:bold; }
`;
document.head.appendChild(style);
const panel = document.createElement('div');
panel.id = 'upload-shipment-panel';
panel.innerHTML = `
POD登录
商品ID导入方式
操作
请导入表格或点击自动获取
=== 执行日志 ===\n
`;
document.body.appendChild(panel);
const POD_SITE_MAP = {
uk: { loginApi: 'https://crossdiy.haoyipodeur.com/api/user/login', queryApi: 'https://crossdiy.haoyipodeur.com/api/orders/getlist' },
us: { loginApi: 'https://crossdiy2.haoyipod.com/api/user/login', queryApi: 'https://crossdiy2.haoyipod.com/api/orders/getlist' },
ecofeng: { loginApi: 'https://crossdiy.ecofengpod.com/api/user/login', queryApi: 'https://crossdiy.ecofengpod.com/api/orders/getlist' }
};
const CONFIG = {
queryApi: POD_SITE_MAP.uk.queryApi, loginApi: POD_SITE_MAP.uk.loginApi,
uploadApi: 'https://sso.geiwohuo.com/gsp/order/importSingleMultipleExpress',
uploadSite: 'shein-es', delay: 800, queryToken: '', currentPodKey: 'uk'
};
let orderList = [], XLSX = window.XLSX;
let isPaused = false, currentIndex = 0, success = 0, fail = 0, skip = 0;
function addLog(text, type = 'info') {
const logEl = document.getElementById('shipLogContainer');
const time = new Date().toLocaleTimeString();
const cls = { success: 'ship-log-success', error: 'ship-log-error', warn: 'ship-log-warn', skip: 'ship-log-skip' }[type] || '';
logEl.innerHTML += `[${time}] ${text}\n`;
logEl.scrollTop = logEl.scrollHeight;
}
// ===================== 本地存储登录信息 =====================
const STORAGE_KEY = 'ship_login_info';
function saveLoginInfo(info) { localStorage.setItem(STORAGE_KEY, JSON.stringify(info)); }
function loadLoginInfo() { const d = localStorage.getItem(STORAGE_KEY); return d ? JSON.parse(d) : null; }
function autoFillLoginInfo() {
const info = loadLoginInfo();
if (info) {
document.getElementById('shipMerchant').value = info.merchant || '';
document.getElementById('shipMobile').value = info.mobile || '';
document.getElementById('shipPwd').value = info.password || '';
if (info.podKey && POD_SITE_MAP[info.podKey]) {
document.getElementById('podSelect').value = info.podKey;
CONFIG.currentPodKey = info.podKey;
CONFIG.loginApi = POD_SITE_MAP[info.podKey].loginApi;
CONFIG.queryApi = POD_SITE_MAP[info.podKey].queryApi;
}
}
}
autoFillLoginInfo();
const podSelect = document.getElementById('podSelect');
podSelect.onchange = () => {
const k = podSelect.value;
CONFIG.currentPodKey = k;
CONFIG.loginApi = POD_SITE_MAP[k].loginApi;
CONFIG.queryApi = POD_SITE_MAP[k].queryApi;
CONFIG.queryToken = '';
document.getElementById('shipLoginStatus').textContent = '已切换仓库,请重新登录';
document.getElementById('shipLoginStatus').style.color = 'red';
};
// 登录
document.getElementById('shipLogin').onclick = async () => {
const mch = document.getElementById('shipMerchant').value.trim();
const mobile = document.getElementById('shipMobile').value.trim();
const pwd = document.getElementById('shipPwd').value.trim();
const st = document.getElementById('shipLoginStatus');
if (!mch || !mobile || !pwd) { st.textContent = '请填写完整信息'; st.style.color = 'red'; return; }
st.textContent = '登录中...'; st.style.color = '#007bff';
try {
const r = await fetch(CONFIG.loginApi, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ merchant: mch, mobile, password: pwd })
});
const j = await r.json();
if (j.code === 1 && j.data?.userinfo?.token) {
CONFIG.queryToken = j.data.userinfo.token;
st.textContent = '✅ 登录成功'; st.style.color = 'green';
addLog('POD 登录成功', 'success');
saveLoginInfo({ merchant: mch, mobile, password: pwd, podKey: CONFIG.currentPodKey });
} else {
st.textContent = '❌ ' + (j.msg || '登录失败');
st.style.color = 'red';
}
} catch (e) {
st.textContent = '❌ 异常:' + e.message;
st.style.color = 'red';
}
};
// ========== Excel导入(保留原有功能) ==========
document.getElementById('shipSelectFile').onclick = () => document.getElementById('shipFileInput').click();
document.getElementById('shipFileInput').onchange = async (e) => {
if (!e.target.files.length) return;
const file = e.target.files[0];
const st = document.getElementById('shipStatus');
st.textContent = '读取中...';
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = (ev) => {
try {
const wb = XLSX.read(new Uint8Array(ev.target.result), { type: 'array' });
const json = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1 });
const rows = json.filter(r => r?.length);
if (rows.length < 2) throw new Error('表格格式无效');
const header = rows[1].map(h => (h || '').trim());
const orderIdx = header.findIndex(c => c.includes('订单号'));
const goodsIdx = header.findIndex(c => c.includes('商品ID'));
if (orderIdx === -1 || goodsIdx === -1) throw new Error('未找到订单号/商品ID列');
orderList = rows.slice(2).map(r => ({
orderNo: (r[orderIdx] || '').trim(),
goodsId: (r[goodsIdx] || '').trim()
})).filter(x => x.orderNo && x.goodsId);
if (!orderList.length) { st.textContent = '无有效数据'; return; }
st.textContent = `✅ 读取成功:${orderList.length} 条`;
addLog(`导入 ${orderList.length} 条`, 'success');
document.getElementById('shipStart').disabled = false;
} catch (err) { st.textContent = '❌ ' + err.message; addLog(err.message, 'error'); }
};
};
// ========== 【新增】自动获取待上传面单订单 ==========
document.getElementById('shipAutoGetOrder').onclick = async () => {
const btn = document.getElementById('shipAutoGetOrder');
const st = document.getElementById('shipStatus');
btn.disabled = true;
btn.textContent = '获取中...';
addLog('正在获取【待上传面单】订单列表...');
try {
// 1. 获取订单列表
const orderRes = await fetch('https://sso.geiwohuo.com/gsp/orderPlus/listOrder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
allocateTimeStart: "2026-03-01 00:00:00",
allocateTimeEnd: "2026-06-30 23:59:59",
excludeOrderType: 5,
tabIndex: 3,
page: 1,
perPage: 200
}),
credentials: 'include'
});
const orderJson = await orderRes.json();
const orderIdList = orderJson.info.data.map(item => ({ orderId: item.orderId, orderNo: item.orderNo }));
if (orderIdList.length === 0) {
addLog('未获取到待上传面单订单', 'warn');
btn.disabled = false; btn.textContent = '📌 自动获取待上传面单订单';
return;
}
addLog(`获取到 ${orderIdList.length} 个订单,正在查询商品ID...`);
// 2. 批量查询商品ID
const itemRes = await fetch('https://sso.geiwohuo.com/gsp/orderPlus/listOrderItem', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orderPlusListPageVOList: orderIdList.map(o => ({ orderId: o.orderId, orderGoodsList: [] })),
tabIndex: 3
}),
credentials: 'include'
});
const itemJson = await itemRes.json();
// 3. 组装 orderNo + goodsId
const autoList = [];
itemJson.info.forEach(item => {
const orderNo = item.orderNo;
const goods = item.groupList?.[0]?.goodsList || [];
goods.forEach(g => autoList.push({ orderNo, goodsId: g.id + '' }));
});
orderList = autoList;
st.textContent = `✅ 自动获取成功:${orderList.length} 条`;
addLog(`自动获取完成,共 ${orderList.length} 条待上传`, 'success');
document.getElementById('shipStart').disabled = false;
} catch (e) {
addLog('获取失败:' + e.message, 'error');
} finally {
btn.disabled = false;
btn.textContent = '📌 自动获取待上传面单订单';
}
};
// 暂停/继续
const pauseBtn = document.getElementById('shipPause');
pauseBtn.onclick = () => {
isPaused = !isPaused;
pauseBtn.textContent = isPaused ? '继续执行' : '暂停执行';
pauseBtn.className = isPaused ? 'ship-btn ship-btn-resume' : 'ship-btn ship-btn-pause';
addLog(isPaused ? '已暂停' : '已继续', 'warn');
};
// 查询运单号
async function queryLogistics(orderNo) {
if (!CONFIG.queryToken) { addLog('请先登录', 'warn'); return null; }
const p = new URLSearchParams({ third_no: orderNo, pageNumber: 1, pageSize: 100, status: '-2,-1,0,1,2,3,6' });
try {
const r = await fetch(`${CONFIG.queryApi}?${p}`, { headers: { Token: CONFIG.queryToken } });
const j = await r.json();
const item = j.data?.list?.[0];
if (!item?.logistics_no) return null;
return { expressIdCode: item.logistics || '', expressCode: item.logistics_no };
} catch { return null; }
}
// 上传运单
async function upload(orderNo, goodsId, ec, eic) {
const r = await fetch(CONFIG.uploadApi, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orderNo, site: CONFIG.uploadSite,
orderGoodsExpressInfos: [{ expressCode: ec, expressIdCode: eic, goodsId: Number(goodsId) }]
}), credentials: 'include'
});
return await r.json();
}
function delay() {
return new Promise(r => {
const check = () => isPaused ? setTimeout(check, 200) : setTimeout(r, CONFIG.delay);
check();
});
}
// 开始执行
document.getElementById('shipStart').onclick = async () => {
if (!CONFIG.queryToken) { alert('请先登录POD'); return; }
if (orderList.length === 0) { alert('无任务数据'); return; }
const btn = document.getElementById('shipStart');
const st = document.getElementById('shipStatus');
if (!isPaused) { currentIndex = success = fail = skip = 0; document.getElementById('shipLogContainer').innerHTML = '=== 执行日志 ===\n'; addLog('开始任务'); }
btn.disabled = true; btn.textContent = '执行中'; pauseBtn.disabled = false;
for (; currentIndex < orderList.length; currentIndex++) {
const { orderNo, goodsId } = orderList[currentIndex];
st.textContent = `[${currentIndex + 1}/${orderList.length}] ${orderNo}`;
try {
const logi = await queryLogistics(orderNo);
if (!logi) { addLog(`${orderNo} 无运单,跳过`, 'skip'); skip++; await delay(); continue; }
const res = await upload(orderNo, goodsId, logi.expressCode, logi.expressIdCode);
if (res.code === '0' && res.msg === 'OK') { addLog(`${orderNo} 上传成功`, 'success'); success++; }
else { addLog(`${orderNo} 失败:${res.msg}`, 'error'); fail++; }
} catch (e) { addLog(`${orderNo} 异常`, 'warn'); fail++; }
st.textContent = `进度:${currentIndex + 1}/${orderList.length} 成功:${success} 失败:${fail} 跳过:${skip}`;
await delay();
}
st.textContent = `🎉 完成!成功:${success} 失败:${fail} 跳过:${skip}`;
addLog('任务结束', 'success');
btn.textContent = '开始自动执行'; btn.disabled = false; pauseBtn.disabled = true; isPaused = false;
pauseBtn.textContent = '暂停执行'; pauseBtn.className = 'ship-btn ship-btn-pause';
};
}
// ====================== 工具5:自动预约(获取+提交 自动时间) ======================
function initTool5() {
const panel = document.createElement('div');
panel.id = 'auto-reserve-panel';
panel.style.cssText = `
position: fixed; top: 20px; left: 300px; z-index: 999998;
background: #fff; padding: 16px; border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.15); width: 320px;
font-family: system-ui, -apple-system, sans-serif;
`;
panel.innerHTML = `
✅ 自动预约(自动2个月时间)
=== 日志输出区 ===
`;
document.body.appendChild(panel);
// 日志输出
function log(text) {
const el = document.getElementById('reserveLog');
const time = new Date().toLocaleTimeString();
el.textContent = `[${time}] ${text}\n` + el.textContent;
}
// 时间格式化:YYYY-MM-DD HH:mm:ss
function formatDateTime(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 主逻辑
async function autoReservePickUp() {
const getInfoUrl = 'https://sso.geiwohuo.com/gsp/store/address/prePickupV2/getPrePickInfo';
const commitUrl = 'https://sso.geiwohuo.com/gsp/store/address/prePickupV2/commit';
// 自动时间:当前时间 和 2个月前
const now = new Date();
const endTime = formatDateTime(now);
const twoMonthsAgo = new Date();
twoMonthsAgo.setMonth(now.getMonth() - 2);
const startTime = formatDateTime(twoMonthsAgo);
log(`自动时间范围:${startTime} → ${endTime}`);
const searchBody = {
orderDeliverPrintListRequest: {
page: 1,
perPage: 50,
placeOrderStartTime: startTime,
placeOrderEndTime: endTime
}
};
try {
// 1. 获取预约列表
log('正在获取可预约数据...');
const getRes = await fetch(getInfoUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(searchBody)
});
const getJson = await getRes.json();
if (getJson.code !== '0') {
log(`❌ 获取失败:${getJson.msg}`);
return;
}
const pickList = getJson.info.pickInfoList;
if (!pickList || pickList.length === 0) {
log('✅ 暂无待预约数据');
return;
}
log(`✅ 获取成功,共 ${pickList.length} 条待预约`);
// 2. 组装提交参数
const commitProviderInfoList = pickList.map(item => {
const firstCanPick = item.pickDateInfo.pickTimeInfoList.find(d => d.permitPickup === 1);
const timeRange = item.pickDateInfo.reserveTimeRanges.at(-1);
return {
packageTotal: item.packageTotal,
formatPackageTotalWeight: { number: item.packageTotalWeight, unionId: 1 },
pickDate: firstCanPick?.pickDate || '',
reserveTimeRange: timeRange || '',
providerId: item.placeProviderId,
warehouseCode: item.placeOrderWarehouseResp.warehouseCode,
defaultPackageTotalWeight: item.packageTotalWeight
};
});
const commitBody = {
commitProviderInfoList,
orderDeliverPrintListRequest: searchBody.orderDeliverPrintListRequest
};
// 3. 提交预约
log('正在提交预约...');
const commitRes = await fetch(commitUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(commitBody)
});
const commitJson = await commitRes.json();
log(`📌 提交结果:${JSON.stringify(commitJson)}`);
if (commitJson.code === '0') {
log('✅ 预约提交成功!');
} else {
log(`❌ 预约失败:${commitJson.msg}`);
}
} catch (err) {
log(`❌ 异常:${err.message}`);
}
}
document.getElementById('startAutoReserve').onclick = autoReservePickUp;
}
// ====================== 【整合】商品榜单监控 + SKC注入(自动清除旧数据) ======================
function initRankMonitor() {
const TARGET_PATH = '/sbn/marketAnalysis/queryProductRanking';
let rankData = [];
let isHooked = false;
console.log("🚀 榜单监控模块已启动,监听接口:" + TARGET_PATH);
function clearOldSKC() {
document.querySelectorAll('.injected-skc').forEach(el => el.remove());
}
function startInjectSKU() {
if (!getMonitorStatus()) {
console.log("ℹ️ 监控未开启,跳过注入");
return;
}
console.log("🔍 开始查找表格并注入SKC,数据长度:" + rankData.length);
if (rankData.length === 0) {
console.log("❌ 无榜单数据,无法注入");
return;
}
const timer = setInterval(() => {
const tbody = document.querySelector('tbody');
if (!tbody) {
console.log("⏳ 未找到表格,继续等待...");
return;
}
if (tbody.children.length === 0) {
console.log("⏳ 表格无数据,继续等待...");
return;
}
clearInterval(timer);
clearOldSKC();
const trList = tbody.querySelectorAll('tr');
console.log(`✅ 找到表格,共 ${trList.length} 行,数据 ${rankData.length} 条`);
trList.forEach((tr, index) => {
const item = rankData[index];
if (!item) {
console.log(`⚠️ 第 ${index} 行无对应数据`);
return;
}
const skc = item.skc;
if (!skc) {
console.log(`⚠️ 第 ${index} 行数据无 skc 字段`, item);
return;
}
const tds = tr.querySelectorAll('td');
if (tds.length < 3) {
console.log(`⚠️ 第 ${index} 行列数不足`);
return;
}
const targetTd = tds[2];
const skuDiv = document.createElement('div');
skuDiv.className = 'injected-skc';
skuDiv.style.marginTop = '4px';
skuDiv.style.display = 'flex';
skuDiv.style.alignItems = 'center';
skuDiv.style.gap = '6px';
// SKC 文本 + 复制
const skcText = document.createElement('span');
skcText.style.color = '#165DFF';
skcText.style.fontWeight = 'bold';
skcText.style.cursor = 'pointer';
skcText.innerText = `skc: ${skc}`;
skcText.addEventListener('click', async () => {
await navigator.clipboard.writeText(skc);
skcText.style.color = '#00B42A';
setTimeout(() => {
skcText.style.color = '#165DFF';
}, 1200);
});
// 跳转按钮
const jumpBtn = document.createElement('button');
jumpBtn.innerText = '跳转';
jumpBtn.style.padding = '2px 6px';
jumpBtn.style.fontSize = '12px';
jumpBtn.style.border = 'none';
jumpBtn.style.borderRadius = '4px';
jumpBtn.style.background = '#1677ff';
jumpBtn.style.color = '#fff';
jumpBtn.style.cursor = 'pointer';
jumpBtn.onclick = () => {
const siteEl = document.querySelector('.soui-select-ellipsis');
const site = siteEl?.textContent?.trim() || '';
let url = '';
switch (site) {
case 'shein-us': url = `https://us.shein.com/pdsearch/${skc}/`; break;
case 'shein-fr': url = `https://fr.shein.com/pdsearch/${skc}/`; break;
case 'shein-de': url = `https://de.shein.com/pdsearch/${skc}/`; break;
case 'shein-es': url = `https://es.shein.com/pdsearch/${skc}/`; break;
case 'shein-it': url = `https://it.shein.com/pdsearch/${skc}/`; break;
case 'shein-uk': url = `https://www.shein.co.uk/pdsearch/${skc}/`; break;
default: alert('不支持的站点:' + site); return;
}
window.open(url, '_blank');
};
skuDiv.appendChild(skcText);
skuDiv.appendChild(jumpBtn);
targetTd.appendChild(skuDiv);
console.log(`✅ 第 ${index} 行注入成功:${skc}`);
});
}, 300);
}
function hookXHR() {
if (isHooked) return;
isHooked = true;
console.log("🔗 已挂载XHR请求监听");
const oriOpen = XMLHttpRequest.prototype.open;
const oriSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url) {
this._url = url;
this._method = method;
oriOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function (body) {
const self = this;
if (self._url.includes(TARGET_PATH) && self._method.toUpperCase() === 'POST') {
console.log("🎯 捕获到榜单接口请求:", self._url);
self.addEventListener('load', function () {
if (!getMonitorStatus()) return;
try {
const res = JSON.parse(self.responseText);
console.log("📊 榜单接口返回数据:", res);
rankData = res?.info?.data || [];
console.log("📦 解析到商品列表:", rankData.length + " 条");
startInjectSKU();
} catch (e) {
console.error("❌ 解析榜单数据失败:", e);
}
});
}
oriSend.apply(this, arguments);
};
}
hookXHR();
}
// 启动全部功能
window.addEventListener('load', () => {
createGlobalFloatBall();
initRankMonitor();
console.log("✅ SHEIN工具合集加载完成,当前监控状态:" + (getMonitorStatus() ? "开启" : "关闭"));
});
})();