学习通

作者: ok v1.0.0 工具
24
安装次数
39
浏览次数
v1.0.0
当前版本

脚本介绍

源码预览

开源
JavaScript
// ==UserScript==
// @name         ✅OK学习通网课作业助手
// @version      4.3
// @author       bzm
// @description  后台任务-不占用宽带-学习通助手,支持视频,作业、考试自动答题,免费高分题库。后台任务版,不占用宽带。后台上报和伪实时进度条。
// @match        *://*.chaoxing.com/*
// @run-at       document-end
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_info
// @grant        GM_getResourceText
// @grant        GM_openInTab
// @icon         http://pan-yz.chaoxing.com/favicon.ico
// @original-script     https://scriptcat.org/zh-CN/script-show-page/5529
// @require      https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js
// @require      https://cdn.bootcdn.net/ajax/libs/limonte-sweetalert2/11.1.0/sweetalert2.all.min.js
// @require      https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js
// @require      https://so.ucuc.net/TyprMd5.js
// @resource     Table https://so.ucuc.net/table.json
// @connect      so.ucuc.net
// @connect      101.96.197.41
// @connect      scriptcat.org
// @tag          免费搜题
// @tag          可选付费
// ==/UserScript==

/*********************************自定义配置区******************************************************** */
var setting = {
    showBox: 1,        // 显示脚本浮窗
    maskImg: 1,        // 显示皮卡丘
    task: 0,           // 只处理任务点任务
    video: 1,          // 处理视频
    audio: 1,          // 处理音频
    rate: 1,           // 视频/音频倍速
    review: 0,         // 复习模式
    work: 1,           // 测验自动处理
    time: 5000,        // 答题时间间隔(ms)
    sub: 0,            // 测验自动提交
    force: 0,          // 测验强制提交
    decrypt: 1,        // 字体解密
    examTurn: 0,       // 考试自动跳转
    examTurnTime: 1,   // 考试自动跳转随机间隔
    goodStudent: 0,    // 好学生模式
    alterTitle: 1,     // 答案插入题目
    autoLogin: 0,      // 自动登录
    phone: '',         // 登录手机号
    password: '',      // 登录密码
    // ========== 视频模拟配置 ==========
    videoMode: 'simulate',  // 'simulate'=模拟上报, 'normal'=正常播放
    reportInterval: 50,     // 上报间隔(秒)
    showProgressBar: 1      // 显示可视化进度条
}
/**************************************************************************************************/

var _w = unsafeWindow,
    _l = location,
    _d = _w.document,
    $ = _w.jQuery || top.jQuery,
    UE = _w.UE,
    md5 = window.md5 || $.md5;

// 获取Typr对象(兼容多种导出方式)
var TyprInstance = null;
try {
    if (typeof unsafeWindow !== 'undefined' && unsafeWindow.Typr) {
        TyprInstance = unsafeWindow.Typr;
    } else if (typeof window !== 'undefined' && window.Typr) {
        TyprInstance = window.Typr;
    } else if (typeof Typr !== 'undefined') {
        TyprInstance = Typr;
    } else if (typeof unsafeWindow !== 'undefined' && unsafeWindow.TyprMd5) {
        TyprInstance = unsafeWindow.TyprMd5;
    } else if (typeof TyprMd5 !== 'undefined') {
        TyprInstance = TyprMd5;
    }
} catch(e) {}

// ========== 通用工具函数 ==========

function getCookie(name) {
    return ('; ' + document.cookie).split('; ' + name + '=').pop().split(';')[0];
}

function formatDuration(seconds) {
    if (!seconds || seconds < 0) return '00:00';
    var total = Math.max(0, Math.floor(Number(seconds) || 0));
    var h = Math.floor(total / 3600);
    var m = Math.floor((total % 3600) / 60);
    var s = total % 60;
    var pad = function(n) { return String(n).padStart(2, '0'); };
    if (h > 0) {
        return pad(h) + ':' + pad(m) + ':' + pad(s);
    }
    return pad(m) + ':' + pad(s);
}

function tidyStr(s) {
    if (!s) return null;
    return s.replace(/<(?!img).*?>/g, "").replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').trim().replace(/&nbsp;/g, '');
}

function tidyQuestion(s) {
    if (!s) return null;
    return s.replace(/<(?!img).*?>/g, "").replace(/^【.*?】\s*/, '').replace(/\s*(\d+\.\d+分)$/, '').replace(/^\d+[.、]/, '').trim();
}

function splitAnswer(answer) {
    if (!answer) return [];
    var parts = answer.split(/[#]+/).map(a => a.trim()).filter(a => a !== '');
    if (parts.length === 0 && answer.trim() !== '') {
        return [answer.trim()];
    }
    return parts;
}

function getStr(str, start, end) {
    let res = str.match(new RegExp(start + '(.*?)' + end));
    return res ? res[1] : null;
}

function isTaskCompleted(task) {
    if (!task) return false;
    if (task.isPassed === true) return true;
    if (task.status === 'completed' || task.status === 'finished') return true;
    if (task.finished === true) return true;
    return false;
}

// ========== 日志系统 ==========
var logQueue = [];
var logTimer = null;

function getTopLogContainer() {
    try {
        var topDoc = top.document;
        var container = topDoc.getElementById('ne-21log');
        if (container) return $(container);
        container = document.getElementById('ne-21log');
        if (container) return $(container);
        return null;
    } catch(e) {
        return null;
    }
}

function flushLogs() {
    if (logQueue.length === 0) return;
    var container = getTopLogContainer();
    if (container && container.length) {
        var logsToShow = logQueue.slice();
        logQueue = [];
        for (var i = logsToShow.length - 1; i >= 0; i--) {
            container.prepend(logsToShow[i]);
        }
        container.scrollTop(0);
    }
    if (logTimer) {
        clearTimeout(logTimer);
        logTimer = null;
    }
}

function scheduleFlushLogs() {
    if (logTimer) return;
    logTimer = setTimeout(flushLogs, 100);
}

function log(str, color) {
    var time = new Date().toLocaleTimeString();
    var logHtml = '<div style="border-top:1px solid #eee;padding:6px 0;"><span style="color:' + (color || '#000') + ';">[' + time + '] ' + str + '</span></div>';
    logQueue.push(logHtml);
    scheduleFlushLogs();
    console.log('[学习通助手][' + time + ']', str);
}

// ========== API配置 ==========
var BASE_URL = "https://so.ucuc.net/prod-api";
var API_URL = BASE_URL + "/system/questionBank/search";

// ========== 认证状态管理 ==========
function getAuthToken() {
    return GM_getValue('auth_token', '');
}
function setAuthToken(token) {
    GM_setValue('auth_token', token);
}
function clearAuth() {
    GM_setValue('auth_token', '');
    GM_setValue('user_info', '');
}
function getUserInfo() {
    var info = GM_getValue('user_info', '');
    if (info) { try { return JSON.parse(info); } catch(e) {} }
    return null;
}
function setUserInfo(info) {
    GM_setValue('user_info', JSON.stringify(info));
    if (info && info.apiKey) {
        GM_setValue('api_key', info.apiKey);
    }
}
function isLoggedIn() {
    return !!getAuthToken();
}

function getApiKey() {
    var info = getUserInfo();
    if (info && info.apiKey) return info.apiKey;
    return GM_getValue('api_key', '');
}

function hasApiKey() {
    var key = getApiKey();
    return key && key.trim() !== '';
}

// ========== 认证 API 函数 ==========
function apiLogin(username, password) {
    return new Promise(function(resolve, reject) {
        GM_xmlhttpRequest({
            method: 'POST',
            url: BASE_URL + '/login',
            headers: {'Content-Type': 'application/json'},
            data: JSON.stringify({username: username, password: password}),
            timeout: 10000,
            onload: function(xhr) {
                try {
                    var res = JSON.parse(xhr.responseText);
                    if (res.code === 200 && res.token) { resolve(res.token); }
                    else { reject(res.msg || '登录失败'); }
                } catch(e) { reject('解析响应失败'); }
            },
            onerror: function() { reject('网络请求失败'); }
        });
    });
}

function apiGetEmailCode(email) {
    return new Promise(function(resolve, reject) {
        GM_xmlhttpRequest({
            method: 'POST',
            url: BASE_URL + '/getEmailCode',
            headers: {'Content-Type': 'application/json'},
            data: JSON.stringify({email: email}),
            timeout: 10000,
            onload: function(xhr) {
                try {
                    var res = JSON.parse(xhr.responseText);
                    if (res.code === 200) { resolve(); }
                    else { reject(res.msg || '发送失败'); }
                } catch(e) { reject('解析响应失败'); }
            },
            onerror: function() { reject('网络请求失败'); }
        });
    });
}

function apiRegister(username, password, email, code) {
    return new Promise(function(resolve, reject) {
        GM_xmlhttpRequest({
            method: 'POST',
            url: BASE_URL + '/register',
            headers: {'Content-Type': 'application/json'},
            data: JSON.stringify({username: username, password: password, email: email, code: code}),
            timeout: 10000,
            onload: function(xhr) {
                try {
                    var res = JSON.parse(xhr.responseText);
                    if (res.code === 200) { resolve(); }
                    else { reject(res.msg || '注册失败'); }
                } catch(e) { reject('解析响应失败'); }
            },
            onerror: function() { reject('网络请求失败'); }
        });
    });
}

function apiFetchUserInfo() {
    return new Promise(function(resolve, reject) {
        var token = getAuthToken();
        if (!token) { reject('未登录'); return; }
        GM_xmlhttpRequest({
            method: 'GET',
            url: BASE_URL + '/system/user/profile/userInfo',
            headers: {'Authorization': 'Bearer ' + token},
            timeout: 10000,
            onload: function(xhr) {
                if (xhr.status === 401) { clearAuth(); reject('TOKEN_EXPIRED'); return; }
                try {
                    var res = JSON.parse(xhr.responseText);
                    if (res.code === 200 && res.data) {
                        var user = res.data;
                        var info = { apiKey: user.apiKey, remainingCount: user.remainingCount, freeCount: user.freeCount, username: user.userName };
                        setUserInfo(info);
                        updateQuotaInfo({remaining: (user.remainingCount || 0) + (user.freeCount || 0)});
                        resolve(info);
                    } else { reject(res.msg || '获取用户信息失败'); }
                } catch(e) { reject('解析响应失败'); }
            },
            onerror: function() { reject('网络请求失败'); }
        });
    });
}

function apiFetchProducts() {
    return new Promise(function(resolve, reject) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: BASE_URL + '/system/product/list',
            timeout: 10000,
            onload: function(xhr) {
                try {
                    var res = JSON.parse(xhr.responseText);
                    if (res.code === 200) { resolve(res.rows || []); }
                    else { reject(res.msg || '获取套餐失败'); }
                } catch(e) { reject('解析响应失败'); }
            },
            onerror: function() { reject('网络请求失败'); }
        });
    });
}

function apiCreateOrder(productId, quantity, paymentMethod) {
    return new Promise(function(resolve, reject) {
        var token = getAuthToken();
        if (!token) { reject('未登录'); return; }
        GM_xmlhttpRequest({
            method: 'POST',
            url: BASE_URL + '/system/order/createRechargeOrder',
            headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token},
            data: JSON.stringify({productId: productId, quantity: quantity || 1, paymentMethod: paymentMethod || 'alipay'}),
            timeout: 10000,
            onload: function(xhr) {
                if (xhr.status === 401) { clearAuth(); reject('TOKEN_EXPIRED'); return; }
                try {
                    var res = JSON.parse(xhr.responseText);
                    if (res.code === 200) { resolve(res.data); }
                    else { reject(res.msg || '创建订单失败'); }
                } catch(e) { reject('解析响应失败'); }
            },
            onerror: function() { reject('网络请求失败'); }
        });
    });
}

function apiCheckOrderStatus(orderId) {
    return new Promise(function(resolve, reject) {
        var token = getAuthToken();
        GM_xmlhttpRequest({
            method: 'GET',
            url: BASE_URL + '/system/order/checkOrderStatus?orderId=' + orderId,
            headers: {'Authorization': 'Bearer ' + token},
            timeout: 10000,
            onload: function(xhr) {
                try {
                    var res = JSON.parse(xhr.responseText);
                    if (res.code === 200) { resolve(res.data); }
                    else { reject(res.msg || '查询失败'); }
                } catch(e) { reject('解析响应失败'); }
            },
            onerror: function() { reject('网络请求失败'); }
        });
    });
}

function getQuotaInfo() {
    return {
        remaining: GM_getValue('quota_remaining', -1)
    };
}

var quotaInfo = getQuotaInfo();

function saveQuotaInfo() {
    GM_setValue('quota_remaining', quotaInfo.remaining);
}

function updateQuotaInfo(newQuotaInfo) {
    if (newQuotaInfo) {
        quotaInfo.remaining = newQuotaInfo.remaining !== undefined ? newQuotaInfo.remaining : quotaInfo.remaining;
        saveQuotaInfo();
        updateQuotaDisplay();
    }
}

function updateQuotaDisplay() {
    try {
        var targetDoc = top.document;
        if (!targetDoc.querySelector('#quotaInfo')) targetDoc = document;
        var $quotaInfo = $('#quotaInfo', targetDoc);
        if ($quotaInfo.length) {
            var remaining = quotaInfo.remaining;
            var hasKey = hasApiKey();
            if (remaining > 0) {
                $quotaInfo.html('📊 剩余次数: ' + remaining);
            } else if (remaining === 0 && hasKey) {
                var rechargeUrl = 'https://so.ucuc.net';
                var tk = getAuthToken();
                if (tk) rechargeUrl += '?token=' + encodeURIComponent(tk);
                $quotaInfo.html('⚠️ 次数已用完 | <a href="' + rechargeUrl + '" target="_blank" style="color:#2196F3;">点击充值</a>');
            } else if (!hasKey) {
                $quotaInfo.html('⚠️ 请先设置API密钥');
            } else {
                $quotaInfo.html('📊 剩余次数: 加载中...');
            }
        }
    } catch(e) {}
}

function checkApiKeyBeforeAction(actionName) {
    var apiKey = getApiKey();
    if (!apiKey || apiKey.trim() === '') {
        if (isLoggedIn()) {
            log('⚠️ 正在获取API密钥...', 'orange');
        } else {
            log('❌ 无法执行' + actionName + ':请先登录', 'red');
        }
        return false;
    }
    quotaInfo = getQuotaInfo();
    if (quotaInfo.remaining === 0) {
        log('❌ 无法执行' + actionName + ':次数已用完,请购买套餐', 'red');
        return false;
    }
    return true;
}

// ========== 接口测试函数 ==========
function testApi(key) {
    return new Promise((resolve, reject) => {
        var payload = JSON.stringify({
            question: "test",
            apiKey: key,
            type: "单选题",
            options: JSON.stringify(["选项A", "选项B"])
        });

        GM_xmlhttpRequest({
            method: 'POST',
            url: API_URL,
            headers: {'Content-Type': 'application/json'},
            data: payload,
            timeout: 10000,
            onload: function(xhr) {
                try {
                    var res = JSON.parse(xhr.responseText);
                    if (res.code == 200) {
                        if (res.data && res.data.remainingCount !== undefined) {
                            updateQuotaInfo({ remaining: res.data.remainingCount });
                        }
                        resolve(res);
                    } else {
                        reject(res.msg || '密钥无效');
                    }
                } catch(e) {
                    reject('解析响应失败');
                }
            },
            onerror: function() {
                reject('网络请求失败');
            },
            ontimeout: function() {
                reject('请求超时');
            }
        });
    });
}

// ========== 本地enc计算 ==========
function generateEncLocal(classId, uid, jobId, objectId, playTime, duration) {
    var str = '[' + classId + '][' + uid + '][' + jobId + '][' + objectId + '][' + (playTime * 1000) + '][d_yHJ!$pdA~5][' + (duration * 1000) + '][0_' + duration + ']';
    return md5(str);
}

// ========== 等待元素 ==========
function waitForElement(selector, timeout = 30000) {
    return new Promise((resolve, reject) => {
        var start = Date.now();
        var timer = setInterval(() => {
            if ($(selector).length) {
                clearInterval(timer);
                resolve();
            } else if (Date.now() - start > timeout) {
                clearInterval(timer);
                reject();
            }
        }, 500);
    });
}

function getElement(parent, selector) {
    return new Promise((resolve) => {
        if (!parent) {
            resolve(null);
            return;
        }
        try {
            var iframe = parent.querySelector(selector);
            if (iframe) { resolve(iframe); return; }
            var observer = new MutationObserver((mutations) => {
                try {
                    var iframe = parent.querySelector(selector);
                    if (iframe) {
                        observer.disconnect();
                        resolve(iframe);
                    }
                } catch(e) {
                    observer.disconnect();
                    resolve(null);
                }
            });
            observer.observe(parent, { childList: true, subtree: true });
            setTimeout(() => {
                observer.disconnect();
                resolve(null);
            }, 10000);
        } catch(e) {
            resolve(null);
        }
    });
}

// ========== 获取填空数量(修复版)==========
function getBlankInputCount($question) {
    var count = 0;
    
    // 方法1: 查找手机端填空输入框 .blankList2 input
    var $inputs = $question.find('.blankList2 input, input[type="text"][name*="answer"]');
    if ($inputs.length) {
        // 收集唯一标识符
        var uniqueIdentifiers = new Set();
        $inputs.each(function() {
            var name = $(this).attr('name');
            var id = $(this).attr('id');
            var placeholder = $(this).attr('placeholder');
            // 使用多种属性组合作为唯一标识
            var key = (name || '') + '_' + (id || '') + '_' + (placeholder || '') + '_' + $(this).index();
            uniqueIdentifiers.add(key);
        });
        count = uniqueIdentifiers.size;
        if (count > 0) {
            log('📝 方法1检测到填空数量: ' + count, 'blue');
            return count;
        }
    }
    
    // 方法2: 查找 data-editorindex 属性(手机端编辑器)
    var $editorBlocks = $question.find('[data-editorindex]');
    if ($editorBlocks.length) {
        var uniqueIndices = new Set();
        $editorBlocks.each(function() {
            var idx = $(this).attr('data-editorindex');
            if (idx !== undefined && idx !== null && idx !== '') {
                uniqueIndices.add(idx);
            }
        });
        count = uniqueIndices.size;
        if (count > 0) {
            log('📝 方法2检测到填空数量: ' + count, 'blue');
            return count;
        }
    }
    
    // 方法3: 查找PC端填空输入框 .Zy_ulTk .XztiHover1 textarea
    var $pcInputs = $question.find('.Zy_ulTk .XztiHover1 textarea, .stem_answer .Answer .divText .textDIV textarea, .subEditor textarea');
    if ($pcInputs.length) {
        var uniquePCInputs = new Set();
        $pcInputs.each(function() {
            var id = $(this).attr('id');
            var name = $(this).attr('name');
            var key = (id || '') + '_' + (name || '') + '_' + $(this).index();
            uniquePCInputs.add(key);
        });
        count = uniquePCInputs.size;
        if (count > 0) {
            log('📝 方法3检测到填空数量: ' + count, 'blue');
            return count;
        }
    }
    
    // 方法4: 查找包含 "blank" 或 "fill" 的输入框
    var $blankInputs = $question.find('input[type="text"], textarea').filter(function() {
        var className = $(this).attr('class') || '';
        var id = $(this).attr('id') || '';
        return className.toLowerCase().indexOf('blank') !== -1 || 
               className.toLowerCase().indexOf('fill') !== -1 ||
               id.toLowerCase().indexOf('blank') !== -1 ||
               id.toLowerCase().indexOf('fill') !== -1;
    });
    if ($blankInputs.length) {
        var uniqueBlanks = new Set();
        $blankInputs.each(function() {
            var name = $(this).attr('name');
            var id = $(this).attr('id');
            var key = (name || '') + '_' + (id || '') + '_' + $(this).index();
            uniqueBlanks.add(key);
        });
        count = uniqueBlanks.size;
        if (count > 0) {
            log('📝 方法4检测到填空数量: ' + count, 'blue');
            return count;
        }
    }
    
    // 方法5: 从题目文本中解析填空数量(如 "第1空"、"第2空" 或 "(1)"、"(2)")
    var questionText = $question.text() || '';
    var pattern1 = questionText.match(/第(\d+)空/g);
    if (pattern1 && pattern1.length > 0) {
        var maxBlank = 0;
        pattern1.forEach(function(match) {
            var num = parseInt(match.match(/\d+/)[0]);
            if (num > maxBlank) maxBlank = num;
        });
        if (maxBlank > 0) {
            log('📝 方法5从"第X空"检测到填空数量: ' + maxBlank, 'blue');
            return maxBlank;
        }
    }
    
    var pattern2 = questionText.match(/[((](\d+)[))]/g);
    if (pattern2 && pattern2.length > 0) {
        var maxBlank = 0;
        pattern2.forEach(function(match) {
            var num = parseInt(match.match(/\d+/)[0]);
            if (num > maxBlank) maxBlank = num;
        });
        if (maxBlank > 0 && maxBlank <= 20) {
            log('📝 方法5从括号数字检测到填空数量: ' + maxBlank, 'blue');
            return maxBlank;
        }
    }
    
    // 方法6: 查找填空题特有的DOM结构
    var $fillItems = $question.find('.fillItem, .fill-item, .blank-item, .blankItem');
    if ($fillItems.length) {
        var uniqueItems = new Set();
        $fillItems.each(function() {
            var idx = $(this).attr('data-index') || $(this).attr('data-id') || $(this).index();
            uniqueItems.add(idx);
        });
        count = uniqueItems.size;
        if (count > 0) {
            log('📝 方法6检测到填空数量: ' + count, 'blue');
            return count;
        }
    }
    
    // 默认返回1(至少有一个填空)
    log('📝 使用默认填空数量: 1', 'blue');
    return 1;
}

