Taobao orders export 淘宝订单快递导出

Based on 淘宝买家订单导出 by CMA, added tracking number on top of it to the export data. Instructions are listed on GitHub

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