您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为微力同步任务增加多时间段定时启停功能,优化状态显示
// ==UserScript== // @name 微力同步任务定时启停(多时间段版) // @namespace http://tampermonkey.net/ // @version 1.0 // @description 为微力同步任务增加多时间段定时启停功能,优化状态显示 // @author [email protected] // @match http://127.0.0.1:8886/* // @match http://localhost:8886/* // @match http://*:8886/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @run-at document-idle // @license GNU GPLv3 // ==/UserScript== (function() { 'use strict'; // 添加样式(重点优化状态显示区域) GM_addStyle(` .schedule-dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 20px; z-index: 10000; width: 450px; max-height: 80vh; overflow-y: auto; font-family: inherit; border: 1px solid #e0e0e0; } .schedule-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #eee; } .schedule-title { font-size: 18px; font-weight: 500; color: #333; } .schedule-close { cursor: pointer; font-size: 24px; color: #999; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 50%; } .schedule-close:hover { background: #f5f5f5; color: #666; } .schedule-form { margin-bottom: 20px; } .form-group { margin-bottom: 15px; } .form-label { display: block; margin-bottom: 5px; font-size: 14px; color: #555; } .form-control { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; box-sizing: border-box; } .form-control:focus { border-color: #3f51b5; outline: none; box-shadow: 0 0 0 2px rgba(63, 81, 181, 0.2); } .time-row { display: flex; align-items: center; gap: 10px; } .time-input { flex: 1; } .schedule-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } .btn { padding: 8px 16px; border-radius: 4px; font-size: 14px; cursor: pointer; border: none; transition: all 0.2s; } .btn-primary { background: #3f51b5; color: white; } .btn-primary:hover { background: #303f9f; } .btn-secondary { background: #f5f5f5; color: #333; } .btn-secondary:hover { background: #e0e0e0; } .btn-add { background: #4caf50; color: white; margin-bottom: 15px; } .btn-add:hover { background: #3d9140; } .btn-remove { background: #f44336; color: white; padding: 4px 8px; margin-top: 25px; } .btn-remove:hover { background: #d32f2f; } /* 优化状态指示器样式,确保文字完整显示 */ .schedule-indicator { display: inline-flex; align-items: center; padding: 4px 8px; border-radius: 4px; background: rgba(63, 81, 181, 0.1); color: #3f51b5; font-size: 12px; margin-left: 5px; white-space: nowrap; /* 禁止文字换行 */ overflow: visible; /* 允许溢出显示 */ text-overflow: clip; /* 不显示省略号 */ max-width: none; /* 取消最大宽度限制 */ } .schedule-icon { margin-right: 5px; } .schedule-btn { background: transparent; border: none; cursor: pointer; color: #666; padding: 5px; border-radius: 4px; display: inline-flex; align-items: center; font-size: 14px; margin-left: 5px; } .schedule-btn:hover { background: #f5f5f5; color: #3f51b5; } .overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999; } .schedule-status { margin-left: 10px; font-size: 12px; color: #666; } .period-container { border: 1px solid #eee; border-radius: 6px; padding: 15px; margin-bottom: 15px; position: relative; } .period-header { font-weight: 500; margin-bottom: 10px; color: #666; display: flex; justify-content: space-between; } /* 优化状态容器样式,确保有足够空间 */ .folder-status, [class*="status-"] { display: inline-flex; align-items: center; flex-wrap: nowrap; max-width: 200px; /* 增加状态容器宽度 */ } `); // 注册暴力猴菜单命令 - 清除所有定时数据 GM_registerMenuCommand("清除所有定时数据", () => { if (confirm("确定要清除所有任务的定时设置吗?此操作不可恢复!")) { // 清除存储的数据 GM_setValue('scheduleRules', '{}'); // 更新内存中的规则 scheduleRules = {}; // 更新UI updateScheduleButtons(); // 显示成功消息 alert("所有定时数据已清除"); } }); // 存储当前任务名称 let currentTaskName = ''; // 存储定时规则 - 多时间段版本 let scheduleRules = {}; try { const storedRules = GM_getValue('scheduleRules', '{}'); scheduleRules = JSON.parse(storedRules); } catch (e) { console.error('读取定时规则失败,重置为默认值', e); scheduleRules = {}; } // 存储定时器 let scheduleTimer = null; // 存储对话框元素 let dialogElement = null; let overlayElement = null; /** * 创建时间段配置区域 */ function createPeriodElement(period, index) { const periodEl = document.createElement('div'); periodEl.className = 'period-container'; periodEl.dataset.index = index; periodEl.innerHTML = ` <div class="period-header"> <span>时间段 ${index + 1}</span> <div class="form-check"> <label> <input type="checkbox" class="period-enabled" ${period.enabled ? 'checked' : ''}> 启用此时间段 </label> </div> </div> <div class="form-group"> <label class="form-label">启动时间</label> <div class="time-row"> <input type="time" class="form-control time-input schedule-start-time" value="${period.startTime || '08:00'}"> </div> </div> <div class="form-group"> <label class="form-label">停止时间</label> <div class="time-row"> <input type="time" class="form-control time-input schedule-stop-time" value="${period.stopTime || '18:00'}"> </div> </div> <div class="form-group"> <label class="form-label">重复周期</label> <select class="form-control schedule-repeat"> <option value="daily" ${(period.repeat || 'daily') === 'daily' ? 'selected' : ''}>每天</option> <option value="weekdays" ${(period.repeat || 'daily') === 'weekdays' ? 'selected' : ''}>工作日(周一到周五)</option> <option value="weekends" ${(period.repeat || 'daily') === 'weekends' ? 'selected' : ''}>周末(周六周日)</option> </select> </div> <button class="btn btn-remove" data-index="${index}">删除</button> `; // 添加删除按钮事件 periodEl.querySelector('.btn-remove').addEventListener('click', function() { const index = parseInt(this.dataset.index); removePeriod(index); }); return periodEl; } /** * 移除指定索引的时间段 */ function removePeriod(index) { const periodsContainer = dialogElement.querySelector('.periods-container'); const periodElements = periodsContainer.querySelectorAll('.period-container'); if (periodElements.length <= 1) { alert('至少保留一个时间段'); return; } // 移除DOM元素 periodElements[index].remove(); // 更新剩余时间段的索引和标题 const remainingPeriods = periodsContainer.querySelectorAll('.period-container'); remainingPeriods.forEach((el, i) => { el.dataset.index = i; el.querySelector('.period-header span').textContent = `时间段 ${i + 1}`; el.querySelector('.btn-remove').dataset.index = i; }); } /** * 添加新的时间段 */ function addNewPeriod() { const periodsContainer = dialogElement.querySelector('.periods-container'); const periodCount = periodsContainer.querySelectorAll('.period-container').length; // 创建新的时间段配置 const newPeriod = { enabled: true, startTime: '08:00', stopTime: '18:00', repeat: 'daily' }; // 创建并添加DOM元素 const periodEl = createPeriodElement(newPeriod, periodCount); periodsContainer.appendChild(periodEl); } /** * 创建定时对话框 */ function createScheduleDialog() { if (dialogElement && overlayElement) return; // 创建对话框 dialogElement = document.createElement('div'); dialogElement.className = 'schedule-dialog'; dialogElement.style.display = 'none'; dialogElement.innerHTML = ` <div class="schedule-header"> <div class="schedule-title">定时设置</div> <div class="schedule-close">×</div> </div> <div class="schedule-form"> <div class="form-group"> <label class="form-label">任务名称</label> <input type="text" class="form-control" id="schedule-task-name" readonly> </div> <div class="form-group"> <label class="form-label">启用定时功能</label> <input type="checkbox" id="schedule-enabled" style="margin-left: 10px;"> </div> <div class="form-group"> <button class="btn btn-add" id="add-period">+ 添加时间段</button> <div class="periods-container"></div> </div> </div> <div class="schedule-actions"> <button class="btn btn-secondary" id="schedule-cancel">取消</button> <button class="btn btn-primary" id="schedule-save">保存</button> </div> `; document.body.appendChild(dialogElement); // 创建遮罩层 overlayElement = document.createElement('div'); overlayElement.className = 'overlay'; overlayElement.style.display = 'none'; document.body.appendChild(overlayElement); // 绑定事件 dialogElement.querySelector('.schedule-close').addEventListener('click', hideScheduleDialog); dialogElement.querySelector('#schedule-cancel').addEventListener('click', hideScheduleDialog); dialogElement.querySelector('#schedule-save').addEventListener('click', saveSchedule); dialogElement.querySelector('#add-period').addEventListener('click', addNewPeriod); } /** * 显示定时对话框 */ function showScheduleDialog(taskName) { currentTaskName = taskName; createScheduleDialog(); // 获取任务的定时规则 const taskRule = scheduleRules[taskName] || { enabled: true, periods: [{ enabled: true, startTime: '08:00', stopTime: '18:00', repeat: 'daily' }] }; // 填充任务名称和启用状态 dialogElement.querySelector('#schedule-task-name').value = taskName; dialogElement.querySelector('#schedule-enabled').checked = taskRule.enabled; // 清空并填充时间段 const periodsContainer = dialogElement.querySelector('.periods-container'); periodsContainer.innerHTML = ''; taskRule.periods.forEach((period, index) => { const periodEl = createPeriodElement(period, index); periodsContainer.appendChild(periodEl); }); // 显示对话框 dialogElement.style.display = 'block'; overlayElement.style.display = 'block'; } /** * 隐藏定时对话框 */ function hideScheduleDialog() { if (dialogElement) dialogElement.style.display = 'none'; if (overlayElement) overlayElement.style.display = 'none'; } /** * 保存定时设置 */ function saveSchedule() { const enabled = dialogElement.querySelector('#schedule-enabled').checked; const periodElements = dialogElement.querySelectorAll('.period-container'); // 收集所有时间段配置 const periods = Array.from(periodElements).map(el => { const startTime = el.querySelector('.schedule-start-time').value; const stopTime = el.querySelector('.schedule-stop-time').value; if (!startTime || !stopTime) { alert('请填写完整的时间设置'); throw new Error('时间设置不完整'); } return { enabled: el.querySelector('.period-enabled').checked, startTime, stopTime, repeat: el.querySelector('.schedule-repeat').value }; }); // 保存规则 scheduleRules[currentTaskName] = { enabled, periods }; GM_setValue('scheduleRules', JSON.stringify(scheduleRules)); hideScheduleDialog(); // 更新UI和定时器 updateScheduleButtons(); startScheduleTimer(); } /** * 为任务行添加定时按钮和状态指示器 */ function addScheduleButtons() { // 定位任务行 const taskRows = document.querySelectorAll('tr:has(.folder-name)'); if (taskRows.length === 0) { console.log('未找到任务行,等待页面加载...'); return; } taskRows.forEach(row => { // 获取任务名称 const nameElement = row.querySelector('.folder-name [class*="cursor-"]'); if (!nameElement) return; const taskName = nameElement.textContent.trim(); if (!taskName) return; // 定位操作按钮容器 const actionContainer = row.querySelector('.q-btn-group, .options-container, [class*="actions-"]'); if (!actionContainer) return; // 避免重复添加按钮 if (actionContainer.querySelector('.schedule-btn')) return; // 创建定时按钮 const scheduleBtn = document.createElement('button'); scheduleBtn.className = 'schedule-btn'; scheduleBtn.title = '定时设置'; scheduleBtn.innerHTML = '<i class="material-icons schedule-icon">access_time</i>定时'; scheduleBtn.addEventListener('click', (e) => { e.stopPropagation(); showScheduleDialog(taskName); }); actionContainer.appendChild(scheduleBtn); // 添加定时状态指示器 const existingIndicator = row.querySelector('.schedule-indicator'); if (existingIndicator) existingIndicator.remove(); const rule = scheduleRules[taskName]; if (rule && rule.enabled) { // 尝试多种位置添加,确保能显示 let statusContainer = row.querySelector('.folder-status, [class*="status-"]'); // 如果找不到状态容器,尝试添加到任务名称后面 if (!statusContainer) { statusContainer = row.querySelector('.folder-name'); } // 如果还是找不到,尝试添加到整行的最后一个单元格 if (!statusContainer) { const cells = row.querySelectorAll('td'); if (cells.length > 0) { statusContainer = cells[cells.length - 1]; } } if (statusContainer) { const indicator = document.createElement('span'); indicator.className = 'schedule-indicator'; indicator.innerHTML = `<i class="material-icons" style="font-size:14px">access_time</i> 已设置 ${rule.periods?.length || 0} 个时段`; // 确保状态容器有足够的显示空间 statusContainer.style.whiteSpace = 'nowrap'; statusContainer.style.overflow = 'visible'; statusContainer.appendChild(indicator); } } }); } /** * 更新所有定时按钮和指示器 */ function updateScheduleButtons() { // 移除现有按钮和指示器 document.querySelectorAll('.schedule-btn').forEach(btn => btn.remove()); document.querySelectorAll('.schedule-indicator').forEach(ind => ind.remove()); // 重新添加 addScheduleButtons(); } /** * 检查当前时间是否在指定的时间段内 */ function isInTimePeriod(currentTime, period) { const { startTime, stopTime } = period; if (startTime <= stopTime) { // 当天内(如08:00~18:00) return currentTime >= startTime && currentTime < stopTime; } else { // 跨天(如22:00~06:00) return currentTime >= startTime || currentTime < stopTime; } } /** * 检查重复周期是否匹配 */ function isRepeatMatch(dayOfWeek, repeat) { switch (repeat) { case 'daily': return true; case 'weekdays': return dayOfWeek >= 1 && dayOfWeek <= 5; // 周一到周五 case 'weekends': return dayOfWeek === 0 || dayOfWeek === 6; // 周六到周日 default: return false; } } /** * 执行定时任务检查(启动/暂停任务) */ function executeSchedules() { const now = new Date(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); const currentTime = `${hours}:${minutes}`; const dayOfWeek = now.getDay(); Object.entries(scheduleRules).forEach(([taskName, taskRule]) => { // 任务级别的启用开关 if (!taskRule.enabled) return; // 检查是否有任何时间段匹配当前时间 let shouldRun = false; (taskRule.periods || []).forEach(period => { // 时间段级别的启用开关 if (!period.enabled) return; // 检查重复周期和时间范围 if (isRepeatMatch(dayOfWeek, period.repeat) && isInTimePeriod(currentTime, period)) { shouldRun = true; } }); // 查找任务行 const taskRow = Array.from(document.querySelectorAll('tr:has(.folder-name)')).find(row => { const nameEl = row.querySelector('.folder-name [class*="cursor-"]'); return nameEl && nameEl.textContent.trim() === taskName; }); if (!taskRow) return; // 查找控制按钮 const controlBtns = taskRow.querySelectorAll('.q-btn-group button, .options-container button'); let targetBtn = null; for (const btn of controlBtns) { const iconText = btn.querySelector('i')?.textContent; if (iconText === 'pause_circle_outline' || iconText === 'play_circle_outline') { targetBtn = btn; break; } } if (!targetBtn) return; // 判断当前状态 const isRunning = targetBtn.querySelector('i').textContent === 'pause_circle_outline'; // 执行操作 if (shouldRun) { if (!isRunning) { targetBtn.click(); console.log(`[定时] 启动任务: ${taskName}(当前时间:${currentTime})`); } } else { if (isRunning) { targetBtn.click(); console.log(`[定时] 暂停任务: ${taskName}(当前时间:${currentTime})`); } } }); } /** * 启动定时器 */ function startScheduleTimer() { if (scheduleTimer) clearInterval(scheduleTimer); // 每分钟检查一次 scheduleTimer = setInterval(executeSchedules, 60 * 1000); executeSchedules(); // 立即执行一次 } /** * 初始化脚本 */ function init() { console.log('微力同步定时脚本(多时间段版)初始化完成'); // 定位任务列表容器 const taskContainer = document.querySelector('.q-table tbody, [class*="task-list-"]'); const observeTarget = taskContainer || document.body; // 监听任务列表变化 const observer = new MutationObserver((mutations) => { if (mutations.some(m => m.addedNodes.length > 0 || m.removedNodes.length > 0)) { addScheduleButtons(); } }); observer.observe(observeTarget, { childList: true, subtree: true, attributes: false }); // 初始添加按钮 addScheduleButtons(); // 启动定时器 startScheduleTimer(); // 添加脚本状态提示 setTimeout(() => { const statusContainer = document.querySelector('.vsync-footer, .status-bar, .footer'); if (statusContainer) { const statusEl = document.createElement('div'); statusEl.className = 'schedule-status'; statusEl.innerHTML = '<i class="material-icons" style="font-size:14px">access_time</i> 多时段定时功能运行中'; statusContainer.appendChild(statusEl); } }, 2000); } // 页面加载完成后初始化 if (document.readyState === 'complete') { init(); } else { window.addEventListener('load', init); } })();