吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4190|回复: 66
上一主题 下一主题
收起左侧

[其他原创] 网页版-学校新生分班系统

  [复制链接]
跳转到指定楼层
楼主
风子是我 发表于 2025-8-13 15:07 回帖奖励
html格式,初次打开稍慢些。
先上界面截图


程序综合考虑成绩均衡、性别均衡,同名(同音)不在同一班、多胞胎选择同一班或者不在同一班。
底部添加测试用的excel数据,使用时可能参考这个格式设计表头。
准备的excel的数据要注意:相同的一组同名(同音)的单元格用相同的数字、中文或者字母表示,多胞胎也类似,考虑到用数字的话数字与文本的不同,建议全部用汉字,比如,张三家的孩子、李四的孩子:)
不废话了,下面上源码:
[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>XX学校新生智能分班系统 v2.0</title>
    <style>
        :root {
            --primary-color: #4b86b4;
            --primary-hover: #3a6a8a;
            --bg-color: #f0f8ff;
            --text-color: #2a4d69;
            --error-color: #ff6b6b;
            --female-color: #ff9999;
            --male-color: #99ccff;
            --border-color: #4b86b4;
            --light-border: #e0e0e0;
            --header-bg: #e6f2ff;
        }

        body {
            font-family: "Microsoft YaHei", sans-serif;
            background-color: var(--bg-color);
            color: var(--text-color);
            margin: 0;
            padding: 0;
        }

        .container {
            display: flex;
            min-height: 100vh;
        }

        /* Header */
        .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px 20px;
            background-color: white;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }

        .header-title {
            font-size: 24px;
            font-weight: bold;
            color: var(--text-color);
        }

        .status-bar {
            flex-grow: 1;
            text-align: right;
            padding-right: 20px;
        }

        /* Control Panel */
        .control-panel {
            width: 300px;
            background-color: white;
            padding: 15px;
            box-shadow: 2px 0 5px rgba(0,0,0,0.1);
            overflow-y: auto;
        }

        .panel-section {
            margin-bottom: 15px;
            border: 1px solid var(--light-border);
            border-radius: 5px;
            padding: 10px;
        }

        .panel-title {
            font-weight: bold;
            margin-bottom: 10px;
            color: var(--text-color);
        }

        .btn {
            display: block;
            width: 100%;
            padding: 8px;
            margin: 5px 0;
            background-color: var(--primary-color);
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-family: "Microsoft YaHei", sans-serif;
            font-size: 14px;
            transition: background-color 0.3s;
        }

        .btn:hover {
            background-color: var(--primary-hover);
        }

        .btn:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }

        .form-group {
            margin-bottom: 10px;
        }

        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }

        input[type="number"], select {
            width: 100%;
            padding: 6px;
            border: 1px solid var(--light-border);
            border-radius: 4px;
        }

        .checkbox-group {
            display: flex;
            align-items: center;
            margin-bottom: 5px;
        }

        .checkbox-group input {
            margin-right: 8px;
        }

        /* Main Content */
        .main-content {
            flex-grow: 1;
            padding: 15px;
            overflow: hidden;
        }

        /* Tabs */
        .tabs {
            display: flex;
            border-bottom: 1px solid var(--light-border);
            margin-bottom: 10px;
        }

        .tab {
            padding: 10px 15px;
            cursor: pointer;
            border: 1px solid transparent;
            border-bottom: none;
            border-radius: 5px 5px 0 0;
            margin-right: 5px;
            font-weight: bold;
        }

        .tab.active {
            border-color: var(--light-border);
            border-bottom-color: white;
            background-color: white;
        }

        .tab-content {
            display: none;
            background-color: white;
            padding: 15px;
            border-radius: 0 5px 5px 5px;
            height: calc(100vh - 150px);
            overflow: auto;
        }

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

        /* Data Table */
        .data-table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 15px;
        }

        .data-table th, .data-table td {
            border: 1px solid var(--light-border);
            padding: 8px;
            text-align: center;
        }

        .data-table th {
            background-color: var(--header-bg);
            font-weight: bold;
        }

        .data-table tr.error {
            background-color: var(--error-color);
        }

        /* Stats Table */
        .stats-table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 15px;
        }

        .stats-table th, .stats-table td {
            border: 1px solid var(--light-border);
            padding: 8px;
            text-align: center;
        }

        .stats-table th {
            background-color: var(--header-bg);
            font-weight: bold;
        }

        /* Animation Canvas */
        .animation-container {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: white;
            z-index: 1000;
            display: none;
            flex-direction: column;
        }

        .animation-header {
            padding: 10px;
            background-color: var(--primary-color);
            color: white;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .animation-canvas {
            flex-grow: 1;
            background-color: white;
            position: relative;
            overflow: auto;
        }

        .class-area {
            position: absolute;
            border: 2px solid var(--border-color);
            background-color: var(--header-bg);
            border-radius: 5px;
        }

        .student-icon {
            position: absolute;
            width: 35px;
            height: 30px;
            border: 1px solid #666;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 8px;
            cursor: pointer;
        }

        .student-icon.female {
            background-color: var(--female-color);
        }

        .student-icon.male {
            background-color: var(--male-color);
        }

        /* Log */
        .log-container {
            height: 100%;
            border: 1px solid var(--light-border);
            padding: 10px;
            overflow-y: auto;
            font-family: monospace;
            white-space: pre-wrap;
        }

        /* Stats Differences */
        .diff-container {
            display: flex;
            flex-wrap: wrap;
            margin-top: 15px;
        }

        .diff-item {
            display: flex;
            align-items: center;
            margin-right: 20px;
            margin-bottom: 10px;
        }

        .diff-label {
            margin-right: 5px;
            font-weight: bold;
        }

        .diff-value {
            font-weight: bold;
        }

        .diff-value.good {
            color: green;
        }

        .diff-value.bad {
            color: red;
        }

        /* Modal */
        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.5);
            z-index: 2000;
            justify-content: center;
            align-items: center;
        }

        .modal-content {
            background-color: white;
            padding: 20px;
            border-radius: 5px;
            max-width: 500px;
            width: 90%;
        }

        .modal-title {
            font-size: 18px;
            font-weight: bold;
            margin-bottom: 15px;
        }

        .modal-message {
            margin-bottom: 20px;
        }

        .modal-buttons {
            display: flex;
            justify-content: flex-end;
        }

        .modal-btn {
            padding: 8px 15px;
            margin-left: 10px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }

        .modal-btn-primary {
            background-color: var(--primary-color);
            color: white;
        }

        .modal-btn-secondary {
            background-color: #cccccc;
        }
    </style>
