// ==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);
}
})();