您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在唯品会联盟订单报表页面,采集订单数据并发送到指定接口,同时展示状态面板和日志,支持自动翻页、重试、手动控制等功能。
当前为
// ==UserScript== // @name 唯品会联盟实时订单采集器-crazyunix // @namespace http://tampermonkey.net/ // @version 2025.06.26.1 // @description 在唯品会联盟订单报表页面,采集订单数据并发送到指定接口,同时展示状态面板和日志,支持自动翻页、重试、手动控制等功能。 // @author Your Name // @match https://union.vip.com/v/index* // @icon https://www.google.com/s2/favicons?sz=64&domain=vip.com // @connect ff.wxmob.cn // @grant GM_xmlhttpRequest // @grant unsafeWindow // ==/UserScript== (function () { 'use strict'; const TARGET_API_URL = 'https://ff.wxmob.cn/api/market/report/data'; const VIP_ORDER_API_KEYWORD = 'v2/order/list'; const CUSTOM_HEADERS = { 'Content-Type': 'application/json' }; const MAX_RETRY = 3; let globalStopFlag = false; let currentPage = 1; let sendSuccessCount = 0; let lastFailedData = null; // 1. 注入浮动状态面板 const panel = document.createElement('div'); panel.id = 'vip-order-panel'; panel.style.cssText = ` position: fixed; top: 100px; right: 20px; width: 300px; background: #f9f9f9; border: 1px solid #ccc; border-radius: 8px; padding: 12px; z-index: 9999; box-shadow: 0 0 10px rgba(0,0,0,0.2); font-size: 14px; font-family: sans-serif; `; panel.innerHTML = ` <div id="panel-header" style="cursor: move; font-weight: bold; background: #eee; padding: 5px 10px; border-radius: 6px 6px 0 0;"> 📊 实时采集状态 <button id="close-panel" style="float: right; color: red; border: none; background: none; font-weight: bold; cursor: pointer;">✖</button> </div> <div style="margin-top: 10px"> <label>📇 Advertiser ID:</label> <input id="advertiser-id" type="text" placeholder="请输入广告主ID" style="width: 100%; padding: 4px; margin-top: 4px; border-radius: 4px; border: 1px solid #ccc;"/> </div> <div id="status-panel" style="margin: 10px 0;"></div> <div style="display: flex; justify-content: space-between;"> <button id="stop-btn">🛑 停止</button> <button id="retry-btn">🔁 重试</button> <button id="resume-btn">▶️ 继续</button> </div> <div id="log-panel" style="margin-top: 10px; max-height: 200px; overflow-y: auto; background: #fff; padding: 6px; border: 1px solid #ddd;"></div> `; document.body.appendChild(panel); // 2. 拖动支持 const drag = document.getElementById('panel-header'); drag.onmousedown = function (e) { const offsetX = e.clientX - panel.offsetLeft; const offsetY = e.clientY - panel.offsetTop; document.onmousemove = function (e) { panel.style.left = e.clientX - offsetX + 'px'; panel.style.top = e.clientY - offsetY + 'px'; }; document.onmouseup = function () { document.onmousemove = null; document.onmouseup = null; }; }; // 3. 控件绑定 document.getElementById('stop-btn').onclick = () => globalStopFlag = true; document.getElementById('retry-btn').onclick = () => { if (lastFailedData) { globalStopFlag = false; sendDataWithRetry(lastFailedData, MAX_RETRY, tryAutoTurnPage); } }; document.getElementById('resume-btn').onclick = () => { globalStopFlag = false; tryAutoTurnPage(); }; document.getElementById('close-panel').onclick = () => panel.style.display = 'none'; function updateStatusPanel({ page, status, retriesLeft, error }) { document.getElementById('status-panel').innerHTML = `📄 第 ${page} 页<br/>状态: ${status}<br/>重试剩余: ${retriesLeft}<br/>错误: ${error}`; } function addTaskLog(text) { const log = document.getElementById('log-panel'); const line = document.createElement('div'); const timestamp = new Date().toLocaleTimeString(); line.textContent = `[${timestamp}] ${text}`; log.appendChild(line); log.scrollTop = log.scrollHeight; console.log(`[LOG][${timestamp}] ${text}`); } // 4. 劫持 XHR const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { this._requestURL = url; return originalOpen.apply(this, arguments); }; const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { this.addEventListener('load', function() { if (this._requestURL && this._requestURL.includes(VIP_ORDER_API_KEYWORD)) { if (this.status === 200 && this.responseText) { try { const responseData = JSON.parse(this.responseText); sendDataWithRetry(responseData, MAX_RETRY, tryAutoTurnPage); } catch (e) { console.error('解析 JSON 失败:', e); } } } }); return originalSend.apply(this, arguments); }; function sendDataWithRetry(data, retries, callback) { const advertiserId = document.getElementById('advertiser-id')?.value?.trim(); if (!advertiserId) { updateStatusPanel({ page: currentPage, status: '❌ 未填写广告主ID', retriesLeft: 0, error: '缺失ID' }); addTaskLog('🚫 未发送:广告主ID为空'); return; } lastFailedData = data; const headers = { ...CUSTOM_HEADERS, advertiserId }; updateStatusPanel({ page: currentPage, status: '发送中...', retriesLeft: retries, error: '' }); console.log(`🛰️ 正在发送第 ${currentPage} 页数据到 ${TARGET_API_URL}`); console.log(`添加的 header advertiserId=${advertiserId}`); GM_xmlhttpRequest({ method: 'POST', url: TARGET_API_URL, headers, data: JSON.stringify(data), onload: function (resp) { if (resp.status >= 200 && resp.status < 300) { sendSuccessCount++; lastFailedData = null; updateStatusPanel({ page: currentPage, status: '✅ 成功', retriesLeft: retries, error: '' }); addTaskLog(`✅ 第 ${currentPage} 页发送成功`); callback && callback(); } else { retryOrFail(`HTTP状态 ${resp.status}`); } }, onerror: () => retryOrFail('网络错误') }); function retryOrFail(msg) { if (retries > 1) { updateStatusPanel({ page: currentPage, status: '重试中...', retriesLeft: retries - 1, error: msg }); setTimeout(() => sendDataWithRetry(data, retries - 1, callback), 1500); } else { updateStatusPanel({ page: currentPage, status: '❌ 失败', retriesLeft: 0, error: msg }); globalStopFlag = true; addTaskLog(`❌ 第 ${currentPage} 页失败:${msg}`); alert('❌ 数据发送失败,请检查接口或广告主ID'); } } } function tryAutoTurnPage() { if (globalStopFlag) return; const nextBtn = document.querySelector('button.btn-next'); if (!nextBtn) { console.warn('⚠️ 未找到翻页按钮'); return; } if (nextBtn.disabled) { updateStatusPanel({ page: currentPage, status: '✅ 全部完成', retriesLeft: 0, error: '' }); addTaskLog(`🎉 全部采集完成,成功 ${sendSuccessCount} 页`); alert('✅ 所有订单数据已采集完成!'); } else { currentPage++; updateStatusPanel({ page: currentPage, status: '翻页中...', retriesLeft: MAX_RETRY, error: '' }); // 👇 添加翻页延迟,1 秒后点击下一页 setTimeout(() => { nextBtn.click(); }, 1000); } } })();