// ========== 填空题填充(修复版)==========
function fillBlankAnswer($question, answers, isPhoneMode, contextWindow) {
    var answerList = splitAnswer(answers);
    log('📝 填空题答案: ' + answerList.join(' | '), 'blue');
    
    // 获取实际填空数量
    var blankCount = getBlankInputCount($question);
    log('📝 检测到填空数量: ' + blankCount, 'blue');
    
    // 如果答案数量不足,重复最后一个答案或使用第一个答案填充
    while (answerList.length < blankCount) {
        answerList.push(answerList[answerList.length - 1] || answerList[0] || '');
    }
    
    if (isPhoneMode) {
        // 手机端:查找 .blankList2 input
        var $allInputs = $question.find('.blankList2 input, input[type="text"][name*="answer"]');
        if ($allInputs.length) {
            // 去重获取唯一输入框
            var uniqueInputs = [];
            var seen = new Set();
            $allInputs.each(function() {
                var name = $(this).attr('name');
                var id = $(this).attr('id');
                var placeholder = $(this).attr('placeholder');
                var key = (name || '') + '_' + (id || '') + '_' + (placeholder || '') + '_' + $(this).index();
                if (!seen.has(key)) {
                    seen.add(key);
                    uniqueInputs.push(this);
                }
            });
            
            log('📝 手机端找到 ' + uniqueInputs.length + ' 个填空输入框', 'blue');
            $(uniqueInputs).each(function(i) {
                setTimeout(() => {
                    var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || '');
                    $(this).val(answerValue);
                    $(this).trigger('input').trigger('change');
                    // 触发change事件后再次触发blur以确保保存
                    setTimeout(() => { $(this).trigger('blur'); }, 50);
                    log('✅ 填空题第' + (i+1) + '空已填写: ' + answerValue, 'green');
                }, i * 200);
            });
            return true;
        }
        
        // 手机端:查找带 data-editorindex 的编辑器
        var $editorBlocks = $question.find('[data-editorindex]');
        if ($editorBlocks.length) {
            var uniqueEditors = [];
            var seenIndices = new Set();
            $editorBlocks.each(function() {
                var idx = $(this).attr('data-editorindex');
                if (idx && !seenIndices.has(idx)) {
                    seenIndices.add(idx);
                    uniqueEditors.push(this);
                }
            });
            
            log('📝 手机端找到 ' + uniqueEditors.length + ' 个编辑器', 'blue');
            uniqueEditors.forEach(function(editor, i) {
                var editorIndex = $(editor).attr('data-editorindex');
                var itemId = $(editor).attr('data-itemid');
                setTimeout(() => {
                    try {
                        var ueditor = null;
                        if (contextWindow && contextWindow.editors && contextWindow.editors[editorIndex]) {
                            ueditor = contextWindow.editors[editorIndex].ueditor;
                        }
                        if (!ueditor && contextWindow && contextWindow.UE && contextWindow.UE.instants) {
                            var instantKey = 'ueditorInstant' + editorIndex;
                            ueditor = contextWindow.UE.instants[instantKey];
                        }
                        if (!ueditor && itemId && contextWindow && contextWindow.UE && contextWindow.UE.getEditor) {
                            ueditor = contextWindow.UE.getEditor('ananas-editor-answer' + itemId);
                        }
                        if (ueditor) {
                            var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || '');
                            ueditor.setContent(answerValue);
                            log('✅ 填空题第' + (i+1) + '空已填写: ' + answerValue, 'green');
                        }
                        if (itemId) {
                            var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || '');
                            $('#answer' + itemId).val(answerValue).trigger('change');
                        }
                    } catch(e) {
                        log('⚠️ 填写编辑器第' + (i+1) + '空失败: ' + e.message, 'orange');
                    }
                }, i * 300);
            });
            return true;
        }
        return false;
    }
    
    // PC端处理
    var $pcInputs = $question.find('.Zy_ulTk .XztiHover1 textarea, .stem_answer .Answer .divText .textDIV textarea, .subEditor textarea');
    if ($pcInputs.length) {
        var uniquePCInputs = [];
        var seenPC = new Set();
        $pcInputs.each(function() {
            var id = $(this).attr('id');
            var name = $(this).attr('name');
            var key = (id || '') + '_' + (name || '') + '_' + $(this).index();
            if (!seenPC.has(key)) {
                seenPC.add(key);
                uniquePCInputs.push(this);
            }
        });
        
        log('📝 PC端找到 ' + uniquePCInputs.length + ' 个填空输入框', 'blue');
        $(uniquePCInputs).each(function(i) {
            setTimeout(() => {
                var $this = $(this);
                var answerValue = answerList[i] !== undefined ? answerList[i] : (answerList[0] || '');
                if (UE && UE.getEditor && UE.getEditor($this.attr('id'))) {
                    try {
                        UE.getEditor($this.attr('id')).setContent(answerValue);
                    } catch(e) {
                        $this.val(answerValue);
                    }
                } else {
                    $this.val(answerValue);
                    $this.trigger('input').trigger('change');
                }
                log('✅ 填空题第' + (i+1) + '空已填写: ' + answerValue, 'green');
            }, i * 200);
        });
        return true;
    }
    
    log('⚠️ 未找到填空输入框', 'orange');
    return false;
}

// ========== 版本更新检测 ==========
function checkUpdate() {
    // 频率限制:每24小时最多检查一次
    var CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24小时
    var lastCheck = GM_getValue('lastUpdateCheck', 0);
    var now = Date.now();
    if (now - lastCheck < CHECK_INTERVAL) {
        // 未到检查间隔,跳过本次检查
        var cachedLatest = GM_getValue('cachedLatestVersion', '');
        var currentVersion = GM_info.script.version;
        if (cachedLatest && compareVersions(currentVersion, cachedLatest) < 0) {
            log('🎉 发现新版本 v' + cachedLatest + '(当前 v' + currentVersion + ')', 'green');
            showUpdateNotice(cachedLatest);
        }
        return;
    }

    var currentVersion = GM_info.script.version;
    // 使用 .meta.js 端点
    GM_xmlhttpRequest({
        method: "GET",
        url: 'https://scriptcat.org/scripts/code/5529/%E2%9C%85OK%E5%AD%A6%E4%B9%A0%E9%80%9A%E7%BD%91%E8%AF%BE%E4%BD%9C%E4%B8%9A%E5%8A%A9%E6%89%8B%EF%BD%9C%E9%A2%98%E5%BA%93%EF%BC%8Bai%E6%90%9C%E9%A2%98%E2%9C%A8%EF%BD%9C%E7%A8%B3%E6%8B%BF%E9%AB%98%E5%88%86%E2%9C%85%EF%BD%9C%E7%BD%91%E8%AF%BE%E4%BD%9C%E4%B8%9A%E9%9A%BE%E9%A2%98%E5%85%A8%E7%A0%B4%E8%A7%A3%F0%9F%92%AA%EF%BD%9C%E5%85%A8%E7%A7%91%E7%9B%AE%E8%A6%86%E7%9B%96.meta.js',
        timeout: 8000,
        onload: function(response) {
            try {
                GM_setValue('lastUpdateCheck', now);
                var versionMatch = response.responseText.match(/\/\/\s*@version\s+(\S+)/);
                if (versionMatch && versionMatch[1]) {
                    var latestVersion = versionMatch[1];
                    GM_setValue('cachedLatestVersion', latestVersion);
                    if (compareVersions(currentVersion, latestVersion) < 0) {
                        log('🎉 发现新版本 v' + latestVersion + '(当前 v' + currentVersion + ')', 'green');
                        showUpdateNotice(latestVersion);
                    } else {
                        log('✅ 已是最新版本 v' + currentVersion, 'green');
                    }
                }
            } catch(e) {}
        },
        onerror: function() {}
    });
}

function compareVersions(v1, v2) {
    var parts1 = v1.split('.').map(Number);
    var parts2 = v2.split('.').map(Number);
    var len = Math.max(parts1.length, parts2.length);
    for (var i = 0; i < len; i++) {
        var a = parts1[i] || 0;
        var b = parts2[i] || 0;
        if (a < b) return -1;
        if (a > b) return 1;
    }
    return 0;
}

function showUpdateNotice(latestVersion) {
    var updateUrl = 'https://scriptcat.org/zh-CN/script-show-page/5529';
    try {
        var targetDoc = top.document;
        var notice = targetDoc.createElement('div');
        notice.id = 'ok-update-notice';
        notice.innerHTML = '<div style="position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:999999;background:#fff;border-radius:10px;padding:16px 24px;box-shadow:0 4px 20px rgba(0,0,0,0.2);display:flex;align-items:center;gap:12px;font-family:system-ui,sans-serif;">' +
            '<span style="font-size:24px;">🎉</span>' +
            '<div><div style="font-size:14px;font-weight:bold;color:#333;">OK学习通助手有新版本 v' + latestVersion + '</div>' +
            '<div style="font-size:12px;color:#666;margin-top:4px;">建议更新以获得更好体验</div></div>' +
            '<a href="' + updateUrl + '" target="_blank" style="padding:8px 16px;background:#4CAF50;color:#fff;border-radius:6px;text-decoration:none;font-size:13px;white-space:nowrap;">立即更新</a>' +
            '<span id="ok-update-close" style="cursor:pointer;color:#999;font-size:18px;margin-left:8px;">✕</span>' +
        '</div>';
        targetDoc.body.appendChild(notice);
        targetDoc.getElementById('ok-update-close').onclick = function() { notice.remove(); };
        setTimeout(function() { if (notice.parentNode) notice.remove(); }, 30000);
    } catch(e) {}
}

// ========== 简答题填充 ==========
function fillShortAnswer($question, answer, isPhoneMode, contextWindow) {
    log('📝 简答题答案: ' + answer.substring(0, 100) + '...', 'blue');

    if (isPhoneMode) {
        var win = contextWindow || unsafeWindow;

        // 方法1: 先尝试通过textarea ID找到对应的UEditor实例
        var $textarea = $question.find('textarea[name^="answer"], textarea.answerTxt, .answerTxt textarea, .textareaBx textarea');
        if (!$textarea.length) {
            $textarea = $question.find('textarea');
        }
        if ($textarea.length) {
            var taId = $textarea.first().attr('id');
            var taName = $textarea.first().attr('name');
            // 尝试通过UEditor设置内容(手机端简答题通常是UEditor)
            var ueSet = false;
            if (win.UE) {
                // 尝试通过textarea的id获取editor
                if (taId && win.UE.getEditor) {
                    try {
                        var ue = win.UE.getEditor(taId);
                        if (ue && ue.setContent) { ue.setContent(answer); ueSet = true; }
                    } catch(e) {}
                }
                // 尝试通过name构造id获取
                if (!ueSet && taName) {
                    var possibleIds = [taName, 'editor_' + taName, taName.replace('answer', 'answerEditor')];
                    for (var pi = 0; pi < possibleIds.length && !ueSet; pi++) {
                        try {
                            var ue2 = win.UE.getEditor(possibleIds[pi]);
                            if (ue2 && ue2.setContent) { ue2.setContent(answer); ueSet = true; }
                        } catch(e) {}
                    }
                }
                // 遍历所有UEditor实例,找到属于当前题目的
                if (!ueSet && win.UE.instants) {
                    for (var key in win.UE.instants) {
                        var editor = win.UE.instants[key];
                        if (!editor) continue;
                        try {
                            var editorContainer = editor.container;
                            if (editorContainer && $question[0].contains(editorContainer)) {
                                editor.setContent(answer);
                                ueSet = true;
                                break;
                            }
                        } catch(e) {}
                    }
                }
            }
            if (ueSet) {
                // 同时设置隐藏textarea的值确保表单提交时有数据
                $textarea.first().val(answer);
                log('✅ 简答题已通过UEditor填写', 'green');
                return true;
            }
            // 没有UEditor,直接设置textarea值
            $textarea.first().val(answer);
            $textarea.first().trigger('input').trigger('change').trigger('blur');
            log('✅ 简答题已填写到textarea', 'green');
            return true;
        }

        // 方法2: 查找UEditor的iframe编辑区域
        var $editorIframe = $question.find('.edui-editor iframe, .edui-editor-iframeholder iframe');
        if ($editorIframe.length) {
            try {
                var iframeDoc = $editorIframe[0].contentDocument || $editorIframe[0].contentWindow.document;
                var $body = $(iframeDoc.body);
                $body.html('<p>' + answer + '</p>');
                log('✅ 简答题已填写到UEditor iframe', 'green');
                return true;
            } catch(e) {}
        }

        // 方法3: contenteditable区域
        var $editorDiv = $question.find('[contenteditable="true"]');
        if ($editorDiv.length) {
            $editorDiv.first().html(answer);
            $editorDiv.first().trigger('input').trigger('change');
            log('✅ 简答题已填写到contenteditable', 'green');
            return true;
        }

        // 方法4: 遍历所有UEditor实例(兜底)
        if (win.UE && win.UE.instants) {
            var editors = win.UE.instants;
            for (var key in editors) {
                var editor = editors[key];
                if (!editor) continue;
                try {
                    var editorContainer = editor.container;
                    if (editorContainer && $question[0].contains(editorContainer)) {
                        editor.setContent(answer);
                        log('✅ 简答题已填写到UEditor(兜底)', 'green');
                        return true;
                    }
                } catch(e) {}
            }
        }

        log('⚠️ 简答题未找到可填写的输入框', 'orange');
        return false;
    }

    // PC端处理
    var $ueTextarea = $question.find('textarea[name^="answerEditor"], .eidtDiv textarea, .divText textarea');
    if ($ueTextarea.length) {
        var id = $ueTextarea.first().attr('id');
        if (id && UE && UE.getEditor && UE.getEditor(id)) {
            try {
                UE.getEditor(id).setContent(answer);
                return true;
            } catch(e) {}
        }
        $ueTextarea.val(answer);
        $ueTextarea.trigger('input').trigger('change');
        return true;
    }

    return false;
}

// ========== Web Worker 后台上报(修复版)==========
var reportWorker = null;
var activeVideoJob = null;

// 完成上报多次未通过时的兜底:直接查 status 接口确认是否实际已 passed
// 学生学习页面用 .ans-job-finished 类判断完成,源头来自 status 接口的 isPassed 字段
function verifyPassedFallback(ctx, worker) {
    // 修复:已取消的 worker 不再发起兜底查询
    if (worker.aborted) {
        log('🚫 Worker 已取消,跳过兜底查询', 'orange');
        return;
    }
    var statusUrl = _l.protocol + '//' + _l.host + '/ananas/status/' + ctx.objectId +
        '?k=' + (getCookie('fid') || '') + '&_uid=' + ctx.uid + '&flag=normal&_dc=' + Date.now();
    GM_xmlhttpRequest({
        method: "GET",
        url: statusUrl,
        headers: {
            'Host': _l.host,
            'Referer': _l.protocol + '//' + _l.host + '/ananas/modules/video/index.html'
        },
        onload: function(res) {
            if (worker.aborted) return;
            try {
                var info = JSON.parse(res.responseText);
                if (info.isPassed) {
                    log('✅ 兜底查询确认任务已通过: ' + ctx.name, 'green');
                    if (!worker.isCompleted) {
                        worker.isCompleted = true;
                        if (worker.completeCallback) worker.completeCallback();
                    }
                } else {
                    log('❌ 兜底查询确认仍未通过,可能受学时限制或防刷限制,继续下一任务', 'red');
                    if (worker.completeCallback) worker.completeCallback();
                }
            } catch(e) {
                log('⚠️ 兜底查询解析失败,继续下一任务', 'orange');
                if (worker.completeCallback) worker.completeCallback();
            }
        },
        onerror: function() {
            if (worker.aborted) return;
            log('⚠️ 兜底查询请求失败,继续下一任务', 'orange');
            if (worker.completeCallback) worker.completeCallback();
        }
    });
}

// 创建Web Worker用于后台计时(包含实际上报逻辑)
function createReportWorker(videoInfo, taskObj) {
    var name = videoInfo.name;
    var duration = videoInfo.duration;
    var playedTime = videoInfo.playedTime || 0;
    var dtoken = videoInfo.dtoken;
    var objectId = videoInfo.objectId;
    var jobId = taskObj.jobid;
    var rt = videoInfo.rt;
    var otherInfo = videoInfo.otherInfo;
    var reportUrl = _defaults.reportUrl;
    var classId = _defaults.clazzId;
    var uid = getCookie('_uid') || getCookie('UID');
    var rate = setting.rate;
    var reportInterval = setting.reportInterval;
    
    // 上报必须从0开始(服务器要求完整序列)
    // 但对已观看部分(0→playedTime)快速跳过,缩短等待时间
    var workerCode = `
        var timer = null;
        var currentTime = 0;
        var duration = ${duration};
        var rate = ${rate};
        var playedTime = ${playedTime};
        var reportInterval = ${reportInterval};
        var lastReportTime = 0;
        var isRunning = true;
        var startTime = Date.now();
        var reportCount = 0;
        
        // 上报函数
        function sendReport(playTime, isComplete, isFirst) {
            self.postMessage({
                type: 'doReport',
                playTime: playTime,
                isComplete: isComplete,
                isFirst: isFirst || false
            });
        }

        // 首次上报(isdrag=3)从0开始
        sendReport(0, false, true);
        
        // 计时循环
        // 对已观看部分(0→playedTime)快速跳过,使用10x加速
        // 超过playedTime后恢复正常速度
        var fastRate = 10; // 已看部分的加速倍率
        
        function updateLoop() {
            if (!isRunning) return;

            var now = Date.now();
            var elapsed = (now - startTime) / 1000;
            // 已看部分用fastRate加速,未看部分用正常rate
            var effectiveRate = (currentTime < playedTime) ? fastRate : rate;
            var targetTime = Math.min(currentTime + (effectiveRate * 0.2), duration); // 每200ms推进
            var isCatchingUp = false;

            if (targetTime > currentTime) {
                // 检测到时间跳跃(超过 1.5 倍上报间隔):步进推进
                if (targetTime - currentTime > reportInterval * 1.5) {
                    isCatchingUp = true;
                    currentTime = Math.min(currentTime + reportInterval, targetTime);
                } else {
                    currentTime = targetTime;
                }

                var timeSinceLastReport = currentTime - lastReportTime;
                var shouldReport = timeSinceLastReport >= reportInterval || currentTime >= duration;

                if (shouldReport) {
                    var reportTime = currentTime >= duration ? duration : Math.ceil(currentTime);
                    sendReport(reportTime, currentTime >= duration);
                    lastReportTime = currentTime;
                }

                self.postMessage({
                    type: 'progress',
                    currentTime: currentTime,
                    duration: duration
                });
            }

            if (currentTime >= duration) {
                isRunning = false;
                if (timer) clearInterval(timer);
                self.postMessage({ type: 'completed' });
            } else {
                // 已看部分快速推进(200ms一次),追上后正常速度
                // 补发心跳期间放慢到3秒
                var nextDelay = isCatchingUp ? 3000 : 200;
                timer = setTimeout(updateLoop, nextDelay);
            }
        }
        
        updateLoop();
        
        self.onmessage = function(e) {
            var data = e.data;
            switch(data.type) {
                case 'updateRate':
                    rate = data.rate;
                    break;
                case 'stop':
                    isRunning = false;
                    if (timer) clearTimeout(timer);
                    self.postMessage({ type: 'stopped' });
                    break;
            }
        };
    `;
    
    var blob = new Blob([workerCode], { type: 'application/javascript' });
    var worker = new Worker(URL.createObjectURL(blob));
    
    // 存储视频信息供上报使用
    worker.videoContext = {
        name: name,
        duration: duration,
        dtoken: dtoken,
        objectId: objectId,
        jobId: jobId,
        rt: rt,
        otherInfo: otherInfo,
        reportUrl: reportUrl,
        classId: classId,
        uid: uid
    };
    
    worker.onmessage = function(e) {
        var data = e.data;
        
        switch(data.type) {
            case 'progress':
                if (worker.aborted) break;
                if (setting.showProgressBar) {
                    // Worker已经从记忆位置开始,直接使用currentTime
                    updateProgressBar(data.currentTime, data.duration);
                }
                break;
                
            case 'doReport':
                // 检查 worker 是否已被取消(forceCleanupAll 设置了 aborted 标志)
                if (worker.aborted) {
                    log('🚫 Worker 已取消,忽略上报请求', 'orange');
                    break;
                }
                // 主线程执行实际上报
                var ctx = worker.videoContext;
                var isdrag;
                if (data.isFirst) {
                    isdrag = '3';
                } else if (data.isComplete) {
                    isdrag = '4';
                } else {
                    isdrag = '0';
                }

                log('📡 上报: ' + formatDuration(data.playTime) + '/' + formatDuration(ctx.duration) + (data.isFirst ? ' [开始]' : '') + (isdrag === '4' ? ' [完成]' : ''), 'blue');

                // 完成上报重试函数
                var doReportRequest = function(retryCount) {
                    // 关键修复:每次重试前检查 worker 是否已被取消
                    // 防止旧任务的重试请求与新任务的上报并行,污染服务器状态
                    if (worker.aborted) {
                        log('🚫 Worker 已取消,停止重试上报', 'orange');
                        return;
                    }
                    retryCount = retryCount || 0;
                    var maxRetries = 10;

                    var enc = generateEncLocal(ctx.classId, ctx.uid, ctx.jobId, ctx.objectId, data.playTime, ctx.duration);
                    var reportsUrl = ctx.reportUrl + '/' + ctx.dtoken +
                        '?clazzId=' + ctx.classId +
                        '&playingTime=' + data.playTime +
                        '&duration=' + ctx.duration +
                        '&clipTime=0_' + ctx.duration +
                        '&objectId=' + ctx.objectId +
                        '&otherInfo=' + ctx.otherInfo +
                        '&jobid=' + ctx.jobId +
                        '&userid=' + ctx.uid +
                        '&isdrag=' + isdrag +
                        '&view=pc' +
                        '&enc=' + enc +
                        '&rt=' + ctx.rt +
                        '&dtype=Video' +
                        '&_t=' + Date.now();

                    GM_xmlhttpRequest({
                        method: "GET",
                        url: reportsUrl,
                        headers: {
                            'Host': _l.host,
                            'Referer': _l.protocol + '//' + _l.host + '/ananas/modules/video/index.html',
                            'Content-Type': 'application/json',
                            'Sec-Fetch-Site': 'same-origin'
                        },
                        onload: function(res) {
                            // 回调中再次检查:请求发出后 worker 可能已被取消
                            if (worker.aborted) return;
                            try {
                                var result = JSON.parse(res.responseText);
                                // 打印每次上报的服务器返回(截取前120字符)
                                var respSnippet = (res.responseText || '').substring(0, 120);
                             //   log('📨 上报返回[isdrag=' + isdrag + ']: ' + respSnippet, 'gray');
                                if (result.isPassed) {
                                    if (!worker.isCompleted) {
                                        worker.isCompleted = true;
                                        log('✅ 视频任务已完成: ' + ctx.name, 'green');
                                        if (worker.completeCallback) worker.completeCallback();
                                    }
                                } else if (isdrag === '4' && retryCount < maxRetries) {
                                    var snippet = (res.responseText || '').substring(0, 160);
                                    log('⚠️ 完成上报未通过(' + (retryCount + 1) + '/' + maxRetries + ') 服务器返回: ' + snippet, 'orange');
                                    setTimeout(function() {
                                        doReportRequest(retryCount + 1);
                                    }, 3000);
                                } else if (isdrag === '4') {
                                    log('❌ 完成上报重试' + maxRetries + '次仍未通过,回退查询任务点状态', 'red');
                                    // 兜底:上报响应没说通过,但服务器实际可能已记完成。直接查 status 接口确认
                                    verifyPassedFallback(ctx, worker);
                                }
                            } catch(e) {
                                log('⚠️ 上报响应解析失败: ' + e.message + ' 内容: ' + (res.responseText || '').substring(0, 120), 'orange');
                                if (isdrag === '4' && retryCount < maxRetries) {
                                    setTimeout(function() {
                                        doReportRequest(retryCount + 1);
                                    }, 3000);
                                }
                            }
                        },
                        onerror: function() {
                            if (worker.aborted) return;
                            if (isdrag === '4' && retryCount < maxRetries) {
                                log('⚠️ 上报失败,重试中(' + (retryCount + 1) + '/' + maxRetries + ')...', 'orange');
                                setTimeout(function() {
                                    doReportRequest(retryCount + 1);
                                }, 3000);
                            } else {
                                log('⚠️ 上报失败,将继续尝试', 'orange');
                            }
                        }
                    });
                };
                doReportRequest(0);
                break;
                
            case 'completed':
                if (worker.aborted) break;
                // Worker计时已到,但如果完成上报还在重试中,等待重试完成
                // isCompleted为true时completeCallback已被调用,这里作为兜底
                log('⏹️ 视频播放计时完成,等待服务器确认...', 'green');
                setTimeout(function() {
                    if (worker.aborted) return;
                    if (!worker.isCompleted && worker.completeCallback) {
                        log('⚠️ 等待超时,强制继续下一任务', 'orange');
                        worker.completeCallback();
                    }
                }, 35000);
                break;
                
            case 'stopped':
                log('📡 Worker已停止', 'blue');
                break;
        }
    };
    
    return worker;
}

