Files
linux-practice/index.html.bak.1772815479
likingcode 5686831d9a feat: Linux练习平台
- Web界面Linux命令练习
- Python后端 + sandbox安全沙箱
- 课程和任务管理
2026-03-07 05:43:51 +00:00

645 lines
25 KiB
Plaintext
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>
<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>