// ==UserScript== // @name 做题计划管理器 // @namespace http://tampermonkey.net/ // @version 1.5 // @description 洛谷做题计划怎么不支持其他OJ?哦咦game不是这么玩的!你应该写一个做题计划管理脚本,后面忘了。 // @author Nuclear_Fish_cyq // @match *://*/* // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addValueChangeListener // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 只在顶层窗口中运行,避免在iframe中重复创建 if (window.self !== window.top) { return; } // 预定义颜色选项 const colorOptions = [ '#FE4C61', '#F39C11', '#FFC116', '#52C41A', '#3498DB', '#9D3DCF', '#0E1D69', '#BFBFBF' ]; // 存储键名 const STORAGE_KEY = 'problemPlanner_data'; const COUNT_KEY = 'problemPlanner_completedCount'; // 状态变量 let problems = []; let completedCount = 0; let isPanelVisible = false; // 从GM存储加载数据 function loadData() { try { const savedData = GM_getValue(STORAGE_KEY); problems = savedData ? JSON.parse(savedData) : []; const savedCount = GM_getValue(COUNT_KEY); completedCount = savedCount || 0; } catch (e) { console.error('加载数据失败:', e); problems = []; completedCount = 0; } } // 保存数据到GM存储 function saveData() { try { GM_setValue(STORAGE_KEY, JSON.stringify(problems)); GM_setValue(COUNT_KEY, completedCount); } catch (e) { console.error('保存数据失败:', e); } } // 监听其他标签页的数据变化 GM_addValueChangeListener(STORAGE_KEY, function(key, oldValue, newValue, remote) { if (remote) { // 数据来自其他标签页 try { problems = newValue ? JSON.parse(newValue) : []; if (isPanelVisible) { renderProblems(); } showSyncNotification(); } catch (e) { console.error('同步数据解析失败:', e); } } }); GM_addValueChangeListener(COUNT_KEY, function(key, oldValue, newValue, remote) { if (remote) { // 数据来自其他标签页 completedCount = newValue || 0; if (isPanelVisible) { updateCounters(); } } }); // 初始化加载数据 loadData(); // 显示同步提示 function showSyncNotification() { let notification = document.querySelector('.sync-notification'); if (!notification) { notification = document.createElement('div'); notification.className = 'sync-notification'; notification.style.cssText = ` position: fixed; top: 10px; right: 10px; background: #3498DB; color: white; padding: 10px 15px; border-radius: 5px; font-family: 'Segoe UI', sans-serif; font-size: 14px; z-index: 1000000; box-shadow: 0 2px 10px rgba(0,0,0,0.2); opacity: 0; transform: translateY(-20px); transition: opacity 0.3s, transform 0.3s; `; document.body.appendChild(notification); } notification.textContent = '数据已同步更新'; notification.style.opacity = '1'; notification.style.transform = 'translateY(0)'; setTimeout(() => { notification.style.opacity = '0'; notification.style.transform = 'translateY(-20px)'; }, 3000); } // 创建主样式 const style = document.createElement('style'); style.textContent = ` .problem-planner-container { position: fixed; bottom: 20px; left: 20px; z-index: 999999; font-family: 'Segoe UI', sans-serif; } .problem-planner-button { width: 60px; height: 60px; border-radius: 50%; background: linear-gradient(135deg, #00d2ff, #3a7bd5); color: white; border: none; cursor: pointer; font-weight: bold; font-size: 20px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); transition: transform 0.2s, box-shadow 0.2s; font-family: 'Segoe UI', sans-serif; } .problem-planner-button:hover { transform: scale(1.1); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); } .problem-planner-panel { position: absolute; bottom: 70px; left: 0; width: 420px; background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); display: none; overflow: hidden; font-family: 'Segoe UI', sans-serif; } .problem-planner-panel .panel-header { background: linear-gradient(135deg, #3a7bd5, #00d2ff); color: white; padding: 20px; text-align: center; font-weight: bold; font-size: 18px; position: relative; } .problem-planner-panel .add-problem-form { padding: 20px; border-bottom: 1px solid #eee; } .problem-planner-panel .form-group { margin-bottom: 15px; } .problem-planner-panel .form-group label { display: block; margin-bottom: 5px; font-weight: 600; color: #333; } .problem-planner-panel .form-input { width: 100%; padding: 10px; border: 2px solid #e0e0e0; border-radius: 6px; font-family: 'Segoe UI', sans-serif; font-size: 14px; box-sizing: border-box; } .problem-planner-panel .form-input:focus { outline: none; border-color: #3a7bd5; } .problem-planner-panel .color-selection { display: grid; grid-template-columns: repeat(8, 1fr); gap: 8px; margin-top: 10px; } .problem-planner-panel .color-option { width: 30px; height: 30px; border-radius: 6px; cursor: pointer; border: 2px solid transparent; transition: transform 0.2s, border-color 0.2s; } .problem-planner-panel .color-option:hover { transform: scale(1.1); } .problem-planner-panel .color-option.selected { border-color: #333 !important; transform: scale(1.1); box-shadow: 0 0 0 2px white, 0 0 0 4px #333 !important; } .problem-planner-panel .add-button { width: 100%; padding: 12px; background: linear-gradient(135deg, #52C41A, #3498DB); color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold; font-size: 16px; font-family: 'Segoe UI', sans-serif; transition: transform 0.2s; } .problem-planner-panel .add-button:hover { transform: translateY(-2px); } .problem-planner-panel .problems-list { max-height: 300px; overflow-y: auto; padding: 20px; } .problem-planner-panel .problem-item { display: flex; justify-content: space-between; align-items: center; padding: 12px; margin-bottom: 10px; background: #f8f9fa; border-radius: 8px; transition: transform 0.2s; } .problem-planner-panel .problem-item:hover { transform: translateX(5px); background: #e9ecef; } .problem-planner-panel .problem-link { text-decoration: none; font-weight: bold; font-size: 14px; display: block; word-break: break-all; flex-grow: 1; margin-right: 10px; } .problem-planner-panel .problem-actions { display: flex; gap: 8px; } .problem-planner-panel .giveup-btn { background: #BFBFBF; color: white; border: none; border-radius: 4px; width: 50px; height: 30px; cursor: pointer; font-weight: bold; font-family: 'Segoe UI', sans-serif; transition: background 0.2s; } .problem-planner-panel .giveup-btn:hover { background: #a0a0a0; } .problem-planner-panel .complete-btn { background: #52C41A; color: white; border: none; border-radius: 4px; width: 50px; height: 30px; cursor: pointer; font-weight: bold; font-family: 'Segoe UI', sans-serif; transition: background 0.2s; } .problem-planner-panel .complete-btn:hover { background: #3da814; } .problem-planner-panel .panel-footer { padding: 15px 20px; background: #f8f9fa; border-top: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; font-weight: 600; } .problem-planner-panel .stats { display: flex; flex-direction: column; gap: 5px; } .problem-planner-panel .stat-item { display: flex; align-items: center; gap: 8px; } .problem-planner-panel .stat-label { color: #666; } .problem-planner-panel .stat-value { color: #3a7bd5; font-size: 18px; font-weight: bold; } .problem-planner-panel .completed-count { color: #52C41A; } .problem-planner-panel .clear-btn { background: #FE4C61; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: bold; font-family: 'Segoe UI', sans-serif; transition: background 0.2s; } .problem-planner-panel .clear-btn:hover { background: #e43a4d; } .problem-planner-panel .empty-message { text-align: center; color: #999; padding: 40px 20px; font-style: italic; } .problem-planner-panel .sync-badge { position: absolute; right: 20px; top: 50%; transform: translateY(-50%); background: rgba(255, 255, 255, 0.2); padding: 2px 8px; border-radius: 10px; font-size: 12px; display: flex; align-items: center; gap: 5px; } .problem-planner-panel .sync-badge::before { content: '🔄'; } .problem-planner-panel .current-url-hint { font-size: 12px; color: #3498DB; margin-top: 5px; display: flex; align-items: center; gap: 5px; } .problem-planner-panel .current-url-hint::before { content: '📌'; } .problem-planner-panel .use-current-btn { background: transparent; border: 1px solid #3498DB; color: #3498DB; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 5px; transition: all 0.2s; } .problem-planner-panel .use-current-btn:hover { background: #3498DB; color: white; } `; document.head.appendChild(style); // 创建容器 const container = document.createElement('div'); container.className = 'problem-planner-container'; // 创建主按钮 const mainButton = document.createElement('button'); mainButton.className = 'problem-planner-button'; mainButton.textContent = '题'; mainButton.title = '打开做题计划(跨网站同步)'; // 创建面板 const panel = document.createElement('div'); panel.className = 'problem-planner-panel'; // 面板HTML结构 panel.innerHTML = `