您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
高效监控游戏平台用户盈亏数据,支持自动选择状态和分页,优化大数据处理
// ==UserScript== // @name 游戏盈亏监控 // @namespace http://greasyfork.icu/users/your-id // @version 2.6.6 // @description 高效监控游戏平台用户盈亏数据,支持自动选择状态和分页,优化大数据处理 // @author Cisco // @match https://7777m.topcms.org/* // @match https://*.topcms.org/* // @icon https://7777m.topcms.org/favicon.ico // @license MIT // @grant GM_notification // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 配置参数 const config = { checkInterval: 2000, profitThreshold: null, lossThreshold: null, monitoring: false, currentIndex: 0, columnIndex: 0, currentPage: 1, totalPages: 1, totalItems: 0, itemsPerPage: 10, batchSize: 5, maxParallel: 2, activeRequests: 0, processedItems: 0, monitoringDuration: 40, lastCheckTime: 0, startTime: 0, panelCollapsed: false, profitAlerts: 0, lossAlerts: 0, dialogQueue: [], // 告警消息队列 isDialogProcessing: false, // 是否正在显示告警 activeDialogs: {}, // 跟踪每个请求的对话框 dialogCounter: 0 // 对话框唯一ID计数器 }; // 存储对象 const storage = { get: function(key, defaultValue) { try { if (typeof GM_getValue !== 'undefined') { return GM_getValue(key, defaultValue); } const value = localStorage.getItem(`monitor_${key}`); return value !== null ? JSON.parse(value) : defaultValue; } catch (e) { console.error('Storage get error:', e); return defaultValue; } }, set: function(key, value) { try { if (typeof GM_setValue !== 'undefined') { GM_setValue(key, value); } else { localStorage.setItem(`monitor_${key}`, JSON.stringify(value)); } } catch (e) { console.error('Storage set error:', e); } } }; // 添加样式 function addStyles() { const css = ` .monitor-panel { position: fixed; top: 20px; right: 20px; z-index: 9999; background: white; padding: 15px; border: 1px solid #ddd; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); font-family: Arial, sans-serif; width: 320px; max-height: 90vh; overflow-y: auto; transition: all 0.3s ease; } .monitor-panel.collapsed { width: 40px; height: 40px; overflow: hidden; padding: 5px; } .toggle-panel { position: absolute; top: 5px; right: 5px; width: 30px; height: 30px; border: none; background: #f0f0f0; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 16px; z-index: 10000; } .toggle-panel:hover { background: #e0e0e0; } .collapsed .panel-content { display: none; } .monitor-header { margin: 0 0 15px 0; color: #333; font-size: 16px; font-weight: bold; border-bottom: 1px solid #eee; padding-bottom: 10px; } .monitor-input-group { margin-bottom: 12px; } .monitor-label { display: block; margin-bottom: 5px; color: #666; font-size: 13px; } .monitor-input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } .monitor-button { width: 100%; padding: 10px; background: #409EFF; color: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; transition: background 0.3s; } .monitor-button.stop { background: #F56C6C; } .monitor-stats { margin-top: 15px; font-size: 12px; color: #666; border-top: 1px solid #eee; padding-top: 10px; } .monitor-stat-row { display: flex; justify-content: space-between; margin-bottom: 5px; } .monitor-progress-container { margin: 10px 0; height: 10px; background: #f0f0f0; border-radius: 5px; overflow: hidden; } .monitor-progress-bar { height: 100%; background: linear-gradient(to right, #67C23A, #409EFF); transition: width 0.3s; } .monitor-speed { font-size: 11px; color: #999; text-align: right; } .monitor-alert-count { display: flex; justify-content: space-between; margin-top: 5px; } .monitor-alert-badge { display: inline-block; padding: 2px 6px; border-radius: 10px; font-size: 11px; font-weight: bold; } .profit-badge { background: #f0f9eb; color: #67C23A; } .loss-badge { background: #fef0f0; color: #F56C6C; } `; const style = document.createElement('style'); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); } // 创建控制面板 function createControlPanel() { addStyles(); const panel = document.createElement('div'); panel.className = 'monitor-panel'; panel.id = 'monitorPanel'; // 添加收起/展开按钮 const toggleBtn = document.createElement('button'); toggleBtn.className = 'toggle-panel'; toggleBtn.innerHTML = '×'; toggleBtn.title = '收起/展开控制面板'; toggleBtn.addEventListener('click', togglePanel); // 面板内容 const panelContent = document.createElement('div'); panelContent.className = 'panel-content'; panelContent.innerHTML = ` <h3 class="monitor-header">游戏盈亏监控</h3> <div class="monitor-input-group"> <label class="monitor-label">盈利阈值(+)</label> <input type="number" id="profitThresholdInput" placeholder="输入正数" class="monitor-input"> </div> <div class="monitor-input-group"> <label class="monitor-label">亏损阈值(-)</label> <input type="number" id="lossThresholdInput" placeholder="输入正数" class="monitor-input"> </div> <div class="monitor-input-group"> <label class="monitor-label">监控时长(分钟)</label> <input type="number" id="minutesInput" value="40" min="1" class="monitor-input"> </div> <div class="monitor-input-group"> <label class="monitor-label">并行数量</label> <input type="number" id="parallelInput" value="3" min="1" max="10" class="monitor-input" disabled> </div> <button id="toggleMonitor" class="monitor-button">开始监控</button> <div class="monitor-stats"> <div class="monitor-stat-row"> <span>状态:</span> <span id="statusText">未启动</span> </div> <div class="monitor-stat-row"> <span>进度:</span> <span id="currentPosition">0</span>/<span id="totalItems">0</span> 条 </div> <div class="monitor-stat-row"> <span>页数:</span> <span id="displayPage">1</span>/<span id="totalPages">1</span> </div> <div class="monitor-stat-row"> <span>速度:</span> <span id="speedText">0 条/分钟</span> </div> <div class="monitor-progress-container"> <div id="progressBar" class="monitor-progress-bar" style="width: 0%"></div> </div> <div class="monitor-speed" id="timeRemaining">预计剩余时间: 计算中...</div> <div class="monitor-alert-count"> <span class="monitor-alert-badge profit-badge">盈利超标: <span id="profitAlerts">0</span></span> <span class="monitor-alert-badge loss-badge">亏损超标: <span id="lossAlerts">0</span></span> </div> </div> `; panel.appendChild(toggleBtn); panel.appendChild(panelContent); document.body.appendChild(panel); // 恢复面板状态和设置 config.panelCollapsed = storage.get('panelCollapsed', false); if (config.panelCollapsed) { panel.classList.add('collapsed'); toggleBtn.innerHTML = '≡'; } const savedProfit = storage.get('profitThreshold', null); const savedLoss = storage.get('lossThreshold', null); const savedMinutes = storage.get('monitoringDuration', 40); const savedParallel = storage.get('parallelCount', 3); config.profitAlerts = storage.get('profitAlerts', 0); config.lossAlerts = storage.get('lossAlerts', 0); if (savedProfit) document.getElementById('profitThresholdInput').value = savedProfit; if (savedLoss) document.getElementById('lossThresholdInput').value = savedLoss; document.getElementById('minutesInput').value = savedMinutes; document.getElementById('parallelInput').value = savedParallel; document.getElementById('profitAlerts').textContent = config.profitAlerts; document.getElementById('lossAlerts').textContent = config.lossAlerts; document.getElementById('toggleMonitor').addEventListener('click', toggleMonitoring); } // 收起/展开面板 function togglePanel() { const panel = document.getElementById('monitorPanel'); config.panelCollapsed = !panel.classList.contains('collapsed'); if (config.panelCollapsed) { panel.classList.add('collapsed'); this.innerHTML = '≡'; } else { panel.classList.remove('collapsed'); this.innerHTML = '×'; } storage.set('panelCollapsed', config.panelCollapsed); } // 切换监控状态 function toggleMonitoring() { const profitVal = parseFloat(document.getElementById('profitThresholdInput').value); const lossVal = parseFloat(document.getElementById('lossThresholdInput').value); const minutes = parseInt(document.getElementById('minutesInput').value) || 40; const parallel = parseInt(document.getElementById('parallelInput').value) || 3; if (isNaN(profitVal) && isNaN(lossVal)) { alert('请至少设置一个阈值'); return; } storage.set('profitThreshold', isNaN(profitVal) ? null : profitVal); storage.set('lossThreshold', isNaN(lossVal) ? null : Math.abs(lossVal)); storage.set('monitoringDuration', minutes); storage.set('parallelCount', parallel); config.profitThreshold = isNaN(profitVal) ? null : profitVal; config.lossThreshold = isNaN(lossVal) ? null : Math.abs(lossVal); config.monitoringDuration = minutes; config.maxParallel = Math.min(Math.max(parallel, 1), 10); config.monitoring = !config.monitoring; const btn = document.getElementById('toggleMonitor'); const status = document.getElementById('statusText'); if (config.monitoring) { btn.textContent = '停止监控'; btn.classList.add('stop'); let statusMsg = '监控中 ('; if (config.profitThreshold) statusMsg += `盈>${config.profitThreshold}`; if (config.lossThreshold) statusMsg += `${config.profitThreshold ? ' ' : ''}亏>${config.lossThreshold}`; status.textContent = statusMsg + ')'; config.startTime = Date.now(); config.processedItems = 0; config.lastCheckTime = Date.now(); startMonitoring(); } else { btn.textContent = '开始监控'; btn.classList.remove('stop'); status.textContent = '已停止'; } } // 主监控流程 function startMonitoring() { if (!config.monitoring) return; const firstPageBtn = document.querySelector('.el-pager .number:first-child'); if (firstPageBtn && !firstPageBtn.classList.contains('active')) { safeClick(firstPageBtn, () => { setTimeout(() => initMonitoring(), 2000); }); } else { initMonitoring(); } } // 安全点击函数(带回调) function safeClick(element, callback) { if (simulateClick(element)) { setTimeout(() => { if (callback) callback(); }, 500); } else if (callback) { callback(); } } // 可靠的模拟点击函数 function simulateClick(element) { if (!element) { console.log('模拟点击失败:元素不存在'); return false; } // 方法1: 直接调用click方法 try { element.click(); return true; } catch (e) { console.log('直接click方法失败,尝试其他方法'); } // 方法2: 创建并派发鼠标事件 try { const mouseDown = new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window }); element.dispatchEvent(mouseDown); const mouseUp = new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window }); element.dispatchEvent(mouseUp); const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, view: window }); element.dispatchEvent(clickEvent); return true; } catch (e) { console.log('标准MouseEvent创建失败:', e); } // 方法3: 最简事件 try { const event = document.createEvent('Event'); event.initEvent('click', true, true); element.dispatchEvent(event); return true; } catch (e) { console.log('最简事件创建失败:', e); } console.log('所有点击模拟方法均失败'); return false; } // 选择下拉选项 function selectDropdownOption(selector, optionText, callback) { const dropdown = document.querySelector(selector); if (!dropdown) { console.log('未找到下拉框:', selector); if (callback) callback(false); return; } // 先检查当前是否已经是目标值 const currentValue = dropdown.querySelector('.el-input__inner')?.value; if (currentValue === optionText) { if (callback) callback(true); return; } // 点击打开下拉框 safeClick(dropdown, () => { setTimeout(() => { // 查找页面中所有可见的下拉菜单 const allMenus = Array.from(document.querySelectorAll('.el-select-dropdown')); const visibleMenus = allMenus.filter(menu => { return !menu.style.display || menu.style.display !== 'none'; }); // 查找与当前下拉框关联的菜单 let targetMenu = null; const dropdownRect = dropdown.getBoundingClientRect(); for (const menu of visibleMenus) { const menuRect = menu.getBoundingClientRect(); // 检查菜单是否出现在下拉框附近 if (Math.abs(menuRect.left - dropdownRect.left) < 50 && (Math.abs(menuRect.top - dropdownRect.bottom) < 20 || Math.abs(menuRect.bottom - dropdownRect.top) < 20)) { targetMenu = menu; break; } } if (!targetMenu) { console.log('未找到关联的下拉菜单'); if (callback) callback(false); return; } // 查找匹配的选项 const options = targetMenu.querySelectorAll('.el-select-dropdown__item'); let optionFound = false; for (const option of options) { if (option.textContent.trim() === optionText) { // 确保选项可见 option.scrollIntoView({ behavior: 'instant', block: 'nearest' }); // 点击选项 setTimeout(() => { if (simulateClick(option)) { optionFound = true; // 等待下拉框关闭 setTimeout(() => { if (callback) callback(true); }, 800); } else { if (callback) callback(false); } }, 200); break; } } if (!optionFound) { console.log('未找到选项:', optionText); if (callback) callback(false); } }, 500); }); } // 设置每页显示条数 function setPageSize(size, callback) { selectDropdownOption('.el-pagination__sizes .el-select', `${size}条/页`, (success) => { if (success) { config.itemsPerPage = size; // 设置成功后强制重新加载数据 setTimeout(() => { updatePaginationInfo(); if (callback) callback(true); }, 1500); // 等待数据重新加载 } else { if (callback) callback(false); } }); } // 选择订单状态 function selectOrderStatus(status, callback) { selectDropdownOption('.el-select.filter-item', status, callback); } // 设置时间范围 function setTimeRange(callback) { const now = new Date(); const startTime = new Date(now.getTime() - config.monitoringDuration * 60000); const endTime = new Date(now); endTime.setHours(23, 59, 59, 0); const formatTime = (date) => { const pad = num => num.toString().padStart(2, '0'); return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; }; const timeInputs = document.querySelectorAll('.el-range-input'); if (timeInputs.length >= 2) { timeInputs[0].value = formatTime(startTime); timeInputs[1].value = formatTime(endTime); timeInputs.forEach(input => { input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); }); setTimeout(() => { if (callback) callback(); }, 500); } else if (callback) { callback(); } } // 初始化监控 function initMonitoring() { // 先重置分页信息 config.currentPage = 1; config.currentIndex = 0; // 新的初始化流程 const initSteps = [ (next) => selectOrderStatus('已支付', (success) => { console.log(success ? '状态设置成功' : '状态设置失败'); next(); }), (next) => setTimeRange(() => { console.log('时间设置完成'); next(); }), (next) => setPageSize(200, (success) => { console.log(success ? '分页设置成功' : '分页设置失败'); next(); }), () => { console.log('开始查询'); // 查询前再次更新分页信息 updatePaginationInfo(); setTimeout(() => clickQueryButton(), 1000); } ]; // 执行步骤 function executeStep() { if (initSteps.length > 0) { const step = initSteps.shift(); step(executeStep); } } executeStep(); } // 更新分页信息 function updatePaginationInfo() { const pagination = document.querySelector('.el-pagination'); if (!pagination) { const rows = document.querySelectorAll('.el-table__row:not(.el-table__row--level)'); config.totalItems = rows.length; config.itemsPerPage = rows.length || config.itemsPerPage; config.totalPages = 1; } else { // 从分页信息中获取总条数(适应不同格式) const totalText = pagination.querySelector('.el-pagination__total')?.textContent || ''; const totalMatch = totalText.match(/(\d+)(?=\s*条)/) || [null, 0]; config.totalItems = parseInt(totalMatch[1]) || 0; // 确保使用当前设置的分页大小 config.itemsPerPage = config.itemsPerPage || 200; config.totalPages = Math.ceil(config.totalItems / config.itemsPerPage); } // 更新UI显示 document.getElementById('totalItems').textContent = config.totalItems; document.getElementById('totalPages').textContent = config.totalPages; updateProgressDisplay(); console.log('更新分页信息 - 总条数:', config.totalItems, '每页:', config.itemsPerPage, '总页数:', config.totalPages); } // 点击查询按钮 function clickQueryButton() { const queryBtn = [...document.querySelectorAll('.filter-container button.el-button')] .find(btn => !btn.classList.contains('is-disabled') && btn.textContent.includes('查询')); if (queryBtn) { safeClick(queryBtn, () => { setTimeout(() => checkUsers(), 3000); }); } else { console.log('未找到查询按钮'); setTimeout(() => { if (config.monitoring) clickQueryButton(); }, 1000); } } // 更新进度显示 function updateProgressDisplay() { const currentPos = (config.currentPage - 1) * config.itemsPerPage + config.currentIndex + 1; document.getElementById('currentPosition').textContent = currentPos; document.getElementById('displayPage').textContent = config.currentPage; const progressPercent = (currentPos / config.totalItems * 100).toFixed(1); document.getElementById('progressBar').style.width = `${progressPercent}%`; const now = Date.now(); const elapsedMinutes = (now - config.startTime) / 60000; const speed = elapsedMinutes > 0 ? Math.round(config.processedItems / elapsedMinutes) : 0; document.getElementById('speedText').textContent = `${speed} 条/分钟`; if (speed > 0) { const remainingItems = config.totalItems - currentPos; const remainingMinutes = Math.ceil(remainingItems / speed); document.getElementById('timeRemaining').textContent = `预计剩余时间: ${remainingMinutes} 分钟`; } } // 检查用户列表 function checkUsers() { const userRows = document.querySelectorAll('.el-table__row:not(.el-table__row--level)'); if (userRows.length === 0) { console.log('未找到用户数据'); setTimeout(() => { if (config.monitoring) startMonitoring(); }, config.checkInterval); return; } config.currentIndex = 0; processBatch(); } // 新增:重置新一轮监控的函数 function resetForNewRound() { // 保存原始设置 const originalProfitThreshold = config.profitThreshold; const originalLossThreshold = config.lossThreshold; const originalMonitoringDuration = config.monitoringDuration; const originalParallel = config.maxParallel; // 重置状态 config.startTime = Date.now(); config.processedItems = 0; config.lastCheckTime = Date.now(); config.currentPage = 1; config.currentIndex = 0; // 重新获取分页信息 updatePaginationInfo(); // 恢复设置 config.profitThreshold = originalProfitThreshold; config.lossThreshold = originalLossThreshold; config.monitoringDuration = originalMonitoringDuration; config.maxParallel = originalParallel; console.log('新一轮监控准备完成,总条数:', config.totalItems); } // 修改processUser函数,确保每次只处理一个对话框 function processUser(row, userNameElement, callback) { const userName = userNameElement.textContent.trim(); const dialogId = 'dialog_' + (++config.dialogCounter); // 保存当前对话框ID到配置中 config.activeDialogs[dialogId] = { userName: userName, callback: callback }; // 先关闭所有可能打开的对话框 closeAllDialogs(() => { // 滚动并点击用户名称 userNameElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); setTimeout(() => { openUserDialog(userNameElement, dialogId); }, 500); }); } // 新增关闭所有对话框的函数 function closeAllDialogs(callback) { const dialogs = document.querySelectorAll('.el-dialog__wrapper:not([style*="display: none"])'); if (dialogs.length === 0) { if (callback) callback(); return; } let closedCount = 0; dialogs.forEach(dialog => { const closeBtn = dialog.querySelector('.el-dialog__headerbtn'); if (closeBtn) { safeClick(closeBtn, () => { closedCount++; if (closedCount === dialogs.length && callback) { setTimeout(callback, 300); } }); } else { closedCount++; if (closedCount === dialogs.length && callback) { setTimeout(callback, 300); } } }); } // 修改openUserDialog函数 function openUserDialog(userNameElement, dialogId) { safeClick(userNameElement, () => { // 增加对话框打开确认 const checkDialog = setInterval(() => { const dialog = document.querySelector('.el-dialog__wrapper:not([style*="display: none"])'); if (dialog) { clearInterval(checkDialog); // 确保对话框内容已加载 setTimeout(() => { checkUserProfit(dialog, config.activeDialogs[dialogId].userName, dialogId, config.activeDialogs[dialogId].callback); }, 800); } }, 200); }); } // 处理批次数据 function processBatch() { if (!config.monitoring || config.activeRequests >= config.maxParallel) { setTimeout(processBatch, 100); return; } const userRows = document.querySelectorAll('.el-table__row:not(.el-table__row--level)'); if (config.currentIndex >= userRows.length) { if (config.currentPage < config.totalPages) { goToNextPage(); } else { console.log('所有数据检查完成'); resetForNewRound(); setTimeout(() => { if (config.monitoring) startMonitoring(); }, config.checkInterval); } return; } // 计算可处理数量 const batchSize = Math.min( config.batchSize, userRows.length - config.currentIndex, config.maxParallel - config.activeRequests ); for (let i = 0; i < batchSize; i++) { const currentRow = userRows[config.currentIndex + i]; const userNameElement = currentRow.querySelector('.el-tooltip[style*="color: rgb(24, 144, 255)"]'); if (userNameElement) { config.activeRequests++; // 将任务放入队列中,由弹窗队列串行处理 config.dialogQueue.push(() => { processUser(currentRow, userNameElement, () => { config.activeRequests--; config.processedItems++; updateProgressDisplay(); setTimeout(processBatch, 50); // 每处理完一个继续调度 }); }); } else { config.processedItems++; updateProgressDisplay(); } } config.currentIndex += batchSize; processDialogQueue(); // 开始弹窗串行处理 } function processDialogQueue() { // 如果已经有弹窗正在处理,退出 if (config.isDialogProcessing) return; const nextTask = config.dialogQueue.shift(); // 取出下一个弹窗任务 if (!nextTask) return; config.isDialogProcessing = true; // 执行任务(内部调用 processUser → openUserDialog → checkUserProfit) nextTask(); } // 处理单个用户 function checkUserProfit(dialog, userName, dialogId, callback) { // 增加重试机制 let retryCount = 0; const maxRetries = 3; function attemptCheck() { const tables = dialog.querySelectorAll('.el-dialog__body .el-table'); if (tables.length < 2) { if (retryCount < maxRetries) { retryCount++; console.log(`[${userName}] 未找到表格,重试 ${retryCount}/${maxRetries}`); setTimeout(attemptCheck, 1000); return; } console.error(`[${userName}] 未找到包含余额的表格`); closeDialog(dialogId); if (callback) callback(); return; } // 获取第二个表格(余额表格) const balanceTable = tables[1]; // 改进的查找余额列逻辑 const headerCells = balanceTable.querySelectorAll('.el-table__header .cell'); let balanceColumnIndex = -1; // 更灵活的查找余额列 for (let i = 0; i < headerCells.length; i++) { const headerText = headerCells[i].textContent.trim().toLowerCase(); if (headerText.includes('余额') || headerText.includes('balance')) { balanceColumnIndex = i; break; } } if (balanceColumnIndex === -1) { console.error(`[${userName}] 未找到余额列`); closeDialog(dialogId); if (callback) callback(); return; } // 更健壮的单元格查找 const balanceCell = balanceTable.querySelector( `.el-table__body tr:first-child td:nth-child(${balanceColumnIndex + 1}) .cell` ); if (!balanceCell) { if (retryCount < maxRetries) { retryCount++; console.log(`[${userName}] 未找到余额单元格,重试 ${retryCount}/${maxRetries}`); setTimeout(attemptCheck, 1000); return; } console.error(`[${userName}] 未找到余额单元格`); closeDialog(dialogId); if (callback) callback(); return; } // 提取数值 const rawText = balanceCell.textContent.trim(); const numericText = rawText.replace(/,/g, ''); const value = parseFloat(numericText.replace(/[^\d.-]/g, '')); console.log(`[${userName}] 余额解析: "${rawText}" → ${value}`); if (isNaN(value)) { console.error(`[${userName}] 余额解析失败:`, rawText); closeDialog(dialogId); if (callback) callback(); return; } // 阈值检查 if (config.profitThreshold !== null && value >= config.profitThreshold) { const exceed = (value - config.profitThreshold).toFixed(2); showAlert(`用户 ${userName} 余额超标: ${value} (超过${exceed})`, 'profit'); incrementAlertCount('profit'); } else if (config.lossThreshold !== null && value <= config.lossThreshold) { const below = (config.lossThreshold - value).toFixed(2); showAlert(`用户 ${userName} 余额不足: ${value} (低于${below})`, 'loss'); incrementAlertCount('loss'); } // 弹窗处理完后,关闭并释放队列控制 closeDialog(dialogId, () => { config.isDialogProcessing = false; processDialogQueue(); // 处理下一个弹窗 if (callback) callback(); }); } attemptCheck(); } // 增加警报计数 function incrementAlertCount(type) { const elementId = type === 'profit' ? 'profitAlerts' : 'lossAlerts'; const currentCount = parseInt(document.getElementById(elementId).textContent) || 0; const newCount = currentCount + 1; document.getElementById(elementId).textContent = newCount; if (type === 'profit') { config.profitAlerts = newCount; storage.set('profitAlerts', newCount); } else { config.lossAlerts = newCount; storage.set('lossAlerts', newCount); } } // 翻到下一页 function goToNextPage() { const nextBtn = document.querySelector('.el-pagination .btn-next:not([disabled])'); if (nextBtn) { safeClick(nextBtn, () => { setTimeout(() => { config.currentPage++; config.currentIndex = 0; updatePaginationInfo(); setTimeout(() => checkUsers(), 2000); }, 1500); }); } } // 改进的 closeDialog 函数 function closeDialog(dialogId, callback) { delete config.activeDialogs[dialogId]; const dialog = document.querySelector('.el-dialog__wrapper:not([style*="display: none"])'); if (dialog) { const closeBtn = dialog.querySelector('.el-dialog__headerbtn'); if (closeBtn) { safeClick(closeBtn, () => { // 等待对话框完全关闭 setTimeout(() => { if (callback) callback(); }, 500); }); } else if (callback) { callback(); } } else if (callback) { callback(); } } // 改进的通知函数 function showAlert(message, type) { // 1. 控制台日志 console.warn(`[ALERT] ${message}`); alert(message); // 2. 使用GM_notification或浏览器通知 if (typeof GM_notification !== 'undefined') { try { GM_notification({ title: type === 'profit' ? '盈利报警' : '亏损报警', text: message, timeout: 5000, onclick: function() { window.focus(); } }); return; } catch (e) { console.error('GM_notification失败:', e); } } // 3. 使用Web Notification if (window.Notification && Notification.permission === 'granted') { new Notification(type === 'profit' ? '盈利报警' : '亏损报警', { body: message, icon: 'https://7777m.topcms.org/favicon.ico' }); return; } else if (window.Notification && Notification.permission !== 'denied') { Notification.requestPermission().then(function(permission) { if (permission === 'granted') { new Notification(type === 'profit' ? '盈利报警' : '亏损报警', { body: message, icon: 'https://7777m.topcms.org/favicon.ico' }); } else { fallbackAlert(message); } }); return; } // 4. 最终弹窗方案 fallbackAlert(message); } function fallbackAlert(message) { // 创建自定义弹窗 const alertDiv = document.createElement('div'); alertDiv.style.position = 'fixed'; alertDiv.style.top = '20px'; alertDiv.style.right = '20px'; alertDiv.style.padding = '15px'; alertDiv.style.background = '#f8f8f8'; alertDiv.style.border = '1px solid #ddd'; alertDiv.style.borderRadius = '5px'; alertDiv.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)'; alertDiv.style.zIndex = '99999'; alertDiv.textContent = message; document.body.appendChild(alertDiv); // 5秒后自动消失 setTimeout(() => { alertDiv.remove(); }, 5000); } // 初始化 function init() { const checkTable = setInterval(() => { if (document.querySelector('.el-table')) { clearInterval(checkTable); createControlPanel(); console.log('脚本初始化完成'); } }, 500); } if (document.readyState === 'complete') { init(); } else { window.addEventListener('load', init); } })();