小雅做做做

🚀 一键管理小雅平台任务,智能跟踪作业进度!✨ 支持自主学习任务的便捷提交,直观展示任务状态,帮你更高效完成学习目标! 📈

// ==UserScript==
// @name         小雅做做做
// @version      1.2
// @description  🚀 一键管理小雅平台任务,智能跟踪作业进度!✨ 支持自主学习任务的便捷提交,直观展示任务状态,帮你更高效完成学习目标! 📈
// @author       Yi
// @license       MIT
// @match        https://*.ai-augmented.com/*
// @icon           https://www.ai-augmented.com/static/logo3.1dbbea8f.png
// @run-at       document-end
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      *.ai-augmented.com
// @homepageURL https://zygame1314.site
// @namespace https://greasyfork.org/users/1268039
// ==/UserScript==

(function () {
    'use strict';
    const domain = window.location.hostname;
    let xiaoYaRecorder = null;

    function getGroupIdFromUrl() {
        const url = window.location.href;
        const match = url.match(/mycourse\/(\d+)/);
        return match ? match[1] : null;
    }

    function getResourceIdFromUrl() {
        const resourceElement = document.querySelector('#xy_app_content > div.ta-frame > div.ta_panel.ta_panel_group.ta_group > section > section > main > div > div.group-resource-body > div');
        if (!resourceElement) {
            return null;
        }

        const url = window.location.href;
        const match = url.match(/resource\/\d+\/(\d+)$/);
        return match ? match[1] : null;
    }

    const userInfoCache = {
        data: null,
        timestamp: 0,
        ttl: 5 * 60 * 1000,

        set(data) {
            this.data = data;
            this.timestamp = Date.now();
        },

        get() {
            if (!this.data) return null;
            if (Date.now() - this.timestamp > this.ttl) {
                this.clear();
                return null;
            }
            return this.data;
        },

        clear() {
            this.data = null;
            this.timestamp = 0;
        }
    };

    async function getUserInfo() {
        const cachedInfo = userInfoCache.get();
        if (cachedInfo) {
            return cachedInfo;
        }

        try {
            const token = getCookie('prd-access-token');
            if (!token) {
                console.error("未找到访问令牌");
                return null;
            }

            const response = await fetch(`https://${domain}/api/jx-auth/oauth2/info`, {
                headers: {
                    "accept": "/",
                    "content-type": "application/json; charset=utf-8",
                    "authorization": `Bearer ${token}`
                },
                method: "GET",
                credentials: "include"
            });

            const data = await response.json();
            if (data.success) {
                const userId = data.data.info.id;
                userInfoCache.set(userId);
                return userId;
            }
        } catch (error) {
            console.error("获取用户信息失败:", error);
            return null;
        }
    }

    function getCookie(keyword = 'prd-access-token') {
        const cookies = document.cookie.split('; ');
        for (const cookie of cookies) {
            const [name, value] = cookie.split('=');
            if (name.includes(keyword)) {
                return value;
            }
        }
        return null;
    }

    function getAuthToken() {
        return new Promise((resolve, reject) => {
            const token = getCookie();
            if (token) {
                resolve(token);
            } else {
                reject('未找到授权令牌');
            }
        });
    }

    function fetchTaskList(authToken) {
        const GROUP_ID = getGroupIdFromUrl();
        if (!GROUP_ID) {
            console.log('当前页面不是课程页面');
            return Promise.resolve([]);
        }
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: `https://${domain}/api/jx-stat/group/task/queryTaskNotices?group_id=${GROUP_ID}&role=1`,
                headers: {
                    "authorization": "Bearer " + authToken
                },
                onload: function (response) {
                    if (response.status === 200) {
                        let data = JSON.parse(response.responseText);
                        if (data.success) {
                            resolve(data.data.student_tasks);
                        } else {
                            reject(data.message);
                        }
                    } else {
                        reject('获取任务列表失败');
                    }
                }
            });
        });
    }

    function fetchResourceList(authToken) {
        const GROUP_ID = getGroupIdFromUrl();
        if (!GROUP_ID) {
            return Promise.resolve([]);
        }
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: `https://${domain}/api/jx-iresource/resource/queryCourseResources?group_id=${GROUP_ID}`,
                headers: {
                    "authorization": "Bearer " + authToken
                },
                onload: function (response) {
                    if (response.status === 200) {
                        let data = JSON.parse(response.responseText);
                        if (data.success) {
                            resolve(data.data);
                        } else {
                            reject(data.message);
                        }
                    } else {
                        reject('获取资源列表失败');
                    }
                }
            });
        });
    }

    function showTaskList(container, tasks, resources) {
        const resourceMap = new Map();
        resources.forEach(resource => {
            if (resource.is_task) {
                resourceMap.set(resource.task_id, resource);
            }
        });

        const fragment = document.createDocumentFragment();

        let title = document.createElement('h3');
        title.innerText = '选择要完成的任务';
        Object.assign(title.style, {
            fontSize: '24px',
            fontWeight: '600',
            color: '#1a1a1a',
            marginBottom: '16px',
            textAlign: 'center',
            position: 'relative',
            padding: '0 0 12px'
        });

        title.innerHTML += `
            <div style="
                position: absolute;
                bottom: 0;
                left: 50%;
                transform: translateX(-50%);
                width: 60px;
                height: 3px;
                background: linear-gradient(90deg, #3B82F6, #60A5FA);
                border-radius: 2px;
            "></div>
        `;
        fragment.appendChild(title);

        const stats = document.createElement('div');
        Object.assign(stats.style, {
            marginBottom: '24px',
            padding: '24px',
            borderRadius: '20px',
            background: 'linear-gradient(145deg, rgba(255,255,255,0.95), rgba(249,250,251,0.95))',
            boxShadow: '0 8px 32px rgba(0,0,0,0.06)',
            border: '1px solid rgba(255,255,255,0.2)',
            backdropFilter: 'blur(8px)',
            position: 'relative',
            overflow: 'hidden',
            cursor: 'pointer',
            transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
        });

        const toggleIcon = document.createElement('div');
        toggleIcon.innerHTML = `
            <div class="stats-toggle-icon" style="
                position: absolute;
                top: 24px;
                right: 24px;
                width: 28px;
                height: 28px;
                display: flex;
                align-items: center;
                justify-content: center;
                background: linear-gradient(145deg, rgba(59,130,246,0.12), rgba(37,99,235,0.12));
                border: 1px solid rgba(59,130,246,0.15);
                border-radius: 8px;
                box-shadow: 0 2px 6px rgba(37,99,235,0.08);
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                color: #3b82f6;
                backdrop-filter: blur(4px);
                ">
                <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" style="transform: rotate(0deg); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);">
                    <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
                </svg>
            </div>
        `;
        stats.appendChild(toggleIcon);

        const statsContent = document.createElement('div');
        statsContent.style.maxHeight = '130px';
        statsContent.style.overflow = 'hidden';
        statsContent.style.transition = 'max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1)';

        const now = new Date();
        const threeDaysLater = new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000);

        const autoSubmitTasks = tasks.filter(task => {
            const deadline = new Date(task.end_time);
            return task.task_type === 1 && task.finish !== 2 && deadline > now;
        });

        const manualTasks = tasks.filter(task => {
            const deadline = new Date(task.end_time);
            return task.task_type !== 1 && task.finish !== 2 && deadline > now;
        });

        const deadlineTasks = tasks.filter(task => {
            const deadline = new Date(task.end_time);
            return task.finish !== 2 && deadline > now && deadline <= threeDaysLater;
        });

        const completedTasks = tasks.filter(task => task.finish === 2);
        const overdueTasks = tasks.filter(task =>
            task.finish !== 2 && new Date(task.end_time) <= now
        );

        const typeStats = {
            document: tasks.filter(t => t.task_type === 1).length,
            homework: tasks.filter(t => t.task_type === 2).length,
            exercise: tasks.filter(t => t.task_type === 3).length,
            quiz: tasks.filter(t => t.task_type === 4).length,
            survey: tasks.filter(t => t.task_type === 5).length,
            discussion: tasks.filter(t => t.task_type === 6).length
        };

        const completionRate = (completedTasks.length / tasks.length * 100).toFixed(1);

        const completedPercent = (+Math.max(0, (completedTasks.length / tasks.length * 100)).toFixed(1)) || 0;
        const overduePercent = (+Math.max(0, (overdueTasks.length / tasks.length * 100)).toFixed(1)) || 0;
        const ongoingPercent = (+Math.max(0, (100 - completedPercent - overduePercent)).toFixed(1)) || 0;

        const content = document.createElement('div');
        content.style.position = 'relative';
        content.style.zIndex = '1';

        const cardHTML = `
            <div style="
                display: grid;
                grid-template-columns: repeat(3, 1fr);
                gap: 20px;
                margin-bottom: 24px;
            ">
                <div class="stat-card" style="
                    display: flex;
                    flex-direction: column;
                    align-items: center;
                    justify-content: center;
                    padding: 20px;
                    border-radius: 16px;
                    background: linear-gradient(135deg, rgba(16,185,129,0.15), rgba(5,150,105,0.15));
                    border: 1px solid rgba(16,185,129,0.2);
                    cursor: pointer;
                ">
                    <div style="
                        font-size: 32px;
                        font-weight: bold;
                        color: #059669;
                        margin-bottom: 8px;
                    ">${autoSubmitTasks.length}</div>
                    <div style="
                        font-size: 14px;
                        color: #065f46;
                        font-weight: bold;
                        display: flex;
                        align-items: center;
                        gap: 6px;
                    ">
                        <span style="font-size: 16px">🚀</span>
                        可自动提交
                    </div>
                </div>

                <div class="stat-card" style="
                    display: flex;
                    flex-direction: column;
                    align-items: center;
                    justify-content: center;
                    padding: 20px;
                    border-radius: 16px;
                    background: linear-gradient(135deg, rgba(59,130,246,0.15), rgba(37,99,235,0.15));
                    border: 1px solid rgba(59,130,246,0.2);
                    cursor: pointer;
                ">
                    <div style="
                        font-size: 32px;
                        font-weight: bold;
                        color: #2563eb;
                        margin-bottom: 8px;
                    ">${manualTasks.length}</div>
                    <div style="
                        font-size: 14px;
                        color: #1e40af;
                        font-weight: bold;
                        display: flex;
                        align-items: center;
                        gap: 6px;
                    ">
                        <span style="font-size: 16px">✍️</span>
                        需手动完成
                    </div>
                </div>

                <div class="stat-card" style="
                    display: flex;
                    flex-direction: column;
                    align-items: center;
                    justify-content: center;
                    padding: 20px;
                    border-radius: 16px;
                    background: linear-gradient(135deg, rgba(239,68,68,0.15), rgba(220,38,38,0.15));
                    border: 1px solid rgba(239,68,68,0.2);
                    cursor: pointer;
                ">
                    <div style="
                        font-size: 32px;
                        font-weight: bold;
                        color: #dc2626;
                        margin-bottom: 8px;
                    ">${deadlineTasks.length}</div>
                    <div style="
                        font-size: 14px;
                        color: #991b1b;
                        font-weight: bold;
                        display: flex;
                        align-items: center;
                        gap: 6px;
                    ">
                        <span style="font-size: 16px">⏰</span>
                        即将截止
                    </div>
                </div>
            </div>

            <div style="
                background: rgba(255,255,255,0.8);
                border-radius: 16px;
                padding: 20px;
                margin-bottom: 24px;
                border: 1px solid rgba(0,0,0,0.05);
            ">
                <div style="
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    margin-bottom: 16px;
                ">
                    <div style="
                        display: flex;
                        align-items: center;
                        gap: 12px;
                    ">
                        <div style="
                            width: 40px;
                            height: 40px;
                            border-radius: 12px;
                            background: linear-gradient(135deg, #3b82f6, #2563eb);
                            display: flex;
                            align-items: center;
                            justify-content: center;
                            color: white;
                            font-size: 18px;
                        ">📊</div>
                        <div>
                            <div style="font-size: 16px; font-weight: 600; color: #1f2937">总体进度</div>
                            <div style="font-size: 13px; color: #6b7280">已完成 ${completedTasks.length} / ${tasks.length} 个任务</div>
                        </div>
                    </div>
                    <div style="
                        background: ${parseFloat(completionRate) >= 80 ? '#dcfce7' : '#fee2e2'};
                        color: ${parseFloat(completionRate) >= 80 ? '#166534' : '#991b1b'};
                        padding: 6px 12px;
                        border-radius: 20px;
                        font-size: 14px;
                        font-weight: 600;
                    ">
                        ${completionRate}%
                    </div>
                </div>

                <div style="
                    height: 8px;
                    background: #e5e7eb;
                    border-radius: 4px;
                    overflow: hidden;
                    position: relative;
                    display: flex;
                    margin-bottom: 8px;
                ">
                    <div style="
                        width: ${completedPercent}%;
                        height: 100%;
                        background: linear-gradient(90deg, #22c55e, #16a34a);
                        position: relative;
                    ">
                        <div style="
                            position: absolute;
                            top: 0;
                            left: 0;
                            right: 0;
                            bottom: 0;
                            background: linear-gradient(
                                90deg,
                                transparent 0%,
                                rgba(255,255,255,0.3) 50%,
                                transparent 100%
                            );
                            animation: shimmer 2s infinite;
                        "></div>
                    </div>
                    <div style="
                        width: ${ongoingPercent}%;
                        height: 100%;
                        background: linear-gradient(90deg, #fbbf24, #f59e0b);
                        position: relative;
                    ">
                        <div style="
                            position: absolute;
                            top: 0;
                            left: 0;
                            right: 0;
                            bottom: 0;
                            background: linear-gradient(
                                90deg,
                                transparent 0%,
                                rgba(255,255,255,0.3) 50%,
                                transparent 100%
                            );
                            animation: shimmer 2s infinite;
                        "></div>
                    </div>
                    <div style="
                        width: ${overduePercent}%;
                        height: 100%;
                        background: linear-gradient(90deg, #ef4444, #dc2626);
                        position: relative;
                    ">
                        <div style="
                            position: absolute;
                            top: 0;
                            left: 0;
                            right: 0;
                            bottom: 0;
                            background: linear-gradient(
                                90deg,
                                transparent 0%,
                                rgba(255,255,255,0.3) 50%,
                                transparent 100%
                            );
                            animation: shimmer 2s infinite;
                        "></div>
                    </div>
                </div>
                <div style="
                    display: flex;
                    justify-content: space-between;
                    font-size: 12px;
                    color: #6b7280;
                ">
                    <div>已完成: ${completedPercent}%</div>
                    <div>进行中: ${ongoingPercent}%</div>
                    <div>已截止: ${overduePercent}%</div>
                </div>

            <div style="
                display: grid;
                grid-template-columns: repeat(3, 1fr);
                gap: 16px;
            ">
                ${Object.entries({
            "自主观看": [typeStats.document, "👁️"],
            "作业": [typeStats.homework, "✍️"],
            "课堂练习": [typeStats.exercise, "📚"],
            "测验": [typeStats.quiz, "💯"],
            "问卷": [typeStats.survey, "📋"],
            "讨论": [typeStats.discussion, "💭"]
        }).map(([name, [count, icon]]) => `
                    <div style="
                        background: rgba(255,255,255,0.8);
                        border-radius: 12px;
                        padding: 16px;
                        text-align: center;
                        border: 1px solid rgba(0,0,0,0.05);
                        transition: all 0.3s ease;
                    ">
                        <div style="font-size: 20px; margin-bottom: 4px">${icon}</div>
                        <div style="font-size: 20px; font-weight: 600; color: #1f2937; margin-bottom: 4px">
                            ${count}
                        </div>
                        <div style="font-size: 13px; color: #6b7280">${name}</div>
                    </div>
                `).join('')}
            </div>
        `;

        content.innerHTML = cardHTML;
        statsContent.appendChild(content);
        stats.appendChild(statsContent);

        let isExpanded = false;
        stats.addEventListener('click', () => {
            isExpanded = !isExpanded;

            if (isExpanded) {
                statsContent.style.maxHeight = statsContent.scrollHeight + 'px';
                toggleIcon.querySelector('svg').style.transform = 'rotate(180deg)';
                stats.style.boxShadow = '0 12px 36px rgba(0,0,0,0.1)';
            } else {
                statsContent.style.maxHeight = '130px';
                toggleIcon.querySelector('svg').style.transform = 'rotate(0deg)';
                stats.style.boxShadow = '0 8px 32px rgba(0,0,0,0.06)';
            }

            const ripple = document.createElement('div');
            ripple.style.cssText = `
                position: absolute;
                top: ${event.offsetY}px;
                left: ${event.offsetX}px;
                width: 5px;
                height: 5px;
                background: rgba(59,130,246,0.3);
                border-radius: 50%;
                pointer-events: none;
                transform: scale(1);
                opacity: 1;
                transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
            `;
            stats.appendChild(ripple);

            requestAnimationFrame(() => {
                ripple.style.transform = 'scale(100)';
                ripple.style.opacity = '0';
                setTimeout(() => ripple.remove(), 500);
            });
        });

        stats.addEventListener('mouseenter', () => {
            toggleIcon.querySelector('.stats-toggle-icon').style.background = 'rgba(59,130,246,0.2)';
            stats.style.boxShadow = '0 0 10px rgba(0,0,0,0.1)';
        });

        stats.addEventListener('mouseleave', () => {
            toggleIcon.querySelector('.stats-toggle-icon').style.background = 'rgba(59,130,246,0.1)';
            stats.style.boxShadow = 'none';
        });

        const style = document.createElement('style');
        style.textContent = `
            @keyframes shimmer {
                0% {
                    transform: translateX(-100%);
                }
                100% {
                    transform: translateX(100%);
                }
            }
        `;

        stats.appendChild(style);
        fragment.appendChild(stats);

        const documentTasks = tasks.filter(task => task.task_type === 1);
        const exerciseTasks = tasks.filter(task => task.task_type === 2);
        const paperTasks = tasks.filter(task => task.task_type === 3);
        const quizTasks = tasks.filter(task => task.task_type === 4);
        const surveyTasks = tasks.filter(task => task.task_type === 5);
        const discussionTasks = tasks.filter(task => task.task_type === 6);

        if (documentTasks.length > 0) {
            let docnotice = document.createElement('div');
            docnotice.innerHTML = `
                <div style="
                    background: linear-gradient(145deg, rgba(220,252,231,0.8), rgba(187,247,208,0.8));
                    padding: 12px 16px;
                    border-radius: 12px;
                    margin-bottom: 20px;
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    box-shadow: 0 2px 6px rgba(34,197,94,0.1);
                ">
                    <span style="font-size: 18px">👁️</span>
                    <span style="
                        color: #166534;
                        font-size: 13px;
                        font-weight: bold;
                        flex: 1;
                    ">自主观看可以自动提交,但不会记录时长</span>
                </div>
            `;
            fragment.appendChild(docnotice);
        }

        if (exerciseTasks.length > 0) {
            let excrcisenotice = document.createElement('div');
            excrcisenotice.innerHTML = `
                <div style="
                    background: linear-gradient(145deg, rgba(254,226,226,0.8), rgba(254,202,202,0.8));
                    padding: 12px 16px;
                    border-radius: 12px;
                    margin-bottom: 20px;
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    box-shadow: 0 2px 6px rgba(239,68,68,0.1);
                ">
                    <span style="font-size: 18px">✍️</span>
                    <span style="
                        color: #991B1B;
                        font-size: 13px;
                        font-weight: bold;
                        flex: 1;
                    ">作业需要手动完成,无法自动提交答案</span>
                </div>
            `;
            fragment.appendChild(excrcisenotice);
        }

        if (paperTasks.length > 0) {
            let paperNotice = document.createElement('div');
            paperNotice.innerHTML = `
                <div style="
                    background: linear-gradient(145deg, rgba(219,234,254,0.8), rgba(191,219,254,0.8));
                    padding: 12px 16px;
                    border-radius: 12px;
                    margin-bottom: 20px;
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    box-shadow: 0 2px 6px rgba(59,130,246,0.1);
                ">
                    <span style="font-size: 18px">📚</span>
                    <span style="
                        color: #1e40af;
                        font-size: 13px;
                        font-weight: bold;
                        flex: 1;
                    ">课堂练习由课堂动态发布,请进入相应页面作答</span>
                </div>
            `;
            fragment.appendChild(paperNotice);
        }

        if (quizTasks.length > 0) {
            let quizNotice = document.createElement('div');
            quizNotice.innerHTML = `
                <div style="
                    background: linear-gradient(145deg, rgba(243,232,255,0.8), rgba(233,213,255,0.8));
                    padding: 12px 16px;
                    border-radius: 12px;
                    margin-bottom: 20px;
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    box-shadow: 0 2px 6px rgba(147,51,234,0.1);
                ">
                    <span style="font-size: 18px">💯</span>
                    <span style="
                        color: #6B21A8;
                        font-size: 13px;
                        font-weight: bold;
                        flex: 1;
                    ">测验需要亲自作答,脚本无法自动完成</span>
                </div>
            `;
            fragment.appendChild(quizNotice);
        }

        if (surveyTasks.length > 0) {
            let surveyNotice = document.createElement('div');
            surveyNotice.innerHTML = `
                <div style="
                    background: linear-gradient(145deg, rgba(253,230,138,0.8), rgba(252,211,77,0.8));
                    padding: 12px 16px;
                    border-radius: 12px;
                    margin-bottom: 20px;
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    box-shadow: 0 2px 6px rgba(217,119,6,0.1);
                ">
                    <span style="font-size: 18px">📋</span>
                    <span style="
                        color: #92400E;
                        font-size: 13px;
                        font-weight: bold;
                        flex: 1;
                    ">问卷需要手动填写,无法自动提交</span>
                </div>
            `;
            fragment.appendChild(surveyNotice);
        }

        if (discussionTasks.length > 0) {
            let discussionNotice = document.createElement('div');
            discussionNotice.innerHTML = `
                <div style="
                    background: linear-gradient(145deg, rgba(236,254,255,0.8), rgba(199,246,249,0.8));
                    padding: 12px 16px;
                    border-radius: 12px;
                    margin-bottom: 20px;
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    box-shadow: 0 2px 6px rgba(6,182,212,0.1);
                ">
                    <span style="font-size: 18px">💭</span>
                    <span style="
                        color: #155E75;
                        font-size: 13px;
                        font-weight: bold;
                        flex: 1;
                    ">讨论需要发表评论或回复他人才能完成</span>
                </div>
            `;
            fragment.appendChild(discussionNotice);
        }

        const separator = document.createElement('div');
        separator.innerHTML = `
            <div style="
                margin: 32px 0;
                padding: 16px 20px;
                background: linear-gradient(145deg, rgba(249,250,251,0.97), rgba(243,244,246,0.97));
                border-radius: 16px;
                border: 1px solid rgba(0,0,0,0.05);
                box-shadow: 0 4px 16px rgba(0,0,0,0.03);
                backdrop-filter: blur(8px);
            ">
                <div style="
                    display: flex;
                    align-items: center;
                    gap: 12px;
                ">
                    <div style="
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        width: 32px;
                        height: 32px;
                        background: linear-gradient(145deg, #FEF3C7, #FDE68A);
                        border-radius: 10px;
                        color: #92400E;
                        font-size: 18px;
                    ">💡</div>
                    <div style="flex: 1;">
                        <div style="
                            font-weight: 600;
                            color: #92400E;
                            margin-bottom: 4px;
                            font-size: 15px;
                        ">在以下区域勾选作业</div>
                        <div style="
                            color: #B45309;
                            font-size: 13px;
                            line-height: 1.5;
                        ">只有自主观看作业可以自动提交,其它作业请手动完成</div>
                    </div>
                </div>
            </div>
        `;
        fragment.appendChild(separator);

        const createSectionTitle = (text, count, defaultExpanded = false) => {
            const wrapper = document.createElement('div');
            wrapper.style.marginBottom = '28px';
            wrapper.style.marginTop = '28px';
            wrapper.style.position = 'relative';

            const header = document.createElement('div');
            header.style.cursor = 'pointer';
            header.style.userSelect = 'none';
            header.style.transition = 'transform 0.3s ease';
            header.innerHTML = `
                <div style="
                    display: flex;
                    align-items: center;
                    gap: 12px;
                    margin-bottom: 16px;
                    padding: 12px 16px;
                    background: linear-gradient(145deg, rgba(255,255,255,0.95), rgba(249,250,251,0.95));
                    border-radius: 16px;
                    box-shadow: 0 4px 16px rgba(0,0,0,0.03);
                    border: 1px solid rgba(0,0,0,0.05);
                    backdrop-filter: blur(8px);
                    transform-origin: center;
                    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                ">
                    <span class="expand-icon" style="
                        display: inline-flex;
                        justify-content: center;
                        align-items: center;
                        width: 28px;
                        height: 28px;
                        background: ${defaultExpanded ?
                    'linear-gradient(145deg, #3B82F6, #2563EB)' :
                    'linear-gradient(145deg, #f3f4f6, #e5e7eb)'};
                        border-radius: 8px;
                        color: ${defaultExpanded ? '#fff' : '#6B7280'};
                        font-size: 14px;
                        font-weight: bold;
                        transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
                        transform: rotate(${defaultExpanded ? '90' : '0'}deg);
                        box-shadow: ${defaultExpanded ?
                    '0 4px 12px rgba(59,130,246,0.2)' :
                    '0 2px 6px rgba(0,0,0,0.05)'};
                    ">▶</span>
                    <h4 style="
                        font-size: 16px;
                        font-weight: 600;
                        color: #374151;
                        margin: 0;
                        flex: 1;
                        transition: all 0.3s ease;
                    ">${text}</h4>
                    <span style="
                        background: ${defaultExpanded ?
                    'linear-gradient(145deg, #EFF6FF, #DBEAFE)' :
                    'linear-gradient(145deg, #F3F4F6, #E5E7EB)'};
                        color: ${defaultExpanded ? '#3B82F6' : '#6B7280'};
                        padding: 6px 12px;
                        border-radius: 20px;
                        font-size: 13px;
                        font-weight: 600;
                        transition: all 0.3s ease;
                        border: 1px solid ${defaultExpanded ?
                    'rgba(59,130,246,0.1)' :
                    'rgba(107,114,128,0.1)'};
                        ">${count}个任务</span>
                </div>
            `;

            const contentWrapper = document.createElement('div');
            contentWrapper.style.position = 'relative';
            contentWrapper.style.overflow = 'hidden';
            contentWrapper.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
            contentWrapper.style.marginBottom = '8px';
            contentWrapper.style.paddingLeft = '20px';
            contentWrapper.style.paddingRight = '20px';

            const content = document.createElement('div');
            content.style.position = 'relative';
            content.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)';

            contentWrapper.appendChild(content);

            let isAnimating = false;

            const updateExpandState = (expanded) => {
                if (isAnimating) return;
                isAnimating = true;

                const headerDiv = header.querySelector('div');
                const icon = header.querySelector('.expand-icon');
                const title = header.querySelector('h4');
                const badge = header.querySelector('span:last-child');

                if (expanded) {
                    contentWrapper.style.display = 'block';
                    contentWrapper.style.height = 'auto';
                    const targetHeight = contentWrapper.scrollHeight;
                    contentWrapper.style.height = '0px';

                    requestAnimationFrame(() => {
                        contentWrapper.style.height = targetHeight + 'px';
                        content.style.opacity = '1';
                        content.style.transform = 'translateY(0)';

                        headerDiv.style.background = 'linear-gradient(145deg, #EFF6FF, #DBEAFE)';
                        headerDiv.style.borderColor = 'rgba(59,130,246,0.1)';

                        icon.style.transform = 'rotate(90deg)';
                        icon.style.background = 'linear-gradient(145deg, #3B82F6, #2563EB)';
                        icon.style.color = '#fff';
                        icon.style.boxShadow = '0 4px 12px rgba(59,130,246,0.2)';

                        title.style.color = '#2563EB';

                        badge.style.background = 'linear-gradient(145deg, #EFF6FF, #DBEAFE)';
                        badge.style.color = '#3B82F6';
                        badge.style.borderColor = 'rgba(59,130,246,0.1)';
                    });
                } else {
                    contentWrapper.style.height = contentWrapper.scrollHeight + 'px';

                    requestAnimationFrame(() => {
                        contentWrapper.style.height = '0';
                        content.style.opacity = '0';
                        content.style.transform = 'translateY(-10px)';

                        headerDiv.style.background = 'linear-gradient(145deg, rgba(255,255,255,0.95), rgba(249,250,251,0.95))';
                        headerDiv.style.borderColor = 'rgba(0,0,0,0.05)';

                        icon.style.transform = 'rotate(0deg)';
                        icon.style.background = 'linear-gradient(145deg, #f3f4f6, #e5e7eb)';
                        icon.style.color = '#6B7280';
                        icon.style.boxShadow = '0 2px 6px rgba(0,0,0,0.05)';

                        title.style.color = '#374151';

                        badge.style.background = 'linear-gradient(145deg, #F3F4F6, #E5E7EB)';
                        badge.style.color = '#6B7280';
                        badge.style.borderColor = 'rgba(107,114,128,0.1)';
                    });
                }

                contentWrapper.addEventListener('transitionend', function handler() {
                    if (expanded) {
                        contentWrapper.style.height = 'auto';
                    } else {
                        contentWrapper.style.display = 'none';
                    }
                    isAnimating = false;
                    contentWrapper.removeEventListener('transitionend', handler);
                }, { once: true });
            };

            if (defaultExpanded) {
                contentWrapper.style.display = 'block';
                contentWrapper.style.height = 'auto';
                content.style.opacity = '1';
                content.style.transform = 'translateY(0)';
            } else {
                contentWrapper.style.display = 'none';
                contentWrapper.style.height = '0';
                content.style.opacity = '0';
                content.style.transform = 'translateY(-10px)';
            }

            header.addEventListener('click', () => {
                const isExpanded = contentWrapper.style.display !== 'none';
                updateExpandState(!isExpanded);
            });

            header.addEventListener('mouseenter', () => {
                const headerDiv = header.querySelector('div');
                headerDiv.style.transform = 'scale(1.01)';
                headerDiv.style.boxShadow = '0 6px 20px rgba(0,0,0,0.05)';
            });

            header.addEventListener('mouseleave', () => {
                const headerDiv = header.querySelector('div');
                headerDiv.style.transform = 'scale(1)';
                headerDiv.style.boxShadow = '0 4px 16px rgba(0,0,0,0.03)';
            });

            wrapper.appendChild(header);
            wrapper.appendChild(contentWrapper);
            return { wrapper, content };
        };

        function createTaskSubTitle(title, count) {
            const subTitleWrapper = document.createElement('div');
            Object.assign(subTitleWrapper.style, {
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'flex-end',
                marginBottom: '12px',
                marginTop: '16px',
                marginLeft: '12px',
                marginRight: '12px'
            });

            const subTitle = document.createElement('div');
            subTitle.innerHTML = `
                <div style="
                    display: flex;
                    align-items: center;
                    gap: 8px;
                ">
                    <h5 style="
                        font-size: 14px;
                        font-weight: 500;
                        color: #6B7280;
                        margin: 0;
                    ">${title}</h5>
                    <span style="
                        background: #F3F4F6;
                        color: #9CA3AF;
                        padding: 1px 6px;
                        border-radius: 10px;
                        font-size: 12px;
                    ">${count}</span>
                </div>
            `;

            subTitleWrapper.appendChild(subTitle);
            return subTitleWrapper;
        }

        if (documentTasks.length > 0) {
            const { wrapper: docWrapper, content: docContent } = createSectionTitle('自主观看 (可自动提交)', documentTasks.length, true);
            fragment.appendChild(docWrapper);

            const ongoingDocTasks = documentTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) > now
            );
            const completedDocTasks = documentTasks.filter(task =>
                task.finish === 2
            );
            const overdueDocTasks = documentTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) <= now
            );

            if (ongoingDocTasks.length > 0) {
                const { wrapper: ongoingWrapper, content: ongoingContent } = createSectionTitle('进行中', ongoingDocTasks.length, true);
                docContent.appendChild(ongoingWrapper);

                const subTitleWrapper = createTaskSubTitle('进行中', ongoingDocTasks.length);
                Object.assign(subTitleWrapper.style, {
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'space-between',
                    marginBottom: '12px',
                    marginTop: '16px',
                    marginLeft: '12px',
                    marginRight: '12px'
                });

                const selectAllWrapper = document.createElement('div');
                selectAllWrapper.style.order = '-1';
                selectAllWrapper.style.display = 'flex';
                selectAllWrapper.style.alignItems = 'center';
                selectAllWrapper.style.gap = '6px';

                const selectAllCheckbox = document.createElement('input');
                selectAllCheckbox.type = 'checkbox';
                selectAllCheckbox.className = 'select-all-checkbox';
                Object.assign(selectAllCheckbox.style, {
                    appearance: 'none',
                    '-webkit-appearance': 'none',
                    width: '18px',
                    height: '18px',
                    border: '2px solid #e2e8f0',
                    borderRadius: '4px',
                    cursor: 'pointer',
                    transition: 'all 0.2s ease',
                    backgroundColor: '#fff',
                    position: 'relative',
                    margin: '0'
                });

                const selectAllLabel = document.createElement('span');
                selectAllLabel.textContent = '全选';
                selectAllLabel.style.fontSize = '12px';
                selectAllLabel.style.color = '#6B7280';
                selectAllLabel.style.cursor = 'pointer';

                selectAllCheckbox.addEventListener('change', function () {
                    const taskCheckboxes = container.querySelectorAll('.task-checkbox:not(:disabled)[data-node-id]');
                    taskCheckboxes.forEach(checkbox => {
                        checkbox.checked = this.checked;
                        checkbox.dispatchEvent(new Event('change'));
                    });
                });

                const observer = new MutationObserver(() => {
                    const taskCheckboxes = container.querySelectorAll('.task-checkbox:not(:disabled)');
                    const checkedCount = container.querySelectorAll('.task-checkbox:checked').length;
                    selectAllCheckbox.checked = checkedCount === taskCheckboxes.length && taskCheckboxes.length > 0;
                });
                observer.observe(container, { childList: true, subtree: true });

                selectAllCheckbox.addEventListener('change', function () {
                    if (this.checked) {
                        this.style.backgroundColor = '#4CAF50';
                        this.style.borderColor = '#4CAF50';
                        this.style.backgroundImage = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z'/%3E%3C/svg%3E\")";
                        this.style.backgroundSize = '14px';
                        this.style.backgroundPosition = 'center';
                        this.style.backgroundRepeat = 'no-repeat';
                    } else {
                        this.style.backgroundColor = '#fff';
                        this.style.borderColor = '#e2e8f0';
                        this.style.backgroundImage = 'none';
                    }
                });

                selectAllWrapper.appendChild(selectAllCheckbox);
                selectAllWrapper.appendChild(selectAllLabel);
                subTitleWrapper.appendChild(selectAllWrapper);
                ongoingContent.appendChild(subTitleWrapper);

                ongoingDocTasks.forEach(task => createTaskElement(task, resourceMap, ongoingContent, true));
            }

            if (completedDocTasks.length > 0) {
                const { wrapper: completedWrapper, content: completedContent } = createSectionTitle('已完成', completedDocTasks.length, false);
                docContent.appendChild(completedWrapper);

                const subTitleWrapper = createTaskSubTitle('已完成', completedDocTasks.length);
                completedContent.appendChild(subTitleWrapper);

                completedDocTasks.forEach(task => createTaskElement(task, resourceMap, completedContent, false));
            }

            if (overdueDocTasks.length > 0) {
                const { wrapper: overdueWrapper, content: overdueContent } = createSectionTitle('已截止', overdueDocTasks.length, false);
                docContent.appendChild(overdueWrapper);

                const subTitleWrapper = createTaskSubTitle('已截止', overdueDocTasks.length);
                overdueContent.appendChild(subTitleWrapper);

                overdueDocTasks.forEach(task => createTaskElement(task, resourceMap, overdueContent, false));
            }
        }

        if (exerciseTasks.length > 0) {
            const { wrapper: exerciseWrapper, content: exerciseContent } = createSectionTitle('作业 (需手动完成)', exerciseTasks.length, false);
            fragment.appendChild(exerciseWrapper);

            const ongoingExerciseTasks = exerciseTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) > now
            );
            const completedExerciseTasks = exerciseTasks.filter(task =>
                task.finish === 2
            );
            const overdueExerciseTasks = exerciseTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) <= now
            );

            if (ongoingExerciseTasks.length > 0) {
                const { wrapper: ongoingWrapper, content: ongoingContent } = createSectionTitle('进行中', ongoingExerciseTasks.length, true);
                exerciseContent.appendChild(ongoingWrapper);

                const subTitleWrapper = createTaskSubTitle('进行中', ongoingExerciseTasks.length);
                ongoingContent.appendChild(subTitleWrapper);

                ongoingExerciseTasks.forEach(task => createTaskElement(task, resourceMap, ongoingContent, false));
            }

            if (completedExerciseTasks.length > 0) {
                const { wrapper: completedWrapper, content: completedContent } = createSectionTitle('已完成', completedExerciseTasks.length, false);
                exerciseContent.appendChild(completedWrapper);

                const subTitleWrapper = createTaskSubTitle('已完成', completedExerciseTasks.length);
                completedContent.appendChild(subTitleWrapper);

                completedExerciseTasks.forEach(task => createTaskElement(task, resourceMap, completedContent, false));
            }

            if (overdueExerciseTasks.length > 0) {
                const { wrapper: overdueWrapper, content: overdueContent } = createSectionTitle('已截止', overdueExerciseTasks.length, false);
                exerciseContent.appendChild(overdueWrapper);

                const subTitleWrapper = createTaskSubTitle('已截止', overdueExerciseTasks.length);
                overdueContent.appendChild(subTitleWrapper);

                overdueExerciseTasks.forEach(task => createTaskElement(task, resourceMap, overdueContent, false));
            }
        }

        if (paperTasks.length > 0) {
            const { wrapper: paperWrapper, content: paperContent } = createSectionTitle('课堂练习 (需手动完成)', paperTasks.length, false);
            fragment.appendChild(paperWrapper);

            const ongoingPaperTasks = paperTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) > now
            );
            const completedPaperTasks = paperTasks.filter(task =>
                task.finish === 2
            );
            const overduePaperTasks = paperTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) <= now
            );

            if (ongoingPaperTasks.length > 0) {
                const { wrapper: ongoingWrapper, content: ongoingContent } = createSectionTitle('进行中', ongoingPaperTasks.length, true);
                paperContent.appendChild(ongoingWrapper);

                const subTitleWrapper = createTaskSubTitle('进行中', ongoingPaperTasks.length);
                ongoingContent.appendChild(subTitleWrapper);

                ongoingPaperTasks.forEach(task => createTaskElement(task, resourceMap, ongoingContent, false));
            }

            if (completedPaperTasks.length > 0) {
                const { wrapper: completedWrapper, content: completedContent } = createSectionTitle('已完成', completedPaperTasks.length, false);
                paperContent.appendChild(completedWrapper);

                const subTitleWrapper = createTaskSubTitle('已完成', completedPaperTasks.length);
                completedContent.appendChild(subTitleWrapper);

                completedPaperTasks.forEach(task => createTaskElement(task, resourceMap, completedContent, false));
            }

            if (overduePaperTasks.length > 0) {
                const { wrapper: overdueWrapper, content: overdueContent } = createSectionTitle('已截止', overduePaperTasks.length, false);
                paperContent.appendChild(overdueWrapper);

                const subTitleWrapper = createTaskSubTitle('已截止', overduePaperTasks.length);
                overdueContent.appendChild(subTitleWrapper);

                overduePaperTasks.forEach(task => createTaskElement(task, resourceMap, overdueContent, false));
            }
        }

        if (quizTasks.length > 0) {
            const { wrapper: quizWrapper, content: quizContent } = createSectionTitle('测验 (需手动完成)', quizTasks.length, false);
            fragment.appendChild(quizWrapper);

            const ongoingQuizTasks = quizTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) > now
            );
            const completedQuizTasks = quizTasks.filter(task =>
                task.finish === 2
            );
            const overdueQuizTasks = quizTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) <= now
            );

            if (ongoingQuizTasks.length > 0) {
                const { wrapper: ongoingWrapper, content: ongoingContent } = createSectionTitle('进行中', ongoingQuizTasks.length, true);
                quizContent.appendChild(ongoingWrapper);

                const subTitleWrapper = createTaskSubTitle('进行中', ongoingQuizTasks.length);
                ongoingContent.appendChild(subTitleWrapper);

                ongoingQuizTasks.forEach(task => createTaskElement(task, resourceMap, ongoingContent, false));
            }

            if (completedQuizTasks.length > 0) {
                const { wrapper: completedWrapper, content: completedContent } = createSectionTitle('已完成', completedQuizTasks.length, false);
                quizContent.appendChild(completedWrapper);

                const subTitleWrapper = createTaskSubTitle('已完成', completedQuizTasks.length);
                completedContent.appendChild(subTitleWrapper);

                completedQuizTasks.forEach(task => createTaskElement(task, resourceMap, completedContent, false));
            }

            if (overdueQuizTasks.length > 0) {
                const { wrapper: overdueWrapper, content: overdueContent } = createSectionTitle('已截止', overdueQuizTasks.length, false);
                quizContent.appendChild(overdueWrapper);

                const subTitleWrapper = createTaskSubTitle('已截止', overdueQuizTasks.length);
                overdueContent.appendChild(subTitleWrapper);

                overdueQuizTasks.forEach(task => createTaskElement(task, resourceMap, overdueContent, false));
            }
        }

        if (surveyTasks.length > 0) {
            const { wrapper: surveyWrapper, content: surveyContent } = createSectionTitle('问卷 (需手动完成)', surveyTasks.length, false);
            fragment.appendChild(surveyWrapper);

            const ongoingSurveyTasks = surveyTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) > now
            );
            const completedSurveyTasks = surveyTasks.filter(task =>
                task.finish === 2
            );
            const overdueSurveyTasks = surveyTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) <= now
            );

            if (ongoingSurveyTasks.length > 0) {
                const { wrapper: ongoingWrapper, content: ongoingContent } = createSectionTitle('进行中', ongoingSurveyTasks.length, true);
                surveyContent.appendChild(ongoingWrapper);

                const subTitleWrapper = createTaskSubTitle('进行中', ongoingSurveyTasks.length);
                ongoingContent.appendChild(subTitleWrapper);

                ongoingSurveyTasks.forEach(task => createTaskElement(task, resourceMap, ongoingContent, false));
            }

            if (completedSurveyTasks.length > 0) {
                const { wrapper: completedWrapper, content: completedContent } = createSectionTitle('已完成', completedSurveyTasks.length, false);
                surveyContent.appendChild(completedWrapper);

                const subTitleWrapper = createTaskSubTitle('已完成', completedSurveyTasks.length);
                completedContent.appendChild(subTitleWrapper);

                completedSurveyTasks.forEach(task => createTaskElement(task, resourceMap, completedContent, false));
            }

            if (overdueSurveyTasks.length > 0) {
                const { wrapper: overdueWrapper, content: overdueContent } = createSectionTitle('已截止', overdueSurveyTasks.length, false);
                surveyContent.appendChild(overdueWrapper);

                const subTitleWrapper = createTaskSubTitle('已截止', overdueSurveyTasks.length);
                overdueContent.appendChild(subTitleWrapper);

                overdueSurveyTasks.forEach(task => createTaskElement(task, resourceMap, overdueContent, false));
            }
        }

        if (discussionTasks.length > 0) {
            const { wrapper: discussionWrapper, content: discussionContent } = createSectionTitle('讨论 (需手动完成)', discussionTasks.length, false);
            fragment.appendChild(discussionWrapper);

            const ongoingDiscussionTasks = discussionTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) > now
            );
            const completedDiscussionTasks = discussionTasks.filter(task =>
                task.finish === 2
            );
            const overdueDiscussionTasks = discussionTasks.filter(task =>
                task.finish !== 2 && new Date(task.end_time) <= now
            );

            if (ongoingDiscussionTasks.length > 0) {
                const { wrapper: ongoingWrapper, content: ongoingContent } = createSectionTitle('进行中', ongoingDiscussionTasks.length, true);
                discussionContent.appendChild(ongoingWrapper);

                const subTitleWrapper = createTaskSubTitle('进行中', ongoingDiscussionTasks.length);
                ongoingContent.appendChild(subTitleWrapper);

                ongoingDiscussionTasks.forEach(task => createTaskElement(task, resourceMap, ongoingContent, false));
            }

            if (completedDiscussionTasks.length > 0) {
                const { wrapper: completedWrapper, content: completedContent } = createSectionTitle('已完成', completedDiscussionTasks.length, false);
                discussionContent.appendChild(completedWrapper);

                const subTitleWrapper = createTaskSubTitle('已完成', completedDiscussionTasks.length);
                completedContent.appendChild(subTitleWrapper);

                completedDiscussionTasks.forEach(task => createTaskElement(task, resourceMap, completedContent, false));
            }

            if (overdueDiscussionTasks.length > 0) {
                const { wrapper: overdueWrapper, content: overdueContent } = createSectionTitle('已截止', overdueDiscussionTasks.length, false);
                discussionContent.appendChild(overdueWrapper);

                const subTitleWrapper = createTaskSubTitle('已截止', overdueDiscussionTasks.length);
                overdueContent.appendChild(subTitleWrapper);

                overdueDiscussionTasks.forEach(task => createTaskElement(task, resourceMap, overdueContent, false));
            }
        }

        const recorderComponent = createRecorderComponent();
        fragment.appendChild(recorderComponent);

        let buttonContainer = document.createElement('div');
        Object.assign(buttonContainer.style, {
            position: 'sticky',
            bottom: '20px',
            left: '0',
            right: '0',
            padding: '0 20px',
            marginTop: '30px',
            zIndex: '1002',
            backgroundColor: 'rgba(255, 255, 255, 0.9)',
            backdropFilter: 'blur(8px)',
            borderTop: '1px solid rgba(0,0,0,0.05)',
            paddingTop: '20px'
        });

        let submitButton = document.createElement('button');
        submitButton.innerHTML = `
            <span style="margin-right: 8px">📤</span>
            提交完成状态
        `;
        Object.assign(submitButton.style, {
            width: '100%',
            padding: '14px',
            border: 'none',
            borderRadius: '12px',
            background: 'linear-gradient(145deg, #3B82F6, #2563EB)',
            color: 'white',
            fontSize: '15px',
            fontWeight: '600',
            cursor: 'pointer',
            transition: 'all 0.3s ease',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            boxShadow: '0 4px 6px rgba(37,99,235,0.1)'
        });

        submitButton.onmouseenter = () => {
            submitButton.style.transform = 'translateY(-2px)';
            submitButton.style.boxShadow = '0 6px 12px rgba(37,99,235,0.2)';
        };
        submitButton.onmouseleave = () => {
            submitButton.style.transform = 'translateY(0)';
            submitButton.style.boxShadow = '0 4px 6px rgba(37,99,235,0.1)';
        };

        submitButton.onmousedown = () => {
            submitButton.style.transform = 'translateY(1px)';
        };
        submitButton.onmouseup = () => {
            submitButton.style.transform = 'translateY(-2px)';
        };

        submitButton.onclick = () => {
            let selectedTasks = Array.from(container.querySelectorAll('input[type="checkbox"]:checked'));
            if (selectedTasks.length > 0) {
                submitButton.style.opacity = '0.7';
                submitButton.innerHTML = `
                    <span style="margin-right: 8px">⏳</span>
                    正在提交...
                `;
                submitTasks(selectedTasks);
                setTimeout(() => {
                    submitButton.style.opacity = '1';
                    submitButton.innerHTML = `
                        <span style="margin-right: 8px">📤</span>
                        提交完成状态
                    `;
                }, 1000);
            } else {
                showNotification('请至少选择一个任务', { type: 'warning', keywords: ['选择', '任务'] });
            }
        };

        buttonContainer.appendChild(submitButton);
        fragment.appendChild(buttonContainer);
        container.replaceChildren(fragment);
    }

    function createTaskElement(task, resourceMap, fragment, enableCheckbox) {
        const resourceInfo = resourceMap.get(task.task_id);
        let taskItem = document.createElement('div');
        taskItem.className = 'task-item';

        Object.assign(taskItem.style, {
            marginBottom: '20px',
            padding: '20px',
            borderRadius: '16px',
            backgroundColor: enableCheckbox ? '#fff' : '#f8f9fa',
            boxShadow: '0 4px 12px rgba(0,0,0,0.03)',
            border: '1px solid rgba(238,240,242,0.8)',
            transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
            display: 'flex',
            alignItems: 'flex-start',
            cursor: enableCheckbox ? 'pointer' : 'default',
            position: 'relative',
            overflow: 'hidden'
        });

        if (enableCheckbox) {
            taskItem.style.background = `
                linear-gradient(135deg,
                    rgba(255,255,255,1) 0%,
                    rgba(250,252,255,0.95) 100%)
            `;
        }

        taskItem.onmouseenter = () => {
            if (enableCheckbox) {
                taskItem.style.transform = 'translateY(-3px) scale(1.01)';
                taskItem.style.boxShadow = '0 8px 24px rgba(0,0,0,0.06)';
                taskItem.style.borderColor = 'rgba(66,153,225,0.2)';
            }
        };

        taskItem.onmouseleave = () => {
            taskItem.style.transform = 'translateY(0) scale(1)';
            taskItem.style.boxShadow = '0 4px 12px rgba(0,0,0,0.03)';
            taskItem.style.borderColor = 'rgba(238,240,242,0.8)';
        };

        let checkboxWrapper = document.createElement('div');
        checkboxWrapper.style.marginRight = '16px';
        checkboxWrapper.style.position = 'relative';

        let checkbox = document.createElement('input');
        checkbox.className = 'task-checkbox';
        checkbox.type = 'checkbox';
        checkbox.value = task.task_id;
        checkbox.dataset.nodeId = task.node_id;
        checkbox.disabled = !enableCheckbox;

        Object.assign(checkbox.style, {
            appearance: 'none',
            '-webkit-appearance': 'none',
            width: '22px',
            height: '22px',
            border: '2px solid #e2e8f0',
            borderRadius: '6px',
            cursor: enableCheckbox ? 'pointer' : 'not-allowed',
            transition: 'all 0.2s ease',
            backgroundColor: enableCheckbox ? '#fff' : '#f0f0f0',
            position: 'relative',
            margin: '0'
        });

        checkbox.addEventListener('change', function () {
            if (this.checked) {
                this.style.backgroundColor = '#4CAF50';
                this.style.borderColor = '#4CAF50';
                this.style.backgroundImage = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z'/%3E%3C/svg%3E\")";
                this.style.backgroundSize = '16px';
                this.style.backgroundPosition = 'center';
                this.style.backgroundRepeat = 'no-repeat';
            } else {
                this.style.backgroundColor = '#fff';
                this.style.borderColor = '#e2e8f0';
                this.style.backgroundImage = 'none';
            }
        });

        let taskInfo = document.createElement('div');
        taskInfo.style.flex = '1';

        const deadlineDate = new Date(task.end_time);
        const isOverdue = deadlineDate < new Date();

        const statusStyles = {
            completed: {
                color: '#10B981',
                bg: '#ECFDF5',
                border: '#A7F3D0'
            },
            overdue: {
                color: '#EF4444',
                bg: '#FEF2F2',
                border: '#FECACA'
            },
            ongoing: {
                color: '#3B82F6',
                bg: '#EFF6FF',
                border: '#BFDBFE'
            }
        };

        const getStatusStyle = () => {
            if (task.finish === 2) return statusStyles.completed;
            return isOverdue ? statusStyles.overdue : statusStyles.ongoing;
        };

        const statusStyle = getStatusStyle();
        const status = `
            <span style="
                display: inline-flex;
                align-items: center;
                padding: 4px 8px;
                border-radius: 6px;
                font-size: 12px;
                font-weight: 600;
                color: ${statusStyle.color};
                background: ${statusStyle.bg};
                border: 1px solid ${statusStyle.border};
            ">
                ${task.finish === 2 ? '✓ 已完成' : (isOverdue ? '⚠️ 已截止' : '⏳ 进行中')}
            </span>
        `;

        const taskTypeText = {
            1: '自主观看',
            2: '作业',
            3: '课堂练习',
            4: '测验',
            5: '问卷',
            6: '讨论'
        }[task.task_type];

        taskInfo.innerHTML = `
            <div style="margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center">
            <div style="font-size: 16px; font-weight: 600; color: #1a1a1a; flex: 1">
                ${resourceInfo ? resourceInfo.name : '未知作业名称'}
            </div>
            ${status}
            </div>
            <div style="display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 8px">
            <span style="display: inline-flex; align-items: center; color: #666">
                <span style="margin-right: 6px; opacity: 0.7">📝</span>
                ${taskTypeText}
            </span>
            <span style="display: inline-flex; align-items: center; color: #666">
                <span style="margin-right: 6px; opacity: 0.7">⏰</span>
                ${new Date(task.start_time).toLocaleString('zh-CN', {
            month: 'short',
            day: 'numeric',
            hour: '2-digit',
            minute: '2-digit'
        })}
            </span>
            <span style="display: inline-flex; align-items: center; color: #666">
                <span style="margin-right: 6px; opacity: 0.7">🔚</span>
                ${new Date(task.end_time).toLocaleString('zh-CN', {
            month: 'short',
            day: 'numeric',
            hour: '2-digit',
            minute: '2-digit'
        })}
            </span>
            </div>
            ${task.finish_time ?
                `<div style="font-size: 13px; color: #10B981; margin-top: 8px; display: flex; align-items: center">
                <span style="margin-right: 6px">✅</span>
                完成于: ${new Date(task.finish_time).toLocaleString('zh-CN')}
            </div>` : ''}
            ${task.task_type === 2 || task.task_type === 3 || task.task_type === 4 || task.task_type === 5 || task.task_type === 6 ?
                `<div style="color: #EF4444; font-size: 13px; margin-top: 8px; display: flex; align-items: center">
                <span style="margin-right: 6px">⚠️</span>
                需要手动完成
            </div>` : ''}
        `;

        checkboxWrapper.appendChild(checkbox);
        taskItem.appendChild(checkboxWrapper);
        taskItem.appendChild(taskInfo);
        fragment.appendChild(taskItem);

        const deadline = new Date(task.end_time);
        const now = new Date();
        const threeDaysFromNow = new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000);

        if (deadline <= threeDaysFromNow && deadline > now && task.finish !== 2) {
            const daysLeft = Math.ceil((deadline - now) / (1000 * 60 * 60 * 24));
            taskInfo.innerHTML += `
                <div style="
                    display: flex;
                    align-items: center;
                    gap: 6px;
                    background: rgba(239,68,68,0.1);
                    color: #dc2626;
                    font-size: 13px;
                    font-weight: bold;
                    margin-top: 8px;
                    padding: 6px 10px;
                    border-radius: 6px;
                    border: 1px solid rgba(239,68,68,0.2);
                ">
                    <span style="font-size: 16px">❗</span>
                    <span>还剩 ${daysLeft} 天截止</span>
                </div>
            `;
        }
    }

    function submitTasks(selectedTasks) {
        const validTasks = selectedTasks.filter(task => task.dataset.nodeId && task.value);

        if (validTasks.length === 0) {
            showNotification('没有有效的任务可提交', { type: 'warning' });
            return;
        }

        getAuthToken().then(authToken => {
            fetchResourceList(authToken).then(resources => {
                const resourceMap = new Map();
                resources.forEach(resource => {
                    if (resource.is_task) {
                        resourceMap.set(resource.task_id, resource);
                    }
                });

                validTasks.forEach(taskElement => {
                    const GROUP_ID = getGroupIdFromUrl();
                    const taskId = taskElement.value;
                    const nodeId = taskElement.dataset.nodeId;
                    const resource = resourceMap.get(taskId);

                    if (!resource) {
                        showNotification(`未找到任务 ${taskId} 的资源信息`, {
                            type: 'warning',
                            keywords: ['未找到', '资源']
                        });
                        return;
                    }

                    const taskName = resource.name;

                    GM_xmlhttpRequest({
                        method: "POST",
                        url: `https://${domain}/api/jx-iresource/resource/finishActivity`,
                        headers: {
                            "authorization": "Bearer " + authToken,
                            "Content-Type": "application/json; charset=UTF-8"
                        },
                        data: JSON.stringify({
                            "group_id": GROUP_ID,
                            "node_id": nodeId,
                            "task_id": taskId
                        }),
                        onload: function (response) {
                            if (response.status === 200) {
                                const data = JSON.parse(response.responseText);
                                if (data.success) {
                                    showNotification(`"${taskName}" 已完成`, {
                                        type: 'success',
                                        keywords: [taskName, '完成']
                                    });

                                    setTimeout(() => {
                                        location.reload();
                                    }, 1500);

                                } else {
                                    showNotification(`"${taskName}" 完成失败:${data.message}`, {
                                        type: 'error',
                                        keywords: [taskName, '失败']
                                    });
                                }
                            } else {
                                showNotification(`"${taskName}" 提交失败`, {
                                    type: 'error',
                                    keywords: [taskName, '失败']
                                });
                            }
                        }
                    });
                });
            }).catch(error => {
                showNotification('获取资源列表失败:' + error, {
                    type: 'error',
                    keywords: ['获取', '失败']
                });
            });
        }).catch(error => {
            showNotification('获取认证令牌失败:' + error, {
                type: 'error',
                keywords: ['认证', '失败']
            });
        });
    }

    function getNotificationContainer() {
        let container = document.getElementById('notification-container');
        if (!container) {
            container = document.createElement('div');
            container.id = 'notification-container';
            container.style.position = 'fixed';
            container.style.top = '20px';
            container.style.left = '50%';
            container.style.transform = 'translateX(-50%)';
            container.style.zIndex = '10000';
            container.style.width = '400px';
            container.style.maxHeight = 'calc(100vh - 40px)';
            container.style.overflowY = 'auto';
            container.style.pointerEvents = 'none';
            container.style.display = 'flex';
            container.style.flexDirection = 'column';
            container.style.alignItems = 'center';
            document.body.appendChild(container);
        }
        return container;
    }

    function showNotification(message, options = {}) {
        const {
            type = 'info',
            duration = 3000,
            keywords = [],
        } = options;

        if (!globalThis._notificationCache) {
            globalThis._notificationCache = new Map();
        }

        const notificationKey = `${message}-${type}`;

        const existingNotification = globalThis._notificationCache.get(notificationKey);
        if (existingNotification) {
            const now = Date.now();
            if (now - existingNotification < 1500) {
                return;
            }
        }

        globalThis._notificationCache.set(notificationKey, Date.now());

        const CACHE_CLEANUP_DELAY = 10000;
        setTimeout(() => {
            globalThis._notificationCache.delete(notificationKey);
        }, CACHE_CLEANUP_DELAY);

        const highlightColors = {
            success: '#ffba08',
            error: '#14b8a6',
            warning: '#8b5cf6',
            info: '#f472b6'
        };

        const highlightColor = highlightColors[type] || highlightColors.info;

        const highlightStyle = `
            color: ${highlightColor};
            font-weight: bold;
            border-bottom: 2px solid ${highlightColor}50;
            transition: all 0.3s ease;
            border-radius: 3px;
        `;

        const highlightedMessage = keywords.reduce((msg, keyword) => {
            if (keyword && keyword.trim()) {
                const regex = new RegExp(keyword.trim(), 'g');
                return msg.replace(regex, `<span style="${highlightStyle}"
                onmouseover="this.style.backgroundColor='${highlightColor}15'; this.style.borderBottomColor='${highlightColor}'"
                onmouseout="this.style.backgroundColor='transparent'; this.style.borderBottomColor='${highlightColor}50'"
            >${keyword}</span>`);
            }
            return msg;
        }, message);

        const notification = document.createElement('div');
        notification.style.position = 'relative';
        notification.style.marginBottom = '10px';
        notification.style.padding = '15px 20px';
        notification.style.borderRadius = '12px';
        notification.style.color = '#333';
        notification.style.fontSize = '16px';
        notification.style.fontWeight = 'bold';
        notification.style.boxShadow = '0 8px 16px rgba(0,0,0,0.08), 0 4px 8px rgba(0,0,0,0.06)';
        notification.style.pointerEvents = 'auto';
        notification.style.opacity = '0';
        notification.style.transform = 'translateY(-20px)';
        notification.style.transition = 'all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55)';
        notification.style.display = 'flex';
        notification.style.alignItems = 'center';
        notification.style.backdropFilter = 'blur(8px)';

        const typeStyles = {
            success: {
                background: 'linear-gradient(145deg, rgba(104, 214, 156, 0.95), rgba(89, 186, 134, 0.95))',
                icon: '🎉'
            },
            error: {
                background: 'linear-gradient(145deg, rgba(248, 113, 113, 0.95), rgba(220, 38, 38, 0.95))',
                icon: '❌'
            },
            warning: {
                background: 'linear-gradient(145deg, rgba(251, 191, 36, 0.95), rgba(245, 158, 11, 0.95))',
                icon: '⚠️'
            },
            info: {
                background: 'linear-gradient(145deg, rgba(96, 165, 250, 0.95), rgba(59, 130, 246, 0.95))',
                icon: 'ℹ️'
            }
        };

        const currentType = typeStyles[type] || typeStyles.info;
        notification.style.background = currentType.background;
        notification.style.color = type === 'info' || type === 'success' ? '#fff' : '#000';

        const progressBar = document.createElement('div');
        progressBar.style.position = 'absolute';
        progressBar.style.bottom = '0';
        progressBar.style.left = '0';
        progressBar.style.height = '4px';
        progressBar.style.width = '100%';
        progressBar.style.background = 'rgba(255, 255, 255, 0.3)';
        progressBar.style.borderRadius = '0 0 12px 12px';
        progressBar.style.transition = `width ${duration}ms cubic-bezier(0.4, 0, 0.2, 1)`;

        const icon = document.createElement('span');
        icon.style.marginRight = '12px';
        icon.style.fontSize = '20px';
        icon.textContent = currentType.icon;
        icon.style.filter = 'saturate(1.2)';

        const messageContainer = document.createElement('div');
        messageContainer.innerHTML = highlightedMessage;
        messageContainer.style.flex = '1';
        messageContainer.style.fontWeight = 'bold';

        const closeBtn = document.createElement('span');
        closeBtn.textContent = '×';
        closeBtn.style.marginLeft = '12px';
        closeBtn.style.fontSize = '24px';
        closeBtn.style.cursor = 'pointer';
        closeBtn.style.opacity = '0.8';
        closeBtn.style.transition = 'opacity 0.2s';
        closeBtn.addEventListener('mouseover', () => {
            closeBtn.style.opacity = '1';
        });
        closeBtn.addEventListener('mouseout', () => {
            closeBtn.style.opacity = '0.8';
        });

        notification.addEventListener('mouseenter', () => {
            notification.style.transform = 'translateY(0) scale(1.02)';
            progressBar.style.transition = 'none';
        });

        notification.addEventListener('mouseleave', () => {
            notification.style.transform = 'translateY(0) scale(1)';
            progressBar.style.transition = `width ${duration}ms linear`;
        });

        notification.appendChild(icon);
        notification.appendChild(messageContainer);
        notification.appendChild(closeBtn);
        notification.appendChild(progressBar);

        const container = getNotificationContainer();

        container.appendChild(notification);

        requestAnimationFrame(() => {
            notification.style.opacity = '1';
            notification.style.transform = 'translateY(0)';
            requestAnimationFrame(() => {
                progressBar.style.width = '0';
            });
        });

        function hideNotification(notification) {
            notification.style.opacity = '0';
            notification.style.transform = 'translateY(-20px)';
            setTimeout(() => {
                container.removeChild(notification);
                if (container.children.length === 0) {
                    document.body.removeChild(container);
                }
            }, 300);
        }

        closeBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            hideNotification(notification);
        });

        notification.addEventListener('click', () => {
            hideNotification(notification);
        });

        if (duration > 0) {
            setTimeout(() => {
                if (container.contains(notification)) {
                    hideNotification(notification);
                }
            }, duration);
        }
    }

    class LearnRecorder {
        constructor() {
            this.apiUrl = `https://${domain}/api/jx-iresource/learnLength/learnRecord`;
            this.interval = 30000;
            this.timer = null;
            this.recordCount = 0;
            this.lastRecordTime = null;
            this.totalTime = 0;
            this.isFirstRecord = true;
            this.realTimer = null;
            this.realTimeSeconds = 0;
            this.destroy = () => {
                this.stop();
                if (this.timer) {
                    clearInterval(this.timer);
                    this.timer = null;
                }
                if (this.realTimer) {
                    clearInterval(this.realTimer);
                    this.realTimer = null;
                }
            };

            setTimeout(() => {
                this.updateStatus('info', '记录器已就绪,点击开始记录按钮开始记录学习时长');
            }, 500);
        }

        async createSignature(message) {
            const encoded = encodeURIComponent(message);
            const msgBuffer = new TextEncoder().encode(encoded);
            const hashBuffer = await crypto.subtle.digest('SHA-1', msgBuffer);
            const hashArray = Array.from(new Uint8Array(hashBuffer));
            return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
        }

        async sendRecord() {
            try {
                const params = await this.getCurrentPageParams();
                if (!params || !params.userId || !params.groupId || !params.resourceId) {
                    console.log('缺少必要参数:', params);
                    this.updateStatus('error', '记录失败:缺少必要参数');
                    return;
                }

                const message = JSON.stringify({
                    user_id: params.userId,
                    group_id: params.groupId,
                    clientType: 1,
                    roleType: 1,
                    resourceId: params.resourceId
                });

                const token = getCookie('prd-access-token');

                if (!token) {
                    console.error('未找到认证令牌');
                    return;
                }

                const body = {
                    message: message,
                    signature: await this.createSignature(message),
                    timestamp: Date.now().toString(),
                    nonce: crypto.randomUUID()
                };

                const response = await fetch(this.apiUrl, {
                    method: 'POST',
                    headers: {
                        'authorization': `Bearer ${token}`,
                        'content-type': 'application/json; charset=UTF-8'
                    },
                    body: JSON.stringify(body)
                });

                const result = await response.json();
                if (result.code === 200 || result.success) {
                    this.recordCount++;
                    this.lastRecordTime = new Date();
                    if (!this.isFirstRecord) {
                        this.totalTime += 30;
                    }
                    this.isFirstRecord = false;

                    this.updateStatus('success', '记录成功');
                } else {
                    this.updateStatus('error', '记录失败:' + (result.message || '未知错误'));
                }
            } catch (error) {
                console.error('记录失败:', error);
                this.updateStatus('error', '记录失败:' + (error.message || '未知错误'));

                this.failureCount = (this.failureCount || 0) + 1;
                if (this.failureCount >= 3) {
                    this.stop();
                    this.updateStatus('error', '由于连续记录失败,已自动停止记录');
                }
            }
        }

        async getCurrentPageParams() {
            const userId = await getUserInfo();
            return {
                userId: userId,
                groupId: getGroupIdFromUrl(),
                resourceId: getResourceIdFromUrl()
            };
        }

        updateStatus(type, message) {
            const event = new CustomEvent('recordStatus', {
                detail: {
                    type,
                    message,
                    count: this.recordCount,
                    time: this.lastRecordTime,
                    totalTime: this.totalTime,
                    realTimeSeconds: this.realTimeSeconds
                }
            });
            document.dispatchEvent(event);
        }

        reset() {
            this.recordCount = 0;
            this.lastRecordTime = null;
            this.totalTime = 0;
            this.isFirstRecord = true;
            this.realTimeSeconds = 0;
            this.updateStatus('info', '记录已重置');
        }

        startRealTimer() {
            if (this.realTimer) return;
            this.realTimer = setInterval(() => {
                this.realTimeSeconds++;
                const event = new CustomEvent('timeUpdate', {
                    detail: {
                        realTimeSeconds: this.realTimeSeconds
                    }
                });
                document.dispatchEvent(event);
            }, 1000);
        }

        stopRealTimer() {
            if (this.realTimer) {
                clearInterval(this.realTimer);
                this.realTimer = null;
            }
            this.realTimeSeconds = 0;
        }

        async start() {
            try {
                const params = await this.getCurrentPageParams();
                if (!params || !params.userId || !params.groupId || !params.resourceId) {
                    this.updateStatus('error', '无法开始记录:缺少必要参数');
                    return false;
                }

                const token = getCookie('prd-access-token');
                if (!token) {
                    this.updateStatus('error', '无法开始记录:未找到访问令牌');
                    return false;
                }

                if (this.timer) return true;
                await this.sendRecord();
                this.timer = setInterval(() => this.sendRecord(), this.interval);
                this.startRealTimer();
                console.log('开始记录学习时长');
                return true;
            } catch (error) {
                this.updateStatus('error', '启动记录器失败:' + error.message);
                return false;
            }
        }

        stop() {
            if (this.timer) {
                clearInterval(this.timer);
                this.timer = null;
                this.stopRealTimer();
                this.reset();
                console.log('停止记录学习时长');
            }
        }
    }

    function createRecorderComponent() {
        const recorderSection = document.createElement('div');
        const recorder = xiaoYaRecorder;

        if (!recorder) {
            console.error('未找到全局记录器实例');
            return recorderSection;
        }

        recorderSection.innerHTML = `
            <div style="
                margin: 32px 0;
                padding: 20px;
                background: linear-gradient(145deg, rgba(249,250,251,0.97), rgba(243,244,246,0.97));
                border-radius: 20px;
                border: 1px solid rgba(59,130,246,0.1);
                box-shadow: 0 4px 16px rgba(0,0,0,0.03);
                backdrop-filter: blur(8px);
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                position: relative;
                overflow: hidden;
            ">
                <div class="pulse-bg" style="
                    position: absolute;
                    top: 0;
                    left: 0;
                    right: 0;
                    bottom: 0;
                    background: radial-gradient(circle at center, rgba(59,130,246,0.1) 0%, transparent 70%);
                    opacity: 0;
                    transition: opacity 0.5s ease;
                    pointer-events: none;
                "></div>

                <div style="
                    display: flex;
                    height: 60px;
                    align-items: center;
                    justify-content: space-between;
                    position: relative;
                ">
                    <div style="
                        display: flex;
                        align-items: center;
                        gap: 16px;
                    ">
                        <div class="timer-icon" style="
                            display: flex;
                            align-items: center;
                            justify-content: center;
                            width: 40px;
                            height: 40px;
                            background: linear-gradient(145deg, #3B82F6, #2563EB);
                            border-radius: 16px;
                            color: white;
                            font-size: 24px;
                            transform-origin: center;
                            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                            box-shadow: 0 4px 12px rgba(37,99,235,0.2);
                        ">⏱️</div>
                        <div style="flex: 1;">
                            <div style="
                                font-weight: 600;
                                color: #2563eb;
                                margin-bottom: 6px;
                                font-size: 16px;
                                display: flex;
                                align-items: center;
                                gap: 8px;
                            ">
                                学习时长记录
                                <span class="record-status" style="
                                    font-size: 12px;
                                    padding: 3px 5px;
                                    background: linear-gradient(145deg, rgba(59,130,246,0.1), rgba(37,99,235,0.1));
                                    border-radius: 12px;
                                    color: #2563eb;
                                    display: none;
                                ">记录中</span>
                            </div>
                            <div style="
                                color: #6b7280;
                                font-size: 13px;
                                line-height: 1.5;
                                display: flex;
                                align-items: center;
                                gap: 4px;
                            ">
                                <span class="dot-pulse" style="
                                    width: 6px;
                                    height: 6px;
                                    border-radius: 50%;
                                    background: #D1D5DB;
                                    display: inline-block;
                                    margin-right: 2px;
                                "></span>
                                每30秒自动记录一次
                            </div>
                        </div>
                    </div>
                    <div style="display: flex; gap: 12px;">
                        <button id="start-record" class="record-btn" style="
                            padding: 10px 20px;
                            border: none;
                            border-radius: 12px;
                            background: linear-gradient(145deg, #3B82F6, #2563EB);
                            color: white;
                            cursor: pointer;
                            font-size: 14px;
                            font-weight: 600;
                            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                            display: flex;
                            align-items: center;
                            gap: 8px;
                            box-shadow: 0 4px 12px rgba(37,99,235,0.2);
                        ">
                            <span class="btn-icon">▶</span>
                            开始记录
                        </button>
                        <button id="stop-record" class="record-btn" style="
                            padding: 10px 20px;
                            border: none;
                            border-radius: 12px;
                            background: linear-gradient(145deg, #EF4444, #DC2626);
                            color: white;
                            cursor: pointer;
                            font-size: 14px;
                            font-weight: 600;
                            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                            display: flex;
                            align-items: center;
                            gap: 8px;
                            box-shadow: 0 4px 12px rgba(239,68,68,0.2);
                            opacity: 0.5;
                        " disabled>
                            <span class="btn-icon">■</span>
                            停止记录
                        </button>
                    </div>
                </div>
                <div class="status-info" style="
                    margin-top: 12px;
                    padding: 16px;
                    border-radius: 12px;
                    background: linear-gradient(145deg, rgba(59,130,246,0.05), rgba(37,99,235,0.05));
                    font-size: 13px;
                    color: #4B5563;
                    display: flex;
                    flex-wrap: wrap;
                    gap: 12px;
                    position: relative;
                    overflow: hidden;
                ">
                    <div class="record-count" style="
                        flex: 1;
                        min-width: 140px;
                        display: flex;
                        align-items: center;
                        gap: 8px;
                    ">
                        <div style="
                            background: linear-gradient(145deg, #3B82F6, #2563EB);
                            color: white;
                            width: 24px;
                            height: 24px;
                            border-radius: 8px;
                            display: flex;
                            align-items: center;
                            justify-content: center;
                            font-size: 12px;
                        ">📊</div>
                        <div>
                            <div style="color: #6B7280; margin-bottom: 2px;">请求次数</div>
                            <div style="
                                font-size: 18px;
                                font-weight: 600;
                                color: #2563EB;
                                transition: all 0.3s ease;
                            "><span>0</span> 次</div>
                        </div>
                    </div>

                    <div class="last-record-time" style="
                        flex: 2;
                        min-width: 200px;
                        display: flex;
                        align-items: center;
                        gap: 8px;
                    ">
                        <div style="
                            background: linear-gradient(145deg, #3B82F6, #2563EB);
                            color: white;
                            width: 24px;
                            height: 24px;
                            border-radius: 8px;
                            display: flex;
                            align-items: center;
                            justify-content: center;
                            font-size: 12px;
                        ">🕒</div>
                        <div>
                            <div style="color: #6B7280; margin-bottom: 2px;">上次记录时间</div>
                            <div style="
                                font-size: 15px;
                                font-weight: 500;
                                color: #1F2937;
                                transition: all 0.3s ease;
                            "><span>暂无记录</span></div>
                        </div>
                    </div>

                    <div class="record-status-message" style="
                        width: 100%;
                        padding: 8px 12px;
                        margin-top: 4px;
                        border-radius: 8px;
                        background: rgba(59,130,246,0.05);
                        color: #3B82F6;
                        font-weight: 500;
                        transition: all 0.3s ease;
                        opacity: 0;
                        transform: translateY(10px);
                    "></div>

                    <div class="total-study-time" style="
                        flex: 2;
                        min-width: 200px;
                        display: flex;
                        align-items: center;
                        gap: 8px;
                        position: relative;
                    ">
                        <div style="
                            background: linear-gradient(145deg, #3B82F6, #2563EB);
                            color: white;
                            width: 24px;
                            height: 24px;
                            border-radius: 8px;
                            display: flex;
                            align-items: center;
                            justify-content: center;
                            font-size: 12px;
                        ">📚</div>
                        <div style="flex: 1;">
                            <div style="color: #6B7280; margin-bottom: 2px;">有效时长</div>
                            <div style="
                                font-size: 18px;
                                font-weight: 600;
                                color: #2563EB;
                                transition: all 0.3s ease;
                                font-variant-numeric: tabular-nums;
                            "><span>0:00</span></div>
                        </div>
                            <div class="real-time" style="
                            flex: 2;
                            min-width: 200px;
                            display: flex;
                            align-items: center;
                            gap: 8px;
                            position: relative;
                        ">
                            <div style="
                                background: linear-gradient(145deg, #3B82F6, #2563EB);
                                color: white;
                                width: 24px;
                                height: 24px;
                                border-radius: 8px;
                                display: flex;
                                align-items: center;
                                justify-content: center;
                                font-size: 12px;
                            ">⏱️</div>
                            <div style="flex: 1;">
                                <div style="color: #6B7280; margin-bottom: 2px;">实时计时</div>
                                <div style="
                                    font-size: 18px;
                                    font-weight: 600;
                                    color: #2563EB;
                                    transition: all 0.3s ease;
                                    font-variant-numeric: tabular-nums;
                                "><span>0:00:00</span></div>
                            </div>
                        </div>
                        <div class="progress-container" style="
                            width: 120px;
                            height: 8px;
                            background: rgba(59,130,246,0.1);
                            border-radius: 4px;
                            overflow: hidden;
                            margin-left: auto;
                            align-self: center;
                            position: relative;
                            box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
                        ">
                            <div class="progress-bar"></div>
                        </div>
                    </div>
                </div>
            </div>

            <style>
                @keyframes pulse {
                    0% { transform: scale(1); }
                    50% { transform: scale(1.1); }
                    100% { transform: scale(1); }
                }

                @keyframes rotate {
                    from { transform: rotate(0deg); }
                    to { transform: rotate(360deg); }
                }

                @keyframes dot-pulse {
                    0% { transform: scale(1); opacity: 1; }
                    50% { transform: scale(1.5); opacity: 0.5; }
                    100% { transform: scale(1); opacity: 1; }
                }

                @keyframes pulse-bg {
                    0% { transform: translate(-50%, -50%) scale(1); opacity: 0.5; }
                    100% { transform: translate(-50%, -50%) scale(1.5); opacity: 0; }
                }

                @keyframes countChange {
                    0% { transform: scale(1); }
                    50% { transform: scale(1.2); }
                    100% { transform: scale(1); }
                }

                @keyframes messageSlideIn {
                    from {
                        opacity: 0;
                        transform: translateY(10px);
                    }
                    to {
                        opacity: 1;
                        transform: translateY(0);
                    }
                }

                @keyframes timeUpdate {
                    0% {
                        transform: translateY(0);
                        opacity: 1;
                    }
                    50% {
                        transform: translateY(-10px);
                        opacity: 0;
                    }
                    51% {
                        transform: translateY(10px);
                        opacity: 0;
                    }
                    100% {
                        transform: translateY(0);
                        opacity: 1;
                    }
                }

                @keyframes progress {
                    from {
                        width: 0%;
                    }
                    to {
                        width: 100%;
                    }
                }

                @keyframes progress-glow {
                    0% {
                        background-position: 0% 50%;
                        filter: brightness(1);
                    }
                    50% {
                        background-position: 100% 50%;
                        filter: brightness(1.2);
                    }
                    100% {
                        background-position: 0% 50%;
                        filter: brightness(1);
                    }
                }

                @keyframes progress-shine {
                    0% {
                        transform: translateX(-100%) skewX(-15deg);
                        opacity: 0;
                    }
                    50% {
                        opacity: 0.3;
                    }
                    100% {
                        transform: translateX(200%) skewX(-15deg);
                        opacity: 0;
                    }
                }

                .record-btn:not(:disabled):hover {
                    transform: translateY(-2px);
                    box-shadow: 0 6px 16px rgba(37,99,235,0.3);
                }

                .record-btn:not(:disabled):active {
                    transform: translateY(1px);
                }

                .recording .timer-icon {
                    animation: pulse 2s infinite ease-in-out;
                }

                .recording .dot-pulse {
                    animation: dot-pulse 2s infinite ease-in-out;
                    background-color: #3B82F6 !important;
                    transition: background-color 0.3s ease;
                }

                .recording .pulse-bg {
                    opacity: 1;
                }

                .pulse-bg::after {
                    content: '';
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    width: 100%;
                    height: 100%;
                    background: inherit;
                    border-radius: inherit;
                    animation: pulse-bg 2s infinite;
                    transform-origin: center;
                    pointer-events: none;
                    will-change: transform;
                }

                .status-info .record-count span {
                    display: inline-block;
                }

                .count-update {
                    animation: countChange 0.3s ease-out;
                }

                .message-show {
                    animation: messageSlideIn 0.3s ease-out forwards;
                }

                .time-update {
                    animation: timeUpdate 0.5s ease-out;
                }

                .progress-bar {
                    height: 100%;
                    background: linear-gradient(
                        90deg,
                        #3B82F6,
                        #2563EB,
                        #4F46E5,
                        #2563EB,
                        #3B82F6
                    );
                    background-size: 200% auto;
                    transform-origin: left;
                    border-radius: 4px;
                    position: relative;
                }

                .progress-bar::after {
                    content: '';
                    position: absolute;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    background: linear-gradient(
                        90deg,
                        transparent,
                        rgba(255,255,255,0.4),
                        transparent
                    );
                    transform: translateX(-100%) skewX(-15deg);
                }

                .recording .progress-bar {
                    animation:
                        progress 30s linear infinite,
                        progress-glow 2s ease-in-out infinite;
                }

                .recording .progress-bar::after {
                    animation: progress-shine 3s ease-in-out infinite;
                }

                .progress-container::before {
                    content: '';
                    position: absolute;
                    top: 0;
                    left: 0;
                    right: 0;
                    height: 1px;
                    background: linear-gradient(
                        90deg,
                        transparent,
                        rgba(255,255,255,0.3),
                        transparent
                    );
                }
            </style>
        `;

        const container = recorderSection.querySelector('div');
        const timerIcon = recorderSection.querySelector('.timer-icon');
        const recordStatus = recorderSection.querySelector('.record-status');
        const startBtn = recorderSection.querySelector('#start-record');
        const stopBtn = recorderSection.querySelector('#stop-record');

        startBtn.onclick = async () => {
            const success = await recorder.start();
            if (!success) {
                return;
            }

            startBtn.style.opacity = '0.5';
            startBtn.disabled = true;
            stopBtn.style.opacity = '1';
            stopBtn.disabled = false;
            container.classList.add('recording');
            recordStatus.style.display = 'inline-block';

            timerIcon.style.transform = 'rotate(360deg)';
            setTimeout(() => {
                timerIcon.style.transition = 'transform 1s linear';
                timerIcon.style.transform = 'rotate(0deg)';
            }, 300);
        };

        stopBtn.onclick = () => {
            recorder.stop();
            startBtn.style.opacity = '1';
            startBtn.disabled = false;
            stopBtn.style.opacity = '0.5';
            stopBtn.disabled = true;
            container.classList.remove('recording');
            recordStatus.style.display = 'none';

            const statusInfo = recorderSection.querySelector('.status-info');
            const countElement = statusInfo.querySelector('.record-count span');
            const timeElement = statusInfo.querySelector('.last-record-time span');

            countElement.textContent = '0';
            timeElement.textContent = '暂无记录';

            timerIcon.style.transform = 'scale(0.8)';
            setTimeout(() => {
                timerIcon.style.transform = 'scale(1)';
            }, 200);
        };

        function formatTime(seconds) {
            const hours = Math.floor(seconds / 3600);
            const minutes = Math.floor((seconds % 3600) / 60);
            const remainingSeconds = seconds % 60;
            return `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
        }

        document.addEventListener('timeUpdate', (e) => {
            const { realTimeSeconds } = e.detail;
            const realTimeElement = recorderSection.querySelector('.real-time span');
            realTimeElement.textContent = formatTime(realTimeSeconds);
        });

        document.addEventListener('recordStatus', (e) => {
            const { type, message, count, time, totalTime, realTimeSeconds } = e.detail;
            const statusInfo = recorderSection.querySelector('.status-info');
            const countElement = statusInfo.querySelector('.record-count span');
            const timeElement = statusInfo.querySelector('.last-record-time span');
            const messageElement = statusInfo.querySelector('.record-status-message');
            const timeStudiedElement = statusInfo.querySelector('.total-study-time span');
            const realTimeElement = statusInfo.querySelector('.real-time span');
            timeStudiedElement.textContent = formatTime(totalTime);
            realTimeElement.textContent = formatTime(realTimeSeconds);

            if (type === 'success') {
                const progressBar = statusInfo.querySelector('.progress-bar');
                progressBar.style.animation = 'none';
                void progressBar.offsetWidth;
                progressBar.style.animation = null;
            }

            countElement.classList.remove('count-update');
            void countElement.offsetWidth;
            countElement.classList.add('count-update');
            countElement.textContent = count;

            timeElement.textContent = time ? time.toLocaleString() : '暂无记录';

            messageElement.textContent = message;
            messageElement.style.color = (() => {
                switch (type) {
                    case 'success':
                        return '#10B981';
                    case 'error':
                        return '#EF4444';
                    case 'info':
                    default:
                        return '#3B82F6';
                }
            })();

            messageElement.style.background = (() => {
                switch (type) {
                    case 'success':
                        return 'rgba(16,185,129,0.05)';
                    case 'error':
                        return 'rgba(239,68,68,0.05)';
                    case 'info':
                    default:
                        return 'rgba(59,130,246,0.05)';
                }
            })();

            messageElement.classList.remove('message-show');
            void messageElement.offsetWidth;
            messageElement.classList.add('message-show');
        });

        return recorderSection;
    }

    function initTaskList() {
        let container = document.getElementById('task-container');
        let controller = document.getElementById('task-controller');

        if (xiaoYaRecorder) {
            xiaoYaRecorder.stop();
            xiaoYaRecorder = null;
        }

        xiaoYaRecorder = new LearnRecorder();

        if (!controller) {
            controller = document.createElement('div');
            controller.id = 'task-controller';
            Object.assign(controller.style, {
                position: 'fixed',
                right: '20px',
                top: '10%',
                width: '48px',
                height: '48px',
                borderRadius: '50%',
                background: 'linear-gradient(145deg, #3B82F6, #2563EB)',
                boxShadow: '0 4px 12px rgba(37,99,235,0.2)',
                cursor: 'pointer',
                zIndex: '1001',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
                transform: 'scale(0)',
            });

            controller.innerHTML = `
                <div class="controller-icon" style="
                    width: 24px;
                    height: 24px;
                    position: relative;
                    transition: transform 0.3s ease;
                ">
                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="white">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
                    </svg>
                </div>
            `;

            document.body.appendChild(controller);
            requestAnimationFrame(() => controller.style.transform = 'scale(1)');
        }

        if (!container) {
            container = document.createElement('div');
            container.id = 'task-container';
            controller.title = '展开任务列表';

            Object.assign(container.style, {
                position: 'fixed',
                top: '10%',
                right: '20px',
                backgroundColor: 'rgba(255, 255, 255, 0.95)',
                borderRadius: '16px',
                padding: '20px',
                zIndex: '1000',
                maxHeight: '80vh',
                width: '600px',
                overflowY: 'auto',
                scrollbarGutter: 'stable',
                boxShadow: '0 10px 30px rgba(0,0,0,0.1)',
                transition: 'all 0.3s ease',
                transform: 'translateX(450px)',
                opacity: '0',
                backdropFilter: 'blur(10px)',
                border: '1px solid rgba(255,255,255,0.1)',
                pointerEvents: 'none'
            });

            container.innerHTML = `
                <div class="empty-container" style="
                    display: flex;
                    flex-direction: column;
                    align-items: center;
                    justify-content: center;
                    min-height: 200px;
                    padding: 20px;
                ">
                    <div class="empty-state" style="
                        display: flex;
                        flex-direction: column;
                        align-items: center;
                        transform-origin: center;
                    ">
                        <svg width="120" height="120" viewBox="0 0 24 24" style="margin-bottom: 20px;">
                            <path fill="#93c5fd" d="M12 3L1 9l4 2.18v6L12 21l7-3.82v-6l2-1.09V17h2V9L12 3zm6.82 6L12 12.72 5.18 9 12 5.28 18.82 9zM17 15.99l-5 2.73-5-2.73v-3.72L12 15l5-2.73v3.72z"/>
                        </svg>
                        <div style="
                            font-size: 18px;
                            font-weight: 600;
                            color: #3b82f6;
                            text-align: center;
                            margin-bottom: 8px;
                        ">
                            当前页面暂无作业
                        </div>
                        <div style="
                            font-size: 14px;
                            font-weight: bold;
                            color: #6b7280;
                            text-align: center;
                        ">
                            休息一下,马上回来 ☕
                        </div>
                    </div>
                </div>
                <style>
                    @keyframes float {
                        0% { transform: translateY(0px); }
                        50% { transform: translateY(-10px); }
                        100% { transform: translateY(0px); }
                    }
                    .empty-state {
                        animation: float 3s ease-in-out infinite;
                    }
                </style>
            `;
            document.body.appendChild(container);
        }

        let isExpanded = false;
        controller.onclick = () => {
            isExpanded = !isExpanded;

            controller.style.transform = isExpanded ? 'scale(0.9)' : 'scale(1)';
            controller.querySelector('.controller-icon').style.transform = isExpanded ? 'rotate(-180deg)' : 'rotate(0)';

            if (isExpanded) {
                container.style.transform = 'translateX(0) scale(1)';
                container.style.opacity = '1';
                container.style.pointerEvents = 'auto';

                container.style.animation = 'expandIn 0.5s cubic-bezier(0.4, 0, 0.2, 1)';
                controller.style.right = '640px';
                controller.title = '收起任务列表';
            } else {
                container.style.transform = 'translateX(450px) scale(0.9)';
                container.style.opacity = '0';
                container.style.pointerEvents = 'none';

                container.style.animation = 'expandOut 0.5s cubic-bezier(0.4, 0, 0.2, 1)';
                controller.style.right = '20px';
                controller.title = '展开任务列表';
            }
        };

        controller.onmouseenter = () => {
            controller.style.transform = 'scale(1.1)';
            controller.style.boxShadow = '0 6px 16px rgba(37,99,235,0.3)';
        };
        controller.onmouseleave = () => {
            controller.style.transform = isExpanded ? 'scale(0.9)' : 'scale(1)';
            controller.style.boxShadow = '0 4px 12px rgba(37,99,235,0.2)';
        };

        const GROUP_ID = getGroupIdFromUrl();
        if (!GROUP_ID) {
            console.log('不在课程页面,跳过初始化');
            return;
        }

        const recorderComponent = createRecorderComponent();
        container.appendChild(recorderComponent);

        getAuthToken().then(authToken => {
            Promise.all([
                fetchTaskList(authToken),
                fetchResourceList(authToken)
            ]).then(([tasks, resources]) => {
                console.log('任务数量:', tasks.length);
                if (Array.isArray(tasks) && tasks.length > 0) {
                    showTaskList(container, tasks, resources);
                }
            }).catch(error => {
                showNotification('获取数据失败:' + error, {
                    type: 'error',
                    keywords: ['获取', '失败']
                });
            });
        }).catch(error => {
            showNotification('无法获取token,请确保已登录并且cookie中包含prd-access-token', {
                type: 'error',
                keywords: ['token', '登录', 'cookie']
            });
        });

        window.learnRecorder = xiaoYaRecorder;
    }

    function onUrlChange() {
        if (location.href.includes('mycourse')) {
            const now = Date.now();
            if (now - (window.lastUrlChange || 0) < 500) {
                return;
            }
            window.lastUrlChange = now;

            if (xiaoYaRecorder) {
                xiaoYaRecorder.stop();
                xiaoYaRecorder = null;
            }

            const oldContainer = document.getElementById('task-container');
            const oldController = document.getElementById('task-controller');
            if (oldContainer) oldContainer.remove();
            if (oldController) oldController.remove();

            initTaskList();
        }
    }

    (function (history) {
        var pushState = history.pushState;
        history.pushState = function () {
            var ret = pushState.apply(history, arguments);
            onUrlChange();
            return ret;
        };
    })(window.history);

    (function (history) {
        var replaceState = history.replaceState;
        history.replaceState = function () {
            var ret = replaceState.apply(history, arguments);
            onUrlChange();
            return ret;
        };
    })(window.history);

    window.addEventListener('popstate', onUrlChange);
    window.addEventListener('hashchange', onUrlChange);

    onUrlChange();
})();