双拼练习计时统计

为双拼练习页面添加可设置X分钟计时和正确率统计功能,按钮更明显地放在顶部菜单区域。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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

})();