</head>
<body>
    <div class="container">
        <!-- Control Panel -->
        <div class="control-panel">
            <div class="panel-section">
                <div class="panel-title">数据导入</div>
                <button id="importBtn" class="btn">导入Excel数据</button>
                <input type="file" id="fileInput" style="display: none;" accept=".xlsx,.xls">
            </div>

            <div class="panel-section">
                <div class="panel-title">分班设置</div>
                <div class="form-group">
                    <label for="classNum">班级数量:</label>
                    <input type="number" id="classNum" min="2" max="20" value="2">
                </div>
                <div class="form-group">
                    <label for="animationSpeed">动画速度:</label>
                    <input type="range" id="animationSpeed" min="0.1" max="2" step="0.1" value="1">
                    <span id="speedValue">1.0</span>
                </div>
                <div class="form-group">
                    <label>平衡因素:</label>
                    <div class="checkbox-group">
                        <input type="checkbox" id="genderBalance" checked>
                        <label for="genderBalance">性别均衡</label>
                    </div>
                    <div class="checkbox-group">
                        <input type="checkbox" id="scoreBalance" checked>
                        <label for="scoreBalance">成绩均衡</label>
                    </div>
                </div>
            </div>

            <div class="panel-section">
                <div class="panel-title">操作</div>
                <button id="startBtn" class="btn">开始分班</button>
                <button id="exportBtn" class="btn" disabled>导出结果</button>
                <button id="statsBtn" class="btn" disabled>查看统计</button>
                <button id="resetBtn" class="btn">重置系统</button>
            </div>
        </div>

        <!-- Main Content -->
        <div class="main-content">
            <div class="tabs">
                <div class="tab active" data-tab="data">学生数据</div>
                <div class="tab" data-tab="stats">分班统计</div>
                <div class="tab" data-tab="log">分班日志</div>
            </div>

            <!-- Data Tab -->
            <div class="tab-content active" id="dataTab">
                <table class="data-table">
                    <thead>
                        <tr>
                            <th>序号</th>
                            <th>姓名</th>
                            <th>性别</th>
                            <th>语文</th>
                            <th>数学</th>
                            <th>英语</th>
                            <th>科学</th>
                            <th>总分</th>
                            <th>同名或近音</th>
                            <th>多胞胎</th>
                            <th>多胞胎同班</th>
                        </tr>
                    </thead>
                    <tbody id="dataTableBody">
                        <!-- Data will be populated here -->
                    </tbody>
                </table>
            </div>

            <!-- Stats Tab -->
            <div class="tab-content" id="statsTab">
                <table class="stats-table">
                    <thead>
                        <tr>
                            <th>班级</th>
                            <th>人数</th>
                            <th>男生</th>
                            <th>女生</th>
                            <th>语文均分</th>
                            <th>数学均分</th>
                            <th>英语均分</th>
                            <th>科学均分</th>
                            <th>总分均分</th>
                        </tr>
                    </thead>
                    <tbody id="statsTableBody">
                        <!-- Stats will be populated here -->
                    </tbody>
                </table>

                <div class="diff-container">
                    <div class="panel-title">各科最大分差</div>
                    <div class="diff-item">
                        <span class="diff-label">语文:</span>
                        <span class="diff-value" id="chineseDiff">0.00</span>
                    </div>
                    <div class="diff-item">
                        <span class="diff-label">数学:</span>
                        <span class="diff-value" id="mathDiff">0.00</span>
                    </div>
                    <div class="diff-item">
                        <span class="diff-label">英语:</span>
                        <span class="diff-value" id="englishDiff">0.00</span>
                    </div>
                    <div class="diff-item">
                        <span class="diff-label">科学:</span>
                        <span class="diff-value" id="scienceDiff">0.00</span>
                    </div>
                    <div class="diff-item">
                        <span class="diff-label">总分:</span>
                        <span class="diff-value" id="totalDiff">0.00</span>
                    </div>
                </div>
            </div>

            <!-- Log Tab -->
            <div class="tab-content" id="logTab">
                <div class="log-container" id="logContent">
                    <!-- Log messages will be displayed here -->
                </div>
            </div>
        </div>
    </div>

    <!-- Animation Window -->
    <div class="animation-container" id="animationWindow">
        <div class="animation-header">
            <div>分班过程</div>
            <button id="closeAnimationBtn">关闭</button>
        </div>
        <div class="animation-canvas" id="animationCanvas">
            <!-- Animation elements will be added here -->
        </div>
    </div>

    <!-- Modal Dialog -->
    <div class="modal" id="modalDialog">
        <div class="modal-content">
            <div class="modal-title" id="modalTitle">提示</div>
            <div class="modal-message" id="modalMessage"></div>
            <div class="modal-buttons">
                <button class="modal-btn modal-btn-secondary" id="modalCancelBtn">取消</button>
                <button class="modal-btn modal-btn-primary" id="modalConfirmBtn">确定</button>
            </div>
        </div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
    <script>
        // Global variables
        let studentData = null;
        let classes = [];
        let classNum = 2;
        let animationSpeed = 1.0;
        const balanceThreshold = 0.05;
        const maxIterations = 300;
        let animationRunning = false;
        let studentIcons = {};
        let classSizes = [];

        // DOM elements
        const importBtn = document.getElementById('importBtn');
        const fileInput = document.getElementById('fileInput');
        const classNumInput = document.getElementById('classNum');
        const animationSpeedInput = document.getElementById('animationSpeed');
        const speedValue = document.getElementById('speedValue');
        const genderBalanceCheckbox = document.getElementById('genderBalance');
        const scoreBalanceCheckbox = document.getElementById('scoreBalance');
        const startBtn = document.getElementById('startBtn');
        const exportBtn = document.getElementById('exportBtn');
        const statsBtn = document.getElementById('statsBtn');
        const resetBtn = document.getElementById('resetBtn');
        const dataTableBody = document.getElementById('dataTableBody');
        const statsTableBody = document.getElementById('statsTableBody');
        const logContent = document.getElementById('logContent');
        const animationWindow = document.getElementById('animationWindow');
        const animationCanvas = document.getElementById('animationCanvas');
        const closeAnimationBtn = document.getElementById('closeAnimationBtn');
        const modalDialog = document.getElementById('modalDialog');
        const modalTitle = document.getElementById('modalTitle');
        const modalMessage = document.getElementById('modalMessage');
        const modalCancelBtn = document.getElementById('modalCancelBtn');
        const modalConfirmBtn = document.getElementById('modalConfirmBtn');
        const tabs = document.querySelectorAll('.tab');
        const tabContents = document.querySelectorAll('.tab-content');

        // Tab switching
        tabs.forEach(tab => {
            tab.addEventListener('click', () => {
                tabs.forEach(t => t.classList.remove('active'));
                tabContents.forEach(c => c.classList.remove('active'));
                
                tab.classList.add('active');
                document.getElementById(`${tab.dataset.tab}Tab`).classList.add('active');
            });
        });

        // Event listeners
        importBtn.addEventListener('click', () => fileInput.click());
        fileInput.addEventListener('change', handleFileImport);
        classNumInput.addEventListener('change', updateClassNum);
        animationSpeedInput.addEventListener('input', updateAnimationSpeed);
        startBtn.addEventListener('click', startAssignment);
        exportBtn.addEventListener('click', exportResults);
        statsBtn.addEventListener('click', showStatistics);
        resetBtn.addEventListener('click', resetSystem);
        closeAnimationBtn.addEventListener('click', closeAnimationWindow);
        modalCancelBtn.addEventListener('click', () => hideModal());
        modalConfirmBtn.addEventListener('click', confirmModalAction);

        // Initialize
        updateAnimationSpeed();

        // Functions
        function handleFileImport(event) {
		const file = event.target.files[0];
		if (!file) return;

		const reader = new FileReader();
		reader.onload = function(e) {
			try {
				const data = new Uint8Array(e.target.result);
				const workbook = XLSX.read(data, { type: 'array' });
				const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
				const jsonData = XLSX.utils.sheet_to_json(firstSheet, { defval: "" });

				// 检查必要的基本列
				const requiredBasicColumns = ["序号", "姓名", "性别", "语文", "数学", "英语", "科学", "总分"];
				const missingBasicColumns = requiredBasicColumns.filter(col => !jsonData[0] || !(col in jsonData[0]));
				
				if (missingBasicColumns.length > 0) {
					showError(`数据文件中缺少以下必要列: ${missingBasicColumns.join(', ')}`);
					return;
				}

				// 处理可选列(同名或近音、多胞胎、多胞胎同班)
				const optionalColumns = ["同名或近音", "多胞胎", "多胞胎同班"];
				
				// 处理数据
				studentData = jsonData.map((row, index) => {
					const student = {
						id: index + 1,
						serial: parseInt(row['序号']) || 0,
						name: String(row['姓名'] || '').trim(),
						gender: String(row['性别'] || '').trim(),
						chinese: parseFloat(row['语文']) || 0,
						math: parseFloat(row['数学']) || 0,
						english: parseFloat(row['英语']) || 0,
						science: parseFloat(row['科学']) || 0,
						total: parseFloat(row['总分']) || 0
					};

					// 处理可选列
					optionalColumns.forEach(col => {
						if (col in row) {
							student[col === "同名或近音" ? "sameName" : 
								   col === "多胞胎" ? "twins" : "twinsSameClass"] = 
								row[col] !== undefined && row[col] !== null ? String(row[col]) : '';
						} else {
							student[col === "同名或近音" ? "sameName" : 
								   col === "多胞胎" ? "twins" : "twinsSameClass"] = '';
						}
					});

					return student;
				});

				updateDataTable();
				logMessage(`导入数据: ${file.name}`);
				logMessage(`学生总数: ${studentData.length}`);
				updateStatus(`成功导入数据: ${studentData.length} 名学生`);
				exportBtn.disabled = false;
				statsBtn.disabled = false;
			} catch (error) {
				showError(`导入数据时出错: ${error.message}`);
			}
		};
		reader.readAsArrayBuffer(file);
	}

        function updateDataTable() {
            if (!studentData) return;

            dataTableBody.innerHTML = '';
            studentData.forEach(student => {
                const row = document.createElement('tr');
                if (student.error) row.classList.add('error');
                
                row.innerHTML = `
                    <td>${student.serial}</td>
                    <td>${student.name}</td>
                    <td>${student.gender}</td>
                    <td>${student.chinese.toFixed(1)}</td>
                    <td>${student.math.toFixed(1)}</td>
                    <td>${student.english.toFixed(1)}</td>
                    <td>${student.science.toFixed(1)}</td>
                    <td>${student.total.toFixed(1)}</td>
                    <td>${student.sameName}</td>
                    <td>${student.twins}</td>
                    <td>${student.twinsSameClass}</td>
                `;
                dataTableBody.appendChild(row);
            });
        }

        function updateClassNum() {
            classNum = parseInt(classNumInput.value) || 2;
            if (classNum < 2) classNum = 2;
            if (classNum > 20) classNum = 20;
            classNumInput.value = classNum;
        }

        function updateAnimationSpeed() {
            animationSpeed = parseFloat(animationSpeedInput.value);
            speedValue.textContent = animationSpeed.toFixed(1);
        }

        function logMessage(message) {
            const line = document.createElement('div');
            line.textContent = message;
            logContent.appendChild(line);
            logContent.scrollTop = logContent.scrollHeight;
        }

        function updateStatus(message) {
            // In a real implementation, you would update a status bar element
            console.log(`Status: ${message}`);
        }

        function showError(message) {
            showModal('错误', message);
            logMessage(`错误: ${message}`);
        }

        function showModal(title, message, confirmCallback = null) {
            modalTitle.textContent = title;
            modalMessage.textContent = message;
            modalDialog.style.display = 'flex';
            
            if (confirmCallback) {
                currentConfirmCallback = confirmCallback;
                modalCancelBtn.style.display = 'inline-block';
            } else {
                modalCancelBtn.style.display = 'none';
            }
        }

        function hideModal() {
            modalDialog.style.display = 'none';
        }

        let currentConfirmCallback = null;
        function confirmModalAction() {
            hideModal();
            if (currentConfirmCallback) {
                currentConfirmCallback();
                currentConfirmCallback = null;
            }
        }

        function startAssignment() {
            if (!studentData || studentData.length === 0) {
                showError('没有可用的学生数据');
                return;
            }

            classNum = parseInt(classNumInput.value) || 2;
            animationSpeed = parseFloat(animationSpeedInput.value);
            
            // Disable buttons
            [importBtn, startBtn, exportBtn, statsBtn, resetBtn].forEach(btn => {
                btn.disabled = true;
            });

            // Clear classes
            classes = Array.from({ length: classNum }, () => []);
            
            // Start assignment in a timeout to allow UI to update
            setTimeout(() => {
                performAssignment();
            }, 100);
        }

        function performAssignment() {
            try {
                logMessage('\n===== 开始分班处理 =====');
                updateStatus('开始分班处理...');
                
                // Clear log
                logContent.innerHTML = '';
                
                // Sort students by total score descending
                const students = [...studentData].sort((a, b) => b.total - a.total);
                
                // Calculate target class sizes
                const totalStudents = students.length;
                const baseSize = Math.floor(totalStudents / classNum);
                const extra = totalStudents % classNum;
                classSizes = Array.from({ length: classNum }, (_, i) => baseSize + (i < extra ? 1 : 0));
                
                logMessage(`班级目标人数: ${classSizes.join(', ')}`);
                
                // Show animation window
                showAnimationWindow();
                drawClassAreas();
                
                // Initial assignment - snake pattern
                let classIdx = 0;
                let direction = 1;
                
                const assignNextStudent = (index) => {
                    if (index >= students.length) {
                        // Initial assignment complete
                        logMessage('\n初始分班完成!');
                        calculateAndLogStats();
                        
                        // Process constraints
                        handleNameAndTwinConstraints();
                        logMessage('\n处理同名或近音和多胞胎约束完成!');
                        calculateAndLogStats();
                        
                        // Start optimization
                        logMessage('\n--- 优化分班 ---');
                        logMessage('开始优化分班结果...');
                        
                        const optimized = optimizeAssignment();
                        
                        if (optimized) {
                            logMessage('优化成功! 各科均分差异均小于0.05分');
                        } else {
                            logMessage('优化完成! 达到最大迭代次数');
                        }
                        
                        // Update animation with final positions
                        updateAnimation();
                        
                        // Show completion message
                        const canvas = document.getElementById('animationCanvas');
                        const completionMsg1 = document.createElement('div');
                        completionMsg1.textContent = '分班完成!';
                        completionMsg1.style.position = 'absolute';
                        completionMsg1.style.left = '50%';
                        completionMsg1.style.bottom = '60px';
                        completionMsg1.style.transform = 'translateX(-50%)';
                        completionMsg1.style.fontSize = '16px';
                        completionMsg1.style.fontWeight = 'bold';
                        completionMsg1.style.color = '#2a4d69';
                        canvas.appendChild(completionMsg1);
                        
                        const completionMsg2 = document.createElement('div');
                        completionMsg2.textContent = '各科均分差异均小于0.05分';
                        completionMsg2.style.position = 'absolute';
                        completionMsg2.style.left = '50%';
                        completionMsg2.style.bottom = '30px';
                        completionMsg2.style.transform = 'translateX(-50%)';
                        completionMsg2.style.fontSize = '12px';
                        completionMsg2.style.color = '#2a4d69';
                        canvas.appendChild(completionMsg2);
                        
                        // Enable buttons
                        [importBtn, startBtn, exportBtn, statsBtn, resetBtn].forEach(btn => {
                            btn.disabled = false;
                        });
                        
                        updateStatus(`分班完成! 共分配 ${students.length} 名学生到 ${classNum} 个班级`);
                        logMessage('\n===== 分班完成! =====');
                        
                        return;
                    }
                    
                    const student = students[index];
                    updateStatus(`分配学生: ${student.name} (总分: ${student.total.toFixed(1)})`);
                    
                    // Find next available class
                    while (classes[classIdx].length >= classSizes[classIdx]) {
                        classIdx += direction;
                        
                        if (classIdx >= classNum) {
                            classIdx = classNum - 1;
                            direction = -1;
                        } else if (classIdx < 0) {
                            classIdx = 0;
                            direction = 1;
                        }
                    }
                    
                    // Add to class
                    classes[classIdx].push(student);
                    
                    // Create student icon
                    createStudentIcon(student, classIdx, classes[classIdx].length);
                    
                    // Update class label
                    updateClassLabel(classIdx);
                    
                    // Move to next student after delay
                    setTimeout(() => {
                        if (classIdx === 0 && direction === -1) {
                            direction = 1;
                        } else if (classIdx === classNum - 1 && direction === 1) {
                            direction = -1;
                        } else {
                            classIdx += direction;
                        }
                        
                        assignNextStudent(index + 1);
                    }, 300 / animationSpeed);
                };
                
                // Start assignment
                assignNextStudent(0);
            } catch (error) {
                logMessage(`错误: ${error.message}`);
                showError(`分班过程中出错: ${error.message}`);
                
                // Enable buttons
                [importBtn, startBtn, exportBtn, statsBtn, resetBtn].forEach(btn => {
                    btn.disabled = false;
                });
            }
        }

        function showAnimationWindow() {
            animationWindow.style.display = 'flex';
            animationCanvas.innerHTML = '';
            
            // Add total students label
            const totalLabel = document.createElement('div');
            totalLabel.textContent = `学生总数: ${studentData.length}`;
            totalLabel.style.position = 'absolute';
            totalLabel.style.top = '10px';
            totalLabel.style.left = '10px';
            totalLabel.style.fontFamily = '"Microsoft YaHei", sans-serif';
            totalLabel.style.fontSize = '12px';
            totalLabel.style.fontWeight = 'bold';
            animationCanvas.appendChild(totalLabel);
        }

        function closeAnimationWindow() {
            animationWindow.style.display = 'none';
        }

        function drawClassAreas() {
            const canvas = document.getElementById('animationCanvas');
            const canvasWidth = canvas.clientWidth;
            const canvasHeight = canvas.clientHeight;
            
            const marginX = 20;
            const marginY = 50;
            const availableWidth = Math.max(100, canvasWidth - 2 * marginX - (classNum - 1) * 15);
            const classWidth = Math.max(100, availableWidth / classNum);
            const classHeight = Math.max(100, canvasHeight - 2 * marginY - 100);
            const classStartX = marginX;
            const classStartY = marginY;
            
            // Clear previous class areas
            const existingAreas = document.querySelectorAll('.class-area');
            existingAreas.forEach(area => area.remove());
            
            // Create new class areas
            for (let i = 0; i < classNum; i++) {
                const classArea = document.createElement('div');
                classArea.className = 'class-area';
                classArea.style.left = `${classStartX + i * (classWidth + 15)}px`;
                classArea.style.top = `${classStartY}px`;
                classArea.style.width = `${classWidth}px`;
                classArea.style.height = `${classHeight}px`;
                
                // Add class label
                const classLabel = document.createElement('div');
                classLabel.textContent = `${i+1}班(0/${classSizes[i]})`;
                classLabel.style.position = 'absolute';
                classLabel.style.top = '20px';
                classLabel.style.left = '50%';
                classLabel.style.transform = 'translateX(-50%)';
                classLabel.style.fontFamily = '"Microsoft YaHei", sans-serif';
                classLabel.style.fontSize = '10px';
                classLabel.style.fontWeight = 'bold';
                classArea.appendChild(classLabel);
                
                classArea.dataset.classIndex = i;
                classArea.dataset.classLabel = classLabel;
                
                canvas.appendChild(classArea);
            }
        }

        function createStudentIcon(student, classIdx, studentIdxInClass) {
            const canvas = document.getElementById('animationCanvas');
            const classAreas = document.querySelectorAll('.class-area');
            const classArea = classAreas[classIdx];
            
            if (!classArea) return;
            
            const classRect = classArea.getBoundingClientRect();
            const canvasRect = canvas.getBoundingClientRect();
            
            const classWidth = classRect.width;
            const classHeight = classRect.height;
            const classLeft = classRect.left - canvasRect.left;
            const classTop = classRect.top - canvasRect.top;
            
            const maxPerCol = Math.max(1, Math.floor(classHeight / 30));
            const col = Math.floor((studentIdxInClass - 1) / maxPerCol);
            const row = (studentIdxInClass - 1) % maxPerCol;
            
            const x = classLeft + 15 + col * 40;
            const y = classTop + 60 + row * 30;
            
            const studentIcon = document.createElement('div');
            studentIcon.className = `student-icon ${student.gender === '女' ? 'female' : 'male'}`;
            studentIcon.style.left = `${x}px`;
            studentIcon.style.top = `${y}px`;
            
            const nameText = document.createElement('div');
            nameText.textContent = student.name;
            nameText.style.fontSize = '8px';
            nameText.style.overflow = 'hidden';
            nameText.style.textOverflow = 'ellipsis';
            nameText.style.whiteSpace = 'nowrap';
            studentIcon.appendChild(nameText);
            
            canvas.appendChild(studentIcon);
            
            // Store reference for later updates
            if (!studentIcons[student.id]) {
                studentIcons[student.id] = [];
            }
            studentIcons[student.id].push({
                element: studentIcon,
                classIdx: classIdx
            });
        }

        function updateClassLabel(classIdx) {
            const classAreas = document.querySelectorAll('.class-area');
            const classArea = classAreas[classIdx];
            
            if (classArea && classArea.dataset.classLabel) {
                const classLabel = classArea.dataset.classLabel;
                classLabel.textContent = `${classIdx+1}班(${classes[classIdx].length}/${classSizes[classIdx]})`;
            }
        }

        function updateAnimation() {
            // In a real implementation, this would update the animation
            // to reflect any student movements during optimization
        }

        function handleNameAndTwinConstraints() {
            // Process same name constraints
            if (studentData.some(s => s.sameName)) {
                logMessage('开始处理同名或近音学生约束 (基于 "同名或近音" 列数字)...');
                
                // Group students by sameName value
                const sameNameGroups = {};
                studentData.forEach(student => {
                    if (student.sameName) {
                        if (!sameNameGroups[student.sameName]) {
                            sameNameGroups[student.sameName] = [];
                        }
                        sameNameGroups[student.sameName].push(student);
                    }
                });
                
                // Process each group
                Object.entries(sameNameGroups).forEach(([groupKey, groupStudents]) => {
                    if (groupStudents.length < 2) {
                        logMessage(`同名或近音组 ${groupKey} 只有 ${groupStudents.length} 个成员,跳过。`);
                        return;
                    }
                    
                    logMessage(`处理同名或近音组 ${groupKey}, 成员数: ${groupStudents.length}`);
                    
                    // Find current class assignments
                    const memberInfo = [];
                    
                    groupStudents.forEach(student => {
                        for (let classIdx = 0; classIdx < classes.length; classIdx++) {
                            const studentIdx = classes[classIdx].findIndex(s => s.id === student.id);
                            if (studentIdx !== -1) {
                                memberInfo.push({
                                    student,
                                    classIdx,
                                    studentIdx
                                });
                                break;
                            }
                        }
                    });
                    
                    if (memberInfo.length !== groupStudents.length) {
                        logMessage(`警告: 同名或近音组 ${groupKey} 成员定位不完整 (${memberInfo.length}/${groupStudents.length})`);
                    }
                    
                    if (memberInfo.length < 2) {
                        logMessage(`信息: 同名或近音组 ${groupKey} 找到的有效成员少于2人,无需处理。`);
                        return;
                    }
                    
                    // Check for students in same class
                    const classCounts = {};
                    memberInfo.forEach(info => {
                        classCounts[info.classIdx] = (classCounts[info.classIdx] || 0) + 1;
                    });
                    
                    const classesWithMultiple = Object.entries(classCounts)
                        .filter(([_, count]) => count > 1)
                        .map(([classIdx]) => parseInt(classIdx));
                    
                    if (classesWithMultiple.length === 0) {
                        logMessage(`该组同名或近音学生已满足"不同班"要求。`);
                        return;
                    }
                    
                    // Move extra students from overfilled classes
                    classesWithMultiple.forEach(classIdx => {
                        const studentsInClass = memberInfo.filter(info => info.classIdx === classIdx);
                        const numToMove = studentsInClass.length - 1;
                        
                        if (numToMove <= 0) return;
                        
                        logMessage(`班级 ${classIdx+1} 有 ${studentsInClass.length} 个该组同名或近音成员,需移动 ${numToMove} 个。`);
                        
                        // Select students to move (skip the first one)
                        const studentsToMove = studentsInClass.slice(1, numToMove + 1);
                        
                        // Remove from current class
                        studentsToMove.forEach(info => {
                            classes[info.classIdx].splice(info.studentIdx, 1);
                        });
                        
                        // Find available classes
                        const availableClasses = Array.from({ length: classNum }, (_, i) => i)
                            .filter(i => i !== classIdx);
                            
                        if (availableClasses.length === 0) {
                            logMessage('警告: 只有一个班级,无法分开同名或近音学生。');
                            // Add back to original class
                            studentsToMove.forEach(info => {
                                classes[classIdx].push(info.student);
                            });
                            return;
                        }
                        
                        // Distribute to other classes
                        studentsToMove.forEach((info, i) => {
                            const targetClass = availableClasses[i % availableClasses.length];
                            classes[targetClass].push(info.student);
                            logMessage(`将同名或近音学生 ${info.student.name} 从班级 ${classIdx+1} 移动到班级 ${targetClass+1}`);
                        });
                    });
                });
                
                logMessage('同名或近音学生约束处理完成。');
            }
            
            // Process twins constraints
            if (studentData.some(s => s.twins)) {
                logMessage('开始处理多胞胎约束...');
                
                // Group students by twins value
                const twinsGroups = {};
                studentData.forEach(student => {
                    if (student.twins) {
                        if (!twinsGroups[student.twins]) {
                            twinsGroups[student.twins] = [];
                        }
                        twinsGroups[student.twins].push(student);
                    }
                });
                
                // Process each group
                Object.entries(twinsGroups).forEach(([groupKey, groupStudents]) => {
                    if (groupStudents.length < 2) {
                        logMessage(`多胞胎组 ${groupKey} 只有 ${groupStudents.length} 个成员,跳过。`);
                        return;
                    }
                    
                    logMessage(`处理多胞胎组 ${groupKey}, 成员数: ${groupStudents.length}`);
                    
                    // Determine constraint (default is separate classes)
                    let sameClass = false;
                    const firstStudent = groupStudents[0];
                    if (firstStudent.twinsSameClass && 
                        ['是', 'yes', 'true', '1'].includes(firstStudent.twinsSameClass.toLowerCase())) {
                        sameClass = true;
                    }
                    
                    logMessage(`组 ${groupKey} 约束: ${sameClass ? '同班' : '不同班'}`);
                    
                    // Find current class assignments
                    const memberInfo = [];
                    
                    groupStudents.forEach(student => {
                        for (let classIdx = 0; classIdx < classes.length; classIdx++) {
                            const studentIdx = classes[classIdx].findIndex(s => s.id === student.id);
                            if (studentIdx !== -1) {
                                memberInfo.push({
                                    student,
                                    classIdx,
                                    studentIdx
                                });
                                break;
                            }
                        }
                    });
                    
                    if (memberInfo.length !== groupStudents.length) {
                        logMessage(`警告: 多胞胎组 ${groupKey} 成员定位不完整 (${memberInfo.length}/${groupStudents.length})`);
                    }
                    
                    if (memberInfo.length < 2) {
                        logMessage(`信息: 多胞胎组 ${groupKey} 找到的有效成员少于2人,无需处理。`);
                        return;
                    }
                    
                    if (sameClass) {
                        // All should be in same class
                        logMessage('执行 "同班" 约束...');
                        
                        // Find the class with most members
                        const classCounts = {};
                        memberInfo.forEach(info => {
                            classCounts[info.classIdx] = (classCounts[info.classIdx] || 0) + 1;
                        });
                        
                        const targetClass = parseInt(Object.entries(classCounts)
                            .sort((a, b) => b[1] - a[1])[0][0]);
                        
                        logMessage(`目标班级: ${targetClass+1} (当前有 ${classCounts[targetClass]} 名该组成员)`);
                        
                        // Check if all are already in target class
                        if (memberInfo.every(info => info.classIdx === targetClass)) {
                            logMessage('所有成员已在此班级,无需移动。');
                            return;
                        }
                        
                        // Move all to target class
                        const studentsToMove = memberInfo.filter(info => info.classIdx !== targetClass)
                            .map(info => info.student);
                        
                        // Remove from current classes
                        memberInfo.forEach(info => {
                            if (info.classIdx !== targetClass) {
                                classes[info.classIdx].splice(info.studentIdx, 1);
                            }
                        });
                        
                        // Add to target class
                        studentsToMove.forEach(student => {
                            classes[targetClass].push(student);
                        });
                        
                        const names = studentsToMove.map(s => s.name).join(', ');
                        logMessage(`已将多胞胎 ${names} 移动到班级 ${targetClass + 1}`);
                    } else {
                        // All should be in different classes
                        logMessage('执行 "不同班" 约束...');
                        
                        // Check for students in same class
                        const classCounts = {};
                        memberInfo.forEach(info => {
                            classCounts[info.classIdx] = (classCounts[info.classIdx] || 0) + 1;
                        });
                        
                        const classesWithMultiple = Object.entries(classCounts)
                            .filter(([_, count]) => count > 1)
                            .map(([classIdx]) => parseInt(classIdx));
                            
                        if (classesWithMultiple.length === 0) {
                            logMessage('该组多胞胎已满足"不同班"要求。');
                            return;
                        }
                        
                        // Move extra students from overfilled classes
                        classesWithMultiple.forEach(classIdx => {
                            const studentsInClass = memberInfo.filter(info => info.classIdx === classIdx);
                            const numToMove = studentsInClass.length - 1;
                            
                            if (numToMove <= 0) return;
                            
                            logMessage(`班级 ${classIdx+1} 有 ${studentsInClass.length} 个该组成员,需移动 ${numToMove} 个。`);
                            
                            // Select students to move (skip the first one)
                            const studentsToMove = studentsInClass.slice(1, numToMove + 1);
                            
                            // Remove from current class
                            studentsToMove.forEach(info => {
                                classes[info.classIdx].splice(info.studentIdx, 1);
                            });
                            
                            // Find available classes
                            const availableClasses = Array.from({ length: classNum }, (_, i) => i)
                                .filter(i => i !== classIdx);
                                
                            if (availableClasses.length === 0) {
                                logMessage('警告: 无可分配的其他班级,部分多胞胎成员可能仍在一起。');
                                // Add back to original class
                                studentsToMove.forEach(info => {
                                    classes[classIdx].push(info.student);
                                });
                                return;
                            }
                            
                            // Distribute to other classes
                            studentsToMove.forEach((info, i) => {
                                const targetClass = availableClasses[i % availableClasses.length];
                                classes[targetClass].push(info.student);
                                logMessage(`将多胞胎 ${info.student.name} 从班级 ${classIdx+1} 移动到班级 ${targetClass+1}`);
                            });
                        });
                    }
                });
                
                logMessage('多胞胎约束处理完成。');
            }
        }

        function optimizeAssignment() {
            let optimized = false;
            let iteration = 0;
            let noImprovementCount = 0;
            const maxNoImprovement = 15;
            
            // Ensure class sizes are correct
            enforceClassSizes();
            
            // Calculate initial stats
            const classStats = calculateClassStats();
            
            // Track subject priorities
            const subjectPriorities = {
                chinese: 1.0,
                math: 1.0,
                english: 1.0,
                science: 1.0,
                total: 1.0
            };
            
            while (iteration < maxIterations) {
                iteration++;
                let improved = false;
                
                // Update priorities based on current differences
                const subjects = ['chinese', 'math', 'english', 'science', 'total'];
                subjects.forEach(sub => {
                    if (classStats[sub].length > 0) {
                        const currentDiff = Math.max(...classStats[sub]) - Math.min(...classStats[sub]);
                        subjectPriorities[sub] = Math.max(1.0, currentDiff * 2);
                    }
                });
                
                // Try student swaps
                for (let i = 0; i < classNum; i++) {
                    for (let j = i + 1; j < classNum; j++) {
                        // Focus on subject with highest priority
                        const focusSubject = Object.keys(subjectPriorities)
                            .reduce((a, b) => subjectPriorities[a] > subjectPriorities[b] ? a : b);
                            
                        if (trySwapStudents(i, j, classStats, focusSubject)) {
                            improved = true;
                            updateClassStats(classStats, i);
                            updateClassStats(classStats, j);
                            break;
                        }
                    }
                    if (improved) break;
                }
                
                // Check if balanced
                if (checkScoreBalanceCondition(classStats)) {
                    optimized = true;
                    logMessage(`迭代 ${iteration}: 满足成绩平衡条件 (差异 < ${balanceThreshold}分),优化完成!`);
                    break;
                }
                
                // Early termination check
                if (!improved) {
                    noImprovementCount++;
                    if (noImprovementCount >= maxNoImprovement) {
                        logMessage(`连续${maxNoImprovement}次无改进,提前终止迭代`);
                        break;
                    }
                } else {
                    noImprovementCount = 0;
                }
                
                // Log progress every 50 iterations
                if (iteration % 50 === 0 || optimized) {
                    logMessage(`迭代 ${iteration}: 继续优化...`);
                    calculateAndLogStats();
                }
            }
            
            return optimized;
        }

        function enforceClassSizes() {
            const totalStudents = classes.reduce((sum, cls) => sum + cls.length, 0);
            const baseSize = Math.floor(totalStudents / classNum);
            const extra = totalStudents % classNum;
            const targetSizes = Array.from({ length: classNum }, (_, i) => baseSize + (i < extra ? 1 : 0));
            
            for (let classIdx = 0; classIdx < classNum; classIdx++) {
                const currentSize = classes[classIdx].length;
                const targetSize = targetSizes[classIdx];
                
                if (currentSize > targetSize) {
                    logMessage(`班级 ${classIdx+1} 人数过多 (${currentSize} > ${targetSize}), 调整中...`);
                    
                    let attempts = 0;
                    const maxAttempts = currentSize - targetSize + 5;
                    
                    while (classes[classIdx].length > targetSize && attempts < maxAttempts) {
                        attempts++;
                        
                        if (classes[classIdx].length === 0) {
                            logMessage(`警告: 尝试从空班级 ${classIdx+1} 移除学生`);
                            break;
                        }
                        
                        const classStudents = classes[classIdx];
                        const meanScore = classStudents.reduce((sum, s) => sum + s.total, 0) / classStudents.length;
                        
                        // Find student with score closest to mean
                        let closestIdx = 0;
                        let minDiff = Math.abs(classStudents[0].total - meanScore);
                        
                        for (let i = 1; i < classStudents.length; i++) {
                            const diff = Math.abs(classStudents[i].total - meanScore);
                            if (diff < minDiff) {
                                minDiff = diff;
                                closestIdx = i;
                            }
                        }
                        
                        // Remove student
                        const student = classes[classIdx].splice(closestIdx, 1)[0];
                        let studentAdded = false;
                        
                        // Try to add to another class with space
                        for (let otherIdx = 0; otherIdx < classNum; otherIdx++) {
                            if (otherIdx !== classIdx && classes[otherIdx].length < targetSizes[otherIdx]) {
                                classes[otherIdx].push(student);
                                logMessage(`将 ${student.name} 从班级 ${classIdx+1} 移动到班级 ${otherIdx+1}`);
                                studentAdded = true;
                                break;
                            }
                        }
                        
                        if (!studentAdded) {
                            // Add to smallest class
                            const smallestClass = classes.reduce((minIdx, cls, idx) => 
                                cls.length < classes[minIdx].length ? idx : minIdx, 0);
                            classes[smallestClass].push(student);
                            logMessage(`将 ${student.name} 从班级 ${classIdx+1} 移动到班级 ${smallestClass+1} (备用方案)`);
                        }
                    }
                } else if (currentSize < targetSize) {
                    logMessage(`班级 ${classIdx+1} 人数不足 (${currentSize} < ${targetSize}), 调整中...`);
                    
                    let attempts = 0;
                    const maxAttempts = targetSize - currentSize + 5;
                    
                    while (classes[classIdx].length < targetSize && attempts < maxAttempts) {
                        attempts++;
                        let studentMoved = false;
                        
                        // Try to find a student from another class to move
                        for (let otherIdx = 0; otherIdx < classNum; otherIdx++) {
                            if (otherIdx !== classIdx && classes[otherIdx].length > targetSizes[otherIdx] && 
                                classes[otherIdx].length > 0) {
                                
                                const otherStudents = classes[otherIdx];
                                const otherMean = otherStudents.reduce((sum, s) => sum + s.total, 0) / otherStudents.length;
                                
                                // Find student with score closest to mean
                                let closestIdx = 0;
                                let minDiff = Math.abs(otherStudents[0].total - otherMean);
                                
                                for (let i = 1; i < otherStudents.length; i++) {
                                    const diff = Math.abs(otherStudents[i].total - otherMean);
                                    if (diff < minDiff) {
                                        minDiff = diff;
                                        closestIdx = i;
                                    }
                                }
                                
                                // Move student
                                const student = classes[otherIdx].splice(closestIdx, 1)[0];
                                classes[classIdx].push(student);
                                logMessage(`将 ${student.name} 从班级 ${otherIdx+1} 移动到班级 ${classIdx+1}`);
                                studentMoved = true;
                                break;
                            }
                        }
                        
                        if (!studentMoved) {
                            logMessage(`警告: 无法为班级 ${classIdx+1} 找到更多学生进行补充`);
                            break;
                        }
                    }
                }
            }
        }

        function trySwapStudents(classA, classB, classStats, focusSubject) {
            if (classA >= classes.length || classB >= classes.length) {
                return false;
            }
            
            const classASize = classes[classA].length;
            const classBSize = classes[classB].length;
            
            // Determine how many candidates to consider
            const sampleSizeA = Math.max(2, Math.min(4, Math.floor(classASize / 10)));
            const sampleSizeB = Math.max(2, Math.min(4, Math.floor(classBSize / 10)));
            
            // Sort classes by total score
            const sortedClassA = [...classes[classA]].sort((a, b) => b.total - a.total);
            const sortedClassB = [...classes[classB]].sort((a, b) => b.total - a.total);
            
            // Select candidates - top and bottom performers
            const candidatesA = [
                ...sortedClassA.slice(0, sampleSizeA),
                ...sortedClassA.slice(-sampleSizeA)
            ];
            
            const candidatesB = [
                ...sortedClassB.slice(0, sampleSizeB),
                ...sortedClassB.slice(-sampleSizeB)
            ];
            
            let bestImprovement = -1;
            let bestPair = null;
            
            // Get current stats for quick calculation
            const statsA = {
                chinese: classStats.chinese[classA],
                math: classStats.math[classA],
                english: classStats.english[classA],
                science: classStats.science[classA],
                total: classStats.total[classA],
                male: classStats.male[classA],
                female: classStats.female[classA],
                count: classStats.count[classA]
            };
            
            const statsB = {
                chinese: classStats.chinese[classB],
                math: classStats.math[classB],
                english: classStats.english[classB],
                science: classStats.science[classB],
                total: classStats.total[classB],
                male: classStats.male[classB],
                female: classStats.female[classB],
                count: classStats.count[classB]
            };
            
            // Evaluate possible swaps
            for (const studentA of candidatesA) {
                for (const studentB of candidatesB) {
                    // Calculate temporary stats after swap
                    const tempStatsA = calculateTempStats(statsA, studentA, studentB);
                    const tempStatsB = calculateTempStats(statsB, studentB, studentA);
                    
                    // Calculate improvement
                    const improvement = calculateImprovement(
                        classStats, 
                        tempStatsA, tempStatsB, 
                        classA, classB, 
                        focusSubject
                    );
                    
                    // Track best swap
                    if (improvement > bestImprovement) {
                        bestImprovement = improvement;
                        bestPair = { studentA, studentB };
                    }
                }
            }
            
            // Execute best swap if improvement is significant
            if (bestImprovement > 0.01 && bestPair) {
                const { studentA, studentB } = bestPair;
                
                // Find indices
                const indexA = classes[classA].findIndex(s => s.id === studentA.id);
                const indexB = classes[classB].findIndex(s => s.id === studentB.id);
                
                if (indexA !== -1 && indexB !== -1) {
                    // Perform swap
                    classes[classA][indexA] = studentB;
                    classes[classB][indexB] = studentA;
                    
                    logMessage(`交换学生: ${studentA.name}(班${classA+1}) <-> ${studentB.name}(班${classB+1}), 改善: ${bestImprovement.toFixed(4)}`);
                    return true;
                }
            }
            
            return false;
        }

        function calculateTempStats(stats, outStudent, inStudent) {
            const n = stats.count;
            if (n === 0) return stats;
            
            const newStats = { ...stats };
            
            // Update scores
            newStats.chinese = ((stats.chinese * n - outStudent.chinese + inStudent.chinese) / n);
            newStats.math = ((stats.math * n - outStudent.math + inStudent.math) / n);
            newStats.english = ((stats.english * n - outStudent.english + inStudent.english) / n);
            newStats.science = ((stats.science * n - outStudent.science + inStudent.science) / n);
            newStats.total = ((stats.total * n - outStudent.total + inStudent.total) / n);
            
            // Update gender counts
            if (outStudent.gender === '男') newStats.male--;
            if (outStudent.gender === '女') newStats.female--;
            if (inStudent.gender === '男') newStats.male++;
            if (inStudent.gender === '女') newStats.female++;
            
            return newStats;
        }

        function calculateImprovement(classStats, tempStatsA, tempStatsB, classA, classB, focusSubject) {
            let improvement = 0;
            const subjects = ['chinese', 'math', 'english', 'science', 'total'];
            
            // Weights - higher for focus subject
            const weights = {
                chinese: focusSubject === 'chinese' ? 1.5 : 1.0,
                math: focusSubject === 'math' ? 1.5 : 1.0,
                english: focusSubject === 'english' ? 1.5 : 1.0,
                science: focusSubject === 'science' ? 1.5 : 1.0,
                total: focusSubject === 'total' ? 1.5 : 1.0
            };
            
            // Calculate improvement for each subject
            subjects.forEach(sub => {
                if (!classStats[sub] || classStats[sub].length === 0) return;
                
                // Current max and min
                const currentMax = Math.max(...classStats[sub]);
                const currentMin = Math.min(...classStats[sub]);
                const currentDiff = currentMax - currentMin;
                
                // New max and min (only updating the two classes involved)
                const newValues = [...classStats[sub]];
                newValues[classA] = tempStatsA[sub];
                newValues[classB] = tempStatsB[sub];
                
                const newMax = Math.max(...newValues);
                const newMin = Math.min(...newValues);
                const newDiff = newMax - newMin;
                
                // Improvement is reduction in difference
                improvement += (currentDiff - newDiff) * weights[sub];
            });
            
            return improvement;
        }

        function checkScoreBalanceCondition(classStats) {
            const subjects = ['chinese', 'math', 'english', 'science', 'total'];
            
            for (const sub of subjects) {
                if (!classStats[sub] || classStats[sub].length === 0) continue;
                
                const maxVal = Math.max(...classStats[sub]);
                const minVal = Math.min(...classStats[sub]);
                
                if (maxVal - minVal > balanceThreshold) {
                    return false;
                }
            }
            
            return true;
        }

        function calculateClassStats() {
            const stats = {
                class: [],
                count: [],
                male: [],
                female: [],
                chinese: [],
                math: [],
                english: [],
                science: [],
                total: []
            };
            
            for (let i = 0; i < classes.length; i++) {
                const classStudents = classes[i];
                stats.class.push(`班级 ${i+1}`);
                stats.count.push(classStudents.length);
                
                // Gender counts
                const maleCount = classStudents.filter(s => s.gender === '男').length;
                const femaleCount = classStudents.filter(s => s.gender === '女').length;
                stats.male.push(maleCount);
                stats.female.push(femaleCount);
                
                // Subject averages
                const chineseAvg = classStudents.reduce((sum, s) => sum + s.chinese, 0) / classStudents.length || 0;
                const mathAvg = classStudents.reduce((sum, s) => sum + s.math, 0) / classStudents.length || 0;
                const englishAvg = classStudents.reduce((sum, s) => sum + s.english, 0) / classStudents.length || 0;
                const scienceAvg = classStudents.reduce((sum, s) => sum + s.science, 0) / classStudents.length || 0;
                const totalAvg = classStudents.reduce((sum, s) => sum + s.total, 0) / classStudents.length || 0;
                
                stats.chinese.push(parseFloat(chineseAvg.toFixed(1)));
                stats.math.push(parseFloat(mathAvg.toFixed(1)));
                stats.english.push(parseFloat(englishAvg.toFixed(1)));
                stats.science.push(parseFloat(scienceAvg.toFixed(1)));
                stats.total.push(parseFloat(totalAvg.toFixed(1)));
            }
            
            return stats;
        }

        function updateClassStats(classStats, classIdx) {
            if (classIdx >= classes.length || classes[classIdx].length === 0) {
                classStats.count[classIdx] = 0;
                classStats.male[classIdx] = 0;
                classStats.female[classIdx] = 0;
                classStats.chinese[classIdx] = 0;
                classStats.math[classIdx] = 0;
                classStats.english[classIdx] = 0;
                classStats.science[classIdx] = 0;
                classStats.total[classIdx] = 0;
                return;
            }
            
            const classStudents = classes[classIdx];
            classStats.count[classIdx] = classStudents.length;
            
            // Gender counts
            const maleCount = classStudents.filter(s => s.gender === '男').length;
            const femaleCount = classStudents.filter(s => s.gender === '女').length;
            classStats.male[classIdx] = maleCount;
            classStats.female[classIdx] = femaleCount;
            
            // Subject averages
            const chineseAvg = classStudents.reduce((sum, s) => sum + s.chinese, 0) / classStudents.length || 0;
            const mathAvg = classStudents.reduce((sum, s) => sum + s.math, 0) / classStudents.length || 0;
            const englishAvg = classStudents.reduce((sum, s) => sum + s.english, 0) / classStudents.length || 0;
            const scienceAvg = classStudents.reduce((sum, s) => sum + s.science, 0) / classStudents.length || 0;
            const totalAvg = classStudents.reduce((sum, s) => sum + s.total, 0) / classStudents.length || 0;
            
            classStats.chinese[classIdx] = parseFloat(chineseAvg.toFixed(1));
            classStats.math[classIdx] = parseFloat(mathAvg.toFixed(1));
            classStats.english[classIdx] = parseFloat(englishAvg.toFixed(1));
            classStats.science[classIdx] = parseFloat(scienceAvg.toFixed(1));
            classStats.total[classIdx] = parseFloat(totalAvg.toFixed(1));
        }

        function calculateAndLogStats() {
            const stats = calculateClassStats();
            logMessage('\n班级统计:');
            
            // Determine column widths
            const headers = ['班级', '人数', '男生', '女生', '语文均分', '数学均分', '英语均分', '科学均分', '总分均分'];
            const colWidths = headers.map(h => h.length);
            
            // Update column widths based on data
            for (let i = 0; i < stats.class.length; i++) {
                colWidths[0] = Math.max(colWidths[0], stats.class[i].length);
                colWidths[1] = Math.max(colWidths[1], stats.count[i].toString().length);
                colWidths[2] = Math.max(colWidths[2], stats.male[i].toString().length);
                colWidths[3] = Math.max(colWidths[3], stats.female[i].toString().length);
                colWidths[4] = Math.max(colWidths[4], stats.chinese[i].toFixed(1).length);
                colWidths[5] = Math.max(colWidths[5], stats.math[i].toFixed(1).length);
                colWidths[6] = Math.max(colWidths[6], stats.english[i].toFixed(1).length);
                colWidths[7] = Math.max(colWidths[7], stats.science[i].toFixed(1).length);
                colWidths[8] = Math.max(colWidths[8], stats.total[i].toFixed(1).length);
            }
            
            // Create header row
            let headerRow = '';
            headers.forEach((header, i) => {
                headerRow += header.padEnd(colWidths[i] + 2);
            });
            logMessage(headerRow);
            logMessage('-'.repeat(headerRow.length));
            
            // Create data rows
            for (let i = 0; i < stats.class.length; i++) {
                let row = '';
                row += stats.class[i].padEnd(colWidths[0] + 2);
                row += stats.count[i].toString().padEnd(colWidths[1] + 2);
                row += stats.male[i].toString().padEnd(colWidths[2] + 2);
                row += stats.female[i].toString().padEnd(colWidths[3] + 2);
                row += stats.chinese[i].toFixed(1).padEnd(colWidths[4] + 2);
                row += stats.math[i].toFixed(1).padEnd(colWidths[5] + 2);
                row += stats.english[i].toFixed(1).padEnd(colWidths[6] + 2);
                row += stats.science[i].toFixed(1).padEnd(colWidths[7] + 2);
                row += stats.total[i].toFixed(1).padEnd(colWidths[8] + 2);
                logMessage(row);
            }
            
            // Calculate max differences
            const chineseDiff = Math.max(...stats.chinese) - Math.min(...stats.chinese);
            const mathDiff = Math.max(...stats.math) - Math.min(...stats.math);
            const englishDiff = Math.max(...stats.english) - Math.min(...stats.english);
            const scienceDiff = Math.max(...stats.science) - Math.min(...stats.science);
            const totalDiff = Math.max(...stats.total) - Math.min(...stats.total);
            
            logMessage('\n各科最大差异:');
            logMessage(`语文: ${chineseDiff.toFixed(2)}`);
            logMessage(`数学: ${mathDiff.toFixed(2)}`);
            logMessage(`英语: ${englishDiff.toFixed(2)}`);
            logMessage(`科学: ${scienceDiff.toFixed(2)}`);
            logMessage(`总分: ${totalDiff.toFixed(2)}`);
            
            return stats;
        }

        function showStatistics() {
            if (!classes || classes.length === 0 || classes.every(c => c.length === 0)) {
                showError('请先完成分班');
                return;
            }
            
            // Switch to stats tab
            tabs.forEach(t => t.classList.remove('active'));
            tabContents.forEach(c => c.classList.remove('active'));
            document.querySelector('.tab[data-tab="stats"]').classList.add('active');
            document.getElementById('statsTab').classList.add('active');
            
            // Update stats table
            const stats = calculateClassStats();
            statsTableBody.innerHTML = '';
            
            for (let i = 0; i < stats.class.length; i++) {
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td>${stats.class[i]}</td>
                    <td>${stats.count[i]}</td>
                    <td>${stats.male[i]}</td>
                    <td>${stats.female[i]}</td>
                    <td>${stats.chinese[i].toFixed(1)}</td>
                    <td>${stats.math[i].toFixed(1)}</td>
                    <td>${stats.english[i].toFixed(1)}</td>
                    <td>${stats.science[i].toFixed(1)}</td>
                    <td>${stats.total[i].toFixed(1)}</td>
                `;
                statsTableBody.appendChild(row);
            }
            
            // Update max differences
            const chineseDiff = Math.max(...stats.chinese) - Math.min(...stats.chinese);
            const mathDiff = Math.max(...stats.math) - Math.min(...stats.math);
            const englishDiff = Math.max(...stats.english) - Math.min(...stats.english);
            const scienceDiff = Math.max(...stats.science) - Math.min(...stats.science);
            const totalDiff = Math.max(...stats.total) - Math.min(...stats.total);
            
            document.getElementById('chineseDiff').textContent = chineseDiff.toFixed(2);
            document.getElementById('mathDiff').textContent = mathDiff.toFixed(2);
            document.getElementById('englishDiff').textContent = englishDiff.toFixed(2);
            document.getElementById('scienceDiff').textContent = scienceDiff.toFixed(2);
            document.getElementById('totalDiff').textContent = totalDiff.toFixed(2);
            
            // Color code based on threshold
            const setDiffClass = (element, diff) => {
                element.className = diff <= balanceThreshold ? 'diff-value good' : 'diff-value bad';
            };
            
            setDiffClass(document.getElementById('chineseDiff'), chineseDiff);
            setDiffClass(document.getElementById('mathDiff'), mathDiff);
            setDiffClass(document.getElementById('englishDiff'), englishDiff);
            setDiffClass(document.getElementById('scienceDiff'), scienceDiff);
            setDiffClass(document.getElementById('totalDiff'), totalDiff);
        }

        function exportResults() {
			// 检查XLSX库是否可用
			if (typeof XLSX === 'undefined') {
				showError('Excel导出功能需要XLSX库,请确保网络连接正常');
				return;
			}

			if (!classes || classes.length === 0 || classes.every(c => c.length === 0)) {
				showError('请先完成分班');
				return;
			}
			
			try {
				// Create a workbook
				const wb = XLSX.utils.book_new();
				
				// 1. Create "全校" sheet with all students
				const allStudents = [];
				classes.forEach((classStudents, classIdx) => {
					classStudents.forEach(student => {
						const studentWithClass = {
							'序号': student.serial,
							'姓名': student.name,
							'性别': student.gender,
							'语文': student.chinese,
							'数学': student.math,
							'英语': student.english,
							'科学': student.science,
							'总分': student.total,
							'同名或近音': student.sameName,
							'多胞胎': student.twins,
							'多胞胎同班': student.twinsSameClass,
							'班级': `${classIdx + 1}班`
						};
						allStudents.push(studentWithClass);
					});
				});
				
				if (allStudents.length > 0) {
					const allStudentsWS = XLSX.utils.json_to_sheet(allStudents);
					XLSX.utils.book_append_sheet(wb, allStudentsWS, "全校");
				}
				
				// 2. Create sheets for each class
				classes.forEach((classStudents, classIdx) => {
					if (classStudents.length > 0) {
						const classStudentsWithCNHeaders = classStudents.map(student => ({
							'序号': student.serial,
							'姓名': student.name,
							'性别': student.gender,
							'语文': student.chinese,
							'数学': student.math,
							'英语': student.english,
							'科学': student.science,
							'总分': student.total,
							'同名或近音': student.sameName,
							'多胞胎': student.twins,
							'多胞胎同班': student.twinsSameClass
						}));
						const classWS = XLSX.utils.json_to_sheet(classStudentsWithCNHeaders);
						XLSX.utils.book_append_sheet(wb, classWS, `${classIdx + 1}班`);
					}
				});
				
				// 3. Create stats sheet
				const stats = calculateClassStats();
				const statsData = [];
				
				for (let i = 0; i < stats.class.length; i++) {
					statsData.push({
						'班级': stats.class[i],
						'人数': stats.count[i],
						'男生': stats.male[i],
						'女生': stats.female[i],
						'语文均分': stats.chinese[i],
						'数学均分': stats.math[i],
						'英语均分': stats.english[i],
						'科学均分': stats.science[i],
						'总分均分': stats.total[i]
					});
				}
				
				if (statsData.length > 0) {
					const statsWS = XLSX.utils.json_to_sheet(statsData);
					XLSX.utils.book_append_sheet(wb, statsWS, "分班统计");
				}
				
				// Generate file and trigger download
				const fileName = prompt("请输入要保存的文件名(不带扩展名):", "分班结果");
				if (fileName) {
					XLSX.writeFile(wb, `${fileName}.xlsx`);
					logMessage(`分班结果已导出至: ${fileName}.xlsx`);
				}
			} catch (error) {
				showError(`导出Excel时出错: ${error.message}`);
			}
		}

        function resetSystem() {
            showModal('确认', '确定要重置系统吗?当前分班结果将丢失。', () => {
                studentData = null;
                classes = [];
                studentIcons = {};
                classSizes = [];
                
                dataTableBody.innerHTML = '';
                statsTableBody.innerHTML = '';
                logContent.innerHTML = '';
                
                exportBtn.disabled = true;
                statsBtn.disabled = true;
                
                updateStatus('就绪');
            });
        }
    </script>
</body>
</html>


2024年七年级新生分班数据 - 测试.rar (14.43 KB, 下载次数: 333)

免费评分

参与人数 9吾爱币 +14 热心值 +8 收起 理由
windby + 1 + 1 热心回复!
SangerTom + 1 我很赞同!
Vic91025 + 1 + 1 谢谢@Thanks!
weidechan + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
leweishang + 1 + 1 热心回复!
smartfind + 1 + 1 用心讨论,共获提升!
untitled15X + 1 + 1 我很赞同!
long8586 + 1 + 1 谢谢@Thanks!
hrh123 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| 风子是我 发表于 2025-10-22 17:00 |楼主
邑城銘品 发表于 2025-10-20 15:36
https://www.52pojie.cn/forum.php?mod=viewthread&tid=1194966&extra=&highlight=%C7%DA%D5%DCexcel&page= ...

原帖子不能编辑了,我在这里补一下吧:
通过网盘分享的文件:勤哲Excel2010和谐机.rar
链接: https://pan.baidu.com/s/1jBUzTP0YYTEZEuVw5LMQQg 提取码: 52pj
--这仅仅是和谐机,原程序你有吧?

没有的话下面下:
通过网盘分享的文件:2010旗舰版V9.4.124
链接: https://pan.baidu.com/s/1Ut6zFH_LXvIkge-OIP70xw 提取码: 52pj
--来自百度网盘超级会员v8的分享
推荐
邑城銘品 发表于 2025-10-20 15:36
https://www.52pojie.cn/forum.php?mod=viewthread&tid=1194966&extra=&highlight=%C7%DA%D5%DCexcel&page=1

大哥能给我和谐机吗?你分享的百度链接不存在了。
补上和谐机(只有2010的,2013的被杀毒软件干掉了,我也没有了)
链接:https://pan.baidu.com/s/1a0kbAPnSh-YfXbExW7sYuQ
提取码:bzvx
沙发
ysjd22 发表于 2025-8-13 15:17
3#
5719sky 发表于 2025-8-13 15:18
厉害了,学习了。
4#
linbsp 发表于 2025-8-13 15:26
感谢分享!
5#
heikexiaohei 发表于 2025-8-13 15:30
感谢分享!!
6#
superstarzjh 发表于 2025-8-13 15:44

感谢分享!!
7#
Shaedow 发表于 2025-8-13 16:33
好东西 感谢分享
8#
nexter1980 发表于 2025-8-13 16:46
已经找了两天了,终于在这里找到。
9#
long8586 发表于 2025-8-13 16:48
虽然用不上,但看起来很不错啊!
10#
z350669159 发表于 2025-8-13 16:50
感谢分享,可以加以利用
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-17 01:18

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表