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