Greasy Fork

Greasy Fork is available in English.

SNH商城票务信息统计

抓取竞价页面信息并导出为Excel

// ==UserScript==
// @name         SNH商城票务信息统计
// @namespace    http://tampermonkey.net/
// @version      0.2.4
// @description  抓取竞价页面信息并导出为Excel
// @author       You
// @match        https://shop.48.cn/*
// @grant        GM_download
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.4/xlsx.full.min.js
// @license      MIT
// ==/UserScript==

/* global XLSX */

(function() {
    'use strict';

    // Your code here...

    // 确保 XLSX 已经加载
    if (typeof XLSX === 'undefined') {
        console.error('XLSX library not loaded!');
        return;
    }

    function parseBidsData(bidListElement, totalBids) {
        const liElements = bidListElement.querySelectorAll("li");
        liElements.forEach(li => {
            try {
                const bidStatus = li.querySelector(".jl_1")?.innerText.trim(); // 竞价状态
                const jl2Elements = li.querySelectorAll(".jl_2");
                const bidder = jl2Elements[0]?.innerText.trim(); // 出价人
                const bidTime = jl2Elements[1]?.innerText.trim(); // 出价时间
                const bidAmount = li.querySelector(".jl_3")?.innerText.trim(); // 出价金额

                if (bidStatus?.includes("竞价成功")) {
                    totalBids.push({
                        "出价状态": bidStatus,
                        "出价人": bidder,
                        "出价时间": bidTime,
                        "出价金额": bidAmount
                    });
                } else if (!bidStatus?.includes("竞价成功") && checkBidExist(totalBids, bidder)) {
                    totalBids.push({
                        "出价状态": "竞价失败",
                        "出价人": bidder,
                        "出价时间": bidTime,
                        "出价金额": bidAmount
                    });
                }
            } catch (e) {
                // 忽略异常
            }
        });
        return totalBids;
    }

    // 检查某个出价人在已有的竞价数据中是否存在
    function checkBidExist(bidsData, bidder) {
        if (bidsData.length === 0) {
            return true;
        }
        for (const bid of bidsData) {
            if (bid["出价人"] && bid["出价人"].includes(bidder)) {
                // 如果出价人已经存在,则返回 false
                return false;
            }
        }
        // 如果出价人不存在,则返回 true
        return true;
    }

    // 模拟点击翻页按钮
    function clickNextPage() {
        const element = document.querySelector("#a_b_n");
        if (element) {
            //console.log("找到元素,准备模拟点击。");
            // 模拟点击
            element.click();
        } else {
            console.log("未找到目标元素。");
        }
    }

    // 延迟函数
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    async function waitForElement(selectorFunc, timeout = 10000, interval = 100) {
        const start = Date.now();
        while (Date.now() - start < timeout) {
            const el = selectorFunc();
            if (el) return el;
            await sleep(interval);
        }
        throw new Error("元素加载超时");
    }

    // 从竞价列表中提取成功竞价的信息
    async function autoBidUntilEnd(targetSuccessfulCount, bidType, theaterName) {
        const totalBidsData = [];

        // 等待实际竞价人数元素加载
        const actualBidNumberElement = await waitForElement(() =>
                                                            document.evaluate('/html/body/div[2]/div/div[2]/div[3]/div[1]/ul/li[7]/span/span[3]',
                                                                              document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue, 10000);

        if (parseInt(actualBidNumberElement?.innerText || "0") === 0) {
            return totalBidsData;
        }

        // 等待最大页码元素加载
        let maxPageElement = await waitForElement(() => document.querySelector(".pink.b_c"), 10000);
        while (maxPageElement.innerText.trim() === '') {
            location.reload(); // 相当于 driver.refresh()
            maxPageElement = await waitForElement(() => document.querySelector(".pink.b_c"), 10000);
        }
        const maxPage = parseInt(maxPageElement.innerText.trim());
        let nowPage = 1;
        while (nowPage <= maxPage) {
            
            updateOverlayMessage(`正在抓取第 ${nowPage} 页数据`);
            
            const uBlist = document.getElementById("u_blist");
            if (uBlist) {
                parseBidsData(uBlist, totalBidsData);
            }

            const uBlistM = document.getElementById("u_blistM");
            if (uBlistM) {
                parseBidsData(uBlistM, totalBidsData);
            }

            // 点击下一页按钮
            const nextButton = await waitForElement(() => document.querySelector("#a_b_n"), 10000);
            if (nextButton) {
                nextButton.click();
                console.log(`加载第 ${nowPage} 页...`);
                nowPage++;
                await sleep(1000); // 模拟 time.sleep(0.15)
            } else {
                break; // 找不到下一页按钮,跳出循环
            }
        }
        
        updateOverlayMessage("数据抓取完毕,正在整理座位信息...");

        // 获取座位数组
        const seats = getSeatPosition(theaterName, bidType, targetSuccessfulCount);

        // 分配座位号
        totalBidsData.forEach((bid, idx) => {
            bid["座位类型"] = bidType;
            bid["座位号"] = (idx >= seats.length) ? "竞价失败" : seats[idx];
        });

        return totalBidsData;
    }

    // 获取座位类型
    function getSeatType(itemName) {
        if (itemName.includes("超级")) {
            return "超级";
        } else if (itemName.includes("SVIP")) {
            return "SVIP";
        } else if (itemName.includes("摄影")) {
            return "摄影";
        } else if (itemName.includes("杆位")) {
            return "杆位";
        } else if (itemName.includes("普站")) {
            return "普站";
        } else if (itemName.includes("VIP")) {
            return "VIP";
        } else if (itemName.includes("普座")) {
            return "普座";
        } else if (itemName.includes("MINILIVE")) {
            return "MINILIVE";
        } else if (itemName.includes("拍立得") || itemName.includes("百场徽章粉丝纪念礼盒")) {
            return "拍立得";
        } else if (itemName.includes("生日会")) {
            return "生日会";
        } else if (itemName.includes("全纪录")) {
            return "全纪录";
        } else {
            return "其他";
        }
    }

    // 获取座位号
    function getSeatPosition(theaterName, bidType, bidCount = 0) {
        if (theaterName.includes("SNHbirthday") && bidCount === 71 && bidType === "普站") {
            return getSeatPositionSNHBirthday(bidType);
        } else if (theaterName.includes("SNHbirthday") && bidCount === 76 && bidType === "普站") {
            return getSeatPositionSNH(bidType);
        } else if (theaterName.includes("SNHbirthday")) {
            return getSeatPositionSNHBirthday(bidType);
        } else if (theaterName.includes("SNH")) {
            return getSeatPositionSNH(bidType);
        } else if (theaterName.includes("HGH")) {
            return getSeatPositionHGH(bidType);
        } else if (theaterName.includes("BEJ")) {
            return getSeatPositionBEJ(bidType);
        } else if (theaterName.includes("MINILIVE")) {
            return getSeatPositionMiniLive(bidCount);
        } else if (theaterName.includes("拍立得")) {
            return getSeatPositionPLD(bidCount);
        } else if (theaterName.includes("生日会")) {
            return getSeatPositionBirthParty(bidCount);
        } else if (theaterName.includes("全纪录")) {
            return getSeatPositionXiezhen(bidCount);
        } else if (theaterName.includes("其它")) {
            return getSeatPositionOther(bidCount);
        }
    }

    // 特殊公演+兑换生公
    function getSeatPositionSNH(bidType) {
        // 根据竞价类型和索引为每个竞价分配座位号
        let seats = [];

        // 普座
        if (bidType === "普座") {
            // 6排1到6排18
            let rows6Col1To18 = [];
            for (let j = 1; j <= 18; j++) {
                rows6Col1To18.push(`6排${j}`);
            }
            // 5排18、19 | 6排18、19
            let rows5_6Col19To20 = [];
            for (let i = 5; i <= 6; i++) {
                for (let j = 19; j <= 20; j++) {
                    rows5_6Col19To20.push(`${i}排${j}`);
                }
            }
            // 4排21、22 | 5排21、22 | 6排21、22
            let rows4_6Col21To22 = [];
            for (let i = 4; i <= 6; i++) {
                for (let j = 21; j <= 22; j++) {
                    rows4_6Col21To22.push(`${i}排${j}`);
                }
            }
            // 3排23、24 | 4排23、24 | 5排23、24 | 6排23、24
            let rows3_6Col23To24 = [];
            for (let i = 3; i <= 6; i++) {
                for (let j = 23; j <= 24; j++) {
                    rows3_6Col23To24.push(`${i}排${j}`);
                }
            }
            // 7排到10排
            let rows7To10 = [];
            for (let i = 7; i <= 10; i++) {
                for (let j = 1; j <= 24; j++) {
                    rows7To10.push(`${i}排${j}`);
                }
            }

            seats = rows6Col1To18.concat(rows5_6Col19To20, rows4_6Col21To22, rows3_6Col23To24, rows7To10);
        }
        else if (bidType === "SVIP") {
            seats = [];
            for (let i = 1; i <= 24; i++) {
                seats.push(`1排${i}`);
            }
        }
        else if (bidType === "VIP") {
            seats = [];
            for (let i = 2; i <= 5; i++) {
                for (let j = 1; j <= (25 - 2 * (i - 2)); j++) {
                    seats.push(`${i}排${j}`);
                }
            }
        }
        else if (bidType === "摄影") {
            seats = [];
            for (let i = 1; i <= 24; i++) {
                seats.push(`1排${i}`);
            }
        }
        else if (bidType === "杆位") {
            seats = [];
            for (let i = 1; i <= 24; i++) {
                seats.push(`${i}`);
            }
        }
        else if (bidType === "普站") {
            seats = [];
            for (let i = 25; i <= 100; i++) {
                seats.push(`${i}`);
            }
        }
        else if (bidType === "超级") {
            seats = ["中", "左", "右"];
        }

        return seats;
    }

    // 上海剧场圈内生公
    function getSeatPositionSNHBirthday(bidType) {
        // 根据竞价类型和索引为每个竞价分配座位号
        let seats = [];

        // 普座
        if (bidType === "普座") {
            // 6排1到6排18
            let rows6Col1To18 = [];
            for (let j = 1; j <= 18; j++) {
                rows6Col1To18.push(`6排${j}`);
            }
            // 5排18、19 | 6排18、19
            let rows5_6Col19To20 = [];
            for (let i = 5; i <= 6; i++) {
                for (let j = 19; j <= 20; j++) {
                    rows5_6Col19To20.push(`${i}排${j}`);
                }
            }
            // 4排21、22 | 5排21、22 | 6排21、22
            let rows4_6Col21To22 = [];
            for (let i = 4; i <= 6; i++) {
                for (let j = 21; j <= 22; j++) {
                    rows4_6Col21To22.push(`${i}排${j}`);
                }
            }
            // 3排23、24 | 4排23、24 | 5排23、24 | 6排23、24
            let rows3_6Col23To24 = [];
            for (let i = 3; i <= 6; i++) {
                for (let j = 23; j <= 24; j++) {
                    rows3_6Col23To24.push(`${i}排${j}`);
                }
            }
            // 7排1、3、5、7、9、11、13、15、17、19
            let rows7Col1To19 = [];
            for (let j = 1; j <= 20; j++) {
                if (j % 2 !== 0) rows7Col1To19.push(`7排${j}`);
            }
            // 7排21、22、23、24
            let rows7Col21To24 = [];
            for (let j = 21; j <= 24; j++) {
                rows7Col21To24.push(`7排${j}`);
            }
            // 8排到10排
            let rows8To10 = [];
            for (let i = 8; i <= 10; i++) {
                for (let j = 1; j <= 24; j++) {
                    rows8To10.push(`${i}排${j}`);
                }
            }

            seats = rows6Col1To18.concat(rows5_6Col19To20, rows4_6Col21To22, rows3_6Col23To24, rows7Col1To19, rows7Col21To24, rows8To10);
        }
        else if (bidType === "VIP") {
            let rows2Col2To10 = [];
            for (let j = 1; j <= 10; j++) {
                if (j % 2 === 0) rows2Col2To10.push(`2排${j}`);
            }
            let rows2Col11To24 = [];
            for (let j = 11; j <= 24; j++) {
                rows2Col11To24.push(`2排${j}`);
            }
            let rows3To5 = [];
            for (let i = 3; i <= 5; i++) {
                for (let j = 1; j <= (25 - 2 * (i - 2)); j++) {
                    rows3To5.push(`${i}排${j}`);
                }
            }
            seats = rows2Col2To10.concat(rows2Col11To24, rows3To5);
        }
        else if (bidType === "摄影") {
            seats = [];
            for (let i = 1; i <= 24; i++) {
                seats.push(`1排${i}`);
            }
        }
        else if (bidType === "杆位") {
            seats = [];
            for (let i = 1; i <= 24; i++) {
                seats.push(`${i}`);
            }
        }
        else if (bidType === "普站") {
            let stand25To30 = [];
            for (let i = 25; i <= 30; i++) {
                stand25To30.push(`${i}`);
            }
            let stand31To100 = [];
            for (let i = 36; i <= 100; i++) {
                stand31To100.push(`${i}`);
            }
            seats = stand25To30.concat(stand31To100);
        }
        else if (bidType === "超级") {
            seats = ["中", "左", "右"];
        }

        return seats;
    }

    // 杭州剧场
    function getSeatPositionHGH(bidType) {
        // 根据竞价类型和索引为每个竞价分配座位号
        let seats = [];

        // 普座
        if (bidType === "超级") {
            // 1排1到1排25
            let rows1Col1To25 = [];
            for (let j = 1; j <= 25; j++) {
                rows1Col1To25.push(`1排${j}`);
            }
            // 2排1到2排29
            let rows2Col1To29 = [];
            for (let j = 1; j <= 29; j++) {
                rows2Col1To29.push(`2排${j}`);
            }
            seats = rows1Col1To25.concat(rows2Col1To29); // 普座座位
        }
        else if (bidType === "摄影") {
            // 摄影座位
            seats = [];
            for (let i = 1; i <= 19; i++) {
                seats.push(`11排${i}`);
            }
        }

        return seats;
    }

    // 北京剧场
    function getSeatPositionBEJ(bidType) {
        // 根据竞价类型和索引为每个竞价分配座位号
        let seats = [];
        // 普座
        if (bidType === "超级") {
            seats = ["中", "左", "右"];
        }
        else if (bidType === "VIP") {
            // VIP座位
            let rows1to4 = [];
            for (let i = 1; i <= 4; i++) {
                for (let j = 1; j <= 17; j++) {
                    rows1to4.push(`${i}排${j}`);
                }
            }
            let rows5 = ["5排3"];
            for (let j = 5; j <= 17; j++) {
                rows5.push(`5排${j}`);
            }
            let rows6 = ["6排13"];
            for (let j = 15; j <= 17; j++) {
                rows6.push(`6排${j}`);
            }
            seats = rows1to4.concat(rows5).concat(rows6); // 限制为84个
        }
        else if (bidType === "摄影") {
            seats = ["6排3", "6排6", "6排5", "6排8", "6排7", "6排10", "6排9", "6排12", "6排11", "6排14"];
        }
        return seats;
    }

    // MiniLice
    function getSeatPositionMiniLive(bidNumber) {
        // 根据竞价类型和索引为每个竞价分配座位号
        let seats = [];
        for (let i = 1; i <= bidNumber; i++) {
            seats.push(i.toString()); // MINILIVE座位
        }
        return seats;
    }

    // 拍立得
    function getSeatPositionPld(bidNumber) {
        // 根据竞价类型和索引为每个竞价分配座位号
        let seats = [];
        for (let i = 1; i <= bidNumber; i++) {
            seats.push(i.toString()); // 拍立得位置
        }
        return seats;
    }
    
    // 冷餐
    function getSeatPositionBirthParty(bidNumber) {
        // 根据竞价类型和索引为每个竞价分配座位号
        let seats = [];
        for (let i = 1; i <= bidNumber; i++) {
            seats.push(i.toString()); // 冷餐座位
        }
        return seats;
    }


    function calculateSeatCount(pattern1, pattern2, text) {
        // 匹配单一区间
        const matches1 = [...text.matchAll(pattern1)];
        // 匹配多个区间
        const matches2 = [...text.matchAll(pattern2)];

        let totalSeats = 0;

        // 如果有单一区间
        if (matches1.length > 0) {
            matches1.forEach(match => {
                const start = parseInt(match[1]);
                const end = parseInt(match[2]);
                totalSeats += (end - start + 1);
            });
        }
        // 如果有多个区间
        else if (matches2.length > 0) {
            matches2.forEach(match => {
                const start1 = parseInt(match[1]);
                const end1 = parseInt(match[2]);
                totalSeats += (end1 - start1 + 1);

                // 处理第二个区间(如果存在)
                if (match[3] && match[4]) {
                    const start2 = parseInt(match[3]);
                    const end2 = parseInt(match[4]);
                    totalSeats += (end2 - start2 + 1);
                }
            });
        }
        return totalSeats;
    }

    function getBidNumberSNH(bidType) {
        // 获取竞价区域的信息
        let itemInfo = "";
        const element = document.querySelector("#TabTab03Con1");
        itemInfo = element.textContent.trim();

        // 如果是“普站”
        if (bidType.includes("普站")) {
            const seatPattern1 = /站区序号(\d{3})至(\d{3})/g; // 单一区间(如:025至100)
            const seatPattern2 = /站区序号(\d{3})至(\d{2})(?:、(\d{2})至(\d{3}))?/g; // 多个区间(如:025至30、36至100)
            const seatCount = calculateSeatCount(seatPattern1, seatPattern2, itemInfo);
            return seatCount;
        }

        // 如果是生日会
        if (itemInfo.includes("生日潮流包")) {
            if (bidType.includes("SVIP")) return 24;
            if (bidType.includes("VIP")) return 79;
            if (bidType.includes("摄影")) return 24;
            if (bidType.includes("杆位")) return 24;
            if (bidType.includes("超级")) return 3;
            if (bidType.includes("普座")) return 122;
            return 0;
        } else {
            // 通用配置
            if (bidType.includes("SVIP")) return 24;
            if (bidType.includes("VIP")) return 84;
            if (bidType.includes("摄影")) return 24;
            if (bidType.includes("杆位")) return 24;
            if (bidType.includes("超级")) return 3;
            if (bidType.includes("普座")) return 132;
            return 0;
        }
    }

    function getBidNumberHGH(bidType) {
        if (bidType.includes("超级")) {
            return 54;
        } else if (bidType.includes("摄影")) {
            return 19;
        }
        return 0;
    }

    function getBidNumberBEJ() {
        let itemInfo = "";
        const element = document.querySelector("#TabTab03Con1");
        itemInfo = element.textContent.trim();

        // 使用正则表达式提取所有票数信息,只需包含 "演出门票"
        const ticketCounts = itemInfo.match(/.*?演出门票.*?(\d+)张/);

        // 如果找到了票数信息
        if (ticketCounts) {
            return parseInt(ticketCounts[1]); // 取第一个匹配的票数
        } else {
            return 0;
        }
    }

    function getBidNumberMiniLive() {
        let itemInfo = "";
        const element = document.querySelector("#TabTab03Con1");
        itemInfo = element.textContent.trim();

        // 使用正则表达式提取所有票数信息,只需包含 "入场资格"
        const ticketCounts = itemInfo.text.match(/入场资格(\d+)位/g);

        // 如果找到了票数信息
        if (ticketCounts) {
            return parseInt(ticketCounts[1]); // 取第一个匹配的票数
        } else {
            return 0;
        }
    }

    function getBidNumberPLD() {
        let itemInfo = "";
        const element = document.querySelector("#TabTab03Con1");
        itemInfo = element.textContent.trim();

        // 使用正则表达式提取所有票数信息,只需包含 "共"
        const ticketCounts = itemInfo.match(/.*?共.*?(\d+)套/);

        // 如果找到了票数信息
        if (ticketCounts) {
            return parseInt(ticketCounts[1]); // 取第一个匹配的票数
        } else {
            return 0;
        }
    }

    function getBidNumberBirthParty(theaterName) {
        let itemInfo = "";
        const element = document.querySelector("#TabTab03Con1");
        itemInfo = element.textContent.trim();

        if (theaterName.includes("SNH")) {
            // 使用正则表达式提取所有票数信息,只需包含 "名额"
            const ticketCounts = itemInfo.match(/.*?名额:.*?(\d+)名/);
            // 如果找到了票数信息
            if (ticketCounts) {
                return parseInt(ticketCounts[1]); // 取第一个匹配的票数
            } else {
                return 0;
            }
        } else if (theaterName.includes("BEJ")) {
            // 使用正则表达式提取所有票数信息,只需包含 "竞拍数量"
            const ticketCounts = itemInfo.match(/.*?竞拍数量:.*?(\d+)张/);
            // 如果找到了票数信息
            if (ticketCounts) {
                return parseInt(ticketCounts[1]); // 取第一个匹配的票数
            } else {
                return 0;
            }
        }
        return 0;
    }
    
    function getSeatPositionXiezhen(bidNumber) {
        const seats = [];
        for (let i = 1; i <= bidNumber; i++) {
            seats.push(String(i));
        }
        return seats;
    }
    
    function getSeatPositionOther(bidNumber) {
        const seats = [];
        for (let i = 1; i <= bidNumber; i++) {
            seats.push(String(i));
        }
        return seats;
    }




    function getItemId() {
        const url = window.location.href; // 获取当前页面的完整 URL
        const path = new URL(url).pathname; // 获取 URL 的路径部分
        const itemId = path.split('/').pop(); // 获取路径中的最后一部分作为 item_id
        return itemId;
    }

    function formatDate(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}`;
    }

    function updateMinMaxInfo(data, wsData) {
        // 过滤出 "竞价成功" 的记录
        const successfulBids = data.filter(bid => bid['出价状态'] === '竞价成功');

        // 转换 '出价时间' 为 Date 类型,并将 '出价金额' 转换为数字
        successfulBids.forEach(bid => {
            bid['出价时间'] = new Date(bid['出价时间']);
            bid['出价金额'] = parseFloat(bid['出价金额']);
        });

        // 提取最早出价时间、最晚出价时间、最高出价、最低出价
        const earliestBid = successfulBids.reduce((a, b) => (a['出价时间'] < b['出价时间'] ? a : b));
        const latestBid = successfulBids.reduce((a, b) => (a['出价时间'] > b['出价时间'] ? a : b));
        const highestBid = successfulBids.reduce((a, b) => (a['出价金额'] > b['出价金额'] ? a : b));
        const lowestBid = successfulBids.reduce((a, b) => (a['出价金额'] < b['出价金额'] ? a : b));

        // 插入数据到 wsData
        wsData.push([]); // 空行
        wsData.push(['最早出价者', earliestBid['出价人'], formatDate(earliestBid['出价时间']), earliestBid['出价金额'], earliestBid['座位类型'], earliestBid['座位号']]);
        wsData.push(['最晚出价者', latestBid['出价人'], formatDate(latestBid['出价时间']), latestBid['出价金额'], latestBid['座位类型'], latestBid['座位号']]);
        wsData.push(['最高出价者', highestBid['出价人'], formatDate(highestBid['出价时间']), highestBid['出价金额'], highestBid['座位类型'], highestBid['座位号']]);
        wsData.push(['最低出价者', lowestBid['出价人'], formatDate(lowestBid['出价时间']), lowestBid['出价金额'], lowestBid['座位类型'], lowestBid['座位号']]);

        //console.log("已更新最早、最晚、最高、最低出价信息");
        return wsData;
    }


    function saveExcel(successfulBidsData, itemName, outputFile = "bidding_results.xlsx") {
        // 创建 Excel 工作簿
        const wb = XLSX.utils.book_new();
        let wsData = [];

        // 写入商品名称
        wsData.push([itemName]);

        // 写入表头
        const header = ["出价状态", "出价人", "出价时间", "出价金额", "座位类型", "座位号"];
        wsData.push(header);

        // 写入竞价数据
        successfulBidsData.forEach(bid => {
            wsData.push([bid["出价状态"], bid["出价人"], bid["出价时间"], bid["出价金额"], bid["座位类型"], bid["座位号"]]);
        });

        wsData = updateMinMaxInfo(successfulBidsData, wsData)

        // 将数据转换为工作表
        const ws = XLSX.utils.aoa_to_sheet(wsData);

        // 格式化表头,设置加粗
        for (let i = 0; i < header.length; i++) {
            // eslint-disable-next-line dot-notation
            ws['A1'].font = { bold: true }; // 表头加粗
        }

        // 将工作表添加到工作簿
        XLSX.utils.book_append_sheet(wb, ws, "Bidding Results");

        // 生成 Excel 文件并下载
        const wbOut = XLSX.write(wb, { bookType: "xlsx", type: "array" });

        // 创建一个 Blob 对象并触发下载
        const blob = new Blob([wbOut], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = outputFile;
        link.click();

        console.log(`竞价信息已下载为 ${outputFile}`);
    }
    
    //霸屏信息框
    function showOverlayMessage() {
        let overlay = document.createElement("div");
        overlay.id = "tampermonkey-overlay";
        overlay.style.position = "fixed";
        overlay.style.top = "0";
        overlay.style.left = "0";
        overlay.style.width = "100vw";
        overlay.style.height = "100vh";
        overlay.style.backgroundColor = "rgba(0, 0, 0, 0.85)";
        overlay.style.color = "#fff";
        overlay.style.fontSize = "24px";
        overlay.style.zIndex = "99999";
        overlay.style.display = "flex";
        overlay.style.flexDirection = "column";
        overlay.style.justifyContent = "center";
        overlay.style.alignItems = "center";
    
        overlay.innerHTML = `
            <div id="overlay-message" style="margin-bottom: 20px;">脚本正在运行中,请稍候...</div>
            <button id="overlay-close-btn" style="
                display: none;
                padding: 10px 20px;
                font-size: 18px;
                background-color: #4CAF50;
                border: none;
                color: white;
                cursor: pointer;
                border-radius: 6px;
            ">关闭</button>
        `;
    
        document.body.appendChild(overlay);
    
        // 添加点击关闭功能
        document.getElementById("overlay-close-btn").addEventListener("click", hideOverlayMessage);
    }
    
    function updateOverlayMessage(msg) {
        const now = new Date().toLocaleTimeString();
        const messageElement = document.getElementById("overlay-message");
        if (messageElement) {
            messageElement.textContent = `[${now}] ${msg}`;
        }
    }
    
    function hideOverlayMessage() {
        const overlay = document.getElementById("tampermonkey-overlay");
        if (overlay) {
            overlay.remove();
        }
    }

    async function statsOneGood() {
        
        showOverlayMessage(); // 显示霸屏提示
        
        // 更新信息
        updateOverlayMessage("正在初始化脚本...");
        
        // 获取剧场名称和 Excel 文件名
        const theaterNameElement = document.querySelector("body > div:nth-child(2) > div > div:nth-child(2) > div:nth-child(2) > ul > li:nth-child(2) > p");
        const excelNameElement = document.querySelector("body > div.body-content > div > div:nth-child(2) > div.i_txt.ma_b10 > ul > li.i_tit");
        let theaterName = theaterNameElement ? theaterNameElement.textContent.trim() : "";
        const excelName = excelNameElement ? excelNameElement.textContent.trim() : "";

        // 获取商品名称
        const titleNameElement = document.querySelector("body > div.body-content > div > div:nth-child(2) > div.i_txt.ma_b10 > ul > li.i_tit");
        const titleName = titleNameElement ? titleNameElement.textContent.trim() : "";

        // 获取商品详细信息并判断是否为生日潮流包
        const itemInfoElement = document.querySelector("#TabTab03Con1");
        const itemInfoText = itemInfoElement ? itemInfoElement.innerText : "";
        const birthday = itemInfoText.includes("生日潮流包");

        // 获取竞价类型
        const bidType = getSeatType(titleName);

        let maxBidNum = 46;
        let bidNumber = 0;
        
        if (theaterName.includes("SNH") && titleName.includes("星梦剧院") && 
            !titleName.includes("MINILIVE") && titleName.includes("礼包")) {
            bidNumber = getBidNumberSNH(bidType);
            if (birthday) theaterName = "SNHbirthday";
        } else if (theaterName.includes("SNH") && titleName.includes("星梦空间") && 
                   !titleName.includes("MINILIVE") && titleName.includes("礼包")) {
            bidNumber = getBidNumberHGH(bidType);
            theaterName = "HGH";
        } else if (theaterName.includes("BEJ") && !titleName.includes("生日会")) {
            bidNumber = getBidNumberBEJ();
        } else if (titleName.includes("MINILIVE")) {
            bidNumber = getBidNumberMiniLive();
            theaterName = "MINILIVE";
        } else if (titleName.includes("拍立得") || titleName.includes("百场徽章粉丝纪念礼盒")) {
            bidNumber = getBidNumberPLD();
            theaterName = "拍立得";
        } else if (titleName.includes("生日会")) {
            bidNumber = getBidNumberBirthParty(theaterName);
            theaterName = "生日会";
        } else if (titleName.includes("全纪录")) {
            bidNumber = getBidNumberXiezhen();
            theaterName = "全纪录";
        } else {
            bidNumber = getBidNumberOther();
            theaterName = "其它";
        }
        
        // 获取座位数后更新显示
        updateOverlayMessage(`一共有 ${maxBidNum} 个位置,正在加载数据...`);
        console.log(`一共有 ${maxBidNum} 个位置`);

        let totalBidsData = []; // 用于存储所有竞价成功信息
        totalBidsData = await autoBidUntilEnd(maxBidNum, bidType, theaterName);

        //console.log(totalBidsData);
        // 获取商品 ID
        //const itemId = getItemId();
        
        updateOverlayMessage("完成数据处理,准备保存 Excel...");
        saveExcel(totalBidsData, excelName, excelName);
        
        // 更新提示信息
        updateOverlayMessage("数据已保存完毕,您可以点击下方按钮关闭提示框");
        
        // 显示关闭按钮
        const closeBtn = document.getElementById("overlay-close-btn");
        if (closeBtn) {
            closeBtn.style.display = "block";
        }
        
        // 设置自动关闭(60秒后)
        setTimeout(() => {
            hideOverlayMessage();
        }, 60000);
        
    }


    // 示例调用
    (async function() {
        // 创建按钮
        const button = document.createElement('button');
        button.innerText = '开始统计';
        button.style.position = 'absolute'; // 使用 absolute 定位
        button.style.zIndex = '1000';

        // 设置按钮样式
        //button.style.border = '2px solid black'; // 边框为黑色
        button.style.backgroundColor = '#59c4ec'; // 底色为蓝色
        button.style.color = 'white'; // 字体为黑色
        button.style.fontSize = '13px'; // 按钮字体大一号
        button.style.padding = '2px 5px'; // 添加内边距让按钮更美观
        button.style.cursor = 'pointer'; // 鼠标悬停时显示手型

        // 设置按钮点击事件
        button.onclick = function() {
            statsOneGood(); // 调用 statsOneGood 函数
        };

        // 获取目标元素
        const targetElement = document.querySelector('.i_tit');
        if (targetElement) {
            // 获取目标元素的位置
            const rect = targetElement.getBoundingClientRect();

            // 设置按钮的位置(上方展示)
            button.style.top = `${rect.top - 13}px`; // 距离目标元素上方 30px
            button.style.left = `${rect.left}px`; // 左对齐

            // 将按钮添加到页面上
            document.body.appendChild(button);
        } else {
            console.error('未找到目标元素 .i_tit');
        }
    })();
})();