// ========== 视频处理相关变量 ==========
var currentVideoInterval = null;
var currentProgressBar = null;
var currentVideoWorker = null;  // 单独存储Worker引用
var currentVideoTaskId = null;   // 当前视频任务ID
var videoTaskCounter = 0;        // 视频任务计数器
var _mlist, _defaults, _domList, $subBtn, $saveBtn, $frame_c, $okBtn, _workIframeDom;
var isProcessing = false;
var isBoxHidden = false;
var pikaqiuAdded = false;
var pendingMissionCount = 0;
var completedMissionCount = 0;
var isJumping = false;
var hasTriggeredNoTaskJump = false;
var isVideoTaskActive = false;
var currentVideoName = null;
var currentVideoPlayedTime = 0;  // 记忆播放位置,进度条从此位置开始显示

// ========== 强制清理所有残留资源(每次新任务前调用)==========
function forceCleanupAll() {
    // 只有本 frame 有活跃任务时才输出清理日志,避免多 frame 噪音
    var hasActiveTask = isVideoTaskActive || currentVideoWorker || currentProgressBar || currentVideoInterval;
    if (hasActiveTask) {
        log('🧹 开始清理残留资源...', 'orange');
    }
    
    // 停止Worker - 使用局部变量保存引用,确保 setTimeout 回调能正确终止
    if (currentVideoWorker) {
        var workerToTerminate = currentVideoWorker;
        currentVideoWorker = null;  // 先清空全局引用
        reportWorker = null;        // 同步清空兼容引用
        try {
            workerToTerminate.aborted = true;  // 标记为已取消,阻止旧请求重试
            workerToTerminate.postMessage({ type: 'stop' });
            setTimeout(function() {
                try {
                    workerToTerminate.terminate();
                } catch(e) {}
            }, 100);
            log('✅ 已终止视频上报Worker', 'green');
        } catch(e) {
            log('⚠️ 终止Worker失败: ' + e.message, 'orange');
        }
    }
    
    // 清除定时器
    if (currentVideoInterval) {
        clearInterval(currentVideoInterval);
        currentVideoInterval = null;
        log('✅ 已清除视频定时器', 'green');
    }
    
    // 清除reportWorker引用(兼容旧代码,Worker 可能已被上面清除)
    if (reportWorker) {
        try {
            reportWorker.aborted = true;
            reportWorker.terminate();
        } catch(e) {}
        reportWorker = null;
    }
    
    // 立即移除进度条
    removeProgressBar();
    
    // 重置所有视频相关状态
    isVideoTaskActive = false;
    currentVideoName = null;
    activeVideoJob = null;
    currentVideoTaskId = null;
    currentVideoPlayedTime = 0;
    
    if (hasActiveTask) {
        log('🧹 残留资源清理完成', 'green');
    }
}

// 单独移除进度条函数
function removeProgressBar() {
    if (currentProgressBar) {
        try {
            $(currentProgressBar).remove();
        } catch(e) {}
        currentProgressBar = null;
    }
    // 也扫描 top.document 清除可能残留的进度条(切换章节时旧引用可能已失效)
    try {
        var topDoc = _w.top.document || document;
        $(topDoc).find('#video-progress-bar').remove();
    } catch(e) {}
    try {
        $(document).find('#video-progress-bar').remove();
    } catch(e) {}
}

// ========== 进度条管理(修复后台更新问题)==========
function createProgressBar(videoName, duration) {
    // 先彻底移除旧的
    removeProgressBar();

    currentVideoName = videoName;
    var taskId = currentVideoTaskId;

    var targetDoc = top.document;
    var barHtml = `
        <div id="video-progress-bar" data-task-id="${taskId}" style="
            position:fixed;
            bottom:80px;
            right:20px;
            width:300px;
            background:rgba(0,0,0,0.85);
            border-radius:12px;
            padding:12px 15px;
            z-index:100000;
            font-family:system-ui,'Segoe UI',sans-serif;
            box-shadow:0 4px 15px rgba(0,0,0,0.3);
            backdrop-filter:blur(8px);
            border:1px solid rgba(255,255,255,0.2);
        ">
            <div style="display:flex;justify-content:space-between;margin-bottom:8px;">
                <span style="color:#fff;font-size:13px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${videoName}">🎬 ${videoName}</span>
                <span id="progress-percent" style="color:#4CAF50;font-size:12px;">0%</span>
            </div>
            <div style="width:100%;height:8px;background:rgba(255,255,255,0.2);border-radius:4px;overflow:hidden;">
                <div id="progress-bar-fill" style="width:0%;height:100%;background:linear-gradient(90deg,#4CAF50,#8BC34A);border-radius:4px;transition:width 0.3s;"></div>
            </div>
            <div style="display:flex;justify-content:space-between;margin-top:8px;">
                <span id="progress-current" style="color:#ccc;font-size:11px;">00:00</span>
                <span id="progress-remaining" style="color:#ccc;font-size:11px;">剩余: --:--</span>
                <span style="color:#ccc;font-size:11px;">${formatDuration(duration)}</span>
            </div>
            <div style="margin-top:8px;text-align:center;">
                <span style="color:#4CAF50;font-size:10px;">● 后台上报中</span>
                <span style="color:#888;font-size:10px;margin-left:8px;">⚡ ${setting.rate}x</span>
            </div>
        </div>
    `;
    
    try {
        $(targetDoc.body).append(barHtml);
        currentProgressBar = targetDoc.querySelector('#video-progress-bar');
    } catch(e) {
        $(document.body).append(barHtml);
        currentProgressBar = document.querySelector('#video-progress-bar');
    }
    
    // 确保进度条在后台时也能更新(使用requestAnimationFrame但不依赖页面激活状态)
    log('📊 进度条已创建,后台模式持续更新', 'blue');
}

function updateProgressBar(playTime, duration) {
    if (!currentProgressBar) return;
    
    // 检查是否是当前任务
    var taskId = currentProgressBar.getAttribute('data-task-id');
    if (taskId && currentVideoTaskId && parseInt(taskId) !== currentVideoTaskId) {
        return;
    }
    
    var percent = Math.min(100, Math.round((playTime / duration) * 100));
    var remaining = Math.max(0, duration - playTime);
    
    var fillBar = currentProgressBar.querySelector('#progress-bar-fill');
    var percentSpan = currentProgressBar.querySelector('#progress-percent');
    var currentSpan = currentProgressBar.querySelector('#progress-current');
    var remainingSpan = currentProgressBar.querySelector('#progress-remaining');
    
    if (fillBar) fillBar.style.width = percent + '%';
    if (percentSpan) percentSpan.textContent = percent + '%';
    if (currentSpan) currentSpan.textContent = formatDuration(playTime);
    if (remainingSpan) remainingSpan.textContent = '剩余: ' + formatDuration(remaining);
}

// ========== 完成任务并继续 ==========
function completeCurrentTask() {
    // 清理残留
    forceCleanupAll();
    
    if (_mlist && _mlist.length > 0) {
        var completedTask = _mlist[0];
        var taskName = completedTask.property ? (completedTask.property.name || completedTask.property.title || '未知') : '未知';
        log('✅ 完成任务: ' + taskName, 'green');
        
        _mlist.splice(0, 1);
        completedMissionCount++;
        log('📋 任务进度: ' + completedMissionCount + '/' + pendingMissionCount + ' | 剩余: ' + _mlist.length, 'blue');
    }
    
    if (_domList && _domList.length > 0) {
        _domList.splice(0, 1);
    }
    
    isProcessing = false;
    
    if (_mlist && _mlist.length > 0) {
        log('📋 还有 ' + _mlist.length + ' 个任务点待处理,继续...', 'green');
        setTimeout(function() {
            missionStart();
        }, 1000);
    } else {
        log('✅ 此页面所有任务处理完毕', 'green');
        toNext();
    }
}

function toNext() {
    if (isJumping) {
        log('⚠️ 跳转锁定中,5秒后重试...', 'orange');
        setTimeout(function() { isJumping = false; toNext(); }, 5000);
        return;
    }
    isJumping = true;

    log('🔄 检查是否有下一章节...', 'blue');

    var nextBtn = null;
    try {
        var topDoc = top.document;
        nextBtn = topDoc.querySelector('#prevNextFocusNext:not(.disabled)') ||
                  topDoc.querySelector('.orientationright:not(.disabled)') ||
                  topDoc.querySelector('.jb_btn.jb_btn_next:not(.disabled)') ||
                  topDoc.querySelector('#mainid > .prev_next.next:not(.disabled)') ||
                  topDoc.querySelector('.prev_next.next:not(.disabled)') ||
                  topDoc.querySelector('.nodeItem.r i') ||
                  topDoc.querySelector('[onclick*="PCount.next"]');
    } catch(e) {
        log('⚠️ 无法访问顶层页面,尝试当前页面...', 'orange');
        try {
            nextBtn = document.querySelector('#prevNextFocusNext:not(.disabled)') ||
                      document.querySelector('.orientationright:not(.disabled)') ||
                      document.querySelector('.prev_next.next:not(.disabled)');
        } catch(e2) {}
    }

    if (nextBtn && !nextBtn.disabled) {
        log('✅ 找到下一节按钮,3秒后跳转', 'green');
        setTimeout(() => {
            nextBtn.click();
            log('📖 已点击跳转按钮', 'green');
            isJumping = false;
        }, 3000);
        return true;
    } else {
        log('📚 未找到下一节按钮,尝试URL跳转...', 'orange');
        try {
            var topDoc = top.document;
            var curNode = topDoc.querySelector('.posCatalog_active, .currents, .jb_item.active');
            if (curNode) {
                var nextNode = curNode.nextElementSibling || curNode.parentElement.nextElementSibling;
                if (nextNode) {
                    var link = nextNode.querySelector('a') || nextNode;
                    if (link && link.click) {
                        log('✅ 通过章节目录跳转下一节', 'green');
                        setTimeout(() => {
                            link.click();
                            isJumping = false;
                        }, 3000);
                        return true;
                    }
                }
            }
        } catch(e) {}
        log('📚 课程已全部完成,无更多章节可跳转', 'green');
    }

    isJumping = false;
    return false;
}

// ========== 获取视频状态 ==========
function getVideoStatus(item, callback, iframeDom) {
    var objectId = item.property.objectid;
    var uid = getCookie('_uid') || getCookie('UID') || '';
    var statusUrl = _l.protocol + '//' + _l.host + '/ananas/status/' + objectId +
        '?k=' + (getCookie('fid') || '') + '&_uid=' + uid + '&flag=normal&_dc=' + Date.now();

    // 从任务对象获取已播放时间(headOffset单位是毫秒)
    var taskPlayedTime = 0;
    if (item.headOffset && item.headOffset > 0) {
        taskPlayedTime = Math.floor(item.headOffset / 1000);
        log('📊 从任务数据获取播放位置: ' + formatDuration(taskPlayedTime), 'blue');
    } else if (item.playTime && item.playTime > 0) {
        taskPlayedTime = Math.floor(item.playTime / 1000);
        log('📊 从任务playTime获取播放位置: ' + formatDuration(taskPlayedTime), 'blue');
    }

    // 尝试从iframe中的video元素获取当前播放位置
    var iframePlayedTime = 0;
    if (iframeDom && iframeDom.length > 0) {
        try {
            var iframeEl = iframeDom[0];
            var iframeDoc = iframeEl.contentDocument || iframeEl.contentWindow.document;
            var videoEl = iframeDoc.querySelector('video');
            if (videoEl && videoEl.currentTime > 0) {
                iframePlayedTime = Math.floor(videoEl.currentTime);
                log('📊 从页面视频元素获取到播放位置: ' + formatDuration(iframePlayedTime), 'blue');
            }
        } catch(e) {
            // 跨域或无法访问iframe,忽略
        }
    }

    GM_xmlhttpRequest({
        method: "GET",
        url: statusUrl,
        headers: {
            'Host': _l.host,
            'Referer': _l.protocol + '//' + _l.host + '/ananas/modules/video/index.html'
        },
        onload: function(res) {
            try {
                var videoInfo = JSON.parse(res.responseText);
                // 取所有来源中最大的播放位置(历史最远)
                var playedTime = Math.max(taskPlayedTime, iframePlayedTime);
                log('📊 视频状态: 已播放 ' + formatDuration(playedTime) + '/' + formatDuration(videoInfo.duration) + (videoInfo.isPassed ? ' (已通过)' : ''), 'blue');
                callback({
                    success: true,
                    duration: videoInfo.duration,
                    dtoken: videoInfo.dtoken,
                    objectId: objectId,
                    rt: item.property.rt || '0.9',
                    otherInfo: item.otherInfo || '',
                    jobid: item.jobid,
                    playedTime: playedTime,
                    isPassed: videoInfo.isPassed || false,
                    name: item.property.name
                });
            } catch(e) {
                callback({ success: false, error: e.message });
            }
        },
        onerror: function(err) {
            callback({ success: false, error: err.error });
        }
    });
}

// ========== 模拟视频上报(修复版)==========
function simulateVideoReport(videoInfo, taskObj, iframeDom) {
    var name = videoInfo.name || taskObj.property.name;
    var duration = videoInfo.duration;
    var isAlreadyPassed = videoInfo.isPassed || false;

    if (isAlreadyPassed && !setting.review) {
        log('✅ 视频已完成: ' + name + ',跳过', 'green');
        completeCurrentTask();
        return;
    }

    // 注意:即使 playedTime >= duration,只要 isPassed 为 false,服务器仍未认定完成
    // 此时需要重新完整上报(从0开始),让服务器记录完整观看序列
    var playedTime = videoInfo.playedTime || 0;
    if (playedTime >= duration && duration > 0) {
        log('📊 视频本地数据显示已播完: ' + name + ' (' + formatDuration(playedTime) + '/' + formatDuration(duration) + '),但服务器未认定完成,将重新完整上报', 'orange');
    }

    log('🎬 模拟上报模式: ' + name + ',总时长: ' + formatDuration(duration), 'purple');
    if (playedTime > 0 && playedTime < duration) {
        log('📡 从0开始上报,已看部分(' + formatDuration(playedTime) + ')将快速跳过(10x),剩余 ' + formatDuration(duration - playedTime), 'blue');
    } else {
        log('📡 从头开始上报', 'blue');
    }

    var taskId = ++videoTaskCounter;
    currentVideoTaskId = taskId;

    // 保存记忆播放位置,进度条以此为起点
    currentVideoPlayedTime = playedTime;

    if (setting.showProgressBar) {
        createProgressBar(name, duration);
        // 进度条从记忆播放位置开始显示(上报仍从0开始)
        updateProgressBar(playedTime, duration);
    }

    isVideoTaskActive = true;
    currentVideoName = name;

    // 创建新的Worker(包含实际上报逻辑)
    currentVideoWorker = createReportWorker(videoInfo, taskObj);
    reportWorker = currentVideoWorker; // 兼容

    var workerTaskId = taskId;

    // 设置完成回调
    currentVideoWorker.completeCallback = function() {
        if (workerTaskId === currentVideoTaskId && isVideoTaskActive) {
            // 关键:触发 iframe 内 video 元素的 ended 事件,让播放器自动给任务点加 .ans-job-finished
            // 学生学习页面用此类判定完成;不触发的话即便服务器已记完成、页面仍显示未完成
            try {
                if (iframeDom && iframeDom.length > 0) {
                    var iframeEl = iframeDom[0];
                    var iframeDoc = iframeEl.contentDocument || iframeEl.contentWindow.document;
                    if (iframeDoc) {
                        var mediaEl = iframeDoc.querySelector('video') || iframeDoc.querySelector('audio');
                        if (mediaEl) {
                            mediaEl.dispatchEvent(new Event('ended', { bubbles: true }));
                            log('🎬 已触发 ended 事件,任务点UI将自动更新为已完成', 'green');
                        } else {
                            log('⚠️ iframe 内未找到 video/audio 元素,任务点UI可能需刷新页面才会更新', 'orange');
                        }
                    }
                }
            } catch(e) {
                log('⚠️ 触发 ended 事件失败: ' + e.message + ',任务点UI可能需刷新页面才会更新', 'orange');
            }
            forceCleanupAll();
            completeCurrentTask();
        }
    };
}

function normalVideoPlay(dom, obj) {
    var name = obj.property.name;
    var target = dom.length > 0 ? dom[0] : null;
    
    if (!target) {
        log('⚠️ 未找到视频iframe,3秒后重试', 'orange');
        isProcessing = false;
        setTimeout(function() {
            normalVideoPlay(dom, obj);
        }, 3000);
        return;
    }
    
    log('🎬 正常播放模式处理视频:' + name, 'purple');
    
    isVideoTaskActive = true;
    currentVideoName = name;
    
    var executed = false;
    var doc = target.contentDocument || target.contentWindow.document;
    
    if (currentVideoInterval) {
        clearInterval(currentVideoInterval);
    }
    
    // 60 秒超时兜底:iframe 加载失败时媒体元素永远找不到,避免永久轮询卡住后续任务
    var _waitStartTime = Date.now();
    var _MAX_WAIT_MS = 60000;
    
    currentVideoInterval = setInterval(function() {
        // 超时检测:60 秒内未找到媒体元素,说明 iframe 加载失败,跳过该任务
        if (!executed && Date.now() - _waitStartTime > _MAX_WAIT_MS) {
            log('⏱️ 视频 [' + name + '] 等待超时,iframe 可能加载失败,跳过', 'orange');
            clearInterval(currentVideoInterval);
            currentVideoInterval = null;
            isVideoTaskActive = false;
            completeCurrentTask();
            return;
        }
        
        var media = doc.querySelector('video') || doc.querySelector('audio');
        
        if (media && !executed) {
            executed = true;
            log('✅ ' + name + ' 开始播放', 'green');
            
            media.pause();
            media.muted = true;
            media.playbackRate = setting.rate > 1 ? Math.min(setting.rate, 16) : 1;
            media.play();
            
            var resumePlay = function() {
                if (media.paused && !media.ended) {
                    media.play();
                }
            };
            media.addEventListener('pause', resumePlay);
            
            var onVideoEnd = function() {
                log('✅ ' + name + ' 播放完成', 'green');
                media.removeEventListener('pause', resumePlay);
                media.removeEventListener('ended', onVideoEnd);
                clearInterval(currentVideoInterval);
                currentVideoInterval = null;
                forceCleanupAll();
                completeCurrentTask();
            };
            media.addEventListener('ended', onVideoEnd);
            
            if (media.ended) {
                onVideoEnd();
            }
        }
    }, 1500);
}

