// ==UserScript== // @name 国家基层糖尿病培训平台学习助手 // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description 国家基层糖尿病防治管理指南培训平台学习辅助插件 // @author duocaikun // @match https://www.jctnb.org.cn/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 创建悬浮窗样式 const style = document.createElement('style'); style.textContent = ` .study-duration-float { position: fixed; top: 20px; right: 20px; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.8) inset; z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 16px; min-width: 140px; text-align: center; border: 1px solid rgba(255, 255, 255, 0.3); transition: all 0.3s ease; cursor: move; user-select: none; opacity: 1; } .study-duration-float:hover { transform: translateY(-1px); box-shadow: 0 6px 25px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(255, 255, 255, 0.9) inset; } .study-duration-float.dragging { opacity: 0.7; transition: none; } .duration-title { font-size: 12px; font-weight: 600; color: #8E8E93; margin-bottom: 6px; } .duration-value { font-size: 22px; font-weight: 700; margin: 6px 0; line-height: 1; } .duration-unit { font-size: 12px; color: #8E8E93; font-weight: 500; margin-bottom: 10px; } .duration-progress { height: 3px; background: rgba(142, 142, 147, 0.16); border-radius: 1.5px; margin: 10px 0; overflow: hidden; } .duration-progress-bar { height: 100%; transition: width 0.5s ease, background 0.3s ease; width: 0%; } .duration-update-time { font-size: 10px; color: #C7C7CC; font-weight: 500; border-top: 1px solid rgba(142, 142, 147, 0.1); padding-top: 10px; margin-top: 10px; } .status-excellent .duration-value { color: #34C759; } .status-good .duration-value { color: #FF9F0A; } .status-poor .duration-value { color: #FF453A; } .status-offline .duration-value { color: #8E8E93; } .status-excellent .duration-progress-bar { background: linear-gradient(90deg, #34C759, #32D74B); } .status-good .duration-progress-bar { background: linear-gradient(90deg, #FFD60A, #FF9F0A); } .status-poor .duration-progress-bar { background: linear-gradient(90deg, #FF453A, #FF375F); } .status-offline .duration-progress-bar { background: #8E8E93; } `; document.head.appendChild(style); // 创建悬浮窗 const floatDiv = document.createElement('div'); floatDiv.className = 'study-duration-float'; floatDiv.innerHTML = `
学习时长
--
小时
更新中...
`; document.body.appendChild(floatDiv); // 获取DOM元素 const [valueEl, timeEl, progressBar] = [ '.duration-value', '.duration-update-time', '.duration-progress-bar' ].map(sel => floatDiv.querySelector(sel)); // 拖动功能 let isDragging = false, dragOffsetX = 0, dragOffsetY = 0; const dragHandlers = { start: (e) => { isDragging = true; floatDiv.classList.add('dragging'); const rect = floatDiv.getBoundingClientRect(); dragOffsetX = e.clientX - rect.left; dragOffsetY = e.clientY - rect.top; e.preventDefault(); }, move: (e) => { if (!isDragging) return; const x = e.clientX - dragOffsetX; const y = e.clientY - dragOffsetY; const maxX = window.innerWidth - floatDiv.offsetWidth; const maxY = window.innerHeight - floatDiv.offsetHeight; floatDiv.style.left = Math.max(0, Math.min(x, maxX)) + 'px'; floatDiv.style.top = Math.max(0, Math.min(y, maxY)) + 'px'; floatDiv.style.transform = 'none'; }, end: () => { if (!isDragging) return; isDragging = false; floatDiv.classList.remove('dragging'); const rect = floatDiv.getBoundingClientRect(); localStorage.setItem('studyDurationFloatPosition', JSON.stringify({ x: rect.left, y: rect.top })); } }; // 绑定事件 floatDiv.addEventListener('mousedown', dragHandlers.start); document.addEventListener('mousemove', dragHandlers.move); document.addEventListener('mouseup', dragHandlers.end); // 恢复位置 const savedPos = localStorage.getItem('studyDurationFloatPosition'); if (savedPos) { const {x, y} = JSON.parse(savedPos); floatDiv.style.left = x + 'px'; floatDiv.style.top = y + 'px'; floatDiv.style.right = 'auto'; } // 获取学习时长 function getStudyDuration() { if (!isLogin()) { updateDisplay('未登录', '请先登录', 0, 'offline'); return; } $.ajax({ url: "/api/User/getExaminationUserInfo", type: 'post', data: {token: getCookie('token')}, dataType: 'json', success: (data) => { if (data.code === 0 && data.data) { const duration = parseFloat(data.data.show_study_watch_duration) || 0; const progress = Math.min((duration / 9) * 100, 100); const status = duration >= 9 ? 'excellent' : duration >= 6 ? 'good' : 'poor'; updateDisplay(duration.toFixed(2), getCurrentTime(), progress, status); } else { updateDisplay('--', '获取失败', 0, 'offline'); } }, error: () => updateDisplay('--', '网络错误', 0, 'offline') }); } // 更新显示 function updateDisplay(duration, updateTime, progress, status) { valueEl.textContent = duration; timeEl.textContent = `更新于 ${updateTime}`; progressBar.style.width = `${progress}%`; floatDiv.className = `study-duration-float status-${status}`; } // 工具函数 const getCurrentTime = () => { const now = new Date(); return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`; }; const isLogin = () => getCookie('token') !== ''; const getCookie = (name) => { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); return parts.length === 2 ? parts.pop().split(';').shift() : ''; }; // 初始化 const init = () => { if (typeof $ !== 'undefined') { getStudyDuration(); setInterval(getStudyDuration, 5000); // 监听页面跳转 let currentUrl = location.href; setInterval(() => { if (location.href !== currentUrl) { currentUrl = location.href; setTimeout(getStudyDuration, 500); } }, 500); } else { setTimeout(init, 1000); } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();