// ==UserScript==
// @name Taobao orders export 淘宝订单快递导出
// @namespace http://tampermonkey.net/
// @version 0.0.2
// @description Based on 淘宝买家订单导出 by CMA, added tracking number on top of it to the export data. Instructions are listed on GitHub
// @author Trizzy33
// @include https://buyertrade.taobao*
// @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js
// @grant window.close
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
//TODO: add retry for failed tracking number.
//github https://github.com/Trizzy33/taobaoTrackingNumberExport/blob/main/script
//TODO: add retry for failed tracking number.
//**********************************global variables****************************************
let orderList = [];
let isProcessing = false;
let shouldStop = false;
let currentWindow = null;
const state = {
orderList: [],
isProcessing: false,
shouldStop: false,
currentWindow: null
};
const orderListPage = /(http|https):\/\/buyertrade\.taobao.*?\/trade/g;
//******************************************************************************************
function addButton(element, onclickFunc, value = "按钮", width = "60px", height = "60px") {
const button = document.createElement("input");
button.type = "button";
button.value = value;
button.style.height = height;
button.style.width = width;
button.style.align = "center";
button.style.marginBottom = "10px";
button.style.marginLeft = "250px";
button.style.color = "white";
button.style.background = "#409EFF";
button.style.border = "1px solid #409EFF";
button.onclick = function () {
onclickFunc();
}
element.appendChild(button);
element.insertBefore(button, element.childNodes[0]);
}
if (orderListPage.exec(document.URL)) {
const orderListMain = document.getElementById("J_bought_main");
addButton(orderListMain, addCurrentPageOrdersToList, "添加本页订单", "160px");
addButton(orderListMain, exportOrders, "导出订单", "160px");
addButton(orderListMain, stopScript, "停止脚本", "160px");
}
function toCsv(header, data, filename) {
let rows = '\uFEFF订单号,商品名称,店铺链接,价格,数量,实付款,状态,运单号\n'
for(let order of data) {
rows += order.join(',') + '\n';
}
let blob = new Blob([rows], {type: 'text/csv;charset=utf-8;'});
let encodedUrl = URL.createObjectURL(blob);
let url = document.createElement("a");
url.setAttribute("href", encodedUrl);
url.setAttribute("download", filename + ".csv");
document.body.appendChild(url);
url.click();
}
//*************************************************Buttons functions****************************************************
function addCurrentPageOrdersToList() {
if (state.isProcessing) {
console.log('已有处理进行中,请等待...');
return;
}
state.isProcessing = true;
state.shouldStop = false;
const orders = document.querySelectorAll(".js-order-container");
processOrders(orders);
}
function exportOrders() {
const header = ["订单号", "商品明细", "商品链接", "单价", "数量", "实付款", "状态", "快递单号"];
toCsv(header, orderList, "淘宝订单导出")
}
function stopScript() {
state.shouldStop = true;
console.log('正在停止处理...');
if (state.currentWindow && !state.currentWindow.closed) {
state.currentWindow.close();
}
if (state.orderList.length > 0) {
exportOrders();
}
state.isProcessing = false;
let statusDiv = document.getElementById('processing-status');
if (statusDiv) {
statusDiv.textContent = '处理已停止';
statusDiv.style.background = '#F56C6C';
// 2秒后移除状态显示
setTimeout(() => {
statusDiv.remove();
}, 2000);
}
}
//*************************************************Buttons functions****************************************************
function processOrders(orders, currentOrderIndex = 0, results = []) {
if (currentOrderIndex >= orders.length || state.shouldStop) {
console.log('所有订单处理完成!');
console.table(results);
return results;
}
try{
const order = orders[currentOrderIndex];
const id = order.getAttribute('data-reactid').match(/order-(\d+)/)?.[1];
console.log(`处理订单 ${id},第 ${currentOrderIndex + 1}/${orders.length} 个订单`);
// 处理单个订单中的商品
processOrderItems(order, id, 0, [], (orderResults) => {
orderList.push(orderResults);
setTimeout(() => {
processOrders(orders, currentOrderIndex + 1, results);
}, 500);
});
}catch(error){
console.error(`当前订单结果处理失败:`, error);
// 继续处理下一个订单
setTimeout(() => {
processOrders(orders, currentOrderIndex + 1, results);
}, 500);
}
}
function processOrderItems(order, id, index, itemResults, callback) {
try{
let productQuery = order.querySelector("span[data-reactid='.0.7:$order-" + id + ".$" + id + ".0.1:1:0.$" + index + ".$0.0.1.0.0.1']");
if (productQuery == null) {
callback(itemResults);
return;
}
const queries = {
product: order.querySelector("span[data-reactid='.0.7:$order-" + id + ".$" + id + ".0.1:1:0.$" + index + ".$0.0.1.0.0.1']"),
price: order.querySelector("span[data-reactid='.0.7:$order-" + id + ".$" + id + ".0.1:1:0.$" + index + ".$1.0.1.1']"),
count: order.querySelector("p[data-reactid='.0.7:$order-" + id + ".$" + id + ".0.1:1:0.$" + index + ".$2.0.0']"),
actualPay: order.querySelector("span[data-reactid='.0.7:$order-" + id + ".$" + id + ".0.1:1:0.$" + index + ".$4.0.0.2.0.1']"),
itemUrl: order.querySelector("a[href]"),
status: index === 0 ? order.querySelector("span[data-reactid='.0.7:$order-" + id + ".$" + id + ".0.1:1:0.$" + index + ".$5.0.0.0']") : null,
tracking: index === 0 ? order.querySelector('#viewLogistic') : null
};
if (!queries.product || !queries.price || !queries.count) {
throw new Error(`订单 ${id} 缺少名称`);
}
const orderItem = [
id,
queries.product.textContent.replace(/,/g, ","),
queries.itemUrl.href,
parseFloat(queries.price.textContent),
queries.count.textContent,
queries.actualPay?.textContent || '',
queries.status?.textContent || ''
];
if (index === 0 && queries.tracking) {
const logisticsUrl = queries.tracking.href;
// 使用异步函数获取快递单号
getTrackingNumber(logisticsUrl).then(trackingNumber => {
orderItem.push(trackingNumber);
itemResults = orderItem;
// 递归处理下一个商品
processOrderItems(order, id, index + 1, itemResults, callback);
});
} else {
// 没有物流信息或不是第一个商品,直接添加默认值并返回
orderItem.push('0');
itemResults.push(orderItem);
callback(itemResults);
}
} catch (error) {
// 直接处理下一个商品
console.error(`处理订单 ${id} 时出错:`, error);
callback(itemResults?itemResults:[]);
}
}
async function getTrackingNumber(logisticsUrl) {
return new Promise((resolve) => {
if (state.shouldStop) {
resolve('0');
return;
}
const currentWindow = window.open(logisticsUrl);
setTimeout(() => {
try {
if (currentWindow && !currentWindow.closed) {
const trackingNumber = currentWindow.document
.querySelector('.rax-view-v2.flexRow.pc-company-wrapper')
?.querySelector('.rax-view-v2.flexRow')
?.querySelectorAll('.rax-text-v2.desc')?.[1]
?.textContent || '0';
currentWindow.close();
resolve(trackingNumber);
} else {
resolve('0');
}
} catch (error) {
console.error('获取物流信息失败:', error);
if (currentWindow) currentWindow.close();
resolve('0');
} finally {
state.currentWindow = null;
resolve('0');
}
}, 2000);
});
}