function missionVideo(dom, obj) {
    if (!setting.video) {
        log('ℹ️ 用户设置不处理视频任务', 'orange');
        completeCurrentTask();
        return;
    }
    
    var isPassed = obj.isPassed;
    var name = obj.property.name;
    
    if (!setting.review && isPassed === true) {
        log('✅ 视频:' + name + ' 已完成,跳过', 'green');
        completeCurrentTask();
        return;
    }
    
    if (setting.videoMode === 'simulate') {
        log('🎬 模拟上报模式处理视频: ' + name, 'purple');

        getVideoStatus(obj, function(result) {
            if (result.success) {
                simulateVideoReport(result, obj, dom);
            } else {
                log('❌ 获取视频信息失败: ' + result.error + ',降级到正常播放', 'red');
                normalVideoPlay(dom, obj);
            }
        }, dom);
        return;
    }
    
    normalVideoPlay(dom, obj);
}

function missionAudio(dom, obj) {
    if (!setting.audio) {
        log('ℹ️ 用户设置不处理音频任务', 'orange');
        completeCurrentTask();
        return;
    }
    
    var isPassed = obj.isPassed;
    var name = obj.property.name;
    
    if (!setting.review && isPassed === true) {
        log('✅ 音频:' + name + ' 已完成,跳过', 'green');
        completeCurrentTask();
        return;
    }
    
    log('🎵 处理音频:' + name + ',等待5秒后完成', 'purple');
    
    setTimeout(function() {
        completeCurrentTask();
    }, 5000);
}

function missionBook(dom, obj) {
    var jobId = obj.property.jobid;
    var name = obj.property.bookname;
    var jtoken = obj.jtoken;
    var knowledgeId = _defaults.knowledgeid;
    var courseId = _defaults.courseid;
    var clazzId = _defaults.clazzId;
    
    if (isTaskCompleted(obj)) {
        log('✅ 读书:' + name + ' 已完成,跳过', 'green');
        completeCurrentTask();
        return;
    }
    
    $.ajax({
        url: _l.protocol + "//" + _l.host + '/ananas/job?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + Date.now(),
        method: 'GET',
        success: function(res) {
            log('📚 读书:' + name + (res.msg || '完成'), 'green');
            completeCurrentTask();
        },
        error: function() {
            log('❌ 读书:' + name + ' 处理失败,3秒后重试', 'red');
            isProcessing = false;
            setTimeout(function() {
                missionBook(dom, obj);
            }, 3000);
        }
    });
}

function missionDoucument(dom, obj) {
    var jobId = obj.property.jobid;
    var name = obj.property.name;
    var jtoken = obj.jtoken;
    var knowledgeId = _defaults.knowledgeid;
    var courseId = _defaults.courseid;
    var clazzId = _defaults.clazzId;
    
    if (isTaskCompleted(obj)) {
        log('✅ 文档:' + name + ' 已完成,跳过', 'green');
        completeCurrentTask();
        return;
    }
    
    $.ajax({
        url: _l.protocol + "//" + _l.host + '/ananas/job/document?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + Date.now(),
        method: 'GET',
        success: function(res) {
            log('📄 文档:' + name + (res.msg || '完成'), 'green');
            completeCurrentTask();
        },
        error: function() {
            log('❌ 文档:' + name + ' 处理失败,3秒后重试', 'red');
            isProcessing = false;
            setTimeout(function() {
                missionDoucument(dom, obj);
            }, 3000);
        }
    });
}

function missionRead(dom, obj) {
    var jobId = obj.property.jobid;
    var name = obj.property.title;
    var jtoken = obj.jtoken;
    var knowledgeId = _defaults.knowledgeid;
    var courseId = _defaults.courseid;
    var clazzId = _defaults.clazzId;
    
    if (isTaskCompleted(obj)) {
        log('✅ 阅读:' + name + ' 已完成,跳过', 'green');
        completeCurrentTask();
        return;
    }
    
    $.ajax({
        url: _l.protocol + '//' + _l.host + '/ananas/job/readv2?jobid=' + jobId + '&knowledgeid=' + knowledgeId + '&courseid=' + courseId + '&clazzid=' + clazzId + '&jtoken=' + jtoken + '&_dc=' + Date.now(),
        method: 'GET',
        success: function(res) {
            log('📖 阅读:' + name + (res.msg || '完成'), 'green');
            completeCurrentTask();
        },
        error: function() {
            log('❌ 阅读:' + name + ' 处理失败,3秒后重试', 'red');
            isProcessing = false;
            setTimeout(function() {
                missionRead(dom, obj);
            }, 3000);
        }
    });
}

// ========== 旧版→新版自动跳转 ==========
function handleExperienceNew() {
    var expLink = document.querySelector('li>a.experience:not([onclick])');
    if (expLink) {
        expLink.click();
        log('🔄 已自动点击"体验新版",跳转新版页面', 'green');
        return;
    }
    var links = document.querySelectorAll('a.experience, .experience');
    for (var i = 0; i < links.length; i++) {
        if (links[i].textContent.indexOf('新版') !== -1) {
            links[i].click();
            log('🔄 已自动点击"体验新版",跳转新版页面', 'green');
            return;
        }
    }
    var allLinks = document.querySelectorAll('a, button, .btn, [onclick]');
    for (var i = 0; i < allLinks.length; i++) {
        var text = allLinks[i].textContent || allLinks[i].innerText || '';
        if (text.indexOf('体验新版') !== -1) {
            allLinks[i].click();
            log('🔄 已自动点击"新版"按钮,跳转新版页面', 'green');
            return;
        }
    }
    if (_l.host === 'i.chaoxing.com' && _l.pathname.indexOf('/base') !== -1) {
        log('🔄 旧版个人页,直接跳转新版...', 'green');
        _l.href = 'https://mooc1.chaoxing.com/visit/interaction';
        return;
    }
    log('ℹ️ 未找到"体验新版"按钮,可能已是新版', 'blue');
}

// ========== 任务调度(每次开始前强制清理残留)==========
function missionStart() {
    if (isProcessing) {
        log('⚠️ 已有任务在处理中,跳过', 'orange');
        return;
    }

    if (!_mlist || _mlist.length <= 0) {
        log('✅ 此页面任务处理完毕', 'green');
        isProcessing = false;
        toNext();
        return;
    }

    // 检查是否有apiKey(视频任务不需要,答题任务需要)
    var nextType = _mlist[0].type || (_mlist[0].property && _mlist[0].property.module);
    if (nextType === 'workid' && !hasApiKey()) {
        log('❌ 未登录,无法处理答题任务。请先在面板中登录', 'red');
        return;
    }
    
    // 关键:每次开始新任务前,强制清理所有残留资源
    forceCleanupAll();
    
    isProcessing = true;
    var _type = _mlist[0].type;
    var _dom = _domList[0];
    var _task = _mlist[0];
    
    if (_type == undefined) {
        _type = _mlist[0].property.module;
    }
    
    var taskName = _task.property ? (_task.property.name || _task.property.title || '未知') : '未知';
    log('🔄 开始处理任务: ' + taskName + ' (类型: ' + _type + ')', 'blue');
    
    var handlers = {
        'video': () => missionVideo(_dom, _task),
        'audio': () => missionAudio(_dom, _task),
        'workid': () => missionWork(_dom, _task),
        'document': () => missionDoucument(_dom, _task),
        'read': () => missionRead(_dom, _task),
        'insertbook': () => missionBook(_dom, _task)
    };
    
    if (handlers[_type]) {
        handlers[_type]();
    } else if (['insertimage'].includes(_type)) {
        log('ℹ️ 发现无需处理任务,跳过', 'orange');
        completeCurrentTask();
    } else {
        log('⚠️ 暂不支持处理此类型:' + _type + ',跳过', 'red');
        completeCurrentTask();
    }
}

// ========== API答题 ==========
function getAnswer(type, questionText, optionsData, _retried) {
    return new Promise((resolve, reject) => {
        var apiKey = getApiKey();
        if (!apiKey || apiKey.trim() === '') {
            if (!_retried && isLoggedIn()) {
                log('⚠️ API密钥为空,自动刷新中...', 'orange');
                apiFetchUserInfo().then(function() {
                    getAnswer(type, questionText, optionsData, true).then(resolve).catch(reject);
                }).catch(function() {
                    log('❌ 刷新失败,请手动重新登录', 'red');
                    reject('NO_API_KEY');
                });
                return;
            }
            log('❌ 请先注册或设置API密钥', 'red');
            reject('NO_API_KEY');
            return;
        }
        log('🔑 使用密钥: ' + apiKey.substring(0, 8) + '...', 'gray');

        var typeMap = {0:'单选题',1:'多选题',2:'填空题',3:'判断题',4:'简答题'};
        var typeText = typeMap[type] || '单选题';

        log('🔍 请求答案 - 题型:' + typeText + ' 题目:' + questionText.substring(0, 40) + '...', 'blue');

        var payload = {
            question: questionText,
            apiKey: apiKey,
            type: typeText
        };
        if (optionsData && optionsData.length) {
            payload.options = JSON.stringify(optionsData);
        }

        GM_xmlhttpRequest({
            method: 'POST',
            url: API_URL,
            headers: {'Content-Type': 'application/json'},
            data: JSON.stringify(payload),
            timeout: 15000,
            onload: function(xhr) {
                if (xhr.status === 401) {
                    clearAuth();
                    log('⚠️ 登录已过期,请重新登录', 'orange');
                    reject('TOKEN_EXPIRED');
                    return;
                }
                try {
                    var res = JSON.parse(xhr.responseText);
                    if (res.code == 200) {
                        var d = res.data || {};
                        if (d.remainingCount !== undefined) {
                            updateQuotaInfo({ remaining: d.remainingCount });
                        }
                        var answer = d.answer;
                        if (answer) {
                            log('✅ 答案: ' + String(answer).substring(0, 100), 'purple');
                            resolve(answer);
                        } else {
                            log('⚠️ 未找到答案', 'orange');
                            resolve('暂无答案');
                        }
                    } else if (res.code == 500 && res.msg && res.msg.indexOf('凭证') !== -1 && !_retried) {
                        log('⚠️ 凭证无效,自动刷新中...', 'orange');
                        apiFetchUserInfo().then(function() {
                            log('✅ 凭证已刷新,正在重试...', 'green');
                            getAnswer(type, questionText, optionsData, true).then(resolve).catch(reject);
                        }).catch(function() {
                            log('❌ 刷新凭证失败,请手动重新登录', 'red');
                            reject('凭证刷新失败');
                        });
                        return;
                    } else if (res.code == 500) {
                        log('❌ ' + (res.msg || 'API错误'), 'red');
                        reject(res.msg || 'API_ERROR');
                    } else {
                        log('❌ 接口异常: ' + (res.msg || '未知错误'), 'red');
                        reject('接口异常');
                    }
                } catch(e) {
                    log('❌ 解析响应失败', 'red');
                    reject('解析响应失败');
                }
            },
            onerror: function() {
                log('❌ 网络请求失败', 'red');
                reject('网络请求失败');
            },
            ontimeout: function() {
                log('❌ 请求超时', 'red');
                reject('请求超时');
            }
        });
    });
}

// ========== 测验处理 ==========
function missionWork(dom, obj) {
    if (!setting.work) {
        log('ℹ️ 用户设置不自动处理测验', 'orange');
        completeCurrentTask();
        return;
    }
    
    if (!checkApiKeyBeforeAction('自动答题')) {
        completeCurrentTask();
        return;
    }
    
    var isDo = true;
    if (setting.task && obj.jobid == undefined) {
        isDo = false;
    }
    
    if (isDo) {
        if (obj.jobid !== undefined) {
            var phoneWeb = _l.protocol + '//' + _l.host + '/work/phone/work?workId=' + obj.jobid.replace('work-', '') + '&courseId=' + _defaults.courseid + '&clazzId=' + _defaults.clazzId + '&knowledgeId=' + _defaults.knowledgeid + '&jobId=' + obj.jobid + '&enc=' + obj.enc;
            log('📝 准备处理测验', 'purple');
            setTimeout(function() {
                startDoPhoneCyWork(0, [dom], phoneWeb, obj);
            }, 3000);
        } else {
            setTimeout(function() {
                startDoCyWork(0, [dom], obj);
            }, 3000);
        }
    } else {
        log('ℹ️ 用户设置只处理属于任务点的任务', 'orange');
        completeCurrentTask();
    }
}

// ========== 手机端测验处理 ==========
var _phoneRetryCount = 0;
var _PHONE_MAX_RETRY = 6; // 最多重试6次(共30秒)

function startDoPhoneCyWork(index, doms, phoneWeb, taskObj) {
    if (index == doms.length) {
        log('✅ 此页面全部测验已处理完毕', 'green');
        completeCurrentTask();
        return;
    }
    
    var $outerIframe = $(doms[index]);
    var outerDoc = null;
    
    try {
        outerDoc = $outerIframe.contents()[0];
    } catch(e) {
        log('⚠️ 无法访问iframe内容(可能跨域): ' + e.message, 'orange');
    }
    
    if (!outerDoc) {
        _phoneRetryCount++;
        if (_phoneRetryCount > _PHONE_MAX_RETRY) {
            log('❌ 无法访问任务iframe内容,已重试' + _PHONE_MAX_RETRY + '次,跳过此任务', 'red');
            _phoneRetryCount = 0;
            completeCurrentTask();
            return;
        }
        log('⏳ 等待iframe加载... (第' + _phoneRetryCount + '次)', 'blue');
        setTimeout(function() {
            startDoPhoneCyWork(index, doms, phoneWeb, taskObj);
        }, 5000);
        return;
    }
    
    // 先尝试在外层iframe内查找嵌套的iframe
    getElement(outerDoc, 'iframe').then(function(iframe) {
        if (!iframe) {
            // 找不到内层iframe,尝试直接使用外层iframe本身作为工作iframe(适配单层结构)
            log('🔍 未找到内层iframe,尝试直接使用外层iframe', 'blue');
            var workStatus = '';
            try {
                workStatus = $outerIframe.contents().find('.newTestCon .newTestTitle .testTit_status').text().trim();
            } catch(e) {}
            
            if (!workStatus) {
                // 也尝试找 .CeYan(PC端测验容器)
                var hasCeYan = false;
                try {
                    hasCeYan = $outerIframe.contents().find('.CeYan').length > 0;
                } catch(e) {}
                
                if (hasCeYan) {
                    log('📝 检测到PC端测验结构(.CeYan),切换到PC模式处理', 'purple');
                    setTimeout(function() {
                        doWork(index, doms, doms[index], taskObj);
                    }, 2000);
                    return;
                }
                
                _phoneRetryCount++;
                if (_phoneRetryCount > _PHONE_MAX_RETRY) {
                    log('❌ 找不到测验状态和内层iframe,已重试' + _PHONE_MAX_RETRY + '次,跳过', 'red');
                    _phoneRetryCount = 0;
                    _domList.splice(0, 1);
                    isProcessing = false;
                    setTimeout(switchMission, 2000);
                    return;
                }
                log('⏳ 等待测验内容加载... (第' + _phoneRetryCount + '次)', 'blue');
                setTimeout(function() {
                    startDoPhoneCyWork(index, doms, phoneWeb, taskObj);
                }, 5000);
                return;
            }
            
            // 找到了状态,直接在外层iframe上处理
            _phoneRetryCount = 0;
            handlePhoneWorkStatus($outerIframe, workStatus, index, doms, phoneWeb, taskObj, outerDoc);
            return;
        }
        
        // 找到了内层iframe,正常流程
        _phoneRetryCount = 0;
        var workIframe = $(iframe);
        var workStatus = '';
        try {
            workStatus = workIframe.contents().find('.newTestCon .newTestTitle .testTit_status').text().trim();
        } catch(e) {
            log('⚠️ 无法读取内层iframe状态: ' + e.message, 'orange');
        }
        
        if (!workStatus) {
            _domList.splice(0, 1);
            isProcessing = false;
            setTimeout(switchMission, 2000);
            return;
        }
        
        handlePhoneWorkStatus(workIframe, workStatus, index, doms, phoneWeb, taskObj, outerDoc);
    });
}

function handlePhoneWorkStatus(workIframe, workStatus, index, doms, phoneWeb, taskObj, outerDoc) {
    log('📋 测验状态: ' + workStatus, 'blue');
    
    if (workStatus.indexOf("待做") != -1 || workStatus.indexOf("待完成") != -1 || workStatus.indexOf("未达到及格线") != -1) {
        log('🔄 设置手机端测验URL并等待加载...', 'blue');
        workIframe.attr('src', phoneWeb);
        
        // 判断是否是外层iframe直接作为工作iframe(单层结构)
        var isOuterAsWork = (workIframe[0] === $(doms[index])[0]);
        
        if (isOuterAsWork) {
            // 单层结构:外层iframe的src被改为phoneWeb,需要等它加载完
            workIframe.on('load', function() {
                workIframe.off('load');
                setTimeout(function() {
                    try {
                        doPhoneWork(workIframe.contents(), taskObj);
                    } catch(e) {
                        log('⚠️ 手机端测验页面加载异常: ' + e.message, 'orange');
                        completeCurrentTask();
                    }
                }, 2000);
            });
            // 超时兜底
            setTimeout(function() {
                workIframe.off('load');
                try {
                    var $c = workIframe.contents();
                    if ($c && $c.find('.Wrappadding').length > 0) {
                        doPhoneWork($c, taskObj);
                    } else {
                        log('⚠️ 手机端页面加载超时,尝试处理...', 'orange');
                        doPhoneWork($c, taskObj);
                    }
                } catch(e) {
                    log('❌ 手机端测验加载失败: ' + e.message, 'red');
                    completeCurrentTask();
                }
            }, 15000);
        } else {
            // 双层结构:内层iframe的src被修改,等待它出现
            getElement(outerDoc, 'iframe[src="' + phoneWeb + '"]').then(function() {
                setTimeout(function() {
                    doPhoneWork(workIframe.contents(), taskObj);
                }, 3000);
            });
        }
    } else if (workStatus.indexOf('待批阅') != -1) {
        _mlist.splice(0, 1);
        _domList.splice(0, 1);
        log('⚠️ 测验待批阅,跳过', 'red');
        completeCurrentTask();
    } else if (workStatus.indexOf('已完成') != -1 || workStatus.indexOf('已交') != -1) {
        log('✅ 测验已完成,跳过', 'green');
        completeCurrentTask();
    } else {
        _mlist.splice(0, 1);
        _domList.splice(0, 1);
        log('⚠️ 未知状态: ' + workStatus + ',跳过', 'red');
        completeCurrentTask();
    }
}

function doPhoneWork($dom, taskObj) {
    // 答题前先解密iframe内的加密字体
    decryptFontInIframe($dom);
    
    var $cy = $dom.find('.Wrappadding form');
    $subBtn = $cy.find('.zquestions .zsubmit .btn-ok-bottom');
    $okBtn = $dom.find('#okBtn');
    $saveBtn = $cy.find('.zquestions .zsubmit .btn-save');
    var TimuList = $cy.find('.zquestions .Py-mian1');
    
    if (!TimuList || TimuList.length === 0) {
        log('⚠️ 未找到题目列表(.Py-mian1),尝试其他选择器...', 'orange');
        // 尝试其他可能的选择器
        TimuList = $dom.find('.Py-mian1');
        if (!TimuList || TimuList.length === 0) {
            TimuList = $dom.find('.questionLi');
        }
        if (!TimuList || TimuList.length === 0) {
            log('❌ 无法找到题目元素,跳过此测验', 'red');
            completeCurrentTask();
            return;
        }
    }
    
    log('📝 找到 ' + TimuList.length + ' 道题目,开始答题', 'purple');
    startDoPhoneTimu(0, TimuList, taskObj);
}

function isQuestionAnswered($timu, _type) {
    if (_type == 0 || _type == 1) {
        var $opts = _type == 0 ? $timu.find('.answerList.singleChoice li') : $timu.find('.answerList.multiChoice li');
        for (var i = 0; i < $opts.length; i++) {
            if ($($opts[i]).attr('aria-label')) return true;
        }
    } else if (_type == 2) {
        var $inputs = $timu.find('.blankList2 input');
        if ($inputs.length && $inputs.first().val() && $inputs.first().val().trim() !== '') return true;
        // 检查编辑器填空
        var $editorBlocks = $timu.find('[data-editorindex]');
        if ($editorBlocks.length) {
            var hasContent = false;
            $editorBlocks.each(function() {
                var itemId = $(this).attr('data-itemid');
                if (itemId && $('#answer' + itemId).val() && $('#answer' + itemId).val().trim() !== '') {
                    hasContent = true;
                }
            });
            if (hasContent) return true;
        }
    } else if (_type == 3) {
        var $pd = $timu.find('.answerList.panduan li');
        for (var i = 0; i < $pd.length; i++) {
            if ($($pd[i]).attr('aria-label')) return true;
        }
    } else if (_type == 4) {
        var $ta = $timu.find('textarea[name^="answer"]');
        if ($ta.length && $ta.first().val() && $ta.first().val().trim() !== '') return true;
    }
    return false;
}

