Zacks网球场预定小助手(浏览器插件)

显示场地状态

// ==UserScript==
// @name         Zacks网球场预定小助手(浏览器插件)
// @namespace    http://zacks.com.cn/
// @version      0.1.1
// @description  显示场地状态
// @author       claude89757
// @match        *://*.ydmap.cn/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const log = (...args) => {
        console.log('%c[预订助手]', 'color: #1890ff', ...args);
    };

    function injectCode() {
        log('开始注入代码');

        const script = document.createElement('script');
        script.textContent = '(' + function() {
            const log = function(...args) {
                console.log('%c[预订助手]', 'color: #1890ff', ...args);
            };

            // 默认查询间隔时间(秒)
            let CHECK_INTERVAL = 180;

            // 查询间隔选项(秒)
            const CHECK_INTERVAL_OPTIONS = {
                '2分钟': 120,
                '3分钟': 180,
                '5分钟': 300,
                '10分钟': 600
            };

            // 统计数据对象
            const statistics = {
                runCount: 0,
                totalSlots: 0,
                availableSlots: 0,
                matchedSlots: 0,
                notificationCount: 0,
                venueCount: 0
            };

            // 声音控制状态
            const soundControl = {
                enabled: true
            };

            // 时间范围控制
            const timeRangeControl = {
                startTime: '18:00',
                endTime: '22:00'
            };

            // 场地名称映射表
            let venueMap = {};
            // 时间段配置
            let timeSlotConfig = [];
            // 场地列表
            let venueList = [];
            // 开放时间
            let openTime = 0;
            let closeTime = 0;

            // 将时间字符串转换为分钟数
            const timeToMinutes = (timeStr) => {
                const [hours, minutes] = timeStr.split(':').map(Number);
                return hours * 60 + minutes;
            };

            // 创建弹窗显示场地状态
            function showVenueStatus(bookedSlots) {
                log('[状态更新] 开始更新', { 数据条数: bookedSlots.length });

                if (!openTime || !closeTime || venueList.length === 0) {
                    log('[状态更新] 缺少开放时间或场地列表');
                    return;
                }

                // 生成所有可能的时间段
                const generateTimeSlots = (startTimestamp, endTimestamp, intervalMinutes) => {
                    const slots = [];
                    const intervalMillis = intervalMinutes * 60 * 1000;
                    for (let time = startTimestamp; time < endTimestamp; time += intervalMillis) {
                        slots.push({
                            startTime: time,
                            endTime: time + intervalMillis
                        });
                    }
                    return slots;
                };

                // 使用开放时间生成时间段
                const allSlots = [];
                const intervalMinutes = 30; // 时间间隔30分钟

                venueList.forEach(venueId => {
                    // 生成场地的所有时间段
                    const timeSlots = generateTimeSlots(openTime, closeTime, intervalMinutes);

                    timeSlots.forEach(slot => {
                        allSlots.push({
                            venueId: venueId,
                            startTime: slot.startTime,
                            endTime: slot.endTime,
                            booked: false
                        });
                    });
                });

                // 更新统计中的场地数量
                statistics.venueCount = venueList.length;

                // 创建已预订时间段的映射
                const bookedSlotsMap = {};
                bookedSlots.forEach(slot => {
                    if (!bookedSlotsMap[slot.venueId]) {
                        bookedSlotsMap[slot.venueId] = [];
                    }
                    bookedSlotsMap[slot.venueId].push({
                        startTime: slot.startTime,
                        endTime: slot.endTime
                    });
                });

                // 标记已预订的时间段
                allSlots.forEach(slot => {
                    const bookings = bookedSlotsMap[slot.venueId] || [];
                    for (const booking of bookings) {
                        if (slot.endTime > booking.startTime && slot.startTime < booking.endTime) {
                            slot.booked = true;
                            break;
                        }
                    }
                });

                statistics.totalSlots = allSlots.length;

                // 过滤可用的时间段
                const availableSlots = allSlots.filter(slot => !slot.booked);

                statistics.availableSlots = availableSlots.length;

                // 过滤符合时间范围的可用时间段
                const startMinutes = timeToMinutes(timeRangeControl.startTime);
                const endMinutes = timeToMinutes(timeRangeControl.endTime);

                const matchedSlots = [];
                const unmatchedSlots = [];

                availableSlots.forEach(slot => {
                    const slotStartTime = new Date(slot.startTime);
                    const slotMinutes = slotStartTime.getHours() * 60 + slotStartTime.getMinutes();

                    if (slotMinutes >= startMinutes && slotMinutes < endMinutes) {
                        matchedSlots.push(slot);
                    } else {
                        unmatchedSlots.push(slot);
                    }
                });

                statistics.matchedSlots = matchedSlots.length;

                // 更新场地信息显示
                const venueInfoContainer = document.getElementById('venue-info');
                if (venueInfoContainer) {
                    let venueInfoHTML = '';

                    if (matchedSlots.length > 0) {
                        venueInfoHTML += `<div style="font-weight: bold; margin-bottom: 8px;">符合时间范围的可预订场地:</div>`;
                        venueInfoHTML += matchedSlots.map(slot => {
                            const startTimeStr = new Date(slot.startTime).toLocaleTimeString('zh-CN', {
                                hour: '2-digit',
                                minute: '2-digit'
                            });
                            const endTimeStr = new Date(slot.endTime).toLocaleTimeString('zh-CN', {
                                hour: '2-digit',
                                minute: '2-digit'
                            });
                            const venueName = venueMap[slot.venueId] || `场地${slot.venueId}`;
                            const status = '可预订';

                            return `
                                <div style="margin-bottom: 8px; padding: 8px; background: white; border-radius: 4px; border: 1px solid #e8e8e8;">
                                    <div style="display: flex; justify-content: space-between;">
                                        <span style="color: #333; font-weight: bold;">${venueName}</span>
                                        <span style="color: #52c41a">${status}</span>
                                    </div>
                                    <div style="color: #666; margin-top: 4px;">
                                        时间:${startTimeStr} - ${endTimeStr}
                                    </div>
                                </div>
                            `;
                        }).join('');
                    } else {
                        venueInfoHTML += '<div style="text-align: center; color: #999; margin-bottom: 8px;">暂无符合时间范围的可预订场地</div>';
                    }

                    // 显示不符合时间范围的可预订场地
                    if (unmatchedSlots.length > 0) {
                        venueInfoHTML += `<div style="font-weight: bold; margin-bottom: 8px; margin-top: 16px;">不符合时间范围的可预订场地(用于调试):</div>`;
                        venueInfoHTML += unmatchedSlots.map(slot => {
                            const startTimeStr = new Date(slot.startTime).toLocaleTimeString('zh-CN', {
                                hour: '2-digit',
                                minute: '2-digit'
                            });
                            const endTimeStr = new Date(slot.endTime).toLocaleTimeString('zh-CN', {
                                hour: '2-digit',
                                minute: '2-digit'
                            });
                            const venueName = venueMap[slot.venueId] || `场地${slot.venueId}`;
                            const status = '可预订(不在时间范围内)';

                            return `
                                <div style="margin-bottom: 8px; padding: 8px; background: #fafafa; border-radius: 4px; border: 1px solid #e8e8e8;">
                                    <div style="display: flex; justify-content: space-between;">
                                        <span style="color: #333; font-weight: bold;">${venueName}</span>
                                        <span style="color: #d9d9d9">${status}</span>
                                    </div>
                                    <div style="color: #999; margin-top: 4px;">
                                        时间:${startTimeStr} - ${endTimeStr}
                                    </div>
                                </div>
                            `;
                        }).join('');
                    }

                    venueInfoContainer.innerHTML = venueInfoHTML;

                    // 发送通知和播放声音
                    if (matchedSlots.length > 0 && Notification.permission === "granted") {
                        matchedSlots.forEach(slot => {
                            const startTimeStr = new Date(slot.startTime).toLocaleTimeString('zh-CN', {
                                hour: '2-digit',
                                minute: '2-digit'
                            });
                            const endTimeStr = new Date(slot.endTime).toLocaleTimeString('zh-CN', {
                                hour: '2-digit',
                                minute: '2-digit'
                            });
                            const venueName = venueMap[slot.venueId] || `场地${slot.venueId}`;

                            const notificationOptions = {
                                body: `${venueName}\n可预订时间段:${startTimeStr} - ${endTimeStr}`,
                                icon: "/favicon.ico"
                            };
                            new Notification("【Zacks网球场预定小助手】", notificationOptions);
                        });
                        playNotificationSound();
                        statistics.notificationCount += matchedSlots.length;
                    }
                }

                // 更新统计信息
                statistics.runCount++;
            }

            // 创建状态弹窗函数
            function createStatusModal() {
                // 创建弹窗容器
                const modal = document.createElement('div');
                modal.style.cssText = `
                    position: fixed;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    background: white;
                    padding: 20px;
                    border-radius: 8px;
                    box-shadow: 0 2px 10px rgba(0,0,0,0.2);
                    z-index: 9999;
                    min-width: 500px;
                    max-height: 80vh;
                    overflow-y: auto;
                `;

                // 创建标题容器
                const titleContainer = document.createElement('div');
                titleContainer.style.cssText = `
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    margin-bottom: 15px;
                `;

                const title = document.createElement('h2');
                title.textContent = 'Zacks网球场预定小助手(插件版)';
                title.style.margin = '0';
                title.style.fontSize = '20px';

                const countdown = document.createElement('span');
                countdown.style.cssText = `
                    color: #999;
                    font-size: 14px;
                `;

                titleContainer.appendChild(title);
                titleContainer.appendChild(countdown);
                modal.appendChild(titleContainer);

                // 添加时间范围和巡检周期选择容器
                const settingsContainer = document.createElement('div');
                settingsContainer.style.cssText = `
                    margin-bottom: 15px;
                    padding: 10px;
                    background: #f0f5ff;
                    border-radius: 4px;
                `;

                const settingsTitle = document.createElement('div');
                settingsTitle.textContent = '场地时间范围设置 & 巡检周期';
                settingsTitle.style.cssText = 'margin-bottom: 10px; font-weight: bold;';
                settingsContainer.appendChild(settingsTitle);

                const settingsInputContainer = document.createElement('div');
                settingsInputContainer.style.cssText = `
                    display: flex;
                    align-items: center;
                    gap: 10px;
                `;

                // 创建开始时间选择
                const startTimeInput = document.createElement('input');
                startTimeInput.type = 'time';
                startTimeInput.value = timeRangeControl.startTime;
                startTimeInput.min = '07:00';
                startTimeInput.max = '23:00';
                startTimeInput.style.cssText = `
                    padding: 4px;
                    border: 1px solid #d9d9d9;
                    border-radius: 4px;
                `;
                startTimeInput.onchange = () => {
                    timeRangeControl.startTime = startTimeInput.value;
                };

                // 创建结束时间选择
                const endTimeInput = document.createElement('input');
                endTimeInput.type = 'time';
                endTimeInput.value = timeRangeControl.endTime;
                endTimeInput.min = '07:00';
                endTimeInput.max = '23:00';
                endTimeInput.style.cssText = startTimeInput.style.cssText;
                endTimeInput.onchange = () => {
                    timeRangeControl.endTime = endTimeInput.value;
                };

                // 建巡检周期选择
                const intervalSelect = document.createElement('select');
                intervalSelect.style.cssText = `
                    padding: 4px;
                    border: 1px solid #d9d9d9;
                    border-radius: 4px;
                `;
                for (const [label, value] of Object.entries(CHECK_INTERVAL_OPTIONS)) {
                    const option = document.createElement('option');
                    option.value = value;
                    option.textContent = label;
                    intervalSelect.appendChild(option);
                }
                intervalSelect.value = CHECK_INTERVAL;
                intervalSelect.onchange = () => {
                    CHECK_INTERVAL = parseInt(intervalSelect.value, 10);
                    restartCheckInterval();
                };

                settingsInputContainer.appendChild(document.createTextNode('从'));
                settingsInputContainer.appendChild(startTimeInput);
                settingsInputContainer.appendChild(document.createTextNode('到'));
                settingsInputContainer.appendChild(endTimeInput);
                settingsInputContainer.appendChild(document.createTextNode('巡检周期'));
                settingsInputContainer.appendChild(intervalSelect);
                settingsContainer.appendChild(settingsInputContainer);
                modal.appendChild(settingsContainer);

                // 创建统计信息容器
                const statsContainer = document.createElement('div');
                statsContainer.style.cssText = `
                    margin-bottom: 15px;
                    padding: 10px;
                    background: #f5f5f5;
                    border-radius: 4px;
                `;

                statsContainer.innerHTML = `
                    <div style="font-weight: bold; margin-bottom: 10px;">统计信息</div>
                    <ul style="list-style: none; padding: 0; margin: 0; color: #333;">
                        <li>已运行次数:${statistics.runCount} 次</li>
                        <li>巡检场地数量:${statistics.venueCount} 个</li>
                        <li>可预订的时间段:${statistics.matchedSlots} 个</li>
                        <li>已发送通知:${statistics.notificationCount} 次</li>
                    </ul>
                `;
                modal.appendChild(statsContainer);

                // 添加场地信息显示区域
                const venueInfoContainer = document.createElement('div');
                venueInfoContainer.id = 'venue-info';
                venueInfoContainer.style.cssText = `
                    margin-bottom: 15px;
                    padding: 10px;
                    background: #f6ffed;
                    border-radius: 4px;
                    max-height: 300px;
                    overflow-y: auto;
                `;
                venueInfoContainer.innerHTML = '<div style="text-align: center; color: #999;">暂无符合条件的场地</div>';
                modal.appendChild(venueInfoContainer);

                // 添加定时任务状态显示
                const taskStatus = document.createElement('div');
                taskStatus.style.cssText = `
                    margin-bottom: 15px;
                    padding: 10px;
                    background: #fffbe6;
                    border-radius: 4px;
                    font-size: 14px;
                    color: #666;
                `;

                const taskCountdown = document.createElement('div');
                const nextCheckTime = document.createElement('div');

                taskStatus.appendChild(taskCountdown);
                taskStatus.appendChild(nextCheckTime);
                modal.appendChild(taskStatus);

                // 添加描述信息容器
                const descContainer = document.createElement('div');
                descContainer.style.cssText = `
                    margin-bottom: 15px;
                    padding: 10px;
                    background: #e6f7ff;
                    border-radius: 4px;
                    color: #666;
                    font-size: 14px;
                    line-height: 1.5;
                `;
                descContainer.innerHTML = `
                    <div style="font-weight: bold; margin-bottom: 10px;">小助手说明:</div>
                    <ul style="list-style: disc; padding-left: 20px; margin: 0;">
                        <li>自动查询周期:每 ${CHECK_INTERVAL} 秒查询一次</li>
                        <li>运行时长:8 小时后自动关闭</li>
                        <li>发现可预订场地时会自动发送系统通知</li>
                        <li>仅通知可预订场地,不支持自动订场,请勿滥用</li>
                    </ul>
                `;
                modal.appendChild(descContainer);

                // 添加作者信息
                const authorInfo = document.createElement('div');
                authorInfo.style.cssText = `
                    margin-bottom: 15px;
                    padding: 10px;
                    background: #f0f0f0;
                    border-radius: 4px;
                    color: #666;
                    font-size: 14px;
                    line-height: 1.5;
                `;
                authorInfo.innerHTML = `
                    <div style="font-weight: bold; margin-bottom: 10px;">作者信息:</div>
                    <ul style="list-style: none; padding-left: 0; margin: 0;">
                        <li>邮箱:<a href="mailto:claude89757@gmail.com">claude89757@gmail.com</a></li>
                        <li>微信:claude89757</li>
                    </ul>
                `;
                modal.appendChild(authorInfo);

                // 更新统计信息的函数
                function updateStats() {
                    statsContainer.innerHTML = `
                        <div style="font-weight: bold; margin-bottom: 10px;">统计信息</div>
                        <ul style="list-style: none; padding: 0; margin: 0; color: #333;">
                            <li>已运行次数:${statistics.runCount} 次</li>
                            <li>巡检场地数量:${statistics.venueCount} 个</li>
                            <li>可预订的时间段:${statistics.matchedSlots} 个</li>
                            <li>已发送通知:${statistics.notificationCount} 次</li>
                        </ul>
                    `;
                }

                // 存储所有定时器的ID
                const timers = {
                    countdown: null,
                    taskStatus: null,
                    stats: null
                };

                // 关闭弹窗函数
                const closeModal = () => {
                    if (document.body.contains(modal)) {
                        // 清除所有定时器
                        clearInterval(timers.stats);
                        clearTimeout(timers.countdown);
                        clearTimeout(timers.taskStatus);
                        clearInterval(window.checkInterval); // 清除主定时查询任务

                        // 移除弹窗
                        document.body.removeChild(modal);

                        // 移除按钮容器
                        const buttonContainer = document.querySelector('#test-sound-button') && document.querySelector('#test-sound-button').parentElement;
                        if (buttonContainer) {
                            buttonContainer.remove();
                        }

                        log('[系统] 已停止所有定时任务');
                    }
                };

                // 倒计时更新函数
                let remainingSeconds = 28800;
                const updateCountdown = () => {
                    const hours = Math.floor(remainingSeconds / 3600);
                    const minutes = Math.floor((remainingSeconds % 3600) / 60);
                    const seconds = remainingSeconds % 60;
                    countdown.textContent = `${hours}小时${minutes}分${seconds}秒后自动关闭`;
                    if (remainingSeconds > 0) {
                        remainingSeconds--;
                        timers.countdown = setTimeout(updateCountdown, 1000);
                    }
                };

                // 定时任务状态更新函数
                let taskRemainingSeconds = CHECK_INTERVAL;
                const updateTaskStatus = () => {
                    if (!document.body.contains(modal)) return;

                    taskCountdown.textContent = `距离下次查询还有:${taskRemainingSeconds} 秒`;
                    nextCheckTime.textContent = `下次查询时间:${new Date(Date.now() + taskRemainingSeconds * 1000).toLocaleTimeString()}`;

                    if (taskRemainingSeconds > 0) {
                        taskRemainingSeconds--;
                        timers.taskStatus = setTimeout(updateTaskStatus, 1000);
                    } else {
                        taskRemainingSeconds = CHECK_INTERVAL;
                        timers.taskStatus = setTimeout(updateTaskStatus, 1000);
                    }
                };

                // 启动统计信息更新定时器
                timers.stats = setInterval(updateStats, 1000);

                // 启动倒计时显示
                updateCountdown();
                updateTaskStatus();

                // 添加关闭按钮
                const closeBtn = document.createElement('button');
                closeBtn.textContent = '关闭小助手';
                closeBtn.style.cssText = `
                    padding: 6px 15px;
                    background: #1890ff;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    cursor: pointer;
                    display: block;
                    margin: 0 auto;
                `;

                closeBtn.onclick = closeModal;
                modal.appendChild(closeBtn);

                // 确保在添加到body之前所有元素都已经正确添加到modal中
                document.body.appendChild(modal);

                // 8小时后自动关闭 (28800000毫秒)
                setTimeout(closeModal, 28800000);
            }

            // 在脚本初始化时请求通知权限
            function requestNotificationPermission() {
                if (Notification.permission !== "granted") {
                    Notification.requestPermission().then(permission => {
                        if (permission === "granted") {
                            log('[通知权限] 通知权限已授予');
                        } else {
                            log('[通知权限] 通知权限被拒绝');
                        }
                    });
                } else {
                    log('[通知权限] 通知权限已授予');
                }
            }

            // 播放通知声音
            function playNotificationSound() {
                if (!soundControl.enabled) {
                    log('[声音通知] 声音已禁用');
                    return;
                }

                const audio = new Audio('https://ws.stream.qqmusic.qq.com/C400004GoXJd3h7s9N.m4a?guid=488327575&vkey=CD36A32A75F8D9347CB9C6B9E3255F0D247557B57641D13DCEEA9AC789A57180900386ACE588237776C016B601C405B8C581FD784E38B1C6&uin=&fromtag=120032');
                audio.play().then(() => {
                    log('[声音通知] 声音播放成功');
                }).catch(error => {
                    log('[声音通知] 声音播放失败:', error);
                });
            }

            // 查找Vue实例的函数
            function findVueInstance() {
                const elements = document.querySelectorAll('*');
                for (const el of elements) {
                    if (el.__vue__) {
                        const vm = el.__vue__;
                        if (vm.onSelect || vm.$refs.scheduleTable) {
                            log('找到目标Vue实例');
                            return vm;
                        }
                        if (vm.$children) {
                            for (const child of vm.$children) {
                                if (child.onSelect || child.$refs.scheduleTable) {
                                    log('在子组件中找到目标Vue实例');
                                    return child;
                                }
                            }
                        }
                    }
                }
                return null;
            }

            // 劫持组件方法
            function hackComponent(vm) {
                if (!vm || vm._hacked) return;

                const scheduleTable = vm.$refs.scheduleTable || vm;

                // 保存原始方法
                const originalMethods = {
                    isAvailable: scheduleTable.isAvailable,
                    isAvailableStatic: scheduleTable.isAvailableStatic,
                    check: scheduleTable.check,
                    loadSchaduleServerData: scheduleTable.loadSchaduleServerData
                };

                // 劫持方法
                const methods = {
                    isAvailable: () => true,
                    isAvailableStatic: () => true,
                    check: async() => true
                };

                // 应用劫持
                Object.keys(methods).forEach(key => {
                    if (scheduleTable[key]) {
                        scheduleTable[key] = methods[key].bind(scheduleTable);
                    }
                });

                // 劫持计算属性
                Object.defineProperty(scheduleTable, 'canNext', {
                    get: () => true,
                    configurable: true
                });

                vm._hacked = true;
                log('组件劫持完成');
            }

            // 添加声音控制按钮
            function addSoundButton() {
                const buttonContainer = document.createElement('div');
                buttonContainer.style.cssText = `
                    position: fixed;
                    bottom: 20px;
                    right: 20px;
                    z-index: 9999;
                    display: flex;
                    flex-direction: column;
                    gap: 10px;
                `;

                // 测试声音按钮
                const playSoundButton = document.createElement('button');
                playSoundButton.id = 'test-sound-button';
                playSoundButton.textContent = '测试通知声音';
                playSoundButton.style.cssText = `
                    padding: 6px 15px;
                    background: #1890ff;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    cursor: pointer;
                `;

                // 声音开关按钮
                const toggleSoundButton = document.createElement('button');
                toggleSoundButton.id = 'toggle-sound-button';
                toggleSoundButton.style.cssText = `
                    padding: 6px 15px;
                    background: #52c41a;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    cursor: pointer;
                `;

                // 更新按钮文本的函数
                const updateToggleButtonText = () => {
                    toggleSoundButton.textContent = soundControl.enabled ? '声音提醒:开' : '声音提醒:关';
                    toggleSoundButton.style.background = soundControl.enabled ? '#52c41a' : '#ff4d4f';
                };

                playSoundButton.onclick = () => {
                    playNotificationSound();
                };

                toggleSoundButton.onclick = () => {
                    soundControl.enabled = !soundControl.enabled;
                    updateToggleButtonText();
                    log('[声音控制] 声音提醒已' + (soundControl.enabled ? '开启' : '关闭'));
                };

                // 初始化按钮文本
                updateToggleButtonText();

                buttonContainer.appendChild(toggleSoundButton);
                buttonContainer.appendChild(playSoundButton);

                // 等待 DOM 加载完成后再添加按钮
                if (document.body) {
                    document.body.appendChild(buttonContainer);
                } else {
                    document.addEventListener('DOMContentLoaded', () => {
                        document.body.appendChild(buttonContainer);
                    });
                }
            }

            // 监听请求的函数
            function monitorRequests() {
                const originalOpen = XMLHttpRequest.prototype.open;
                const originalSend = XMLHttpRequest.prototype.send;

                // 添加定时任务函数
                function scheduleCheck() {
                    const today = new Date();
                    const timeStr = today.toLocaleTimeString();

                    log(`[定时任务] ${timeStr} 开始执行场地查询...`);

                    // 查找Vue实例
                    const vm = findVueInstance();
                    if (!vm) {
                        log('[定时任务] 未找到Vue实例,跳过查询');
                        return;
                    }

                    // 使用原有的请求方法
                    try {
                        // 获取原始请求方法
                        const originRequest = vm.$refs.scheduleTable && vm.$refs.scheduleTable.loadSchaduleServerData;

                        if (typeof originRequest === 'function') {
                            log('[定时任务] 使用原有请求方法');
                            originRequest.call(vm.$refs.scheduleTable, () => {
                                log('[定时任务] 请求完成回调');
                            });
                        } else {
                            log('[定时任务] 未找到原有请求方法');
                        }
                    } catch (error) {
                        log('[定时任务] 执行出错:', error);
                    }
                }

                // 设置定时任务,并将定时器ID存储在window对象中
                log(`[系统] 启动定时查询任务 (间隔:${CHECK_INTERVAL} 秒)`);
                window.checkInterval = setInterval(() => {
                    scheduleCheck();
                    if (window.taskRemainingSeconds !== undefined) {
                        window.taskRemainingSeconds = CHECK_INTERVAL;
                    }
                }, CHECK_INTERVAL * 1000);

                // 重新启动定时任务函数
                function restartCheckInterval() {
                    if (window.checkInterval) {
                        clearInterval(window.checkInterval);
                    }
                    log(`[系统] 重启定时查询任务 (间隔:${CHECK_INTERVAL} 秒)`);
                    window.checkInterval = setInterval(() => {
                        scheduleCheck();
                        if (window.taskRemainingSeconds !== undefined) {
                            window.taskRemainingSeconds = CHECK_INTERVAL;
                        }
                    }, CHECK_INTERVAL * 1000);
                }

                // 保持原有的请求监听代码
                XMLHttpRequest.prototype.open = function(method, url, ...rest) {
                    if (url.includes('/pub/sport/venue/getVenueOrderList')) {
                        this._isVenueOrderRequest = true;
                        log('[请求监听] 检测到场地状态查询请求:', { method, url });
                    }
                    if (url.includes('/pub/sport/venue/getSportVenueConfig')) {
                        this._isVenueConfigRequest = true;
                        log('[请求监听] 检测到场地配置请求:', { method, url });
                    }
                    return originalOpen.call(this, method, url, ...rest);
                };

                XMLHttpRequest.prototype.send = function(body) {
                    if (this._isVenueOrderRequest || this._isVenueConfigRequest) {
                        log('[请求监听] 发送请求数据:', body ? JSON.parse(body) : null);

                        this.addEventListener('load', function() {
                            try {
                                const response = JSON.parse(this.responseText);
                                if (this._isVenueConfigRequest) {
                                    log('[请求监听] 收到场地配置响应');
                                    if (response.data) {
                                        if (response.data.venueResponses) {
                                            response.data.venueResponses.forEach(venue => {
                                                venueMap[venue.venueId] = venue.venueName;
                                            });
                                            venueList = response.data.venueResponses.map(v => v.venueId);
                                            log('[请求监听] 更新场地名称映射表:', venueMap);
                                            log('[请求监听] 更新场地列表:', venueList);
                                        }
                                        if (response.data.venueTimeSlotResponses) {
                                            timeSlotConfig = response.data.venueTimeSlotResponses;
                                            log('[请求监听] 更新时间段配置:', timeSlotConfig);

                                            // 设置开放时间
                                            const startTimes = timeSlotConfig.map(slot => slot.startTime);
                                            const endTimes = timeSlotConfig.map(slot => slot.endTime);
                                            openTime = Math.min(...startTimes);
                                            closeTime = Math.max(...endTimes);
                                            log('[请求监听] 更新开放时间:', { openTime: new Date(openTime).toLocaleTimeString(), closeTime: new Date(closeTime).toLocaleTimeString() });
                                        }
                                    }
                                } else if (this._isVenueOrderRequest) {
                                    log('[请求监听] 收到场地状态响应:', {
                                        code: response.code,
                                        message: response.message,
                                        场地数量: response.data ? (response.data.length || 0) : 0
                                    });

                                    if (response.data && response.data.length >= 0) {
                                        if (!window.__modalCreated) {
                                            log('[请求监听] 首次加载,创建弹窗');
                                            window.__modalCreated = true;
                                            createStatusModal();
                                        }
                                        showVenueStatus(response.data);
                                    }
                                }
                            } catch (err) {
                                log('[请求监听] 处理响应失败:', err);
                            }
                        });
                    }
                    return originalSend.call(this, body);
                };

                // 在开始监听请求之前添加按钮
                addSoundButton();
            }

            // 定时检查并劫持组件
            const hackInterval = setInterval(() => {
                const vm = findVueInstance();
                if (vm) {
                    hackComponent(vm);
                    if (vm._hacked) {
                        clearInterval(hackInterval);
                    }
                }
            }, 1000);

            // 开始监听请
            monitorRequests();

            // 在脚本初始化时请求通知权限
            requestNotificationPermission();
        } + ')();';

        // 注入样式
        const style = document.createElement('style');
        style.textContent = `
            .schedule-table td,
            .schedule-table td.col-booking-disabled,
            .schedule-table td.noBook,
            .schedule-table td.expired {
                cursor: default !important;
                opacity: 1 !important;
                pointer-events: none !important;
                background-color: white !important;
            }

            .schedule-table td.col-scheduled {
                background-color: #ffccc7 !important;
            }

            .primary-button,
            .primary-button[disabled] {
                opacity: 0.5 !important;
                cursor: not-allowed !important;
                pointer-events: none !important;
                background: #ccc !important;
            }
        `;

        (document.head || document.documentElement).appendChild(script);
        (document.head || document.documentElement).appendChild(style);

        log('代码注入完成');
    }

    injectCode();
})();