645 lines
25 KiB
Plaintext
645 lines
25 KiB
Plaintext
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Linux 命令沙盒练习 - 菜鸟式学习平台</title>
|
||
<meta charset="utf-8">
|
||
<style>
|
||
* { box-sizing: border-box; }
|
||
:root {
|
||
--bg-color: #f5f5f5;
|
||
--text-color: #333;
|
||
--sidebar-bg: #fff;
|
||
--sidebar-border: #ddd;
|
||
--main-bg: #fff;
|
||
--cmd-bg: #f0f0f0;
|
||
--cmd-border: #ccc;
|
||
--cmd-text: #333;
|
||
--success-color: #4caf50;
|
||
--error-color: #f44336;
|
||
--highlight-color: #ff9800;
|
||
}
|
||
[data-theme="dark"] {
|
||
--bg-color: #1e1e1e;
|
||
--text-color: #e0e0e0;
|
||
--sidebar-bg: #2d2d2d;
|
||
--sidebar-border: #444;
|
||
--main-bg: #1e1e1e;
|
||
--cmd-bg: #2d2d2d;
|
||
--cmd-border: #444;
|
||
--cmd-text: #fff;
|
||
}
|
||
body {
|
||
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
||
background: var(--bg-color);
|
||
color: var(--text-color);
|
||
margin: 0;
|
||
padding: 0;
|
||
transition: background 0.3s, color 0.3s;
|
||
}
|
||
/* Header */
|
||
header {
|
||
background: var(--sidebar-bg);
|
||
border-bottom: 1px solid var(--sidebar-border);
|
||
padding: 10px 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
header h1 { margin: 0; font-size: 20px; color: var(--success-color); }
|
||
.header-actions { display: flex; gap: 10px; align-items: center; }
|
||
.theme-toggle, .logout-btn {
|
||
background: transparent;
|
||
border: 1px solid var(--sidebar-border);
|
||
padding: 5px 10px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
color: var(--text-color);
|
||
}
|
||
.theme-toggle:hover, .logout-btn:hover { background: var(--cmd-bg); }
|
||
/* Layout */
|
||
.container {
|
||
display: flex;
|
||
height: calc(100vh - 50px);
|
||
}
|
||
/* Sidebar */
|
||
.sidebar {
|
||
width: 260px;
|
||
background: var(--sidebar-bg);
|
||
border-right: 1px solid var(--sidebar-border);
|
||
overflow-y: auto;
|
||
padding: 15px;
|
||
}
|
||
.sidebar h2 {
|
||
font-size: 16px;
|
||
margin: 10px 0 5px;
|
||
color: var(--success-color);
|
||
border-bottom: 2px solid var(--highlight-color);
|
||
padding-bottom: 5px;
|
||
}
|
||
.lesson-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
.lesson-item {
|
||
padding: 8px 12px;
|
||
margin: 4px 0;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.lesson-item:hover { background: var(--cmd-bg); }
|
||
.lesson-item.active {
|
||
background: var(--highlight-color);
|
||
color: #fff;
|
||
}
|
||
.lesson-item.locked { opacity: 0.6; cursor: not-allowed; }
|
||
.lesson-badge {
|
||
font-size: 12px;
|
||
padding: 2px 6px;
|
||
border-radius: 10px;
|
||
background: #ccc;
|
||
color: #fff;
|
||
}
|
||
.lesson-item.active .lesson-badge { background: #fff; color: var(--highlight-color); }
|
||
.lesson-item.locked .lesson-badge { background: var(--highlight-color); }
|
||
/* Main Content */
|
||
.main-content {
|
||
flex: 1;
|
||
padding: 20px;
|
||
overflow-y: auto;
|
||
}
|
||
.task-card {
|
||
background: var(--main-bg);
|
||
border: 1px solid var(--sidebar-border);
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.task-title {
|
||
font-size: 22px;
|
||
color: var(--success-color);
|
||
margin: 0 0 10px;
|
||
}
|
||
.task-desc {
|
||
background: var(--cmd-bg);
|
||
padding: 15px;
|
||
border-radius: 6px;
|
||
border-left: 4px solid var(--highlight-color);
|
||
margin: 10px 0;
|
||
line-height: 1.6;
|
||
}
|
||
.task-hint {
|
||
color: var(--highlight-color);
|
||
font-weight: bold;
|
||
margin: 10px 0;
|
||
}
|
||
.task-success { display: none; background: #e8f5e9; padding: 15px; border-radius: 6px; margin: 15px 0; border:1px solid var(--success-color); }
|
||
.task-success.show { display: block; }
|
||
.task-success h4 { margin: 0 0 5px; color: var(--success-color); }
|
||
/* Command Area */
|
||
.cmd-area {
|
||
margin-top: 20px;
|
||
}
|
||
.cmd-bar {
|
||
background: var(--cmd-bg);
|
||
border: 1px solid var(--cmd-border);
|
||
border-radius: 6px;
|
||
padding: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.prompt { margin-right: 10px; color: var(--success-color); font-weight: bold; }
|
||
#cmd {
|
||
flex: 1;
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--cmd-text);
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 16px;
|
||
padding: 8px;
|
||
outline: none;
|
||
}
|
||
#output {
|
||
margin-top: 15px;
|
||
background: var(--sidebar-bg);
|
||
border: 1px solid var(--sidebar-border);
|
||
border-radius: 6px;
|
||
padding: 15px;
|
||
min-height: 60px;
|
||
font-family: 'Courier New', monospace;
|
||
white-space: pre-wrap;
|
||
word-wrap: break-word;
|
||
overflow-x: auto;
|
||
color: var(--text-color);
|
||
}
|
||
.btn {
|
||
background: var(--success-color);
|
||
color: #fff;
|
||
border: none;
|
||
padding: 10px 20px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
margin-top: 10px;
|
||
transition: background 0.2s;
|
||
}
|
||
.btn:hover { background: #45a049; }
|
||
.btn:disabled { background: #ccc; cursor: not-allowed; }
|
||
.btn-next { background: var(--highlight-color); }
|
||
.btn-next:hover { background: #f57c00; }
|
||
/* Footer */
|
||
footer {
|
||
background: var(--sidebar-bg);
|
||
border-top: 1px solid var(--sidebar-border);
|
||
padding: 10px 20px;
|
||
text-align: right;
|
||
font-size: 12px;
|
||
color: #888;
|
||
}
|
||
.progress-bar {
|
||
display: flex;
|
||
gap: 2px;
|
||
margin: 10px 0;
|
||
}
|
||
.progress-step {
|
||
width: 30px;
|
||
height: 4px;
|
||
background: var(--cmd-bg);
|
||
border-radius: 2px;
|
||
}
|
||
.progress-step.done { background: var(--success-color); }
|
||
.progress-step.current { background: var(--highlight-color); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<h1>💻 Linux 命令沙盒练习</h1>
|
||
<div class="header-actions">
|
||
<button class="theme-toggle" onclick="showWrongAnswers()" title="错题本">📚 错题本</button>
|
||
<button class="theme-toggle" onclick="toggleTheme()" title="切换深色/浅色模式">
|
||
<span id="themeLabel">☀️</span> <span id="themeLabelText">浅色</span>
|
||
</button>
|
||
<span id="userStatus">未登录</span>
|
||
<button id="logoutBtn" class="logout-btn" onclick="logout()" style="display:none">👋 退出</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="container">
|
||
<aside class="sidebar">
|
||
<div id="sidebarContent">
|
||
<!-- 课程目录将由 JS 动态生成 -->
|
||
<p style="text-align:center; color:#888; margin-top:20px;">加载课程中...</p>
|
||
</div>
|
||
</aside>
|
||
|
||
<main class="main-content">
|
||
<div id="taskPanel" class="task-card" style="display:none">
|
||
<h2 class="task-title" id="taskTitle"></h2>
|
||
<div class="progress-bar" id="progressBar"></div>
|
||
<p class="task-hint" id="taskHint"></p>
|
||
<div class="task-desc" id="taskDesc"></div>
|
||
<div class="task-success" id="taskSuccess">
|
||
<h4>✅ 回答正确!🎉</h4>
|
||
<p id="taskSuccessMsg"></p>
|
||
</div>
|
||
<div class="cmd-area">
|
||
<div class="cmd-bar">
|
||
<span class="prompt">$</span>
|
||
<input type="text" id="cmd" placeholder="输入 Linux 命令..." autocomplete="off" autofocus>
|
||
</div>
|
||
<div id="output">等待输入命令...</div>
|
||
<button id="submitBtn" class="btn" onclick="runCommand()">执行命令</button>
|
||
<button id="nextBtn" class="btn btn-next" onclick="nextLevel()" style="display:none">下一关 →</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="welcomePanel" class="task-card" style="text-align:center; padding:40px 20px;">
|
||
<h2>欢迎来到 Linux 命令沙盒练习平台!</h2>
|
||
<p style="font-size:18px; margin:20px 0;">这是一个零风险的 Linux 命令学习环境,你可以安全地练习常用命令。</p>
|
||
<p><strong>📌 使用说明:</strong></p>
|
||
<ul style="text-align:left; display:inline-block;">
|
||
<li>左侧是课程目录,按顺序学习</li>
|
||
<li>阅读任务描述,输入对应命令</li>
|
||
<li>答对后自动解锁下一关</li>
|
||
<li>支持深色/浅色主题切换</li>
|
||
</ul>
|
||
<button class="btn" style="margin-top:30px;" onclick="startFirstLesson()">开始学习 →</button>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<footer>
|
||
<span id="userBadge">🔒 未认证</span> |
|
||
<span id="currentLevel">Lv.1</span> |
|
||
<a href="https://github.com/" target="_blank">Powered by Linux Sandbox</a>
|
||
</footer>
|
||
|
||
<script>
|
||
// ==============================
|
||
// 全局状态
|
||
// ==============================
|
||
let currentToken = localStorage.getItem('linux_sandbox_token') || '';
|
||
let currentUser = localStorage.getItem('linux_sandbox_user') || '';
|
||
let currentTheme = localStorage.getItem('linux_sandbox_theme') || 'light';
|
||
let currentTaskId = null;
|
||
let completedTasks = JSON.parse(localStorage.getItem('linux_sandbox_completed') || '[]');
|
||
let wrongAnswers = JSON.parse(localStorage.getItem('linux_sandbox_wrong' || '[]');
|
||
let COURSE_DATA = null; // 将从 /api/tasks 动态加载
|
||
|
||
// ==============================
|
||
// 主题切换
|
||
// ==============================
|
||
function applyTheme(theme) {
|
||
document.documentElement.setAttribute('data-theme', theme);
|
||
localStorage.setItem('linux_sandbox_theme', theme);
|
||
document.getElementById('themeLabel').textContent = theme === 'dark' ? '🌙' : '☀️';
|
||
document.getElementById('themeLabelText').textContent = theme === 'dark' ? '深色' : '浅色';
|
||
}
|
||
|
||
function toggleTheme() {
|
||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||
applyTheme(newTheme);
|
||
currentTheme = newTheme;
|
||
}
|
||
|
||
applyTheme(currentTheme);
|
||
|
||
// ==============================
|
||
// 登录/登出
|
||
// ==============================
|
||
async function login() {
|
||
// 建议:实际从登录弹窗调用,这里简化直接用 token
|
||
if (currentToken && currentUser) {
|
||
updateUI();
|
||
return;
|
||
}
|
||
alert('请先登录!当前简化版支持直接使用内置 Token 执行命令。');
|
||
// 临时允许
|
||
currentToken = 'safe_linux_2026';
|
||
currentUser = 'sandbox_user';
|
||
localStorage.setItem('linux_sandbox_token', currentToken);
|
||
localStorage.setItem('linux_sandbox_user', currentUser);
|
||
logoutBtn.style.display = 'inline-block';
|
||
updateUI();
|
||
}
|
||
|
||
function logout() {
|
||
localStorage.removeItem('linux_sandbox_token');
|
||
localStorage.removeItem('linux_sandbox_user');
|
||
currentToken = '';
|
||
currentUser = '';
|
||
logoutBtn.style.display = 'none';
|
||
updateUI();
|
||
}
|
||
|
||
function updateUI() {
|
||
const userStatus = document.getElementById('userStatus');
|
||
const userBadge = document.getElementById('userBadge');
|
||
if (currentToken && currentUser) {
|
||
userStatus.textContent = `👤 ${currentUser}`;
|
||
userBadge.textContent = '✅ 已认证';
|
||
document.getElementById('logoutBtn').style.display = 'inline-block';
|
||
} else {
|
||
userStatus.textContent = '未登录';
|
||
userBadge.textContent = '🔒 未认证';
|
||
}
|
||
}
|
||
|
||
// ==============================
|
||
// 课程渲染
|
||
// ==============================
|
||
function renderSidebar() {
|
||
const sidebar = document.getElementById('sidebarContent');
|
||
let html = '<ul class="lesson-list">';
|
||
|
||
COURSE_DATA.levels.forEach(level => {
|
||
html += `<h2>${level.title}</h2>`;
|
||
level.challenges.forEach((task, idx) => {
|
||
const done = completedTasks.includes(task.id);
|
||
const active = task.id === currentTaskId ? 'active' : '';
|
||
const locked = !done && idx > 0 && !completedTasks.includes(level.challenges[idx-1].id);
|
||
html += `<li class="lesson-item ${active} ${locked ? 'locked' : ''}" onclick="loadTask('${task.id}')">
|
||
<span>${idx+1}. ${task.title}</span>
|
||
<span class="lesson-badge">${done ? '✅' : '🔒'}</span>
|
||
</li>`;
|
||
});
|
||
});
|
||
|
||
html += '</ul>';
|
||
sidebar.innerHTML = html;
|
||
}
|
||
|
||
// ==============================
|
||
// 任务加载
|
||
// ==============================
|
||
function loadTask(taskId) {
|
||
// 检查前序任务是否完成
|
||
let prevTaskId = null;
|
||
for (const level of COURSE_DATA.levels) {
|
||
for (const task of level.challenges) {
|
||
if (task.id === taskId) {
|
||
if (prevTaskId && !completedTasks.includes(prevTaskId)) {
|
||
alert('请先完成前一关!');
|
||
return;
|
||
}
|
||
currentTaskId = taskId;
|
||
renderTask(task);
|
||
renderSidebar(); // 更新高亮
|
||
return;
|
||
}
|
||
prevTaskId = task.id;
|
||
}
|
||
}
|
||
alert('任务未找到');
|
||
}
|
||
|
||
function renderTask(task) {
|
||
document.getElementById('welcomePanel').style.display = 'none';
|
||
document.getElementById('taskPanel').style.display = 'block';
|
||
|
||
document.getElementById('taskTitle').innerHTML = task.title;
|
||
document.getElementById('taskDesc').innerHTML = task.description;
|
||
document.getElementById('taskHint').textContent = `💡 ${task.hint}`;
|
||
document.getElementById('taskSuccess').classList.remove('show');
|
||
document.getElementById('taskSuccessMsg').textContent = task.success_msg || '恭喜你,任务完成!';
|
||
|
||
document.getElementById('submitBtn').style.display = 'inline-block';
|
||
document.getElementById('nextBtn').style.display = 'none';
|
||
|
||
// 渲染进度条
|
||
const progressBar = document.getElementById('progressBar');
|
||
let progressHtml = '';
|
||
for (let i=0; i<5; i++) {
|
||
progressHtml += `<div class="progress-step ${i === 0 ? 'current' : ''} ${i < 0 ? 'done' : ''}"></div>`;
|
||
}
|
||
progressBar.innerHTML = progressHtml;
|
||
|
||
// 聚焦输入框
|
||
document.getElementById('cmd').value = '';
|
||
document.getElementById('cmd').focus();
|
||
}
|
||
|
||
function startFirstLesson() {
|
||
if (!currentToken) {
|
||
login();
|
||
}
|
||
if (!COURSE_DATA) {
|
||
alert('课程数据加载中,请稍候...');
|
||
return;
|
||
}
|
||
loadTask(COURSE_DATA.levels[0].challenges[0].id);
|
||
}
|
||
|
||
// ==============================
|
||
// 命令执行
|
||
// ==============================
|
||
async function runCommand() {
|
||
const cmd = document.getElementById('cmd').value.trim();
|
||
if (!cmd) return;
|
||
|
||
const output = document.getElementById('output');
|
||
output.textContent = `执行: ${cmd}\n正在运行...`;
|
||
document.getElementById('submitBtn').disabled = true;
|
||
document.getElementById('cmd').disabled = true;
|
||
|
||
try {
|
||
const res = await fetch('/api/run?cmd=' + encodeURIComponent(cmd));
|
||
const data = await res.json();
|
||
|
||
output.textContent = data.output || data.message || '(无输出)';
|
||
|
||
// 检查任务是否完成
|
||
checkTaskCompletion(cmd, data.output || '');
|
||
} catch (e) {
|
||
output.textContent = `❌ 错误: ${e.message}`;
|
||
} finally {
|
||
document.getElementById('submitBtn').disabled = false;
|
||
document.getElementById('cmd').disabled = false;
|
||
}
|
||
}
|
||
|
||
function checkTaskCompletion(cmd, output) {
|
||
const task = findTaskById(currentTaskId);
|
||
if (!task) return;
|
||
|
||
// 检查 solution 匹配
|
||
const solutionMatch = task.success_test || (task.solution && task.solution.includes(cmd));
|
||
|
||
if (solutionMatch || task.solution?.includes(cmd)) {
|
||
handleTaskSuccess(task);
|
||
} else {
|
||
// 简单启发式检查(未实现 full success_test 解析)
|
||
console.log('需要完成任务检查逻辑');
|
||
recordWrongAnswer(currentTaskId, cmd, output);
|
||
}
|
||
}
|
||
|
||
function handleTaskSuccess(task) {
|
||
if (!completedTasks.includes(task.id)) {
|
||
completedTasks.push(task.id);
|
||
localStorage.setItem('linux_sandbox_completed', JSON.stringify(completedTasks));
|
||
alert('✅ 回答正确!\n奖励:经验值 +100');
|
||
}
|
||
|
||
document.getElementById('submitBtn').style.display = 'none';
|
||
const successBox = document.getElementById('taskSuccess');
|
||
successBox.querySelector('h4').innerHTML = '🎉 回答正确!';
|
||
successBox.querySelector('p').textContent = task.success_msg || '你已经掌握这个命令的基本用法!';
|
||
successBox.classList.add('show');
|
||
|
||
document.getElementById('nextBtn').style.display = 'inline-block';
|
||
}
|
||
|
||
function nextLevel() {
|
||
const task = findTaskById(currentTaskId);
|
||
if (!task) return;
|
||
|
||
// 找到下一关
|
||
let found = false;
|
||
for (const level of COURSE_DATA.levels) {
|
||
for (const t of level.challenges) {
|
||
if (found) {
|
||
loadTask(t.id);
|
||
return;
|
||
}
|
||
if (t.id === task.id) found = true;
|
||
}
|
||
}
|
||
alert('🎉 所有课程已完成!你已经是 Linux 大师了!🏆');
|
||
}
|
||
|
||
function findTaskById(id) {
|
||
for (const level of COURSE_DATA.levels) {
|
||
const task = level.challenges.find(t => t.id === id);
|
||
if (task) return task;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// ==============================
|
||
// 事件监听
|
||
// ==============================
|
||
document.getElementById('cmd').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') runCommand();
|
||
});
|
||
|
||
// ==============================
|
||
// 初始化
|
||
// ==============================
|
||
function init() {
|
||
updateUI();
|
||
// 渲染侧边栏(课程数据加载后调用 renderSidebar)
|
||
if (COURSE_DATA) renderSidebar();
|
||
|
||
// 检查是否已有学习进度
|
||
if (currentToken && !currentTaskId && !completedTasks.length) {
|
||
// 从第一关开始(延迟加载)
|
||
setTimeout(startFirstLesson, 1000);
|
||
} else if (currentTaskId) {
|
||
const task = findTaskById(currentTaskId);
|
||
if (task) renderTask(task);
|
||
}
|
||
}
|
||
|
||
// 等待课程数据加载完成后初始化 UI
|
||
let dataLoaded = false;
|
||
setInterval(() => {
|
||
if (COURSE_DATA && !dataLoaded) {
|
||
init();
|
||
dataLoaded = true;
|
||
}
|
||
}, 500);
|
||
|
||
// ==============================
|
||
// 加载课程数据(API)
|
||
// ==============================
|
||
async function loadCourseData() {
|
||
try {
|
||
const res = await fetch('/api/tasks');
|
||
if (res.ok) {
|
||
COURSE_DATA = await res.json();
|
||
console.log('✅ 课程数据加载成功', COURSE_DATA);
|
||
renderSidebar();
|
||
} else {
|
||
console.error('❌ 课程数据加载失败:', res.statusText);
|
||
}
|
||
} catch (e) {
|
||
console.error('❌ 课程数据获取异常:', e);
|
||
}
|
||
}
|
||
|
||
// 页面全局函数(供 onclick 调用)
|
||
window.loadTask = loadTask;
|
||
window.runCommand = runCommand;
|
||
window.nextLevel = nextLevel;
|
||
window.startFirstLesson = startFirstLesson;
|
||
|
||
// ==============================
|
||
// 错题记录功能
|
||
// ==============================
|
||
function recordWrongAnswer(taskId, cmd, output) {
|
||
const wrongItem = {
|
||
taskId: taskId,
|
||
cmd: cmd,
|
||
output: output,
|
||
timestamp: new Date().toISOString(),
|
||
count: 1
|
||
};
|
||
|
||
// 检查是否已存在
|
||
const existing = wrongAnswers.find(w => w.taskId === taskId);
|
||
if (existing) {
|
||
existing.count++;
|
||
existing.cmd = cmd;
|
||
existing.output = output;
|
||
existing.timestamp = wrongItem.timestamp;
|
||
} else {
|
||
wrongAnswers.push(wrongItem);
|
||
}
|
||
|
||
localStorage.setItem('linux_sandbox_wrong', JSON.stringify(wrongAnswers));
|
||
}
|
||
|
||
function showWrongAnswers() {
|
||
if (wrongAnswers.length === 0) {
|
||
alert('🎉 还没有错题记录!继续保持!');
|
||
return;
|
||
}
|
||
|
||
let html = '<h3>📚 错题本</h3><ul>';
|
||
wrongAnswers.forEach(w => {
|
||
const task = findTaskById(w.taskId);
|
||
const title = task ? task.title : w.taskId;
|
||
html += `<li style="margin:10px 0;padding:10px;background:#fff3e0;border-radius:4px;">
|
||
<strong>${title}</strong> (错误 ${w.count} 次)<br>
|
||
<code>${w.cmd}</code><br>
|
||
<small>${new Date(w.timestamp).toLocaleString()}</small>
|
||
</li>`;
|
||
});
|
||
html += '</ul>';
|
||
|
||
// 创建弹窗显示
|
||
const modal = document.createElement('div');
|
||
modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:1000;display:flex;align-items:center;justify-content:center;';
|
||
modal.innerHTML = `<div style="background:white;padding:20px;border-radius:8px;max-width:600px;max-height:80vh;overflow:auto;">${html}<br><button onclick="this.parentElement.parentElement.remove()" style="padding:10px 20px;background:#4caf50;color:white;border:none;border-radius:4px;cursor:pointer;">关闭</button></div>`;
|
||
document.body.appendChild(modal);
|
||
}
|
||
|
||
function clearWrongAnswers() {
|
||
if (confirm('确定要清空所有错题记录吗?')) {
|
||
wrongAnswers = [];
|
||
localStorage.removeItem('linux_sandbox_wrong');
|
||
alert('✅ 错题记录已清空');
|
||
}
|
||
}
|
||
|
||
// 立即加载课程数据
|
||
document.addEventListener('DOMContentLoaded', loadCourseData);
|
||
</script>
|
||
</body>
|
||
</html>
|