function getQuestionType($timu, typeName) {
    var typeMap = {
        '单选题': 0,
        '多选题': 1,
        '填空题': 2,
        '判断题': 3,
        '简答题': 4
    };
    var _type = typeMap[typeName];

    if (_type === undefined) {
        // 手机端选择器
        if ($timu.find('.answerList.singleChoice li').length) _type = 0;
        else if ($timu.find('.answerList.multiChoice li').length) _type = 1;
        else if ($timu.find('.blankList2 input').length) _type = 2;
        else if ($timu.find('.answerList.panduan li').length) _type = 3;
        // PC端选择器(章节测验)
        else if ($timu.find('.Zy_ulTop li').length) {
            var hasCheckbox = $timu.find('.Zy_ulTop li input[type="checkbox"]').length > 0;
            _type = hasCheckbox ? 1 : 0;
        }
        else if ($timu.find('textarea').length) _type = 4;
    }
    return _type;
}

function handlePhoneJudge($timu, answer) {
    var trueList = ['正确', '是', '对', '√', 'T', 'true', 'ri', 'right'];
    var ansLower = (answer || '').trim().toLowerCase();
    var isTrue = trueList.some(function(k) { return ansLower === k.toLowerCase() || ansLower.indexOf(k.toLowerCase()) !== -1; });
    var $pd = $timu.find('.answerList.panduan li');

    if (isTrue) {
        $pd.each(function(i, t) {
            if ($(t).attr('val-param') == 'true') {
                $(t).click();
                log('✅ 手机端判断题选择: 正确 (接口返回: ' + answer + ')', 'green');
            }
        });
    } else {
        $pd.each(function(i, t) {
            if ($(t).attr('val-param') == 'false') {
                $(t).click();
                log('✅ 手机端判断题选择: 错误 (接口返回: ' + answer + ')', 'green');
            }
        });
    }
}

function startDoPhoneTimu(index, TimuList, taskObj) {
    if (index == TimuList.length) {
        finishQuiz();
        return;
    }
    
    var contextWindow = TimuList[index] ? (TimuList[index].ownerDocument.defaultView || unsafeWindow) : unsafeWindow;
    var $timu = $(TimuList[index]);
    var questionFull = $timu.find('.Py-m1-title').html();
    var _question = tidyQuestion(questionFull).replace(/.*?\[.*?题\]\s*\n\s*/, '').trim();
    var typeName = questionFull.match(/.*?\[(.*?)]|$/)[1];
    var _type = getQuestionType($timu, typeName);
    
    if (isQuestionAnswered($timu, _type)) {
        log('📌 第' + (index + 1) + '题已作答,跳过', 'green');
        setTimeout(() => startDoPhoneTimu(index + 1, TimuList, taskObj), 30);
        return;
    }
    
    var $opts = [];
    var optsText = [];
    var optionsData = [];
    var pureQuestion = _question;
    
    if (_type == 0 || _type == 1) {
        $opts = _type == 0 ? $timu.find('.answerList.singleChoice li') : $timu.find('.answerList.multiChoice li');
        $opts.each(function() {
            optsText.push(tidyStr($(this).html()).replace(/^[A-Z]\s*\n\s*/, '').trim());
        });
        optionsData = optsText;
    } else if (_type == 2) {
        var blankCount = getBlankInputCount($timu);
        optionsData = [blankCount.toString()];
        log('📝 填空题传入空数量: ' + blankCount, 'blue');
    } else if (_type == 3) {
        optionsData = ["对", "错"];
    } else if (_type == 4) {
        optionsData = ["1"];
    }
    
    getAnswer(_type, pureQuestion, optionsData).then((agrs) => {
        if (agrs == '暂无答案' || !agrs) {
            log('⚠️ 无法匹配正确答案,跳过此题', 'red');
            setTimeout(() => startDoPhoneTimu(index + 1, TimuList, taskObj), setting.time);
            return;
        }
        
        if (setting.alterTitle) {
            $timu.find('.Py-m1-title').html($timu.find('.Py-m1-title').html() + '<p style="color:green;">📖 ' + agrs + '</p>');
        }
        
        if (_type == 0) {
            var idx = optsText.findIndex(t => t == agrs);
            if (idx != -1) $timu.find('.answerList.singleChoice li').eq(idx).click();
            else log('⚠️ 未找到匹配选项: ' + agrs, 'orange');
        } else if (_type == 1) {
            var ansArr = splitAnswer(agrs);
            $opts.each((i, t) => {
                if (ansArr.includes(optsText[i])) {
                    setTimeout(() => $(t).click(), 300);
                }
            });
        } else if (_type == 2) {
            fillBlankAnswer($timu, agrs, true, contextWindow);
        } else if (_type == 3) {
            handlePhoneJudge($timu, agrs);
        } else if (_type == 4) {
            var filled = fillShortAnswer($timu, agrs, true, contextWindow);
            if (!filled) {
                log('⚠️ 第' + (index + 1) + '题答案未能填入,跳过', 'orange');
                setTimeout(() => startDoPhoneTimu(index + 1, TimuList, taskObj), setting.time);
                return;
            }
        }

        log('✅ 第' + (index + 1) + '题自动答题成功', 'green');
        setTimeout(() => startDoPhoneTimu(index + 1, TimuList, taskObj), setting.time);
    }).catch((err) => {
        if (err === 'NO_API_KEY' || err === 'TOKEN_EXPIRED' || err === '凭证刷新失败') {
            log('❌ 未登录,停止答题。请先登录后再使用', 'red');
            isProcessing = false;
            return;
        }
        log('⚠️ 第' + (index + 1) + '题获取答案失败,跳过', 'orange');
        setTimeout(() => startDoPhoneTimu(index + 1, TimuList, taskObj), setting.time);
    });
}

// ========== PC端测验处理 ==========
var _pcRetryCount = 0;
var _PC_MAX_RETRY = 6;

function startDoCyWork(index, doms, taskObj) {
    if (index == doms.length) {
        log('✅ 此页面全部测验已处理完毕', 'green');
        completeCurrentTask();
        return;
    }
    
    var $outerIframe = $(doms[index]);
    var outerDoc = null;
    
    try {
        outerDoc = $outerIframe.contents()[0];
    } catch(e) {
        log('⚠️ PC端:无法访问iframe内容(可能跨域): ' + e.message, 'orange');
    }
    
    if (!outerDoc) {
        _pcRetryCount++;
        if (_pcRetryCount > _PC_MAX_RETRY) {
            log('❌ PC端:无法访问任务iframe内容,已重试' + _PC_MAX_RETRY + '次,跳过', 'red');
            _pcRetryCount = 0;
            completeCurrentTask();
            return;
        }
        log('⏳ PC端:等待iframe加载... (第' + _pcRetryCount + '次)', 'blue');
        setTimeout(function() {
            startDoCyWork(index, doms, taskObj);
        }, 5000);
        return;
    }
    
    getElement(outerDoc, 'iframe').then(function(iframe) {
        if (!iframe) {
            // 找不到内层iframe,尝试直接使用外层iframe(单层结构兼容)
            log('🔍 PC端:未找到内层iframe,尝试直接使用外层iframe', 'blue');
            var hasCeYan = false;
            try {
                hasCeYan = $outerIframe.contents().find('.CeYan').length > 0;
            } catch(e) {}
            
            if (hasCeYan) {
                log('📝 PC端:检测到测验结构(.CeYan),直接处理', 'purple');
                _pcRetryCount = 0;
                setTimeout(function() {
                    doWork(index, doms, doms[index], taskObj);
                }, 2000);
                return;
            }
            
            _pcRetryCount++;
            if (_pcRetryCount > _PC_MAX_RETRY) {
                log('❌ PC端:找不到测验内容,已重试' + _PC_MAX_RETRY + '次,跳过', 'red');
                _pcRetryCount = 0;
                _domList.splice(0, 1);
                isProcessing = false;
                setTimeout(switchMission, 2000);
                return;
            }
            log('⏳ PC端:等待内层iframe加载... (第' + _pcRetryCount + '次)', 'blue');
            setTimeout(function() {
                startDoCyWork(index, doms, taskObj);
            }, 5000);
            return;
        }
        
        _pcRetryCount = 0;
        var workIframe = $(iframe);
        var workStatus = '';
        try {
            workStatus = workIframe.contents().find(".newTestCon .newTestTitle .testTit_status").text().trim();
        } catch(e) {
            log('⚠️ PC端:无法读取内层iframe状态: ' + e.message, 'orange');
        }
        
        if (!workStatus) {
            _domList.splice(0, 1);
            isProcessing = false;
            setTimeout(switchMission, 2000);
            return;
        }
        
        if (workStatus.indexOf("待做") != -1 || workStatus.indexOf("待完成") != -1) {
            log('📝 准备处理测验 (状态: ' + workStatus + ')', 'purple');
            setTimeout(function() {
                doWork(index, doms, iframe, taskObj);
            }, 5000);
        } else if (workStatus.indexOf('待批阅') != -1) {
            _mlist.splice(0, 1);
            _domList.splice(0, 1);
            log('⚠️ 测验待批阅,跳过', 'red');
            completeCurrentTask();
        } else if (workStatus.indexOf('已完成') != -1 || workStatus.indexOf('已交') != -1) {
            log('✅ 测验已完成,跳过', 'green');
            completeCurrentTask();
        } else {
            _mlist.splice(0, 1);
            _domList.splice(0, 1);
            log('⚠️ 未知状态: ' + workStatus + ',跳过', 'red');
            completeCurrentTask();
        }
    });
}

function doWork(index, doms, dom, taskObj) {
    _workIframeDom = dom; // 保存iframe元素引用,供finishQuiz获取contentWindow
    $frame_c = $(dom).contents();
    
    // 答题前先解密iframe内的加密字体
    decryptFontInIframe($frame_c);
    
    var $CyHtml = $frame_c.find('.CeYan');
    var TiMuList = $CyHtml.find('.TiMu');
    
    if (!TiMuList || TiMuList.length === 0) {
        log('⚠️ PC端:未找到题目列表(.TiMu),检查页面结构', 'orange');
        log('🔍 .CeYan元素数: ' + $CyHtml.length + ', iframe内容长度: ' + ($frame_c.find('body').html() || '').length, 'blue');
    } else {
        log('📝 PC端:找到 ' + TiMuList.length + ' 道题目,开始答题', 'purple');
    }
    
    $subBtn = $frame_c.find(".ZY_sub").find(".btnSubmit");
    $saveBtn = $frame_c.find(".ZY_sub").find(".btnSave");
    startDoWork(0, TiMuList);
}

function handlePCJudge($timu, answer) {
    var trueList = ['正确', '是', '对', '√', 'T', 'true', 'ri', 'right'];
    var ansLower = (answer || '').trim().toLowerCase();
    var isTrue = trueList.some(function(k) { return ansLower === k.toLowerCase() || ansLower.indexOf(k.toLowerCase()) !== -1; });

    var $pdContainer = $timu.find('.Zy_ulTop li');
    if ($pdContainer.length === 0) {
        $pdContainer = $timu.find('.stem_answer .answer_p').parent();
    }

    function selectJudgeOption(el) {
        if (!el) return;
        var dom = el.jquery ? el[0] : el;
        if (!dom) return;

        // 如果已经选中,不再操作(避免取消选择)
        if (dom.classList.contains('on') || dom.classList.contains('cur') || dom.classList.contains('selected') || dom.getAttribute('aria-checked') === 'true') {
            return;
        }

        var iframeWin = dom.ownerDocument.defaultView;
        var iframe$ = iframeWin && iframeWin.jQuery ? iframeWin.jQuery : $;

        // 直接操作DOM: 清除其他选项,选中当前
        $(dom).siblings('li').removeClass('on');
        $(dom).addClass('on');
        try { iframe$(dom).siblings('li').removeClass('on'); } catch(e) {}
        try { iframe$(dom).addClass('on'); } catch(e) {}

        // 处理 radio input
        var inputs = dom.querySelectorAll('input[type="radio"], input[type="checkbox"]');
        for (var i = 0; i < inputs.length; i++) {
            inputs[i].checked = true;
            try { inputs[i].dispatchEvent(new Event('change', {bubbles: true})); } catch(e) {}
        }

        // 更新隐藏答案字段
        var $tm = $(dom).closest('.TiMu');
        if ($tm.length) {
            var ansVal = '';
            $tm.find('.Zy_ulTop li.on').each(function(i) {
                var letter = String.fromCharCode(65 + $(this).index());
                ansVal += (i > 0 ? ',' : '') + letter;
            });
            var $ansInput = $tm.find('.Zy_ulBottom input[name^="answer"]');
            if ($ansInput.length) $ansInput.val(ansVal);
            var $ansTa = $tm.find('.Zy_ulBottom textarea[name^="answer"]');
            if ($ansTa.length) $ansTa.val(ansVal);
        }

        // 补充点击尝试
        try { iframe$(dom).click(); } catch(e) {}
        try { dom.click(); } catch(e) {}
        try {
            dom.dispatchEvent(new MouseEvent('click', {bubbles: true, cancelable: true, view: iframeWin}));
        } catch(e) {}
    }

    if (isTrue) {
        var clicked = false;
        $pdContainer.each(function(i, t) {
            var valAttr = $(t).find('span').attr('val-param') || $(t).attr('val-param');
            if (valAttr == 'true') {
                selectJudgeOption(t);
                clicked = true;
                log('✅ PC端判断题选择: 正确 (接口返回: ' + answer + ')', 'green');
            }
        });
        if (!clicked && $pdContainer.length > 0) {
            selectJudgeOption($pdContainer[0]);
            log('✅ PC端判断题选择: 正确(默认第一个) (接口返回: ' + answer + ')', 'green');
        }
    } else {
        var clicked = false;
        $pdContainer.each(function(i, t) {
            var valAttr = $(t).find('span').attr('val-param') || $(t).attr('val-param');
            if (valAttr == 'false') {
                selectJudgeOption(t);
                clicked = true;
                log('✅ PC端判断题选择: 错误 (接口返回: ' + answer + ')', 'green');
            }
        });
        if (!clicked && $pdContainer.length > 1) {
            selectJudgeOption($pdContainer[1]);
            log('✅ PC端判断题选择: 错误(默认第二个) (接口返回: ' + answer + ')', 'green');
        } else if (!clicked && $pdContainer.length === 1) {
            selectJudgeOption($pdContainer[0]);
            log('✅ PC端判断题选择: 错误(唯一选项) (接口返回: ' + answer + ')', 'green');
        }
    }
}

// 关闭所有可能残留的确认弹窗(iframe内、顶层、父级)
function closeAllPopups(iframeWin) {
    // 可安全删除的弹窗(iframe内部/动态生成的)
    var removableSelectors = '#confirmSubWin, .layui-layer, .layui-layer-shade, .layui-layer-move, .dialog-box, .el-overlay, .ui-dialog, .ui-widget-overlay';
    // 只隐藏不删除的弹窗(页面级复用组件)
    var hideOnlySelectors = '#workpop, .maskDiv, .mask, .popbg, #popWin, .popDiv, .AlertCon02, .modal';

    // 关闭iframe内弹窗(可删除)
    if (iframeWin) {
        try {
            var iframeDoc = iframeWin.document;
            iframeDoc.querySelectorAll(removableSelectors).forEach(function(el) { el.style.display = 'none'; el.remove(); });
            iframeDoc.querySelectorAll(hideOnlySelectors).forEach(function(el) { el.style.display = 'none'; });
        } catch(e) {}
    }
    // 关闭$frame_c中的弹窗
    if ($frame_c && $frame_c.length) {
        try {
            $frame_c.find(removableSelectors).hide().remove();
            $frame_c.find(hideOnlySelectors).hide();
        } catch(e) {}
    }

    // 关闭顶层、父级、当前页面的弹窗(只隐藏不删除,这些是页面级组件)
    var allDocs = [document];
    try { allDocs.push(_w.top.document); } catch(e) {}
    try { allDocs.push(_w.parent.document); } catch(e) {}
    allDocs.forEach(function(doc) {
        try {
            $(doc).find(hideOnlySelectors).hide();
            $(doc).find(removableSelectors).hide().remove();
        } catch(e) {}
    });

    log('🧹 已清理确认弹窗', 'gray');
}

function finishQuiz() {
    if (setting.sub || setting.force) {
        log('✅ 测验处理完成,准备自动提交', 'green');

        // 获取iframe的window并覆盖alert/confirm,阻止提交确认弹窗残留
        var iframeWin = null;
        try {
            if (_workIframeDom && _workIframeDom.contentWindow) {
                iframeWin = _workIframeDom.contentWindow;
            } else if ($frame_c && $frame_c.length) {
                iframeWin = $frame_c[0].defaultView || $frame_c[0].parentWindow;
            }
        } catch(e) {}

        // 覆盖iframe内的alert和confirm,阻止弹窗卡住页面
        if (iframeWin) {
            try {
                iframeWin.alert = function() {};
                iframeWin.confirm = function() { return true; };
            } catch(e) {}
        }
        // 同时覆盖顶层和父级的confirm/alert以防万一
        try {
            var _oldTopConfirm = _w.top.confirm;
            _w.top.confirm = function(msg) {
                if (msg && (msg.includes('确认提交') || msg.includes('未做完'))) return true;
                return _oldTopConfirm ? _oldTopConfirm(msg) : true;
            };
        } catch(e) {}
        try {
            var _oldParentConfirm = _w.parent.confirm;
            _w.parent.confirm = function(msg) {
                if (msg && (msg.includes('确认提交') || msg.includes('未做完'))) return true;
                return _oldParentConfirm ? _oldParentConfirm(msg) : true;
            };
        } catch(e) {}

        setTimeout(() => {
            // 直接调用iframe内原生提交函数
            var usedNativeSubmit = false;
            if (iframeWin) {
                try {
                    if (typeof iframeWin.btnBlueSubmit === 'function') {
                        iframeWin.btnBlueSubmit();
                        usedNativeSubmit = true;
                        log('📤 调用btnBlueSubmit()提交', 'blue');
                        setTimeout(() => {
                            try {
                                if (typeof iframeWin.submitCheckTimes === 'function') {
                                    iframeWin.submitCheckTimes();
                                    log('✅ 调用submitCheckTimes()确认提交成功', 'green');
                                } else {
                                    log('✅ 提交成功(无需submitCheckTimes)', 'green');
                                }
                            } catch(e) {
                                log('✅ 提交成功', 'green');
                            }
                            // 延时关闭弹窗,确保提交请求已发出
                            setTimeout(() => {
                                closeAllPopups(iframeWin);
                                completeCurrentTask();
                            }, 2000);
                        }, 1500);
                        return; // 已用原生函数提交,不走DOM点击流程
                    }
                } catch(e) {}
            }

            // 回退方式:DOM点击提交按钮
            if ($subBtn && $subBtn.length) $subBtn.click();
            setTimeout(() => {
                var confirmed = false;
                // 方式1: PC端确认弹窗在测验iframe内 (#confirmSubWin)
                if (!confirmed && $frame_c && $frame_c.length) {
                    var $confirmBtn = $frame_c.find('#confirmSubWin > div > div > a.bluebtn');
                    if ($confirmBtn.length) {
                        $confirmBtn.click();
                        confirmed = true;
                    }
                }
                // 方式1.5: 尝试调用submitCheckTimes确认
                if (!confirmed && iframeWin) {
                    try {
                        if (typeof iframeWin.submitCheckTimes === 'function') {
                            iframeWin.submitCheckTimes();
                            confirmed = true;
                        }
                    } catch(e) {}
                }
                // 方式2: 顶层页面的确认弹窗 (#popok)
                if (!confirmed) {
                    try {
                        var topDoc = _w.top.document;
                        var $popok = $(topDoc).find('#popok');
                        if ($popok.length && $popok.is(':visible')) {
                            $popok.click();
                            confirmed = true;
                        }
                    } catch(e) {}
                }
                // 方式3: 父级页面的确认弹窗
                if (!confirmed) {
                    try {
                        var parentDoc = _w.parent.document;
                        var $popok = $(parentDoc).find('#popok');
                        if ($popok.length && $popok.is(':visible')) {
                            $popok.click();
                            confirmed = true;
                        }
                    } catch(e) {}
                }
                // 方式4: 手机端确认按钮
                if (!confirmed && $okBtn && $okBtn.length) {
                    $okBtn.click();
                    confirmed = true;
                }
                // 强制关闭所有可能的确认弹窗
                closeAllPopups(iframeWin);

                if (confirmed) {
                    log('✅ 提交成功', 'green');
                } else {
                    log('⚠️ 未找到提交确认按钮,弹窗已被拦截', 'orange');
                }
                completeCurrentTask();
            }, 3000);
        }, 5000);
    } else {
        log('💾 测验处理完成,自动保存', 'green');
        setTimeout(() => {
            if ($saveBtn && $saveBtn.length) $saveBtn.click();
            log('✅ 保存成功', 'green');
            completeCurrentTask();
        }, 5000);
    }
}

