为双拼练习页面添加可设置X分钟计时和正确率统计功能,按钮更明显地放在顶部菜单区域。
// ==UserScript==
// @name 双拼练习计时统计
// @namespace http://tampermonkey.net/
// @version 1.1
// @description 为双拼练习页面添加可设置X分钟计时和正确率统计功能,按钮更明显地放在顶部菜单区域。
// @author
// @match https://api.ihint.me/shuang*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
let attempts = [];
let timer = null;
let timeLeft = 300; // 默认5分钟
let isTiming = false;
let timerDisplay;
let startButton;
let inputMinutes; // 新增:用户输入分钟数
let waitForShuangInterval = setInterval(() => {
if (window.Shuang && window.$) {
clearInterval(waitForShuangInterval);
initTimerUI();
patchJudgeFunction();
}
}, 500);
function initTimerUI() {
// 尝试在页面顶部插入UI,比如与".header"同级或在header内部
const header = document.querySelector('.header');
if (!header) {
console.warn('Header not found, cannot insert timer UI. Will try body.');
}
// 创建一个容器,让UI更清晰
const container = document.createElement('div');
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.margin = '10px 0';
container.style.gap = '10px';
// 创建输入框用于设置分钟数
inputMinutes = document.createElement('input');
inputMinutes.type = 'number';
inputMinutes.min = 1;
inputMinutes.value = 5; // 默认5分钟
inputMinutes.style.width = '50px';
inputMinutes.title = '设置练习时长(分钟)';
// 创建"开始计时"按钮
startButton = document.createElement('button');
startButton.innerText = '开始计时练习';
startButton.title = '开始倒计时并统计正确率';
startButton.onclick = startPractice;
startButton.style.padding = '5px 10px';
startButton.style.cursor = 'pointer';
startButton.style.background = '#4CAF50';
startButton.style.color = '#fff';
startButton.style.border = 'none';
startButton.style.borderRadius = '4px';
// 创建显示剩余时间的元素
timerDisplay = document.createElement('div');
timerDisplay.style.fontSize = '16px';
timerDisplay.innerText = '剩余时间: 未开始';
timerDisplay.style.marginLeft = '10px';
// 将元素加入container
container.appendChild(document.createTextNode('设置时长(分钟):'));
container.appendChild(inputMinutes);
container.appendChild(startButton);
container.appendChild(timerDisplay);
// 将container插入header中
if (header) {
header.appendChild(container);
} else {
// 如果header不存在,就插入到body顶部
document.body.insertBefore(container, document.body.firstChild);
}
}
function startPractice() {
if (isTiming) return;
// 根据用户输入的分钟数设置timeLeft
const minutes = parseInt(inputMinutes.value, 10);
if (isNaN(minutes) || minutes < 1) {
alert('请输入有效的分钟数(>=1)');
return;
}
timeLeft = minutes * 60;
isTiming = true;
attempts = [];
timerDisplay.innerText = `剩余时间: ${timeLeft}秒`;
timer = setInterval(()=>{
timeLeft--;
timerDisplay.innerText = '剩余时间: ' + timeLeft + '秒';
if (timeLeft <= 0) {
clearInterval(timer);
isTiming = false;
timerDisplay.innerText = '时间到!';
showStatistics();
}
}, 1000);
}
function showStatistics() {
let total = attempts.length;
let correctCount = attempts.filter(a=>a.correct).length;
let wrongCount = total - correctCount;
let accuracy = total > 0 ? ((correctCount/total)*100).toFixed(2) + '%' : 'N/A';
let wrongItems = attempts.filter(a=>!a.correct).map(a=>a.dict);
// 创建遮罩层
const overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.background = 'rgba(0,0,0,0.5)';
overlay.style.display = 'flex';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
overlay.style.zIndex = '9999';
// 创建对话框
const modal = document.createElement('div');
modal.style.background = '#fff';
modal.style.padding = '20px';
modal.style.borderRadius = '8px';
modal.style.maxWidth = '400px';
modal.style.width = '90%';
modal.style.boxShadow = '0 0 10px rgba(0,0,0,0.3)';
modal.style.fontFamily = 'Arial, sans-serif';
modal.style.textAlign = 'center';
// 标题
const title = document.createElement('h2');
title.innerText = '练习结束!';
title.style.marginTop = '0';
// 统计信息内容
const stats = document.createElement('div');
stats.style.textAlign = 'left';
stats.style.margin = '15px 0';
// 使用换行和特殊样式呈现信息
stats.innerHTML = `
<p><strong>总次数:</strong> ${total}</p>
<p><strong>正确:</strong> ${correctCount}</p>
<p><strong>错误:</strong> ${wrongCount}</p>
<p><strong>正确率:</strong> ${accuracy}</p>
${ wrongCount > 0
? `<p><strong>错误的字有:</strong> ${wrongItems.join('、')}</p>`
: `<p>全部正确,棒棒哒!</p>`
}
`;
// 关闭按钮
const closeButton = document.createElement('button');
closeButton.innerText = '关闭';
closeButton.style.padding = '8px 16px';
closeButton.style.marginTop = '10px';
closeButton.style.border = 'none';
closeButton.style.borderRadius = '4px';
closeButton.style.background = '#007BFF';
closeButton.style.color = '#fff';
closeButton.style.cursor = 'pointer';
closeButton.addEventListener('click', () => {
document.body.removeChild(overlay);
});
modal.appendChild(title);
modal.appendChild(stats);
modal.appendChild(closeButton);
overlay.appendChild(modal);
document.body.appendChild(overlay);
}
function patchJudgeFunction() {
if (!Shuang.app || !Shuang.app.action || !Shuang.app.action.judge) {
console.warn("Unable to patch judge function, Shuang structure changed?");
return;
}
const originalJudge = Shuang.app.action.judge;
Shuang.app.action.judge = function() {
const inputVal = $("#a").value;
const correct = Shuang.core.current.judge(inputVal[0], inputVal[1]);
if (isTiming && inputVal.length === 2) {
attempts.push({
sheng: Shuang.core.current.sheng,
yun: Shuang.core.current.yun,
dict: Shuang.core.current.dict,
correct: correct
});
}
return originalJudge.call(this);
};
}
})();