您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
监控征纳互动等待人数变化,是否离线,并进行语音提示,带折叠面板
// ==UserScript== // @name 征纳互动人数和在线监控 // @namespace http://tampermonkey.net/ // @version 25.7.22 // @description 监控征纳互动等待人数变化,是否离线,并进行语音提示,带折叠面板 // @author runos // @match https://znhd.hunan.chinatax.gov.cn:8443/* // @icon data:image/png;base64,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 // @grant GM_addStyle // @grant unsafeWindow // @homepage https://scriptcat.org/zh-CN/script-show-page/3650 // @license MIT // ==/UserScript== (function () { 'use strict'; // 添加自定义样式 GM_addStyle(` #monitorLogContainer { position: fixed; top: 10px; /* 减小 top 值,将面板向上移动 */ right: 500px; width: 300px; background: #ffffff; /* 更纯净的白色背景 */ border: none; /* 移除边框 */ border-radius: 12px; /* 更大的圆角 */ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); /* 更柔和的阴影 */ z-index: 9999; font-family: 'Segoe UI', Roboto, sans-serif; /* 更现代的字体 */ max-height: 250px; overflow: hidden; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* 更顺滑的过渡效果 */ } #monitorLogContainer.collapsed { max-height: none; /* 移除最大高度限制,仅折叠日志部分 */ overflow: hidden; } #monitorLogHeader { background-color: #f5f5f5; /* 更柔和的头部背景色 */ padding: 10px; /* 调小头部内边距 */ border-bottom: none; /* 移除边框 */ display: flex; justify-content: space-between; align-items: center; cursor: pointer; border-top-left-radius: 12px; border-top-right-radius: 12px; font-size: 12px; /* 调小头部字体大小 */ } #monitorLogs { padding: 16px; font-size: 14px; /* 更大的字体 */ max-height: 200px; overflow-y: auto; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* 更顺滑的过渡效果 */ } #monitorLogContainer.collapsed #monitorLogs { display: none; } .log-item { padding: 8px 0; border-bottom: 1px solid #eeeeee; /* 更柔和的分割线 */ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .log-warning { color: #ff9800; /* 更鲜明的警告色 */ } .log-info { color: #2196f3; /* 更鲜明的信息色 */ } .log-success { color: #4caf50; /* 更鲜明的成功色 */ } #toggleCollapseBtn, #toggleVoiceBtn { background: #2196f3; /* 统一按钮颜色 */ color: white; border: none; padding: 4px 10px; /* 调小按钮内边距 */ border-radius: 8px; /* 按钮圆角 */ cursor: pointer; font-size: 14px; margin-left: 8px; transition: background 0.2s; } #toggleCollapseBtn:hover, #toggleVoiceBtn.voice-enabled { background: #2196f3; /* 语音开启时的蓝色 */ } #toggleVoiceBtn.voice-disabled { background: #f44336; /* 语音关闭时的红色 */ } #toggleVoiceBtn:hover.voice-enabled { background: #1976d2; /* 语音开启时的深蓝色悬停效果 */ } #toggleVoiceBtn:hover.voice-disabled { background: #d32f2f; /* 语音关闭时的深红色悬停效果 */ } #toggleCollapseBtn { background: #9e9e9e; /* 折叠按钮颜色 */ } #toggleCollapseBtn:hover { background: #757575; /* 折叠按钮悬停颜色 */ } #monitorTitle { margin: 0; display: flex; align-items: center; color: #212121; /* 更暗的标题颜色 */ font-size: 12px; /* 调小标题字体大小 */ } .collapse-icon { margin-right: 12px; font-size: 16px; /* 更大的图标 */ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); } #monitorLogContainer.collapsed .collapse-icon { transform: rotate(-90deg); } .control-buttons { display: flex; } `); // 语音播报开关 (true: 开启语音, false: 静音) let voiceEnabled = true; // 面板折叠状态 let panelCollapsed = false; // 存储日志条目 const logEntries = []; // 配置对象,集中管理可配置项 const CONFIG = { // 检查间隔(毫秒) CHECK_INTERVAL: 3000, // 最大日志条目数 MAX_LOG_ENTRIES: 5, WORKING_HOURS: { MORNING: { START: 9, END: 12 }, AFTERNOON: { START: 13.5, END: 18 } } }; let offlineNotifyCount = 0; let lastOfflineStatus = false; // 记录上次的离线状态 // 工具函数:获取当前小时(支持小数) function getCurrentHour() { const now = new Date(); return now.getHours() + now.getMinutes() / 60; } // 检查是否在工作时间内 function isWorkingHours() { const currentHour = getCurrentHour(); return (currentHour >= CONFIG.WORKING_HOURS.MORNING.START && currentHour <= CONFIG.WORKING_HOURS.MORNING.END) || (currentHour >= CONFIG.WORKING_HOURS.AFTERNOON.START && currentHour <= CONFIG.WORKING_HOURS.AFTERNOON.END); } // 修改主要检测函数 function checkCount() { if (!isWorkingHours()) { addLog('当前不在工作时间,已停止脚本', 'warning'); return; } try { // 获取等待人数 const ocurrentElement = document.querySelector('.count:nth-child(2)'); if (!ocurrentElement) { addLog('找不到人数元素', 'warning'); speak("找不到人数元素"); return; } const currentCount = parseInt(ocurrentElement.textContent.trim()); // 检查currentCount是否为有效数字 if (isNaN(currentCount)) { addLog('无法解析等待人数', 'warning'); speak("无法解析等待人数"); return; } if (currentCount === 0) { addLog('当前等待人数为0', 'success'); } else if (currentCount < 5) { // 使用具体数字替代length比较 addLog(`当前等待人数: ${currentCount}`, 'info'); speak("征纳互动有人来了"); } // 检查离线状态 const offlineElement = document.querySelector('.t-dialog__body__icon:nth-child(2)'); const isCurrentlyOffline = offlineElement && offlineElement.textContent.trim().includes('离线'); if (isCurrentlyOffline) { // 系统当前离线 if (!lastOfflineStatus) { // 刚刚从在线变为离线 offlineNotifyCount = 1; // 直接设置为1,表示第一次提醒 addLog('检测到系统刚刚离线 (第1次提醒)', 'warning'); speak("征纳互动已离线"); } else { // 持续离线状态 if (offlineNotifyCount < 5) { offlineNotifyCount++; addLog(`征纳互动已离线 (第${offlineNotifyCount}次提醒)`, 'warning'); speak("征纳互动已离线"); } else if (offlineNotifyCount === 5) { // 第一次达到5次通知限制时,记录暂停提醒的日志 addLog('离线:已通知5次,暂停语音提醒', 'warning'); offlineNotifyCount++; // 增加计数,避免重复记录此日志 } else { // 超过5次后,静默计数,不记录日志也不语音提醒 offlineNotifyCount++; } } lastOfflineStatus = true; } else { // 系统当前在线 if (lastOfflineStatus) { // 刚刚从离线变为在线 addLog('系统已重新上线', 'success'); speak("征纳互动已重新上线"); offlineNotifyCount = 0; // 重置计数器 } lastOfflineStatus = false; } } catch (error) { addLog(`检测错误: ${error.message}`, 'warning'); } } const speechQueue = []; let isSpeaking = false; let voicesReady = false; // 确保语音加载完成 speechSynthesis.onvoiceschanged = () => { voicesReady = !!speechSynthesis.getVoices().length; }; let firstSpeak = true; function speak(text) { if (firstSpeak && !voicesReady) { const checkVoices = () => { if (voicesReady) { firstSpeak = false; addLog('语音首次加载完成,可以开始播报。', 'info'); speak(text); } else { setTimeout(checkVoices, 100); addLog("等待语音加载完成", "info"); } }; checkVoices(); return; } if (!voiceEnabled || !('speechSynthesis' in window) || !voicesReady) return; const utterance = new SpeechSynthesisUtterance(text); utterance.lang = 'zh-CN'; utterance.rate = 1.0; speechQueue.push(utterance); processSpeechQueue(); } function processSpeechQueue() { if (isSpeaking || speechQueue.length === 0 || !voiceEnabled) return; isSpeaking = true; const utterance = speechQueue.shift(); utterance.onend = utterance.onerror = (e) => { isSpeaking = false; processSpeechQueue(); }; window.speechSynthesis.speak(utterance); } // 添加日志条目 function addLog(message, type = 'info') { const timestamp = new Date().toTimeString().slice(0, 8); const logItem = { timestamp, message, type }; logEntries.unshift(logItem); if (logEntries.length > CONFIG.MAX_LOG_ENTRIES) { logEntries.pop(); } updateLogDisplay(); console.log(`[监控] ${timestamp} ${message}`); } // 更新日志显示(使用文档片段优化性能) function updateLogDisplay() { const logContainer = document.getElementById('monitorLogs'); if (!logContainer) return; const fragment = document.createDocumentFragment(); logEntries.forEach(log => { const logElement = document.createElement('div'); logElement.className = `log-item log-${log.type}`; logElement.innerHTML = `<strong>${log.timestamp}</strong> - ${log.message}`; fragment.appendChild(logElement); }); logContainer.innerHTML = ''; logContainer.appendChild(fragment); } // 切换面板折叠状态 function togglePanel() { const container = document.getElementById('monitorLogContainer'); if (!container) return; panelCollapsed = !panelCollapsed; if (panelCollapsed) { container.classList.add('collapsed'); } else { container.classList.remove('collapsed'); } // 更新折叠按钮文本 const collapseBtn = document.getElementById('toggleCollapseBtn'); if (collapseBtn) { collapseBtn.textContent = panelCollapsed ? '展开面板' : '折叠面板'; } } // 创建控制面板 function createControlPanel() { const panel = document.createElement('div'); panel.id = 'monitorLogContainer'; // 面板头部 const header = document.createElement('div'); header.id = 'monitorLogHeader'; // 标题区域(可点击折叠) const titleArea = document.createElement('div'); titleArea.style.display = 'flex'; titleArea.style.alignItems = 'center'; const title = document.createElement('h4'); title.id = 'monitorTitle'; title.innerHTML = '<span class="collapse-icon">▼</span> 征纳互动监控'; titleArea.appendChild(title); header.appendChild(titleArea); // 按钮容器 const btnContainer = document.createElement('div'); btnContainer.className = 'control-buttons'; // 语音开关 const toggleBtn = document.createElement('button'); toggleBtn.id = 'toggleVoiceBtn'; toggleBtn.textContent = voiceEnabled ? '🔊 语音' : '🔇 静音'; toggleBtn.className = voiceEnabled ? 'voice-enabled' : 'voice-disabled'; toggleBtn.title = voiceEnabled ? '关闭语音提示' : '开启语音提示'; toggleBtn.onclick = (e) => { e.stopPropagation(); // 阻止冒泡,避免触发折叠 voiceEnabled = !voiceEnabled; // 如果禁用语音,立即停止当前播放的语音并清空队列 if (!voiceEnabled) { window.speechSynthesis.cancel(); speechQueue.length = 0; isSpeaking = false; } toggleBtn.textContent = voiceEnabled ? '🔊 语音' : '🔇 静音'; toggleBtn.className = voiceEnabled ? 'voice-enabled' : 'voice-disabled'; toggleBtn.title = voiceEnabled ? '关闭语音提示' : '开启语音提示'; addLog(`语音功能已${voiceEnabled ? '启用' : '禁用'}`); }; btnContainer.appendChild(toggleBtn); // 折叠按钮 const collapseBtn = document.createElement('button'); collapseBtn.id = 'toggleCollapseBtn'; collapseBtn.textContent = '折叠面板'; collapseBtn.title = '折叠/展开控制面板'; collapseBtn.onclick = (e) => { e.stopPropagation(); // 阻止冒泡,避免触发折叠 togglePanel(); }; btnContainer.appendChild(collapseBtn); header.appendChild(btnContainer); panel.appendChild(header); // 日志内容区域 const logContent = document.createElement('div'); logContent.id = 'monitorLogs'; logContent.innerHTML = '<div class="log-item log-info">监控启动...</div>'; panel.appendChild(logContent); document.body.appendChild(panel); // 添加点击折叠功能(点击标题栏可折叠) header.addEventListener('click', togglePanel); // 初始添加一条日志 addLog('监控已启动'); } // 初始化监控 function initMonitor() { createControlPanel(); // 只在工作时间内播放启动语音 if (isWorkingHours()) { speak("监控启动"); } else { addLog('当前不在工作时间,监控已启动但暂停语音提示', 'warning'); } // 每3秒检查一次 setInterval(checkCount, 3000); } // 页面加载完成后启动监控 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initMonitor); } else { initMonitor(); } })();