function pcClickOption($opts, idx, isSingle) {
    var aEl = $opts[idx];
    if (!aEl) { log('⚠️ pcClickOption: aEl为空, idx=' + idx, 'orange'); return; }
    var liEl = $(aEl).parent()[0];
    if (!liEl) { log('⚠️ pcClickOption: liEl为空', 'orange'); return; }

    // 如果选项已经是选中状态,不再点击(避免取消选择)
    if (liEl.classList.contains('on') || liEl.classList.contains('cur') || liEl.classList.contains('selected') || liEl.getAttribute('aria-checked') === 'true') {
        var spanCheck = liEl.querySelector('span.num_option_dx') || liEl.querySelector('span.num_option');
        if (!spanCheck || spanCheck.classList.contains('cur') || spanCheck.classList.contains('selected')) {
            return;
        }
    }

    var iframeDoc = aEl.ownerDocument;
    var iframeWin = iframeDoc.defaultView;
    var iframe$ = iframeWin && iframeWin.jQuery ? iframeWin.jQuery : $;

    // 策略: 直接操作DOM状态 + 多种点击方式并用
    // 超星章节测验选中选项时,li 会加上 class "on"

    // 1. 单选题先清除同组其他选项的选中状态
    if (isSingle !== false) {
        $(liEl).siblings('li').removeClass('on');
        try { iframe$(liEl).siblings('li').removeClass('on'); } catch(e) {}
    }

    // 2. 给当前选项添加 "on" class
    $(liEl).addClass('on');
    try { iframe$(liEl).addClass('on'); } catch(e) {}
    // 3. 处理 radio/checkbox input
    var inputs = liEl.querySelectorAll('input[type="radio"], input[type="checkbox"]');
    for (var i = 0; i < inputs.length; i++) {
        inputs[i].checked = true;
        try {
            var changeEvt = new Event('change', {bubbles: true});
            inputs[i].dispatchEvent(changeEvt);
        } catch(e) {}
    }

    // 4. 更新隐藏的答案字段(超星用 .Zy_ulBottom textarea 或 input[name^="answer"] 存答案)
    var $timu = $(liEl).closest('.TiMu');
    if ($timu.length) {
        var answerVal = '';
        var $allLi = $timu.find('.Zy_ulTop li.on');
        $allLi.each(function(i) {
            var letter = String.fromCharCode(65 + $(this).index());
            answerVal += (i > 0 ? ',' : '') + letter;
        });
        var $ansInput = $timu.find('.Zy_ulBottom input[name^="answer"]');
        if ($ansInput.length) {
            $ansInput.val(answerVal);
            try { iframe$($ansInput[0]).val(answerVal).trigger('change'); } catch(e) {}
        }
        var $ansTa = $timu.find('.Zy_ulBottom textarea[name^="answer"]');
        if ($ansTa.length) {
            $ansTa.val(answerVal);
            try { iframe$($ansTa[0]).val(answerVal).trigger('change'); } catch(e) {}
        }
    } else {
        log('⚠️ pcClickOption: 未找到.TiMu父元素', 'orange');
    }

    // 5. 仍然尝试各种点击方式作为补充
    try { iframe$(liEl).click(); } catch(e) {}
    try { liEl.click(); } catch(e) {}
    try { iframe$(aEl).click(); } catch(e) {}
    try { aEl.click(); } catch(e) {}
    try {
        var evt = new MouseEvent('click', {bubbles: true, cancelable: true, view: iframeWin});
        liEl.dispatchEvent(evt);
    } catch(e) {}
}

function isPCQuestionAnswered($timu, _type) {
    // 检查隐藏答案字段是否已有值
    var $ansInput = $timu.find('.Zy_ulBottom input[name^="answer"]');
    if ($ansInput.length && $ansInput.val() && $ansInput.val().trim() !== '') return true;
    var $ansTa = $timu.find('.Zy_ulBottom textarea[name^="answer"]');
    if ($ansTa.length && $ansTa.val() && $ansTa.val().trim() !== '') return true;

    if (_type == 0 || _type == 1 || _type == 3) {
        var $lis = $timu.find('.Zy_ulTop li');
        for (var i = 0; i < $lis.length; i++) {
            var li = $lis[i];
            // 检查 li 上的选中class
            if (li.classList.contains('on') || li.classList.contains('cur') || li.classList.contains('selected')) return true;
            // 检查 aria-checked 属性
            if (li.getAttribute('aria-checked') === 'true') return true;
            // 检查 span.num_option_dx / span.num_option 上的选中class
            var spanEl = li.querySelector('span.num_option_dx') || li.querySelector('span.num_option') || li.querySelector('span[data]');
            if (spanEl && (spanEl.classList.contains('cur') || spanEl.classList.contains('selected'))) return true;
            // 检查 .onChecked / .check_answer 子元素
            if (li.querySelector('.onChecked') || li.querySelector('.check_answer') || li.querySelector('.check_answer_dx')) return true;
        }
    } else if (_type == 2) {
        var answered = false;
        $timu.find('.Zy_ulTk .XztiHover1 textarea, .stem_answer .Answer .divText .textDIV textarea, .subEditor textarea').each(function() {
            if ($(this).val() && $(this).val().trim() !== '') answered = true;
        });
        return answered;
    } else if (_type == 4) {
        var $ta = $timu.find('textarea');
        if ($ta.length && $ta.val() && $ta.val().trim() !== '') return true;
        return false;
    }
    return false;
}

function startDoWork(c, TiMuList) {
    if (c == TiMuList.length) {
        finishQuiz();
        return;
    }
    
    var $timu = $(TiMuList[c]);
    var questionFull = $timu.find('.Zy_TItle.clearfix > div').html();
    var _question = tidyQuestion(questionFull);
    var typeName = questionFull ? questionFull.match(/【(.*?)】|$/)[1] : undefined;
    var _TimuType = getQuestionType($timu, typeName);

    // PC端已作答检测,避免重复点击导致取消选择
    if (isPCQuestionAnswered($timu, _TimuType)) {
        log('📌 PC第' + (c + 1) + '题已作答,跳过', 'green');
        setTimeout(() => startDoWork(c + 1, TiMuList), 30);
        return;
    }

    var $opts = [];
    var optsText = [];
    var optionsData = [];
    var pureQuestion = _question;

    if (_TimuType == 0 || _TimuType == 1) {
        $opts = $timu.find('.Zy_ulTop li a');
        $opts.each(function() {
            optsText.push(tidyStr($(this).html()).replace(/^[A-Za-z]\s*[\n\r]*\s*/, '').trim());
        });
        optionsData = optsText;
    } else if (_TimuType == 2) {
        var blankCount = getBlankInputCount($timu);
        optionsData = [blankCount.toString()];
        log('📝 PC端填空题传入空数量: ' + blankCount, 'blue');
    } else if (_TimuType == 3) {
        optionsData = ["对", "错"];
    } else if (_TimuType == 4) {
        optionsData = ["1"];
    }

    getAnswer(_TimuType, pureQuestion, optionsData).then((agrs) => {
        if (agrs == '暂无答案' || !agrs) {
            log('⚠️ PC第' + (c + 1) + '题无法获取答案,跳过', 'orange');
            setTimeout(() => startDoWork(c + 1, TiMuList), setting.time);
            return;
        }

        if (setting.alterTitle) {
            $timu.find('.Zy_TItle.clearfix > div').html($timu.find('.Zy_TItle.clearfix > div').html() + '<p style="color:green;">📖 ' + agrs + '</p>');
        }

        var matched = false;
        if (_TimuType == 0) {
            var ansNorm = agrs.replace(/\s+/g, '').trim();
            var idx = optsText.findIndex(t => t == agrs);
            if (idx == -1) idx = optsText.findIndex(t => t.replace(/\s+/g, '').trim() == ansNorm);
            if (idx == -1) idx = optsText.findIndex(t => t.indexOf(agrs) != -1 || agrs.indexOf(t) != -1);
            if (idx != -1) {
                pcClickOption($opts, idx, true);
                matched = true;
            } else {
                log('⚠️ PC第' + (c + 1) + '题未找到匹配选项,答案: [' + agrs + '],选项: [' + optsText.join(' | ') + ']', 'orange');
            }
        } else if (_TimuType == 1) {
            var ansArr = splitAnswer(agrs);
            $opts.each((i, t) => {
                var optNorm = optsText[i].replace(/\s+/g, '').trim();
                for (var ai = 0; ai < ansArr.length; ai++) {
                    var aNorm = ansArr[ai].replace(/\s+/g, '').trim();
                    if (optsText[i] == ansArr[ai] || optNorm == aNorm || optsText[i].indexOf(ansArr[ai]) != -1 || ansArr[ai].indexOf(optsText[i]) != -1) {
                        pcClickOption($opts, i, false);
                        matched = true;
                        break;
                    }
                }
            });
            if (!matched) {
                log('⚠️ PC第' + (c + 1) + '题多选未匹配,答案: [' + agrs + '],选项: [' + optsText.join(' | ') + ']', 'orange');
            }
        } else if (_TimuType == 2) {
            fillBlankAnswer($timu, agrs, false, null);
            matched = true;
        } else if (_TimuType == 3) {
            handlePCJudge($timu, agrs);
            matched = true;
        } else if (_TimuType == 4) {
            fillShortAnswer($timu, agrs, false, null);
            matched = true;
        }

        if (matched) {
            log('✅ PC第' + (c + 1) + '题自动答题成功', 'green');
        }
        setTimeout(() => startDoWork(c + 1, TiMuList), setting.time);
    }).catch((err) => {
        if (err === 'NO_API_KEY' || err === 'TOKEN_EXPIRED' || err === '凭证刷新失败') {
            log('❌ 未登录,停止答题。请先登录后再使用', 'red');
            isProcessing = false;
            return;
        }
        log('⚠️ PC第' + (c + 1) + '题获取答案失败,跳过', 'orange');
        setTimeout(() => startDoWork(c + 1, TiMuList), setting.time);
    });
}

function switchMission() {
    isProcessing = false;
    missionStart();
}

// ========== 作业处理 ==========
function missonHomeWork() {
    log('📝 开始处理作业', 'green');
    if (!checkApiKeyBeforeAction('作业自动答题')) return;
    
    var $_homeworktable = $('.mark_table').find('form');
    var TimuList = $_homeworktable.find('.questionLi');
    doHomeWork(0, TimuList);
}

function doHomeWork(index, TiMuList) {
    if (index == TiMuList.length) {
        log('✅ 作业题目已全部完成', 'green');
        return;
    }
    
    var typeName = $(TiMuList[index]).attr('typename');
    var typeMap = {
        '单选题': 0,
        '多选题': 1,
        '填空题': 2,
        '判断题': 3,
        '简答题': 4
    };
    var _type = typeMap[typeName];
    var _questionFull = $(TiMuList[index]).find('.mark_name').html();
    var _question = tidyQuestion(_questionFull).replace(/^[(].*?[)]/, '').trim();
    
    if (_type === undefined) {
        var _answerTmpArr = $(TiMuList[index]).find('.stem_answer').find('.answer_p');
        if (_answerTmpArr && _answerTmpArr.length > 0) {
            _type = $(TiMuList[index]).find('input[type="checkbox"]').length ? 1 : 0;
        } else if ($(TiMuList[index]).find('.stem_answer').find('.divText textarea').length) {
            _type = 4;
        }
    }
    
    var checkAnswered = function() {
        if (_type == 0 || _type == 1) {
            var $opts = $(TiMuList[index]).find('.stem_answer .answer_p');
            for (var i = 0; i < $opts.length; i++) {
                if ($($opts[i]).parent().find('span').attr('class') && $($opts[i]).parent().find('span').attr('class').indexOf('check_answer') != -1) {
                    return true;
                }
            }
        } else if (_type == 2) {
            var $inputs = $(TiMuList[index]).find('.stem_answer .Answer .divText .textDIV textarea');
            if ($inputs.length && $inputs.val() && $inputs.val().trim() !== '') return true;
        } else if (_type == 3) {
            var $opts = $(TiMuList[index]).find('.stem_answer .answer_p');
            for (var i = 0; i < $opts.length; i++) {
                if ($($opts[i]).parent().find('span').attr('class') && $($opts[i]).parent().find('span').attr('class').indexOf('check_answer') != -1) {
                    return true;
                }
            }
        } else if (_type == 4) {
            var $ta = $(TiMuList[index]).find('.stem_answer .eidtDiv textarea');
            if ($ta.length && $ta.val() && $ta.val().trim() !== '') return true;
        }
        return false;
    };
    
    if (checkAnswered()) {
        log('📌 第' + (index + 1) + '题已作答,跳过', 'green');
        setTimeout(() => doHomeWork(index + 1, TiMuList), 30);
        return;
    }
    
    var $opts = [];
    var optsText = [];
    var optionsData = [];
    var pureQuestion = _question;
    
    if (_type == 0 || _type == 1) {
        $opts = $(TiMuList[index]).find('.stem_answer .answer_p');
        $opts.each(function() {
            optsText.push(tidyStr($(this).html()));
        });
        optionsData = optsText;
    } else if (_type == 2) {
        var blankCount = getBlankInputCount($(TiMuList[index]));
        optionsData = [blankCount.toString()];
        log('📝 作业填空题传入空数量: ' + blankCount, 'blue');
    } else if (_type == 3) {
        optionsData = ["对", "错"];
        log('📝 作业判断题传入固定选项: ["对","错"]', 'blue');
    } else if (_type == 4) {
        optionsData = ["1"];
        log('📝 作业简答题传入固定参数: ["1"]', 'blue');
    }
    
    getAnswer(_type, pureQuestion, optionsData).then((agrs) => {
        if (agrs == '暂无答案' || !agrs) {
            log('⚠️ 作业第' + (index + 1) + '题无法获取答案,跳过', 'orange');
            setTimeout(() => doHomeWork(index + 1, TiMuList), setting.time);
            return;
        }
        
        if (setting.alterTitle) {
            $(TiMuList[index]).find('.mark_name').html($(TiMuList[index]).find('.mark_name').html() + '<p style="color:green;">📖 ' + agrs + '</p>');
        }
        
        if (_type == 0) {
            var idx = optsText.findIndex(t => t == agrs);
            if (idx != -1) $($opts[idx]).parent().click();
        } else if (_type == 1) {
            var ansArr = splitAnswer(agrs);
            $opts.each((i, t) => {
                if (ansArr.includes(optsText[i])) {
                    setTimeout(() => $($opts[i]).parent().click(), 300);
                }
            });
        } else if (_type == 2) {
            fillBlankAnswer($(TiMuList[index]), agrs, false, null);
        } else if (_type == 3) {
            handlePCJudge($(TiMuList[index]), agrs);
        } else if (_type == 4) {
            fillShortAnswer($(TiMuList[index]), agrs, false, null);
        }
        
        log('✅ 作业第' + (index + 1) + '题自动答题成功', 'green');
        setTimeout(() => doHomeWork(index + 1, TiMuList), setting.time);
    }).catch((err) => {
        if (err === 'NO_API_KEY' || err === 'TOKEN_EXPIRED' || err === '凭证刷新失败') {
            log('❌ 未登录,停止答题。请先登录后再使用', 'red');
            isProcessing = false;
            return;
        }
        log('⚠️ 作业第' + (index + 1) + '题获取答案失败,跳过', 'orange');
        setTimeout(() => doHomeWork(index + 1, TiMuList), setting.time);
    });
}

// ========== 考试处理 ==========
function missonExam() {
    if (!checkApiKeyBeforeAction('考试自动答题')) return;
    
    var $_examtable = $('.mark_table').find('.whiteDiv');
    var _questionFull = tidyStr($_examtable.find('h3.mark_name').html().trim());
    var typeName = _questionFull.match(/[(](.*?),.*?分[)]|$/)[1];
    var typeMap = {
        '单选题': 0,
        '多选题': 1,
        '填空题': 2,
        '判断题': 3,
        '简答题': 4
    };
    var _qType = typeMap[typeName];
    var _question = tidyQuestion(_questionFull.replace(/[(].*?分[)]/, '').replace(/^\s*/, ''));
    var $_ansdom = $_examtable.find('#submitTest').find('.stem_answer');
    
    if (_qType === undefined) {
        var $opts = $_ansdom.find('.clearfix.answerBg .fl.answer_p');
        if ($opts.length) {
            _qType = $_ansdom.find('input[type="checkbox"]').length ? 1 : 0;
        } else if ($_ansdom.find('.Answer .divText .subEditor textarea').length) {
            _qType = 4;
        }
    }
    
    var checkAnswered = function() {
        if (_qType == 0 || _qType == 1) {
            var $opts = $_ansdom.find('.clearfix.answerBg .fl.answer_p');
            for (var i = 0; i < $opts.length; i++) {
                if ($($opts[i]).parent().find('span').attr('class') && $($opts[i]).parent().find('span').attr('class').indexOf('check_answer') != -1) {
                    return true;
                }
            }
        } else if (_qType == 2) {
            var $inputs = $_ansdom.find('.Answer .divText .subEditor textarea');
            if ($inputs.length && $inputs.val() && $inputs.val().trim() !== '') return true;
        } else if (_qType == 4) {
            var $ta = $_ansdom.find('.subEditor textarea');
            if ($ta.length && $ta.val() && $ta.val().trim() !== '') return true;
        }
        return false;
    };
    
    if (checkAnswered()) {
        log('📌 此题已作答,跳过', 'green');
        toNextExam();
        return;
    }
    
    var $opts = [];
    var optsText = [];
    var optionsData = [];
    var pureQuestion = _question;
    
    if (_qType == 0 || _qType == 1) {
        $opts = $_ansdom.find('.clearfix.answerBg .fl.answer_p');
        $opts.each(function() {
            optsText.push(tidyStr($(this).html()));
        });
        optionsData = optsText;
    } else if (_qType == 2) {
        var blankCount = getBlankInputCount($_ansdom);
        optionsData = [blankCount.toString()];
        log('📝 考试填空题传入空数量: ' + blankCount, 'blue');
    } else if (_qType == 4) {
        optionsData = ["1"];
        log('📝 考试简答题传入固定参数: ["1"]', 'blue');
    } else if (_qType == 3) {
        optionsData = ["对", "错"];
        log('📝 考试判断题传入固定选项: ["对","错"]', 'blue');
    }
    
    getAnswer(_qType, pureQuestion, optionsData).then((agrs) => {
        if (agrs == '暂无答案' || !agrs) {
            log('⚠️ 考试此题无法获取答案,跳过', 'orange');
            setTimeout(() => toNextExam(), setting.time);
            return;
        }
        
        if (setting.alterTitle) {
            $_examtable.find('h3.mark_name').html($_examtable.find('h3.mark_name').html() + '<span style="color:green;">📖 ' + agrs + '</span>');
        }
        
        if (_qType == 0) {
            var idx = optsText.findIndex(t => t == agrs);
            if (idx != -1) {
                var $target = $($opts[idx]).parent();
                if (setting.goodStudent) {
                    $target.find('span').css('font-weight', 'bold');
                    log('📝 好学生模式:答案已加粗,未自动选择', 'blue');
                } else {
                    $target.click();
                }
            }
        } else if (_qType == 1) {
            var ansArr = splitAnswer(agrs);
            $opts.each((i, t) => {
                if (ansArr.includes(optsText[i])) {
                    if (setting.goodStudent) {
                        $($opts[i]).parent().find('span').css('font-weight', 'bold');
                    } else {
                        setTimeout(() => $($opts[i]).parent().click(), 300);
                    }
                }
            });
        } else if (_qType == 2) {
            fillBlankAnswer($_ansdom, agrs, false, null);
        } else if (_qType == 3) {
            handleExamJudge($_ansdom, agrs, setting.goodStudent);
        } else if (_qType == 4) {
            fillShortAnswer($_ansdom, agrs, false, null);
        }
        
        log('✅ 考试自动答题成功', 'green');
        setTimeout(() => toNextExam(), setting.time);
    }).catch((err) => {
        if (err === 'NO_API_KEY' || err === 'TOKEN_EXPIRED' || err === '凭证刷新失败') {
            log('❌ 未登录,停止答题。请先登录后再使用', 'red');
            isProcessing = false;
            return;
        }
        log('⚠️ 考试此题获取答案失败,跳过', 'orange');
        setTimeout(() => toNextExam(), setting.time);
    });
}

function handleExamJudge($container, answer, goodStudent) {
    var trueList = ['正确', '是', '对', '√', 'T', 'true', 'ri', 'right'];
    var ansLower = (answer || '').trim().toLowerCase();
    var isTrue = trueList.some(function(k) { return ansLower === k.toLowerCase() || ansLower.indexOf(k.toLowerCase()) !== -1; });
    var $pdContainer = $container.find('.clearfix.answerBg .fl.answer_p').parent();

    if (isTrue) {
        if ($pdContainer.length > 0) {
            if (goodStudent) {
                $pdContainer.find('span').css('font-weight', 'bold');
                log('📝 好学生模式:答案已加粗,未自动选择', 'blue');
            } else {
                $($pdContainer[0]).click();
            }
            log('✅ 考试判断题选择: 正确 (接口返回: ' + answer + ')', 'green');
        }
    } else {
        if ($pdContainer.length > 1) {
            if (goodStudent) {
                $pdContainer.find('span').css('font-weight', 'bold');
                log('📝 好学生模式:答案已加粗,未自动选择', 'blue');
            } else {
                $($pdContainer[1]).click();
            }
            log('✅ 考试判断题选择: 错误 (接口返回: ' + answer + ')', 'green');
        } else if ($pdContainer.length === 1) {
            if (goodStudent) {
                $pdContainer.find('span').css('font-weight', 'bold');
            } else {
                $($pdContainer[0]).click();
            }
            log('✅ 考试判断题选择: 错误(唯一选项) (接口返回: ' + answer + ')', 'green');
        }
    }
}

function toNextExam() {
    if (setting.examTurn) {
        var $nextbtn = $('.mark_table .whiteDiv .nextDiv a.jb_btn');
        var delay = setting.examTurnTime ? 2000 + (Math.floor(Math.random() * 5 + 1) * 1000) : 2000;
        setTimeout(() => $nextbtn.click(), delay);
        log('⏭️ ' + (delay/1000) + '秒后自动跳转下一题', 'blue');
    }
}

