Enhanced GPA Calculator

现代化设计的GPA计算器,支持全屏自由拖动、手动计算、课程筛选和导出功能

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Enhanced GPA Calculator
// @namespace    http://tampermonkey.net/
// @version      2.2.0
// @description  现代化设计的GPA计算器,支持全屏自由拖动、手动计算、课程筛选和导出功能
// @author       Toony
// @match        https://jw.ahu.edu.cn/student/home
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // 配置选项
    const CONFIG = {
        defaultExcludedCourses: ['GG18002', 'GG82001'],
        animationDuration: 300,
        storageKeys: {
            position: 'gpaCalculatorPosition',
            darkMode: 'gpaCalculatorDarkMode',
            excludedCourses: 'gpaCalculatorExcludedCourses',
            history: 'gpaCalculatorHistory'
        }
    };

    // 缓存DOM元素的引用
    const DOM = {
        container: null,
        gpaValue: null,
        creditsValue: null,
        pointsValue: null,
        excludedCoursesList: null,
        historyList: null,
        manualCredits: null,
        manualPoints: null,
        manualGpaResult: null
    };

    // 状态管理
    const state = {
        isDragging: false,
        currentX: 0,
        currentY: 0,
        initialX: 0,
        initialY: 0,
        xOffset: 0,
        yOffset: 0,
        isDarkMode: false,
        excludedCourses: [...CONFIG.defaultExcludedCourses],
        calculationHistory: [],
        currentTab: 'stats', // 'stats', 'courses', 'history', 'manual', 'export'
        lastCalculatedGPA: null,
        courseCategories: {}, // 用于存储课程分类统计
        courseGrades: [] // 用于存储所有课程成绩
    };

    /**
     * 计算GPA的核心功能
     * @param {Document} doc - 包含成绩表的文档对象
     * @returns {Object|null} - 计算结果或null
     */
    function calculateGPA(doc) {
        try {
            const tables = doc.querySelectorAll('.student-grade-table');
            if (tables.length === 0) return null;

            let totalGPA = 0;
            let totalCredits = 0;
            let totalGradePoints = 0;
            let courseDetails = [];
            state.courseCategories = {}; // 重置分类统计
            state.courseGrades = []; // 重置成绩数组

            tables.forEach(table => {
                const rows = table.querySelectorAll('tbody tr');
                rows.forEach(row => {
                    try {
                        const courseCodeElement = row.querySelector('td div.text-color-6.one-line-nowarp span[data-original-title="课程代码"]');
                        let courseCode = courseCodeElement ? courseCodeElement.textContent.trim() : '';

                        // 检查课程是否被排除
                        if (state.excludedCourses.includes(courseCode)) return;

                        const cells = row.cells;
                        if (cells.length < 3) return;

                        // 获取课程名称 - 修复选择器,课程名称在div.course-name中
                        const courseNameElement = row.querySelector('td div.course-name');
                        let courseName = courseNameElement ? courseNameElement.textContent.trim() : '未知课程';

                        // 获取课程分类
                        const courseCategoryElement = row.querySelector('td div.text-color-6.one-line-nowarp span[data-original-title="课程分类"]');
                        let courseCategory = courseCategoryElement ? courseCategoryElement.textContent.trim() : '未知分类';

                        const creditsCell = cells[1];
                        const gpaCell = cells[2];
                        const gradeCell = cells[3];

                        if (creditsCell && gpaCell) {
                            const credits = parseFloat(creditsCell.textContent.trim());
                            const gpa = parseFloat(gpaCell.textContent.trim());
                            const grade = gradeCell ? gradeCell.textContent.trim() : '';

                            if (!isNaN(credits) && !isNaN(gpa)) {
                                totalGPA += credits * gpa;
                                totalCredits += credits;
                                totalGradePoints += credits * gpa;

                                // 更新分类统计
                                if (!state.courseCategories[courseCategory]) {
                                    state.courseCategories[courseCategory] = {
                                        totalCredits: 0,
                                        totalPoints: 0,
                                        count: 0
                                    };
                                }
                                state.courseCategories[courseCategory].totalCredits += credits;
                                state.courseCategories[courseCategory].totalPoints += credits * gpa;
                                state.courseCategories[courseCategory].count += 1;

                                // 保存成绩分布
                                state.courseGrades.push({
                                    gpa: gpa,
                                    credits: credits
                                });

                                // 保存课程详情
                                courseDetails.push({
                                    code: courseCode,
                                    name: courseName,
                                    category: courseCategory,
                                    credits: credits,
                                    gpa: gpa,
                                    grade: grade,
                                    points: credits * gpa
                                });
                            }
                        }
                    } catch (rowError) {
                        console.error('处理课程行时出错:', rowError);
                    }
                });
            });

            if (totalCredits === 0) return null;

            // 排序课程详情
            courseDetails.sort((a, b) => b.gpa - a.gpa);

            const result = {
                gpa: totalGPA / totalCredits,
                totalCredits: totalCredits,
                totalGradePoints: totalGradePoints,
                courses: courseDetails,
                categories: state.courseCategories,
                timestamp: new Date().toISOString()
            };

            // 保存到状态
            state.lastCalculatedGPA = result;

            // 保存到历史记录
            saveToHistory(result);

            return result;
        } catch (error) {
            console.error('计算GPA时出错:', error);
            return null;
        }
    }

    /**
     * 手动计算GPA
     * @param {number} credits - 总学分
     * @param {number} points - 总绩点
     * @returns {number} - 计算得到的GPA
     */
    function calculateManualGPA(credits, points) {
        if (credits <= 0) return 0;
        return points / credits;
    }

    /**
     * 保存计算结果到历史记录
     * @param {Object} result - GPA计算结果
     */
    function saveToHistory(result) {
        // 只保存主要数据到历史记录
        const historyEntry = {
            gpa: result.gpa,
            totalCredits: result.totalCredits,
            totalGradePoints: result.totalGradePoints,
            timestamp: result.timestamp,
            excludedCourses: [...state.excludedCourses]
        };

        // 限制历史记录最多保存10条
        state.calculationHistory.unshift(historyEntry);
        if (state.calculationHistory.length > 10) {
            state.calculationHistory.pop();
        }

        // 保存到存储
        GM_setValue(CONFIG.storageKeys.history, JSON.stringify(state.calculationHistory));
    }

    /**
     * 从存储加载数据
     */
    function loadSavedData() {
        try {
            // 加载位置
            const savedPosition = GM_getValue(CONFIG.storageKeys.position);
            if (savedPosition) {
                const position = JSON.parse(savedPosition);
                state.xOffset = position.x || 0;
                state.yOffset = position.y || 0;
            }

            // 加载主题
            const savedDarkMode = GM_getValue(CONFIG.storageKeys.darkMode);
            if (savedDarkMode !== undefined) {
                state.isDarkMode = savedDarkMode === 'true';
            }

            // 加载排除课程
            const savedExcludedCourses = GM_getValue(CONFIG.storageKeys.excludedCourses);
            if (savedExcludedCourses) {
                state.excludedCourses = JSON.parse(savedExcludedCourses);
            }

            // 加载历史记录
            const savedHistory = GM_getValue(CONFIG.storageKeys.history);
            if (savedHistory) {
                state.calculationHistory = JSON.parse(savedHistory);
            }
        } catch (error) {
            console.error('加载保存的数据时出错:', error);
            // 出错时使用默认值
        }
    }

    /**
     * 保存位置信息
     */
    function savePosition() {
        const position = { x: state.xOffset, y: state.yOffset };
        GM_setValue(CONFIG.storageKeys.position, JSON.stringify(position));
    }

    /**
     * 保存深色模式设置
     */
    function saveDarkMode() {
        GM_setValue(CONFIG.storageKeys.darkMode, state.isDarkMode.toString());
    }

    /**
     * 保存排除课程列表
     */
    function saveExcludedCourses() {
        GM_setValue(CONFIG.storageKeys.excludedCourses, JSON.stringify(state.excludedCourses));
    }

    /**
     * 创建样式
     */
    function injectStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .gpa-calculator {
                position: fixed;
                top: 0;
                left: 0;
                width: 320px;
                background: linear-gradient(145deg, #ffffff, #f5f5f5);
                border-radius: 20px;
                padding: 20px;
                box-shadow: 0 10px 20px rgba(0,0,0,0.1);
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
                transition: all 0.3s ease, transform 0.1s ease;
                z-index: 9999;
                max-height: 80vh;
                display: flex;
                flex-direction: column;
                transform: translate(20px, 20px);
            }

            .gpa-calculator.dark-mode {
                background: linear-gradient(145deg, #2d2d2d, #1a1a1a);
                color: #ffffff;
                box-shadow: 0 10px 20px rgba(0,0,0,0.3);
            }

            .gpa-calculator:hover {
                box-shadow: 0 15px 30px rgba(0,0,0,0.15);
            }

            .gpa-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 15px;
                padding-bottom: 10px;
                border-bottom: 2px solid #eee;
            }

            .dark-mode .gpa-header {
                border-bottom-color: #444;
            }

            .gpa-title {
                font-size: 18px;
                font-weight: 600;
                color: #333;
                margin: 0;
                user-select: none;
            }

            .dark-mode .gpa-title {
                color: #fff;
            }

            .gpa-controls {
                display: flex;
                gap: 10px;
            }

            .gpa-button {
                background: #4CAF50;
                color: white;
                border: none;
                padding: 8px 16px;
                border-radius: 8px;
                cursor: pointer;
                font-size: 14px;
                transition: all 0.2s ease;
                display: flex;
                align-items: center;
                gap: 5px;
            }

            .gpa-button:hover {
                background: #45a049;
                transform: translateY(-2px);
                box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            }

            .gpa-button:active {
                transform: translateY(0);
                box-shadow: none;
            }

            .gpa-button.secondary {
                background: #2196F3;
            }

            .gpa-button.secondary:hover {
                background: #1E88E5;
            }

            .gpa-button.danger {
                background: #F44336;
            }

            .gpa-button.danger:hover {
                background: #E53935;
            }

            .gpa-button.small {
                padding: 4px 8px;
                font-size: 12px;
            }

            .gpa-theme-toggle {
                background: none;
                border: none;
                cursor: pointer;
                padding: 5px;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: transform 0.3s ease;
            }

            .gpa-theme-toggle:hover {
                transform: rotate(30deg);
            }

            .gpa-tabs {
                display: flex;
                gap: 5px;
                margin-bottom: 15px;
                border-bottom: 1px solid #eee;
                padding-bottom: 10px;
                flex-wrap: wrap;
            }

            .dark-mode .gpa-tabs {
                border-bottom-color: #444;
            }

            .gpa-tab {
                padding: 5px 10px;
                cursor: pointer;
                border-radius: 5px;
                transition: all 0.2s ease;
                font-size: 14px;
                user-select: none;
                margin-bottom: 5px;
            }

            .gpa-tab:hover {
                background: rgba(0, 0, 0, 0.05);
            }

            .dark-mode .gpa-tab:hover {
                background: rgba(255, 255, 255, 0.1);
            }

            .gpa-tab.active {
                background: #4CAF50;
                color: white;
            }

            .tab-content {
                display: none;
                animation: fadeIn 0.3s ease-out;
                overflow-y: auto;
                max-height: 300px;
                scrollbar-width: thin;
            }

            .tab-content.active {
                display: block;
            }

            .gpa-content {
                display: grid;
                gap: 15px;
            }

            .gpa-stat {
                background: rgba(255, 255, 255, 0.9);
                padding: 15px;
                border-radius: 12px;
                display: flex;
                flex-direction: column;
                gap: 5px;
                transition: all 0.2s ease;
                box-shadow: 0 2px 5px rgba(0,0,0,0.05);
            }

            .dark-mode .gpa-stat {
                background: rgba(255, 255, 255, 0.1);
                box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            }

            .gpa-stat:hover {
                transform: translateX(5px);
            }

            .gpa-stat-label {
                font-size: 14px;
                color: #666;
            }

            .dark-mode .gpa-stat-label {
                color: #aaa;
            }

            .gpa-stat-value {
                font-size: 24px;
                font-weight: 600;
                color: #2196F3;
            }

            .dark-mode .gpa-stat-value {
                color: #64B5F6;
            }

            .gpa-error {
                color: #f44336;
                font-size: 14px;
                text-align: center;
                padding: 15px;
            }

            .dark-mode .gpa-error {
                color: #ef9a9a;
            }

            .move-handle {
                cursor: move;
                padding: 5px;
                margin: -5px;
                user-select: none;
            }

            .course-list {
                display: flex;
                flex-direction: column;
                gap: 10px;
            }

            .course-item {
                display: flex;
                justify-content: space-between;
                align-items: center;
                background: rgba(255, 255, 255, 0.9);
                padding: 10px;
                border-radius: 8px;
                transition: all 0.2s ease;
            }

            .dark-mode .course-item {
                background: rgba(255, 255, 255, 0.1);
            }

            .course-info {
                flex: 1;
            }

            .course-code {
                font-size: 12px;
                color: #666;
            }

            .dark-mode .course-code {
                color: #aaa;
            }

            .course-name {
                font-weight: 500;
            }

            .course-detail {
                font-size: 12px;
                color: #666;
                margin-top: 2px;
            }

            .dark-mode .course-detail {
                color: #aaa;
            }

            .course-gpa {
                font-weight: 600;
                color: #2196F3;
            }

            .dark-mode .course-gpa {
                color: #64B5F6;
            }

            .excluded-courses {
                display: flex;
                flex-direction: column;
                gap: 10px;
            }

            .excluded-item {
                display: flex;
                justify-content: space-between;
                align-items: center;
                background: rgba(255, 255, 255, 0.9);
                padding: 10px;
                border-radius: 8px;
            }

            .dark-mode .excluded-item {
                background: rgba(255, 255, 255, 0.1);
            }

            .add-excluded {
                display: flex;
                gap: 5px;
                margin-top: 10px;
            }

            .add-excluded input {
                flex: 1;
                padding: 8px;
                border-radius: 5px;
                border: 1px solid #ddd;
                background: #fff;
            }

            .dark-mode .add-excluded input {
                background: #333;
                border-color: #555;
                color: #fff;
            }

            .history-list {
                display: flex;
                flex-direction: column;
                gap: 10px;
            }

            .history-item {
                background: rgba(255, 255, 255, 0.9);
                padding: 15px;
                border-radius: 12px;
                transition: all 0.2s ease;
            }

            .dark-mode .history-item {
                background: rgba(255, 255, 255, 0.1);
            }

            .history-date {
                font-size: 12px;
                color: #666;
                margin-bottom: 5px;
            }

            .dark-mode .history-date {
                color: #aaa;
            }

            .history-value {
                font-size: 18px;
                font-weight: 600;
                color: #2196F3;
            }

            .dark-mode .history-value {
                color: #64B5F6;
            }

            .history-details {
                font-size: 14px;
                color: #666;
                margin-top: 5px;
            }

            .dark-mode .history-details {
                color: #aaa;
            }

            .manual-calculator {
                display: flex;
                flex-direction: column;
                gap: 15px;
            }

            .manual-form {
                display: flex;
                flex-direction: column;
                gap: 10px;
                background: rgba(255, 255, 255, 0.9);
                padding: 15px;
                border-radius: 12px;
            }

            .dark-mode .manual-form {
                background: rgba(255, 255, 255, 0.1);
            }

            .form-group {
                display: flex;
                flex-direction: column;
                gap: 5px;
            }

            .form-group label {
                font-size: 14px;
                color: #666;
            }

            .dark-mode .form-group label {
                color: #aaa;
            }

            .form-group input {
                padding: 8px;
                border-radius: 5px;
                border: 1px solid #ddd;
                background: #fff;
            }

            .dark-mode .form-group input {
                background: #333;
                border-color: #555;
                color: #fff;
            }

            .manual-result {
                background: rgba(255, 255, 255, 0.9);
                padding: 15px;
                border-radius: 12px;
                display: flex;
                flex-direction: column;
                gap: 5px;
                text-align: center;
            }

            .dark-mode .manual-result {
                background: rgba(255, 255, 255, 0.1);
            }

            .manual-gpa {
                font-size: 32px;
                font-weight: 600;
                color: #2196F3;
            }

            .dark-mode .manual-gpa {
                color: #64B5F6;
            }

            .category-list {
                display: flex;
                flex-direction: column;
                gap: 10px;
            }

            .category-item {
                background: rgba(255, 255, 255, 0.9);
                padding: 15px;
                border-radius: 12px;
            }

            .dark-mode .category-item {
                background: rgba(255, 255, 255, 0.1);
            }

            .category-name {
                font-weight: 600;
                margin-bottom: 5px;
            }

            .category-stats {
                display: flex;
                justify-content: space-between;
                font-size: 14px;
                color: #666;
            }

            .dark-mode .category-stats {
                color: #aaa;
            }

            .export-section {
                display: flex;
                flex-direction: column;
                gap: 15px;
            }

            .export-option {
                background: rgba(255, 255, 255, 0.9);
                padding: 15px;
                border-radius: 12px;
                display: flex;
                gap: 10px;
                align-items: center;
            }

            .dark-mode .export-option {
                background: rgba(255, 255, 255, 0.1);
            }

            .export-icon {
                font-size: 24px;
            }

            .export-info {
                flex: 1;
            }

            .export-title {
                font-weight: 600;
                margin-bottom: 3px;
            }

            .export-desc {
                font-size: 12px;
                color: #666;
            }

            .dark-mode .export-desc {
                color: #aaa;
            }

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

            /* 滚动条样式 */
            .tab-content::-webkit-scrollbar {
                width: 6px;
            }

            .tab-content::-webkit-scrollbar-track {
                background: rgba(0, 0, 0, 0.05);
                border-radius: 10px;
            }

            .tab-content::-webkit-scrollbar-thumb {
                background: rgba(0, 0, 0, 0.2);
                border-radius: 10px;
            }

            .dark-mode .tab-content::-webkit-scrollbar-track {
                background: rgba(255, 255, 255, 0.05);
            }

            .dark-mode .tab-content::-webkit-scrollbar-thumb {
                background: rgba(255, 255, 255, 0.2);
            }

            /* 分布图样式 */
            .distribution-chart {
                width: 100%;
                height: 8px;
                background: #eee;
                border-radius: 4px;
                margin-top: 10px;
                position: relative;
                overflow: hidden;
            }

            .dark-mode .distribution-chart {
                background: #444;
            }

            .chart-segment {
                height: 100%;
                position: absolute;
                transition: width 0.5s ease;
            }

            .segment-excellent {
                background: #4CAF50;
                left: 0;
            }

            .segment-good {
                background: #2196F3;
            }

            .segment-average {
                background: #FFC107;
            }

            .segment-poor {
                background: #F44336;
                right: 0;
            }

            .chart-legend {
                display: flex;
                justify-content: space-between;
                margin-top: 5px;
                font-size: 10px;
                color: #666;
            }

            .dark-mode .chart-legend {
                color: #aaa;
            }

            .legend-item {
                display: flex;
                align-items: center;
                gap: 3px;
            }

            .legend-dot {
                width: 8px;
                height: 8px;
                border-radius: 50%;
            }

            .dot-excellent {
                background: #4CAF50;
            }

            .dot-good {
                background: #2196F3;
            }

            .dot-average {
                background: #FFC107;
            }

            .dot-poor {
                background: #F44336;
            }

            .filter-options {
                display: flex;
                flex-wrap: wrap;
                gap: 5px;
                margin-bottom: 15px;
            }

            .filter-tag {
                background: #eee;
                padding: 3px 8px;
                border-radius: 15px;
                font-size: 12px;
                cursor: pointer;
                transition: all 0.2s ease;
            }

            .dark-mode .filter-tag {
                background: #444;
            }

            .filter-tag:hover {
                background: #ddd;
            }

            .dark-mode .filter-tag:hover {
                background: #555;
            }

            .filter-tag.active {
                background: #4CAF50;
                color: white;
            }
        `;
        document.head.appendChild(style);
    }

    /**
     * 创建UI界面
     * @returns {HTMLElement} 计算器容器元素
     */
    function createGPADisplay() {
        const container = document.createElement('div');
        container.className = 'gpa-calculator';
        if (state.isDarkMode) {
            container.classList.add('dark-mode');
        }

        container.innerHTML = `
            <div class="gpa-header">
                <div class="move-handle">
                    <div class="gpa-title">GPA 计算器</div>
                </div>
                <div class="gpa-controls">
                    <button class="gpa-theme-toggle" title="切换主题">${state.isDarkMode ? '🌞' : '🌙'}</button>
                    <button class="gpa-button" id="calculate-gpa">
                        <span>计算</span>
                        <span class="calculation-icon">📊</span>
                    </button>
                </div>
            </div>

            <div class="gpa-tabs">
                <div class="gpa-tab active" data-tab="stats">统计</div>
                <div class="gpa-tab" data-tab="courses">课程</div>
                <div class="gpa-tab" data-tab="manual">手动计算</div>
                <div class="gpa-tab" data-tab="history">历史</div>
                <div class="gpa-tab" data-tab="export">导出</div>
            </div>

            <div id="stats-tab" class="tab-content active">
                <div class="gpa-content">
                    <div class="gpa-stat">
                        <span class="gpa-stat-label">总平均 GPA</span>
                        <span class="gpa-stat-value" id="gpa-value">-</span>
                    </div>
                    <div class="gpa-stat">
                        <span class="gpa-stat-label">总学分</span>
                        <span class="gpa-stat-value" id="credits-value">-</span>
                    </div>
                    <div class="gpa-stat">
                        <span class="gpa-stat-label">总绩点</span>
                        <span class="gpa-stat-value" id="points-value">-</span>
                    </div>

                    <div class="gpa-stat">
                        <span class="gpa-stat-label">成绩分布</span>
                        <div class="distribution-chart">
                            <div class="chart-segment segment-excellent" style="width: 0%"></div>
                            <div class="chart-segment segment-good" style="width: 0%"></div>
                            <div class="chart-segment segment-average" style="width: 0%"></div>
                            <div class="chart-segment segment-poor" style="width: 0%"></div>
                        </div>
                        <div class="chart-legend">
                            <div class="legend-item">
                                <span class="legend-dot dot-excellent"></span>
                                <span>90+</span>
                            </div>
                            <div class="legend-item">
                                <span class="legend-dot dot-good"></span>
                                <span>80-89</span>
                            </div>
                            <div class="legend-item">
                                <span class="legend-dot dot-average"></span>
                                <span>70-79</span>
                            </div>
                            <div class="legend-item">
                                <span class="legend-dot dot-poor"></span>
                                <span>≤69</span>
                            </div>
                        </div>
                    </div>

                    <h3>课程分类统计</h3>
                    <div class="category-list" id="category-stats">
                        <div class="gpa-error">请先计算GPA以查看分类统计</div>
                    </div>
                </div>
            </div>

            <div id="courses-tab" class="tab-content">
                <h3>排除的课程</h3>
                <div class="excluded-courses" id="excluded-courses-list">
                    ${state.excludedCourses.map(code => `
                        <div class="excluded-item" data-code="${code}">
                            <span>${code}</span>
                            <button class="gpa-button small danger remove-excluded">移除</button>
                        </div>
                    `).join('')}
                </div>
                <div class="add-excluded">
                    <input type="text" id="new-excluded-course" placeholder="输入课程代码">
                    <button class="gpa-button small" id="add-excluded-btn">添加</button>
                </div>
                <h3>课程列表</h3>
                <div class="filter-options" id="category-filters">
                    <div class="filter-tag active" data-category="all">全部</div>
                </div>
                <div class="course-list" id="course-list">
                    <div class="gpa-error">请先计算GPA以查看课程列表</div>
                </div>
            </div>

            <div id="manual-tab" class="tab-content">
                <div class="manual-calculator">
                    <div class="manual-form">
                        <div class="form-group">
                            <label for="manual-credits">总学分</label>
                            <input type="number" id="manual-credits" placeholder="输入总学分" step="0.1" min="0">
                        </div>
                        <div class="form-group">
                            <label for="manual-points">总绩点</label>
                            <input type="number" id="manual-points" placeholder="输入总绩点" step="0.1" min="0">
                        </div>
                        <button class="gpa-button" id="calculate-manual">计算</button>
                    </div>
                    <div class="manual-result">
                        <div class="gpa-stat-label">计算结果</div>
                        <div class="manual-gpa" id="manual-gpa-result">-</div>
                    </div>
                    <div class="gpa-error" id="manual-error" style="display: none;"></div>
                </div>
            </div>

            <div id="history-tab" class="tab-content">
                <div class="history-list" id="history-list">
                    ${state.calculationHistory.length === 0 ?
                        '<div class="gpa-error">暂无历史记录</div>' :
                        state.calculationHistory.map(entry => {
                            const date = new Date(entry.timestamp);
                            return `
                                <div class="history-item">
                                    <div class="history-date">${date.toLocaleString()}</div>
                                    <div class="history-value">GPA: ${entry.gpa.toFixed(4)}</div>
                                    <div class="history-details">
                                        学分: ${entry.totalCredits.toFixed(1)} |
                                        总绩点: ${entry.totalGradePoints.toFixed(4)}
                                    </div>
                                </div>
                            `;
                        }).join('')
                    }
                </div>
            </div>

            <div id="export-tab" class="tab-content">
                <div class="export-section">
                    <div class="export-option">
                        <div class="export-icon">📋</div>
                        <div class="export-info">
                            <div class="export-title">复制为文本</div>
                            <div class="export-desc">将GPA计算结果复制为纯文本格式</div>
                        </div>
                        <button class="gpa-button small" id="export-text">复制</button>
                    </div>
                    <div class="export-option">
                        <div class="export-icon">📊</div>
                        <div class="export-info">
                            <div class="export-title">导出为CSV</div>
                            <div class="export-desc">导出课程详细成绩为CSV文件</div>
                        </div>
                        <button class="gpa-button small" id="export-csv">导出</button>
                    </div>
                    <div class="export-option">
                        <div class="export-icon">🖨️</div>
                        <div class="export-info">
                            <div class="export-title">打印成绩单</div>
                            <div class="export-desc">生成打印友好的成绩单</div>
                        </div>
                        <button class="gpa-button small" id="export-print">打印</button>
                    </div>
                </div>
            </div>
        `;

        return container;
    }

    /**
     * 使元素可拖动
     * @param {HTMLElement} element - 需要拖动的元素
     */
    function makeDraggable(element) {
        const handle = element.querySelector('.move-handle');

        handle.addEventListener('mousedown', dragStart);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', dragEnd);

        function dragStart(e) {
            if (e.button !== 0) return; // 只响应左键

            state.initialX = e.clientX - state.xOffset;
            state.initialY = e.clientY - state.yOffset;

            if (e.target === handle || handle.contains(e.target)) {
                state.isDragging = true;
                element.style.transition = 'none';
            }
        }

        function drag(e) {
            if (state.isDragging) {
                e.preventDefault();

                state.currentX = e.clientX - state.initialX;
                state.currentY = e.clientY - state.initialY;

                // 边界检查
                const rect = element.getBoundingClientRect();
                const maxX = window.innerWidth - rect.width;
                const maxY = window.innerHeight - rect.height;

                state.xOffset = Math.min(Math.max(0, state.currentX), maxX);
                state.yOffset = Math.min(Math.max(0, state.currentY), maxY);

                updateElementPosition(element);
            }
        }

        function dragEnd() {
            if (state.isDragging) {
                state.isDragging = false;
                element.style.transition = 'all 0.3s ease, transform 0.1s ease';
                savePosition();
            }
        }
    }

    /**
     * 更新元素位置
     * @param {HTMLElement} element - 需要更新位置的元素
     */
    function updateElementPosition(element) {
        element.style.transform = `translate(${state.xOffset}px, ${state.yOffset}px)`;
    }

    /**
     * 更新GPA显示
     * @param {Object|null} gpaData - GPA计算结果
     */
    function updateGPADisplay(gpaData) {
        if (gpaData === null) {
            const statsContent = DOM.container.querySelector('.gpa-content');
            statsContent.innerHTML = `
                <div class="gpa-error">
                    未找到成绩表格或有效数据
                </div>
            `;

            // 清空课程列表
            const courseList = DOM.container.querySelector('#course-list');
            courseList.innerHTML = '<div class="gpa-error">无可用数据</div>';

            // 清空分类列表
            const categoryStats = DOM.container.querySelector('#category-stats');
            categoryStats.innerHTML = '<div class="gpa-error">无可用数据</div>';

            return;
        }

        // 更新统计数据
        DOM.gpaValue.textContent = gpaData.gpa.toFixed(4);
        DOM.creditsValue.textContent = gpaData.totalCredits.toFixed(1);
        DOM.pointsValue.textContent = gpaData.totalGradePoints.toFixed(4);

        // 添加动画效果
        [DOM.gpaValue, DOM.creditsValue, DOM.pointsValue].forEach(el => {
            el.style.animation = 'none';
            el.offsetHeight; // 触发重绘
            el.style.animation = 'fadeIn 0.5s ease-out';
        });

        // 更新成绩分布
        updateGradeDistribution(state.courseGrades);

        // 更新分类统计
        updateCategoryStats(gpaData.categories);

        // 更新课程列表和过滤器
        updateCourseFilters(gpaData.courses);
        updateCourseList(gpaData.courses);

        // 更新历史记录列表
        updateHistoryList();

        // 将最新计算结果填充到手动计算器
        if (DOM.manualCredits && DOM.manualPoints) {
            DOM.manualCredits.value = gpaData.totalCredits.toFixed(1);
            DOM.manualPoints.value = gpaData.totalGradePoints.toFixed(2);
        }
    }

    /**
     * 更新成绩分布
     * @param {Array} grades - 成绩数组
     */
    function updateGradeDistribution(grades) {
        if (!grades || grades.length === 0) return;

        // 计算成绩分布
        let excellent = 0, good = 0, average = 0, poor = 0;
        let totalCredits = 0;

        grades.forEach(grade => {
            if (grade.gpa >= 4.5) { // 90-100分
                excellent += grade.credits;
            } else if (grade.gpa >= 3.5) { // 80-89分
                good += grade.credits;
            } else if (grade.gpa >= 2.5) { // 70-79分
                average += grade.credits;
            } else { // 60-69分
                poor += grade.credits;
            }
            totalCredits += grade.credits;
        });

        // 计算百分比
        const excellentPercent = (excellent / totalCredits) * 100;
        const goodPercent = (good / totalCredits) * 100;
        const averagePercent = (average / totalCredits) * 100;
        const poorPercent = (poor / totalCredits) * 100;

        // 更新图表
        const chartSegments = DOM.container.querySelectorAll('.chart-segment');
        chartSegments[0].style.width = `${excellentPercent}%`;
        chartSegments[1].style.width = `${goodPercent}%`;
        chartSegments[1].style.left = `${excellentPercent}%`;
        chartSegments[2].style.width = `${averagePercent}%`;
        chartSegments[2].style.left = `${excellentPercent + goodPercent}%`;
        chartSegments[3].style.width = `${poorPercent}%`;
    }

    /**
     * 更新分类统计
     * @param {Object} categories - 课程分类统计
     */
    function updateCategoryStats(categories) {
        const categoryStats = DOM.container.querySelector('#category-stats');
        if (!categories || Object.keys(categories).length === 0) {
            categoryStats.innerHTML = '<div class="gpa-error">无分类数据</div>';
            return;
        }

        let categoryHtml = '';
        for (const [category, stats] of Object.entries(categories)) {
            const categoryGPA = stats.totalPoints / stats.totalCredits;
            categoryHtml += `
                <div class="category-item">
                    <div class="category-name">${category}</div>
                    <div class="category-stats">
                        <span>GPA: ${categoryGPA.toFixed(2)}</span>
                        <span>学分: ${stats.totalCredits.toFixed(1)}</span>
                        <span>课程数: ${stats.count}</span>
                    </div>
                </div>
            `;
        }

        categoryStats.innerHTML = categoryHtml;
    }

    /**
     * 更新课程过滤器
     * @param {Array} courses - 课程数组
     */
    function updateCourseFilters(courses) {
        if (!courses || courses.length === 0) return;

        // 获取所有分类
        const categories = new Set();
        courses.forEach(course => categories.add(course.category));

        // 更新过滤器
        const filtersContainer = DOM.container.querySelector('#category-filters');
        let filtersHtml = '<div class="filter-tag active" data-category="all">全部</div>';

        categories.forEach(category => {
            filtersHtml += `<div class="filter-tag" data-category="${category}">${category}</div>`;
        });

        filtersContainer.innerHTML = filtersHtml;

        // 绑定过滤器点击事件
        const filterTags = filtersContainer.querySelectorAll('.filter-tag');
        filterTags.forEach(tag => {
            tag.addEventListener('click', function() {
                filterTags.forEach(t => t.classList.remove('active'));
                this.classList.add('active');
                const category = this.dataset.category;
                filterCourses(courses, category);
            });
        });
    }

    /**
     * 过滤课程列表
     * @param {Array} courses - 所有课程
     * @param {string} category - 要过滤的分类
     */
    function filterCourses(courses, category) {
        let filteredCourses = courses;
        if (category !== 'all') {
            filteredCourses = courses.filter(course => course.category === category);
        }

        updateCourseList(filteredCourses);
    }

    /**
     * 更新课程列表
     * @param {Array} courses - 课程数组
     */
    function updateCourseList(courses) {
        const courseList = DOM.container.querySelector('#course-list');
        if (!courses || courses.length === 0) {
            courseList.innerHTML = '<div class="gpa-error">无课程数据</div>';
            return;
        }

        courseList.innerHTML = courses.map(course => `
            <div class="course-item">
                <div class="course-info">
                    <div class="course-name">${course.name}</div>
                    <div class="course-code">${course.code}</div>
                    <div class="course-detail">
                        ${course.category} | ${course.credits}学分 | ${course.grade}
                    </div>
                </div>
                <div class="course-gpa">${course.gpa.toFixed(1)}</div>
            </div>
        `).join('');
    }

    /**
     * 更新历史记录列表
     */
    function updateHistoryList() {
        if (!DOM.historyList) return;

        if (state.calculationHistory.length === 0) {
            DOM.historyList.innerHTML = '<div class="gpa-error">暂无历史记录</div>';
            return;
        }

        DOM.historyList.innerHTML = state.calculationHistory.map(entry => {
            const date = new Date(entry.timestamp);
            return `
                <div class="history-item">
                    <div class="history-date">${date.toLocaleString()}</div>
                    <div class="history-value">GPA: ${entry.gpa.toFixed(4)}</div>
                    <div class="history-details">
                        学分: ${entry.totalCredits.toFixed(1)} |
                        总绩点: ${entry.totalGradePoints.toFixed(4)}
                    </div>
                </div>
            `;
        }).join('');
    }

    /**
     * 更新排除课程列表
     */
    function updateExcludedCoursesList() {
        if (!DOM.excludedCoursesList) return;

        DOM.excludedCoursesList.innerHTML = state.excludedCourses.map(code => `
            <div class="excluded-item" data-code="${code}">
                <span>${code}</span>
                <button class="gpa-button small danger remove-excluded">移除</button>
            </div>
        `).join('');

        // 重新绑定移除按钮事件
        DOM.excludedCoursesList.querySelectorAll('.remove-excluded').forEach(btn => {
            btn.addEventListener('click', function() {
                const code = this.parentElement.dataset.code;
                const index = state.excludedCourses.indexOf(code);
                if (index > -1) {
                    state.excludedCourses.splice(index, 1);
                    updateExcludedCoursesList();
                    saveExcludedCourses();
                }
            });
        });
    }

    /**
     * 生成导出文本
     * @returns {string} 格式化的文本
     */
    function generateExportText() {
        if (!state.lastCalculatedGPA) return '暂无数据可导出';

        const data = state.lastCalculatedGPA;
        let text = `GPA计算结果\n`;
        text += `------------------------\n`;
        text += `总平均GPA: ${data.gpa.toFixed(4)}\n`;
        text += `总学分: ${data.totalCredits.toFixed(1)}\n`;
        text += `总绩点: ${data.totalGradePoints.toFixed(4)}\n`;
        text += `计算时间: ${new Date(data.timestamp).toLocaleString()}\n`;
        text += `------------------------\n\n`;

        text += `课程分类统计:\n`;
        for (const [category, stats] of Object.entries(data.categories)) {
            const categoryGPA = stats.totalPoints / stats.totalCredits;
            text += `${category}: GPA=${categoryGPA.toFixed(2)}, 学分=${stats.totalCredits.toFixed(1)}, 课程数=${stats.count}\n`;
        }
        text += `------------------------\n\n`;

        text += `课程列表 (共${data.courses.length}门):\n`;
        data.courses.forEach((course, index) => {
            text += `${index + 1}. ${course.name} (${course.code})\n`;
            text += `   分类: ${course.category}, 学分: ${course.credits}, GPA: ${course.gpa.toFixed(1)}, 成绩: ${course.grade}\n`;
        });

        return text;
    }

    /**
     * 生成CSV数据
     * @returns {string} CSV格式的字符串
     */
    function generateCSV() {
        if (!state.lastCalculatedGPA) return null;

        const data = state.lastCalculatedGPA;
        let csv = '课程代码,课程名称,课程分类,学分,GPA,成绩,绩点\n';

        data.courses.forEach(course => {
            csv += `${course.code},${course.name},${course.category},${course.credits},${course.gpa.toFixed(1)},${course.grade},${course.points.toFixed(2)}\n`;
        });

        return csv;
    }

    /**
     * 导出为CSV文件
     */
    function exportCSV() {
        const csv = generateCSV();
        if (!csv) {
            alert('暂无数据可导出');
            return;
        }

        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
        const link = document.createElement('a');
        const url = URL.createObjectURL(blob);

        link.setAttribute('href', url);
        link.setAttribute('download', `GPA统计_${new Date().toISOString().split('T')[0]}.csv`);
        link.style.visibility = 'hidden';

        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    /**
     * 复制文本到剪贴板
     * @param {string} text - 要复制的文本
     */
    function copyToClipboard(text) {
        const textarea = document.createElement('textarea');
        textarea.value = text;
        textarea.style.position = 'fixed';
        document.body.appendChild(textarea);
        textarea.select();

        try {
            document.execCommand('copy');
            alert('复制成功');
        } catch (err) {
            console.error('复制失败:', err);
            alert('复制失败');
        }

        document.body.removeChild(textarea);
    }

    /**
     * 打印成绩单
     */
    function printGradeReport() {
        if (!state.lastCalculatedGPA) {
            alert('暂无数据可打印');
            return;
        }

        const data = state.lastCalculatedGPA;
        const printWindow = window.open('', '_blank');

        printWindow.document.write(`
            <html>
            <head>
                <title>成绩单 - ${new Date().toLocaleDateString()}</title>
                <style>
                    body {
                        font-family: Arial, sans-serif;
                        margin: 30px;
                        line-height: 1.5;
                    }
                    h1, h2, h3 {
                        color: #333;
                    }
                    .summary {
                        margin: 20px 0;
                        padding: 15px;
                        border: 1px solid #ddd;
                        border-radius: 5px;
                    }
                    .summary-item {
                        margin: 10px 0;
                    }
                    .value {
                        font-weight: bold;
                        color: #2196F3;
                    }
                    .category {
                        margin: 15px 0;
                        padding: 10px;
                        border-left: 4px solid #4CAF50;
                        background: #f9f9f9;
                    }
                    table {
                        width: 100%;
                        border-collapse: collapse;
                        margin: 20px 0;
                    }
                    th, td {
                        padding: 8px 12px;
                        text-align: left;
                        border-bottom: 1px solid #ddd;
                    }
                    th {
                        background-color: #f5f5f5;
                    }
                    tr:hover {
                        background-color: #f8f8f8;
                    }
                    .footer {
                        margin-top: 30px;
                        font-size: 12px;
                        color: #888;
                        text-align: center;
                    }
                    @media print {
                        body {
                            margin: 0.5cm;
                        }
                        .no-print {
                            display: none;
                        }
                    }
                </style>
            </head>
            <body>
                <h1>成绩单</h1>
                <div class="summary">
                    <div class="summary-item">总平均GPA: <span class="value">${data.gpa.toFixed(4)}</span></div>
                    <div class="summary-item">总学分: <span class="value">${data.totalCredits.toFixed(1)}</span></div>
                    <div class="summary-item">总绩点: <span class="value">${data.totalGradePoints.toFixed(4)}</span></div>
                    <div class="summary-item">计算时间: ${new Date(data.timestamp).toLocaleString()}</div>
                </div>

                <h2>课程分类统计</h2>
                <div class="categories">
        `);

        for (const [category, stats] of Object.entries(data.categories)) {
            const categoryGPA = stats.totalPoints / stats.totalCredits;
            printWindow.document.write(`
                <div class="category">
                    <h3>${category}</h3>
                    <div>GPA: <span class="value">${categoryGPA.toFixed(2)}</span></div>
                    <div>学分: ${stats.totalCredits.toFixed(1)}</div>
                    <div>课程数: ${stats.count}</div>
                </div>
            `);
        }

        printWindow.document.write(`
                </div>

                <h2>课程列表 (共${data.courses.length}门)</h2>
                <table>
                    <thead>
                        <tr>
                            <th>课程名称</th>
                            <th>课程代码</th>
                            <th>分类</th>
                            <th>学分</th>
                            <th>GPA</th>
                            <th>成绩</th>
                        </tr>
                    </thead>
                    <tbody>
        `);

        data.courses.forEach(course => {
            printWindow.document.write(`
                <tr>
                    <td>${course.name}</td>
                    <td>${course.code}</td>
                    <td>${course.category}</td>
                    <td>${course.credits}</td>
                    <td>${course.gpa.toFixed(1)}</td>
                    <td>${course.grade}</td>
                </tr>
            `);
        });

        printWindow.document.write(`
                    </tbody>
                </table>

                <div class="footer">
                    此成绩单由GPA计算器生成于 ${new Date().toLocaleString()}
                </div>

                <div class="no-print" style="text-align: center; margin-top: 20px;">
                    <button onclick="window.print()">打印</button>
                    <button onclick="window.close()">关闭</button>
                </div>
            </body>
            </html>
        `);

        printWindow.document.close();
    }

    /**
     * 初始化标签页切换功能
     */
    function initTabs() {
        const tabs = DOM.container.querySelectorAll('.gpa-tab');
        const tabContents = DOM.container.querySelectorAll('.tab-content');

        tabs.forEach(tab => {
            tab.addEventListener('click', () => {
                // 移除所有活动标签
                tabs.forEach(t => t.classList.remove('active'));
                tabContents.forEach(c => c.classList.remove('active'));

                // 激活当前标签
                tab.classList.add('active');
                const tabId = tab.dataset.tab;
                DOM.container.querySelector(`#${tabId}-tab`).classList.add('active');
                state.currentTab = tabId;
            });
        });
    }

    /**
     * 计算GPA并更新显示
     */
    function calculateAndUpdate() {
        const iframe = document.querySelector('iframe');
        if (iframe) {
            try {
                const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
                if (iframeDoc) {
                    const gpaData = calculateGPA(iframeDoc);
                    updateGPADisplay(gpaData);
                } else {
                    updateGPADisplay(null);
                }
            } catch (error) {
                console.error('访问iframe内容时出错:', error);
                updateGPADisplay(null);
            }
        } else {
            updateGPADisplay(null);
        }
    }

    /**
     * 手动计算GPA
     */
    function handleManualCalculation() {
        // 清除错误信息
        const errorElement = DOM.container.querySelector('#manual-error');
        errorElement.style.display = 'none';

        // 获取输入值
        const credits = parseFloat(DOM.manualCredits.value);
        const points = parseFloat(DOM.manualPoints.value);

        // 验证输入
        if (isNaN(credits) || isNaN(points)) {
            errorElement.textContent = '请输入有效的数字';
            errorElement.style.display = 'block';
            return;
        }

        if (credits <= 0) {
            errorElement.textContent = '学分必须大于0';
            errorElement.style.display = 'block';
            return;
        }

        // 计算GPA
        const gpa = calculateManualGPA(credits, points);

        // 显示结果
        DOM.manualGpaResult.textContent = gpa.toFixed(4);
        DOM.manualGpaResult.style.animation = 'none';
        DOM.manualGpaResult.offsetHeight; // 触发重绘
        DOM.manualGpaResult.style.animation = 'fadeIn 0.5s ease-out';
    }

    /**
     * 绑定事件
     */
    function bindEvents() {
        // 计算按钮事件
        const calculateButton = DOM.container.querySelector('#calculate-gpa');
        calculateButton.addEventListener('click', calculateAndUpdate);

        // 主题切换
        const themeToggle = DOM.container.querySelector('.gpa-theme-toggle');
        themeToggle.addEventListener('click', () => {
            state.isDarkMode = !state.isDarkMode;
            DOM.container.classList.toggle('dark-mode');
            themeToggle.innerHTML = state.isDarkMode ? '🌞' : '🌙';
            saveDarkMode();
        });

        // 添加排除课程
        const addExcludedBtn = DOM.container.querySelector('#add-excluded-btn');
        const newExcludedInput = DOM.container.querySelector('#new-excluded-course');

        if (addExcludedBtn && newExcludedInput) {
            addExcludedBtn.addEventListener('click', () => {
                const code = newExcludedInput.value.trim();
                if (code && !state.excludedCourses.includes(code)) {
                    state.excludedCourses.push(code);
                    updateExcludedCoursesList();
                    saveExcludedCourses();
                    newExcludedInput.value = '';
                }
            });

            newExcludedInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    addExcludedBtn.click();
                }
            });
        }

        // 手动计算
        const calculateManualBtn = DOM.container.querySelector('#calculate-manual');
        if (calculateManualBtn) {
            calculateManualBtn.addEventListener('click', handleManualCalculation);
        }

        // 导出功能
        const exportTextBtn = DOM.container.querySelector('#export-text');
        if (exportTextBtn) {
            exportTextBtn.addEventListener('click', () => {
                const text = generateExportText();
                copyToClipboard(text);
            });
        }

        const exportCsvBtn = DOM.container.querySelector('#export-csv');
        if (exportCsvBtn) {
            exportCsvBtn.addEventListener('click', exportCSV);
        }

        const exportPrintBtn = DOM.container.querySelector('#export-print');
        if (exportPrintBtn) {
            exportPrintBtn.addEventListener('click', printGradeReport);
        }

        // 监听窗口大小变化,确保计算器不会超出界面
        window.addEventListener('resize', () => {
            const rect = DOM.container.getBoundingClientRect();
            const maxX = window.innerWidth - rect.width;
            const maxY = window.innerHeight - rect.height;

            if (state.xOffset > maxX || state.yOffset > maxY) {
                state.xOffset = Math.min(state.xOffset, maxX);
                state.yOffset = Math.min(state.yOffset, maxY);
                updateElementPosition(DOM.container);
                savePosition();
            }
        });
    }

    /**
     * 缓存DOM引用
     */
    function cacheDOMReferences() {
        DOM.container = document.querySelector('.gpa-calculator');
        DOM.gpaValue = DOM.container.querySelector('#gpa-value');
        DOM.creditsValue = DOM.container.querySelector('#credits-value');
        DOM.pointsValue = DOM.container.querySelector('#points-value');
        DOM.excludedCoursesList = DOM.container.querySelector('#excluded-courses-list');
        DOM.historyList = DOM.container.querySelector('#history-list');
        DOM.manualCredits = DOM.container.querySelector('#manual-credits');
        DOM.manualPoints = DOM.container.querySelector('#manual-points');
        DOM.manualGpaResult = DOM.container.querySelector('#manual-gpa-result');
    }

    /**
     * 初始化应用
     */
    function init() {
        // 加载保存的数据
        loadSavedData();

        // 创建UI
        injectStyles();
        DOM.container = createGPADisplay();
        document.body.appendChild(DOM.container);

        // 缓存DOM引用
        cacheDOMReferences();

        // 初始化功能
        updateElementPosition(DOM.container);
        makeDraggable(DOM.container);
        initTabs();
        bindEvents();

        // 应用深色模式
        if (state.isDarkMode) {
            DOM.container.classList.add('dark-mode');
        }
    }

    // 启动应用
    init();
})();