1
0
Fork 0
ClassroomAssistant/index.html

1707 lines
45 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>课堂小助手</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #ffffff;
min-height: 100vh;
color: #333;
}
.title-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 9999;
display: flex;
justify-content: space-between;
align-items: center;
background: #f8f9fa;
padding: 8px 15px;
border-bottom: 1px solid #e9ecef;
-webkit-app-region: drag;
user-select: none;
}
.title-bar-left {
display: flex;
align-items: center;
gap: 10px;
}
.app-icon {
width: 24px;
height: 24px;
border-radius: 4px;
}
.app-title {
font-size: 14px;
font-weight: 600;
color: #495057;
}
.title-bar-menu {
display: flex;
gap: 5px;
-webkit-app-region: no-drag;
}
.menu-btn {
padding: 4px 12px;
background: transparent;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
color: #495057;
transition: background 0.2s;
}
.menu-btn:hover {
background: #e9ecef;
}
.title-bar-right {
display: flex;
align-items: center;
gap: 2px;
-webkit-app-region: no-drag;
}
.window-btn {
width: 36px;
height: 28px;
border: none;
background: transparent;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #495057;
transition: background 0.2s;
}
.window-btn:hover {
background: #e9ecef;
}
.window-btn.close:hover {
background: #dc3545;
color: white;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
padding-top: 50px;
}
.main-content {
display: grid;
grid-template-columns: 220px 1fr;
gap: 20px;
}
.sidebar {
background: #f8f9fa;
border-radius: 10px;
padding: 15px;
height: fit-content;
border: 1px solid #e9ecef;
}
.menu-title {
font-size: 14px;
font-weight: 600;
color: #495057;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #dee2e6;
}
.menu-list {
list-style: none;
}
.menu-item {
padding: 10px 12px;
margin-bottom: 4px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 10px;
color: #495057;
font-weight: 500;
font-size: 14px;
}
.menu-item:hover {
background: #e9ecef;
}
.menu-item.active {
background: #667eea;
color: white;
}
.menu-icon {
font-size: 16px;
width: 20px;
text-align: center;
}
.content-area {
background: #ffffff;
border-radius: 10px;
padding: 25px;
border: 1px solid #e9ecef;
}
.welcome-section {
text-align: center;
padding: 30px 20px;
}
.welcome-title {
font-size: 28px;
color: #212529;
margin-bottom: 10px;
font-weight: 700;
}
.welcome-subtitle {
font-size: 16px;
color: #6c757d;
margin-bottom: 25px;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 15px;
margin-top: 25px;
}
.feature-card {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
transition: all 0.2s ease;
border: 1px solid #e9ecef;
}
.feature-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.feature-icon {
width: 50px;
height: 50px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
margin-bottom: 12px;
}
.feature-icon.blue {
background: #667eea;
color: white;
}
.feature-icon.green {
background: #28a745;
color: white;
}
.feature-icon.orange {
background: #fd7e14;
color: white;
}
.feature-icon.purple {
background: #6f42c1;
color: white;
}
.feature-title {
font-size: 16px;
font-weight: 600;
color: #212529;
margin-bottom: 8px;
}
.feature-desc {
font-size: 13px;
color: #6c757d;
line-height: 1.5;
}
.stats-section {
margin-top: 30px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
}
.stat-card {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
text-align: center;
border: 1px solid #e9ecef;
}
.stat-number {
font-size: 28px;
font-weight: 700;
color: #667eea;
margin-bottom: 5px;
}
.stat-label {
font-size: 13px;
color: #6c757d;
}
.quick-actions {
margin-top: 25px;
display: flex;
gap: 10px;
justify-content: center;
}
.btn {
padding: 10px 20px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
border: none;
font-size: 14px;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd6;
transform: translateY(-1px);
}
.btn-secondary {
background: #f8f9fa;
color: #667eea;
border: 1px solid #667eea;
}
.btn-secondary:hover {
background: #667eea;
color: white;
}
.version-info {
text-align: center;
margin-top: 25px;
padding-top: 15px;
border-top: 1px solid #e9ecef;
color: #adb5bd;
font-size: 12px;
}
@media (max-width: 768px) {
.main-content {
grid-template-columns: 1fr;
}
.stats-section {
grid-template-columns: repeat(2, 1fr);
}
.quick-actions {
flex-direction: column;
align-items: center;
}
}
.page {
display: none;
}
.page.active {
display: block;
}
.timer-container {
text-align: center;
padding: 20px;
}
.timer-tabs {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 30px;
}
.timer-tab {
padding: 10px 30px;
border: 2px solid #667eea;
background: #f8f9fa;
color: #667eea;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: all 0.2s;
}
.timer-tab.active {
background: #667eea;
color: white;
}
.timer-display-container {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin: 30px 0;
cursor: pointer;
padding: 20px;
border-radius: 12px;
transition: background 0.2s;
}
.timer-display-container:hover {
background: #f8f9fa;
}
.timer-display-container.clickable {
cursor: pointer;
}
.timer-display-container.not-clickable {
cursor: default;
}
.timer-display-container.not-clickable:hover {
background: transparent;
}
.timer-digit-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.timer-digit-wrapper {
display: flex;
align-items: center;
gap: 5px;
}
.timer-digit-btn {
width: 80px;
height: 100px;
background: #f8f9fa;
border: 3px solid #dee2e6;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 60px;
font-weight: 700;
color: #212529;
cursor: pointer;
transition: all 0.2s;
user-select: none;
}
.timer-digit-btn:hover {
border-color: #667eea;
background: #e9ecef;
}
.timer-digit-btn.editing {
border-color: #667eea;
background: #fff;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
.timer-digit-btn.running {
background: #667eea;
color: white;
border-color: #667eea;
cursor: default;
}
.timer-adj-btns {
display: flex;
gap: 8px;
}
.timer-adj-btn {
width: 50px;
height: 45px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
font-size: 24px;
font-weight: 700;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.timer-adj-btn:hover {
background: #5a6fd6;
}
.timer-adj-btn:active {
transform: scale(0.95);
}
.timer-separator {
font-size: 60px;
font-weight: 700;
color: #212529;
margin: 0 5px;
display: flex;
align-items: center;
height: 100px;
}
.timer-label {
font-size: 16px;
color: #6c757d;
font-weight: 500;
}
.timer-fullscreen-btn {
position: absolute;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
background: #f8f9fa;
border: 2px solid #dee2e6;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
transition: all 0.2s;
}
.timer-fullscreen-btn:hover {
background: #e9ecef;
border-color: #667eea;
}
.timer-fullscreen-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #1a1a2e;
z-index: 10000;
flex-direction: column;
align-items: center;
justify-content: center;
}
.timer-fullscreen-overlay.active {
display: flex;
}
.timer-fullscreen-display {
display: flex;
align-items: center;
gap: 30px;
}
.timer-fullscreen-digit {
font-size: 200px;
font-weight: 700;
color: #ffffff;
font-family: 'Consolas', 'Monaco', monospace;
min-width: 200px;
text-align: center;
}
.timer-fullscreen-separator {
font-size: 200px;
font-weight: 700;
color: #ffffff;
}
.timer-fullscreen-close {
position: absolute;
bottom: 40px;
right: 40px;
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
color: white;
transition: all 0.2s;
}
.timer-fullscreen-close:hover {
background: rgba(255, 255, 255, 0.2);
}
.timer-controls {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 40px;
}
.timer-btn {
padding: 18px 60px;
border: none;
border-radius: 10px;
font-size: 22px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.timer-btn.start {
background: #28a745;
color: white;
}
.timer-btn.start:hover {
background: #218838;
}
.timer-btn.pause {
background: #ffc107;
color: #212529;
}
.timer-btn.pause:hover {
background: #e0a800;
}
.timer-btn.reset {
background: #6c757d;
color: white;
}
.timer-btn.reset:hover {
background: #5a6268;
}
.random-container {
text-align: center;
padding: 20px;
}
.random-number {
font-size: 200px;
font-weight: 700;
color: #667eea;
margin: 40px 0;
font-family: 'Consolas', 'Monaco', monospace;
min-height: 250px;
display: flex;
align-items: center;
justify-content: center;
}
.random-controls {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 30px;
}
.random-btn {
padding: 15px 50px;
border: none;
border-radius: 6px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.random-btn.generate {
background: #667eea;
color: white;
}
.random-btn.generate:hover {
background: #5a6fd6;
}
.random-btn.stop {
background: #dc3545;
color: white;
}
.random-btn.stop:hover {
background: #c82333;
}
.random-settings {
margin-top: 30px;
padding: 25px;
background: #f8f9fa;
border-radius: 12px;
display: inline-flex;
align-items: center;
gap: 15px;
}
.random-settings label {
font-size: 18px;
color: #495057;
font-weight: 500;
}
.number-input-group {
display: flex;
align-items: center;
gap: 5px;
}
.number-adj-btn {
width: 50px;
height: 50px;
background: #667eea;
color: white;
border: none;
border-radius: 10px;
font-size: 24px;
font-weight: 700;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.number-adj-btn:hover {
background: #5a6fd6;
}
.number-adj-btn:active {
transform: scale(0.95);
}
.number-display {
width: 100px;
height: 50px;
background: white;
border: 2px solid #dee2e6;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 700;
color: #212529;
}
.settings-container {
max-width: 600px;
margin: 0 auto;
}
.settings-title {
font-size: 22px;
font-weight: 700;
color: #212529;
margin-bottom: 25px;
text-align: center;
}
.settings-section {
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #e9ecef;
}
.settings-section-title {
font-size: 16px;
font-weight: 600;
color: #495057;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #dee2e6;
}
.settings-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
}
.settings-item + .settings-item {
border-top: 1px solid #e9ecef;
}
.settings-label {
font-size: 14px;
color: #495057;
}
.settings-hint {
display: block;
font-size: 12px;
color: #adb5bd;
margin-top: 2px;
font-weight: normal;
}
.settings-radio-group {
display: flex;
gap: 15px;
}
.settings-radio-item {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
font-size: 14px;
color: #495057;
}
.settings-radio-item input[type="radio"] {
accent-color: #667eea;
width: 16px;
height: 16px;
cursor: pointer;
}
.settings-custom-input {
display: none;
align-items: center;
gap: 8px;
margin-top: 10px;
padding: 10px;
background: white;
border-radius: 8px;
}
.settings-custom-input.visible {
display: flex;
}
.settings-custom-input label {
font-size: 14px;
color: #495057;
white-space: nowrap;
}
.settings-input {
width: 100px;
padding: 8px 12px;
border: 2px solid #dee2e6;
border-radius: 6px;
font-size: 14px;
text-align: center;
transition: border-color 0.2s;
}
.settings-input:focus {
outline: none;
border-color: #667eea;
}
.settings-unit {
font-size: 14px;
color: #6c757d;
}
.toggle-switch {
position: relative;
width: 48px;
height: 26px;
cursor: pointer;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #dee2e6;
border-radius: 26px;
transition: all 0.3s;
}
.toggle-slider::before {
content: '';
position: absolute;
width: 20px;
height: 20px;
left: 3px;
bottom: 3px;
background: white;
border-radius: 50%;
transition: all 0.3s;
}
.toggle-switch input:checked + .toggle-slider {
background: #667eea;
}
.toggle-switch input:checked + .toggle-slider::before {
transform: translateX(22px);
}
.about-section {
text-align: center;
}
.about-content {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 15px;
gap: 15px;
}
.about-logo {
width: 128px;
height: 128px;
border-radius: 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.about-info {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.about-name {
font-size: 18px;
font-weight: 600;
color: #343a40;
margin: 0;
}
.about-version {
font-size: 14px;
color: #868e96;
margin: 0;
}
.about-developer {
font-size: 14px;
color: #667eea;
margin: 4px 0 0 0;
font-weight: 500;
}
.about-developer strong {
color: #5a6fd6;
}
.about-link {
font-size: 14px;
color: #667eea;
text-decoration: none;
padding: 8px 20px;
border: 1px solid #667eea;
border-radius: 20px;
transition: all 0.3s;
}
.about-link:hover {
background: #667eea;
color: white;
}
.license-modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 10000;
justify-content: center;
align-items: center;
}
.license-modal-overlay.active {
display: flex;
}
.license-modal {
background: white;
border-radius: 12px;
width: 90%;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.license-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #e9ecef;
}
.license-modal-header h3 {
font-size: 16px;
font-weight: 600;
color: #333;
}
.license-modal-close {
width: 28px;
height: 28px;
border: none;
background: transparent;
font-size: 18px;
color: #666;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.license-modal-close:hover {
background: #e9ecef;
}
.license-modal-body {
padding: 20px;
font-family: 'Consolas', 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
color: #333;
white-space: pre-wrap;
}
</style>
</head>
<body>
<div class="title-bar">
<div class="title-bar-left">
<img class="app-icon" src="assets/icon.ico" alt="课堂小助手">
<span class="app-title">课堂小助手</span>
</div>
<!--
<div class="title-bar-menu">
<button class="menu-btn" onclick="showFileMenu()">文件</button>
<button class="menu-btn" onclick="showViewMenu()">视图</button>
<button class="menu-btn" onclick="showHelpMenu()">帮助</button>
</div>
-->
<div class="title-bar-right">
<button class="window-btn minimize" onclick="minimizeWindow()" title="最小化"></button>
<button class="window-btn close" onclick="closeWindow()" title="关闭"></button>
</div>
</div>
<div class="container">
<div class="main-content">
<nav class="sidebar">
<div class="menu-title">功能菜单</div>
<ul class="menu-list">
<li class="menu-item active" onclick="showPage('home')">
<span class="menu-icon"><EFBFBD></span>
<span>工作台</span>
</li>
<li class="menu-item" onclick="showPage('timer')">
<span class="menu-icon">⏱️</span>
<span>高效计时</span>
</li>
<li class="menu-item" onclick="showPage('random')">
<span class="menu-icon">🎲</span>
<span>随机摇号</span>
</li>
<li class="menu-item" onclick="showPage('settings')">
<span class="menu-icon">⚙️</span>
<span>设置</span>
</li>
</ul>
</nav>
<main class="content-area">
<div id="page-home" class="page active">
<div class="welcome-section">
<h2 class="welcome-title">欢迎使用课堂小助手</h2>
<p class="welcome-subtitle">让教学更高效,让课堂更有趣</p>
</div>
</div>
<div id="page-timer" class="page">
<div class="timer-container">
<div class="timer-tabs">
<button class="timer-tab active" onclick="switchTimerMode('countdown')">倒计时</button>
<button class="timer-tab" onclick="switchTimerMode('stopwatch')">正计时</button>
</div>
<div class="timer-display-container" onclick="handleTimerDisplayClick()" id="timer-display-area">
<div class="timer-digit-group">
<div class="timer-adj-btns">
<button class="timer-adj-btn" onclick="adjustDigit('h-tens', 1)">+</button>
<button class="timer-adj-btn" onclick="adjustDigit('h-ones', 1)">+</button>
</div>
<div class="timer-digit-wrapper">
<div class="timer-digit-btn" id="h-tens" onclick="cycleDigit(this)">0</div>
<div class="timer-digit-btn" id="h-ones" onclick="cycleDigit(this)">0</div>
</div>
<div class="timer-adj-btns">
<button class="timer-adj-btn" onclick="adjustDigit('h-tens', -1)">-</button>
<button class="timer-adj-btn" onclick="adjustDigit('h-ones', -1)">-</button>
</div>
<div class="timer-label"></div>
</div>
<div class="timer-separator">:</div>
<div class="timer-digit-group">
<div class="timer-adj-btns">
<button class="timer-adj-btn" onclick="adjustDigit('m-tens', 1)">+</button>
<button class="timer-adj-btn" onclick="adjustDigit('m-ones', 1)">+</button>
</div>
<div class="timer-digit-wrapper">
<div class="timer-digit-btn" id="m-tens" onclick="cycleDigit(this)">0</div>
<div class="timer-digit-btn" id="m-ones" onclick="cycleDigit(this)">5</div>
</div>
<div class="timer-adj-btns">
<button class="timer-adj-btn" onclick="adjustDigit('m-tens', -1)">-</button>
<button class="timer-adj-btn" onclick="adjustDigit('m-ones', -1)">-</button>
</div>
<div class="timer-label"></div>
</div>
<div class="timer-separator">:</div>
<div class="timer-digit-group">
<div class="timer-adj-btns">
<button class="timer-adj-btn" onclick="adjustDigit('s-tens', 1)">+</button>
<button class="timer-adj-btn" onclick="adjustDigit('s-ones', 1)">+</button>
</div>
<div class="timer-digit-wrapper">
<div class="timer-digit-btn" id="s-tens" onclick="cycleDigit(this)">0</div>
<div class="timer-digit-btn" id="s-ones" onclick="cycleDigit(this)">0</div>
</div>
<div class="timer-adj-btns">
<button class="timer-adj-btn" onclick="adjustDigit('s-tens', -1)">-</button>
<button class="timer-adj-btn" onclick="adjustDigit('s-ones', -1)">-</button>
</div>
<div class="timer-label"></div>
</div>
</div>
<div class="timer-controls">
<button class="timer-btn start" id="timer-start-btn" onclick="startTimer()">开始</button>
<button class="timer-btn pause" id="timer-pause-btn" onclick="pauseTimer()" style="display:none;">暂停</button>
<button class="timer-btn reset" onclick="resetTimer()">重置</button>
</div>
<button class="timer-fullscreen-btn" onclick="openFullscreen()" title="全屏"></button>
</div>
</div>
<div id="timer-fullscreen" class="timer-fullscreen-overlay">
<div class="timer-fullscreen-display">
<div class="timer-fullscreen-digit" id="fs-h">00</div>
<div class="timer-fullscreen-separator">:</div>
<div class="timer-fullscreen-digit" id="fs-m">00</div>
<div class="timer-fullscreen-separator">:</div>
<div class="timer-fullscreen-digit" id="fs-s">00</div>
</div>
<button class="timer-fullscreen-close" onclick="closeFullscreen()"></button>
</div>
<div id="page-random" class="page">
<div class="random-container">
<div class="random-number" id="random-display">?</div>
<div class="random-controls">
<button class="random-btn generate" id="random-start-btn" onclick="startRandom()">开始摇号</button>
</div>
<div class="random-settings">
<label>最大号码:</label>
<div class="number-input-group">
<button class="number-adj-btn" onclick="adjustRandomMax(-10)">-10</button>
<button class="number-adj-btn" onclick="adjustRandomMax(-1)">-</button>
<div class="number-display" id="random-max-display">75</div>
<button class="number-adj-btn" onclick="adjustRandomMax(1)">+</button>
<button class="number-adj-btn" onclick="adjustRandomMax(10)">+10</button>
</div>
</div>
</div>
</div>
<div id="page-settings" class="page">
<div class="settings-container">
<h2 class="settings-title">设置</h2>
<div class="settings-section">
<div class="settings-section-title">⏱️ 高效计时</div>
<div class="settings-item">
<span class="settings-label">默认填充的时间</span>
<div class="settings-radio-group">
<label class="settings-radio-item">
<input type="radio" name="timer-fill-mode" value="last" checked onchange="handleFillModeChange(this.value)">
来自上次设定
</label>
<label class="settings-radio-item">
<input type="radio" name="timer-fill-mode" value="custom" onchange="handleFillModeChange(this.value)">
自定义
</label>
</div>
</div>
<div class="settings-custom-input" id="custom-seconds-container">
<label>自定义倒计时默认填充时间</label>
<input type="number" class="settings-input" id="custom-seconds-input" min="1" max="86400" value="300" oninput="handleCustomSecondsChange(this.value)">
<span class="settings-unit"></span>
</div>
</div>
<div class="settings-section">
<div class="settings-section-title">🎲 随机摇号</div>
<div class="settings-item">
<span class="settings-label">跳过动画</span>
<label class="toggle-switch">
<input type="checkbox" id="skip-animation-toggle" onchange="handleSkipAnimationChange(this.checked)">
<span class="toggle-slider"></span>
</label>
</div>
<div class="settings-item">
<span class="settings-label">智能匹配
<span class="settings-hint">避免重复抽中同一号码</span>
</span>
<label class="toggle-switch">
<input type="checkbox" id="smart-match-toggle" onchange="handleSmartMatchChange(this.checked)">
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="settings-section about-section">
<div class="settings-section-title">关于</div>
<div class="about-content">
<img src="assets/icon.ico" alt="Logo" class="about-logo">
<div class="about-info">
<h3 class="about-name">课堂小助手 Classroom Assistant</h3>
<p class="about-version" id="about-version"></p>
<p class="about-developer"><strong>Yunyun云云</strong> 开发</p>
<a href="https://www.caellab.com/2026/ca/" target="_blank" class="about-link" onclick="openSourceCode(event)">介绍页</a>
<a href="https://github.com/yunyun-3782/Classroom-Assistant" target="_blank" class="about-link" onclick="openSourceCode(event)">开源仓库</a>
<a href="#" class="about-link" onclick="openLicenseModal(event)">查看许可证</a>
</div>
</div>
</div>
</div>
</div>
<div class="version-info">
<p id="version-info">课堂小助手 Copyrights 2026 Yunyun By CaelLab</p>
</div>
</main>
</div>
</div>
<div class="license-modal-overlay" id="license-modal-overlay">
<div class="license-modal">
<div class="license-modal-header">
<h3>许可证</h3>
<button class="license-modal-close" onclick="closeLicenseModal()">&times;</button>
</div>
<div class="license-modal-body">CaelLab BY-SA Code License
Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this work (the "Work"), to use, copy, modify, merge, publish, distribute,
sublicense, and sell copies of the Work, for personal, commercial, or
non-commercial purposes, subject to the following conditions:
1. Source Availability: If you distribute the Work, or any derivative work
based on the Work, you must make the complete corresponding source code
available under the terms of this same license.
2. License Preservation: The above copyright notice and this permission notice
shall be included in all copies or substantial portions of the Work.
3. ShareAlike: Any distributed derivative work must be licensed under the
CaelLab BY-SA Code License.
THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.</div>
</div>
</div>
<script>
function minimizeWindow() {
if (window.electronAPI) {
window.electronAPI.windowMinimize()
}
}
function closeWindow() {
if (window.electronAPI) {
window.electronAPI.windowClose()
}
}
function showPage(pageName) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'))
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'))
document.getElementById('page-' + pageName).classList.add('active')
event.currentTarget.classList.add('active')
}
let timerMode = 'countdown'
let timerInterval = null
let timerSeconds = 0
let isTimerRunning = false
let isTimerPaused = false
function switchTimerMode(mode) {
if (isTimerRunning || isTimerPaused) return
timerMode = mode
document.querySelectorAll('.timer-tab').forEach(t => t.classList.remove('active'))
event.currentTarget.classList.add('active')
resetTimer()
updateTimerButtonsVisibility()
}
function updateTimerButtonsVisibility() {
const btns = document.querySelectorAll('.timer-adj-btn')
const digitBtns = document.querySelectorAll('.timer-digit-btn')
const displayArea = document.getElementById('timer-display-area')
const isActive = isTimerRunning || isTimerPaused
const hideAdj = isActive || timerMode === 'stopwatch'
btns.forEach(b => b.style.visibility = hideAdj ? 'hidden' : 'visible')
digitBtns.forEach(b => {
if (isActive) {
b.classList.add('running')
} else {
b.classList.remove('running')
}
if (timerMode === 'stopwatch' && !isActive) {
b.style.cursor = 'default'
} else {
b.style.cursor = isActive ? 'default' : 'pointer'
}
})
document.querySelectorAll('.timer-tab').forEach(t => {
t.style.opacity = isActive ? '0.5' : '1'
t.style.pointerEvents = isActive ? 'none' : 'auto'
})
if (displayArea) {
if (isActive) {
displayArea.classList.remove('not-clickable')
displayArea.classList.add('clickable')
} else {
displayArea.classList.remove('clickable')
displayArea.classList.add('not-clickable')
}
}
}
function adjustDigit(id, delta) {
if (isTimerRunning) return
const el = document.getElementById(id)
let val = parseInt(el.textContent) + delta
const isTens = id.includes('tens')
const isHour = id.startsWith('h-')
if (isTens) {
const maxTens = isHour ? 9 : 5
val = val < 0 ? maxTens : val > maxTens ? 0 : val
} else {
val = val < 0 ? 9 : val > 9 ? 0 : val
}
el.textContent = val
}
function cycleDigit(el) {
if (isTimerRunning) return
let val = parseInt(el.textContent) + 1
const isTens = el.id.includes('tens')
const isHour = el.id.startsWith('h-')
if (isTens) {
const maxTens = isHour ? 9 : 5
val = val > maxTens ? 0 : val
} else {
val = val > 9 ? 0 : val
}
el.textContent = val
}
function getTimerSeconds() {
const h = parseInt(document.getElementById('h-tens').textContent) * 10 + parseInt(document.getElementById('h-ones').textContent)
const m = parseInt(document.getElementById('m-tens').textContent) * 10 + parseInt(document.getElementById('m-ones').textContent)
const s = parseInt(document.getElementById('s-tens').textContent) * 10 + parseInt(document.getElementById('s-ones').textContent)
return h * 3600 + m * 60 + s
}
function setTimerDigits(totalSeconds) {
const h = Math.floor(totalSeconds / 3600)
const m = Math.floor((totalSeconds % 3600) / 60)
const s = totalSeconds % 60
document.getElementById('h-tens').textContent = Math.floor(h / 10)
document.getElementById('h-ones').textContent = h % 10
document.getElementById('m-tens').textContent = Math.floor(m / 10)
document.getElementById('m-ones').textContent = m % 10
document.getElementById('s-tens').textContent = Math.floor(s / 10)
document.getElementById('s-ones').textContent = s % 10
}
async function loadTimeLastSeconds() {
if (window.electronAPI) {
try {
const fillMode = await window.electronAPI.getTimeFillMode()
let seconds
if (fillMode === 'custom') {
seconds = await window.electronAPI.getTimeCustomSeconds()
} else {
seconds = await window.electronAPI.getTimeLastSeconds()
}
setTimerDigits(seconds)
} catch (error) {
console.log('加载计时器记忆失败:', error)
}
}
}
function startTimer() {
if (isTimerRunning) return
if (!isTimerPaused) {
if (timerMode === 'countdown') {
timerSeconds = getTimerSeconds()
if (timerSeconds <= 0) return
if (window.electronAPI) {
window.electronAPI.setTimeLastSeconds(timerSeconds)
}
} else {
timerSeconds = 0
setTimerDigits(0)
}
}
isTimerRunning = true
isTimerPaused = false
document.getElementById('timer-start-btn').style.display = 'none'
document.getElementById('timer-pause-btn').style.display = 'inline-block'
updateTimerButtonsVisibility()
timerInterval = setInterval(() => {
if (timerMode === 'countdown') {
timerSeconds--
if (timerSeconds <= 0) {
timerSeconds = 0
clearInterval(timerInterval)
isTimerRunning = false
isTimerPaused = false
document.getElementById('timer-start-btn').style.display = 'inline-block'
document.getElementById('timer-pause-btn').style.display = 'none'
updateTimerButtonsVisibility()
setTimerDigits(0)
alert('倒计时结束!')
return
}
} else {
timerSeconds++
}
setTimerDigits(timerSeconds)
}, 1000)
}
function pauseTimer() {
if (!isTimerRunning) return
isTimerRunning = false
isTimerPaused = true
clearInterval(timerInterval)
document.getElementById('timer-start-btn').style.display = 'inline-block'
document.getElementById('timer-pause-btn').style.display = 'none'
updateTimerButtonsVisibility()
}
function resetTimer() {
isTimerRunning = false
isTimerPaused = false
clearInterval(timerInterval)
document.getElementById('timer-start-btn').style.display = 'inline-block'
document.getElementById('timer-pause-btn').style.display = 'none'
if (timerMode === 'countdown') {
const savedSeconds = getTimerSeconds()
setTimerDigits(savedSeconds > 0 ? savedSeconds : 300)
} else {
timerSeconds = 0
setTimerDigits(0)
}
updateTimerButtonsVisibility()
}
function handleTimerDisplayClick() {
const isActive = isTimerRunning || isTimerPaused
if (isActive) {
openFullscreen()
}
}
let fullscreenInterval = null
let wasMaximizedBeforeFullscreen = false
async function openFullscreen() {
if (window.electronAPI) {
wasMaximizedBeforeFullscreen = await window.electronAPI.windowIsMaximized()
if (!wasMaximizedBeforeFullscreen) {
window.electronAPI.windowMaximize()
}
}
setTimeout(() => {
const overlay = document.getElementById('timer-fullscreen')
overlay.classList.add('active')
updateFullscreenDisplay()
fullscreenInterval = setInterval(updateFullscreenDisplay, 100)
}, 100)
}
function closeFullscreen() {
const overlay = document.getElementById('timer-fullscreen')
overlay.classList.remove('active')
if (fullscreenInterval) {
clearInterval(fullscreenInterval)
fullscreenInterval = null
}
if (window.electronAPI && !wasMaximizedBeforeFullscreen) {
window.electronAPI.windowRestore()
}
}
function updateFullscreenDisplay() {
const h = document.getElementById('h-tens').textContent + document.getElementById('h-ones').textContent
const m = document.getElementById('m-tens').textContent + document.getElementById('m-ones').textContent
const s = document.getElementById('s-tens').textContent + document.getElementById('s-ones').textContent
document.getElementById('fs-h').textContent = h
document.getElementById('fs-m').textContent = m
document.getElementById('fs-s').textContent = s
}
let isRandomRunning = false
let randomMax = 75
let randomTimeout = null
let skipAnimation = false
let smartMatch = true
async function loadRandomMax() {
if (window.electronAPI) {
const max = await window.electronAPI.getRandomMaxNumber()
randomMax = max
document.getElementById('random-max-display').textContent = max
}
}
async function adjustRandomMax(delta) {
if (isRandomRunning) return
randomMax = Math.max(1, randomMax + delta)
document.getElementById('random-max-display').textContent = randomMax
if (window.electronAPI) {
await window.electronAPI.setRandomMaxNumber(randomMax)
await window.electronAPI.resetSmartMatchWeights()
}
}
async function startRandom() {
if (isRandomRunning) return
isRandomRunning = true
document.getElementById('random-start-btn').disabled = true
document.getElementById('random-start-btn').style.opacity = '0.5'
document.getElementById('random-display').style.color = '#667eea'
let finalNum
if (smartMatch && window.electronAPI) {
finalNum = await window.electronAPI.pickWeightedRandom()
} else {
finalNum = Math.floor(Math.random() * randomMax) + 1
}
if (skipAnimation) {
document.getElementById('random-display').textContent = finalNum
isRandomRunning = false
document.getElementById('random-start-btn').disabled = false
document.getElementById('random-start-btn').style.opacity = '1'
} else {
rollRandom(25, 0, finalNum)
}
}
function rollRandom(delay, count, finalNum) {
const num = Math.floor(Math.random() * randomMax) + 1
document.getElementById('random-display').textContent = num
const maxRolls = 15 + Math.floor(Math.random() * 8)
const newDelay = count > maxRolls * 0.5 ? delay * 1.12 : delay
if (newDelay < 350) {
randomTimeout = setTimeout(() => rollRandom(newDelay, count + 1, finalNum), newDelay)
} else {
document.getElementById('random-display').textContent = finalNum
isRandomRunning = false
document.getElementById('random-start-btn').disabled = false
document.getElementById('random-start-btn').style.opacity = '1'
document.getElementById('random-display').style.color = '#667eea'
}
}
async function loadAppInfo() {
try {
if (window.electronAPI) {
const version = await window.electronAPI.getAppVersion()
const appName = await window.electronAPI.getAppName()
document.getElementById('version-info').textContent =
appName + ' v' + version + ' | Copyrights 2026 Yunyun By CaelLab'
document.getElementById('about-version').textContent = 'v' + version
}
} catch (error) {
console.log('无法获取应用信息:', error)
}
}
function handleFillModeChange(mode) {
const container = document.getElementById('custom-seconds-container')
if (mode === 'custom') {
container.classList.add('visible')
} else {
container.classList.remove('visible')
}
if (window.electronAPI) {
window.electronAPI.setTimeFillMode(mode)
}
}
let customSecondsDebounce = null
function handleCustomSecondsChange(value) {
clearTimeout(customSecondsDebounce)
customSecondsDebounce = setTimeout(() => {
const seconds = Math.max(1, Math.min(86400, parseInt(value) || 300))
if (window.electronAPI) {
window.electronAPI.setTimeCustomSeconds(seconds)
}
}, 500)
}
function handleSkipAnimationChange(checked) {
skipAnimation = checked
if (window.electronAPI) {
window.electronAPI.setRandomSkipAnimation(checked)
}
}
function handleSmartMatchChange(checked) {
smartMatch = checked
if (window.electronAPI) {
window.electronAPI.setSmartMatch(checked)
if (checked) {
window.electronAPI.resetSmartMatchWeights()
}
}
}
async function loadSettings() {
if (window.electronAPI) {
const fillMode = await window.electronAPI.getTimeFillMode()
const radios = document.querySelectorAll('input[name="timer-fill-mode"]')
radios.forEach(radio => {
radio.checked = radio.value === fillMode
})
if (fillMode === 'custom') {
document.getElementById('custom-seconds-container').classList.add('visible')
}
const customSeconds = await window.electronAPI.getTimeCustomSeconds()
document.getElementById('custom-seconds-input').value = customSeconds
skipAnimation = await window.electronAPI.getRandomSkipAnimation()
document.getElementById('skip-animation-toggle').checked = skipAnimation
smartMatch = await window.electronAPI.getSmartMatch()
document.getElementById('smart-match-toggle').checked = smartMatch
}
}
function openSourceCode(e) {
e.preventDefault()
if (window.electronAPI) {
window.electronAPI.openExternalUrl('https://git.caellab.com/yunyun/Classroom-Assistant')
}
}
function openLicenseModal(e) {
e.preventDefault()
document.getElementById('license-modal-overlay').classList.add('active')
}
function closeLicenseModal() {
document.getElementById('license-modal-overlay').classList.remove('active')
}
document.addEventListener('DOMContentLoaded', async () => {
loadAppInfo()
loadRandomMax()
await loadTimeLastSeconds()
loadSettings()
})
</script>
</body>
</html>