From 27e0985e2cdfb8149676a2056a52fc40b44480c7 Mon Sep 17 00:00:00 2001 From: Michael Kane <15847202+jhhd88@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:38:24 +0800 Subject: [PATCH] Update LDStatus.user.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 模块化结构:将代码组织为清晰的模块,包括配置、UI、数据处理、事件处理和存储管理,提高了代码的可维护性和可读性。 性能优化: 使用 transform 而非直接修改 left/top 属性进行拖拽,减少重排 合理缓存数据,减少不必要的网络请求 清理过时的历史数据,避免存储溢出 用户体验增强: 添加进度条直观显示完成情况 添加深色/浊色主题切换功能 数据变化高亮显示 改进数据加载和错误处理流程 添加24小时和48小时统计对比,以及趋势指示 可靠性提升: 添加错误处理和离线模式支持 数据请求增加超时处理 自动检查脚本更新功能 代码质量提升: 使用常量配置而非硬编码值 添加详细注释 统一代码风格和命名规范 --- LDStatus.user.js | 1900 +++++++++++++++++++++++++--------------------- 1 file changed, 1029 insertions(+), 871 deletions(-) diff --git a/LDStatus.user.js b/LDStatus.user.js index 974787a..89ab78c 100644 --- a/LDStatus.user.js +++ b/LDStatus.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name LDStatus // @namespace http://tampermonkey.net/ -// @version 1.9 +// @version 2.0 // @description 在 Linux.do 页面显示信任级别进度 // @author 1e0n // @match https://linux.do/* @@ -19,958 +19,1116 @@ (function() { 'use strict'; - // 创建样式 - 使用更特定的选择器以避免影响帖子界面的按钮 - const style = document.createElement('style'); - style.textContent = ` - /* 深色主题 */ - #ld-trust-level-panel.ld-dark-theme { - background-color: #2d3748; - color: #e2e8f0; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); - } - - #ld-trust-level-panel.ld-dark-theme #ld-trust-level-header { - background-color: #1a202c; - color: white; - } - - #ld-trust-level-panel.ld-dark-theme .ld-trust-level-item.ld-success .ld-value { - color: #68d391; - } + // 模块化结构 + const LDStatus = { + // 配置 + config: { + refreshInterval: 300000, // 5分钟刷新一次 + storageKeys: { + position: 'ld_panel_position', + collapsed: 'ld_panel_collapsed', + theme: 'ld_panel_theme', + lastData: 'ld_last_successful_data' + }, + maxStorageItems: 500, + statsToTrack: [ + '浏览的话题(所有时间)', + '回复的话题', + '已读帖子(所有时间)', + '获赞:点赞用户数量', + '点赞' + ], + nameMapping: { + '已读帖子(所有时间)': '已读帖子(总)', + '浏览的话题(所有时间)': '浏览话题(总)', + '获赞:点赞用户数量': '点赞用户数', + '获赞:单日最高数量': '单日最高获赞', + '被禁言(过去 6 个月)': '被禁言', + '被封禁(过去 6 个月)': '被封禁' + } + }, + + // 变量 + vars: { + panel: null, + header: null, + content: null, + toggleBtn: null, + refreshBtn: null, + updateBtn: null, + themeBtn: null, + isDragging: false, + lastX: 0, + lastY: 0, + refreshTimer: null, + previousRequirements: [], + isDarkTheme: true + }, + + // UI相关方法 + ui: { + // 创建CSS样式 + createStyles: function() { + const style = document.createElement('style'); + style.textContent = ` + /* CSS变量 - 主题颜色 */ + :root { + --ld-bg-color: #ffffff; + --ld-text-color: #1a202c; + --ld-header-bg: #3182ce; + --ld-header-color: #ffffff; + --ld-success-color: #276749; + --ld-fail-color: #c53030; + --ld-border-color: #e2e8f0; + --ld-shadow: 0 0 10px rgba(0, 0, 0, 0.15); + --ld-secondary-color: #4a5568; + --ld-increase-color: #d69e2e; + --ld-decrease-color: #2b6cb0; + --ld-day1-color: #276749; + --ld-day2-color: #2d3748; + } - #ld-trust-level-panel.ld-dark-theme .ld-trust-level-item.ld-fail .ld-value { - color: #fc8181; - } + .ld-dark-theme { + --ld-bg-color: #2d3748; + --ld-text-color: #e2e8f0; + --ld-header-bg: #1a202c; + --ld-header-color: #ffffff; + --ld-success-color: #68d391; + --ld-fail-color: #fc8181; + --ld-border-color: #4a5568; + --ld-shadow: 0 0 10px rgba(0, 0, 0, 0.4); + --ld-secondary-color: #a0aec0; + --ld-increase-color: #ffd700; + --ld-decrease-color: #4299e1; + --ld-day1-color: #68d391; + --ld-day2-color: #cbd5e1; + } - #ld-trust-level-panel.ld-dark-theme .ld-loading { - color: #a0aec0; - } + /* 面板基础样式 */ + #ld-trust-level-panel { + position: fixed; + left: 10px; + top: 100px; + width: 210px; + border-radius: 8px; + z-index: 9999; + font-family: Arial, sans-serif; + transition: all 0.3s ease; + overflow: hidden; + font-size: 12px; + background-color: var(--ld-bg-color); + color: var(--ld-text-color); + box-shadow: var(--ld-shadow); + border: 1px solid var(--ld-border-color); + } - #ld-trust-level-panel.ld-dark-theme .ld-daily-stats-title { - color: #a0aec0; - } + #ld-trust-level-header { + padding: 8px 10px; + cursor: move; + display: flex; + justify-content: space-between; + align-items: center; + user-select: none; + background-color: var(--ld-header-bg); + color: var(--ld-header-color); + } - #ld-trust-level-panel.ld-dark-theme .ld-daily-stats-item .ld-value { - color: #68d391; - } + .ld-header-content { + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + white-space: nowrap; + } - #ld-trust-level-panel.ld-dark-theme .ld-version { - color: #a0aec0; - } + .ld-header-content > span:first-child { + margin-right: auto; + font-weight: bold; + } - /* 亮色主题 - 提高对比度 */ - #ld-trust-level-panel.ld-light-theme { - background-color: #ffffff; - color: #1a202c; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); - border: 1px solid #e2e8f0; - } + #ld-trust-level-content { + padding: 10px; + max-height: none; + overflow-y: visible; + } - #ld-trust-level-panel.ld-light-theme #ld-trust-level-header { - background-color: #3182ce; /* 更深的蓝色 */ - color: #ffffff; - border-bottom: 1px solid #2c5282; /* 添加底部边框 */ - } + .ld-trust-level-item { + margin-bottom: 6px; + display: flex; + white-space: nowrap; + width: 100%; + justify-content: space-between; + } - #ld-trust-level-panel.ld-light-theme .ld-trust-level-item.ld-success .ld-value { - color: #276749; /* 更深的绿色 */ - font-weight: bold; - } + .ld-trust-level-item .ld-name { + flex: 0 1 auto; + overflow: hidden; + text-overflow: ellipsis; + max-width: 60%; + } - #ld-trust-level-panel.ld-light-theme .ld-trust-level-item.ld-fail .ld-value { - color: #c53030; - font-weight: bold; - } + .ld-trust-level-item .ld-value { + font-weight: bold; + flex: 0 0 auto; + text-align: right; + min-width: 70px; + } - /* 亮色主题下的文本颜色 */ - #ld-trust-level-panel.ld-light-theme .ld-name { - color: #2d3748; /* 深灰色 */ - } + .ld-trust-level-item.ld-success .ld-value { + color: var(--ld-success-color); + } - #ld-trust-level-panel.ld-light-theme .ld-loading { - color: #4a5568; - } + .ld-trust-level-item.ld-fail .ld-value { + color: var(--ld-fail-color); + } - #ld-trust-level-panel.ld-light-theme .ld-daily-stats-title { - color: #4a5568; - font-weight: bold; - } + .ld-toggle-btn, .ld-refresh-btn, .ld-update-btn, .ld-theme-btn { + background: none; + border: none; + color: var(--ld-header-color); + cursor: pointer; + font-size: 14px; + margin-left: 5px; + } - #ld-trust-level-panel.ld-light-theme .ld-daily-stats-item .ld-value { - color: #2c7a4b; - font-weight: bold; - } + .ld-version { + font-size: 10px; + color: var(--ld-secondary-color); + margin-left: 5px; + font-weight: normal; + } - #ld-trust-level-panel.ld-light-theme .ld-version { - color: #e2e8f0; - } + .ld-collapsed { + width: 40px !important; + height: 40px !important; + min-width: 40px !important; + max-width: 40px !important; + border-radius: 8px; + overflow: hidden; + transform: none !important; + } - /* 共用样式 */ - #ld-trust-level-panel { - position: fixed; - left: 10px; - top: 100px; - width: 210px; - border-radius: 8px; - z-index: 9999; - font-family: Arial, sans-serif; - transition: all 0.3s ease; - overflow: hidden; - font-size: 12px; - } + .ld-collapsed #ld-trust-level-header { + justify-content: center; + width: 40px !important; + height: 40px !important; + min-width: 40px !important; + max-width: 40px !important; + padding: 0; + display: flex; + align-items: center; + } - #ld-trust-level-header { - padding: 8px 10px; - cursor: move; - display: flex; - justify-content: space-between; - align-items: center; - user-select: none; - } + .ld-collapsed #ld-trust-level-header > div { + justify-content: center; + width: 100%; + height: 100%; + } - .ld-header-content { - display: flex; - width: 100%; - align-items: center; - justify-content: space-between; - white-space: nowrap; - } + .ld-collapsed #ld-trust-level-content { + display: none !important; + } - .ld-header-content > span:first-child { - margin-right: auto; - font-weight: bold; - } + .ld-collapsed .ld-header-content > span, + .ld-collapsed .ld-refresh-btn, + .ld-collapsed .ld-update-btn, + .ld-collapsed .ld-theme-btn, + .ld-collapsed .ld-version { + display: none !important; + } - #ld-trust-level-content { - padding: 10px; - max-height: none; - overflow-y: visible; - } + .ld-collapsed .ld-toggle-btn { + margin: 0; + font-size: 16px; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + } - .ld-trust-level-item { - margin-bottom: 6px; - display: flex; - white-space: nowrap; - width: 100%; - justify-content: space-between; - } + .ld-loading { + text-align: center; + padding: 10px; + color: var(--ld-secondary-color); + } - .ld-trust-level-item .ld-name { - flex: 0 1 auto; - overflow: hidden; - text-overflow: ellipsis; - max-width: 60%; - } + .ld-increase { + color: var(--ld-increase-color); + } - .ld-trust-level-item .ld-value { - font-weight: bold; - flex: 0 0 auto; - text-align: right; - min-width: 70px; - } + .ld-decrease { + color: var(--ld-decrease-color); + } - /* 这些样式已移动到主题特定样式中 */ + /* 活动数据区域 */ + .ld-daily-stats { + margin-top: 10px; + font-size: 11px; + border-top: 1px solid var(--ld-border-color); + padding-top: 10px; + } - .ld-toggle-btn, .ld-refresh-btn, .ld-update-btn, .ld-theme-btn { - background: none; - border: none; - color: white; - cursor: pointer; - font-size: 14px; - margin-left: 5px; - } + .ld-daily-stats-title { + font-weight: bold; + margin-bottom: 5px; + color: var(--ld-secondary-color); + } - .ld-version { - font-size: 10px; - color: #a0aec0; - margin-left: 5px; - font-weight: normal; - } + .ld-daily-stats-item { + display: flex; + justify-content: space-between; + margin-bottom: 4px; + } - .ld-collapsed { - width: 40px !important; - height: 40px !important; - min-width: 40px !important; - max-width: 40px !important; - border-radius: 8px; - overflow: hidden; - transform: none !important; - } + .ld-daily-stats-item .ld-name { + flex: 0 1 auto; + color: inherit; + } - .ld-collapsed #ld-trust-level-header { - justify-content: center; - width: 40px !important; - height: 40px !important; - min-width: 40px !important; - max-width: 40px !important; - padding: 0; - display: flex; - align-items: center; - } + .ld-daily-stats-item .ld-value { + flex: 0 0 auto; + font-weight: bold; + color: inherit; + } - .ld-collapsed #ld-trust-level-header > div { - justify-content: center; - width: 100%; - height: 100%; - } + /* 两天数据的样式 */ + .ld-dual-stats { + display: flex; + justify-content: flex-end; + gap: 5px; + min-width: 70px; + text-align: right; + } - .ld-collapsed #ld-trust-level-content { - display: none !important; - } + .ld-day-stat { + min-width: 25px; + width: 25px; + text-align: right; + display: inline-block; + } - .ld-collapsed .ld-header-content > span, - .ld-collapsed .ld-refresh-btn, - .ld-collapsed .ld-update-btn, - .ld-collapsed .ld-theme-btn, - .ld-collapsed .ld-version { - display: none !important; - } + .ld-day1 { + color: var(--ld-day1-color); + } - .ld-collapsed .ld-toggle-btn { - margin: 0; - font-size: 16px; - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; - } + .ld-day2 { + color: var(--ld-day2-color); + } - .ld-loading { - text-align: center; - padding: 10px; - } + .ld-trend-indicator { + margin-left: 2px; + display: inline-block; + min-width: 25px; + width: 25px; + text-align: left; + } - /* 深色主题下的变化指示器 */ - .ld-dark-theme .ld-increase { - color: #ffd700; /* 黄色 */ - } + .ld-stats-header { + display: flex; + justify-content: space-between; + margin-bottom: 6px; + font-size: 10px; + color: inherit; + } - .ld-dark-theme .ld-decrease { - color: #4299e1; /* 蓝色 */ - } + .ld-stats-header-cols { + display: flex; + gap: 5px; + min-width: 70px; + justify-content: flex-end; + } - /* 亮色主题下的变化指示器 */ - .ld-light-theme .ld-increase { - color: #d69e2e; /* 深黄色 */ - font-weight: bold; - } + .ld-stats-header-col { + min-width: 25px; + width: 25px; + text-align: center; + } - .ld-light-theme .ld-decrease { - color: #2b6cb0; /* 深蓝色 */ - font-weight: bold; - } + .ld-stats-header-trend { + min-width: 25px; + width: 25px; + text-align: center; + } - /* 所有主题下的活动数据区域 */ - .ld-daily-stats { - margin-top: 10px; - font-size: 11px; - } + /* 进度条样式 */ + .ld-progress-container { + height: 3px; + background-color: var(--ld-border-color); + border-radius: 2px; + margin-top: 3px; + overflow: hidden; + } - /* 深色主题下的分隔线 */ - .ld-dark-theme .ld-daily-stats { - border-top: 1px solid #4a5568; - padding-top: 10px; - } + .ld-progress-bar { + height: 100%; + background-color: var(--ld-success-color); + border-radius: 2px; + transition: width 0.3s; + } - /* 亮色主题下的分隔线 */ - .ld-light-theme .ld-daily-stats { - border-top: 1px solid #cbd5e0; - padding-top: 10px; - } + .ld-fail .ld-progress-bar { + background-color: var(--ld-fail-color); + } - .ld-daily-stats-title { - font-weight: bold; - margin-bottom: 5px; - } + .ld-notice { + font-size: 10px; + color: var(--ld-secondary-color); + text-align: center; + margin-top: 5px; + padding-top: 5px; + border-top: 1px dashed var(--ld-border-color); + } + `; + document.head.appendChild(style); + }, - .ld-daily-stats-item { - display: flex; - justify-content: space-between; - margin-bottom: 4px; - } + // 创建面板 + createPanel: function() { + // 创建主面板 + const panel = document.createElement('div'); + panel.id = 'ld-trust-level-panel'; + LDStatus.vars.panel = panel; + + // 设置默认主题 + const currentTheme = GM_getValue(LDStatus.config.storageKeys.theme, 'dark'); + LDStatus.vars.isDarkTheme = currentTheme === 'dark'; + panel.classList.add(currentTheme === 'dark' ? 'ld-dark-theme' : 'ld-light-theme'); + + // 获取脚本版本号 + const scriptVersion = GM_info.script.version; + + // 创建面板头部 + const header = document.createElement('div'); + header.id = 'ld-trust-level-header'; + header.innerHTML = ` +
+ Status + v${scriptVersion} + + + + +
+ `; + LDStatus.vars.header = header; + + // 创建内容区域 + const content = document.createElement('div'); + content.id = 'ld-trust-level-content'; + content.innerHTML = '
加载中...
'; + LDStatus.vars.content = content; + + // 组装面板 + panel.appendChild(header); + panel.appendChild(content); + document.body.appendChild(panel); + + // 设置按钮引用 + LDStatus.vars.toggleBtn = header.querySelector('.ld-toggle-btn'); + LDStatus.vars.refreshBtn = header.querySelector('.ld-refresh-btn'); + LDStatus.vars.updateBtn = header.querySelector('.ld-update-btn'); + LDStatus.vars.themeBtn = header.querySelector('.ld-theme-btn'); + + // 更新主题按钮图标 + this.updateThemeButtonIcon(); + }, - .ld-daily-stats-item .ld-name { - flex: 0 1 auto; - color: inherit; - } + // 更新主题按钮图标 + updateThemeButtonIcon: function() { + const themeBtn = LDStatus.vars.themeBtn; + if (!themeBtn) return; - .ld-daily-stats-item .ld-value { - flex: 0 0 auto; - font-weight: bold; - color: inherit; - } + const isDarkTheme = LDStatus.vars.panel.classList.contains('ld-dark-theme'); + themeBtn.textContent = isDarkTheme ? '🌙' : '☀️'; // 月亮或太阳图标 + themeBtn.title = isDarkTheme ? '切换为亮色主题' : '切换为深色主题'; + }, - /* 添加两天数据的样式 */ - .ld-dual-stats { - display: flex; - justify-content: flex-end; - gap: 5px; - min-width: 70px; - text-align: right; - } + // 显示通知 + showNotice: function(message, type = 'info', duration = 3000) { + const noticeEl = document.createElement('div'); + noticeEl.className = `ld-notice ld-notice-${type}`; + noticeEl.textContent = message; + LDStatus.vars.content.appendChild(noticeEl); + + if (duration > 0) { + setTimeout(() => { + if (noticeEl.parentNode) { + noticeEl.parentNode.removeChild(noticeEl); + } + }, duration); + } - .ld-day-stat { - min-width: 25px; - width: 25px; - text-align: right; - display: inline-block; - } + return noticeEl; + }, - .ld-day1 { - color: #68d391; /* 跟上部一致的绿色 */ - } + // 创建进度条 + createProgressBar: function(current, required) { + const currentNum = parseInt(current.match(/\d+/)[0], 10); + const requiredNum = parseInt(required.match(/\d+/)[0], 10); + const percent = Math.min(100, Math.floor((currentNum / requiredNum) * 100)); + + return ` +
+
+
+ `; + }, - .ld-day2 { - color: #a0aec0; /* 跟上部一致的灰色 */ - } + // 渲染信任级别数据 + renderTrustLevelData: function(username, targetLevel, requirements, isMeetingRequirements, dailyChanges = {}) { + const content = LDStatus.vars.content; + + let html = ` +
+ ${username} - 信任级别 ${targetLevel} +
+
+ ${isMeetingRequirements ? '已' : '未'}符合信任级别 ${targetLevel} 要求 +
+ `; + + requirements.forEach(req => { + // 简化项目名称 + let name = req.name; + // 使用配置中的名称映射简化名称 + Object.entries(LDStatus.config.nameMapping).forEach(([original, simplified]) => { + name = name.replace(original, simplified); + }); + + // 提取数字部分以简化显示 + let current = req.current; + let required = req.required; + + // 尝试从字符串中提取数字 + const currentMatch = req.current.match(/(\d+)/); + const requiredMatch = req.required.match(/(\d+)/); + + if (currentMatch) current = currentMatch[1]; + if (requiredMatch) required = requiredMatch[1]; + + // 添加目标完成数变化的标识 + let changeIndicator = ''; + if (req.hasChanged) { + const diff = req.changeValue; + if (diff > 0) { + changeIndicator = ` ▲${diff}`; + } else if (diff < 0) { + changeIndicator = ` ▼${Math.abs(diff)}`; + } + } - .ld-light-theme .ld-day1 { - color: #276749; /* 浅色主题下与主面板绿色一致 */ - } + html += ` +
+ ${name} + ${current}${changeIndicator} / ${required} +
+ ${LDStatus.ui.createProgressBar(current, required)} + `; + }); - .ld-light-theme .ld-day2 { - color: #2d3748; /* 浅色主题下更深的灰色 */ - } + // 添加近期活动数据显示 + html += ` +
+
近期的活动
+
+ + + 48h + 24h + + +
+ `; + + // 添加每个数据项 + const dailyStatsItems = [ + { name: '浏览话题', key: '浏览的话题(所有时间)' }, + { name: '回复话题', key: '回复的话题' }, + { name: '已读帖子', key: '已读帖子(所有时间)' }, + { name: '获得点赞', key: '获赞:点赞用户数量' }, + { name: '点赞帖子', key: '点赞' } + ]; + + dailyStatsItems.forEach(item => { + const data = dailyChanges[item.key] || { day1: 0, day2: 0, trend: 0 }; + + // 创建趋势指示器 + let trendIndicator = ''; + if (data.trend > 0) { + trendIndicator = `▲${Math.abs(data.trend)}`; + } else if (data.trend < 0) { + trendIndicator = `▼${Math.abs(data.trend)}`; + } else { + trendIndicator = `0`; + } - .ld-dark-theme .ld-day2 { - color: #cbd5e1; /* 深色主题下更亮的灰色,增强可读性 */ - } + html += ` +
+ ${item.name} + + + ${data.day2} + ${data.day1} + ${trendIndicator} + + +
+ `; + }); - .ld-trend-indicator { - margin-left: 2px; - display: inline-block; - min-width: 25px; - width: 25px; - text-align: left; - } + html += `
`; - .ld-stats-header { - display: flex; - justify-content: space-between; - margin-bottom: 6px; - font-size: 10px; - color: inherit; - } + // 检查是否使用缓存数据 + const cachedData = GM_getValue(LDStatus.config.storageKeys.lastData, null); + if (cachedData && !navigator.onLine) { + html += ` +
+ 使用缓存数据,最后更新: ${new Date(cachedData.timestamp).toLocaleString()} +
+ `; + } - .ld-light-theme .ld-stats-header { - color: #2d3748; - } + content.innerHTML = html; + }, - .ld-dark-theme .ld-stats-header { - color: #e2e8f0; - } + // 渲染缓存数据 + renderCachedData: function() { + const cachedData = GM_getValue(LDStatus.config.storageKeys.lastData, null); + if (cachedData) { + this.renderTrustLevelData( + cachedData.username, + cachedData.targetLevel, + cachedData.requirements, + cachedData.isMeetingRequirements, + cachedData.dailyChanges + ); + this.showNotice(`使用缓存数据,最后更新: ${new Date(cachedData.timestamp).toLocaleString()}`); + } else { + LDStatus.vars.content.innerHTML = '
无可用数据,请检查网络连接
'; + } + } + }, + + // 数据处理相关方法 + data: { + // 获取信任级别数据 + fetchTrustLevelData: function() { + LDStatus.vars.content.innerHTML = '
加载中...
'; + + // 如果离线,使用缓存数据 + if (!navigator.onLine) { + LDStatus.ui.renderCachedData(); + return; + } - .ld-stats-header-cols { - display: flex; - gap: 5px; - min-width: 70px; - justify-content: flex-end; - } + GM_xmlhttpRequest({ + method: 'GET', + url: 'https://connect.linux.do', + timeout: 10000, // 设置超时时间 + onload: function(response) { + try { + if (response.status === 200) { + LDStatus.data.parseTrustLevelData(response.responseText); + } else { + throw new Error(`HTTP错误: ${response.status}`); + } + } catch (error) { + console.error('数据处理错误:', error); + LDStatus.vars.content.innerHTML = `
处理数据时出错: ${error.message}
`; + // 尝试使用缓存数据 + LDStatus.ui.renderCachedData(); + } + }, + onerror: function(error) { + console.error('请求错误:', error); + LDStatus.vars.content.innerHTML = '
网络请求失败,请检查网络连接
'; + // 尝试使用缓存数据 + LDStatus.ui.renderCachedData(); + }, + ontimeout: function() { + LDStatus.vars.content.innerHTML = '
请求超时,请稍后再试
'; + // 尝试使用缓存数据 + LDStatus.ui.renderCachedData(); + } + }); + }, - .ld-stats-header-col { - min-width: 25px; - width: 25px; - text-align: center; - } + // 解析信任级别数据 + parseTrustLevelData: function(html) { + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); - .ld-stats-header-trend { - min-width: 25px; - width: 25px; - text-align: center; - } - `; - document.head.appendChild(style); - - // 定义存储键 - const STORAGE_KEY_POSITION = 'ld_panel_position'; - const STORAGE_KEY_COLLAPSED = 'ld_panel_collapsed'; - const STORAGE_KEY_THEME = 'ld_panel_theme'; - - // 创建面板 - const panel = document.createElement('div'); - panel.id = 'ld-trust-level-panel'; - - // 设置默认主题 - const currentTheme = GM_getValue(STORAGE_KEY_THEME, 'dark'); - panel.classList.add(currentTheme === 'dark' ? 'ld-dark-theme' : 'ld-light-theme'); - - // 获取脚本版本号 - const scriptVersion = GM_info.script.version; - - // 创建面板头部 - const header = document.createElement('div'); - header.id = 'ld-trust-level-header'; - header.innerHTML = ` -
- Status - v${scriptVersion} - - - - -
- `; - - // 创建内容区域 - const content = document.createElement('div'); - content.id = 'ld-trust-level-content'; - content.innerHTML = '
加载中...
'; - - // 组装面板 - panel.appendChild(header); - panel.appendChild(content); - document.body.appendChild(panel); - - // 保存窗口位置的函数 - function savePanelPosition() { - const transform = window.getComputedStyle(panel).transform; - if (transform && transform !== 'none') { - const matrix = new DOMMatrix(transform); - GM_setValue(STORAGE_KEY_POSITION, { x: matrix.e, y: matrix.f }); - } - } - - // 保存窗口折叠状态的函数 - function savePanelCollapsedState() { - GM_setValue(STORAGE_KEY_COLLAPSED, panel.classList.contains('ld-collapsed')); - } - - // 恢复窗口状态 - function restorePanelState() { - // 恢复折叠状态 - const isCollapsed = GM_getValue(STORAGE_KEY_COLLAPSED, false); - if (isCollapsed) { - panel.classList.add('ld-collapsed'); - toggleBtn.textContent = '▶'; // 右箭头 - } else { - panel.classList.remove('ld-collapsed'); - toggleBtn.textContent = '◀'; // 左箭头 - } + // 查找信任级别区块 + const trustLevelSection = Array.from(doc.querySelectorAll('.bg-white.p-6.rounded-lg')).find(div => { + const heading = div.querySelector('h2'); + return heading && heading.textContent.includes('信任级别'); + }); - // 恢复位置 - const position = GM_getValue(STORAGE_KEY_POSITION, null); - if (position) { - panel.style.transform = `translate(${position.x}px, ${position.y}px)`; - } - } + if (!trustLevelSection) { + LDStatus.vars.content.innerHTML = '
未找到信任级别数据,请确保已登录
'; + return; + } - // 拖动功能 - let isDragging = false; - let lastX, lastY; - - header.addEventListener('mousedown', (e) => { - if (panel.classList.contains('ld-collapsed')) return; - - isDragging = true; - lastX = e.clientX; - lastY = e.clientY; - - // 添加拖动时的样式 - panel.style.transition = 'none'; - document.body.style.userSelect = 'none'; - }); - - document.addEventListener('mousemove', (e) => { - if (!isDragging) return; - - // 使用 transform 而不是改变 left/top 属性,性能更好 - const dx = e.clientX - lastX; - const dy = e.clientY - lastY; - - const currentTransform = window.getComputedStyle(panel).transform; - const matrix = new DOMMatrix(currentTransform === 'none' ? '' : currentTransform); - - const newX = matrix.e + dx; - const newY = matrix.f + dy; - - panel.style.transform = `translate(${newX}px, ${newY}px)`; - - lastX = e.clientX; - lastY = e.clientY; - }); - - document.addEventListener('mouseup', () => { - if (!isDragging) return; - - isDragging = false; - panel.style.transition = ''; - document.body.style.userSelect = ''; - - // 保存窗口位置 - savePanelPosition(); - }); - - // 展开/收起功能 - const toggleBtn = header.querySelector('.ld-toggle-btn'); - toggleBtn.addEventListener('click', () => { - panel.classList.toggle('ld-collapsed'); - toggleBtn.textContent = panel.classList.contains('ld-collapsed') ? '▶' : '◀'; - - // 保存折叠状态 - savePanelCollapsedState(); - }); - - // 刷新按钮 - const refreshBtn = header.querySelector('.ld-refresh-btn'); - refreshBtn.addEventListener('click', fetchTrustLevelData); - - // 检查更新按钮 - const updateBtn = header.querySelector('.ld-update-btn'); - updateBtn.addEventListener('click', checkForUpdates); - - // 主题切换按钮 - const themeBtn = header.querySelector('.ld-theme-btn'); - themeBtn.addEventListener('click', toggleTheme); - - // 更新主题按钮图标 - updateThemeButtonIcon(); - - // 切换主题函数 - function toggleTheme() { - const isDarkTheme = panel.classList.contains('ld-dark-theme'); - - // 切换主题类 - panel.classList.remove(isDarkTheme ? 'ld-dark-theme' : 'ld-light-theme'); - panel.classList.add(isDarkTheme ? 'ld-light-theme' : 'ld-dark-theme'); - - // 保存主题设置 - GM_setValue(STORAGE_KEY_THEME, isDarkTheme ? 'light' : 'dark'); - - // 更新主题按钮图标 - updateThemeButtonIcon(); - } - - // 更新主题按钮图标 - function updateThemeButtonIcon() { - const isDarkTheme = panel.classList.contains('ld-dark-theme'); - themeBtn.textContent = isDarkTheme ? '🌙' : '☀️'; // 月亮或太阳图标 - themeBtn.title = isDarkTheme ? '切换为亮色主题' : '切换为深色主题'; - - // 在亮色主题下调整按钮颜色 - if (!isDarkTheme) { - document.querySelectorAll('.ld-toggle-btn, .ld-refresh-btn, .ld-update-btn, .ld-theme-btn').forEach(btn => { - btn.style.color = 'white'; // 亮色主题下按钮使用白色,因为标题栏是蓝色 - btn.style.textShadow = '0 0 1px rgba(0,0,0,0.3)'; // 添加文字阴影增强可读性 - }); - } else { - document.querySelectorAll('.ld-toggle-btn, .ld-refresh-btn, .ld-update-btn, .ld-theme-btn').forEach(btn => { - btn.style.color = 'white'; - btn.style.textShadow = 'none'; - }); - } - } - - // 检查脚本更新 - function checkForUpdates() { - const updateURL = 'https://raw.githubusercontent.com/1e0n/LinuxDoStatus/master/LDStatus.user.js'; - - // 显示正在检查的状态 - updateBtn.textContent = '⌛'; // 沙漏图标 - updateBtn.title = '正在检查更新...'; - - GM_xmlhttpRequest({ - method: 'GET', - url: updateURL, - onload: function(response) { - if (response.status === 200) { - // 提取远程脚本的版本号 - const versionMatch = response.responseText.match(/@version\s+([\d\.]+)/); - if (versionMatch && versionMatch[1]) { - const remoteVersion = versionMatch[1]; - - // 比较版本 - if (remoteVersion > scriptVersion) { - // 有新版本 - updateBtn.textContent = '⚠️'; // 警告图标 - updateBtn.title = `发现新版本 v${remoteVersion},点击前往更新页面`; - updateBtn.style.color = '#ffd700'; // 黄色 - - // 点击按钮跳转到更新页面 - updateBtn.onclick = function() { - window.open(updateURL, '_blank'); - }; - } else { - // 已是最新版本 - updateBtn.textContent = '✔'; // 勾选图标 - updateBtn.title = '已是最新版本'; - updateBtn.style.color = '#68d391'; // 绿色 - - // 3秒后恢复原样式 - setTimeout(() => { - updateBtn.textContent = '🔎'; // 放大镜图标 - updateBtn.title = '检查更新'; - updateBtn.style.color = 'white'; - updateBtn.onclick = checkForUpdates; - }, 3000); + // 获取用户名和当前级别 + const heading = trustLevelSection.querySelector('h2').textContent.trim(); + const match = heading.match(/(.*) - 信任级别 (\d+) 的要求/); + const username = match ? match[1] : '未知用户'; + const targetLevel = match ? match[2] : '未知'; + + // 获取表格数据 + const tableRows = trustLevelSection.querySelectorAll('table tr'); + const requirements = []; + + for (let i = 1; i < tableRows.length; i++) { // 跳过表头 + const row = tableRows[i]; + const cells = row.querySelectorAll('td'); + + if (cells.length >= 3) { + const name = cells[0].textContent.trim(); + const current = cells[1].textContent.trim(); + const required = cells[2].textContent.trim(); + const isSuccess = cells[1].classList.contains('text-green-500'); + + // 提取当前完成数的数字部分 + const currentMatch = current.match(/(\d+)/); + const currentValue = currentMatch ? parseInt(currentMatch[1], 10) : 0; + // 查找上一次的数据记录 + let changeValue = 0; + let hasChanged = false; + + if (LDStatus.vars.previousRequirements.length > 0) { + const prevReq = LDStatus.vars.previousRequirements.find(pr => pr.name === name); + if (prevReq) { + // 如果完成数有变化,更新变化值 + if (currentValue !== prevReq.currentValue) { + changeValue = currentValue - prevReq.currentValue; + hasChanged = true; + } else if (prevReq.changeValue) { + // 如果完成数没有变化,但之前有变化值,保留之前的变化值 + changeValue = prevReq.changeValue; + hasChanged = true; + } + } } - } else { - handleUpdateError(); + + requirements.push({ + name, + current, + required, + isSuccess, + currentValue, + changeValue, // 变化值 + hasChanged // 是否有变化 + }); } - } else { - handleUpdateError(); } + + // 获取总体结果 + const resultText = trustLevelSection.querySelector('p.text-red-500, p.text-green-500'); + const isMeetingRequirements = resultText ? !resultText.classList.contains('text-red-500') : false; + + // 存储48小时内的数据变化 + const dailyChanges = this.saveDailyStats(requirements); + + // 缓存数据,以备网络问题时使用 + GM_setValue(LDStatus.config.storageKeys.lastData, { + username, + targetLevel, + requirements, + isMeetingRequirements, + dailyChanges, + timestamp: new Date().getTime() + }); + + // 渲染数据 + LDStatus.ui.renderTrustLevelData(username, targetLevel, requirements, isMeetingRequirements, dailyChanges); + + // 保存当前数据作为下次比较的基准 + LDStatus.vars.previousRequirements = [...requirements]; }, - onerror: handleUpdateError - }); - - // 处理更新检查错误 - function handleUpdateError() { - updateBtn.textContent = '❌'; // 错误图标 - updateBtn.title = '检查更新失败,请稍后再试'; - updateBtn.style.color = '#fc8181'; // 红色 - - // 3秒后恢复原样式 - setTimeout(() => { - updateBtn.textContent = '🔎'; // 放大镜图标 - updateBtn.title = '检查更新'; - updateBtn.style.color = 'white'; - }, 3000); - } - } - - // 获取信任级别数据 - function fetchTrustLevelData() { - content.innerHTML = '
加载中...
'; - - GM_xmlhttpRequest({ - method: 'GET', - url: 'https://connect.linux.do', - onload: function(response) { - if (response.status === 200) { - parseTrustLevelData(response.responseText); - } else { - content.innerHTML = '
获取数据失败,请稍后再试
'; + + // 存储48小时内的数据变化 + saveDailyStats: function(requirements) { + const statsToTrack = LDStatus.config.statsToTrack; + + // 获取当前时间 + const now = new Date().getTime(); + + // 从 localStorage 中获取已存储的数据 + let dailyStats = JSON.parse(localStorage.getItem('ld_daily_stats') || '[]'); + + // 删除超过48小时的数据 + const twoDaysAgo = now - 48 * 60 * 60 * 1000; + dailyStats = dailyStats.filter(item => item.timestamp > twoDaysAgo); + + // 对于每个要跟踪的数据项,找到当前值并添加到历史记录中 + statsToTrack.forEach(statName => { + const req = requirements.find(r => r.name === statName); + if (req) { + // 提取数字值 + const currentMatch = req.current.match(/(\d+)/); + const currentValue = currentMatch ? parseInt(currentMatch[1], 10) : 0; + + // 添加新的数据点 + dailyStats.push({ + name: statName, + value: currentValue, + timestamp: now + }); + } + }); + + // 清理过量的历史数据 + this.cleanupStorage(dailyStats); + + // 将更新后的数据保存回 localStorage + localStorage.setItem('ld_daily_stats', JSON.stringify(dailyStats)); + + return this.calculateDailyChanges(dailyStats); + }, + + // 清理过量的存储数据 + cleanupStorage: function(stats) { + const maxItems = LDStatus.config.maxStorageItems; + + if (stats.length > maxItems) { + // 按时间戳排序并只保留最新的数据 + stats.sort((a, b) => b.timestamp - a.timestamp); + return stats.slice(0, maxItems); } + + return stats; }, - onerror: function() { - content.innerHTML = '
获取数据失败,请稍后再试
'; - } - }); - } - - // 解析信任级别数据 - function parseTrustLevelData(html) { - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - - // 查找信任级别区块 - const trustLevelSection = Array.from(doc.querySelectorAll('.bg-white.p-6.rounded-lg')).find(div => { - const heading = div.querySelector('h2'); - return heading && heading.textContent.includes('信任级别'); - }); - - if (!trustLevelSection) { - content.innerHTML = '
未找到信任级别数据,请确保已登录
'; - return; - } - // 获取用户名和当前级别 - const heading = trustLevelSection.querySelector('h2').textContent.trim(); - const match = heading.match(/(.*) - 信任级别 (\d+) 的要求/); - const username = match ? match[1] : '未知用户'; - const targetLevel = match ? match[2] : '未知'; - - // 获取表格数据 - const tableRows = trustLevelSection.querySelectorAll('table tr'); - const requirements = []; - - for (let i = 1; i < tableRows.length; i++) { // 跳过表头 - const row = tableRows[i]; - const cells = row.querySelectorAll('td'); - - if (cells.length >= 3) { - const name = cells[0].textContent.trim(); - const current = cells[1].textContent.trim(); - const required = cells[2].textContent.trim(); - const isSuccess = cells[1].classList.contains('text-green-500'); - - // 提取当前完成数的数字部分 - const currentMatch = current.match(/(\d+)/); - const currentValue = currentMatch ? parseInt(currentMatch[1], 10) : 0; - - // 查找上一次的数据记录 - let changeValue = 0; - let hasChanged = false; - - if (previousRequirements.length > 0) { - const prevReq = previousRequirements.find(pr => pr.name === name); - if (prevReq) { - // 如果完成数有变化,更新变化值 - if (currentValue !== prevReq.currentValue) { - changeValue = currentValue - prevReq.currentValue; - hasChanged = true; - } else if (prevReq.changeValue) { - // 如果完成数没有变化,但之前有变化值,保留之前的变化值 - changeValue = prevReq.changeValue; - hasChanged = true; + // 计算近两天内的变化量 + calculateDailyChanges: function(dailyStats) { + const statsToTrack = LDStatus.config.statsToTrack; + const result = {}; + const now = new Date().getTime(); + const oneDayAgo = now - 24 * 60 * 60 * 1000; + const twoDaysAgo = now - 48 * 60 * 60 * 1000; + + // 对于每个要跟踪的数据项,计算两天内的变化 + statsToTrack.forEach(statName => { + // 过滤出当前数据项的所有记录,并按时间戳排序 + const statRecords = dailyStats + .filter(item => item.name === statName) + .sort((a, b) => a.timestamp - b.timestamp); + + // 初始化结果对象结构 + result[statName] = { + day1: 0, // 最近24小时 + day2: 0, // 24-48小时 + trend: 0 // 趋势:day1 - day2 + }; + + if (statRecords.length >= 2) { + // 找出最新记录和其前面两个时间段的记录 + const newest = statRecords[statRecords.length - 1]; + + // 找最近24小时内最早的记录 + const oldestDay1 = statRecords.filter(item => item.timestamp > oneDayAgo)[0]; + + // 找24-48小时内最早的记录和最新的记录 + const recordsDay2 = statRecords.filter(item => + item.timestamp <= oneDayAgo && item.timestamp > twoDaysAgo); + + const oldestDay2 = recordsDay2.length > 0 ? recordsDay2[0] : null; + const newestDay2 = recordsDay2.length > 0 ? + recordsDay2[recordsDay2.length - 1] : null; + + // 计算最近24小时的变化 + if (oldestDay1) { + result[statName].day1 = newest.value - oldestDay1.value; } + + // 计算24-48小时的变化 + if (oldestDay2 && newestDay2) { + result[statName].day2 = newestDay2.value - oldestDay2.value; + } + + // 计算趋势(今天和昨天的变化差异) + result[statName].trend = result[statName].day1 - result[statName].day2; } - } + }); + + return result; + }, - requirements.push({ - name, - current, - required, - isSuccess, - currentValue, - changeValue, // 变化值 - hasChanged // 是否有变化 + // 检查脚本更新 + checkForUpdates: function() { + const updateURL = 'https://raw.githubusercontent.com/1e0n/LinuxDoStatus/master/LDStatus.user.js'; + const updateBtn = LDStatus.vars.updateBtn; + + // 显示正在检查的状态 + updateBtn.textContent = '⌛'; // 沙漏图标 + updateBtn.title = '正在检查更新...'; + + GM_xmlhttpRequest({ + method: 'GET', + url: updateURL, + onload: function(response) { + if (response.status === 200) { + // 提取远程脚本的版本号 + const versionMatch = response.responseText.match(/@version\s+([\d\.]+)/); + if (versionMatch && versionMatch[1]) { + const remoteVersion = versionMatch[1]; + const scriptVersion = GM_info.script.version; + + // 比较版本 + if (remoteVersion > scriptVersion) { + // 有新版本 + updateBtn.textContent = '⚠️'; // 警告图标 + updateBtn.title = `发现新版本 v${remoteVersion},点击前往更新页面`; + updateBtn.style.color = 'var(--ld-increase-color)'; // 黄色 + + // 点击按钮跳转到更新页面 + updateBtn.onclick = function() { + window.open(updateURL, '_blank'); + }; + } else { + // 已是最新版本 + updateBtn.textContent = '✔'; // 勾选图标 + updateBtn.title = '已是最新版本'; + updateBtn.style.color = 'var(--ld-success-color)'; // 绿色 + + // 3秒后恢复原样式 + setTimeout(() => { + updateBtn.textContent = '🔎'; // 放大镜图标 + updateBtn.title = '检查更新'; + updateBtn.style.color = ''; + updateBtn.onclick = LDStatus.events.onUpdateBtnClick; + }, 3000); + } + } else { + LDStatus.data.handleUpdateError(); + } + } else { + LDStatus.data.handleUpdateError(); + } + }, + onerror: LDStatus.data.handleUpdateError }); + }, + + // 处理更新检查错误 + handleUpdateError: function() { + const updateBtn = LDStatus.vars.updateBtn; + updateBtn.textContent = '❌'; // 错误图标 + updateBtn.title = '检查更新失败,请稍后再试'; + updateBtn.style.color = 'var(--ld-fail-color)'; // 红色 + + // 3秒后恢复原样式 + setTimeout(() => { + updateBtn.textContent = '🔎'; // 放大镜图标 + updateBtn.title = '检查更新'; + updateBtn.style.color = ''; + }, 3000); } - } + }, + + // 事件处理相关方法 + events: { + // 注册所有事件监听 + registerEvents: function() { + // 拖动面板相关事件 + this.setupDragEvents(); + + // 按钮点击事件 + LDStatus.vars.toggleBtn.addEventListener('click', this.onToggleBtnClick); + LDStatus.vars.refreshBtn.addEventListener('click', this.onRefreshBtnClick); + LDStatus.vars.updateBtn.addEventListener('click', this.onUpdateBtnClick); + LDStatus.vars.themeBtn.addEventListener('click', this.onThemeBtnClick); + + // 页面可见性变化时刷新数据 + document.addEventListener('visibilitychange', this.onVisibilityChange); + }, + + // 设置拖动面板的事件 + setupDragEvents: function() { + const header = LDStatus.vars.header; + + header.addEventListener('mousedown', this.onPanelDragStart); + document.addEventListener('mousemove', this.onPanelDragMove); + document.addEventListener('mouseup', this.onPanelDragEnd); + }, + + // 面板拖动开始 + onPanelDragStart: function(e) { + if (LDStatus.vars.panel.classList.contains('ld-collapsed')) return; + + LDStatus.vars.isDragging = true; + LDStatus.vars.lastX = e.clientX; + LDStatus.vars.lastY = e.clientY; + + // 添加拖动时的样式 + LDStatus.vars.panel.style.transition = 'none'; + document.body.style.userSelect = 'none'; + }, + + // 面板拖动中 + onPanelDragMove: function(e) { + if (!LDStatus.vars.isDragging) return; + + // 使用 transform 而不是改变 left/top 属性,性能更好 + const dx = e.clientX - LDStatus.vars.lastX; + const dy = e.clientY - LDStatus.vars.lastY; + + const currentTransform = window.getComputedStyle(LDStatus.vars.panel).transform; + const matrix = new DOMMatrix(currentTransform === 'none' ? '' : currentTransform); + + const newX = matrix.e + dx; + const newY = matrix.f + dy; + + LDStatus.vars.panel.style.transform = `translate(${newX}px, ${newY}px)`; + + LDStatus.vars.lastX = e.clientX; + LDStatus.vars.lastY = e.clientY; + }, + + // 面板拖动结束 + onPanelDragEnd: function() { + if (!LDStatus.vars.isDragging) return; + + LDStatus.vars.isDragging = false; + LDStatus.vars.panel.style.transition = ''; + document.body.style.userSelect = ''; + + // 保存窗口位置 + LDStatus.storage.savePanelPosition(); + }, + + // 折叠/展开面板按钮点击 + onToggleBtnClick: function() { + const panel = LDStatus.vars.panel; + panel.classList.toggle('ld-collapsed'); + LDStatus.vars.toggleBtn.textContent = panel.classList.contains('ld-collapsed') ? '▶' : '◀'; + + // 保存折叠状态 + LDStatus.storage.savePanelCollapsedState(); + }, + + // 刷新按钮点击 + onRefreshBtnClick: function() { + LDStatus.data.fetchTrustLevelData(); + }, + + // 更新按钮点击 + onUpdateBtnClick: function() { + LDStatus.data.checkForUpdates(); + }, - // 获取总体结果 - const resultText = trustLevelSection.querySelector('p.text-red-500, p.text-green-500'); - const isMeetingRequirements = resultText ? !resultText.classList.contains('text-red-500') : false; - - // 存储48小时内的数据变化 - const dailyChanges = saveDailyStats(requirements); - - // 渲染数据 - renderTrustLevelData(username, targetLevel, requirements, isMeetingRequirements, dailyChanges); - - // 保存当前数据作为下次比较的基准 - previousRequirements = [...requirements]; - } - - // 渲染信任级别数据 - function renderTrustLevelData(username, targetLevel, requirements, isMeetingRequirements, dailyChanges = {}) { - let html = ` -
- ${username} - 信任级别 ${targetLevel} -
-
- ${isMeetingRequirements ? '已' : '未'}符合信任级别 ${targetLevel} 要求 -
- `; - - requirements.forEach(req => { - // 简化项目名称 - let name = req.name; - // 将一些常见的长名称缩短 - name = name.replace('已读帖子(所有时间)', '已读帖子(总)'); - name = name.replace('浏览的话题(所有时间)', '浏览话题(总)'); - name = name.replace('获赞:点赞用户数量', '点赞用户数'); - name = name.replace('获赞:单日最高数量', '单日最高获赞'); - name = name.replace('被禁言(过去 6 个月)', '被禁言'); - name = name.replace('被封禁(过去 6 个月)', '被封禁'); - - // 提取数字部分以简化显示 - let current = req.current; - let required = req.required; - - // 尝试从字符串中提取数字 - const currentMatch = req.current.match(/(\d+)/); - const requiredMatch = req.required.match(/(\d+)/); - - if (currentMatch) current = currentMatch[1]; - if (requiredMatch) required = requiredMatch[1]; - - // 添加目标完成数变化的标识 - let changeIndicator = ''; - if (req.hasChanged) { - const diff = req.changeValue; - if (diff > 0) { - changeIndicator = ` ▲${diff}`; // 增加标识,黄色 - } else if (diff < 0) { - changeIndicator = ` ▼${Math.abs(diff)}`; // 减少标识,蓝色 + // 主题按钮点击 + onThemeBtnClick: function() { + const panel = LDStatus.vars.panel; + const isDarkTheme = panel.classList.contains('ld-dark-theme'); + + // 切换主题类 + panel.classList.remove(isDarkTheme ? 'ld-dark-theme' : 'ld-light-theme'); + panel.classList.add(isDarkTheme ? 'ld-light-theme' : 'ld-dark-theme'); + + // 更新主题变量 + LDStatus.vars.isDarkTheme = !isDarkTheme; + + // 更新按钮图标 + LDStatus.ui.updateThemeButtonIcon(); + + // 保存主题设置 + GM_setValue(LDStatus.config.storageKeys.theme, LDStatus.vars.isDarkTheme ? 'dark' : 'light'); + }, + + // 页面可见性变化处理 + onVisibilityChange: function() { + if (!document.hidden) { + // 检查上次刷新时间,如果超过指定间隔则刷新数据 + const lastRefreshTime = LDStatus.vars.lastRefreshTime || 0; + const now = Date.now(); + + if (now - lastRefreshTime > LDStatus.config.refreshInterval) { + LDStatus.data.fetchTrustLevelData(); + LDStatus.vars.lastRefreshTime = now; + } } } + }, - html += ` -
- ${name} - ${current}${changeIndicator} / ${required} -
- `; - }); - - // 添加近期活动数据显示 - html += ` -
-
近期的活动
-
- - - 48h - 24h - - -
- `; - - // 添加每个数据项 - const dailyStatsItems = [ - { name: '浏览话题', key: '浏览的话题(所有时间)' }, - { name: '回复话题', key: '回复的话题' }, - { name: '已读帖子', key: '已读帖子(所有时间)' }, - { name: '获得点赞', key: '获赞:点赞用户数量' }, - { name: '点赞帖子', key: '点赞' } - ]; - - dailyStatsItems.forEach(item => { - const data = dailyChanges[item.key] || { day1: 0, day2: 0, trend: 0 }; - - // 创建趋势指示器 - let trendIndicator = ''; - if (data.trend > 0) { - trendIndicator = `▲${Math.abs(data.trend)}`; - } else if (data.trend < 0) { - trendIndicator = `▼${Math.abs(data.trend)}`; - } else { - trendIndicator = `0`; - } - - html += ` -
- ${item.name} - - - ${data.day2} - ${data.day1} - ${trendIndicator} - - -
- `; - }); - - html += `
`; - - content.innerHTML = html; - } - - // 存储上一次获取的数据,用于比较变化 - let previousRequirements = []; - - // 存储48小时内的数据变化 - function saveDailyStats(requirements) { - // 定义要跟踪的数据项 - const statsToTrack = [ - '浏览的话题(所有时间)', // 浏览话题总数 - '回复的话题', // 回复话题数 - '已读帖子(所有时间)', // 已读帖子总数 - '获赞:点赞用户数量', // 获赞数 - '点赞' // 点赞数 - ]; - - // 调试信息:输出所有数据项的名称 - console.log('数据项名称:', requirements.map(r => r.name)); - console.log('要跟踪的数据项:', statsToTrack); - - // 获取当前时间 - const now = new Date().getTime(); - - // 从 localStorage 中获取已存储的数据 - let dailyStats = JSON.parse(localStorage.getItem('ld_daily_stats') || '[]'); - - // 删除超过48小时的数据 - const twoDaysAgo = now - 48 * 60 * 60 * 1000; - dailyStats = dailyStats.filter(item => item.timestamp > twoDaysAgo); - - // 对于每个要跟踪的数据项,找到当前值并添加到历史记录中 - statsToTrack.forEach(statName => { - const req = requirements.find(r => r.name === statName); - console.log('在requirements中查找数据项:', statName, req ? '找到了' : '未找到'); - if (req) { - // 提取数字值 - const currentMatch = req.current.match(/(\d+)/); - const currentValue = currentMatch ? parseInt(currentMatch[1], 10) : 0; - console.log('数据项值:', statName, req.current, currentValue); - - // 添加新的数据点 - dailyStats.push({ - name: statName, - value: currentValue, - timestamp: now - }); - } - }); - - // 调试信息:输出保存的数据 - console.log('保存的dailyStats数据:', dailyStats); - - // 将更新后的数据保存回 localStorage - localStorage.setItem('ld_daily_stats', JSON.stringify(dailyStats)); - - return calculateDailyChanges(dailyStats); - } - - // 计算近两天内的变化量 - function calculateDailyChanges(dailyStats) { - // 定义要跟踪的数据项 - const statsToTrack = [ - '浏览的话题(所有时间)', // 浏览话题总数 - '回复的话题', // 回复话题数 - '已读帖子(所有时间)', // 已读帖子总数 - '获赞:点赞用户数量', // 获赞数 - '点赞' // 点赞数 - ]; - - const result = {}; - const now = new Date().getTime(); - const oneDayAgo = now - 24 * 60 * 60 * 1000; - const twoDaysAgo = now - 48 * 60 * 60 * 1000; - - // 对于每个要跟踪的数据项,计算两天内的变化 - statsToTrack.forEach(statName => { - // 过滤出当前数据项的所有记录,并按时间戳排序 - const statRecords = dailyStats - .filter(item => item.name === statName) - .sort((a, b) => a.timestamp - b.timestamp); - - // 初始化结果对象结构 - result[statName] = { - day1: 0, // 最近24小时 - day2: 0, // 24-48小时 - trend: 0 // 趋势:day1 - day2 - }; - - if (statRecords.length >= 2) { - // 找出最新记录和其前面两个时间段的记录 - const newest = statRecords[statRecords.length - 1]; - - // 找最近24小时内最早的记录 - const oldestDay1 = statRecords.filter(item => item.timestamp > oneDayAgo)[0]; - - // 找24-48小时内最早的记录和最新的记录 - const recordsDay2 = statRecords.filter(item => - item.timestamp <= oneDayAgo && item.timestamp > twoDaysAgo); - - const oldestDay2 = recordsDay2.length > 0 ? recordsDay2[0] : null; - const newestDay2 = recordsDay2.length > 0 ? - recordsDay2[recordsDay2.length - 1] : null; - - // 计算最近24小时的变化 - if (oldestDay1) { - result[statName].day1 = newest.value - oldestDay1.value; + // 存储相关方法 + storage: { + // 初始化存储 + initStorage: function() { + // 恢复面板位置 + this.restorePanelPosition(); + + // 恢复折叠状态 + this.restorePanelCollapsedState(); + }, + + // 保存面板位置 + savePanelPosition: function() { + const style = window.getComputedStyle(LDStatus.vars.panel); + const transform = style.transform; + + if (transform !== 'none') { + GM_setValue(LDStatus.config.storageKeys.position, transform); } + }, - // 计算24-48小时的变化 - if (oldestDay2 && newestDay2) { - result[statName].day2 = newestDay2.value - oldestDay2.value; + // 恢复面板位置 + restorePanelPosition: function() { + const savedPosition = GM_getValue(LDStatus.config.storageKeys.position, null); + + if (savedPosition) { + LDStatus.vars.panel.style.transform = savedPosition; } + }, + + // 保存面板折叠状态 + savePanelCollapsedState: function() { + const isCollapsed = LDStatus.vars.panel.classList.contains('ld-collapsed'); + GM_setValue(LDStatus.config.storageKeys.collapsed, isCollapsed); + }, + + // 恢复面板折叠状态 + restorePanelCollapsedState: function() { + const isCollapsed = GM_getValue(LDStatus.config.storageKeys.collapsed, false); - // 计算趋势(今天和昨天的变化差异) - result[statName].trend = result[statName].day1 - result[statName].day2; + if (isCollapsed) { + LDStatus.vars.panel.classList.add('ld-collapsed'); + LDStatus.vars.toggleBtn.textContent = '▶'; + } else { + LDStatus.vars.panel.classList.remove('ld-collapsed'); + LDStatus.vars.toggleBtn.textContent = '◀'; + } } - }); + }, + + // 初始化方法 + init: function() { + // 初始化 UI + this.ui.createStyles(); + this.ui.createPanel(); - console.log('dailyChanges result:', result); - return result; - } + // 初始化存储 + this.storage.initStorage(); - // 初始加载 - fetchTrustLevelData(); + // 注册事件 + this.events.registerEvents(); - // 恢复窗口状态和主题 - // 在所有DOM操作完成后执行,确保 toggleBtn 和 themeBtn 已经定义 - setTimeout(() => { - restorePanelState(); - updateThemeButtonIcon(); - }, 100); + // 获取数据 + this.data.fetchTrustLevelData(); + + // 设置定时刷新 + LDStatus.vars.refreshTimer = setInterval(function() { + LDStatus.data.fetchTrustLevelData(); + LDStatus.vars.lastRefreshTime = Date.now(); + }, this.config.refreshInterval); + + // 记录初始化时间 + LDStatus.vars.lastRefreshTime = Date.now(); + } + }; - // 定时刷新(每五分钟) - setInterval(fetchTrustLevelData, 300000); + // 初始化脚本 + LDStatus.init(); })();