// ========== 字体解密 ==========
function base64ToUint8Array(base64) {
    var data = window.atob(base64);
    var buf = new Uint8Array(data.length);
    for (var i = 0; i < data.length; i++) buf[i] = data.charCodeAt(i);
    return buf;
}

function decryptFont($context) {
    if (setting.decrypt !== 1) return;
    
    try {
        if (!TyprInstance || !TyprInstance.U || !TyprInstance.parse) {
            try {
                if (typeof unsafeWindow !== 'undefined' && unsafeWindow.Typr) {
                    TyprInstance = unsafeWindow.Typr;
                } else if (typeof window !== 'undefined' && window.Typr) {
                    TyprInstance = window.Typr;
                } else if (typeof Typr !== 'undefined') {
                    TyprInstance = Typr;
                } else if (typeof unsafeWindow !== 'undefined' && unsafeWindow.TyprMd5) {
                    TyprInstance = unsafeWindow.TyprMd5;
                } else if (typeof TyprMd5 !== 'undefined') {
                    TyprInstance = TyprMd5;
                }
            } catch(e) {}
            
            if (!TyprInstance || !TyprInstance.U) {
                log('⏳ 等待字体解密库加载...', 'blue');
                setTimeout(decryptFont, 1000);
                return;
            }
        }

        // 确定搜索上下文:默认为当前页面,也可以传入iframe的$contents
        var $searchCtx = $context || $(document);
        
        var $tip = $searchCtx.find('style:contains(font-cxsecret)');
        // 如果是当前页面调用且没有context,也尝试顶层选择器
        if (!$tip.length && !$context) {
            $tip = $('style:contains(font-cxsecret)');
        }
        if (!$tip.length) {
            return;
        }
        
        var styleText = $tip.text();
        var fontBase64 = null;
        
        var match1 = styleText.match(/base64,([\w\W]+?)'/);
        if (match1 && match1[1]) fontBase64 = match1[1];
        
        if (!fontBase64) {
            var match2 = styleText.match(/url\("data:application\/font-woff;charset=utf-8;base64,([\w\W]+?)"\)/);
            if (match2 && match2[1]) fontBase64 = match2[1];
        }
        
        if (!fontBase64) {
            var match3 = styleText.match(/url\('data:application\/font-woff;charset=utf-8;base64,([\w\W]+?)'\)/);
            if (match3 && match3[1]) fontBase64 = match3[1];
        }
        
        if (!fontBase64) return;
        
        var fontUint8Array = base64ToUint8Array(fontBase64);
        var parsedFont = TyprInstance.parse(fontUint8Array);
        if (!parsedFont || parsedFont.length === 0) return;
        
        var font = parsedFont[0];
        var tableJson = GM_getResourceText('Table');
        if (!tableJson) return;
        
        var table;
        try {
            table = JSON.parse(tableJson);
        } catch(e) {
            return;
        }
        
        var match = {};
        var matchCount = 0;
        
        for (var i = 19968; i < 40870; i++) {
            var glyph = TyprInstance.U.codeToGlyph(font, i);
            if (!glyph) continue;
            
            var path = TyprInstance.U.glyphToPath(font, glyph);
            if (!path) continue;
            
            var pathStr = JSON.stringify(path);
            var hash = md5(pathStr);
            if (!hash) continue;
            
            var hashKey = hash.slice(24);
            var realChar = table[hashKey];
            
            if (realChar !== undefined && realChar !== null && realChar !== 0) {
                match[i] = realChar;
                matchCount++;
            }
        }
        
        if (matchCount === 0) return;
        
        var $elements = $searchCtx.find('.font-cxsecret');
        // 如果没有context参数,也搜索顶层
        if (!$context) {
            $elements = $elements.add($('.font-cxsecret'));
        }
        if ($elements.length === 0) return;
        
        $elements.each(function() {
            var $el = $(this);
            var html = $el.html();
            if (!html) return;
            
            for (var code in match) {
                var encryptedChar = String.fromCharCode(parseInt(code));
                var realChar = String.fromCharCode(match[code]);
                var escapedChar = encryptedChar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                var regex = new RegExp(escapedChar, 'g');
                html = html.replace(regex, realChar);
            }
            
            if ($el.html() !== html) {
                $el.html(html);
                $el.removeClass('font-cxsecret');
            }
        });
        
        log('✅ 字体解密完成!已还原 ' + matchCount + ' 个字符' + ($context ? ' (iframe内)' : ''), 'green');
        
    } catch(e) {
        log('❌ 字体解密失败: ' + e.message, 'red');
    }
}

// ========== iframe内字体解密(供答题前调用)==========
function decryptFontInIframe($iframeContents) {
    if (setting.decrypt !== 1) return;
    if (!$iframeContents || !$iframeContents.length) return;
    
    try {
        var $secretElements = $iframeContents.find('.font-cxsecret');
        if ($secretElements.length === 0) return;
        
        log('🔤 检测到iframe内有 ' + $secretElements.length + ' 个加密字体元素,开始解密...', 'blue');
        decryptFont($iframeContents);
    } catch(e) {
        log('⚠️ iframe字体解密异常: ' + e.message, 'orange');
    }
}

// ========== 皮卡丘 ==========
function showPikaqiu() {
    if (!setting.maskImg) return;
    if (pikaqiuAdded) return;
    try {
        var targetDoc = top.document;
        if (targetDoc.querySelector('#pikaqiu-img')) return;
        var imgHtml = '<img id="pikaqiu-img" src="https://pkpkq.n1t.cn/pkq.gif" style="position:fixed;bottom:20px;left:20px;z-index:99999;width:100px;cursor:pointer;opacity:0.7;" title="学习通助手">';
        $(targetDoc.body).append(imgHtml);
        pikaqiuAdded = true;
        $('#pikaqiu-img', targetDoc).click(function() {
            var $box = $('#ne-21box', targetDoc);
            if ($box.length) {
                if ($box.css('display') === 'none') {
                    $box.css('display', 'block');
                    isBoxHidden = false;
                } else {
                    $box.css('display', 'none');
                    isBoxHidden = true;
                }
            }
        });
    } catch(e) {}
}

// ========== 认证面板渲染 ==========
function renderAuthPanel(targetDoc) {
    var $auth = $('#authContent', targetDoc);
    if (!$auth.length) return;
    if (isLoggedIn()) {
        var info = getUserInfo();
        var username = info ? info.username : '用户';
        var remaining = info ? (info.remainingCount || 0) : 0;
        var freeCount = info ? (info.freeCount || 0) : 0;
        $auth.html(
            '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">' +
                '<span style="font-weight:bold;">👤 ' + username + '</span>' +
            '</div>' +
            '<div id="quotaInfo" style="font-size:12px;margin-bottom:8px;color:#666;">📊 每日免费: ' + freeCount + ' 次 | 付费余额: ' + remaining + ' 次</div>' +
            '<div style="display:flex;gap:8px;flex-wrap:wrap;">' +
                '<button id="buyPackageBtn" style="padding:5px 10px;background:#FF9800;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;">购买套餐</button>' +
                '<button id="refreshInfoBtn" style="padding:5px 10px;background:#2196F3;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;">刷新</button>' +
                '<a href="https://so.ucuc.net" target="_blank" id="goWebsiteBtn" style="padding:5px 10px;background:#9C27B0;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;text-decoration:none;display:inline-block;">官网</a>' +
                '<button id="logoutBtn" style="padding:5px 10px;background:#f44336;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;">退出</button>' +
            '</div>' +
            '<div id="packageList" style="display:none;margin-top:10px;"></div>' +
            '<div id="authMsg" style="font-size:12px;margin-top:5px;color:#999;"></div>'
        );
    } else {
        var existingKey = getApiKey();
        $auth.html(
            '<div style="margin-bottom:8px;font-weight:bold;">🔑 登录 / 注册</div>' +
            '<div id="loginForm">' +
                '<input type="text" id="loginUsername" placeholder="用户名" style="width:100%;padding:6px;border:1px solid #ddd;border-radius:4px;margin-bottom:6px;box-sizing:border-box;">' +
                '<input type="password" id="loginPassword" placeholder="密码" style="width:100%;padding:6px;border:1px solid #ddd;border-radius:4px;margin-bottom:6px;box-sizing:border-box;">' +
                '<div style="display:flex;gap:8px;">' +
                    '<button id="loginBtn" style="flex:1;padding:6px;background:#4CAF50;color:#fff;border:none;border-radius:4px;cursor:pointer;">登录</button>' +
                    '<button id="showRegisterBtn" style="flex:1;padding:6px;background:#2196F3;color:#fff;border:none;border-radius:4px;cursor:pointer;">注册</button>' +
                '</div>' +
            '</div>' +
            '<div id="registerForm" style="display:none;">' +
                '<input type="email" id="regEmail" placeholder="请输入qq邮箱" style="width:100%;padding:6px;border:1px solid #ddd;border-radius:4px;margin-bottom:6px;box-sizing:border-box;">' +
                '<div style="display:flex;gap:8px;margin-bottom:6px;">' +
                    '<input type="text" id="regCode" placeholder="验证码" style="flex:1;padding:6px;border:1px solid #ddd;border-radius:4px;">' +
                    '<button id="getCodeBtn" style="padding:6px 10px;background:#FF9800;color:#fff;border:none;border-radius:4px;cursor:pointer;white-space:nowrap;font-size:12px;">获取验证码</button>' +
                '</div>' +
                '<input type="text" id="regUsername" placeholder="用户名" style="width:100%;padding:6px;border:1px solid #ddd;border-radius:4px;margin-bottom:6px;box-sizing:border-box;">' +
                '<input type="password" id="regPassword" placeholder="密码" style="width:100%;padding:6px;border:1px solid #ddd;border-radius:4px;margin-bottom:6px;box-sizing:border-box;">' +
                '<div style="display:flex;gap:8px;">' +
                    '<button id="registerBtn" style="flex:1;padding:6px;background:#4CAF50;color:#fff;border:none;border-radius:4px;cursor:pointer;">注册</button>' +
                    '<button id="showLoginBtn" style="flex:1;padding:6px;background:#999;color:#fff;border:none;border-radius:4px;cursor:pointer;">返回登录</button>' +
                '</div>' +
            '</div>' +
            '<div id="authMsg" style="font-size:12px;margin-top:5px;color:#999;">' + (existingKey ? '✅ 已配置密钥,可直接使用' : '') + '</div>' +
            '<div style="margin-top:8px;border-top:1px solid #eee;padding-top:8px;">' +
                '<div style="display:flex;gap:6px;align-items:center;">' +
                    '<input type="text" id="manualApiKey" placeholder="或直接粘贴apikey" value="' + (existingKey || '') + '" style="flex:1;padding:5px;border:1px solid #ddd;border-radius:4px;font-size:12px;">' +
                    '<button id="saveManualKeyBtn" style="padding:5px 8px;background:#666;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px;white-space:nowrap;">保存</button>' +
                    '<a href="https://so.ucuc.net/userInfo/" target="_blank" id="goWebsiteBtnLogin" style="padding:5px 8px;background:#9C27B0;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px;white-space:nowrap;text-decoration:none;display:inline-block;">官网</a>' +
                '</div>' +
            '</div>'
        );
    }
    bindAuthEvents(targetDoc);
}

function bindAuthEvents(targetDoc) {
    $('#loginBtn', targetDoc).off('click').on('click', function() {
        var username = $('#loginUsername', targetDoc).val();
        var password = $('#loginPassword', targetDoc).val();
        if (!username || !password) { $('#authMsg', targetDoc).html('❌ 请输入用户名和密码').css('color', 'red'); return; }
        $('#authMsg', targetDoc).html('⏳ 登录中...').css('color', '#666');
        apiLogin(username, password).then(function(token) {
            setAuthToken(token);
            return apiFetchUserInfo();
        }).then(function(info) {
            log('✅ 登录成功,欢迎 ' + info.username, 'green');
            renderAuthPanel(targetDoc);
        }).catch(function(err) {
            $('#authMsg', targetDoc).html('❌ ' + err).css('color', 'red');
        });
    });

    $('#showRegisterBtn', targetDoc).off('click').on('click', function() {
        $('#loginForm', targetDoc).hide();
        $('#registerForm', targetDoc).show();
    });
    $('#showLoginBtn', targetDoc).off('click').on('click', function() {
        $('#registerForm', targetDoc).hide();
        $('#loginForm', targetDoc).show();
    });

    $('#getCodeBtn', targetDoc).off('click').on('click', function() {
        var email = $('#regEmail', targetDoc).val();
        if (!email) { $('#authMsg', targetDoc).html('❌ 请输入邮箱').css('color', 'red'); return; }
        var $btn = $(this);
        $btn.prop('disabled', true).text('发送中...');
        apiGetEmailCode(email).then(function() {
            $('#authMsg', targetDoc).html('✅ 验证码已发送到邮箱').css('color', 'green');
            var countdown = 60;
            var timer = setInterval(function() {
                countdown--;
                if (countdown <= 0) { clearInterval(timer); $btn.prop('disabled', false).text('获取验证码'); }
                else { $btn.text(countdown + 's'); }
            }, 1000);
        }).catch(function(err) {
            $btn.prop('disabled', false).text('获取验证码');
            $('#authMsg', targetDoc).html('❌ ' + err).css('color', 'red');
        });
    });

    $('#registerBtn', targetDoc).off('click').on('click', function() {
        var email = $('#regEmail', targetDoc).val();
        var code = $('#regCode', targetDoc).val();
        var username = $('#regUsername', targetDoc).val();
        var password = $('#regPassword', targetDoc).val();
        if (!email || !code || !username || !password) { $('#authMsg', targetDoc).html('❌ 请填写所有字段').css('color', 'red'); return; }
        $('#authMsg', targetDoc).html('⏳ 注册中...').css('color', '#666');
        apiRegister(username, password, email, code).then(function() {
            $('#authMsg', targetDoc).html('✅ 注册成功,自动登录中...').css('color', 'green');
            return apiLogin(username, password);
        }).then(function(token) {
            setAuthToken(token);
            return apiFetchUserInfo();
        }).then(function(info) {
            log('✅ 注册并登录成功', 'green');
            renderAuthPanel(targetDoc);
        }).catch(function(err) {
            $('#authMsg', targetDoc).html('❌ ' + err).css('color', 'red');
        });
    });

    $('#logoutBtn', targetDoc).off('click').on('click', function() {
        clearAuth();
        quotaInfo = {remaining: -1};
        saveQuotaInfo();
        log('👋 已退出登录', 'blue');
        renderAuthPanel(targetDoc);
    });

    $('#refreshInfoBtn', targetDoc).off('click').on('click', function() {
        $('#authMsg', targetDoc).html('⏳ 刷新中...').css('color', '#666');
        apiFetchUserInfo().then(function() {
            renderAuthPanel(targetDoc);
            log('✅ 信息已刷新', 'green');
        }).catch(function(err) {
            if (err === 'TOKEN_EXPIRED') { clearAuth(); renderAuthPanel(targetDoc); }
            $('#authMsg', targetDoc).html('❌ ' + err).css('color', 'red');
        });
    });

    $('#buyPackageBtn', targetDoc).off('click').on('click', function() {
        var $list = $('#packageList', targetDoc);
        if ($list.is(':visible')) { $list.hide(); return; }
        $list.html('⏳ 加载套餐...').show();
        apiFetchProducts().then(function(products) {
            if (!products.length) { $list.html('暂无可用套餐'); return; }
            var html = '';
            products.forEach(function(p) {
                if (p.status === '0') {
                    html += '<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid #eee;">' +
                        '<span style="font-size:12px;">' + p.title + ' (' + p.searchCount + '次) - ¥' + p.price + '</span>' +
                        '<button class="buyProductBtn" data-id="' + p.id + '" style="padding:3px 8px;background:#4CAF50;color:#fff;border:none;border-radius:3px;cursor:pointer;font-size:11px;">购买</button>' +
                    '</div>';
                }
            });
            $list.html(html || '暂无上架套餐');
            $('.buyProductBtn', targetDoc).off('click').on('click', function() {
                var productId = $(this).data('id');
                showPayMethodChoice(productId, targetDoc);
            });
        }).catch(function(err) { $list.html('❌ ' + err); });
    });

    $('#saveManualKeyBtn', targetDoc).off('click').on('click', function() {
        var key = $('#manualApiKey', targetDoc).val();
        if (!key || !key.trim()) { $('#authMsg', targetDoc).html('❌ 请输入apikey').css('color', 'red'); return; }
        GM_setValue('api_key', key.trim());
        quotaInfo = {remaining: -1};
        saveQuotaInfo();
        log('✅ API密钥已保存', 'green');
        $('#authMsg', targetDoc).html('✅ 已保存').css('color', 'green');
        $('#manualApiKey', targetDoc).val('');
    });

    // 官网按钮带token跳转
    $('#goWebsiteBtn, #goWebsiteBtnLogin', targetDoc).off('click').on('click', function(e) {
        e.preventDefault();
        var token = getAuthToken();
        var url = 'https://so.ucuc.net';
        if (token) {
            url += '?token=' + encodeURIComponent(token);
        }
        GM_openInTab(url, { active: true });
    });
}

function showPayMethodChoice(productId, targetDoc) {
    var $list = $('#packageList', targetDoc);
    $list.html(
        '<div style="text-align:center;padding:8px;">' +
            '<div style="margin-bottom:8px;font-size:13px;">选择支付方式</div>' +
            '<div style="display:flex;gap:10px;justify-content:center;">' +
                '<button class="payMethodBtn" data-method="alipay" data-pid="' + productId + '" style="padding:8px 16px;background:#1677FF;color:#fff;border:none;border-radius:4px;cursor:pointer;">支付宝</button>' +
                '<button class="payMethodBtn" data-method="wechat" data-pid="' + productId + '" style="padding:8px 16px;background:#07C160;color:#fff;border:none;border-radius:4px;cursor:pointer;">微信支付</button>' +
            '</div>' +
        '</div>'
    );
    $('.payMethodBtn', targetDoc).off('click').on('click', function() {
        var method = $(this).data('method');
        var pid = $(this).data('pid');
        purchaseProduct(pid, method, targetDoc);
    });
}

function purchaseProduct(productId, payMethod, targetDoc) {
    var $msg = $('#authMsg', targetDoc);
    $msg.html('⏳ 创建订单中...').css('color', '#666');
    apiCreateOrder(productId, 1, payMethod).then(function(data) {
        var order = data.order;
        var qrParams = data.qrCodeParams;
        if (!qrParams || !qrParams.success) {
            $msg.html('❌ ' + (qrParams && qrParams.errorMsg || '创建支付失败')).css('color', 'red');
            return;
        }
        var payLabel = payMethod === 'wechat' ? '微信' : '支付宝';
        var qrImgSrc;
        if (payMethod === 'wechat' && qrParams.qrCodeImage) {
            qrImgSrc = qrParams.qrCodeImage.startsWith('data:') ? qrParams.qrCodeImage : ('data:image/png;base64,' + qrParams.qrCodeImage);
        } else {
            var qrContent = qrParams.qrCode || qrParams.codeUrl || '';
            qrImgSrc = 'https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=' + encodeURIComponent(qrContent);
        }
        var $list = $('#packageList', targetDoc);
        $list.html(
            '<div style="text-align:center;padding:10px;">' +
                '<div style="margin-bottom:8px;font-size:13px;color:#333;">请用' + payLabel + '扫码支付</div>' +
                '<img src="' + qrImgSrc + '" style="width:180px;height:180px;border:1px solid #eee;border-radius:4px;">' +
                '<div style="margin-top:8px;font-size:12px;color:#999;">订单号: ' + order.orderId + '</div>' +
                '<button id="cancelPayBtn" style="margin-top:8px;padding:4px 12px;background:#999;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;">取消</button>' +
            '</div>'
        ).show();
        $msg.html('⏳ 等待扫码支付...').css('color', '#FF9800');
        $('#cancelPayBtn', targetDoc).off('click').on('click', function() {
            clearInterval(pollTimer);
            $list.hide();
            $msg.html('已取消').css('color', '#999');
        });
        var pollCount = 0;
        var pollTimer = setInterval(function() {
            pollCount++;
            if (pollCount > 100) {
                clearInterval(pollTimer);
                $msg.html('⏰ 支付超时,如已支付请点刷新').css('color', 'orange');
                $list.hide();
                return;
            }
            apiCheckOrderStatus(order.id).then(function(status) {
                if (status !== '待支付') {
                    clearInterval(pollTimer);
                    $list.hide();
                    $msg.html('✅ 支付成功!').css('color', 'green');
                    log('✅ 购买成功,刷新余额...', 'green');
                    apiFetchUserInfo().then(function() { renderAuthPanel(targetDoc); });
                }
            }).catch(function() {});
        }, 3000);
    }).catch(function(err) {
        $msg.html('❌ ' + err).css('color', 'red');
    });
}

function showBox() {
    if (!setting.showBox) return;
    try {
        var targetDoc = top.document;
        if (targetDoc.querySelector('#ne-21box')) {
            flushLogs();
            quotaInfo = getQuotaInfo();
            updateQuotaDisplay();
            return;
        }
    } catch(e) {}
    
    var boxHtml = '<div id="ne-21box" style="position:fixed;top:5%;right:20%;z-index:99999;width:480px;background:#fff;border-radius:8px;box-shadow:0 2px 12px rgba(0,0,0,0.15);font-size:14px;max-height:90vh;overflow-y:auto;">' +
        '<div id="ne-21header" style="display:flex;justify-content:space-between;padding:12px 15px;border-bottom:1px solid #eee;cursor:move;user-select:none;">' +
            '<h3 style="margin:0;font-size:16px;">✅学习通小助手</h3>' +
            '<span id="ne-21close" style="cursor:pointer;color:#999;">[F9隐藏]</span>' +
        '</div>' +
        '<div id="ne-21notice" style="padding:12px 15px;border-bottom:1px solid #eee;background:#f9f9f9;"></div>' +
        '<div style="padding:15px;">' +
            '<div id="authSection" style="margin-bottom:15px;padding:10px;background:#f5f5f5;border-radius:4px;">' +
                '<div id="authContent">加载中...</div>' +
            '</div>' +
            '<div style="margin-bottom:15px;padding:10px;background:#e8f5e9;border-radius:4px;">' +
                '<div style="margin-bottom:10px;font-weight:bold;">🎬 视频处理模式</div>' +
                '<div style="display:flex;gap:20px;align-items:center;">' +
                    '<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">' +
                        '<input type="radio" name="videoMode" value="simulate" style="appearance:auto;-webkit-appearance:radio;width:16px;height:16px;margin:0;display:inline-block;position:static;opacity:1;" ' + (setting.videoMode === 'simulate' ? 'checked' : '') + '>' +
                        '<span>📡 模拟上报(可后台)</span>' +
                    '</label>' +
                    '<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">' +
                        '<input type="radio" name="videoMode" value="normal" style="appearance:auto;-webkit-appearance:radio;width:16px;height:16px;margin:0;display:inline-block;position:static;opacity:1;" ' + (setting.videoMode === 'normal' ? 'checked' : '') + '>' +
                        '<span>▶️ 正常播放</span>' +
                    '</label>' +
                '</div>' +
                '<div style="font-size:12px;color:#666;margin-top:8px;">模拟上报:使用Web Worker后台计时,最小化/切换标签页不影响上报</div>' +
            '</div>' +
            '<div style="margin-bottom:15px;padding:10px;background:#fff3e0;border-radius:4px;">' +
                '<div style="margin-bottom:12px;font-weight:bold;">⚙️ 视频参数</div>' +
                '<div>' +
                    '<span>📹 视频倍速: <span id="rateValue">' + setting.rate.toFixed(2) + '</span>x</span>' +
                    '<input type="range" id="videoRateSlider" min="1" max="16" step="0.25" value="' + setting.rate + '" style="width:100%;margin-top:8px;">' +
                    '<div style="font-size:11px;color:#e65100;margin-top:6px;">⚠️ 若看完任务点仍未完成,可能是学校不允许倍速,建议用1.5x或2x自行测试</div>' +
                '</div>' +
                '<div style="margin-top:10px;">' +
                    '<label><input type="checkbox" id="showProgressBarCheck" style="appearance:auto;-webkit-appearance:checkbox;width:16px;height:16px;margin:0 4px 0 0;display:inline-block;position:static;opacity:1;vertical-align:middle;" ' + (setting.showProgressBar ? 'checked' : '') + '> 📊 显示进度条</label>' +
                '</div>' +
            '</div>' +
            '<div id="moreSettings" style="margin-top:12px;">' +
                '<div style="margin:8px 0;">' +
                    '<label style="display:inline-block;width:120px;">⏱️ 答题间隔(ms):</label>' +
                    '<input type="number" id="timeInterval" value="' + setting.time + '" style="width:80px;padding:4px;border:1px solid #ddd;border-radius:4px;" min="1000" max="10000" step="500">' +
                '</div>' +
                '<div style="margin:8px 0;"><input type="checkbox" id="GPTJsSetting.sub" style="appearance:auto;-webkit-appearance:checkbox;width:16px;height:16px;margin:0 4px 0 0;display:inline-block;position:static;opacity:1;vertical-align:middle;"> <label>测验自动提交</label></div>' +
                '<div style="margin:8px 0;"><input type="checkbox" id="GPTJsSetting.force" style="appearance:auto;-webkit-appearance:checkbox;width:16px;height:16px;margin:0 4px 0 0;display:inline-block;position:static;opacity:1;vertical-align:middle;"> <label>测验强制提交</label></div>' +
                '<div style="margin:8px 0;"><input type="checkbox" id="GPTJsSetting.examTurn" style="appearance:auto;-webkit-appearance:checkbox;width:16px;height:16px;margin:0 4px 0 0;display:inline-block;position:static;opacity:1;vertical-align:middle;"> <label>考试自动跳转</label></div>' +
                '<div style="margin:8px 0;"><input type="checkbox" id="GPTJsSetting.goodStudent" style="appearance:auto;-webkit-appearance:checkbox;width:16px;height:16px;margin:0 4px 0 0;display:inline-block;position:static;opacity:1;vertical-align:middle;"> <label>好学生模式(答案加粗不自动选择)</label></div>' +
                '<div style="margin:8px 0;"><input type="checkbox" id="GPTJsSetting.alterTitle" style="appearance:auto;-webkit-appearance:checkbox;width:16px;height:16px;margin:0 4px 0 0;display:inline-block;position:static;opacity:1;vertical-align:middle;"> <label>答案插入题目</label></div>' +
                '<div style="margin:8px 0;"><input type="checkbox" id="GPTJsSetting.review" style="appearance:auto;-webkit-appearance:checkbox;width:16px;height:16px;margin:0 4px 0 0;display:inline-block;position:static;opacity:1;vertical-align:middle;"> <label>复习模式(处理已完成任务)</label></div>' +
            '</div>' +
            '<div id="ne-21log" style="margin-top:12px;max-height:250px;overflow-y:auto;font-size:12px;background:#fafafa;border-radius:4px;padding:8px;"></div>' +
        '</div>' +
    '</div>';
    
    try {
        $(top.document.body).append(boxHtml);
    } catch(e) {
        $(document.body).append(boxHtml);
    }
    
    setTimeout(function() {
        try {
            var targetDoc = top.document;
            if (!targetDoc.querySelector('#ne-21close')) {
                targetDoc = document;
            }
            
            $('#ne-21close', targetDoc).click(function() {
                $('#ne-21box', targetDoc).hide();
                isBoxHidden = true;
            });
            
            // 拖动功能
            (function() {
                var box = targetDoc.getElementById('ne-21box');
                var header = targetDoc.getElementById('ne-21header');
                if (!box || !header) return;
                var isDragging = false, startX, startY, origLeft, origTop;
                header.addEventListener('mousedown', function(e) {
                    if (e.target.id === 'ne-21close') return;
                    isDragging = true;
                    var rect = box.getBoundingClientRect();
                    startX = e.clientX;
                    startY = e.clientY;
                    origLeft = rect.left;
                    origTop = rect.top;
                    box.style.right = 'auto';
                    box.style.left = origLeft + 'px';
                    box.style.top = origTop + 'px';
                    e.preventDefault();
                });
                targetDoc.addEventListener('mousemove', function(e) {
                    if (!isDragging) return;
                    var dx = e.clientX - startX;
                    var dy = e.clientY - startY;
                    box.style.left = (origLeft + dx) + 'px';
                    box.style.top = (origTop + dy) + 'px';
                });
                targetDoc.addEventListener('mouseup', function() {
                    isDragging = false;
                });
            })();
            
            $('input[name="videoMode"]', targetDoc).change(function() {
                setting.videoMode = this.value;
                GM_setValue('videoMode', setting.videoMode);
                log('🎬 视频模式切换为: ' + (setting.videoMode === 'simulate' ? '模拟上报' : '正常播放'), 'green');
            });
            
            $('#videoRateSlider', targetDoc).on('input', function() {
                $('#rateValue', targetDoc).text(parseFloat(this.value).toFixed(2));
            }).on('change', function() {
                var newRate = parseFloat(this.value);
                setting.rate = newRate;
                GM_setValue('videoRate', newRate);
                if (currentVideoWorker && isVideoTaskActive) {
                    currentVideoWorker.postMessage({ type: 'updateRate', rate: newRate });
                }
                log('📹 视频倍速设置为: ' + newRate.toFixed(2) + 'x', 'green');
            });
            
            $('#showProgressBarCheck', targetDoc).change(function() {
                setting.showProgressBar = this.checked ? 1 : 0;
                GM_setValue('showProgressBar', setting.showProgressBar);
            });
            
            // 渲染认证面板
            renderAuthPanel(targetDoc);
            if (isLoggedIn()) {
                apiFetchUserInfo().then(function(info) {
                    log('✅ 自动登录成功', 'green');
                    renderAuthPanel(targetDoc);
                }).catch(function(err) {
                    if (err === 'TOKEN_EXPIRED') {
                        clearAuth();
                        renderAuthPanel(targetDoc);
                        log('⚠️ 登录已过期,请重新登录', 'orange');
                    }
                });
            }
            
            var timeIntervalInput = targetDoc.getElementById('timeInterval');
            if (timeIntervalInput) {
                timeIntervalInput.addEventListener('change', function(e) {
                    var newTime = parseInt(e.target.value);
                    if (newTime >= 1000 && newTime <= 10000) {
                        setting.time = newTime;
                        localStorage.setItem('GPTJsSetting.time', newTime);
                        log('⚙️ 答题间隔: ' + newTime + 'ms', 'blue');
                    } else {
                        e.target.value = setting.time;
                    }
                });
            }
            
            var settingsList = ['sub', 'force', 'examTurn', 'goodStudent', 'alterTitle', 'review'];
            settingsList.forEach(function(id) {
                var cb = targetDoc.getElementById('GPTJsSetting.' + id);
                if (cb) {
                    var newCb = cb.cloneNode(true);
                    cb.parentNode.replaceChild(newCb, cb);
                    var savedValue = localStorage.getItem('GPTJsSetting.' + id);
                    newCb.checked = savedValue !== null ? savedValue === 'true' : setting[id];
                    newCb.addEventListener('change', function(e) {
                        var checked = e.target.checked;
                        var settingKey = e.target.id.replace('GPTJsSetting.', '');
                        setting[settingKey] = checked;
                        localStorage.setItem(e.target.id, checked);
                        log('⚙️ ' + settingKey + ' = ' + checked, 'blue');
                    });
                }
            });
            
            var savedTime = localStorage.getItem('GPTJsSetting.time');
            if (savedTime) {
                setting.time = parseInt(savedTime);
                if (timeIntervalInput) timeIntervalInput.value = setting.time;
            }
            
            $('#ne-21notice', targetDoc).html('<div style="font-size:12px;">💡 脚本已加载 v4.3| F9隐藏/显示 | 视频模式: ' + (setting.videoMode === 'simulate' ? '模拟上报' : '正常播放') + ' | 倍速: ' + setting.rate.toFixed(2) + 'x | 答题间隔: ' + (setting.time/1000) + '秒 | 每次新任务自动清理残留 | 填空题空数检测已修复</div>');
            
            flushLogs();
            showPikaqiu();
        } catch(e) {
            console.error('绑定事件失败:', e);
        }
    }, 100);
}

// ========== 键盘快捷键 ==========
$(document).keydown(function(e) {
    if (e.keyCode == 120) {
        var $box = $('#ne-21box');
        if ($box.length === 0) return;
        if (isBoxHidden) {
            $box.show();
            isBoxHidden = false;
            log('📌 面板已显示', 'green');
        } else {
            $box.hide();
            isBoxHidden = true;
            log('📌 面板已隐藏,按F9恢复', 'blue');
        }
    }
});

// ========== 获取任务参数 ==========
function getTaskParams() {
    try {
        var scripts = _d.scripts;
        for (let i = 0; i < scripts.length; i++) {
            if (scripts[i].innerHTML.indexOf('mArg = "";') != -1 && scripts[i].innerHTML.indexOf('==UserScript==') == -1) {
                return getStr(scripts[i].innerHTML.replace(/\s/g, ""), 'try{mArg=', ';}catch');
            }
        }
        return null;
    } catch(e) {
        return null;
    }
}

// ========== 重新初始化任务列表 ==========
function reinitTaskList() {
    if (isProcessing) {
        log('⚠️ 正在处理任务,稍后重新初始化', 'orange');
        setTimeout(reinitTaskList, 3000);
        return;
    }
    
    forceCleanupAll();
    
    var params = getTaskParams();
    if (!params || params == '$mArg') {
        log('⚠️ 无法获取任务参数', 'red');
        return;
    }
    
    try {
        var allTasks = $.parseJSON(params).attachments;
        if (!allTasks || allTasks.length <= 0) {
            log('⚠️ 无任务点', 'red');
            return;
        }
        
        _defaults = $.parseJSON(params).defaults;
        
        var pendingTasks = [];
        for (var i = 0; i < allTasks.length; i++) {
            var task = allTasks[i];
            if (!isTaskCompleted(task)) {
                pendingTasks.push(task);
            }
        }
        
        pendingMissionCount = pendingTasks.length;
        
        if (pendingMissionCount === 0) {
            log('✅ 所有任务点已完成', 'green');
            return;
        }
        
        log('📋 重新加载任务列表,发现 ' + pendingMissionCount + ' 个待处理任务', 'green');
        
        _mlist = [];
        _domList = [];
        
        $('.wrap .ans-cc .ans-attach-ct').each((i, t) => {
            if (i < allTasks.length && !isTaskCompleted(allTasks[i])) {
                _mlist.push(allTasks[i]);
                _domList.push($(t).find('iframe'));
            }
        });
        
        if (_mlist.length > 0 && !isProcessing) {
            log('🚀 继续处理剩余任务', 'green');
            missionStart();
        }
    } catch(e) {
        log('❌ 重新初始化任务列表失败: ' + e.message, 'red');
    }
}

// ========== 路由处理函数 ==========

function handleLogin() {
    if (!setting.autoLogin) return;
    waitForElement('#phone').then(() => {
        $('#phone').val(setting.phone);
        $('#pwd').val(setting.password);
        $('#loginBtn').click();
    });
}

function handleStudentStudy() {
    log('✅ 初始化完毕!视频模式: ' + (setting.videoMode === 'simulate' ? '模拟上报' : '正常播放') + ' | 倍速: ' + setting.rate.toFixed(2) + 'x', 'green');
}

function handleKnowledgeCards() {
    var params = getTaskParams();

    if (!params || params == '$mArg') {
        log('⚠️ 无任务点可处理', 'red');
        if (!hasTriggeredNoTaskJump) {
            hasTriggeredNoTaskJump = true;
            setTimeout(() => { toNext(); }, 2000);
        }
        return;
    }

    try {
        var allTasks = $.parseJSON(params).attachments;
        if (!allTasks || allTasks.length <= 0) {
            log('⚠️ 无任务点可处理', 'red');
            if (!hasTriggeredNoTaskJump) {
                hasTriggeredNoTaskJump = true;
                setTimeout(() => { toNext(); }, 2000);
            }
            return;
        }
    } catch(e) {
        log('⚠️ 解析任务参数失败', 'red');
        return;
    }

    waitForElement('.wrap .ans-cc .ans-attach-ct').then(() => {
        if (top.checkJob) top.checkJob = function() { return false; };

        var allTasks = $.parseJSON(params).attachments;
        _defaults = $.parseJSON(params).defaults;

        var pendingTasks = [];
        for (var i = 0; i < allTasks.length; i++) {
            var task = allTasks[i];
            if (!isTaskCompleted(task)) {
                pendingTasks.push(task);
            } else {
                var taskName = task.property?.name || task.property?.title || '任务点' + (i+1);
                log('✅ 任务点 ' + taskName + ' 已完成,跳过', 'green');
            }
        }

        pendingMissionCount = pendingTasks.length;
        completedMissionCount = 0;

        if (pendingMissionCount === 0) {
            log('✅ 所有任务点已完成', 'green');
            setTimeout(() => { toNext(); }, 2000);
            return;
        }

        log('📋 发现 ' + allTasks.length + ' 个任务点,其中 ' + pendingMissionCount + ' 个待处理', 'green');

        _mlist = [];
        _domList = [];

        $('.wrap .ans-cc .ans-attach-ct').each((i, t) => {
            if (i < allTasks.length && !isTaskCompleted(allTasks[i])) {
                _mlist.push(allTasks[i]);
                _domList.push($(t).find('iframe'));
            }
        });

        if (_mlist.length > 0) {
            log('🚀 开始处理 ' + _mlist.length + ' 个待完成任务', 'green');
            missionStart();
        } else {
            log('✅ 所有任务点已完成', 'green');
            setTimeout(() => { toNext(); }, 2000);
        }
    }).catch(() => {
        log('❌ 等待元素超时', 'red');
        if (!hasTriggeredNoTaskJump) {
            hasTriggeredNoTaskJump = true;
            setTimeout(() => { toNext(); }, 2000);
        }
    });
}

function handleExam() {
    waitForElement('.mark_table .whiteDiv').then(() => missonExam());
}

function handleHomework() {
    waitForElement('.mark_table form').then(() => missonHomeWork());
}

function handlePhoneHomework() {
    var _oldal = _w.alert;
    _w.alert = function(msg) {
        if (msg == '保存成功') return;
        return _oldal(msg);
    };
    var _oldcf = _w.confirm;
    _w.confirm = function(msg) {
        if (msg.includes('确认提交') || msg.includes('未做完')) return true;
        return _oldcf(msg);
    };
}

// ========== 路由表 ==========
var routes = [
    {
        pattern: /\/login$/,
        handler: handleLogin,
        needBox: true,
        desc: '自动登录'
    },
    {
        pattern: /mooc1\.chaoxing\.com\/mycourse\/studentcourse/,
        handler: handleExperienceNew,
        needBox: false,
        desc: '旧版课程页→跳转新版'
    },
    {
        pattern: /i\.chaoxing\.com\/base/,
        handler: handleExperienceNew,
        needBox: false,
        desc: '旧版个人页→跳转新版'
    },
    {
        pattern: /\/mycourse\/studentstudy/,
        handler: handleStudentStudy,
        needBox: true,
        desc: '学习页面'
    },
    {
        pattern: /\/knowledge\/cards/,
        handler: handleKnowledgeCards,
        needBox: true,
        desc: '知识点/任务页面'
    },
    {
        pattern: /\/exam\/test\/reVersionTestStartNew/,
        handler: handleExam,
        needBox: true,
        desc: '考试页面'
    },
    {
        pattern: /\/mooc2\/work\/dowork/,
        handler: handleHomework,
        needBox: true,
        desc: '作业页面'
    },
    {
        pattern: /\/work\/phone\/doHomeWork/,
        handler: handlePhoneHomework,
        needBox: false,
        desc: '手机端作业'
    }
];

// ========== 路由匹配引擎 ==========
function matchRoute() {
    var url = _l.href;
    for (var i = 0; i < routes.length; i++) {
        var route = routes[i];
        if (route.pattern.test(url)) {
            log('📍 匹配路由: ' + route.desc, 'blue');
            if (route.needBox) showBox();
            route.handler();
            return true;
        }
    }
    return false;
}

// ========== 页面初始化 ==========
$(function() {
    setting.videoMode = GM_getValue('videoMode', 'simulate');
    setting.rate = GM_getValue('videoRate', 1);
    setting.showProgressBar = GM_getValue('showProgressBar', 1);

    var settingsMap = {
        'sub': 'sub',
        'force': 'force',
        'examTurn': 'examTurn',
        'goodStudent': 'goodStudent',
        'alterTitle': 'alterTitle',
        'review': 'review',
        'time': 'time'
    };

    for (var key in settingsMap) {
        var val = localStorage.getItem('GPTJsSetting.' + key);
        if (val !== null) {
            if (key === 'time') {
                setting[settingsMap[key]] = parseInt(val);
            } else {
                setting[settingsMap[key]] = val === 'true';
            }
        }
    }

    _w.confirm = function() { return true; };

    if (setting.decrypt) {
        setTimeout(decryptFont, 2000);
        setTimeout(function() {
            if ($('.font-cxsecret').length > 0) decryptFont();
        }, 5000);
        setTimeout(function() {
            if ($('.font-cxsecret').length > 0) decryptFont();
        }, 10000);
    }

    var _expLink = document.querySelector('li>a.experience:not([onclick])');
    if (_expLink) {
        _expLink.click();
        log('🔄 已自动点击"体验新版",跳转新版页面', 'green');
        return;
    }

    matchRoute();
    checkUpdate();
});

// ========== 监听URL变化 ==========
var lastChapterUrl = _l.href;
// 判断当前页面是否是任务处理页面(知识点页面)
var isTaskPage = /\/knowledge\/cards/.test(_l.href);
setInterval(function() {
    if (_l.href !== lastChapterUrl) {
        lastChapterUrl = _l.href;
        // 只有任务处理页面才需要在URL变化时清理资源
        // 父页面(学习页面)不应该清理子iframe创建的进度条
        if (isTaskPage) {
            // 修复:如果有活跃的视频任务正在运行,保留Worker但清理进度条
            if (isVideoTaskActive && currentVideoWorker) {
                log('📍 URL变化但视频任务进行中,清理进度条但保留Worker', 'blue');
                removeProgressBar();
            } else {
                forceCleanupAll();
            }
        }
        setTimeout(function() {
            matchRoute();
        }, 3000);
    }
}, 2000);

window.addEventListener('beforeunload', function() {
    // 只有任务处理页面卸载时才清理
    if (isTaskPage) {
        // 修复:如果有活跃的视频 Worker 任务,不清理进度条和 Worker
        // Worker 作为独立线程会继续运行,只释放本 frame 的引用
        if (isVideoTaskActive && currentVideoWorker) {
            log('📍 页面卸载但视频任务进行中,保留 Worker 继续运行', 'blue');
            // 清理进度条DOM(防止残留),但不 terminate Worker
            removeProgressBar();
            isVideoTaskActive = false;
            currentVideoName = null;
        } else {
            forceCleanupAll();
        }
    }
});